-
Keep one central project note named TODO.
Use one main GameMaker note, TODO, as the central place for active goals, bugs, cleanup tasks, feature ideas, and design reminders. Update it throughout development instead of scattering important TODOs across many notes, comments, or temporary files. Code comments can still mark local work, but anything important or project-level should also be reflected in TODO.
-
Group resources by system or feature, not by resource type.
Prefer folders like GUI, Combat, Player, World, Audio, or Dialogue over broad folders like Sprites, Scripts, Objects, and Shaders. A sprite, object, script, shader, and sound that all belong to the GUI should live near each other so the whole system is easier to understand and move around.
-
Prefer fewer reusable systems over many tiny one-off systems.
When possible, use one flexible, reusable struct or controller for related behavior instead of many small specialized ones. For example, one reusable juice/effects/squish struct is usually better than separate structs for bounce, pulse, squash, shake, pop, and hover effects if those effects share the same basic behavior. Reuse and consistency are preferred over variety when variety adds tracking, resources, or maintenance cost.
-
Use one main global game controller object for bootstrapping.
Prefer one main global controller object that initializes core systems, controller structs, managers, save data, debug tools, and persistent state. Avoid creating many separate global objects for each system unless they truly need object behavior, room placement, collisions, alarms, draw events, or independent GameMaker event lifecycles. Structs are usually lighter, easier to pass around, and easier to trace.
-
Give each system a clear owner.
Every major feature should have an obvious owning object, struct, or script entry point. Avoid situations where responsibility is split across many unrelated resources. For example, GUI layout should not be partly owned by a room object, partly by a random draw script, and partly by a global variable unless there is a clear reason.
-
Keep system internals private by convention.
Organize each system so most scripts and structs are used only inside that system. Expose a small, clear public API for other systems to call. This keeps unrelated systems from reaching into internal state and makes future refactors safer.
-
Prefer consistent naming that shows ownership.
Use names that make the resource’s system obvious. For example, GUI resources should share a clear prefix, folder, or naming pattern. Avoid generic names like manager, controller, helper, or data unless the folder context makes the owner obvious.
-
Do not create new managers by default.
Only add a new manager/controller when there is a real lifetime, ownership, or coordination problem to solve. Many “manager” objects become vague dumping grounds. Prefer plain functions, structs, or data tables until a dedicated manager is clearly needed.
-
Keep rooms lightweight.
Rooms should mostly place instances and configure scene-specific data. Avoid hiding important game logic directly in room creation code or scattered room-specific instance overrides. Shared behavior should live in scripts, structs, objects, or system controllers.
-
Regularly remove dead resources and merge duplicates.
During development, periodically delete unused sprites, objects, scripts, notes, and test assets. If two systems solve the same problem in different ways, prefer merging them into one reusable version instead of keeping both indefinitely.
-
Group simple utility functions into shared script files.
Prefer grouping small, generic helper functions into one clearly named script resource instead of creating a separate script resource for every tiny function. For example, custom buffer helpers can live in group_buffers, array helpers in group_arrays, string helpers in group_strings, and math helpers in group_math. Keep these files focused by topic, and avoid turning them into random catch-all dumping grounds.
-
Use object inheritance for shared instance behavior.
Prefer clear parent-child object hierarchies when multiple objects share behavior, state, events, or lifecycle logic. For example, use a hierarchy like obj_entity -> obj_enemy -> obj_zombie instead of duplicating movement, health, collision, damage, animation, or cleanup logic across many separate objects. This makes shared behavior easier to update in one place, keeps specific objects smaller, and makes the project easier to reason about because each child object only needs to define what makes it different.
-
Maintain/use one top-level script file macros for project-wide macros and configurations.
This "macros" script should contain any/all project-wide macros like RELEASE, a true/false flag that indicates whether the project is in release mode. During development, it should be set to false, and debug-only code/objects can be conditionally compiled/included based on this flag. Avoid scattering these values across multiple scripts or objects. This centralization makes it easier to manage and update configurations as the project evolves.
-
Keep the code DRY.
Be careful not to introduce duplicate functionality; search the codebase to confirm the behavior does not already exist elsewhere. Only when you are certain no existing code provides the required capability should new functionality be added, and it must be placed in the correct domain layer rather than reinventing or shadowing existing code.
-
Do not add legacy-support or backwards-compatibility shims to the codebase.
When updating or extending the codebase, favor clean, forward-looking implementations that document breaking changes succinctly instead of maintaining compatibility code. Always prefer a single, clear way to do something rather than multiple ways that lead to confusion and maintenance overhead. If you encounter existing legacy code, refactor it to the current design rather than building around it.
-
When any unrelated changes appear in the repo, ignore them and continue. Do not discard those changes; just focus on the scope of your task(s). Other agents, processes, or human developers may be working on different parts of the codebase simultaneously, and their changes may be interleaved in commits or pull requests. As long as your specific task is completed correctly, you can safely ignore unrelated modifications made by others. Do not pause or delay or ask for confirmation to continue your work; focus on your assigned tasks and trust that other contributors will handle their respective areas appropriately.
-
When fixing lint/test errors/failures, your goal is NOT simply to perform minimal fixes that merely silence type/lint/test errors. Instead, you must drive the codebase toward a well-architected, de-duplicated, clean, DRY and maintainable design; fix the underlying issues properly. You may introduce short-term breakage if doing so enables a clearer, more correct, and more coherent long-term structure. Structural correctness overrides temporary stability.
-
Use meaningful function names over brevity. Avoid vague verbs like handle, process, or doWork. Function names should describe the gameplay system, object behavior, or engine operation they affect, e.g., spawn_enemy_wave, update_player_dash_state, apply_knockback_to_entity, draw_inventory_slot, or resolve_room_transition.