Tips for Construct 3 developers

Construct 3 project organization tips

I came up with / yoinked some practices I usually follow when working on Construct 3 projects. Warning: opinionated content. I'm not saying these are "best practices" in general, but maybe other C3 gamedevs might some of them useful too, so here goes.

Sheet organization

I usually have 4 folders in every project: Actions, Events, Functions and Values.

The Actions folder contains sheets that have custom actions for a single object type or family. Sheets are named after the relevant object type or family, prefixed with the letter "a", eg. aPlayers, aEnemies, aPortals.

The Events folder contains sheets that only have certain kind of actual events (conditions + actions), with names prefixed with the letter "e". Some examples: eCreated (containing only "on X created" events), eCollisions (with only "on collision with" and "is overlapping" events), eInput (only Keyboard / Gamepad / Mouse / Touch events), eMovement (only events with conditions of movement behaviours), et cetera.

The Functions folder contains sheets that only have functions in them, their names are prefixed with the letter "f". Some examples: fUI (functions that update the user interface), fLevel (level initialization, ending, etc), fUtilities (full screen toggler function, various value returning math functions, etc), et cetera.

The Values folder contains exactly 3 sheets that only have global variables or constants in them: vState for global game state related variables, vConstants for global constants (I prefix those with "C_") and vMacros (more on that later).

A single event sheet at the root level named Main (sometimes Level) that includes all sheets in the Events folder (there's no need to include sheets from the other folders, custom actions, functions and global variables/constants are always available) and nothing else. This sheet is set as the "Event sheet" on all level layouts. For menu screens I usually use a separate layout with its own event sheet.

I suffix all sheets with emojis (pro tip for Windows devs: press Win + comma to open the emoji panel), so I can spot them quicker when I have a metric crapton of tabs open.

Tangential sidenote
Using emojis in names of C3 primitives, and project folders instead of single file (.c3p) projects means that you'll have file names on your filesystem that have emojis in them. NTFS on Windows 10 seems to work fine, but your mileage might vary on Linux or Mac. You might also have issues if you store project folders in Dropbox, as it will refuse to sync those files. Because of that, I went back to saving my projects in .c3p files and storing those in Dropbox. Dropbox saved my bacon a few times when I've accidentally saved or overwritten a project, as it keeps previous versions of files (as long as it's able to sync them to their server) which you can restore.

Using "macros"

I never use literal strings in conditions/actions when referencing animation names, layer names, effects, tags and such: instead, I use "macros", which are just constants I define in my vMacros sheet. Example: if I have a sprite that has an "Idle" and "Walking" animation, I create constants named "ANIM_Idle" and "ANIM_Walking" in the "vMacros" sheet with the values "Idle" and "Walking" respectively, and use those in conditions and actions instead of the string literals "Idle" and "Walking".

This has a couple of benefits:

  • You get autocompletion: just start typing the name of the constant wherever you want to use it's value in a condition or action and you don't have to worry about typos either.
  • You can use "Find all references" on the macro to find its occurences
  • I you decide you want to rename "Walking" to "Running", you only have to make the change in two places: in the animation editor and the global constant. I almost never need use the search & replace function because of this.

I also use three special macros which have no prefixes:

  • NOTHING, with the numeric value of -1: whenever I have variables that store UIDs and want to clear them, I set their values to NOTHING. If I want to check if they have been assigned, I compare them to NOTHING.
  • YES and NO with the respective numeric values of 1 and 0. This stems from the fact that you can't pass a variable or calculated value to a function or custom action with a boolean parameter: when calling it, you have to use a checkbox to either pass true or false, you cannot write an expression or reference a variable. Because of this I refrain from using boolean parameters in functions and custom actions, instead I use numbers just in case I ever need to do the aforementioned. And when I don't, I pass the YES or NO macro.

Picky blindspots (sorry)

A significant amount of bugs I create are related to screwing up picking conditions: either picking something I don't want to pick, picking the wrong instance or failing to pick something. Here are some suggestions and known footguns related to object instance picking.

  • If you only want to pick a single object, make sure that the conditions only pick one. If unsure, add a "pick nth instance" condition with the parameter "0".
  • If you're storing UIDs in (instance) variables, make their default value is a negative number (eg. -1) and not 0, since 0 could be a valid UID since it's zero indexed. When you clear the variable, also reset it to a negative number.
  • Don't forget that the timer behaviour's "On timer" condition might pick multiple instances if those instances reach their time in the same tick and you might need to put in an extra "For each" to run actions on all of them. This is mentioned in a big yellow warning box in the docs by the way.
  • Functions and custom actions have a similarly named option: "copy picked" and "copy all picked". If you have a custom action for an object type or family "X" and call it from an event that already picked some instances of "X", those will be passed on to the custom action even if you don't check "copy all picked" (which is for passing other non-"X" instances to the custom action).

This article was updated on 2024-04-14 16:29