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. aPlayers, aEnemies, aPortals. These sheets only contain custom actions for a single object type or family. Sheets are named after the relevant object type or family. If I want to run actions when a certain object is created, I put this in a custom cation called init; if an object needs to be constantly "updated", I create a custom action called tick and handle things there. This is similar to how you'd do things in Godot with the _ready and _process functions.

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, etc
  • fUI: functions that update parts the user interface
  • fUtilities: full screen toggle function, math functions that return values, 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
  • vConstants: global constants (I prefix those with "C_"), mostly numbers
  • vMacros: also global constants that serve as "macros" - more on that later :)

Event sheets that are not in a subfolder are assigned to layouts. Usually I have one called Main (or Level) 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 "macros"

I try not to use literals in conditions / actions when referencing animation names, layers, effects, tags and such: instead, I use "macros", which are global constants defined in the 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 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" on the "macro" to find its occurrences.
  • If you decide 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 "macro". This is less error prone compared to search & replace.

I also use some 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.

Footguns

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

Picking instances

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

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, actions, functions, comments, inline scripts, parameters and variables alone. One thing that is missing at the moment is a keyboard shortcut for creating custom actions, but I have a feature request for that too (I'd sure appreciate some upvotes on it if you have a GitHub account, thanks ;).

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.

This article was updated on 2024-11-01 13:22