Post news RSS Making Freedom!

Making Freedom was a way for me to refine my programming skills and see how well I can make a game on my own. Freedom 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 will be a discussion of some of the more technical aspects of making the game, along with a general thought process that can be applied to other engines.

Posted by on

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.


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).


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.


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.


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.


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

  1. In the create event, you need to have a timer for the buffer set to how long in frames you want to accept input.
  2. 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 high number, but that risks making the player lose control anyway when the character is falling 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).


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.


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.


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 is an excellent resource in case you want to create a display object a lot faster than I did ;).


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 (YYC) 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.


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.


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 can exist in the 2D space as a result, and these are using the new GPU control functions.


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.


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 great rest of your day!

Available at:,,, and the official website!

Any comments are appreciated!

Some photos:


Post a comment
Sign in or join with:

Only registered members can share their thoughts. So come on! Join the community today (totally free - or sign in with your social account on the right) and join in the conversation.