Serialization
ExcaliburJS Serialization System
The Serialization System provides a centralized, extensible architecture for saving and loading game Entities, Actors, and Components in ExcaliburJS.
Overview
The Serializer class serves as the central manager for all serialization operations, providing:
- Component type registration and management
- Entity and Actor serialization/deserialization
- Custom Actor class support
- Graphics registry for reference-based serialization
- Custom type serializers for complex objects
- JSON conversion utilities
- Data validation
Getting Started
Basic Setup
ts// Initialize with auto-registration of common components (default)ex .Serializer .init ();// OR initialize without auto-registering componentsex .Serializer .init (false);
ts// Initialize with auto-registration of common components (default)ex .Serializer .init ();// OR initialize without auto-registering componentsex .Serializer .init (false);
Serializing an Entity
ts// Serialize to objectconstentityData =ex .Serializer .serializeEntity (myEntity );// Serialize to JSON stringconstjson =ex .Serializer .entityToJSON (myEntity , true); // pretty print
ts// Serialize to objectconstentityData =ex .Serializer .serializeEntity (myEntity );// Serialize to JSON stringconstjson =ex .Serializer .entityToJSON (myEntity , true); // pretty print
Deserializing an Entity
ts// Deserialize from objectconstentity =ex .Serializer .deserializeEntity (entityData );// Deserialize from JSON stringletnewEntity =ex .Serializer .entityFromJSON (json );
ts// Deserialize from objectconstentity =ex .Serializer .deserializeEntity (entityData );// Deserialize from JSON stringletnewEntity =ex .Serializer .entityFromJSON (json );
Component Registry
Components must be registered before they can be serialized/deserialized. Common Excalibur Actor components are pre-registered on init.
Registering Components
ts// Register a single componentclassHealthComponent extendsex .Component {currentHealth : number;maxHealth : number;constructor(maxHealth : number) {super();this.maxHealth =maxHealth ;this.currentHealth =maxHealth ;}}ex .Serializer .registerComponent (HealthComponent );// Register multiple componentsex .Serializer .registerComponents ([myComponent1 ,myComponent2 ]);
ts// Register a single componentclassHealthComponent extendsex .Component {currentHealth : number;maxHealth : number;constructor(maxHealth : number) {super();this.maxHealth =maxHealth ;this.currentHealth =maxHealth ;}}ex .Serializer .registerComponent (HealthComponent );// Register multiple componentsex .Serializer .registerComponents ([myComponent1 ,myComponent2 ]);
Checking Registration
ts// Check if a component is registeredif (ex .Serializer .isComponentRegistered ('TransformComponent')) {// Component is registered}// Get all registered componentsconstregisteredTypes =ex .Serializer .getRegisteredComponents ();console .log (registeredTypes ); // ['TransformComponent', 'MotionComponent', ...]
ts// Check if a component is registeredif (ex .Serializer .isComponentRegistered ('TransformComponent')) {// Component is registered}// Get all registered componentsconstregisteredTypes =ex .Serializer .getRegisteredComponents ();console .log (registeredTypes ); // ['TransformComponent', 'MotionComponent', ...]
Unregistering Components
ts// Unregister a specific componentex .Serializer .unregisterComponent ('TransformComponent');// Clear all componentsex .Serializer .clearComponents ();
ts// Unregister a specific componentex .Serializer .unregisterComponent ('TransformComponent');// Clear all componentsex .Serializer .clearComponents ();
Custom Actor Classes
The serialization system supports custom Actor subclasses, preserving their specific implementations.
Registering Custom Actors
Custom Actor classes are registered by the 'name' property on construction, which leaves them as defaults if you do not specify.
tsclassPlayer extendsex .Actor {constructor() {super({name : 'Player',});// Player-specific initialization}}classEnemy extendsex .Actor {constructor() {super({name : 'Enemy',});// Enemy-specific initialization}}// Register custom actorsex .Serializer .registerCustomActor (Player );ex .Serializer .registerCustomActors ([Enemy ,Boss ,NPC ]);
tsclassPlayer extendsex .Actor {constructor() {super({name : 'Player',});// Player-specific initialization}}classEnemy extendsex .Actor {constructor() {super({name : 'Enemy',});// Enemy-specific initialization}}// Register custom actorsex .Serializer .registerCustomActor (Player );ex .Serializer .registerCustomActors ([Enemy ,Boss ,NPC ]);
Serializing Custom Actors
After you register a custom Actor class, then when you serialize a custom Actor, the system automatically detects its type and includes a customInstance field:
tsclassPlayer extendsex .Actor {constructor() {super({name : 'myTestActor',});// Player-specific initialization}}ex .Serializer .registerCustomActor (Player );constplayer = newPlayer ();constactorData =ex .Serializer .serializeActor (player );// actorData.customInstance === 'myTestActor'
tsclassPlayer extendsex .Actor {constructor() {super({name : 'myTestActor',});// Player-specific initialization}}ex .Serializer .registerCustomActor (Player );constplayer = newPlayer ();constactorData =ex .Serializer .serializeActor (player );// actorData.customInstance === 'myTestActor'
Deserializing Custom Actors
The system automatically instantiates the correct custom Actor class:
tsletactorData :ex .EntityData = {type : 'Actor',name : "player",tags : [],components : [],children : [],customInstance : 'Player'};constplayer =ex .Serializer .deserializeActor (actorData );// player is an instance of the class with name: 'Player', not just Actor
tsletactorData :ex .EntityData = {type : 'Actor',name : "player",tags : [],components : [],children : [],customInstance : 'Player'};constplayer =ex .Serializer .deserializeActor (actorData );// player is an instance of the class with name: 'Player', not just Actor
Managing Custom Actors
ts// Check if registeredif (ex .Serializer .isCustomActorRegistered ('Player')) {// Player is registered}// Get registered custom actorsconstcustomActors =ex .Serializer .getRegisteredCustomActors ();// Get a specific actor constructorconstPlayerCtor =ex .Serializer .getCustomActor ('Player');// Unregisterex .Serializer .unregisterCustomActor ('Player');// Clear allex .Serializer .clearCustomActors ();
ts// Check if registeredif (ex .Serializer .isCustomActorRegistered ('Player')) {// Player is registered}// Get registered custom actorsconstcustomActors =ex .Serializer .getRegisteredCustomActors ();// Get a specific actor constructorconstPlayerCtor =ex .Serializer .getCustomActor ('Player');// Unregisterex .Serializer .unregisterCustomActor ('Player');// Clear allex .Serializer .clearCustomActors ();
Graphics Registry
The Graphics Registry enables reference-based serialization for graphics. Instead of serializing entire graphic objects, you serialize references (IDs) and resolve them during deserialization.
Why Use Graphics Registry?
Graphics objects (Sprites, Animations, etc.) can be large and complex. The registry pattern:
- Reduces serialized data size
- Avoids duplicate graphic data
- Enables sharing graphics between entities
- Simplifies graphic management
The purpose of registering Graphics to the Serializer is to assist the 'deserialization' as we are not serializing and storing the graphics themselves, just the reference to the graphics. Thus, this is assisted by using graphics in this pattern:
tsex .Serializer .registerGraphic ('mygraphic',playerSprite );classPlayer extendsex .Actor {constructor(){super();// add grahphic by keythis.graphics .add ('mygraphic',playerSprite ) //<-- this sets the key identifier for serializationthis.graphics .use ('mygraphic')}}
tsex .Serializer .registerGraphic ('mygraphic',playerSprite );classPlayer extendsex .Actor {constructor(){super();// add grahphic by keythis.graphics .add ('mygraphic',playerSprite ) //<-- this sets the key identifier for serializationthis.graphics .use ('mygraphic')}}
When the Graphics Component is deserialized it will grab the 'mygraphic' and attempt to reattach the Sprite to the new Actor.
Registering Graphics
ts// Load your resourcesconstplayerImage = newex .ImageSource ('./player.png');playerImage .load ();// Create graphicsconstplayerIdleSprite =playerImage .toSprite ();constplayerWalkAnimation =newex .Animation (testAnimationOptions );// Register graphics with unique IDs// When an actor deserializes, it will use the id set to look up the Graphicex .Serializer .registerGraphic ('player-idle',playerIdleSprite );ex .Serializer .registerGraphic ('player-walk',playerWalkAnimation );// Register multiple at onceex .Serializer .registerGraphics ({'enemy-idle':enemyIdleSprite ,'enemy-attack':enemyAttackSprite ,'coin-spin':coinAnimation });
ts// Load your resourcesconstplayerImage = newex .ImageSource ('./player.png');playerImage .load ();// Create graphicsconstplayerIdleSprite =playerImage .toSprite ();constplayerWalkAnimation =newex .Animation (testAnimationOptions );// Register graphics with unique IDs// When an actor deserializes, it will use the id set to look up the Graphicex .Serializer .registerGraphic ('player-idle',playerIdleSprite );ex .Serializer .registerGraphic ('player-walk',playerWalkAnimation );// Register multiple at onceex .Serializer .registerGraphics ({'enemy-idle':enemyIdleSprite ,'enemy-attack':enemyAttackSprite ,'coin-spin':coinAnimation });
Managing Graphics
ts// Get a graphicconstgraphic =ex .Serializer .getGraphic ('player-idle');// Check if registeredif (ex .Serializer .isGraphicRegistered ('player-idle')) {// Graphic is available}// Get all registered graphic IDsconstgraphicIds =ex .Serializer .getRegisteredGraphics ();// Unregister a graphicex .Serializer .unregisterGraphic ('player-idle');// Clear all graphicsex .Serializer .clearGraphics ();
ts// Get a graphicconstgraphic =ex .Serializer .getGraphic ('player-idle');// Check if registeredif (ex .Serializer .isGraphicRegistered ('player-idle')) {// Graphic is available}// Get all registered graphic IDsconstgraphicIds =ex .Serializer .getRegisteredGraphics ();// Unregister a graphicex .Serializer .unregisterGraphic ('player-idle');// Clear all graphicsex .Serializer .clearGraphics ();
Actor Serialization
Actors have special handling to preserve their convenience properties and references.
Serializing Actors
tsconstactor = newex .Actor ({x : 100,y : 200 });// ... configure actor ...constactorData =ex .Serializer .serializeActor (actor );
tsconstactor = newex .Actor ({x : 100,y : 200 });// ... configure actor ...constactorData =ex .Serializer .serializeActor (actor );
Deserializing Actors
The system automatically:
- Removes existing default components
- Adds deserialized components
- Restores actor property references (
actor.transform,actor.graphics, etc.)
tsconstactor =ex .Serializer .deserializeActor (actorData );// actor.transform, actor.graphics, etc. are properly set
tsconstactor =ex .Serializer .deserializeActor (actorData );// actor.transform, actor.graphics, etc. are properly set
Custom Type Serializers
For complex types that need special serialization logic, you can register custom serializers.
Registering Custom Serializers
tsex .Serializer .registerCustomSerializer ('MyCustomType',// Serialize function(obj ) => ({data :obj .someData ,value :obj .someValue }),// Deserialize function(data ) => newMyCustomType (data .data ,data .value ));
tsex .Serializer .registerCustomSerializer ('MyCustomType',// Serialize function(obj ) => ({data :obj .someData ,value :obj .someValue }),// Deserialize function(data ) => newMyCustomType (data .data ,data .value ));
Example
ts// Color serializerex .Serializer .registerCustomSerializer ('Color',(color ) => ({r :color .r ,g :color .g ,b :color .b ,a :color .a }),(data ) => ({r :data .r ,g :data .g ,b :data .b ,a :data .a }));
ts// Color serializerex .Serializer .registerCustomSerializer ('Color',(color ) => ({r :color .r ,g :color .g ,b :color .b ,a :color .a }),(data ) => ({r :data .r ,g :data .g ,b :data .b ,a :data .a }));
Built-in Serializers
The system provides serializers for common ExcaliburJS types:
Vectors
Registered as {x: number, y: number}
Colors
Registered as {r: number, g: number, b: number, a: number}
BoundingBox
Registered as {left: number, top: number, right: number, bottom: number}
Data Formats
EntityData Format
tsinterfaceEntityData {type : 'Entity' | 'Actor';name : string;tags : string[];components :ComponentData [];children :EntityData [];customInstance ?: string; // For custom Actor classes}
tsinterfaceEntityData {type : 'Entity' | 'Actor';name : string;tags : string[];components :ComponentData [];children :EntityData [];customInstance ?: string; // For custom Actor classes}
ComponentData Format
tsinterfaceComponentData {type : string; // Component class name// ... component-specific fields}
tsinterfaceComponentData {type : string; // Component class name// ... component-specific fields}
Complete Example
ts// Initializeex .Serializer .init ();// Register custom actorsclassPlayer extendsex .Actor { /* ... */ }ex .Serializer .registerCustomActor (Player );// Load and register graphicsconstspriteSheet = newex .ImageSource ('./player.png');spriteSheet .load ();constplayerSprite =spriteSheet .toSprite ();ex .Serializer .registerGraphic ('player-sprite',playerSprite );// Create and serialize an actorconstplayer = newPlayer ();player .pos .x = 100;player .pos .y = 200;player .graphics .use (playerSprite );constsavedActorData =ex .Serializer .serializeActor (player );console .log ('Saved:',savedActorData );// Later: deserializeconstloadedPlayer :ex .Entity =ex .Serializer .deserializeActor (savedActorData ) asex .Entity ;game .add (loadedPlayer );
ts// Initializeex .Serializer .init ();// Register custom actorsclassPlayer extendsex .Actor { /* ... */ }ex .Serializer .registerCustomActor (Player );// Load and register graphicsconstspriteSheet = newex .ImageSource ('./player.png');spriteSheet .load ();constplayerSprite =spriteSheet .toSprite ();ex .Serializer .registerGraphic ('player-sprite',playerSprite );// Create and serialize an actorconstplayer = newPlayer ();player .pos .x = 100;player .pos .y = 200;player .graphics .use (playerSprite );constsavedActorData =ex .Serializer .serializeActor (player );console .log ('Saved:',savedActorData );// Later: deserializeconstloadedPlayer :ex .Entity =ex .Serializer .deserializeActor (savedActorData ) asex .Entity ;game .add (loadedPlayer );