8f96b520d6
Clears the three follow-ups deferred by the combat-overhaul pass (c3b53cef2) + the
DR-041 "needs its own ShaderGraph slice" note. All client-only, observe-only presentation
(PresentationSystemGroup; no sim mutation, no [GhostField], no server work).
- Item 1: EnemyHitFlashSystem flashes the ACTUAL enemy body by driving the stock
Entities-Graphics URPMaterialPropertyBaseColor override on the Rukhanka render-entity
LEG children (root has no MaterialMeshInfo) -- NO ShaderGraph edit, no new component type.
Lerp white->BodyFlashColor on a Health-decrease edge, decay back to white. Verified on
screen (the AnimatedLitShader honors the per-instance _BaseColor override).
- Item 2: per-remote-player slash-arc pool in CombatFeedbackSystem, edge-detected from the
replicated MeleeCombo on interpolated teammates (.WithDisabled<GhostOwnerIsLocal>());
BuildSlashMesh -> BuildSlashInto(mesh,...) refactor; local player keeps _slashMr.
- Item 3: once-per-windup near-impact strike beep folded into the danger-cone loop, gated
to a resolved local player.
- 9 new FeelConfig knobs (+ ResetDefaults).
390/390 EditMode, clean compile, zero Play exceptions. 3-lens adversarial review
(wf_8a998c6c-af9) -- no critical/major; fixed 4 minors: spurious beep at base origin before
the local player resolves, frozen tint if BodyFlashEnabled toggles off mid-flash, render-child
capture with no recovery, OnDestroy GO symmetry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
240 lines
13 KiB
C#
240 lines
13 KiB
C#
using UnityEngine;
|
|
|
|
namespace ProjectM.Client
|
|
{
|
|
/// <summary>
|
|
/// Live-tunable knobs for the client-only COMBAT-FEEL slice (Stage E). A static bridge — mirrors
|
|
/// <see cref="AimPresentation"/> — so values can be poked at runtime via MCP <c>execute_code</c>
|
|
/// (e.g. <c>ProjectM.Client.FeelConfig.HitShakeLocal = 0.4f;</c>) WITHOUT a recompile, for interactive
|
|
/// tuning. Read ONLY by client-presentation systems (<see cref="CombatFeedbackSystem"/>,
|
|
/// <see cref="AimReticleSystem"/>) and the <see cref="PrototypeCameraRig"/> MonoBehaviour — all non-Burst,
|
|
/// main-thread. NEVER read these from a <c>[BurstCompile]</c> system (managed-static + Color/enum-in-Burst
|
|
/// hazards); they are presentation-only and never touch the deterministic simulation.
|
|
/// <para>
|
|
/// Defaults match the values previously hardcoded in CombatFeedbackSystem so behaviour is byte-identical
|
|
/// until a knob is poked. <see cref="ResetDefaults"/> re-stamps every field on play-enter via
|
|
/// <c>[RuntimeInitializeOnLoadMethod(SubsystemRegistration)]</c> because statics survive fast-enter-playmode
|
|
/// domain reloads — without it a poked value would leak across play-enters and flash stale feel (the exact
|
|
/// bug <see cref="AimPresentation"/>'s reset prevents).
|
|
/// </para>
|
|
/// </summary>
|
|
public static class FeelConfig
|
|
{
|
|
// ---- Feature 1: hit camera punch + (camera-only) hit-stop ----
|
|
/// <summary>Camera shake when the LOCAL player is hit (fed to PrototypeCameraRig.AddShake, clamp 0.8).</summary>
|
|
public static float HitShakeLocal;
|
|
/// <summary>Camera shake when a remote player / Husk is hit.</summary>
|
|
public static float HitShakeRemote;
|
|
/// <summary>Hit-spark particle burst count (procedural fallback path).</summary>
|
|
public static int HitBurstCount;
|
|
/// <summary>Hit SFX volume.</summary>
|
|
public static float HitSfxVolume;
|
|
/// <summary>Degrees of transient FOV "kick" on a LOCAL hit — the netcode-safe hit-stop (NEVER Time.timeScale). 0 = off.</summary>
|
|
public static float HitStopFovKick;
|
|
/// <summary>Milliseconds the FOV kick eases back to base.</summary>
|
|
public static float HitStopDurationMs;
|
|
|
|
// ---- MC-3: player-dealt-hit punch (magnitude-scaled) + deferred enemy flash ----
|
|
/// <summary>Min FOV kick (deg) when the LOCAL player lands a hit on an enemy (chip damage).</summary>
|
|
public static float HitStopFovKickMin;
|
|
/// <summary>Max FOV kick (deg) on a heavy player-dealt hit (delta >= HitStopRefDamage).</summary>
|
|
public static float HitStopFovKickMax;
|
|
/// <summary>Reserved cap for the (deferred) true freeze-frame, in fixed frames.</summary>
|
|
public static int HitStopMaxFrames;
|
|
/// <summary>Damage delta that saturates the player-dealt punch to HitStopFovKickMax.</summary>
|
|
public static float HitStopRefDamage;
|
|
/// <summary>Tint for the (deferred) enemy material hit-flash — exposed now, wired in the ShaderGraph slice.</summary>
|
|
public static Color HitFlashColor;
|
|
/// <summary>Duration (ms) of the deferred enemy hit-flash.</summary>
|
|
public static float HitFlashDurationMs;
|
|
/// <summary>Master gate for the (deferred) true freeze-frame hit-stop. FALSE for v1 (camera-punch only).</summary>
|
|
public static bool HitStopFreezeEnabled;
|
|
|
|
// ---- Feature 1/2: death camera punch ----
|
|
/// <summary>Camera shake on LOCAL player death (loudest event by design).</summary>
|
|
public static float PlayerDeathShake;
|
|
/// <summary>Camera shake on a remote player's death.</summary>
|
|
public static float RemotePlayerDeathShake;
|
|
/// <summary>Base death-burst particle count (player death + Husk-death base).</summary>
|
|
public static int DeathBurstCount;
|
|
|
|
// ---- Feature 2: kill-shot fanfare (Husk death) ----
|
|
/// <summary>Camera shake on a Husk kill (nudged above a glancing hit, kept under PlayerDeathShake).</summary>
|
|
public static float KillShake;
|
|
/// <summary>Multiplier on DeathBurstCount for a Husk kill (result clamped by MaxActiveVfx).</summary>
|
|
public static float KillBurstScale;
|
|
/// <summary>Optional FOV kick on a kill (degrees). 0 = off.</summary>
|
|
public static float KillFovKick;
|
|
/// <summary>Husk-death SFX volume.</summary>
|
|
public static float KillSfxVolume;
|
|
|
|
// ---- Feature 3: respawn shimmer / fade-in (local player recovery) ----
|
|
/// <summary>Master gate for the local-player respawn shimmer.</summary>
|
|
public static bool RespawnShimmerEnabled;
|
|
/// <summary>Particle burst count for the recovery shimmer.</summary>
|
|
public static int RespawnShimmerBurst;
|
|
/// <summary>Light camera punch on recovery so respawn reads as "reinforcing".</summary>
|
|
public static float RespawnShimmerShake;
|
|
|
|
// ---- Feature 4: reticle lock-on tether (cosmetic aim HINT) ----
|
|
/// <summary>Master gate for the lock-on tether.</summary>
|
|
public static bool LockOnEnabled;
|
|
/// <summary>Show the tether only on the Gamepad scheme (mirrors the server's gamepad-only auto-target assist).</summary>
|
|
public static bool LockOnGamepadOnly;
|
|
/// <summary>Max world distance from the player to a tethered Husk.</summary>
|
|
public static float LockOnRange;
|
|
/// <summary>Forward half-arc (degrees) around PlayerFacing within which a Husk is eligible.</summary>
|
|
public static float LockOnArcDegrees;
|
|
/// <summary>Tether line tint (subtle highlight, not a laser).</summary>
|
|
public static Color LockOnLineColor;
|
|
/// <summary>Tether line width (world units).</summary>
|
|
public static float LockOnLineWidth;
|
|
|
|
// ---- Feature B: enemy health bars (Slice 1) ----
|
|
/// <summary>Squared world-distance beyond which a bar is hidden when the pool cap is reached (default 400 = 20m).</summary>
|
|
public static float HealthBarMaxDistSq;
|
|
|
|
// ---- Feature 5 (MC-1): dash juice ----
|
|
/// <summary>Camera shake on the local player's dash start.</summary>
|
|
public static float DashShake;
|
|
/// <summary>Transient FOV punch (degrees) on dash start — the "lurch" read (camera punch, never Time.timeScale).</summary>
|
|
public static float DashFovKick;
|
|
/// <summary>Afterimage/whoosh particle burst count at dash start.</summary>
|
|
public static int DashBurstCount;
|
|
/// <summary>Dash whoosh SFX volume.</summary>
|
|
public static float DashSfxVolume;
|
|
/// <summary>Particles emitted per frame while the local i-frame window is active (the shimmer trail).</summary>
|
|
public static int DashShimmerPerFrame;
|
|
/// <summary>Suppress local hit-feedback during the local i-frame window (masks the prediction-reconciliation
|
|
/// Health flicker on a clean dodge — the documented acceptable-not-a-bug interaction).</summary>
|
|
public static bool DashHitSuppress;
|
|
|
|
// ---- Combat feel pass (2026-06): connect cue, hit-flash, kill pop, footsteps, rumble, telegraph beep ----
|
|
/// <summary>Hit-flash puff density (a colored particle burst in HitFlashColor on an enemy damage edge).</summary>
|
|
public static int HitFlashBurstCount;
|
|
/// <summary>Extra FOV punch (deg) when the LOCAL melee cleave is confirmed to connect (vs a whiff).</summary>
|
|
public static float MeleeConnectFovKick;
|
|
/// <summary>Volume of the meaty melee connect "thunk".</summary>
|
|
public static float MeleeConnectVolume;
|
|
/// <summary>Kill-pop colored flash density on an enemy death.</summary>
|
|
public static int KillFlashBurstCount;
|
|
/// <summary>Soft footstep SFX volume.</summary>
|
|
public static float FootstepVolume;
|
|
/// <summary>Seconds between footsteps while the local player is moving.</summary>
|
|
public static float FootstepIntervalSec;
|
|
/// <summary>Local player speed (u/s) above which footsteps play.</summary>
|
|
public static float FootstepMinSpeed;
|
|
/// <summary>Master gate for gamepad rumble (no-op on KBM).</summary>
|
|
public static bool RumbleEnabled;
|
|
/// <summary>Rumble strength on a local hit taken / dealt.</summary>
|
|
public static float RumbleHit;
|
|
/// <summary>Rumble strength on a kill.</summary>
|
|
public static float RumbleKill;
|
|
/// <summary>Rumble strength on dash / local death (the heaviest).</summary>
|
|
public static float RumbleHeavy;
|
|
/// <summary>Seconds a rumble pulse lasts before it auto-stops.</summary>
|
|
public static float RumbleDurationSec;
|
|
|
|
// ---- Deferred-items pass (2026-06): true body hit-flash, remote co-op swings, near-impact strike beep ----
|
|
/// <summary>Master gate for the enemy material BODY hit-flash (drives URPMaterialPropertyBaseColor on render children).</summary>
|
|
public static bool BodyFlashEnabled;
|
|
/// <summary>Peak _BaseColor the enemy body flashes to on a hit (HDR; lerps from the baked white base and decays back).</summary>
|
|
public static Color BodyFlashColor;
|
|
/// <summary>Seconds the body flash decays from peak back to the baked white base.</summary>
|
|
public static float BodyFlashDurationSec;
|
|
/// <summary>Master gate for rendering REMOTE teammates' melee cleave arcs (co-op readability).</summary>
|
|
public static bool RemoteSwingEnabled;
|
|
/// <summary>Tint of a remote teammate's slash arc (cooler/friendlier than the local warm arc).</summary>
|
|
public static Color RemoteSlashColor;
|
|
/// <summary>Master gate for the near-impact \"dodge NOW\" strike beep on a winding-up enemy.</summary>
|
|
public static bool StrikeBeepEnabled;
|
|
/// <summary>Volume of the near-impact strike beep.</summary>
|
|
public static float StrikeBeepVolume;
|
|
/// <summary>Ticks before the strike lands that the beep fires (the dodge-reaction lead).</summary>
|
|
public static int StrikeBeepLeadTicks;
|
|
/// <summary>Squared world-distance from the local player beyond which the strike beep is suppressed (avoids a distant cacophony).</summary>
|
|
public static float StrikeBeepMaxDistSq;
|
|
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
|
public static void ResetDefaults()
|
|
{
|
|
// Feature 1
|
|
HitShakeLocal = 0.32f;
|
|
HitShakeRemote = 0.10f;
|
|
HitBurstCount = 10;
|
|
HitSfxVolume = 0.70f;
|
|
HitStopFovKick = 1.5f;
|
|
HitStopDurationMs = 90f;
|
|
HitStopFovKickMin = 0.6f;
|
|
HitStopFovKickMax = 2.2f;
|
|
HitStopMaxFrames = 3;
|
|
HitStopRefDamage = 30f;
|
|
HitFlashColor = new Color(1f, 0.85f, 0.55f, 1f);
|
|
HitFlashDurationMs = 80f;
|
|
HitStopFreezeEnabled = false;
|
|
|
|
// Feature 1/2 death
|
|
PlayerDeathShake = 0.50f;
|
|
RemotePlayerDeathShake = 0.25f;
|
|
DeathBurstCount = 28;
|
|
|
|
// Feature 2 kill-shot
|
|
KillShake = 0.20f;
|
|
KillBurstScale = 1.5f;
|
|
KillFovKick = 1.0f;
|
|
KillSfxVolume = 0.75f;
|
|
|
|
// Feature 3 respawn
|
|
RespawnShimmerEnabled = true;
|
|
RespawnShimmerBurst = 24;
|
|
RespawnShimmerShake = 0.12f;
|
|
|
|
// Feature 4 tether
|
|
LockOnEnabled = true;
|
|
LockOnGamepadOnly = true;
|
|
LockOnRange = 9.0f;
|
|
LockOnArcDegrees = 60f;
|
|
LockOnLineColor = new Color(0.55f, 0.9f, 1f, 0.35f);
|
|
LockOnLineWidth = 0.05f;
|
|
|
|
// Feature B health bars (Slice 1)
|
|
HealthBarMaxDistSq = 400f; // 20 m radius
|
|
|
|
// Feature 5 dash (MC-1)
|
|
DashShake = 0.18f;
|
|
DashFovKick = 1.2f;
|
|
DashBurstCount = 14;
|
|
DashSfxVolume = 0.55f;
|
|
DashShimmerPerFrame = 2;
|
|
DashHitSuppress = true;
|
|
|
|
// Combat feel pass (2026-06)
|
|
HitFlashBurstCount = 16;
|
|
MeleeConnectFovKick = 0.8f;
|
|
MeleeConnectVolume = 0.55f;
|
|
KillFlashBurstCount = 20;
|
|
FootstepVolume = 0.16f;
|
|
FootstepIntervalSec = 0.32f;
|
|
FootstepMinSpeed = 1.5f;
|
|
RumbleEnabled = true;
|
|
RumbleHit = 0.25f;
|
|
RumbleKill = 0.45f;
|
|
RumbleHeavy = 0.6f;
|
|
RumbleDurationSec = 0.12f;
|
|
|
|
// Deferred-items pass (2026-06)
|
|
BodyFlashEnabled = true;
|
|
BodyFlashColor = new Color(3.2f, 2.8f, 2.2f, 1f); // hot near-white overdrive (multiplies the Synty atlas base map)
|
|
BodyFlashDurationSec = 0.16f;
|
|
RemoteSwingEnabled = true;
|
|
RemoteSlashColor = new Color(1.4f, 2.2f, 2.8f, 1f); // cool teammate arc
|
|
StrikeBeepEnabled = true;
|
|
StrikeBeepVolume = 0.40f;
|
|
StrikeBeepLeadTicks = 8;
|
|
StrikeBeepMaxDistSq = 225f; // 15 m
|
|
|
|
}
|
|
}
|
|
}
|