Tips for Construct 3 developers

Construct 3 project organization tips

I have a growing number of practices I usually follow when working on Construct 3 projects. Some of them I've seen elsewhere, some I came up with. Warning: opinionated content. I'm not saying these are "best practices", but you might find some of them useful.

Table of Contents

Sheet organization

I usually have 4 event sheet folders in my projects: Actions, Events, Functions and Values.

The Actions folder contains sheets prefixed with the letter "a", eg. aPlayer, aEnemies, aPortals. These sheets only contain custom actions for one object type or family only and are named after the relevant object type or family. Common custom actions include:

  • spawn: to dynamically spawn objects during runtime. This usually contains a "create object" action and then a call to the init custom action of the same object / family.
  • init: initialize variables / behaviours of the object right after it's spawned (or later if the object needs to be "reset" at any point). This is similar to _ready in Godot.
  • tick: if an object needs to be constantly "updated", I call this custom action at every tick. This is similar to _process in Godot.

The Events folder contains sheets prefixed with the letter "e". Each sheet only has events with primary conditions of a certain "category". Some examples:

  • eCreated: "on X created" events
  • eCollisions: "on collision with" and "is overlapping" events
  • eCycles: "on start of layout", "every tick" and "every X seconds" events
  • eInput: keyboard / gamepad / mouse / touch events
  • eMovement: events with conditions of movement behaviours

The Functions folder has sheets prefixed with the letter "f". These sheets only contain functions of a certain "category". Some examples:

  • fLevel: level initialization, win / lose condition handling, pause, etc
  • fAudio: functions that play/stop background music and sound effects
  • fUtilities: helper functions like toggleFullscreen, toggleLayer; maths & formatting utilities, et cetera

The Values folder has exactly 3 sheets that only contain global variables or constants in them:

  • vState: for global game state related variables
  • vConfig: global constants (I prefix those with "C_"), these are magic numbers / strings.
  • vNames: global constants that kinda act like enums - more on that later :)
  • vPrimitives: a fixed set of global constants
    • YES and NO: numbers 1 and 0, I use them instead of booleans*.
    • NOTHING: the number -1: when I use variables that store object UIDs, I use this to "reset" them or to determine if they contain a UID or not (UIDs are never -1).
    • EMPTY: an empty string, similar to NOTHING but for string variables.

* Why I don't use booleans in Construct 3
My slight beef is that I can't pass a dynamic value to a function or custom action with a boolean parameter: when calling it, I have to use a checkbox to either pass true or false, I cannot pass a result of an expression or just a variable. Because of this I refrain from using boolean parameters in functions and custom actions, instead I use numbers. Also, AFAIK Construct 3 internally uses the numbers (0 and 1) instead of JavaScript booleans, so there probably isn't a big difference in memory use for using booleans in event sheets. But even if there is, I prefer the better DX in most cases.

If you want to give my organization system a go, you can download my template that I almost always use for new projects.

Event sheets that are not in a subfolder are assigned to layouts. Usually I have one called Level (or Main) that includes all sheets in the Events folder and nothing else; there's no need to include sheets from the other folders, custom actions, functions and global variables / constants are always available. 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 sheet names with emojis (pro tip for Windows devs: press Win + comma to open the emoji panel), so I can find them more easily 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 contain emojis. NTFS on Windows 10 seems to handle that without problems, but your mileage might vary on Linux or Mac filesystems. 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.

If you want to give my organization system a go, you can download my template that I almost always use for new projects.

Using "names"

I try not to use string literals in conditions / actions when referencing animation names, layers, effects, tags and similar identifiers; instead, I use "names" , which are global constants defined in the vNames sheet. They kinda act like enums, which don't exist in Construct.

An example: if I have a sprite that has an "Idle" and "Walking" animation, I create constants named "ANIM_Idle" and "ANIM_Walking" in the vNames sheet with the values "Idle" and "Walking" respectively, and use those in conditions and actions instead of typing in the string literals "Idle" and "Walking". Sometimes I also add the name of the related object to the name of the global constant, like "ANIM_Player_Idle".

This has a couple of benefits:

  • You get autocompletion that is typo-proof: just start typing the name of the constant wherever you want to use it's value in a condition or action.
  • You can use "Find all references" to find occurrences of these "names".
  • If you want to rename eg. an animation from "Walking" to "Running", you only have to make the change in two places: the animation editor and the "name". This is quicker and less error prone compared to search & replace.

Footguns

Just like with all engines and programming languages, there are a few WTF moments in Construct too, or things that some devs (like yours truly) expect to work in a different way.

Picking instances

A good 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. To reset such a variable, you can use any negative number (I use the previously mentioned global constant called NOTHING, which has the value -1).
  • 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 is by design and makes sense, but can be slightly confusing 

Other stuff

  • The int() system expression works like floor() and does not work like parseInt in JavaScript. If you pass it -0.1, it will return -1 instead of 0. There is a related feature request here with more info workarounds.

Keyboard shortcuts

In event sheets you can do almost everything without touching the mouse. Although I still find myself using it at times, I gained considerable speed by learning the handful of shortcuts for creating events, conditions, (custom) actions, functions, comments, inline scripts, parameters and variables alone.

You can select events, conditions and actions using the arrow keys (which will also require some practice, but it's totally worth it), toggle breakpoints, bookmarks and disabled state. Navigating the condition / action editor is also fully possible using keyboard shortcuts, and of course you can use the system-wide shortcuts like (Shift+)Tab to jump between fields, Ctrl + left / right arrows to move the cursor in text fields one word at a time and Enter to apply changes. The automatically focused search bar in the condition / action editor is also a godsend, you can just start typing what you're looking for, it's basically a 2D autocomplete that you can also navigate using the arrow keys.

Embrace the web platform

Construct 3 runs entirely in a browser and you can also install it as a PWA if you are using Chromium (my choice), Google Chrome, Edge or possibly other Chromium-derived browsers. This means you'll be able to start Construct like you start any other desktop app, use it in an almost full screen window (you can go full screen with F11 if you prefer) and have it associated with the .c3p file type.

Another great thing is that you can integrate JavaScript libraries in your projects fairly easily, which opens up a lot of cool possibilities. Some examples of libraries I've glued in previously:

And since Construct projects are JavaScript apps, you can also use the developer tools in your browser, including the console: the stock Browser plugin / object has actions that write to the console, but of course you can use console.log() and friends in code blocks or the code editor as well.

Useful tools

Here are a few external tools that you might found useful:

This article was updated on 2025-05-27 17:46