Phase 1 B3: enemies die with a corpse window instead of popping out of existence
Server: HealthApplyDamageSystem marks EnemyTag Dying{UntilTick} (TickUtil.
NonZero, ~54 ticks) on the lethal 0-crossing instead of instant destroy,
zeroes every live cue (replicated AttackWindup, LungeState + IsLunging bit,
KnockbackState), destroys on expiry; plain-world tests keep instant destroy
(no NetworkTime) so the suite's assertions stay meaningful.
Every consumer now ignores corpses (all confirmed entity-count/unfiltered by
the design review): EnemyAISystem all 4 passes, BossAISystem brain + summon
cap, RoomEnemyDirector room-clear + MaxAlive fit, WaveSystem cap + cleared,
CyclePhaseSystem breach wipe + DefendCleared, ThreatDirector timeout cull,
TurretFire targeting, CoreDamage drain, ProjectileDamage snapshot (corpses
are not shields), AbilityFire auto-aim candidates, debug cull + telemetry.
Wipe passes skip Dying to avoid cross-ECB double-destroys.
Client: EnemyAnimationDriveSystem finally drives the controller's IsDead
param (Health.Current<=0 is the replicated death read; corpse locomotion
zeroed); CombatFeedbackSystem moves the kill CRUNCH to the 0-crossing (the
prune-edge timing would land it ~1 s late) - the prune keeps a small
dissolve puff for corpses and the legacy full read for alive-vanish culls.
456/456 EditMode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,7 @@ namespace ProjectM.Client
|
||||
static readonly FastAnimatorParameter k_MoveZ = new FastAnimatorParameter("MoveZ");
|
||||
static readonly FastAnimatorParameter k_Speed = new FastAnimatorParameter("Speed");
|
||||
static readonly FastAnimatorParameter k_IsAttacking = new FastAnimatorParameter("IsAttacking");
|
||||
static readonly FastAnimatorParameter k_IsDead = new FastAnimatorParameter("IsDead"); // B3: the controller's Death state existed but was never driven
|
||||
|
||||
// prevPos cache (per Husk Entity). Pruned every frame (a vanished Husk = a server-authoritative death).
|
||||
NativeParallelHashMap<Entity, float3> _prevPos;
|
||||
@@ -63,7 +64,7 @@ namespace ProjectM.Client
|
||||
var seen = new NativeParallelHashSet<Entity>(64, Allocator.TempJob);
|
||||
var job = new EnemyDriveJob
|
||||
{
|
||||
moveX = k_MoveX, moveZ = k_MoveZ, speed = k_Speed, isAttacking = k_IsAttacking,
|
||||
moveX = k_MoveX, moveZ = k_MoveZ, speed = k_Speed, isAttacking = k_IsAttacking, isDead = k_IsDead,
|
||||
dt = dt,
|
||||
prevPos = _prevPos,
|
||||
seen = seen,
|
||||
@@ -91,7 +92,7 @@ namespace ProjectM.Client
|
||||
[WithAll(typeof(EnemyTag))]
|
||||
partial struct EnemyDriveJob : IJobEntity
|
||||
{
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isAttacking;
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isAttacking, isDead;
|
||||
[Unity.Collections.ReadOnly] public ComponentLookup<IsLunging> isLunging; // A7: a lunge has no AttackWindup; OR it in so the boss/Charger attack anim plays during the committed lunge
|
||||
|
||||
public float dt;
|
||||
@@ -104,7 +105,8 @@ namespace ProjectM.Client
|
||||
DynamicBuffer<AnimatorControllerParameterComponent> parametersArr,
|
||||
in LocalTransform xform,
|
||||
in EnemyStats stats,
|
||||
in AttackWindup windup)
|
||||
in AttackWindup windup,
|
||||
in Health health)
|
||||
{
|
||||
seen.Add(e);
|
||||
float3 cur = xform.Position;
|
||||
@@ -117,11 +119,17 @@ namespace ProjectM.Client
|
||||
float3 p = AnimParamMath.LocomotionParams(vel, facing, stats.MoveSpeed);
|
||||
bool attacking = windup.WindUpUntilTick != 0 || (isLunging.HasComponent(e) && isLunging.IsComponentEnabled(e)); // A7: the committed lunge (which zeroes AttackWindup) still animates as an attack
|
||||
|
||||
// B3: the corpse window — Health.Current is replicated, so <=0 IS the death read. A corpse
|
||||
// plays the Death state and nothing else (no jog, no frozen attack).
|
||||
bool dead = health.Current <= 0f;
|
||||
if (dead) { p = float3.zero; attacking = false; }
|
||||
|
||||
var a = new AnimatorParametersAspect(parametersArr, indexTable);
|
||||
if (a.HasParameter(moveX)) a.SetParameterValue(moveX, p.x);
|
||||
if (a.HasParameter(moveZ)) a.SetParameterValue(moveZ, p.y);
|
||||
if (a.HasParameter(speed)) a.SetParameterValue(speed, p.z);
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking, attacking);
|
||||
if (a.HasParameter(isDead)) a.SetParameterValue(isDead, dead);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user