World System - Building Your Game Universe
Imagine your game as a stage where actors perform. The World System is that stage. It’s where everything in your game exists and interacts. Whether it’s the player character, enemies, power-ups, or environmental objects, they all live in the world.
Traktor uses an Entity-Component architecture, which is a fancy way of saying “build complex things from simple, reusable pieces.” Think of it like building with Lego: instead of having one giant, custom-molded block for each type of object, you have small,standard pieces that click together in different ways.
![TODO: Diagram showing World containing multiple Entities, each Entity containing multiple Components]
Understanding the Pattern
The World System has three key players:
Worlds are containers. They hold all the entities in a scene and coordinate their updates. When you load a level, you’re loading a World. When the game ticks forward one frame, the World updates everything inside it.
Entities are game objects. The player, an enemy, a crate, a light source, whatever. But here’s the twist: entities themselves are quite simple. They’re basically just a position in space (a transform) and a collection of components. An entity without components is just an invisible point floating in your scene.
Components are where the magic happens. They’re modular pieces of functionality that you attach to entities. Want an entity to be visible? Add a MeshComponent. Want it to be affected by physics? Add a RigidBodyComponent. Want it to think and make decisions? Add a ScriptComponent. Each component adds one capability, and by combining components, you create rich, complex game objects.
This approach. Composition over inheritance. Is incredibly powerful. Instead of writing a new class for every type of enemy or object, you just mix and match existing components in new ways.
Core Concepts
World
The World class is the container for all entities in your scene:
class World : public Object
{
public:
// Create world
explicit World(
resource::IResourceManager* resourceManager,
render::IRenderSystem* renderSystem
);
// Destroy world resources
void destroy();
// World components
void setComponent(IWorldComponent* component);
IWorldComponent* getComponent(const TypeInfo& componentType) const;
template<typename T> T* getComponent() const;
const RefArray<IWorldComponent>& getComponents() const;
// Entity management
void addEntity(Entity* entity);
void removeEntity(Entity* entity);
bool haveEntity(const Entity* entity) const;
// Entity queries
Entity* getEntity(const Guid& id) const;
Entity* getEntity(const std::wstring& name, int32_t index = 0) const;
RefArray<Entity> getEntities(const std::wstring& name) const;
RefArray<Entity> getEntitiesWithinRange(const Vector4& position, float range) const;
const RefArray<Entity>& getEntities() const;
// Update all entities
void update(const UpdateParams& update);
};
World Components are global systems that operate on the entire world (e.g., physics world, lighting system).
Entity
Entities are the game objects in your world:
class Entity : public Object
{
public:
// Create entity
Entity(
const Guid& id,
const std::wstring_view& name,
const Transform& transform,
const EntityState& state = EntityState(),
const RefArray<IEntityComponent>& components = RefArray<IEntityComponent>()
);
// Destroy entity resources
void destroy();
// World
void setWorld(World* world);
World* getWorld() const;
// Identity
const Guid& getId() const;
const std::wstring& getName() const;
// State management
EntityState setState(const EntityState& state, const EntityState& mask, bool includeChildren);
const EntityState& getState() const;
void setVisible(bool visible);
bool isVisible() const;
void setDynamic(bool dynamic);
bool isDynamic() const;
void setLocked(bool locked);
bool isLocked() const;
// Transform
void setTransform(const Transform& transform);
Transform getTransform() const;
Aabb3 getBoundingBox() const;
// Component management
void setComponent(IEntityComponent* component);
IEntityComponent* getComponent(const TypeInfo& componentType) const;
template<typename T> T* getComponent() const;
const RefArray<IEntityComponent>& getComponents() const;
// Update
void update(const UpdateParams& update);
};
Key Methods:
- setState() - Set entity state with mask and optional children propagation. Returns the previous state.
- getBoundingBox() - Get the combined bounding box of all components in entity space
Entity State Flags:
- Visible: Entity is rendered
- Dynamic: Entity can move/change
- Locked: Entity cannot be modified (editor only)
Components
Components add functionality to entities. All components derive from IEntityComponent:
class IEntityComponent : public Object
{
virtual void destroy() = 0;
virtual void setOwner(Entity* owner) = 0;
virtual void setWorld(World* world) {}
virtual void setState(const EntityState& state, const EntityState& mask, bool includeChildren) {}
virtual void setTransform(const Transform& transform) = 0;
virtual Aabb3 getBoundingBox() const = 0;
virtual void update(const UpdateParams& update) = 0;
};
Component Lifecycle Methods:
- destroy() - Called when the component is destroyed, cleanup resources here
- setOwner() - Called when the component is attached to an entity
- setWorld() - Called when the entity is added to or removed from a world (optional, default is empty)
- setState() - Called when the entity’s state changes (visible, dynamic, locked). Optional, default is empty
- setTransform() - Called when the entity’s transform is modified
- getBoundingBox() - Returns the component’s bounding box in entity space
- update() - Called every frame to update component logic
Common Components
Entity Transform (Built-in)
Every entity has a transform as a built-in property (not a component):
// Set position, rotation, scale
Transform transform(
Vector4(x, y, z, 1.0f), // Position
Quaternion::identity(), // Rotation
Vector4(1.0f, 1.0f, 1.0f) // Scale
);
entity->setTransform(transform);
// Get transform
Transform current = entity->getTransform();
// Transform is part of Entity itself, not a component
// Entity stores it directly and notifies all components when it changes
// via IEntityComponent::setTransform()
Mesh Component
Renders 3D geometry. MeshComponent is an abstract base class - you typically use concrete implementations like StaticMeshComponent:
// MeshComponent is abstract - use specific implementations
// Example: StaticMeshComponent for static geometry
// Components are typically created through entity factories
// when loading scenes from the editor
Script Component
Attaches Lua scripts for behavior:
// ScriptComponent requires a runtime class proxy and properties
Ref<ScriptComponent> scriptComp = new ScriptComponent(
clazz, // resource::Proxy<IRuntimeClass> - the script class
properties // PropertyGroup* - script properties
);
entity->setComponent(scriptComp);
// Scripts are typically attached through the editor
// by adding ScriptComponentData to entities
Light Component
Provides illumination:
Ref<LightComponent> lightComp = new LightComponent(
LightType::Directional, // Light type
Vector4(1.0f, 1.0f, 1.0f), // Color
true, // Cast shadow
1.0f, // Near range
100.0f, // Far range
10.0f, // Radius
0.0f, // Flicker amount
0.0f // Flicker filter
);
entity->setComponent(lightComp);
// You can also modify properties after creation
lightComp->setLightType(LightType::Point);
lightComp->setColor(Vector4(1.0f, 0.8f, 0.6f));
lightComp->setCastShadow(false);
Light Types:
- Directional: Sun-like lighting (e.g., for outdoor scenes)
- Point: Omnidirectional light source (e.g., light bulb)
- Spot: Cone-shaped light (e.g., flashlight)
Rigid Body Component
Adds physics simulation:
// RigidBodyComponent requires a physics Body and event handlers
Ref<RigidBodyComponent> bodyComp = new RigidBodyComponent(
body, // Body* - physics body (already configured with mass, shape, etc.)
eventCollide, // IEntityEvent* - collision event handler
0.5f // transformFilter - transform smoothing factor
);
entity->setComponent(bodyComp);
// The physics Body is typically created through the physics system
// and configured with mass, shape, and other properties before
// being passed to the component
Sound Component
Plays sound effects and music (from Spray module):
// SoundComponent requires a SoundPlayer and Sound resource
Ref<SoundComponent> soundComp = new SoundComponent(
soundPlayer, // sound::SoundPlayer* - the sound player system
soundProxy // resource::Proxy<sound::Sound> - the sound resource
);
entity->setComponent(soundComp);
// Control playback
soundComp->play();
soundComp->stop();
soundComp->setVolume(0.8f);
soundComp->setPitch(1.2f);
soundComp->setParameter(L"filter", 0.5f);
Note: Components are typically created through entity factories when loading scenes from the editor, rather than being manually constructed in code. The editor provides a user-friendly interface for configuring component properties.
Working with Entities
Creating Entities in Code
// Create entity
Ref<Entity> entity = new Entity(
Guid::create(), // Unique ID
L"MyEntity", // Name
Transform::identity(), // Transform
EntityState::All // State (visible, dynamic)
);
// Add components
entity->setComponent(new MeshComponent(...));
entity->setComponent(new ScriptComponent(...));
// Add to world
world->addEntity(entity);
Finding Entities
// By ID
Ref<Entity> entity = world->getEntity(guid);
// By name
Ref<Entity> entity = world->getEntity(L"Player");
// Multiple entities with same name
RefArray<Entity> enemies = world->getEntities(L"Enemy");
// Within range
RefArray<Entity> nearby = world->getEntitiesWithinRange(
playerPosition,
100.0f // Radius
);
Accessing Components
// Get specific component
MeshComponent* mesh = entity->getComponent<MeshComponent>();
if (mesh)
{
// Use mesh component
}
// Iterate all components
const RefArray<IEntityComponent>& components = entity->getComponents();
for (auto* component : components)
{
// Process component
}
Modifying Entities
// Change transform
Transform t = entity->getTransform();
t.setTranslation(Vector4(10, 0, 0, 1));
entity->setTransform(t);
// Change visibility
entity->setVisible(false);
// Add new component
entity->setComponent(new AudioComponent(...));
Removing Entities
world->removeEntity(entity);
Entity Lifecycle
The entity lifecycle follows this sequence (verified from World.cpp and Entity.cpp):
Create → Add to World → Update Loop → Remove from World → Destroy
1. Create - Entity constructor is called
Ref<Entity> entity = new Entity(
Guid::create(), // Unique ID
L"MyEntity", // Name
Transform::identity(), // Initial transform
EntityState::All, // Initial state (visible, dynamic)
components // Optional: components to attach
);
Component callbacks: setOwner(), setState(), setTransform()
2. Add to World - world->addEntity(entity) → entity->setWorld(world)
world->addEntity(entity);
// Internally calls: entity->setWorld(world)
Component callbacks: setWorld(world)
3. Update Loop - world->update() → entity->update()
// Called every frame by the world
world->update(updateParams);
// Internally: entity->update() for each entity
Component callbacks: update() each frame
4. Remove from World - world->removeEntity(entity) → entity->setWorld(nullptr)
world->removeEntity(entity);
// Internally calls: entity->setWorld(nullptr)
Component callbacks: setWorld(nullptr)
5. Destroy - entity->destroy() (MUST be removed from world first!)
// IMPORTANT: Entity must be removed from world before destroy
entity->destroy();
// Fatal assertion fails if entity is still in world!
Component callbacks: destroy()
Important Notes:
- Deferred Add/Remove: If you add or remove entities during
update(), the operation is deferred until after all entities have been updated (to avoid iterator invalidation) - Must Remove Before Destroy:
Entity::destroy()has a fatal assertionT_FATAL_ASSERT(m_world == nullptr)- you MUST remove the entity from the world before destroying it - World::destroy() automatically calls
setWorld(nullptr)anddestroy()on all entities
Update Cycle
void World::update(const UpdateParams& update)
{
// Update all entities
for (Entity* entity : m_entities)
{
entity->update(update);
}
}
void Entity::update(const UpdateParams& update)
{
// Update all components
for (IEntityComponent* component : m_components)
{
component->update(update);
}
}
UpdateParams contains:
struct UpdateParams
{
Object* contextObject; // Update context object (Stage instance during runtime)
double totalTime; // Total time since first update
double alternateTime; // Alternative absolute time
double deltaTime; // Delta time since last update
};
Entity Hierarchy
Traktor implements entity hierarchies through the GroupComponent:
class GroupComponent : public IEntityComponent
{
public:
void addEntity(Entity* entity);
void removeEntity(Entity* entity);
void removeAllEntities();
const RefArray<Entity>& getEntities() const;
Entity* getEntity(const std::wstring& name, int32_t index) const;
RefArray<Entity> getEntities(const std::wstring& name) const;
};
How GroupComponent Works:
- Add GroupComponent to parent entity - The parent entity gets a GroupComponent
- Add child entities - Child entities are added to the GroupComponent using
addEntity() - Automatic transform propagation - When the parent entity’s transform changes:
- GroupComponent stores the parent’s transform
- It calculates each child’s local transform relative to the parent
- When parent moves, it applies the new parent transform to each child’s local transform
- Children are updated in world space
-
State propagation - When
setState()is called withincludeChildren = true, state changes (visible, dynamic, locked) propagate to all children - Bounding box - GroupComponent computes a combined bounding box of all children in parent space
Example:
// Create parent entity with GroupComponent
Ref<Entity> parent = new Entity(...);
Ref<GroupComponent> group = new GroupComponent();
parent->setComponent(group);
// Create child entities
Ref<Entity> child1 = new Entity(...);
Ref<Entity> child2 = new Entity(...);
// Add children to the group
group->addEntity(child1);
group->addEntity(child2);
// Add parent to world (children should already be in world)
world->addEntity(parent);
// When parent moves, children move with it automatically
parent->setTransform(Transform(Vector4(10, 0, 0)));
// child1 and child2 are automatically updated in world space
// Query children
Entity* namedChild = group->getEntity(L"ChildName");
const RefArray<Entity>& allChildren = group->getEntities();
Important Notes:
- Children must be in the world - Child entities must be added to the world separately via
world->addEntity() - Local transforms - GroupComponent maintains the relative transform between parent and children
- Transform propagation - Happens automatically when
setTransform()is called on the parent - No multi-level nesting shown - You can nest groups (add entities with GroupComponents as children), creating deep hierarchies
Creating Custom Components
Component Data
First, create a data class for the editor (derives from IEntityComponentData):
class MyComponentData : public IEntityComponentData
{
T_RTTI_CLASS;
public:
// Properties (serialized)
float m_speed = 1.0f;
int32_t m_health = 100;
virtual int32_t getOrdinal() const override;
virtual void setTransform(const EntityData* owner, const Transform& transform) override;
virtual void serialize(ISerializer& s) override;
};
Component Runtime
Then create the runtime component (derives from IEntityComponent):
class MyComponent : public IEntityComponent
{
T_RTTI_CLASS;
public:
MyComponent(float speed, int32_t health)
: m_speed(speed)
, m_health(health)
{
}
virtual void destroy() override
{
// Cleanup
}
virtual void setOwner(Entity* owner) override
{
m_owner = owner;
}
virtual void setWorld(World* world) override
{
// Optional: handle world changes
// Default implementation in base is empty
}
virtual void setState(const EntityState& state, const EntityState& mask, bool includeChildren) override
{
// Optional: handle state changes
// Default implementation in base is empty
}
virtual void setTransform(const Transform& transform) override
{
// Handle transform changes
}
virtual Aabb3 getBoundingBox() const override
{
return Aabb3();
}
virtual void update(const UpdateParams& update) override
{
// Update logic here
Vector4 pos = m_owner->getTransform().translation();
pos += Vector4(m_speed * update.deltaTime, 0, 0);
Transform t = m_owner->getTransform();
t.setTranslation(pos);
m_owner->setTransform(t);
}
private:
Entity* m_owner = nullptr;
float m_speed;
int32_t m_health;
};
Component Factory
Register your component so it can be instantiated:
class MyComponentFactory : public IEntityComponentFactory
{
T_RTTI_CLASS;
public:
virtual Ref<IEntityComponent> createComponent(
const IEntityComponentData* componentData,
...
) const override
{
const MyComponentData* data = checked_type_cast<const MyComponentData*>(componentData);
return new MyComponent(data->m_speed, data->m_health);
}
};
World Components
World components are global systems that operate on the entire world. Unlike entity components (which are attached to individual entities), world components are attached to the World itself and provide world-level functionality:
class IWorldComponent : public Object
{
virtual void destroy() = 0;
virtual void update(World* world, const UpdateParams& update) = 0;
};
Available World Components:
CullingComponent (World/Entity) - GPU-driven culling system:
- Manages instance rendering and visibility culling
- Builds instance buffers for GPU rendering
- Handles frustum and occlusion culling
- Used internally by world renderer
EventManagerComponent (World/Entity) - Entity event management:
- Centralized system for entity events (collision, triggers, etc.)
- Raises and manages entity event instances
- Handles event cancellation
IrradianceGridComponent (World/Entity) - Global illumination:
- Stores irradiance grid data for indirect lighting
- Provides ambient lighting information to renderers
- One per world for global GI
RTWorldComponent (World/Entity) - Ray tracing support:
- Manages top-level acceleration structure (TLAS) for ray tracing
- Tracks all ray-traced instances in the world
- Updates TLAS when instances move
- Used by ray tracing renderer
NavMeshComponent (Ai module) - Navigation mesh:
- Provides navigation mesh for AI pathfinding
- One per world, accessible to all AI entities
- Used by AI pathfinding systems
TheaterComponent (Theater module) - Cinematic sequences:
- Plays cinematic “acts” (sequences of scripted events)
- Manages timeline-based world events
- Used for cutscenes and scripted sequences
Example: Adding a World Component
// Create world with render system and resource manager
Ref<World> world = new World(resourceManager, renderSystem);
// Add a navigation mesh component
Ref<NavMeshComponent> navMesh = new NavMeshComponent(navMeshProxy);
world->setComponent(navMesh);
// Add an irradiance grid for global illumination
Ref<IrradianceGridComponent> irradiance = new IrradianceGridComponent(irradianceGridProxy);
world->setComponent(irradiance);
// Retrieve a world component
NavMeshComponent* navMeshComp = world->getComponent<NavMeshComponent>();
Note: Most world components are added automatically by the world renderer or through scene data. You rarely need to manually add world components unless building custom rendering or AI systems.
Best Practices
1. Favor Composition
Instead of:
class PlayerEntity : public Enemy, public Character, public Interactive { };
Use:
Ref<Entity> player = new Entity(...);
player->setComponent(new CharacterController());
player->setComponent(new HealthComponent());
player->setComponent(new InventoryComponent());
2. Single Responsibility Components
Keep components focused:
- ✓
HealthComponent- Manages health - ✓
DamageComponent- Handles damage - ✗
PlayerComponent- Does everything
3. Use Entity Queries Wisely
// Cache results when possible
RefArray<Entity> enemies = world->getEntities(L"Enemy");
// Avoid expensive queries every frame
for (int i = 0; i < 1000; i++)
{
world->getEntitiesWithinRange(...); // Slow!
}
4. Clean Up Properly
void MyComponent::destroy()
{
// Release resources
m_resource = nullptr;
// Call base
IEntityComponent::destroy();
}
5. Handle Null Components
// Always check for null
if (MeshComponent* mesh = entity->getComponent<MeshComponent>())
{
// Safe to use mesh
}
Performance Tips
Spatial Queries
Use spatial data structures for efficient entity queries:
// Efficient range query
RefArray<Entity> nearby = world->getEntitiesWithinRange(position, radius);
Component Updates
Only update components that need it:
virtual void update(const UpdateParams& update) override
{
if (!m_active)
return; // Skip inactive components
// Update logic
}
Batch Operations
Process similar entities together:
// Get all entities with physics
RefArray<Entity> dynamicEntities;
for (Entity* entity : world->getEntities())
{
if (entity->isDynamic())
dynamicEntities.push_back(entity);
}
// Process in batch
for (Entity* entity : dynamicEntities)
{
// Physics update
}
Integration with Other Systems
With Scripting
-- From Lua script
local entity = world:getEntity("Player")
local health = entity:getComponent(game.HealthComponent)
health:damage(10)
With Physics
// Physics component automatically syncs with entity transform
RigidBodyComponent* body = entity->getComponent<RigidBodyComponent>();
body->applyForce(Vector4(0, 100, 0)); // Entity will move
With Rendering
// Mesh component automatically renders at entity position
MeshComponent* mesh = entity->getComponent<MeshComponent>();
mesh->setMaterial(newMaterial); // Material changes
Debugging
Entity Inspector
In the editor, select an entity to see:
- Transform
- State flags
- All components
- Component properties
Logging
log::info << "Entity count: " << world->getEntities().size() << Endl;
log::info << "Entity '" << entity->getName() << "' at "
<< entity->getTransform().translation() << Endl;
See Also
- Runtime System - Application and stages
- Physics System - Physics components
- Scripting - Lua integration with entities
- Render System - Rendering components
References
- Source:
code/World/World.h - Source:
code/World/Entity.h - Source:
code/World/IEntityComponent.h - Source:
code/World/IWorldComponent.h