NPC Behavior System
Detailní dokumentace k NPC behavior systému v Hytale.
---
Přehled Architektury
NPC Behavior System
│
├── NPCPlugin
│ ├── spawnNPC() / spawnEntity()
│ ├── Builder registrations
│ └── Role asset loading
│
├── NPCEntity (Component)
│ ├── Role reference
│ ├── PathManager
│ ├── DamageData
│ └── Blackboard views
│
├── Role (Behavior definition)
│ ├── CombatSupport
│ ├── StateSupport
│ ├── WorldSupport
│ ├── EntitySupport
│ ├── PositionCache
│ └── MotionControllers
│
└── Instruction (Behavior tree node)
├── Sensor (podmínka)
├── BodyMotion (pohyb těla)
├── HeadMotion (pohyb hlavy)
└── ActionList (akce)
---
NPCPlugin
Hlavní plugin pro správu NPC:
public class NPCPlugin extends JavaPlugin {
public static final String ROLE_ASSETS_PATH = "Server/NPC/Roles"; public static final PluginManifest MANIFEST = PluginManifest.corePlugin(NPCPlugin.class)
.depends(EntityModule.class)
.depends(EntityStatsModule.class)
// ... další závislosti
.build();
// Singleton
public static NPCPlugin get() { return instance; }
// Spawn NPC
public Pair, INonPlayerCharacter> spawnNPC(
@Nonnull Store store,
@Nonnull String npcType,
@Nullable String groupType,
@Nonnull Vector3d position,
@Nonnull Vector3f rotation
);
// Spawn entity
public Pair, NPCEntity> spawnEntity(
@Nonnull Store store,
int roleIndex,
@Nonnull Vector3d position,
@Nullable Vector3f rotation,
@Nullable Model spawnModel,
@Nullable TriConsumer, Store> postSpawn
);
// Role management
public int getIndex(String roleName);
public String getName(int roleIndex);
public Builder tryGetCachedValidRole(int roleIndex);
}
Spawn NPC
// Základní spawn
World world = player.getWorld();
world.execute(() -> {
Store store = world.getEntityStore().getStore(); if (result != null) {
Ref npcRef = result.first();
INonPlayerCharacter npc = result.second();
// NPC spawnut
}
});
Spawn s Parametry
world.execute(() -> {
Store store = world.getEntityStore().getStore();
NPCPlugin npcPlugin = NPCPlugin.get(); int roleIndex = npcPlugin.getIndex("Zombie");
if (roleIndex < 0) {
// Role neexistuje
return;
}
---
NPCEntity
Komponenta NPC entity:
public class NPCEntity extends LivingEntity implements INonPlayerCharacter {
public static final BuilderCodec CODEC; // Role
@Nullable private Role role;
private int roleIndex = Integer.MIN_VALUE;
private String roleName;
// Path
private PathManager pathManager = new PathManager();
// Damage tracking
private final DamageData damageData = new DamageData();
// Leash (spawn) position
private final Vector3d leashPoint = new Vector3d();
private float leashHeading;
private float leashPitch;
// Despawn
private boolean isDespawning;
private float despawnRemainingSeconds;
// ComponentType
public static ComponentType getComponentType() {
return EntityModule.get().getComponentType(NPCEntity.class);
}
// Role
@Nullable
public Role getRole() {
return this.role;
}
// Animace
public void playAnimation(
@Nonnull Ref ref,
@Nonnull AnimationSlot animationSlot,
@Nullable String animationId,
@Nonnull ComponentAccessor componentAccessor
);
// Inventory
@Override
protected Inventory createDefaultInventory() {
return new Inventory((short)0, Inventory.DEFAULT_ARMOR_CAPACITY, (short)3, (short)0, (short)0);
}
// Damage
public boolean getCanCauseDamage(
@Nonnull Ref attackerRef,
@Nonnull ComponentAccessor componentAccessor
) {
return this.role.getCombatSupport().getCanCauseDamage(attackerRef, componentAccessor);
}
}
---
Role
Hlavní třída definující chování NPC:
public class Role implements IAnnotatedComponentCollection {
public static final double INTERACTION_PLAYER_DISTANCE = 10.0; // Support třídy
@Nonnull protected final CombatSupport combatSupport;
@Nonnull protected final StateSupport stateSupport;
@Nonnull protected final MarkedEntitySupport markedEntitySupport;
@Nonnull protected final WorldSupport worldSupport;
@Nonnull protected final EntitySupport entitySupport;
@Nonnull protected final PositionCache positionCache;
@Nonnull protected final DebugSupport debugSupport;
// Stats
protected final int initialMaxHealth;
protected final double knockbackScale;
protected final double inertia;
// Motion
protected final Steering bodySteering = new Steering();
protected final Steering headSteering = new Steering();
@Nonnull protected Map motionControllers;
protected MotionController activeMotionController;
// Instructions
protected Instruction rootInstruction;
@Nullable protected Instruction interactionInstruction;
@Nullable protected Instruction deathInstruction;
// Properties
protected String roleName;
protected int roleIndex;
protected String appearance;
protected boolean invulnerable;
protected boolean breathesInAir;
protected boolean breathesInWater;
protected String dropListId;
// Konstruktor
public Role(@Nonnull BuilderRole builder, @Nonnull BuilderSupport builderSupport) {
// Inicializace supports
this.combatSupport = new CombatSupport(npcComponent, builder, builderSupport);
this.stateSupport = new StateSupport(builder, builderSupport);
this.markedEntitySupport = new MarkedEntitySupport(npcComponent);
this.worldSupport = new WorldSupport(npcComponent, builder, builderSupport);
this.entitySupport = new EntitySupport(npcComponent, builder);
this.positionCache = new PositionCache(this);
this.debugSupport = new DebugSupport(npcComponent, builder);
// Načti instructions
List instructionList = builder.getInstructionList(builderSupport);
Instruction[] instructions = instructionList.toArray(Instruction[]::new);
this.rootInstruction = Instruction.createRootInstruction(instructions, builderSupport);
this.interactionInstruction = builder.getInteractionInstruction(builderSupport);
this.deathInstruction = builder.getDeathInstruction(builderSupport);
}
// Tick
public void tick(@Nonnull Ref ref, float tickTime, @Nonnull Store store) {
// Zpracuj deferred actions
// Compute actions and steering
this.computeActionsAndSteering(ref, tickTime, this.bodySteering, this.headSteering, store);
}
// Lifecycle
public void loaded();
public void spawned(@Nonnull Holder holder, @Nonnull NPCEntity npcComponent);
public void unloaded();
public void removed();
public void teleported(@Nonnull World from, @Nonnull World to);
}
Role Supports
// Combat support
CombatSupport combatSupport = role.getCombatSupport();
boolean canDamage = combatSupport.getCanCauseDamage(attackerRef, componentAccessor);// State support
StateSupport stateSupport = role.getStateSupport();
// Správa stavů NPC
// World support
WorldSupport worldSupport = role.getWorldSupport();
// Interakce se světem
// Entity support
EntitySupport entitySupport = role.getEntitySupport();
// Správa entity
// Position cache
PositionCache positionCache = role.getPositionCache();
// Cache pozic
// Marked entity support
MarkedEntitySupport markedEntitySupport = role.getMarkedEntitySupport();
// Správa označených entit (targets)
---
Instruction
Uzel behavior tree obsahující sensor, motion a actions:
public class Instruction implements RoleStateChange, IAnnotatedComponentCollection {
public static final Instruction[] EMPTY_ARRAY = new Instruction[0]; // Identifikace
@Nullable protected final String name;
@Nullable protected final String tag;
protected int index;
// Behavior komponenty
protected final Sensor sensor;
@Nullable protected final BodyMotion bodyMotion;
@Nullable protected final HeadMotion headMotion;
@Nonnull protected final ActionList actions;
// Child instructions
protected final Instruction[] instructionList;
// Properties
protected final double weight;
protected final boolean treeMode;
protected final boolean continueAfter;
// Konstruktor
public Instruction(@Nonnull BuilderInstruction builder, Sensor sensor,
@Nullable Instruction[] instructionList,
@Nonnull BuilderSupport support) {
this.name = builder.getName();
this.tag = builder.getTag();
this.sensor = sensor;
if (instructionList != null) {
// Branch node
this.instructionList = instructionList;
this.bodyMotion = null;
this.headMotion = null;
this.actions = ActionList.EMPTY_ACTION_LIST;
} else {
// Leaf node
this.instructionList = EMPTY_ARRAY;
this.bodyMotion = builder.getBodyMotion(support);
this.headMotion = builder.getHeadMotion(support);
this.actions = builder.getActionList(support);
}
}
// Gettery
public Sensor getSensor() { return this.sensor; }
public BodyMotion getBodyMotion() { return this.bodyMotion; }
public HeadMotion getHeadMotion() { return this.headMotion; }
// Lifecycle
public void loaded(Role role);
public void spawned(Role role);
public void unloaded(Role role);
public void removed(Role role);
public void teleported(Role role, World from, World to);
// Execution
public void execute(Ref ref, Role role, double tickTime, Store store);
}
---
Sensor
Interface pro detekci podmínek:
public interface Sensor extends RoleStateChange, IAnnotatedComponent {
Sensor NULL = new NullSensor(); // Hlavní metoda - kontroluje podmínku
boolean matches(
@Nonnull Ref ref,
@Nonnull Role role,
double tickTime,
@Nonnull Store store
);
// Informace o sensoru
@Nullable
InfoProvider getSensorInfo();
// Cleanup
default void done() {}
}
Typy Sensorů (z NPCPlugin)
| Sensor | Popis |
|--------|-------|
| Player | Detekce hráče |
| Mob | Detekce mobu |
| State | Kontrola stavu |
| Damage | Přijatý damage |
| Kill | Kill event |
| Beacon | Beacon detekce |
| Time | Časová podmínka |
| Path | Pathfinding |
| Weather | Počasí |
| Block | Blok detekce |
| Random | Náhodná podmínka |
| Flag | Flag kontrola |
| Target | Target kontrola |
| Timer | Timer event |
| InAir | V povětří |
| OnGround | Na zemi |
| InWater | Ve vodě |
| Light | Světlo |
| Age | Stáří NPC |
Kombinační Sensory
| Sensor | Popis |
|--------|-------|
| Any | Libovolný (OR) |
| And | Všechny (AND) |
| Or | Libovolný (OR) |
| Not | Negace |
---
Motion
Interface pro pohyb (tělo/hlava):
public interface Motion extends RoleStateChange, IAnnotatedComponent { // Aktivace/Deaktivace
default void activate(@Nonnull Ref ref, @Nonnull Role role,
@Nonnull ComponentAccessor componentAccessor) {}
default void deactivate(@Nonnull Ref ref, @Nonnull Role role,
@Nonnull ComponentAccessor componentAccessor) {}
// Před výpočtem steering
default void preComputeSteering(@Nonnull Ref ref, @Nonnull Role role,
@Nullable InfoProvider provider,
@Nonnull Store store) {}
// Hlavní výpočet steering
boolean computeSteering(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Steering steering,
@Nonnull ComponentAccessor componentAccessor
);
}
BodyMotion
public interface BodyMotion extends Motion {
@Nullable
default BodyMotion getSteeringMotion() {
return this;
}
}
HeadMotion
public interface HeadMotion extends Motion {
}
Typy BodyMotion (z NPCPlugin)
| BodyMotion | Popis |
|------------|-------|
| Nothing | Žádný pohyb |
| Wander | Náhodné putování |
| WanderInCircle | Putování v kruhu |
| WanderInRect | Putování v obdélníku |
| Flee | Útěk |
| Seek | Hledání cíle |
| Path | Pathfinding |
| Teleport | Teleportace |
| Land | Přistání |
| TakeOff | Vzlet |
| MatchLook | Kopírování pohledu |
| MaintainDistance | Udržení vzdálenosti |
| Timer | Časovaný |
| Sequence | Sekvence |
Typy HeadMotion (z NPCPlugin)
| HeadMotion | Popis |
|------------|-------|
| Nothing | Žádný pohyb |
| Aim | Zamíření |
| Watch | Sledování |
| Observe | Pozorování |
| Sequence | Sekvence |
| Timer | Časovaný |
---
Action
Interface pro akce:
public interface Action extends RoleStateChange, IAnnotatedComponent {
Action[] EMPTY_ARRAY = new Action[0]; // Kontrola možnosti provedení
boolean canExecute(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Store store
);
// Provedení akce
boolean execute(
@Nonnull Ref ref,
@Nonnull Role role,
@Nullable InfoProvider provider,
double tickTime,
@Nonnull Store store
);
// Aktivace/Deaktivace
void activate(Role role, InfoProvider provider);
void deactivate(Role role, InfoProvider provider);
boolean isActivated();
}
Typy Akcí (z NPCPlugin)
| Action | Popis |
|--------|-------|
| Attack | Útok |
| Spawn | Spawn entity |
| State | Změna stavu |
| Inventory | Manipulace s inventářem |
| PlaySound | Přehrání zvuku |
| Despawn | Despawn |
| PlayAnimation | Přehrání animace |
| SpawnParticles | Spawn částic |
| Die | Smrt |
| Role | Změna role |
| SetFlag | Nastavení flagu |
| DropItem | Zahození itemu |
| PickUpItem | Sebrání itemu |
| Beacon | Beacon akce |
| DisplayName | Změna jména |
| Crouch | Přikrčení |
| PlaceBlock | Položení bloku |
| ApplyEntityEffect | Aplikace efektu |
| SetStat | Nastavení statu |
Timer Actions
| Action | Popis |
|--------|-------|
| TimerStart | Start timeru |
| TimerContinue | Pokračování timeru |
| TimerPause | Pauza timeru |
| TimerModify | Modifikace timeru |
| TimerStop | Stop timeru |
| TimerRestart | Restart timeru |
---
EntityFilter
Filtry pro výběr entit:
// Typy filtrů (z NPCPlugin)
public enum EntityFilterType {
Attitude, // Podle attitude
LineOfSight, // Přímý pohled
HeightDifference, // Výškový rozdíl
ViewSector, // Úhel pohledu
Combat, // Combat stav
ItemInHand, // Item v ruce
NPCGroup, // NPC skupina
MovementState, // Stav pohybu
SpotsMe, // Vidí mě
StandingOnBlock, // Stojí na bloku
Stat, // Stat hodnota
Inventory, // Inventář
Not, // Negace
And, // AND
Or, // OR
Altitude, // Nadmořská výška
InsideBlock // Uvnitř bloku
}
---
MotionController
Kontrolér pro pohyb NPC:
public abstract class MotionController {
protected Role role;
protected double inertia;
protected double knockbackScale; public void setRole(Role role) { this.role = role; }
public void setInertia(double inertia) { this.inertia = inertia; }
public void setKnockbackScale(double knockbackScale) { this.knockbackScale = knockbackScale; }
public abstract void activate();
public abstract void deactivate();
public abstract void spawned();
public abstract void updateMovementState(
Ref ref,
MovementStates movementStates,
Steering steering,
Vector3d velocity,
ComponentAccessor componentAccessor
);
public abstract void addForce(Vector3d velocity, VelocityConfig velocityConfig);
public abstract void forceVelocity(Vector3d velocity, VelocityConfig velocityConfig, boolean ignoreDamping);
public abstract boolean onGround();
}
---
NPC Systémy
RoleSystems.BehaviourTickSystem
Hlavní tick systém pro NPC behavior:
public static class BehaviourTickSystem extends TickingSystem { @Override
public void tick(float dt, int systemIndex, @Nonnull Store store) {
// Pro každou NPC entitu
store.forEachChunk(npcComponentType, (archetypeChunk, commandBuffer) -> {
for (int index = 0; index < archetypeChunk.size(); index++) {
entities.add(archetypeChunk.getReferenceTo(index));
}
});
// Tick každé NPC
for (Ref entityReference : entities) {
if (entityReference.isValid()) {
NPCEntity npcComponent = store.getComponent(entityReference, npcComponentType);
try {
Role role = npcComponent.getRole();
role.tick(entityReference, tickLength, store);
} catch (Exception e) {
// Error handling - remove NPC
store.removeEntity(entityReference, RemoveReason.REMOVE);
}
}
}
}
}
NPCSystems.AddedSystem
Systém při přidání NPC:
public static class AddedSystem extends RefSystem { @Override
public void onEntityAdded(Ref ref, AddReason reason,
Store store, CommandBuffer commandBuffer) {
NPCEntity npcComponent = store.getComponent(ref, npcComponentType);
Role role = npcComponent.getRole();
if (role == null) {
// NPC bez role - odstranit
commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
return;
}
// Inicializace
npcComponent.initBlockChangeBlackboardView(ref, commandBuffer);
role.loaded();
// Přidej komponenty
commandBuffer.ensureComponent(ref, PrefabCopyableComponent.getComponentType());
commandBuffer.ensureComponent(ref, PositionDataComponent.getComponentType());
commandBuffer.ensureComponent(ref, MovementAudioComponent.getComponentType());
if (reason == AddReason.SPAWN) {
NewSpawnComponent newSpawn = new NewSpawnComponent(role.getSpawnLockTime());
commandBuffer.addComponent(ref, NewSpawnComponent.getComponentType(), newSpawn);
}
}
@Override
public void onEntityRemove(Ref ref, RemoveReason reason,
Store store, CommandBuffer commandBuffer) {
NPCEntity npcComponent = store.getComponent(ref, npcComponentType);
switch (reason) {
case REMOVE:
npcComponent.getRole().removed();
break;
case UNLOAD:
npcComponent.getRole().unloaded();
break;
}
}
@Override
public Query getQuery() {
return npcComponentType;
}
}
---
Role Assets
Role jsou definovány v Server/NPC/Roles/:
{
"Type": "Role",
"Model": "mob_zombie",
"MaxHealth": 20,
"Appearance": "zombie_default",
"Invulnerable": false,
"BreathesInAir": true,
"BreathesInWater": false,
"KnockbackScale": 1.0,
"DropListId": "zombie_drops",
"MotionControllers": {
"ground": {
"Type": "GroundMotionController",
"MaxSpeed": 5.0
}
},
"InitialMotionController": "ground",
"Instructions": [
{
"Sensor": {
"Type": "Player",
"Range": 16.0
},
"BodyMotion": {
"Type": "Seek"
},
"HeadMotion": {
"Type": "Watch"
},
"Actions": [
{
"Type": "Attack"
}
]
},
{
"Sensor": {
"Type": "Any"
},
"BodyMotion": {
"Type": "Wander"
}
}
]
}
---
Příklady
Custom NPC Spawner
public class CustomNPCSpawner { public void spawnZombie(World world, Vector3d position) {
world.execute(() -> {
Store store = world.getEntityStore().getStore();
if (result != null) {
Ref npcRef = result.first();
getLogger().atInfo().log("Spawned zombie at %s", position);
}
});
}
}
NPC Event Listener
public class NPCDeathListener extends DeathSystems.OnDeathSystem { @Override
public Query getQuery() {
return NPCEntity.getComponentType();
}
@Override
public void onComponentAdded(Ref ref, DeathComponent component,
Store store, CommandBuffer commandBuffer) {
NPCEntity npc = store.getComponent(ref, NPCEntity.getComponentType());
if (npc == null) return;
String roleName = npc.getRoleName();
getLogger().atInfo().log("NPC %s died!", roleName);
// Custom death handling
}
}
NPC Damage Modifier
public class NPCDamageModifier extends DamageEventSystem { @Override
public Query getQuery() {
return NPCEntity.getComponentType();
}
@Override
public void handle(int index, ArchetypeChunk chunk,
Store store, CommandBuffer commandBuffer,
Damage damage) {
NPCEntity npc = chunk.getComponent(index, NPCEntity.getComponentType());
if (npc == null) return;
Role role = npc.getRole();
if (role.isInvulnerable()) {
damage.setCancelled(true);
return;
}
// Modifikace damage
float amount = damage.getAmount();
damage.setAmount(amount * 0.8f); // 20% redukce
}
}
---
Shrnutí
| Třída | Účel |
|-------|------|
| NPCPlugin | Hlavní NPC plugin |
| NPCEntity | NPC komponenta |
| Role | Behavior definice |
| Instruction | Behavior tree uzel |
| Sensor | Detekce podmínek |
| BodyMotion | Pohyb těla |
| HeadMotion | Pohyb hlavy |
| Action | Akce |
| MotionController | Kontrolér pohybu |
| Support | Účel |
|---------|------|
| CombatSupport | Combat logika |
| StateSupport | Správa stavů |
| WorldSupport | Interakce se světem |
| EntitySupport | Správa entity |
| PositionCache | Cache pozic |
| MarkedEntitySupport | Správa targets |
| Metoda | Popis |
|--------|-------|
| NPCPlugin.spawnNPC() | Spawn NPC podle jména role |
| NPCPlugin.spawnEntity() | Spawn NPC podle role indexu |
| NPCPlugin.getIndex() | Získá index role |
| Role.tick() | Tick behavior |
| Sensor.matches() | Kontrola podmínky |
| Motion.computeSteering() | Výpočet pohybu |
| Action.execute() | Provedení akce |