14 KiB
Actor Behavior System Design
Goal
Build a reusable actor behavior system for lobby actors and mini games. Behaviors are configured from JSON and can describe skills, actions, visual effects, projectiles, buffs, and logic effects. Client and server load the same JSON files; the server runs only authoritative logic, while the client consumes presentation events and synchronized authoritative results.
The first implementation scope is the shared core, example configuration, and automated tests. It does not directly integrate the lobby actor controller or any mini game UI yet.
Current Project Context
- Unity client shared framework code lives in
Client/Assets/Framework/Shared. Server/Framework.Shared/Framework.Shared.csprojcompiles the same shared source files from the client tree.- Mini game Core code follows the same pattern: a
scripts~source directory is compiled by both client and server projects. - The existing virtual HUD input work intentionally stops at raw joystick and action button events. This behavior system sits after that input layer and turns input into behavior requests.
- Existing character JSON uses
Client/Assets/Resources/Config, while the server needs direct file/string loading for the same JSON content.
Recommended Approach
Add a pure C# shared module under:
Client/Assets/Framework/Shared/ActorBehavior/
The module namespace is:
XWorld.Framework.ActorBehavior
The shared core does not reference Unity. It owns configuration models, deterministic runtime state, timeline triggering, projectile movement, buff ticking, logic effect target selection, and structured event output.
Unity-specific work is kept outside the core. Client code will later adapt TimelineAnimation, TimelineEffect, and projectile presentation events to Animator, prefab loading, and visual effects. Server code will later adapt authoritative logic events to real game attributes, snapshots, and network broadcasts.
Alternatives Considered
Shared Core First
This is the selected approach. It matches the existing shared framework design, keeps client and server behavior rules in one place, and lets lobby and mini games adopt the same API later.
Trade-off: the first version proves the system through tests and example configuration, not through a visible lobby skill.
Per Mini Game Behavior Core
Each mini game could package its own behavior runtime.
This isolates game modules, but it duplicates fighter/mage style character configuration and makes lobby reuse weak.
Unity Client First
The client could implement skills directly with Unity animation, prefab, and collision APIs, then the server could be added later.
This would show visuals quickly, but it would bind rules to Unity object lifetimes and make server authority hard to add cleanly.
Core Components
BehaviorConfigCatalog
Owns one actor type configuration set, such as fighter.
Inputs:
fighter_skill.jsonfighter_flyobj.jsonfighter_buff.json
Responsibilities:
- Parse JSON strings.
- Validate duplicate ids, missing references, invalid durations, invalid shape parameters, and unsupported enum values.
- Expose behavior, projectile, buff, and logic effect definitions by id.
BehaviorWorld
Owns runtime simulation for many actors and projectiles.
Responsibilities:
- Register multiple
BehaviorConfigCataloginstances. - Add, remove, and update actor runtime state.
- Switch an actor's active catalog by actor type.
- Start and stop behaviors.
- Tick active behavior timelines.
- Tick projectile movement and collision.
- Tick buffs by duration, interval, and repeat count.
- Drain generated
BehaviorEventinstances.
Primary API shape:
var catalog = BehaviorConfigCatalog.LoadFromJson(type, skillJson, flyObjJson, buffJson);
var world = new BehaviorWorld(new BehaviorWorldOptions { Mode = BehaviorRunMode.ServerLogic });
world.RegisterCatalog(catalog);
world.AddActor(actorState);
world.TryStartBehavior(actorId, "slash", BehaviorInput.FromDirection(x, z));
world.Tick(deltaTime);
IReadOnlyList<BehaviorEvent> events = world.DrainEvents();
ActorBehaviorController
Represents behavior state for one actor inside BehaviorWorld.
Responsibilities:
- Track the actor's active catalog type.
- Track the current active behavior and elapsed time.
- Enforce that only one active behavior runs at a time.
- Allow explicit special interruption when the current behavior permits interruption.
- Own active buffs for that actor.
Behavior control:
world.TryStartBehavior(actorId, "slash", BehaviorInput.FromDirection(1f, 0f));
world.StopBehavior(actorId, BehaviorStopReason.Stunned);
world.TryStartBehavior(actorId, "stun_fall", BehaviorInput.None);
Projectile Runtime
Projectiles are independent runtime objects configured by id.
Responsibilities:
- Spawn from behavior timelines or logic effects.
- Update position by configured movement mode.
- Check collision through configured collision shapes.
- Run projectile-owned timelines.
- Trigger logic effects on collision or on configured times.
First version movement modes:
LineParabolaHoming
First version collision mode:
- Sphere collision with radius and hit limit.
Buff Runtime
Buffs are special attached effects.
Responsibilities:
- Track duration.
- Tick at configured intervals.
- Respect repeat count.
- Apply stacking behavior.
- Trigger configured logic effects on add, tick, or remove.
First version stacking modes:
RefreshDurationIgnoreIfExistsStackIndependent
Logic Effect Runtime
Logic effects select targets by shape and request one or more business effects.
First version shapes:
SectorSphereRingLineLocked
First version effect operations:
Attribute: request an attribute delta, such ashp = -30.Buff: request applying a configured buff id.Projectile: request spawning a configured projectile id.
The core does not own the real business attribute table. It emits structured events or calls an adapter so lobby and mini games can decide how hp, moveSpeed, score, or custom attributes are applied.
JSON Configuration
Each actor type owns three files. File names use the actor type prefix:
fighter_skill.json
fighter_flyobj.json
fighter_buff.json
All files include:
type: actor type name, such asfighter.version: integer config version.
Behavior JSON
{
"type": "fighter",
"version": 1,
"behaviors": [
{
"id": "slash",
"duration": 0.8,
"canBeInterrupted": true,
"input": "DirectionOrTarget",
"timeline": [
{ "time": 0.0, "kind": "Animation", "animation": "slash_01", "duration": 0.8 },
{ "time": 0.12, "kind": "Effect", "prefab": "Assets/Game/Art/Effect/slash.prefab", "duration": 0.5 },
{ "time": 0.25, "kind": "LogicEffect", "effectId": "slash_hit" },
{ "time": 0.32, "kind": "Projectile", "flyObjectId": "blade_wave" }
],
"logicEffects": [
{
"id": "slash_hit",
"shape": "Sector",
"radius": 2.4,
"angle": 90,
"target": "Enemy",
"effects": [
{ "type": "Attribute", "attribute": "hp", "value": -30 }
]
}
]
}
]
}
Projectile JSON
{
"type": "fighter",
"version": 1,
"flyObjects": [
{
"id": "blade_wave",
"duration": 1.5,
"movement": { "kind": "Line", "speed": 6.0 },
"collision": { "shape": "Sphere", "radius": 0.4, "hitLimit": 1 },
"timeline": [
{ "time": 0.0, "kind": "Effect", "prefab": "Assets/Game/Art/Effect/blade_wave.prefab", "duration": 1.5 },
{ "time": 0.0, "kind": "LogicEffect", "effectId": "blade_wave_hit", "trigger": "OnCollision" }
],
"logicEffects": [
{
"id": "blade_wave_hit",
"shape": "Locked",
"target": "HitActor",
"effects": [
{ "type": "Attribute", "attribute": "hp", "value": -20 },
{ "type": "Buff", "buffId": "slow_01" }
]
}
]
}
]
}
Buff JSON
{
"type": "fighter",
"version": 1,
"buffs": [
{
"id": "slow_01",
"duration": 3.0,
"tickInterval": 1.0,
"repeatCount": 3,
"stacking": "RefreshDuration",
"logicEffects": [
{
"id": "slow_tick",
"shape": "Locked",
"target": "Owner",
"effects": [
{ "type": "Attribute", "attribute": "moveSpeed", "value": -0.2 }
]
}
]
}
]
}
Input Model
BehaviorInput supports:
NoneDirectionTargetPositionTargetActor
The existing virtual joystick and action pad can later map raw input events to this input model.
Examples:
- Joystick direction plus attack button maps to
Direction. - Drag-release skill maps to
DirectionorTargetPosition. - Target lock skill maps to
TargetActor.
Run Modes
ServerLogic
The server mode runs authoritative logic:
- Behavior start and stop.
- Timeline logic events.
- Projectile movement and collision.
- Buff add, tick, and remove.
- Logic effect target selection.
- Attribute, buff, and projectile requests.
It does not execute animation or effect presentation. It may still emit presentation events for logging or optional network relay, but these are not authoritative.
ClientPresentation
The client presentation mode consumes behavior state and events for visuals:
- Animation timeline events.
- Effect timeline events.
- Projectile presentation spawn and movement.
- Buff visual add/remove markers.
It does not perform authoritative target selection or attribute application.
ClientPredict
Client prediction may be added after the core is stable. It can run the same timeline and movement logic locally, then reconcile against server events or snapshots.
The first version can define the enum value but does not need full reconciliation.
Event Model
The core emits BehaviorEvent values. Events are deterministic data records, not callbacks into Unity.
First version event kinds:
BehaviorStartedBehaviorStoppedTimelineAnimationTimelineEffectProjectileSpawnedProjectileMovedProjectileHitBuffAddedBuffTickedBuffRemovedLogicEffectAppliedAttributeEffectRequested
Server and client consume the same event stream differently:
- Client consumes
TimelineAnimation,TimelineEffect, and projectile presentation events. - Server consumes
LogicEffectApplied,AttributeEffectRequested, buff events, and projectile spawn/hit events. - Network integration will later broadcast authoritative events and snapshots from the server to clients.
Targeting And Filtering
Actors tracked by the core have minimal deterministic state:
- Actor id.
- Catalog type.
- Position.
- Forward direction.
- Team or faction id.
- Alive/enabled flag.
- Optional radius for collision and target selection.
Logic effect target filters:
SelfAllyEnemyAllOwnerHitActor
The first version keeps filtering simple and deterministic. Complex gameplay rules can be added through adapter decisions later.
Error Handling
Configuration loading fails with clear validation messages for:
- Missing
type. - Mismatched type across skill, projectile, and buff JSON.
- Duplicate behavior, projectile, buff, or logic effect ids.
- Timeline entries with negative time.
- Timelines referencing missing logic effect ids.
- Behaviors referencing missing projectile ids.
- Logic operations referencing missing projectile or buff ids.
- Unsupported enum strings.
- Invalid shape parameters, such as negative radius or sector angle outside
(0, 360].
Runtime behavior:
- Starting an unknown behavior returns failure and emits no events.
- Starting a second behavior while one is active returns failure unless the current behavior was explicitly stopped or interrupted.
- Non-interruptible behaviors ignore special stop requests except terminal reasons like
Death. - Projectiles expire when duration ends or hit limit is reached.
- Buffs expire when duration ends or repeat count is reached.
Tests
Add xUnit tests in Server/Framework.Shared.Tests.
Required coverage:
- Loads a complete fighter config from three JSON strings.
- Rejects mismatched config type names.
- Rejects duplicate ids and missing references.
- Allows only one active behavior per actor.
- Allows special stop and behavior switch when the current behavior is interruptible.
- Triggers timeline animation, effect, logic effect, and projectile entries at configured times.
- Selects targets for sector, sphere, ring, line, and locked shapes.
- Updates line, parabola, and homing projectile movement.
- Triggers projectile sphere collision and hit-limited expiry.
- Ticks buffs by duration, interval, and repeat count.
- Emits logic effect requests for attribute, buff, and projectile operations.
- In
ServerLogic, authoritative logic events are produced and presentation execution is skipped. - In
ClientPresentation, presentation events are produced and authoritative attribute requests are skipped.
Acceptance Criteria
- A caller can load a
fighterbehavior catalog fromfighter_skill.json,fighter_flyobj.json, andfighter_buff.json. - A
BehaviorWorldcan register multiple actor type catalogs. - Each actor can switch its active behavior catalog by type name.
- A caller can start a behavior by actor id and behavior id with direction or target input.
- One actor cannot run two active behaviors at the same time.
- Special logic can stop or interrupt behavior and then start another behavior when rules allow it.
- Behavior timelines can emit animation, effect, projectile, buff, and logic events.
- Projectiles can move independently and trigger logic on collision.
- Buffs can attach to actors, tick repeatedly, and expire.
- Logic effects support sector, sphere, ring, line, and locked target selection.
- Server logic can run without Unity animation, prefab, or effect dependencies.
- Client code can consume the same event stream later for animation, prefab, and effect playback.