Wrangling Game Objects with an EntityManager
Let's talk about managing game objects in our data-driven Babylon.js world. We need a way to create, track, update, and destroy entities efficiently. That's where the EntityManager
class comes in, acting as the conductor of our ECS orchestra.
The EntityManager
: Your Entity Command Center
The EntityManager
class is the central hub for managing all the entities in our game. It provides methods for creating, finding, updating, and deleting entities, abstracting away the complexities of entity management.
import { Scene } from "@babylonjs/core";
export class EntityManager {
entities: Map<String, Entity>; // Stores our entities, keyed by their names
scene: Scene; // A reference to the Babylon.js scene
constructor(scene: Scene) {
this.entities = new Map();
this.scene = scene;
}
search(name: string[] | string) {
// Find one or more entities by name
if (Array.isArray(name)) {
return name.map((n) => {
return this.entities.get(n);
});
}
return this.entities.get(name);
}
getPlayers() {
// A helper to quickly get player entities
return [this.entities["Player"], this.entities["Player2"]];
}
createEntity(name) {
// Creates a new entity and adds it to our collection
const entity = new Entity(name);
this.entities.set(entity.name, entity);
if (name == "Player") {
this.playerEntity = entity;
}
if (name == "Player2") {
this.player2Entity = entity;
}
return entity;
}
removeEntity(entity: Entity) {
// Removes an entity from our game
this.entities.delete(entity.id);
}
serialize() {
// Serializes entity data, useful for saving/loading
const entitiesData = [];
for (const entity of this.entities) {
entitiesData.push(entity.serialize());
}
return JSON.stringify(entitiesData, null, 2); // Indented JSON string
}
}
Breakdown:
entities
Map: This stores our entities. We're using aMap
for efficient key-value lookups.scene
Reference: Keeps a handy reference to our Babylon.js scene.search(name)
: Finds one or more entities based on their names.getPlayers()
: A convenience method for fetching player entities (you can add more specific getters as needed).createEntity(name)
: Creates a newEntity
and adds it to ourentities
map.removeEntity(entity)
: Removes an entity, effectively destroying it in our game world.serialize()
: Converts our entity data into a JSON string, useful for saving game state.
The Entity
Class: A Container for Components
Now, let's define the Entity
class. Remember, entities are primarily containers for components:
// Entity.ts
export class Entity {
private static _nextId = 1; // Generates unique IDs for our entities
public name: string; // The name of the entity
private _id: number; // The entity's unique ID
private _components: Map<string, any>; // Stores the entity's components
constructor(name: string) {
this._id = Entity._nextId++;
this._components = new Map();
this.name = name;
}
get id(): number {
return this._id;
}
// Add a component to this entity
addComponent(component: any): Entity {
this._components.set(component.constructor.name, component);
return this;
}
// Remove a component from this entity by its class
removeComponent(componentClass: any): Entity {
this._components.delete(componentClass.name);
return this;
}
// Get a component by its class
getComponent<T>(componentClass: new (...args: any[]) => T): T {
return this._components.get(componentClass.name) as T;
}
// Check if the entity has a specific component
hasComponent(componentClass: any): boolean {
return this._components.has(componentClass.name);
}
serialize() {
// Convert component data to a serializable format
const data = {
id: this.id,
components: {},
};
for (const component of this._components) {
data.components[component.constructor.name] = component.serialize();
}
return data;
}
}
Key Points:
_components
Map: This stores the components attached to the entity, again using aMap
for efficient lookups.addComponent(component)
: Attaches a component to the entity.removeComponent(componentClass)
: Detaches a component from the entity.getComponent(componentClass)
: Retrieves a component by its class (e.g.,getComponent(PositionComponent)
).hasComponent(componentClass)
: Checks if the entity has a specific component attached.serialize()
: Like theEntityManager
, this prepares component data for saving or other operations.
In Sum
With our EntityManager
and Entity
classes in place, we have a robust foundation for managing game objects in our data-driven Babylon.js game. We can create entities, attach components to them (like position, movement, health, etc.), and use systems to process these components and bring our game logic to life.
Next we'll dive into creating our first component and system so we can load some 3d meshes and start moving them around the scene with player input.