Blender-authored clips: real death fall, fire thrust, dash lean, hit-react (Phase 1 anim triage)
Four clips authored programmatically in Blender on the PolygonSyntyCharacter skeleton (full-body keys per pose, numeric world-space verification of the fall: hips 0.88->0.14m) and exported per-action FBX (Key All Bones, Force Start/End Keying, FBX Units Scale). Imported as HUMANOID - the project's pipeline is Human rigs (CharactersAvatar isHuman=true; CLAUDE.md's 'Generic' note was stale), so muscle-space retargeting plays these on the player AND every monster rig with zero bone-path matching. CopyFromOther failed on the Blender-added 'Armature' node (hierarchy mismatch) -> CreateFromThisModel; root motion fully baked into pose (the CC owns the transform). - A_Death_FallBack (0.90s = exactly the 54-tick corpse window): recoil -> fall -> flat on the back -> settle. Wired as the Death state on BOTH controllers, replacing the crouch-idle placeholder. - A_Fire_OneHand (0.4s): new IsFiring param + Fire state on the player controller; PlayerAnimationDriveSystem routes FireActive to it (melee owns IsAttacking again). - A_Dash_Lean (0.33s): new IsDashing param + Dash state; driven from the local player's predicted DashState window (not replicated -> remotes skip). - A_HitReact (0.27s): imported + avatar'd, wiring deferred (needs a replicated hit-tick source; body flash covers the read today). 456/456 EditMode; Play-validated: forced IsDead renders the soldier flat on the ground in-game (Blender->FBX->Humanoid->Rukhanka chain proven), Fire state enters and plays; console clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,8 @@ namespace ProjectM.Client
|
||||
static readonly FastAnimatorParameter k_Speed = new FastAnimatorParameter("Speed");
|
||||
static readonly FastAnimatorParameter k_IsDead = new FastAnimatorParameter("IsDead");
|
||||
static readonly FastAnimatorParameter k_IsAttacking = new FastAnimatorParameter("IsAttacking");
|
||||
static readonly FastAnimatorParameter k_IsFiring = new FastAnimatorParameter("IsFiring"); // Blender-authored A_Fire_OneHand (B7)
|
||||
static readonly FastAnimatorParameter k_IsDashing = new FastAnimatorParameter("IsDashing"); // Blender-authored A_Dash_Lean (local player only - DashState is not replicated)
|
||||
|
||||
// Ticks after a swing-start that IsAttacking stays true (drives the MeleeSwing state). Kept < the swing lock
|
||||
// (MeleeRecoverTicks ~16) so a CHAINED swing re-pulses the bool false->true and re-triggers the Any State
|
||||
@@ -75,7 +77,8 @@ namespace ProjectM.Client
|
||||
var localJob = new LocalDriveJob
|
||||
{
|
||||
moveX = k_MoveX, moveZ = k_MoveZ, speed = k_Speed, isDead = k_IsDead,
|
||||
isAttacking = k_IsAttacking, serverTick = serverTick, attackTicks = k_AttackAnimTicks,
|
||||
isAttacking = k_IsAttacking, isFiring = k_IsFiring, isDashing = k_IsDashing,
|
||||
serverTick = serverTick, attackTicks = k_AttackAnimTicks,
|
||||
};
|
||||
Dependency = localJob.ScheduleParallel(Dependency);
|
||||
|
||||
@@ -84,7 +87,8 @@ namespace ProjectM.Client
|
||||
var remoteJob = new RemoteDriveJob
|
||||
{
|
||||
moveX = k_MoveX, moveZ = k_MoveZ, speed = k_Speed, isDead = k_IsDead,
|
||||
isAttacking = k_IsAttacking, serverTick = serverTick, attackTicks = k_AttackAnimTicks,
|
||||
isAttacking = k_IsAttacking, isFiring = k_IsFiring, isDashing = k_IsDashing,
|
||||
serverTick = serverTick, attackTicks = k_AttackAnimTicks,
|
||||
dt = dt,
|
||||
prevPos = _prevPos,
|
||||
seen = seen,
|
||||
@@ -135,7 +139,7 @@ namespace ProjectM.Client
|
||||
[WithPresent(typeof(Dead))]
|
||||
partial struct LocalDriveJob : IJobEntity
|
||||
{
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isDead, isAttacking;
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isDead, isAttacking, isFiring, isDashing;
|
||||
public NetworkTick serverTick;
|
||||
public uint attackTicks;
|
||||
|
||||
@@ -148,14 +152,24 @@ namespace ProjectM.Client
|
||||
in MeleeCombo melee,
|
||||
in AbilityCooldown fireCooldown,
|
||||
in EffectiveAbilityStats abilityStats,
|
||||
in DashState dashState,
|
||||
EnabledRefRO<Dead> dead)
|
||||
{
|
||||
var a = new AnimatorParametersAspect(parametersArr, indexTable);
|
||||
float3 p = AnimParamMath.LocomotionParams(body.RelativeVelocity, facing.Direction, stats.MoveSpeed);
|
||||
Write(ref a, p, dead.ValueRO, moveX, moveZ, speed, isDead);
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking,
|
||||
SwingActive(melee, serverTick, attackTicks)
|
||||
|| FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking, SwingActive(melee, serverTick, attackTicks));
|
||||
if (a.HasParameter(isFiring)) a.SetParameterValue(isFiring,
|
||||
FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
// Dash lean: the LOCAL player's predicted DashState window [StartTick, IFrameUntilTick+tail).
|
||||
bool dashActive = false;
|
||||
if (dashState.StartTick != 0u && dashState.IFrameUntilTick != 0u && serverTick.IsValid)
|
||||
{
|
||||
var dStart = new NetworkTick(dashState.StartTick);
|
||||
var dEnd = new NetworkTick(TickUtil.NonZero(dashState.IFrameUntilTick + 6u));
|
||||
dashActive = dStart.IsValid && dEnd.IsValid && !dStart.IsNewerThan(serverTick) && dEnd.IsNewerThan(serverTick);
|
||||
}
|
||||
if (a.HasParameter(isDashing)) a.SetParameterValue(isDashing, dashActive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +180,7 @@ namespace ProjectM.Client
|
||||
[WithPresent(typeof(Dead))]
|
||||
partial struct RemoteDriveJob : IJobEntity
|
||||
{
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isDead, isAttacking;
|
||||
public FastAnimatorParameter moveX, moveZ, speed, isDead, isAttacking, isFiring, isDashing;
|
||||
public NetworkTick serverTick;
|
||||
public uint attackTicks;
|
||||
public float dt;
|
||||
@@ -195,9 +209,10 @@ namespace ProjectM.Client
|
||||
var a = new AnimatorParametersAspect(parametersArr, indexTable);
|
||||
float3 p = AnimParamMath.LocomotionParams(vel, facing.Direction, stats.MoveSpeed);
|
||||
Write(ref a, p, dead.ValueRO, moveX, moveZ, speed, isDead);
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking,
|
||||
SwingActive(melee, serverTick, attackTicks)
|
||||
|| FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
if (a.HasParameter(isAttacking)) a.SetParameterValue(isAttacking, SwingActive(melee, serverTick, attackTicks));
|
||||
if (a.HasParameter(isFiring)) a.SetParameterValue(isFiring,
|
||||
FireActive(fireCooldown.NextFireTick, abilityStats.CooldownTicks, serverTick, attackTicks));
|
||||
if (a.HasParameter(isDashing)) a.SetParameterValue(isDashing, false); // DashState is not replicated to remotes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user