Quick answer: Define shared variables on the family or on the individual object — never both. Construct’s save system keys instance variables by exact name, and a name collision between a family and a member silently drops the value on load.
You build a family of enemies in Construct 3, give them shared instance variables for HP, damage, and patrol pattern, and ship a save and load system. Players report that all enemies revert to default stats after loading. You test it yourself and the variables come back as zero or empty. The save system is doing what you told it to — the bug is in how the family and the object are both claiming ownership of the same variable name.
The Symptom
You define a variable like hp on a family called FamEnemy. You also (forgotten history, copied from a tutorial, accidentally clicked on the wrong panel) define hp on the individual EnemySkeleton object. Then you call System → Save followed later by System → Load, and:
- Some variables come back as zero, some come back correct.
- The bug is consistent on a given player’s machine but different across machines.
- It works in preview but not in the exported build.
- The browser console shows a warning about “duplicate instance variable” or “variable type mismatch.”
What Construct’s Save System Actually Does
When you call System Save, Construct walks every object instance in the layout and serializes its instance variables as a flat JSON-like object keyed by variable name. On load, Construct walks the instance list again and assigns values back by name. There is no version check, no schema, no type verification beyond “does the name match.”
This means three things will silently break a save:
Renaming a variable. The save file has the old name. The new project has the new name. Construct cannot match them and leaves the new variable at its default.
Changing a variable’s type (number to text, text to boolean). Construct serializes by type, and a number cannot deserialize into a string slot.
Duplicate definitions across family and member. If both the family and the object define hp, Construct’s runtime resolves the lookup based on which definition was registered first. Save and load resolve it independently, sometimes picking different ones, leading to silent value loss.
The Fix
Step 1: Audit your instance variable definitions.
For every family in your project, list all the instance variables defined on the family. Then for every object that belongs to that family, list its own instance variables. Any variable that appears in both lists is a bug. Pick one location and remove the other.
The recommended pattern is: variables shared by every family member go on the family; variables unique to one specific object stay on that object. Treat the family as an interface and the objects as implementations.
Step 2: Rebuild after removing duplicates.
Construct caches some lookups inside the runtime. After deleting a duplicate definition, save the project, close it, and reopen. This forces a clean rebuild of the instance variable schema. Then export a fresh test build.
Step 3: Add a save schema version.
Even after fixing the duplicates, you will hit save migration issues every time you ship an update. Add a global number called save_version. Bump it on every release that changes instance variables. On load, compare the loaded version to the current and run migrations.
/* Event sheet pseudocode */
On Loaded →
If save_version < 2 Then
// migrate old "health" -> new "hp"
FamEnemy.hp = FamEnemy.health
save_version = 2
If save_version < 3 Then
// add new "armor" default for old saves
FamEnemy.armor = 10
save_version = 3
Step 4: Never rename a shipped variable.
Once a save with a variable name has been shipped to players, that name is permanent. If you really want to rename it, add the new name as a separate variable, write a migration to copy the value, and leave the old name in place forever. Removing the old name will silently strip its value from every player’s save file.
Family Type Conflicts
Construct also lets you assign objects with incompatible default types to the same family if the variable is overridden. This is a footgun: if FamEnemy.hp is a number on the family but the original EnemySkeleton defined hp as text before being added to the family, the runtime will sometimes pick the text definition. Always define types on the family first, then add objects to the family second.
Verifying the Fix
Build a test event sheet that:
- Spawns one of each family member.
- Sets each family variable to a known unique value (1, 2, 3, 4, 5).
- Calls System Save.
- Sets all variables to 99.
- Calls System Load.
- Asserts each variable is back to its original unique value.
Run this in the editor and the exported build before every release. If any value comes back as 99, you have a duplicate or a type conflict to fix.
“Construct’s save system is simple, fast, and very, very literal. It saves what you wrote down by name. If two things claim the same name, only one of them gets remembered.”
Related Issues
For Construct 3 save and load patterns more generally, see Construct 3 save and load not working. For local storage persistence problems, check Construct 3 local storage data not persisting. For global variable resets across layouts, see Construct 3 global variable reset on layout restart.
A family is an interface. Treat shared variables as part of that interface and define them in exactly one place.