Entity Component System
Voxelize servers run on the Specs ECS crate. It is recommended to read through the Specs ECS tutorial before continuing.
Components
Essentially, ECS allows Voxelize to decouple in-game objects into separate components. For instance, an entity that simply moves up and down could have a Position component and a Velocity component. An entity would simply be a holder of a set of components.
By default, Voxelize registers these components to the ECS world:
Null-storage Flags
These flags take up no space on disk, and are used to distinguish clients from non-client entities.
ClientFlag- A component indicating if an entity is a client.
EntityFlag- A component indicating if an entity is a non-client.
Informational Components
These components add additional information about an entity, whether a client or not.
IDComp- All entities have their Voxelize-given ID.
NameComp- A name given to the entity.
ChunkRequestsComp- A list of chunks requested by entity (client).
CurrentChunkComp- Which chunk the entity is in, updated each frame.
PositionComp- A set of 3D coordinates describing the position of an entity.
DirectionComp- A set of 3D coordinates indicating the direction the entity is looking.
ETypeComp- A string to differentiate the type of entity, such as
"Cow".
- A string to differentiate the type of entity, such as
MetadataComp- A JSON-compatible object to store data sent to the client-side or saved to disk.
Physical Components
The components below make an entity physical in the Voxelize world.
RigidBodyComp- A collision box that can collide with voxel blocks.
InteractorComp- A collision box that can collide with other collision boxes.
CollisionsComp- A vector storing all collisions this entity has per frame.
AI & Pathfinding Components
These components enable AI behaviors and pathfinding for entities.
BrainComp- Controls entity movement behavior including walking, jumping, and sprinting. Contains configurable physics parameters like
max_speed,jump_impulse, andresponsiveness.
- Controls entity movement behavior including walking, jumping, and sprinting. Contains configurable physics parameters like
TargetComp- Allows an entity to scan nearby and track the closest target. Supports filtering by
TargetType::All,TargetType::Players, orTargetType::Entities.
- Allows an entity to scan nearby and track the closest target. Supports filtering by
PathComp- Stores a computed A* path and pathfinding parameters like
max_nodes,max_distance, andmax_pathfinding_time.
- Stores a computed A* path and pathfinding parameters like
Block Entity Components
These components are used for entities attached to specific voxel positions (block entities).
VoxelComp- Stores the voxel position an entity is attached to.
JsonComp- Stores arbitrary JSON data for block entities.
Miscellaneous Components
AddrComp- Client's Actix actor address.
To register new components to the Voxelize world, we do the following:
use specs::{Component, VecStorage};
#[derive(Component, Debug)]
#[storage(VecStorage)]
struct CustomComp {
a: f32,
b: f32,
}
world.ecs().register::<CustomComp>();
We can then create entities with this CustomComp:
let custom_entity = world
.create_entity("custom-id", "Custom Entity")
.with(CustomComp { a: 1.0, b: 3.0 })
.build();
world.create_entity(id, etype) calls world.ecs().create_entity() internally, and adds these components by default to integrate with Voxelize:
IDCompEntityFlagETypeCompMetadataCompCurrentChunkCompCollisionsComp
Resources
Another building block of a Voxelize world is a set of resources built-in. Resources are stateful structs that can be shared across all systems.
Informational
These are the static resources that shouldn't be modified.
String- A string of the name of the world.
WorldConfig- The configurations of the world. Can be accessed through
world.config().
- The configurations of the world. Can be accessed through
Managers
These are the structs that manage and pass around the data stored in the world.
Chunks- The chunking manager of the world.
EntitiesSaver- A manager that can handle the spawning and saving of entities.
Pipeline- The chunking pipeline that takes care of generating chunks in parallel.
Clients- A hash map of all clients that have joined this world.
MessageQueue- A list of encoded protobuf messages that gets sent to the client each tick.
Events- Managing all events that can be emitted to the clients.
ChunkInterests- Tracks which clients are interested in which chunks.
EntityIDs- Maps entity IDs to their ECS entity handles.
The manager resources can be accessed through the world directly. For instance, world.chunks() or world.chunks_mut() or world.clients_mut().
Utilities
These are the utility resources that can be used as helpers.
Stats- The world stats such as delta time.
Mesher- A manager that takes care of all the chunk 3D meshing in parallel.
Search- A 3-dimensional tree that has all the clients and entities to search for.
Physics- Manages the Rapier physics simulation for entity collisions.
KdTree- A spatial index for fast entity lookups, used by the targeting system.
You can add your own resources to the ECS world in order to be used in an ECS system too by doing so:
struct CustomResource {
a: f32,
}
world.ecs().insert(CustomResource { a: 1.0 });
Systems
Developers can then write systems that operate on specific components. An example could be a PositionUpdateSystem that operates on all entities with a Position and a Velocity component, and this system could simply add each entity's Velocity to Position to move the entity accordingly.
Voxelize by default comes with a Specs dispatcher that runs these systems:
Core Systems
UpdateStatsSystem- Should run at the start of the dispatcher
- Updates the
Statsresource to the latest delta time which can be used by systems in thePhysicsSystem.
EntitiesMetaSystem- Should run at the start of the dispatcher
- Adds the
PositionCompof all non-client entities into their respectiveMetadataCompto be sent to the client side.
PeersMetaSystem- Should run at the start of the dispatcher
- Adds the
PositionComp,DirectionComp, andNameCompinto all client entities'MetadataCompto update peers.
CurrentChunkSystem- Should run at the start of the dispatcher
- Calculates the current chunks of all entities.
Chunk Systems
ChunkUpdatingSystem- Depends on:
CurrentChunkSystem - Handles the voxel updates by updating
config.max_updates_per_tickof received updates per tick.
- Depends on:
ChunkRequestsSystem- Depends on:
CurrentChunkSystem - Queues all chunks from any
ChunkRequestsCompinto the chunk pipeline to be processed. - Adds any chunks that are ready to
world.chunks().to_sendto be sent to the clients.
- Depends on:
ChunkGeneratingSystem- Depends on:
ChunkRequestsSystem - Pushes
config.max_chunks_per_tickof chunks per tick into a list of chunk phases to populate them with chunk data.
- Depends on:
ChunkSendingSystem- Depends on:
ChunkGeneratingSystem - Packs the chunks from
world.chunks().to_sendalong with clients that had requested for those chunks into theMessageQueueresource.
- Depends on:
ChunkSavingSystem- Depends on:
ChunkGeneratingSystem - Every
config.save_intervalticks, saves the chunk data intoconfig.save_dirifconfig.savingis set true.
- Depends on:
Physics & Collision Systems
PhysicsSystem- Depends on:
CurrentChunkSystem,UpdateStatsSystem - Updates
RigidBodyCompaccording to chunk data. - Calculates
CollisionsCompthroughInteractorCompby calculating the physics collisions through Rapier physics.
- Depends on:
AI & Pathfinding Systems
EntityTreeSystem- Builds a KdTree spatial index of all entities and players for fast lookups.
EntityObserveSystem- Uses the KdTree to find and track the closest target for entities with
TargetComp.
- Uses the KdTree to find and track the closest target for entities with
TargetMetadataSystem- Syncs
TargetCompdata to the entity's metadata for client-side access.
- Syncs
PathFindingSystem- Depends on:
EntityObserveSystem - Runs A* pathfinding for entities with both
PathCompandTargetComp. Computes paths with configurable constraints and path smoothing.
- Depends on:
PathMetadataSystem- Syncs computed paths to the entity's metadata.
WalkTowardsSystem- Depends on:
PathFindingSystem - Operates the
BrainCompto make entities walk along their computed paths, handling jumping, cornering, and target approach.
- Depends on:
Network & Persistence Systems
DataSavingSystem- Depends on:
EntitiesMetaSystem - Every
config.save_interval, saves the entities data intoconfig.save_dirifconfig.savingis set true.
- Depends on:
EntitiesSendingSystem- Depends on:
EntitiesMetaSystem - If any entities have changed their metadata, the metadata is packed and pushed to the
MessageQueueresource.
- Depends on:
PeersSendingSystem- Depends on:
PeersMetaSystem - If any clients have changed their metadata, the metadata is packed and pushed to the
MessageQueueresource.
- Depends on:
BroadcastSystem- Depends on:
ChunkSendingSystem,EntitiesSendingSystem,PeersSendingSystem - Actually sends the packed messages in the
MessageQueueto the specified clients.
- Depends on:
CleanupSystem- Depends on:
EntitiesSendingSystem,PeersSendingSystem - Clears the collisions generated by
PhysicsSystemand other per-frame data.
- Depends on:
EventsSystem- Depends on:
BroadcastSystem - Packs all events in the
Eventsresource and sends them to the specified clients.
- Depends on:
To customize the dispatcher, checkout this tutorial.
Creating AI Entities
To create an entity with AI behavior, combine the AI components:
use std::time::Duration;
world.set_entity_loader("mob", |world, metadata| {
let body = RigidBody::new(&AABB::new().scale_x(0.6).scale_y(1.8).scale_z(0.6).build());
let interactor = world.physics_mut().register(&body);
world
.create_entity(&nanoid!(), "mob")
.with(PositionComp::default())
.with(RigidBodyComp::new(&body))
.with(InteractorComp::new(&interactor))
.with(BrainComp::new(BrainOptions::default()))
.with(TargetComp::players())
.with(PathComp::new(
100, // max_nodes
32.0, // max_distance
10000, // max_depth_search
Duration::from_millis(50), // max_pathfinding_time
))
});
The AI systems will automatically:
- Build a spatial index of all entities (
EntityTreeSystem) - Find the closest player (
EntityObserveSystem) - Compute a path to the target (
PathFindingSystem) - Move the entity along the path (
WalkTowardsSystem)