Finishing a 2D Minimalist Platformer With Game Maker 8.1 to GameMaker (Studio 2) And Discussion
Note: This is actually a part Postmortem, Tech Discussion, and Game Design article with some tutorials for coding in GML in between. GameMaker (Studio 2) will be referred to as GM(S2) for typing. Logic as referred to in the post simply means the flow of your code or a set of steps. Please bear with me as this is a pretty long post, and it could get technical in some areas.
Background
This game was a way for me to refine my programming skills and see how well I can make a game on my own. This game has evolved from the custom loading bars of Game Maker 8.x to the non-existent loading bars, cameras, and layers of GameMaker (Studio 2), and I'm pretty happy with how it turned out.
This game would have been made in a much shorter time frame, but there was one major obstacle (other than college work): daily engine issues...
*** For those issues, I am just an outlier/exception, and I really like the engines regardless!!!! ;) ***
General
The name of the game is “Freedom” and the game is about a struggle. Set in a minimalist world loosely based on the 1860s, you explore and witness a story of suffering and a struggle. I can describe it as a 2D platformer that can go 2.5D at times. It is the result of layers and cameras being supported in GM(S2), and I intended to push it to the limits!
The game supports a gamepad and a keyboard, and the keyboard can be remapped if you want. The art style is intentionally minimalist, not because I lack drawing skills (which may be a coincidence).
Organization
I have a game design document, several in-game notes, and many notes all over the place for how I want the game to look. I also have a scheme for prefixes and organizing things in most cases as well. Having a prefix like “spr_” or “S_” or “s” in front of a name of a resource tells you what you expect the asset to be and prevents GM(S2) from giving you errors for not having unique names.
I usually organized things, so I do not have problems accessing the resources I need, even though there are many hundreds of objects (thousands of assets in general) for me to go through, but that is something that you have to plan from the beginning as inconsistent names will become frustrating further into the project.
Localization
I used JSON for dealing with files, especially for translations if that was an option in the future. Keeping text separate in an external file allows for the application size to be smaller, and if you are using different files for localization, then it would be a smaller hit on memory usage. If you had a typo or some error with the text, you can just resend a text file to players instead of recompiling the whole game, saving lots of time and preventing extra bugs in the meantime. Java-Script Object Notation is a human-readable and machine-readable file type, usually used for data exchange. They are typically files that you receive when requesting information from websites, but they can be used in GM(S2) and other places as well. It is probably good practice to get your text from an external file instead of hard-coding it as managing the project becomes a whole lot more complex the more languages you add, as well as adding to the size of the application itself, especially for text-heavy games. How I implemented it was by creating a JSON file, having all of my strings placed, usually in this format:
{ "english": { "my_string": "hi" } }
Then I would save it as a text file, save it as an included file in GM(S2), then run a script at the start of the game to open the file, and read in all the JSON (there is the function json_parse: to do this relatively easily), close the file, and convert it into a global ds_map (I used a different function, but with json_parse you need a struct or array instead) which is used throughout the game and destroyed at the end of the game. In an object that needs a string, the string is loaded from the ds_map and given to the object so it can be drawn to the screen as usual. Now, it would be quite a bit to type out the string loading functions and transfer all the strings one by one. Thankfully, I knew enough scripting from college to create a couple of scripts to automate most of the repetitiveness of the load calls, and that saved a lot of time as there are hundreds of death messages to call and many times more lines of dialogue.
Sound
For sounds and music, I mostly used LMMS and MuseScore. MuseScore is useful for making arrangements of instruments, useful when making music sheets and passing them to others, and I used LMMS basically as a free alternative to FL Studio.
For playing the sounds and music in-game, I have a persistent object and use different scripts to feed the object which music to play when in a certain room. I also have global volume variables that can be 0-1 so that I can use audio_sound_gain to have control over the sound volume and allow users to change it later. The function takes the gain in a decimal percentage, so I use the variable there, but multiply the global variable by 10 for use in menus. Here is a sample of a somewhat simple function anyone can use to play sounds with a specific volume:
function play_sound(sound, priority, looping) { var volume = global.volume; if (ceil(volume) == 0.0) { exit; } var sound_id = audio_play_sound(sound, priority, looping); audio_sound_gain(sound_id, volume, 0); // Set volume }
It uses the same parameters as audio_play_sound, which is the function that GM(S2) uses to play audio normally, so I did not include the volume as a parameter. I set a local variable as the global sound effect volume to shorten access time (as accessing globals can be slow when doing this a lot of times), and because there is a chance that the audio can be true at decimal volume levels, I use ceil to prevent that when checking. GM(S2) uses floating-point values, and numbers are not precise depending on the target, so this helps keep things fixed. When there is no volume, exit the script, otherwise play the sound at that volume. The last argument in the audio_sound_gain function is to prevent fading sounds and have sounds play instantly.
Jumping
This game includes a lot of jumps, including variable jumps (holding the key to jump higher), wall jumps, and air jumps. Wall jumping is different from other games as you need to have another wall to regain control and kick off the wall again. The thought behind this is that I wanted it to be as simple as possible to jump on walls, so all you have to do is move onto a wall, do not touch any inputs afterward, and press jump when seeing the debris come off the wall. With this, you do not have to worry about anything other than timing your jumps. Air jumping, one of the main points in the game, is an incredibly lazy version of coyote time. Coyote time is the point where the game allows you a couple of seconds "buffer" after you have left a ledge, as players find it unforgiving to press the button when they are about to leave the ledge and press the jump input key but don't see a jump animation.
Air Jumping
- In the create event, you need to have a timer for the buffer set to how long in frames you want to accept input.
- Then, in the step event, check if you are just off the ground (probably some variable named off_ground that checks if you are "not" meeting the ground), then decrement the timer or set an alarm. When the timer or alarm goes off, no longer accept input unless the player collides with the ground again, where you reset the input and the buffer.
The difference between air jumping and coyote time is that I don't use a buffer with the ground check at the same time (although you can just replicate the effect by setting the buffer to a really high number, but that risks making the player lose control anyway when the character is falling down in a long room)
Object/Tile Collisions
For collisions, there is usually the option to have the player collide with all objects (really slow) or collide with tiles (fast). I took a hybrid approach by using tiles and making tiny and long objects to collide with because the game does not easily follow a grid. A general optimization could be to have the collision code in the object with the least number of instances in the room (have collision code in the player instead of the wall).
Extras
While making the death messages for the game, I realized there was a bug in choosing the localized strings in which an empty string would sometimes show up on the death screen, even when all the strings were there. To remedy this (but also taking into account that a translation may not be completed and also cause this to show up) is that I have a certain time-to-live value, which is a loop that I use to reshuffle a new message to display if the previous one was empty.
Delta time was also used in some of the heavier-to-perform parts of the game as I wanted things to look consistent among different devices.
Porting
I have made an x86 build for compatibility when distributing the game, but I have the Windows x64 build on my website. I wanted to do something special for those who have ARM devices as well, so the game is compatible with ARM versions as well (Windows uses an emulation layer for its ARM devices that runs both x86 and x64 apps reasonably well so I didn't use an ARM Windows device (and I realized that ARM Windows were included in the sold-separately UWP license instead of my permanent desktop license anyway), but Mac and Linux are native builds). I used my Raspberry Pi 4 for porting the game over to Linux for the ARM builds. I do believe that these are all the ports that I will be able to complete at the moment.
Resolution
Start planning what resolution you want from the start of the project. The resolution in a general sense is the number of pixels packed on a display monitor, typically measured horizontally and vertically. The aspect ratio is the number you get from dividing the width of the screen by the height of that area. This will be the determining factor for if you will have blurry or stretched pixel art / HD art, not only when testing but especially on your players' devices. A very long time ago I did things manually by setting the camera of each room to the settings I wanted (I had a camera set up with code, but the room editor was able to change that camera, but you should only ever use code, not mix both unless necessary). As I have a 3:2 resolution for the laptop I am making this game with, I did not experience any problems at the time. Later, I ended up testing on a resolution of 1920 by 1080, the most common resolution, and all the art was skewed. I then had to create a flexible system that resized the application surface, the GUI, and the view based on the resolution that I set before (Dividing 1920 and 1080 by 4 or 6 for pixel art, depending on your needs) and taking into account of the display the game is running on. The GM(S2) camera page are an excellent resource in case you want to create a display object a lot faster than I did ;).
Compiling
I used VM (virtual machine, which comes with GM(S2) out-of-the-box) builds for testing and quickly iterating, but all of my released builds (the ones offering YYC anyway) use the YoYo Compiler for speed gains (and the added benefit of not being flagged as a virus by my anti-virus software). Using YYC is not a problem for me since I write code in a C/JavaScript-like fashion anyway (Using parentheses, braces, == and =, and semicolons where needed), so I did not have the problem where YYC would scream for not having some code formatted in the right place. For CPU-intensive games, there are a lot of performance gains to be made if you code things in a certain way, such as using a lot of local variables or creating arrays in reverse.
Git
I set up the source control for the game before the 2.3 updates that took those controls away and made users rely on external tools for source control. My options still work, but it could be a bit more involved if I wanted to start all over again. How I used to set it up was by creating an empty repository on a source control website (GitHub, GitLab, GitBucket), then providing a link to that repository to GM(S2) after enabling source control in the Game Options. Now, you may have to install and lead GM(S2) to applications like a Git.exe file and the application that you want to use for managing conflicts (useful for teams of more than one person). Here is how I completed it:
Git Information
For using GM(S2) with Git, create a .gitattributes file at the base of your project directory (next to the icon to open your project in your file explorer), and add this code to that file:
*.yy linguist-language=GML
Then commit and push the changes to GitHub, and YACC won't be listed anymore.
3D
I dabbled a bit with the 3D functions as well. The camera in the game by itself is a hybrid 2D-3D camera, that I control using triggers I set in the room. When the trigger is activated by the player, it changes the camera's perspective (2D to 3D-ish 2.5D) and does other things like changing the Field of View (How far the player is). The 3D cubes are able to exist in the 2D space as a result, and these are basically using the new GPU control functions.
Challenges
One of the hardest parts of making the game, apart from trying to make the engine/game not crash on me, was the customization. I knew that not a lot of games would even touch this field (now I understand why), but I thought it would make my game unique as a result. The problem is that to change colors, the easiest way to not have spaghetti code or tons of assets is to use a shader. One small problem is that trying to deal with comparing RGB decimal values went above my head, as you have to program the shader in a specific way for it to give you any color other than gray. After that, I moved on to HS* values because it allowed me to have more colors and did not only give me gray. The really “interesting” thing is that GM(S2) has two different versions of HS*, and you don't know which one you will need unless you experimented around with it before. In the image editor, HS* values go up to 360, but in code, it can either be 360 or 255, which gets confusing quickly. Anyway, for anyone who wants to avoid that pain, the magic formula is this: (color_to_convert/255)*360. For changing hats, it's two arrays or a 2D array for the items themselves and the descriptions. I have an active item variable that I take information from to display above the player.
Programming
With this section, I will try not to assume anything as I have read lots of forum pages where I am scratching my head even after the first sentence, so I will try to explain things as I understand them. Another thing is that the below examples will use GML (Code) as I have never used DND (GML Visual now) in GM(S2).
You can use F1 to access the manual, or middle-click on a function to directly access that function.
Preliminary
You can create all types of assets, but one of the most important is objects. Objects are basically like “blueprints” for a house, and you put instances (the finished house) in a room, which is where all the interesting things occur. You can place several instances in a room, or none at all. Each instance has an ID or identifier that you can manipulate through the room editor or through code.
For those who have used room_speed for their projects, it is now only used for legacy purposes, and it is better to use the game control functions like game_get_speed to determine the number of game frames to run per second (users would say to set the game speed to 60 for 60 frames per second as it is smoother)
Timelines are something that I have not used much in anything, but they can take an amount of time in frames (a fraction of the game speed time) and perform something during that time. If there is a time that is slightly off, or you change the game speed, then you may have to redo all of them.
Directions in GM(S2) take some getting used to, but 0° is right, 90° is up, 180° is left and 270° is down.
True is one or "on" but can sometimes be extended to any non-zero value, while false is zero or "off".
Sprite_index is the sprite name itself, while image_index is the sub-image of that sprite. A sub-image is a frame (the specific slice of the animated image) of the sprite and starts from 0.
If coming from GMS1.x, there is a "new" event known as the Clean Up event that usually handles things you need to clean up, such as particle effects, buffers, and surfaces.
Conditionals
Conditional logic such as if and else control the flow of the program's execution. Code is usually read top-down, or from the first line at the top to the last line at the bottom, but these conditional statements change that flow. In order to "check" something, like a player's health going under 0, potentially destroying them, then we check using these statements. Else is seen as a catch-all, where if you did not match anything with an if or an else if, then it reaches the else before continuing execution with the rest of the program.
For conditionals: I always see something like this:
if condition { return "value when true" } else { return "value when false" }
However, there are different and potentially cleaner (depending on who you talk to) ways to do this. There is a ternary operator that functions like this: (condition)? value when true: value when false. How I remember this is as if you are asking a question. You are asking if the condition is true, and if it is, then go over to the first value. If it is false, then reach all the way over to the second value. Some programmers believe that it helps with readability and keeping things concise, while others use normal if statements. Depending on the compiler (which is the program that “compiles” the code you write into machine code, instructions that your computer can understand), it can get converted over the same way, so I take it as a matter of taste. For the example above, there is a way that combats all the extra typing: return (condition);. This returns whatever value condition was regardless of if it's true or false.
If you were replacing a value like this:
var xx; if (condition) { xx = 0; } else { xx = 1; }
You can just do:
var xx = condition;
This works as you are using a boolean type, which is 0 (off) or 1 (on). There are also integers (whole numbers), doubles (basically decimal numbers), and strings (text like what you are reading now).
For a more complicated example:
var xx; if (condition) { xx = 89; } else { xx = 65; }
You can leave the structure like this, or you can use the ternary or even one if statement
Ternary:
var xx = (condition) ? 89 : 65;
If:
var xx = 65; if (condition) { xx = 89; }
Variables
Variables are used to store information to be referenced and used by your programs. They also provide a means of labeling data (a section of computer memory) with a symbolic name to refer to later. This is to make our jobs easier as programmers so that we do not have to remember all kinds of different values and improves readability for other programmers. The computer does not really care what you name them (except for the times you name a variable with a number or another special character first before letters) as all the variable names get stripped and substituted with the actual values during compilation/execution anyway.
Scripts (Functions)
Scripts are a nice way of making code cleaner, reusable, and much more flexible. Instead of having the same code in 10 different places, you can put all of it into a script and call it in all of those places, which saves typing, and allows you to change a value in one place instead of 10. Relatively recently GM(S2) expanded to functions so that users can use one script file to hold several functions and users can call each one individually or set them to a variable for use later. Parameters are also local variables, so you may not need the ‘argument#’ anymore.
Parameters are the variables that you create next to the name of the function itself, while arguments are what you pass into a function when calling it elsewhere.
function func(local1) { return 0; }
vs.
func(0);
“local1” in the top example is a parameter and 0 is an argument in the bottom example.
Language Features/General Information
Loops like “for”, “while”, and “until” all loop over values for an amount of time. For is usually for when you know the number of times to loop, like drawing lives to the screen. While is for an unspecified number of times, like when you want the user to play a game again, but you don't know how many times the player will play the game again (and even if you set a high number in a for loop, a hypothetical player can play the game for more than that many times and break the game). While checking the condition before continuing to loop. Until checks the condition after it has looped at least once. Repeat is specific to GML, and while it may be faster than other loops at times, I prefer to use the loop structure I use in other languages unless I need the speed gains, or I was trying to get things done quickly.
Arrays hold a number of values that can be related to each other. This can be used for things like inventories. With version 2.3, arrays are much more powerful and are more like arrays from other languages. Now, they act like arrays within arrays, which is what I used them as anyway, but the look of it is a lot better. If I didn't already use data structures, which are special types of ways to handle data in a game, then I would have just used these new arrays and structs as I do not have to worry about freeing them the same way as data structures (however, there was a bug with the deallocation of structs that was fixed in a later update, so I basically avoided that by not using them frequently). I usually use a ds_list, which is a list of values, and a ds_map, which is similar to a table. These were important as my save file loading uses some arrays and json_decode in a part of it, which converts my multidimensional arrays for the hat data into data structures, which I then had to create functions for regenerating all of the arrays.
On that note - memory leaks are essentially what happens when a certain process that's been running on your computer refuses to release the memory that it has been assigned. Sometimes the RAM (memory on your computer) doesn't free up even long after the executed program's window has been closed, and that hoarded memory can cause all sorts of problems by slowing down your machine, and in extreme instances causing it to become unresponsive or even crash (by then the operating system would take back all the memory for use again afterward). They happen when you do not clean up dynamic resources like data structures.
For built-in vars (like speed or gravity), it may be better to limit your use of them as they can lead to very strange or unpredictable bugs when you try to make more complicated projects, as far as I recall. They are also dependent on the game/room_speed, so changing that could lead to having to redo all your other variables if you wanted to change your game’s speed (30 frames to 60). Fortunately, I planned for the framerate to always be 60 frames per second at the beginning of the project, so I did not have to redo anything. Usually, experienced programmers want more control over what their code does and want to limit how much GM(S2) is controlling things behind the scenes, so they create their own variables to imitate the built-in ones. You also have more control over where you can place your code and do not have to worry about creating checks to make sure the variables are working properly. I don't believe I used the built-in movement variables for anything but very fast prototypes, which I would then replace with my own variables, but I can definitely see their uses in quick prototyping and when users just start out coding.
Surfaces are blank canvases that you can draw to. GM(S2) itself uses a surface called the application surface that it uses to put on the screen. The benefits of using your own surface are to manipulate things (could be used to draw text, destruction effects, or other special effects) and draw them to the screen. Surfaces are volatile, which means if something happens to the game window (minimized or someone switches to a different application), the surface could be destroyed, which causes its own set of bugs. Surfaces are also not placed on the screen as usual but are relative to what you are using (usually a camera).
Shaders are two-part programs that run directly on the GPU (graphics card), making it very fast since the GPU is doing the work. A GPU does one thing very quickly, so not only is this great for effects, but it will be very fast for processing as well as long as you are using them correctly. If you have a view (going to the room editor, enabling views, and ticking the “Visible” checkbox on one of the views), it is best to set that shader to the view and not the whole room to prevent wasted processing time. The full shader is composed of a vertex shader program (the one a lot of people don’t touch), and a fragment/pixel shader (the one that is more used). To use them in an object:
shader_set(firstShader); // Drawing code shader_reset();
This tells GM(S2) to allow the shader to run, then do any of the drawing code that you want to be affected by the shader, then reset the drawing of the shader.
You can also use uniforms, which can be variables you pass to a shader to manipulate what the shader draws.
If you wanted to find a shader somewhere, make sure it uses the GLSL ES format so that it is compatible with all targets just in case you wanted to port your game in the future. I used shaders quite a bit for the death animation and the colors for the hats you collect.
Sequences are a visual way to do cutscenes and flashy effects without having to constantly code and rerun the entire project to see if an effect is playing correctly. I, unfortunately, did not use this frequently, but I did remake the bridge at the start of the game to use this, so that was my attempt at using it. You can also code sequences if you want using structs.
Animation Curves go hand in hand with sequences, and they allow for really nice effects, especially bouncing, or easing between transitions.
Filters are basically built-in shaders that accomplish many of the common effects game developers try to achieve, such as screen shakes or desert effects, or underwater scenes.
Structs, or structures, are containers of information. These are different from full objects as they are not heavily processed like full objects. They can contain variables, values, and even other structures. You originally had to make things like inventories with arrays or other data structures, but another option is to just use structs as they are quite flexible and allow for constructors, which is a way to initialize values when a struct instance is created. It also goes hand-in-hand with sequences, as sequences communicate with structs to manipulate data.
Particles are effects that can be used to really spice up games. They are not processed in the same way as objects, so you can potentially put "tons" without any slowdowns (compared to objects). You must free them when not using them, and you can use the create_effect functions, or create your own particles using the built-in system. I typically have all the particle definitions in a script and free the particles I don’t need to use throughout the game. Sometimes emitters may be useful to your needs, where you need a region filled with particles, otherwise, the regular part_particles_create will be needed. All the particles except for the create_effect ones have to be defined before use!
Gamepads/Controllers
This was interesting as the analog stick uses continuous values, but the buttons react as keyboard presses would. In earlier versions of GM, it was necessary to check if the gamepad was connected in the step event, but now you should not really be putting the gamepad check code into the step event. With the 2.3 update and the tutorials they have now, I was able to find a way to check for the gamepad without the step event. Asynchronous activities allow for different events to happen in the background. This means that outside of the flow of the game code itself, GM(S2) will update the access to gamepads once one is connected, so you do not have to waste processing time checking for it yourself. It is an event under Asynchronous->Async-System, then using this as a base:
switch (async_load[? "event_type"]) // Go through the async_load map to see which event has been triggered { case "gamepad discovered": // Game pad has been discovered var pad = async_load[? "pad_index"]; // Get the pad index value from the async_load map // Set the "deadzone" for the axis // Set the "threshold" for the triggers break; case "gamepad lost": // Gamepad has been removed or otherwise disabled var pad = async_load[? "pad_index"]; // Get the pad index break; }
Deadzones are how far you have to press or move the analog sticks before the input is actually registered, useful as gamepads can drift and players may not want that to affect their gameplay.
Website
Kind of unrelated to GM(S2) tips, but if you ever decide to make a website, or pay someone else to do it, be sure to plan everything out beforehand. Having a wireframe (A diagram for how each part of the site would look, typically in shades of gray to not focus too much on colors) saves time when programming as you already know how everything looks. The colors and flashy effects come after. You should also decide how you want to display your email. E-mail addresses can be scraped from websites using a variety of tools hackers have at their disposal, and in case you do not want spam to fill up your mailbox, there are different solutions you can take. The easiest is to make a contact form, as that is not vulnerable to all types of spam. The second easiest way to make it hard for scammers is to make an image, but that annoys potential players as they can't click a link. There are also some other coding-related solutions, but the above two are quite common.
The website is here, made with standard HTML, CSS, and JS. I have a contact form and am using an advanced spam filter for the email link.
General Tips (In No Order):
- During the time that GM/GMS would hang or just exit, I would in the meantime work on a game design document to make sure that I have all of my ideas on paper and do not spend extra time programming things that I would not need. It could be useful for anyone as well, with plenty of templates online. This helps to prevent those situations where developers may be coding for days on a mechanic that doesn't end up fitting with their game.
- Do not be afraid of game errors! The engine is not telling you that you are a bad programmer or anything, it is simply saying that some logic is not structured in a way that GM(S2) would like, or a variable was not set before being used.
- There are runtime errors (errors when running the game) and compile errors (errors you get at the time of building the game)
- When naming the sprite from an external image editor, use the _strip# (# is the number of frames or pictures in the sprite) at the end of the image name, and GM(S2) will "try" to do the stripping for you. This only works if there is an equal amount of space between each sprite, otherwise, you will have to do it manually.
- There is an inspector on the asset browser to get a quick view of assets without clicking on them.
- Use Ctrl/Cmd when using the pencil in the image editor to quickly select a color on the sprite, useful when dealing with palettes.
- My first language was not GML, so I do not have the same experience as someone who only knows GML and then moves on to other languages. I would say to at least learn the syntax and rules of some C-like language at a beginner level (C++ is my favorite), especially as it is easy to have messy code without knowing how to structure code beforehand. I'm sure that a majority of issues that fellow programmers have (= for comparisons instead of ==) would be solved by looking at how a similar language works. I would recommend learning how to do assignments, loops, conditionals, and creating variables to really appreciate how forgiving GM(S2) is (the VM build anyway) and how much it does for you.
- Do not micro-optimize or try over-optimizing code before there is an actual issue, especially because you can potentially find a better solution during development, and you would just end up removing the code that you spent so much time trying to optimize in the first place. There are a bunch of ways of optimizing a solution and making it more efficient, but there must be a balance between optimization and actually working on content/finishing the game. I optimized everything at the end of development as I knew that would be the final code that I would be using and would not be making significant changes unless absolutely necessary.
- External files, surfaces, resources created with code (Like sprite_add) or structs, ds_* data structures, surfaces, anything with particles except for the built-in effect_create_* functions, code-created cameras, buffers, and physics fixtures can all cause memory leaks and need to be destroyed or freed or they will slow down your game! Technically, you have to free the arrays you are not using by setting them to a number like -1, but that is helpful for games where arrays take up a decent portion of memory or you want to use as little of the processor as possible.
- Imposter Syndrome is something that we can all deal with from time to time. It's not particularly something that someone overcomes but remember that the important thing is that you are unique and can always bring something different to the table with each game you make. A different perspective or a different take on a well-established genre or making a completely new one is always exciting!
- Camel Case is the style of naming variables with a capital letter for each consecutive work. myCode is an example. Snake Case is the styling that uses underscores between words. my_code is an example of this. It doesn't technically matter which one you use (the compiler will strip all variables, enums, and all other names and put in the values themselves) but stay consistent throughout the project.
- Included files are the files that you package with the game when it is compiled. Save files are saved in a separate location.
- Features are big tasks that you need to divide into smaller and easily manageable pieces. It is easy to get overwhelmed thinking about how to make a huge game with tons of mechanics, but it is most helpful to break them down into chunks that you can wrap your head around.
Other Things:
- I learned quite a bit about the inner workings of GameMaker using the forums. For the most part, I can see that the community is really caring and supportive and a lot of the older articles that I have to "search" for really deep in search engines can provide a solution or a really good start to a problem that I was facing. I also kept the previous versions of GameMaker during development because a lot of different objects that I needed were either GM8 files or GMS files, and I needed to convert them for use with GMS to test if they work and clean them up before transferring them to GM(S2).
- This is a completely free game (ad-free and everything) because of a previous agreement and because I really do not like having ads in the games I play. All I want users to do is play the game. There is no stealing of private information, no going through your private files, no making you pay money ever, and no places to waste your time watching ads that do not even apply to the actual game!
Finishing any game takes a massive amount of work and dedication, regardless of how big or beautiful it is. I am always in awe whenever someone shows their game, and my first instinct is to figure out how they were able to accomplish it and add it in some way to my projects. No matter if it is successful or not commercially (if it is a commercial game), everyone should be proud of what they have made! I have had a ton of projects throughout the years, usually small features or full features built out, but this is the first game where I was able to combine everything I learned and have all the code play nicely with each other. I know it's tough to stick with a project, even over years, but finally releasing it feels so great (unless everyone finds the bugs that you didn't want them to know about).
If you have made it to the bottom, then thank you so much for your time! These are just a couple of things that I have dealt with when creating the game, and I just wanted to share them in case they helped someone out. Who knows, maybe someone reading this could make or could even be making the next hit game. Anyway, if you have any other questions, I'll probably be around to respond. Apart from that, thank you, and have a nice rest of your day!
Get Freedom
Freedom
Break free from boredom!
Status | Released |
Author | eleferret |
Genre | Platformer |
Tags | 2D, Cats, Controller, Dogs, GameMaker, Indie, Minimalist, Pixel Art, Short, Singleplayer |
Languages | English |
Accessibility | Configurable controls |
More posts
- Version B Release!May 26, 2023
- Soundtrack ReleaseJan 14, 2023
- Freedom - ReflectionAug 24, 2022
- Freedom - Ghost DashAug 01, 2022
- Freedom - ParallaxJul 11, 2022
- Freedom - Best PartsJun 20, 2022
- Version A.1 Release!May 27, 2022
- Version A Release!May 20, 2022
Leave a comment
Log in with itch.io to leave a comment.