Game Maker is a great tool; it is especially suited for rapid development and small projects. However, as a project becomes bigger, it becomes more difficult to find things, easier to break it, and generally harder to work on. This is of course true for any production environment, and there are many things you can do to tame the beast of scale. Here are 60 things to make Game Maker projects more maintainable.
Use postfixes to prevent name clashes
It is possible (but incorrect) to give, for example, an object and sprite the same name. To prevent this from happening accidentally, use postfixes to distinguish different types of game-elements (sprites, sounds, objects, rooms, and so on). Use postfixes instead of prefixes – this makes it easier to find specific objects in the object tree, and to read your code.
The following Game Maker file illustrates how name clashes can lead to bugs:
Use folders to structure resources
Generally, the object folder structure should follow the inheritance hierarchy, with further separation as necessary. Other resource folders should reflect the groups of the objects to which they belong, or be subdivided by their function.
Use a Development Standards Document
Compile and work from a development standards document to promote consistency across projects and between developers. At the very least, this document should define:
- your naming convention;
- your folder structure;
- your layering convention (see below);
- positions of controllers; and
- icon code (if you use one – see below).
Do not make the embedded drawing tool part of the pipeline
…if your main drawing tool is an external tool. This prevents every change from becoming an error-prone two-step process.
Use dynamic content
Loading images from files, creating sprites and objects dynamically, and constructing levels programmatically makes it a lot easier to change assets, since the Game Maker file doesn’t need to change whenever the asset changes.
Design art around a grid
Use a grid, and design objects for that grid to simplify world construction and object placement.
Draw your GUI in a separate room
…and overlay this room over every level.
Use a widget-and-style approach for complicated GUIs
For complicated GUIs, it is worth defining generic widgets (such as buttons or menus) that can be configured using parameters. Keep style elements (colour, font, borders) in separate objects. This makes it easier to change styles in one place, instead of in every widget. It also makes it easier to make the GUI skinnable.
Do as much of the GUI layout programmatically as is possible
Laying out a complicated GUI can be extremely labour intensive, so that changes to the GUI layout can become unmanageable. Using layout functions can greatly improve the ease with which the GUI can be extended or otherwise modified.
Make extensive use of Game Maker extensions
Using extensions provide several advantages:
- extensions make it possible to reuse functions more effectively;
- extensions make it easier for more people to work together (people making extensions needn’t modify the main Game Maker file); and
- extensions can be put under separate version control, and so reduce the impact of a roll-back.
Every script function you define should be considered for implementation as a DnD action in an extension. It is useful to have a general usage extension, that you can use across projects, as well as a project-specific extension. Remember to check whether somebody has not already implemented a particular function in an extension!
Document extension actions properly
Don’t leave any of the fields (such as Description, List Text, and Hint Text) in the extension maker empty, and take care to make the List Text and Hint Text display as much field information as possible (by using the @keywords – see the Extension Maker Help Documentation).
Spend time making (or searching for) proper icons for your extension actions
Forgetting what an action does kills maintainability; a descriptive icon will significantly reduce this.
Always define a script function for every DnD action
This way it is easier to unit-test features (see below), and possible to use the functionality from a script.
Actions and Scripts
Learn how to use the merge facility effectively to facilitate for teamwork
This is not Game Maker’s safest feature: there are many pitfalls, for example:
- be careful for name clashes; and
- all elements get new IDs (so be careful if you use hard=coded IDs).
However, with careful planning, this feature can help several developers can work together on a project. for example, every developer works on his / her own file, which is merged into a final game.
Avoid complicated branch logic in the GUI action editor
Complicated branch statements are hard to read in the DnD interface, and editing these constructs is error-prone. Put complicated branch logic in a script.
Avoid assigning too many variables in the GUI action editor
Rather use a script. This way, tweaking the values of several variables becomes a lot easier.
Never use the Execute Code action
Always use the Run Script action, and put the code in a separate script. This way the code is easier to find, reuse, and use in an extension.
Always use blocks to demarcate if-then-else constructs in the DnD interface
…even when a statement block is only one line. This makes it much more convenient to add stements to the block in future, and reduces the risk of introducing a bug because of incorrect blocks.
Make use of named global constants
Using named constants makes code easier to read, less error-prone, and easier to change the value. If you define them in the Global Game Settings dialog, they appear highlighted in your code.
Use expressions as constant values
You can use expressions when you define constants (in the Game Settings dialog). For instance, to define a colour, you can set the value as make_color(255, 128, 64).
Initialise the variables of an object in one place
…either in a single file, or as DnD actions in the Create event of that object. It might be better to do it in a file if you need to document usage.
Declare global variables in one file only
This serve as an easy way to see all the global variables, and is a good place to document their usage.
Always assign function arguments to named variables
Notice that you can immediately deduce what the arguments should be, and that this function returns an area.
Use the with-statement as a for-each loop
The with-statement can be used as a for-each loop over the instances of an object (where the variable
self serves as the loop counter).
The following two examples illustrate this:
Use sprite frames to represent object states,
…rather than using separate sprites or objects per state.
… and refactor aggressively throughout development. See The Power of Inheritance.
Use tiles instead of sprites for the visual appearance of objects
See for example http://www.youtube.com/watch?v=zM3iQbzM4C4.
Use unique sprites even for invisible and control objects
This makes it easier to place these objects in rooms, and identify them properly. If you cannot spend time making proper visually descriptive icons, use a coding system (based on colour, shape and text).
Avoid creation order dependence…
Creation order dependence is hard to remember, and thus something that is easy to break. Creation order dependence can often be eliminated by removing initialisation code, and making sure that the update function will do the work the first time it is called.
…Or use spawning to control order dependence
When creation order dependence is unavoidable, let objects spawn those objects which should be created after them. This way you control the creation order, but also make it explicit (and thus, not necessary to remember).
Use parameterised objects instead of separate objects where possible…
For example, when the only difference between two objects is their sprites, you could do better to make them the same object, and control the sprite that is used by a parameter. The fewer objects you have, the easier it is to restructure or otherwise change the objects.
…But use separate objects to avoid type checking
If you have to do type checking to distinguish between objects to differentiate behaviour, you should use separate objects. This is especially true when you have to perform group operations.
Use Game Maker objects to mimic record data types
For example, you might create an Abilities object that contains variables for strength and magic power. Every character in the game can make its own Abilities object, which allows you to write code like this:
Of course, nowhere in the game the user will actually see an Abilities object, nor will it ever be put in a room (as would monsters, for example).
Use object composition
Object composition is a great way to reuse objects, and is a great substitute for multiple inheritance (which is not available in Game Maker). For example, a monster can be composed of its static attributes (strength, aggressiveness), its current state (health, anger) and its items (weapons and potions).
Avoid Using the Test Chance action
Because it is only possible to express a limited set of probabilities with a single Test Chance action, it is not easy to tweak it. Rather use a Test Expression action, with the expression something like:
random(100) <= 60
Choose a layering convention
Choose it up-front, document it, and keep to it! For example, choose the layers for the background, static elements, dynamic elements, main character, and effects (such as particles and glows).
Use the controller idiom
Controllers are used to group together logic that does not belong to an in-game-object (such as the player, a monster, or item). Typically, controllers are used to maintain room state and global game state, and to draw interface elements.
Choose a standard position on screen for controllers
This makes them easy to find, and avoids duplication.
Standardise idioms that are relevant to your game
For example, I always use the init-update-draw idiom for each interesting object (i.e. init_monster, update_monster, draw_monster). Recognise the patterns in your own games, document them, and follow these as standards.
Avoid putting code in the room initialisation section
It is usually better to put initialisation code in a controller, and it is necessary if you want the code to execute before other objects are created. It is easy to forget about code in the room initialisation section, which can result in code duplication and hard-to-track bugs.
Use timelines with care
Step numbers can only be hard-coded values. Thus, tweaking a set of timelines can become a maintenance nightmare if their step numbers are somehow related to each other or other game parameters. It might be a better idea to use a script and counter approach, which will allow you to define these relationships (for example, the growling time of a monster is always proportional to it’s size).
Use a script file to document important global issues, such as script entry points, general architecture of the game, and so on.
Design your game around fixed unit time intervals
For example, everything in the game happens in intervals of multiples of ten steps. Using this scheme makes constructing time-dependent events easier for the same reason that using a grid make level-construction easier.
Never use frame rate to change simulation speed
This can lead to ugly glitches in animation. Simulation speed is better controlled with a time scale variable independent of the frame rate.
Always animate sprites at the same frame rate
It is just easier not to have to think about this for every object. And if you need to tweak the frame rates of sprites individually, you can easily find yourself in a maintenance nightmare.
Do not use any text in your graphics
When text is part of a graphic, it is very hard to change it. Also, it means that you need a new graphic for each piece of text, which puts a bottleneck on the amount you can produce. Drawing text with Game Maker is a pain, but it will save a lot of work in the long run, and will make it easier to make the game data-driven and scale gracefully.
Choose a fixed place to call the parent event
You should always call the parent event either first, or last, and do it consistently throughout (the game, or even better – all your games). This reduces the chance of having hard-to-debug logic as a result of calling the parent event, especially along complicated hierarchies.
Never rely on the order of events for something to work
Even though the order is well defined, and can be expected to stay the same for future releases of Game Maker, it is hard to remember this order, and hard to remember that the order for a piece of logic is important. Therefor mistakes are easily made. This is especially true in teamwork, where reliance on event order might not be communicated properly.
Avoid using custom events when possible…
Custom events are not named (you have to use indices to refer to them), and hence it is hard to remember which event does what.
…But use custom events instead of global states
Custom events can be used to broadcast changes in game conditions. This not only eliminates global variables, but also the if-statements that are necessary to check the global state at every step.
Testing and Debugging
Define an assert function, and use it to confirm game conditions. Using asserts make it much easier to make changes safely.
Always make sure that the number of enums is correct
When you iterate over enums, you usually have a constant that holds the number of enums in a set. It is important to assert that this number is correct (it is easy to forget to update this number when inserting or deleting enums). Do it in a place where it is easy to see how many there should be. Below is an example.
Always check that functions do not return noone
When searching for the nearest instance of an object, for example, make sure that the function does not return noone (even when you think it is impossible). At the very least, display a helpful message. It is common that your assumption of impossibility might be violated by future changes.
Use unit tests
Write unit tests for all your script functions. You can also set up unit tests in special test rooms, that will test object behaviour in specific preset conditions. When you are protected with unit tests, you make make changes boldly. Log success and failures to a file.
Make unit tests runnable with command line parameter
This way, unit testing can be automated. Use the log file to detect failures. You can use the
parameter_string functions to get the command line parameters fed to the game.
Use a Game Maker file to put in unit tests for the general purpose library
This special file will not only help detect bugs in your extensions, but also serve as a usage document.
Implement a debug menu screen
Implement a screen with special debug functionality, and only display this when the game is run under a special mode. The debug screen should have ways to:
- run any level;
- run scratchpad levels (see below); and
- run unit tests.
Implement a test room for a scratch pad
It is often necessary to test a new feature in a room that is already part of the game. Resist the temptation to test new features in existing rooms. It is easy to mess up a room when implementing a new, untested feature, and it can be hard to remove this feature if it doesn’t work out. Rather, have a minimal room ready that is fully integrated into the game, and use it as a scratchpad to test new features.
Implement debug drawing mode
In many cases your game can be too complex to rely on logging or inspecting variables one by one to perform adequate debugging. Drawing extra information with instances, and using colours and other graphical elements effectively, can make it a lot easier to spot errors. This technique is especially useful for AI and simulation.
It is not only a useful debugging aid, but also guides the design (the technical design – not the game design!) to be more modular and flexible. A cheat (for example, a God-mode) that is hard to implement, can indicate a problem in the design (for instance, deductions to health is made in several objects, instead of in a single one or just a few).
Use a global game options to switch on and off some features
For example, this can be used to display debug info, or skip some eye-candy to increase load speeds. Do not chaotically comment in and out pieces of code or action logic to achieve this effect: it is error prone in larger projects and in teams.