Compare commits
2 Commits
4ae022c5ad
...
cca8c1d44e
| Author | SHA1 | Date | |
|---|---|---|---|
| cca8c1d44e | |||
| a335ca3f21 |
@@ -19,6 +19,8 @@ namespace ProjectM.Client
|
|||||||
[Header("Projectile lights (pooled, follow every live projectile ghost)")]
|
[Header("Projectile lights (pooled, follow every live projectile ghost)")]
|
||||||
[Min(0)] public int MaxProjectileLights = 24;
|
[Min(0)] public int MaxProjectileLights = 24;
|
||||||
public Color ProjectileColor = new Color(0.45f, 0.85f, 1f, 1f);
|
public Color ProjectileColor = new Color(0.45f, 0.85f, 1f, 1f);
|
||||||
|
[Tooltip("Un-owned (enemy) projectiles — Spitter spit reads toxic green vs the player's cyan.")]
|
||||||
|
public Color EnemyProjectileColor = new Color(0.55f, 1f, 0.35f, 1f);
|
||||||
[Min(0f)] public float ProjectileIntensity = 2.6f;
|
[Min(0f)] public float ProjectileIntensity = 2.6f;
|
||||||
[Min(0.5f)] public float ProjectileRange = 7f;
|
[Min(0.5f)] public float ProjectileRange = 7f;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using ProjectM.Simulation;
|
using ProjectM.Simulation;
|
||||||
using Unity.Entities;
|
using Unity.Entities;
|
||||||
|
using Unity.NetCode;
|
||||||
using Unity.Transforms;
|
using Unity.Transforms;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ namespace ProjectM.Client
|
|||||||
Color projColor = cfg != null ? cfg.ProjectileColor : new Color(0.45f, 0.85f, 1f, 1f);
|
Color projColor = cfg != null ? cfg.ProjectileColor : new Color(0.45f, 0.85f, 1f, 1f);
|
||||||
float projIntensity = cfg != null ? cfg.ProjectileIntensity : 2.6f;
|
float projIntensity = cfg != null ? cfg.ProjectileIntensity : 2.6f;
|
||||||
float projRange = cfg != null ? cfg.ProjectileRange : 7f;
|
float projRange = cfg != null ? cfg.ProjectileRange : 7f;
|
||||||
|
Color enemyProjColor = cfg != null ? cfg.EnemyProjectileColor : new Color(0.55f, 1f, 0.35f, 1f);
|
||||||
_seen.Clear();
|
_seen.Clear();
|
||||||
foreach (var (lt, entity) in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<Projectile>().WithEntityAccess())
|
foreach (var (lt, entity) in SystemAPI.Query<RefRO<LocalTransform>>().WithAll<Projectile>().WithEntityAccess())
|
||||||
{
|
{
|
||||||
@@ -84,7 +86,8 @@ namespace ProjectM.Client
|
|||||||
light = Rent();
|
light = Rent();
|
||||||
_projectileLights[entity] = light;
|
_projectileLights[entity] = light;
|
||||||
}
|
}
|
||||||
light.color = projColor;
|
// Attack-distinctness: owned shots glow the player cyan; un-owned (enemy spit) glows toxic green.
|
||||||
|
light.color = EntityManager.HasComponent<GhostOwner>(entity) ? projColor : enemyProjColor;
|
||||||
light.intensity = projIntensity;
|
light.intensity = projIntensity;
|
||||||
light.range = projRange;
|
light.range = projRange;
|
||||||
var p = lt.ValueRO.Position;
|
var p = lt.ValueRO.Position;
|
||||||
|
|||||||
@@ -35,11 +35,22 @@ namespace ProjectM.Client
|
|||||||
readonly Dictionary<Entity, uint> _prevWindup = new(); // self-detect the windup-onset edge (was the core _cache.Windup)
|
readonly Dictionary<Entity, uint> _prevWindup = new(); // self-detect the windup-onset edge (was the core _cache.Windup)
|
||||||
readonly HashSet<Entity> _enemySeen = new(); // ALL enemies this frame (prunes _pulseStart/_strikeBeeped/_prevWindup)
|
readonly HashSet<Entity> _enemySeen = new(); // ALL enemies this frame (prunes _pulseStart/_strikeBeeped/_prevWindup)
|
||||||
AudioClip _strikeBeepClip; // near-impact "dodge NOW" beep
|
AudioClip _strikeBeepClip; // near-impact "dodge NOW" beep
|
||||||
|
AudioClip[] _kindGrowls; // per-kind windup voice (attack distinctness)
|
||||||
|
Material[] _kindMats; // per-kind telegraph colours (attack distinctness)
|
||||||
Entity _localPlayer = Entity.Null;
|
Entity _localPlayer = Entity.Null;
|
||||||
|
|
||||||
protected override void OnCreate()
|
protected override void OnCreate()
|
||||||
{
|
{
|
||||||
_strikeBeepClip = MakeClip("strike", 1150f, 1500f, 0.05f, 0.30f, noise: false); // near-impact beep
|
_strikeBeepClip = MakeClip("strike", 1150f, 1500f, 0.05f, 0.30f, noise: false); // near-impact beep
|
||||||
|
// Attack-distinctness: per-kind windup VOICES so kinds read by EAR at onset (0 grunt thud,
|
||||||
|
// 1 charger rising roar, 2 spitter wet hiss, 3 swarmer chitter).
|
||||||
|
_kindGrowls = new AudioClip[]
|
||||||
|
{
|
||||||
|
MakeClip("growl_grunt", 180f, 110f, 0.14f, 0.30f, noise: false),
|
||||||
|
MakeClip("growl_charger", 90f, 420f, 0.32f, 0.34f, noise: false),
|
||||||
|
MakeClip("growl_spitter", 1900f, 500f, 0.18f, 0.26f, noise: true),
|
||||||
|
MakeClip("growl_swarmer", 1500f, 2100f, 0.07f, 0.22f, noise: false),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnStartRunning()
|
protected override void OnStartRunning()
|
||||||
@@ -48,13 +59,32 @@ namespace ProjectM.Client
|
|||||||
_fxRoot = new GameObject("~EnemyDangerFX").transform;
|
_fxRoot = new GameObject("~EnemyDangerFX").transform;
|
||||||
_dangerMat = MakeParticleMaterial();
|
_dangerMat = MakeParticleMaterial();
|
||||||
_dangerMat.name = "EnemyDanger";
|
_dangerMat.name = "EnemyDanger";
|
||||||
_dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red (per-zone intensity carried in vertex alpha)
|
_dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red fallback (per-zone intensity in vertex alpha)
|
||||||
|
// Attack-distinctness: per-kind telegraph COLORS (0 grunt orange, 1 charger crimson,
|
||||||
|
// 2 spitter toxic green, 3 swarmer yellow; the boss shares the charger crimson — shapes differ).
|
||||||
|
_kindMats = new Material[4];
|
||||||
|
var kindColors = new Color[]
|
||||||
|
{
|
||||||
|
new Color(3.2f, 0.85f, 0.15f, 1f),
|
||||||
|
new Color(3.2f, 0.18f, 0.12f, 1f),
|
||||||
|
new Color(0.55f, 2.9f, 0.35f, 1f),
|
||||||
|
new Color(2.9f, 2.3f, 0.22f, 1f),
|
||||||
|
};
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
_kindMats[i] = MakeParticleMaterial();
|
||||||
|
_kindMats[i].name = "EnemyDanger_K" + i;
|
||||||
|
_kindMats[i].color = kindColors[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
|
if (_fxRoot != null) Object.Destroy(_fxRoot.gameObject);
|
||||||
if (_dangerMat != null) Object.Destroy(_dangerMat);
|
if (_dangerMat != null) Object.Destroy(_dangerMat);
|
||||||
|
if (_kindMats != null)
|
||||||
|
for (int i = 0; i < _kindMats.Length; i++)
|
||||||
|
if (_kindMats[i] != null) Object.Destroy(_kindMats[i]);
|
||||||
foreach (var kv in _dangerZones)
|
foreach (var kv in _dangerZones)
|
||||||
if (kv.Value != null) { var mf = kv.Value.GetComponent<MeshFilter>(); if (mf != null && mf.sharedMesh != null) Object.Destroy(mf.sharedMesh); }
|
if (kv.Value != null) { var mf = kv.Value.GetComponent<MeshFilter>(); if (mf != null && mf.sharedMesh != null) Object.Destroy(mf.sharedMesh); }
|
||||||
}
|
}
|
||||||
@@ -107,7 +137,18 @@ namespace ProjectM.Client
|
|||||||
// transition of WindUpUntilTick arms the anticipation scale-pulse (Feature C). Requires a prior 0
|
// transition of WindUpUntilTick arms the anticipation scale-pulse (Feature C). Requires a prior 0
|
||||||
// record so a mid-windup relevancy re-entry doesn't spuriously pulse (matches the old prev.Windup==0).
|
// record so a mid-windup relevancy re-entry doesn't spuriously pulse (matches the old prev.Windup==0).
|
||||||
bool hadPrev = _prevWindup.TryGetValue(entity, out var pw);
|
bool hadPrev = _prevWindup.TryGetValue(entity, out var pw);
|
||||||
if (until != 0u && hadPrev && pw == 0u) _pulseStart[entity] = (float)SystemAPI.Time.ElapsedTime;
|
if (until != 0u && hadPrev && pw == 0u)
|
||||||
|
{
|
||||||
|
_pulseStart[entity] = (float)SystemAPI.Time.ElapsedTime;
|
||||||
|
// Attack-distinctness: the per-kind windup VOICE at onset (same gate family as the strike beep).
|
||||||
|
if (FeelConfig.StrikeBeepEnabled && _localPlayer != Entity.Null && _kindGrowls != null
|
||||||
|
&& math.distancesq(xf.ValueRO.Position, localPos) <= FeelConfig.StrikeBeepMaxDistSq)
|
||||||
|
{
|
||||||
|
byte gk = tele.ValueRO.Kind;
|
||||||
|
if (gk < _kindGrowls.Length && _kindGrowls[gk] != null)
|
||||||
|
PlayClip(_kindGrowls[gk], (Vector3)xf.ValueRO.Position, 0.22f);
|
||||||
|
}
|
||||||
|
}
|
||||||
_prevWindup[entity] = until;
|
_prevWindup[entity] = until;
|
||||||
|
|
||||||
// Feature D: a committed Charger lunge keeps the cue ALIVE past windup (AttackWindup zeroes at commit).
|
// Feature D: a committed Charger lunge keeps the cue ALIVE past windup (AttackWindup zeroes at commit).
|
||||||
@@ -163,7 +204,8 @@ namespace ProjectM.Client
|
|||||||
go.transform.SetParent(_fxRoot, false);
|
go.transform.SetParent(_fxRoot, false);
|
||||||
go.AddComponent<MeshFilter>().sharedMesh = new Mesh { name = "EnemyDanger" };
|
go.AddComponent<MeshFilter>().sharedMesh = new Mesh { name = "EnemyDanger" };
|
||||||
var mr = go.AddComponent<MeshRenderer>();
|
var mr = go.AddComponent<MeshRenderer>();
|
||||||
mr.sharedMaterial = _dangerMat;
|
mr.sharedMaterial = _kindMats != null && tele.ValueRO.Kind < _kindMats.Length && _kindMats[tele.ValueRO.Kind] != null
|
||||||
|
? _kindMats[tele.ValueRO.Kind] : _dangerMat; // per-kind telegraph colour (kind is fixed per entity)
|
||||||
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||||
mr.receiveShadows = false;
|
mr.receiveShadows = false;
|
||||||
_dangerZones[entity] = go;
|
_dangerZones[entity] = go;
|
||||||
|
|||||||
@@ -139,9 +139,9 @@ attacks/animations/effects**. Art direction deliberately UNDECIDED — lighting
|
|||||||
5. ~~Breakable clutter with drops~~ **DONE (2026-07-09, `53bc21143`)** — the BlightClutter chain already
|
5. ~~Breakable clutter with drops~~ **DONE (2026-07-09, `53bc21143`)** — the BlightClutter chain already
|
||||||
existed end-to-end (seeding/unified-sweep/drops); the gap was feel: barrels (SM_Prop_Barrel_01) replace the
|
existed end-to-end (seeding/unified-sweep/drops); the gap was feel: barrels (SM_Prop_Barrel_01) replace the
|
||||||
rock chunk, ONE-hit pop (Remaining 8→2), density 6→8/room. Live-verified pop + drop credit.
|
rock chunk, ONE-hit pop (Remaining 8→2), density 6→8/room. Live-verified pop + drop credit.
|
||||||
6. **Attack distinctness:** every enemy kind + class ability gets a unique read — anim + VFX + effect shape (not more
|
6. **Attack distinctness — CODE-SIDE DONE (2026-07-09, `a335ca3f2`):** per-kind telegraph colours (shapes
|
||||||
hit-stop). Builds on the shipped Blender clip pipeline + sword-combo work; creature locomotion for monsters still
|
were already per-kind), per-kind windup voices, ownership-keyed projectile lights. The anim-side depth
|
||||||
open from Phase 1.3.
|
(per-kind clips, creature locomotion — still open from Phase 1.3) rides the parallel Blender workstream.
|
||||||
|
|
||||||
### Long-term (server-sim; adversarial design-review REQUIRED per slice)
|
### Long-term (server-sim; adversarial design-review REQUIRED per slice)
|
||||||
|
|
||||||
|
|||||||
@@ -202,13 +202,26 @@ a combat room; an injected projectile through the real sweep popped one in ONE h
|
|||||||
466/466; console clean. Also committed the operator's own F3-panel slider drags from the art-look lap
|
466/466; console clean. Also committed the operator's own F3-panel slider drags from the art-look lap
|
||||||
(`8c2bf3c37` — master stays OFF).
|
(`8c2bf3c37` — master stays OFF).
|
||||||
|
|
||||||
|
## Part I — attack distinctness pass (`a335ca3f2`)
|
||||||
|
|
||||||
|
Per-kind unique reads on the existing replicated state (client-only, zero netcode): **telegraph COLOURS**
|
||||||
|
per `EnemyTelegraph.Kind` (grunt orange / charger+boss crimson / spitter toxic-green lane / swarmer yellow —
|
||||||
|
shapes were already per-kind, colours were uniformly red) · **windup VOICES** at the existing onset edge
|
||||||
|
(thud / rising roar / wet hiss / chitter, strike-beep distance gate) · **projectile light colour by
|
||||||
|
ownership** in DynamicLightSystem (player cyan vs toxic-green enemy spit, `EnemyProjectileColor` knob).
|
||||||
|
Verified live: a 12 s combat histogram showed zones rendering K0+K1 materials keyed to real windups
|
||||||
|
(screenshot); 466/466; console clean. Gotcha: bare `GhostOwner` needs `using Unity.NetCode;` (CS0246 in a
|
||||||
|
Client presentation file that only used Simulation types before). Remaining distinctness depth (per-kind
|
||||||
|
anim clips, creature locomotion) rides the parallel Blender workstream.
|
||||||
|
|
||||||
## Next-session intent
|
## Next-session intent
|
||||||
|
|
||||||
Phase 1.5 short-term wins remaining: **attack distinctness** (unique read per enemy kind + class ability —
|
**Phase 1.5 short-term wins are COMPLETE** (collision ✓ lighting ✓ art-look ✓ ambient motion ✓ breakables ✓
|
||||||
anim + VFX + effect shape, on the Blender clip pipeline). Long-term 1.5 (hazards, destructible cover) =
|
attack distinctness ✓). Next: either the 1.5 long-term slices (**environmental hazards** / **destructible
|
||||||
adversarial design-review-first. Queued taste items: re-enable curated base flora (barren hub) · arid
|
cover** — BOTH adversarial design-review-first, and cover must re-validate the enemy backstop) or jump to
|
||||||
brightness · craters-as-blocks call · B5 split-panels smoke. Then Phase 1.7 boon overhaul (design review
|
**Phase 1.7 boon overhaul** (~12 mechanic-changers; design review required — replicated ability swaps).
|
||||||
required — replicated ability swaps).
|
Operator gate: a feel-lap over the whole 1.5 package ("no longer static"?) decides. Queued taste items:
|
||||||
|
re-enable curated base flora · arid brightness · craters call · B5 split-panels smoke.
|
||||||
|
|
||||||
Related: [[Iteration_2026-07_CoopHades]] · [[2026-07-07_Workflow_Consolidation]] ·
|
Related: [[Iteration_2026-07_CoopHades]] · [[2026-07-07_Workflow_Consolidation]] ·
|
||||||
[[2026-07-07_Expedition_Enemy_Stuck_Harvest_Feedback]]
|
[[2026-07-07_Expedition_Enemy_Stuck_Harvest_Feedback]]
|
||||||
|
|||||||
Reference in New Issue
Block a user