Entity Component System (ECS) Overview
Entity Component System (ECS) Overview
Section titled “Entity Component System (ECS) Overview”Hytale uses an Entity Component System architecture for managing game entities, chunks, and their associated data. ECS separates data (components) from behavior (systems), enabling efficient processing of large numbers of entities.
Package Location
Section titled “Package Location”- Core ECS:
com.hypixel.hytale.component - Systems:
com.hypixel.hytale.component.system - Queries:
com.hypixel.hytale.component.query - EntityStore:
com.hypixel.hytale.server.core.universe.world.storage.EntityStore - ChunkStore:
com.hypixel.hytale.server.core.universe.world.storage.ChunkStore
Core Concepts
Section titled “Core Concepts”ECS Architecture
Section titled “ECS Architecture”┌─────────────────────────────────────────────────────────────────┐│ Store ││ ┌──────────────────────────────────────────────────────────┐ ││ │ Entities (Refs) │ ││ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ ││ │ │ Entity │ │ Entity │ │ Entity │ │ Entity │ ... │ ││ │ │ Ref 0 │ │ Ref 1 │ │ Ref 2 │ │ Ref 3 │ │ ││ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ ││ └───────│───────────│───────────│───────────│──────────────┘ ││ ▼ ▼ ▼ ▼ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ Components (Data) │ ││ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ││ │ │Position │ │Velocity │ │ Health │ │Inventory │ ... │ ││ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ││ └──────────────────────────────────────────────────────────┘ ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ Resources (World Data) │ ││ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ││ │ │TimeOfDay │ │Weather │ │GameRules │ ... │ ││ │ └──────────┘ └──────────┘ └──────────┘ │ ││ └──────────────────────────────────────────────────────────┘ ││ ││ ┌──────────────────────────────────────────────────────────┐ ││ │ Systems (Logic) │ ││ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ││ │ │ Physics │ │ AI │ │ Combat │ │ Weather │ ... │ ││ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ ││ └──────────────────────────────────────────────────────────┘ │└─────────────────────────────────────────────────────────────────┘Key Terminology
Section titled “Key Terminology”| Term | Description |
|---|---|
| Store | Container for entities, components, resources, and systems |
| Entity (Ref) | A unique identifier for a game object |
| Component | Data attached to an entity (no behavior) |
| Resource | World-level data shared across all entities |
| System | Logic that operates on entities with specific components |
| Archetype | The set of component types an entity has |
| Query | Filter for selecting entities by component composition |
| Holder | Temporary container for entity components |
Store Types
Section titled “Store Types”Hytale uses two primary store types:
EntityStore
Section titled “EntityStore”For entity-level data (players, mobs, items, etc.):
// Access via plugin registrygetEntityStoreRegistry().registerComponent(MyComponent.class, MyComponent::new);getEntityStoreRegistry().registerSystem(new MyEntitySystem());ChunkStore
Section titled “ChunkStore”For chunk-level data (terrain, block data, etc.):
// Access via plugin registrygetChunkStoreRegistry().registerComponent(ChunkData.class, ChunkData::new);getChunkStoreRegistry().registerSystem(new ChunkProcessingSystem());Components
Section titled “Components”Components are pure data containers attached to entities. They should contain no game logic.
Defining a Component
Section titled “Defining a Component”import com.hypixel.hytale.component.Component;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class HealthComponent implements Component<EntityStore> { private float health; private float maxHealth;
public HealthComponent() { this.health = 20.0f; this.maxHealth = 20.0f; }
public float getHealth() { return health; } public void setHealth(float health) { this.health = health; }
public float getMaxHealth() { return maxHealth; } public void setMaxHealth(float maxHealth) { this.maxHealth = maxHealth; }
@Override public Component<EntityStore> clone() { HealthComponent copy = new HealthComponent(); copy.health = this.health; copy.maxHealth = this.maxHealth; return copy; }}Registering a Component
Section titled “Registering a Component”@Overrideprotected void setup() { // Register without serialization ComponentType<EntityStore, HealthComponent> healthType = getEntityStoreRegistry().registerComponent( HealthComponent.class, HealthComponent::new );
// Register with serialization (saved to disk) ComponentType<EntityStore, PersistentData> dataType = getEntityStoreRegistry().registerComponent( PersistentData.class, "myplugin:persistent_data", PersistentData.CODEC );}ComponentType
Section titled “ComponentType”ComponentType<ECS_TYPE, T> is a handle for accessing a specific component type:
private ComponentType<EntityStore, HealthComponent> healthType;
// In setup()this.healthType = getEntityStoreRegistry().registerComponent( HealthComponent.class, HealthComponent::new);
// In systemHealthComponent health = store.getComponent(ref, healthType);if (health != null) { health.setHealth(health.getHealth() - 5);}Resources
Section titled “Resources”Resources are world-level data shared across all entities. Unlike components, resources aren’t attached to specific entities.
Defining a Resource
Section titled “Defining a Resource”import com.hypixel.hytale.component.Resource;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class WeatherResource implements Resource<EntityStore> { private String currentWeather = "sunny"; private float intensity = 1.0f;
public String getCurrentWeather() { return currentWeather; } public void setCurrentWeather(String weather) { this.currentWeather = weather; }
public float getIntensity() { return intensity; } public void setIntensity(float intensity) { this.intensity = intensity; }
@Override public Resource<EntityStore> clone() { WeatherResource copy = new WeatherResource(); copy.currentWeather = this.currentWeather; copy.intensity = this.intensity; return copy; }}Registering a Resource
Section titled “Registering a Resource”@Overrideprotected void setup() { ResourceType<EntityStore, WeatherResource> weatherType = getEntityStoreRegistry().registerResource( WeatherResource.class, WeatherResource::new );}Accessing Resources
Section titled “Accessing Resources”// Get resource from storeWeatherResource weather = store.getResource(weatherType);weather.setCurrentWeather("rain");Systems
Section titled “Systems”Systems contain the logic that operates on entities. They query for entities with specific components and process them.
System Base Class
Section titled “System Base Class”import com.hypixel.hytale.component.system.System;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class HealthRegenSystem extends System<EntityStore> {
private ComponentType<EntityStore, HealthComponent> healthType;
public HealthRegenSystem() { // Register components this system needs this.healthType = registerComponent(HealthComponent.class, HealthComponent::new); }}System Types
Section titled “System Types”| System Type | Description |
|---|---|
TickingSystem | Runs every tick |
TickableSystem | Ticking with enable/disable control |
EntityTickingSystem | Processes each matching entity per tick |
ArchetypeTickingSystem | Batch processes entities by archetype |
ArchetypeChunkSystem | Processes archetype chunks |
EventSystem | Responds to ECS events |
EntityEventSystem | Handles entity-specific events |
WorldEventSystem | Handles world-level events |
QuerySystem | Query-based entity iteration |
RefSystem | Tracks entity references |
RefChangeSystem | Detects reference changes |
StoreSystem | Store-level operations |
HolderSystem | Works with entity holders |
EntityTickingSystem Example
Section titled “EntityTickingSystem Example”import com.hypixel.hytale.component.ArchetypeChunk;import com.hypixel.hytale.component.CommandBuffer;import com.hypixel.hytale.component.Store;import com.hypixel.hytale.component.system.tick.EntityTickingSystem;import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
public class HealthRegenSystem extends EntityTickingSystem<EntityStore> {
private ComponentType<EntityStore, HealthComponent> healthType;
public HealthRegenSystem() { this.healthType = registerComponent(HealthComponent.class, HealthComponent::new); }
@Override public Query<EntityStore> getQuery() { return healthType; // Only process entities with HealthComponent }
@Override public void tick(float dt, int index, ArchetypeChunk<EntityStore> chunk, Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer) { // Get component from the archetype chunk at the given index HealthComponent health = chunk.getComponent(index, healthType); if (health.getHealth() < health.getMaxHealth()) { health.setHealth(Math.min(health.getHealth() + 0.1f * dt, health.getMaxHealth())); } }}Registering Systems
Section titled “Registering Systems”@Overrideprotected void setup() { getEntityStoreRegistry().registerSystem(new HealthRegenSystem());}Queries
Section titled “Queries”Queries filter entities based on their component composition.
Query Types
Section titled “Query Types”| Query | Description |
|---|---|
ComponentType | Entities that have this component |
AndQuery | Entities with ALL specified components |
OrQuery | Entities with ANY of the specified components |
NotQuery | Entities that DON’T have the component |
AnyQuery | Matches any entity |
ExactArchetypeQuery | Matches exact archetype |
Using Queries
Section titled “Using Queries”import com.hypixel.hytale.component.query.AndQuery;import com.hypixel.hytale.component.query.NotQuery;
// Entities with both Position and VelocityQuery<EntityStore> movingEntities = new AndQuery<>(positionType, velocityType);
// Entities with Health but NOT DeadQuery<EntityStore> aliveEntities = new AndQuery<>( healthType, new NotQuery<>(deadType));
// Use in system@Overridepublic Query<EntityStore> getQuery() { return new AndQuery<>(healthType, hungerType);}Entity References (Refs)
Section titled “Entity References (Refs)”Ref<ECS_TYPE> is a reference to an entity in a store.
Working with Refs
Section titled “Working with Refs”// Check if ref is validif (ref.isValid()) { // Get component HealthComponent health = store.getComponent(ref, healthType);
// Check if entity has component if (store.hasComponent(ref, healthType)) { // ... }}Creating Entities
Section titled “Creating Entities”// Create a holder with componentsHolder<EntityStore> holder = store.getRegistry().newHolder();holder.addComponent(positionType, new PositionComponent(x, y, z));holder.addComponent(healthType, new HealthComponent());
// Spawn entity from holderRef<EntityStore> ref = store.spawn(holder);Removing Entities
Section titled “Removing Entities”store.destroy(ref);Holders
Section titled “Holders”Holder<ECS_TYPE> is a temporary container for entity components, used when creating entities or transferring component data.
// Create holderHolder<EntityStore> holder = store.getRegistry().newHolder();
// Add componentsholder.addComponent(componentType, component);
// Get componentComponent comp = holder.getComponent(componentType);
// Check for componentif (holder.getArchetype().contains(componentType)) { // ...}
// Clone holderHolder<EntityStore> cloned = holder.clone();Component Serialization
Section titled “Component Serialization”For components that need to be saved/loaded, define a codec:
public class PersistentComponent implements Component<EntityStore> { private int level; private String name;
public static final BuilderCodec<PersistentComponent> CODEC = BuilderCodec .builder(PersistentComponent.class, PersistentComponent::new) .append(new KeyedCodec<>("Level", Codec.INTEGER), (c, v) -> c.level = v, c -> c.level) .add() .append(new KeyedCodec<>("Name", Codec.STRING), (c, v) -> c.name = v, c -> c.name) .add() .build();
// ... getters, setters, clone}Register with serialization:
getEntityStoreRegistry().registerComponent( PersistentComponent.class, "myplugin:persistent", PersistentComponent.CODEC);Best Practices
Section titled “Best Practices”- Keep components pure data: No game logic in components
- Systems do the work: All behavior goes in systems
- Use appropriate queries: Only query for what you need
- Cache ComponentTypes: Store them as fields, not in methods
- Check ref validity: Always check
ref.isValid()before use - Clone when copying: Use
clone()for component copies - Register in setup(): All registrations happen during plugin setup
Complete Example
Section titled “Complete Example”public class DamagePlugin extends JavaPlugin {
private ComponentType<EntityStore, HealthComponent> healthType; private ComponentType<EntityStore, DamageOverTimeComponent> dotType;
public DamagePlugin(JavaPluginInit init) { super(init); }
@Override protected void setup() { // Register components this.healthType = getEntityStoreRegistry().registerComponent( HealthComponent.class, HealthComponent::new );
this.dotType = getEntityStoreRegistry().registerComponent( DamageOverTimeComponent.class, DamageOverTimeComponent::new );
// Register system getEntityStoreRegistry().registerSystem(new DamageOverTimeSystem(healthType, dotType)); }}
public class DamageOverTimeSystem extends EntityTickingSystem<EntityStore> {
private final ComponentType<EntityStore, HealthComponent> healthType; private final ComponentType<EntityStore, DamageOverTimeComponent> dotType;
public DamageOverTimeSystem( ComponentType<EntityStore, HealthComponent> healthType, ComponentType<EntityStore, DamageOverTimeComponent> dotType ) { this.healthType = healthType; this.dotType = dotType; }
@Override public Query<EntityStore> getQuery() { return new AndQuery<>(healthType, dotType); }
@Override public void tick(float dt, int index, ArchetypeChunk<EntityStore> chunk, Store<EntityStore> store, CommandBuffer<EntityStore> commandBuffer) { HealthComponent health = chunk.getComponent(index, healthType); DamageOverTimeComponent dot = chunk.getComponent(index, dotType);
health.setHealth(health.getHealth() - dot.getDamagePerTick());
dot.decrementDuration(); if (dot.getDuration() <= 0) { // Use command buffer for structural changes commandBuffer.removeComponent(chunk.getRef(index), dotType); } }}Related
Section titled “Related”- Plugin Registries - ECS registration methods
- Entity System - Entity types and spawning
- World System - World and chunk management