Systems
Systémy v ECS architektuře Hytale - logika operující nad komponentami.
---
Co je Systém?
Systém obsahuje logiku a operuje nad entitami s určitými komponentami. Na rozdíl od komponent (data), systémy obsahují behavior.
Komponenta = DATA (co entita má)
Systém = LOGIKA (co entita dělá)
---
Typy Systémů
EntityEventSystem
Reaguje na ECS eventy (BreakBlockEvent, DeathEvent, atd.)
public class BlockBreakSystem extends EntityEventSystem { public BlockBreakSystem() {
super(BreakBlockEvent.class);
}
@Override
public void handle(
int i,
@Nonnull ArchetypeChunk chunk,
@Nonnull Store store,
@Nonnull CommandBuffer commandBuffer,
@Nonnull BreakBlockEvent event
) {
if (event.getBlockType() == BlockType.EMPTY) return;
Ref ref = chunk.getReferenceTo(i);
Player player = store.getComponent(ref, Player.getComponentType());
if (player != null) {
player.sendMessage(Message.raw("Rozbil jsi blok!"));
}
}
@Nullable
@Override
public Query getQuery() {
return PlayerRef.getComponentType();
}
}
RefChangeSystem
Reaguje na změny komponent (přidání, změna, odebrání).
public class EnterBedSystem extends RefChangeSystem { public static final Query QUERY =
Query.and(MountedComponent.getComponentType(), PlayerRef.getComponentType());
@Override
public ComponentType componentType() {
return MountedComponent.getComponentType();
}
@Override
public Query getQuery() {
return QUERY;
}
// Komponenta přidána
public void onComponentAdded(
@Nonnull Ref ref,
@Nonnull MountedComponent component,
@Nonnull Store store,
@Nonnull CommandBuffer commandBuffer
) {
if (component.getBlockMountType() == BlockMountType.Bed) {
// Hráč si lehl do postele
onEnterBed(ref, store);
}
}
// Komponenta změněna
public void onComponentSet(
@Nonnull Ref ref,
@Nullable MountedComponent oldComponent,
@Nonnull MountedComponent newComponent,
@Nonnull Store store,
@Nonnull CommandBuffer commandBuffer
) {
if (newComponent.getBlockMountType() == BlockMountType.Bed) {
onEnterBed(ref, store);
}
}
// Komponenta odebrána
public void onComponentRemoved(
@Nonnull Ref ref,
@Nonnull MountedComponent component,
@Nonnull Store store,
@Nonnull CommandBuffer commandBuffer
) {
// Hráč vstal z postele
}
private void onEnterBed(Ref ref, Store store) {
// Logika pro vstup do postele
}
}
---
Anatomie EntityEventSystem
public class MySystem extends EntityEventSystem { // 1. Konstruktor - předej třídu eventu
public MySystem() {
super(MyEvent.class);
}
// 2. Handler - hlavní logika
@Override
public void handle(
int i, // Index v chunku
@Nonnull ArchetypeChunk chunk, // Chunk s entitami
@Nonnull Store store, // ECS store
@Nonnull CommandBuffer buffer, // Pro modifikace
@Nonnull MyEvent event // Event data
) {
// Získej referenci na entitu
Ref ref = chunk.getReferenceTo(i);
// Přístup ke komponentům
Player player = store.getComponent(ref, Player.getComponentType());
MyComponent myComp = store.getComponent(ref, MyPlugin.get().getMyComponentType());
if (player != null && myComp != null) {
// Logika
}
// Modifikace přes CommandBuffer
buffer.addComponent(ref, SomeType.getComponentType(), newComponent);
}
// 3. Query - které entity dostanou event
@Nullable
@Override
public Query getQuery() {
// Pouze entity s PlayerRef
return PlayerRef.getComponentType();
// Pro všechny entity:
// return null;
}
}
---
Query
Query filtruje které entity jsou zpracovány systémem.
Jednoduchá Query
@Override
public Query getQuery() {
// Entity s PlayerRef komponentou
return PlayerRef.getComponentType();
}
Složená Query
@Override
public Query getQuery() {
// Entity s PlayerRef A CustomComponent
return Query.and(
PlayerRef.getComponentType(),
CustomComponent.getComponentType()
);
}
Bez Query (všechny entity)
@Override
public Query getQuery() {
return null; // Všechny entity
}
---
ArchetypeChunk
Chunk v ECS je skupina entit se stejnou kombinací komponent.
@Override
public void handle(int i, ArchetypeChunk chunk, ...) {
// Index i je pozice v chunku
Ref ref = chunk.getReferenceTo(i); // Procházení všech entit v chunku
for (int j = 0; j < chunk.size(); j++) {
Ref entityRef = chunk.getReferenceTo(j);
// Zpracuj entitu
}
}
---
CommandBuffer
Pro bezpečné modifikace během iterace.
@Override
public void handle(..., CommandBuffer commandBuffer, ...) {
Ref ref = chunk.getReferenceTo(i); // Přidání komponenty
commandBuffer.addComponent(ref, MyType.getComponentType(), new MyComponent());
// Odebrání komponenty
commandBuffer.removeComponent(ref, MyType.getComponentType());
// Zničení entity
commandBuffer.destroyEntity(ref);
}
Proč CommandBuffer?
- Přímé modifikace během iterace mohou způsobit problémy
- CommandBuffer shromažďuje změny a aplikuje je po dokončení iterace
- Thread-safe
---
Registrace Systému
@Override
protected void setup() {
// Registrace systémů
getEntityStoreRegistry().registerSystem(new BlockBreakSystem());
getEntityStoreRegistry().registerSystem(new DeathSystem());
getEntityStoreRegistry().registerSystem(new EnterBedSystem());
}
---
Příklad: Kompletní Plugin se Systémy
public class StatsPlugin extends JavaPlugin { private static StatsPlugin instance;
private ComponentType statsType;
public StatsPlugin(JavaPluginInit init) {
super(init);
}
public static StatsPlugin get() {
return instance;
}
@Override
protected void setup() {
instance = this;
// Registrace komponenty
statsType = getEntityStoreRegistry().registerComponent(
StatsComponent.class,
StatsComponent::new
);
// Registrace systémů
getEntityStoreRegistry().registerSystem(new BlockBreakStatsSystem());
getEntityStoreRegistry().registerSystem(new DeathStatsSystem());
// Inicializace stats pro nové hráče
getEventRegistry().registerGlobal(PlayerReadyEvent.class, event -> {
Player player = event.getPlayer();
World world = player.getWorld();
world.execute(() -> {
Ref ref = player.getRef();
Store store = ref.getStore();
store.ensureAndGetComponent(ref, statsType);
});
});
}
public ComponentType getStatsType() {
return statsType;
}
}
// Systém pro sledování rozbitých bloků
public class BlockBreakStatsSystem extends EntityEventSystem {
public BlockBreakStatsSystem() {
super(BreakBlockEvent.class);
}
@Override
public void handle(int i, ArchetypeChunk chunk,
Store store, CommandBuffer buffer,
BreakBlockEvent event) {
if (event.getBlockType() == BlockType.EMPTY) return;
Ref ref = chunk.getReferenceTo(i);
StatsComponent stats = store.getComponent(ref, StatsPlugin.get().getStatsType());
if (stats != null) {
stats.incrementBlocksBroken();
}
}
@Override
public Query getQuery() {
return PlayerRef.getComponentType();
}
}
// Systém pro sledování smrtí
public class DeathStatsSystem extends EntityEventSystem {
public DeathStatsSystem() {
super(DeathEvent.class);
}
@Override
public void handle(int i, ArchetypeChunk chunk,
Store store, CommandBuffer buffer,
DeathEvent event) {
Ref ref = chunk.getReferenceTo(i);
StatsComponent stats = store.getComponent(ref, StatsPlugin.get().getStatsType());
if (stats != null) {
stats.incrementDeaths();
}
}
@Override
public Query getQuery() {
return PlayerRef.getComponentType();
}
}
// Komponenta
public class StatsComponent implements Component {
private int blocksBroken = 0;
private int deaths = 0;
public void incrementBlocksBroken() { blocksBroken++; }
public void incrementDeaths() { deaths++; }
public int getBlocksBroken() { return blocksBroken; }
public int getDeaths() { return deaths; }
@Override
public Component clone() {
StatsComponent copy = new StatsComponent();
copy.blocksBroken = this.blocksBroken;
copy.deaths = this.deaths;
return copy;
}
}
---
Thread Safety v Systémech
Systémy jsou automaticky spouštěny na správném world threadu:
EntityEventSystem.handle() - běží na world threaduRefChangeSystem callbacks - běží na world threaduworld.execute()---
Shrnutí
| Typ Systému | Účel | Kdy použít |
|-------------|------|------------|
| EntityEventSystem | Reakce na ECS eventy | BreakBlockEvent, DeathEvent |
| RefChangeSystem | Reakce na změny komponent | Sledování přidání/odebrání komponent |
| Třída | Účel |
|-------|------|
| Query | Filtrování entit |
| ArchetypeChunk | Skupina entit se stejnými komponentami |
| CommandBuffer | Bezpečné modifikace během iterace |
| Store | Přístup ke komponentám |
| Ref | Reference na entitu |