Combat feel: correct boss bar, player hit-flash, dash-to-move, finisher hit-stop
Boss bar reconstructs the true max client-side (Health.Max unreplicated) via Kind==Charger + expedition-pos filter; boss slam telegraph = a full ground ring at BossSlamRadius; IsAttacking OR's IsLunging. Player body flash on hurt (distinct red, jitter-safe threshold). Dash toward movement input when moving else facing. Warrior cone: muzzle-cue suppressed (cone arc is its cue). Minimal finisher-only hit-stop (PrototypeCameraRig.Hold, behind FeelConfig flag; never Time.timeScale). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ namespace ProjectM.Client
|
||||
public float LastHp;
|
||||
public float Flash; // 1 on a fresh hit, decays to 0
|
||||
public bool Settled; // wrote the final white frame after a flash ended (skips per-frame writes at rest)
|
||||
public bool IsPlayer; // C2: a player entry flashes PlayerHurtFlashColor (red), an enemy the white overdrive rest)
|
||||
}
|
||||
|
||||
readonly Dictionary<Entity, FlashEntry> _tracked = new();
|
||||
@@ -50,12 +51,12 @@ namespace ProjectM.Client
|
||||
_seen.Clear();
|
||||
var ecb = new EntityCommandBuffer(Unity.Collections.Allocator.Temp);
|
||||
foreach (var (health, entity) in
|
||||
SystemAPI.Query<RefRO<Health>>().WithAll<EnemyTag, LinkedEntityGroup>().WithEntityAccess())
|
||||
SystemAPI.Query<RefRO<Health>>().WithAny<EnemyTag, PlayerTag>().WithAll<LinkedEntityGroup>().WithEntityAccess())
|
||||
{
|
||||
_seen.Add(entity);
|
||||
if (_tracked.ContainsKey(entity)) continue;
|
||||
|
||||
var entry = new FlashEntry { LastHp = health.ValueRO.Current, Flash = 0f, Settled = true };
|
||||
var entry = new FlashEntry { LastHp = health.ValueRO.Current, Flash = 0f, Settled = true, IsPlayer = EntityManager.HasComponent<PlayerTag>(entity) };
|
||||
var leg = EntityManager.GetBuffer<LinkedEntityGroup>(entity);
|
||||
for (int i = 0; i < leg.Length; i++)
|
||||
{
|
||||
@@ -73,7 +74,9 @@ namespace ProjectM.Client
|
||||
|
||||
// Pass 2: edge-detect Health, drive + decay the flash, write _BaseColor to the render children.
|
||||
var bc = FeelConfig.BodyFlashColor;
|
||||
float4 peak = new float4(bc.r, bc.g, bc.b, bc.a);
|
||||
float4 enemyPeak = new float4(bc.r, bc.g, bc.b, bc.a);
|
||||
var pc = FeelConfig.PlayerHurtFlashColor;
|
||||
float4 playerPeak = new float4(pc.r, pc.g, pc.b, pc.a);
|
||||
float decay = dt / math.max(0.01f, FeelConfig.BodyFlashDurationSec);
|
||||
foreach (var kv in _tracked)
|
||||
{
|
||||
@@ -82,7 +85,7 @@ namespace ProjectM.Client
|
||||
if (!_seen.Contains(entity)) continue; // despawned -> pruned below
|
||||
|
||||
float cur = EntityManager.GetComponentData<Health>(entity).Current;
|
||||
if (cur < entry.LastHp - 0.001f) { entry.Flash = 1f; entry.Settled = false; }
|
||||
if (cur < entry.LastHp - 0.5f) { entry.Flash = 1f; entry.Settled = false; }
|
||||
entry.LastHp = cur;
|
||||
|
||||
if (entry.Flash <= 0f)
|
||||
@@ -92,6 +95,7 @@ namespace ProjectM.Client
|
||||
}
|
||||
|
||||
entry.Flash = math.max(0f, entry.Flash - decay);
|
||||
float4 peak = entry.IsPlayer ? playerPeak : enemyPeak;
|
||||
WriteColor(entry, math.lerp(White, peak, entry.Flash));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user