Entity Creation
Detailní dokumentace k vytváření entit v Hytale.
---
Přehled Architektury
Entity System
│
├── Entity (Abstract Component)
│ ├── networkId, world, reference
│ ├── remove(), loadIntoWorld()
│ └── CODEC pro serializaci
│
├── LivingEntity (extends Entity)
│ ├── Inventory
│ ├── StatModifiersManager
│ └── Fall damage tracking
│
├── NPCEntity (extends LivingEntity)
│ ├── Role
│ ├── PathManager
│ ├── DamageData
│ └── Blackboard views
│
└── Entity Components
├── TransformComponent (pozice, rotace)
├── NetworkId (síťová identifikace)
├── BoundingBox (kolize)
├── ModelComponent (3D model)
└── ... další komponenty
---
Entity Třída
Základní abstraktní třída pro všechny entity:
public abstract class Entity implements Component {
public static final BuilderCodec CODEC; protected int networkId = -1;
@Nullable protected UUID legacyUuid;
@Nullable protected World world;
@Nullable protected Ref reference;
// Konstruktor
public Entity() {}
@Deprecated
public Entity(@Nullable World world) {
this.networkId = world != null ? world.getEntityStore().takeNextNetworkId() : -1;
this.world = world;
}
// Odstranění entity
public boolean remove() {
this.world.debugAssertInTickingThread();
if (this.wasRemoved.getAndSet(true)) {
return false;
}
// Dispatch EntityRemoveEvent
IEventDispatcher dispatcher =
HytaleServer.get().getEventBus().dispatchFor(EntityRemoveEvent.class, key);
if (dispatcher.hasListener()) {
dispatcher.dispatch(new EntityRemoveEvent(this));
}
// Odstraň ze store
if (this.reference.isValid()) {
this.world.getEntityStore().getStore().removeEntity(this.reference, RemoveReason.REMOVE);
}
return true;
}
// Načtení do světa
public void loadIntoWorld(@Nonnull World world) {
if (this.world != null) {
throw new IllegalArgumentException("Entity is already in a world!");
}
this.world = world;
if (this.networkId == -1) {
this.networkId = world.getEntityStore().takeNextNetworkId();
}
}
}
---
LivingEntity
Entity se životem, inventářem a staty:
public abstract class LivingEntity extends Entity {
public static final BuilderCodec CODEC; private final StatModifiersManager statModifiersManager = new StatModifiersManager();
private Inventory inventory;
protected double currentFallDistance;
// Konstruktor
public LivingEntity() {
this.setInventory(this.createDefaultInventory());
}
// Abstraktní metoda pro výchozí inventář
protected abstract Inventory createDefaultInventory();
// Inventář
public Inventory getInventory() {
return this.inventory;
}
public Inventory setInventory(Inventory inventory) {
if (this.inventory != null) {
this.inventory.unregister();
}
inventory.setEntity(this);
this.inventory = inventory;
return inventory;
}
// Kontrola dýchání
public boolean canBreathe(
@Nonnull Ref ref,
@Nonnull BlockMaterial breathingMaterial,
int fluidId,
@Nonnull ComponentAccessor componentAccessor
) {
boolean invulnerable = componentAccessor.getArchetype(ref).contains(Invulnerable.getComponentType());
return invulnerable || breathingMaterial == BlockMaterial.Empty && fluidId == 0;
}
}
---
Holder Pattern
Vytvoření nové entity pomocí Holder pattern:
Základní Holder
public Holder createCustomEntity(Store store, Vector3d position) {
// 1. Vytvoř holder
Holder holder = EntityStore.REGISTRY.newHolder(); // 2. Přidej TransformComponent (pozice, rotace)
holder.addComponent(
TransformComponent.getComponentType(),
new TransformComponent(position, new Vector3f(0, 0, 0))
);
// 3. Přidej NetworkId (pro síťovou synchronizaci)
holder.addComponent(
NetworkId.getComponentType(),
new NetworkId(store.getExternalData().takeNextNetworkId())
);
// 4. Přidej BoundingBox (kolize)
holder.addComponent(
BoundingBox.getComponentType(),
new BoundingBox(/ bounding box data /)
);
return holder;
}
Entity s Modelem
public Holder createEntityWithModel(Store store, Vector3d position, Model model) {
Holder holder = EntityStore.REGISTRY.newHolder(); // Pozice
holder.addComponent(
TransformComponent.getComponentType(),
new TransformComponent(position, new Vector3f(0, 0, 0))
);
// Network ID
holder.addComponent(
NetworkId.getComponentType(),
new NetworkId(store.getExternalData().takeNextNetworkId())
);
// Model
holder.addComponent(
ModelComponent.getComponentType(),
new ModelComponent(model)
);
// Bounding box z modelu
holder.addComponent(
BoundingBox.getComponentType(),
new BoundingBox(model.getBoundingBox())
);
// Jmenovka
holder.addComponent(
Nameplate.getComponentType(),
new Nameplate("Entity Name")
);
return holder;
}
Speciální Marker Komponenty
// Nelze interagovat
holder.ensureComponent(Intangible.getComponentType());// Skrytá v adventure módu
holder.ensureComponent(HiddenFromAdventurePlayers.getComponentType());
// Neserializovat (neukládat)
holder.ensureComponent(EntityStore.REGISTRY.getNonSerializedComponentType());
// Nesmrtelná
holder.ensureComponent(Invulnerable.getComponentType());
// Zamrzlá (frozen)
holder.ensureComponent(Frozen.getComponentType());
---
Spawn Entity do Světa
Základní Spawn
public void spawnEntity(World world, Holder holder) {
world.execute(() -> {
Store store = world.getEntityStore().getStore();
store.addEntity(holder, AddReason.SPAWN);
});
}
AddReason Enum
public enum AddReason {
SPAWN, // Nově vytvořená entita
LOAD, // Načtená ze souboru/databáze
// ... další
}
Spawn při Načtení Chunku
private void onChunkPreLoadProcess(ChunkPreLoadProcessEvent event) {
WorldChunk chunk = event.getChunk();
BlockChunk blockChunk = chunk.getBlockChunk(); if (blockChunk == null) return;
int chunkX = blockChunk.getX();
int chunkZ = blockChunk.getZ();
World world = chunk.getWorld();
for (MyEntity entity : getEntitiesForChunk(chunkX, chunkZ)) {
Vector3d position = entity.getPosition();
// Ověř pozici v chunku
if (ChunkUtil.isInsideChunk(chunkX, chunkZ,
MathUtil.floor(position.x),
MathUtil.floor(position.z))) {
world.execute(() -> {
Store store = world.getEntityStore().getStore();
Holder holder = createEntityHolder(entity, store);
store.addEntity(holder, AddReason.LOAD);
});
}
}
}
---
Entity Komponenty
Základní Komponenty
| Komponenta | Popis |
|------------|-------|
| TransformComponent | Pozice a rotace |
| NetworkId | ID pro síťovou synchronizaci |
| BoundingBox | Kolizní box |
| ModelComponent | 3D model |
| Nameplate | Jmenovka nad entitou |
| UUIDComponent | Unikátní UUID |
| HeadRotation | Rotace hlavy |
TransformComponent
public class TransformComponent implements Component {
public static final BuilderCodec CODEC; private final Vector3d position = new Vector3d();
private final Vector3f rotation = new Vector3f();
// Konstruktory
public TransformComponent() {}
public TransformComponent(@Nonnull Vector3d position, @Nonnull Vector3f rotation) {
this.position.assign(position);
this.rotation.assign(rotation);
}
// Gettery/Settery
public Vector3d getPosition() { return this.position; }
public void setPosition(@Nonnull Vector3d position) { this.position.assign(position); }
public Vector3f getRotation() { return this.rotation; }
public void setRotation(@Nonnull Vector3f rotation) { this.rotation.assign(rotation); }
// Teleport (respektuje NaN hodnoty)
public void teleportPosition(@Nonnull Vector3d position) {
if (!Double.isNaN(position.getX())) this.position.setX(position.getX());
if (!Double.isNaN(position.getY())) this.position.setY(position.getY());
if (!Double.isNaN(position.getZ())) this.position.setZ(position.getZ());
}
// ComponentType
public static ComponentType getComponentType() {
return EntityModule.get().getTransformComponentType();
}
}
Speciální Marker Komponenty
| Komponenta | Popis |
|------------|-------|
| Intangible | Nelze interagovat |
| HiddenFromAdventurePlayers | Skrytá pro adventure hráče |
| Invulnerable | Nesmrtelná |
| Frozen | Zamrzlá (netikuje) |
| NewSpawnComponent | Právě spawnutá |
| FromWorldGen | Z world generátoru |
| FromPrefab | Z prefabu |
---
Práce s Existujícími Entitami
Získání Reference
// Z eventu
Ref ref = chunk.getReferenceTo(i);// Z hráče
Ref playerRef = player.getRef();
Čtení Komponent
world.execute(() -> {
Store store = world.getEntityStore().getStore(); // Získej komponentu
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (transform != null) {
Vector3d position = transform.getPosition();
}
// Kontrola existence komponenty
boolean hasComponent = store.getArchetype(ref).contains(SomeComponent.getComponentType());
});
Modifikace Entity
world.execute(() -> {
Store store = world.getEntityStore().getStore(); // Změna pozice
TransformComponent transform = store.getComponent(ref, TransformComponent.getComponentType());
if (transform != null) {
transform.setPosition(newPosition);
}
// Přidání komponenty
store.addComponent(ref, MyComponent.getComponentType(), new MyComponent());
// Zajištění komponenty (přidá pokud neexistuje)
store.ensureComponent(ref, SomeComponent.getComponentType());
// Odebrání komponenty
store.removeComponent(ref, SomeComponent.getComponentType());
});
Zničení Entity
// V systému s CommandBuffer
commandBuffer.removeEntity(ref, RemoveReason.REMOVE);// Přímo přes store
store.removeEntity(ref, RemoveReason.REMOVE);
// Přes Entity instanci
entity.remove();
RemoveReason Enum
public enum RemoveReason {
REMOVE, // Permanentní odstranění
UNLOAD, // Dočasné unload (chunk unload)
// ... další
}
---
EntityModule
Hlavní modul pro správu entity komponent:
public class EntityModule extends JavaPlugin {
public static final PluginManifest MANIFEST = PluginManifest.corePlugin(EntityModule.class)
.depends(/ dependencies /)
.build(); // Singleton
public static EntityModule get() { return instance; }
// Základní ComponentTypes
public ComponentType getTransformComponentType();
public ComponentType getBoundingBoxComponentType();
public ComponentType getNetworkIdComponentType();
public ComponentType getModelComponentType();
public ComponentType getPlayerComponentType();
public ComponentType getUuidComponentType();
public ComponentType getHeadRotationComponentType();
// Speciální ComponentTypes
public ComponentType getEffectControllerComponentType();
public ComponentType getMovementStatesComponentType();
public ComponentType getFrozenComponentType();
public ComponentType getInvulnerableComponentType();
// Generic ComponentType lookup
public > ComponentType getComponentType(Class clazz);
}
---
EntityRemoveEvent
Event při odstranění entity:
public class EntityRemoveSystem extends EntityEventSystem { public EntityRemoveSystem() {
super(EntityRemoveEvent.class);
}
@Override
public void handle(int i, ArchetypeChunk chunk,
Store store, CommandBuffer buffer,
EntityRemoveEvent event) {
Ref ref = chunk.getReferenceTo(i);
// Entita je odstraňována
// Proveď cleanup
}
@Override
public Query getQuery() {
return null; // Všechny entity
}
}
---
Příklady
Kompletní Entity Spawn
public class MyEntitySpawner { public void spawnCustomEntity(World world, Vector3d position, String name) {
world.execute(() -> {
Store store = world.getEntityStore().getStore();
// Vytvoř holder
Holder holder = EntityStore.REGISTRY.newHolder();
// Pozice
holder.addComponent(
TransformComponent.getComponentType(),
new TransformComponent(position, new Vector3f(0, 0, 0))
);
// Network ID
holder.addComponent(
NetworkId.getComponentType(),
new NetworkId(store.getExternalData().takeNextNetworkId())
);
// UUID
holder.addComponent(
UUIDComponent.getComponentType(),
new UUIDComponent(UUID.randomUUID())
);
// Jmenovka
holder.addComponent(
Nameplate.getComponentType(),
new Nameplate(name)
);
// Spawn
Ref ref = store.addEntity(holder, AddReason.SPAWN);
getLogger().atInfo().log("Spawned entity at %s with ref %s", position, ref);
});
}
}
Entity Tracker System
public class EntityTrackerSystem extends RefSystem { private final Map> trackedEntities = new ConcurrentHashMap<>();
@Override
public void onEntityAdded(Ref ref, AddReason reason,
Store store, CommandBuffer buffer) {
UUIDComponent uuid = store.getComponent(ref, UUIDComponent.getComponentType());
if (uuid != null) {
trackedEntities.put(uuid.getUuid(), ref);
}
}
@Override
public void onEntityRemove(Ref ref, RemoveReason reason,
Store store, CommandBuffer buffer) {
UUIDComponent uuid = store.getComponent(ref, UUIDComponent.getComponentType());
if (uuid != null) {
trackedEntities.remove(uuid.getUuid());
}
}
@Override
public Query getQuery() {
return UUIDComponent.getComponentType();
}
public Ref getEntity(UUID uuid) {
return trackedEntities.get(uuid);
}
}
---
Shrnutí
| Operace | Metoda |
|---------|--------|
| Vytvořit holder | EntityStore.REGISTRY.newHolder() |
| Přidat komponentu | holder.addComponent(type, component) |
| Zajistit komponentu | holder.ensureComponent(type) |
| Spawn entity | store.addEntity(holder, AddReason.SPAWN) |
| Získat network ID | store.getExternalData().takeNextNetworkId() |
| Získat komponentu | store.getComponent(ref, type) |
| Modifikovat komponentu | store.setComponent(ref, type, component) |
| Zničit entity | store.removeEntity(ref, RemoveReason.REMOVE) |
| Třída | Účel |
|-------|------|
| Entity | Základní abstraktní entita |
| LivingEntity | Entita s inventářem a staty |
| TransformComponent | Pozice a rotace |
| NetworkId | Síťová identifikace |
| BoundingBox | Kolizní box |
| EntityModule | Modul pro entity komponenty |
| EntityStore.REGISTRY | Registr pro vytváření holderů |
| AddReason | Použití |
|-----------|---------|
| SPAWN | Nově vytvořená entita |
| LOAD | Načtená entita |
| RemoveReason | Použití |
|--------------|---------|
| REMOVE | Permanentní odstranění |
| UNLOAD | Dočasné unload |