Portal made visible: cyan beacon pillar + always-on RoomExplore steer prompt (DR-046 follow-up)
The room-exit portal was invisible in the first playtest - players loitered out the 30s ExploreGrace timeout. Adds RegionMath.ExpeditionPortalPos as the single client-derivable portal-position authority (HUD prompt + beacon can't drift), a pooled breathing HDR pillar in CombatFeedbackSystem shown only during RoomExplore, and an in-range/out-of-range two-stage prompt. Also repairs the comment-swallowed _portalMat initialization (NRE in OnStartRunning killed ALL combat feedback each session and auto-paused the editor via Error Pause). 456/456 EditMode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -66,7 +66,9 @@ namespace ProjectM.Client
|
||||
double _lastHoldTime; // C4: last hit-stop hold time (throttle so a horde wipe doesn't stutter)
|
||||
bool _coneTickInit;
|
||||
Material _dangerMat;
|
||||
readonly Dictionary<Entity, GameObject> _dangerZones = new();
|
||||
readonly Dictionary<Entity, GameObject> _dangerZones = new(); Material _portalMat; // DR-046: room-exit portal beacon glow (mutated for the pulse; beacon-only mat)
|
||||
GameObject _portalBeacon; // DR-046: pooled world-space "go here" pillar, shown only during RoomExplore
|
||||
|
||||
readonly HashSet<Entity> _dangerSeen = new();
|
||||
readonly List<Entity> _dangerStale = new();
|
||||
// ---- Enemy health bars (Slice 1, Feature B) — one pooled world-space Canvas per live Husk ----
|
||||
@@ -148,6 +150,10 @@ namespace ProjectM.Client
|
||||
_dangerMat = MakeParticleMaterial();
|
||||
_dangerMat.name = "EnemyDanger";
|
||||
_dangerMat.color = new Color(3.2f, 0.28f, 0.18f, 1f); // HDR red (per-zone intensity carried in vertex alpha)
|
||||
_portalMat = MakeParticleMaterial();
|
||||
_portalMat.name = "RoomPortal";
|
||||
_portalMat.color = new Color(0.5f, 2.6f, 3.4f, 0.85f); // DR-046: HDR cyan portal glow (pushes past bloom)
|
||||
|
||||
// Health-bar materials (UI/Default = always-included URP-compatible UI shader; per-instance Image.color carries alpha).
|
||||
Shader uiShader = Shader.Find("UI/Default") ?? Shader.Find("Sprites/Default");
|
||||
_barBgMat = new Material(uiShader) { name = "HealthBarBg" };
|
||||
@@ -163,7 +169,8 @@ namespace ProjectM.Client
|
||||
Object.Destroy(_fxRoot.gameObject);
|
||||
if (_slashMesh != null) Object.Destroy(_slashMesh);
|
||||
if (_slashMat != null) Object.Destroy(_slashMat);
|
||||
if (_dangerMat != null) Object.Destroy(_dangerMat);
|
||||
if (_dangerMat != null) Object.Destroy(_dangerMat); if (_portalMat != null) Object.Destroy(_portalMat);
|
||||
|
||||
if (_barBgMat != null) Object.Destroy(_barBgMat);
|
||||
if (_barFillMat != null) Object.Destroy(_barFillMat);
|
||||
foreach (var kv in _dangerZones)
|
||||
@@ -500,7 +507,8 @@ namespace ProjectM.Client
|
||||
PruneVfx();
|
||||
AnimateNumbers(dt, cam);
|
||||
UpdateSlash(dt);
|
||||
UpdateEnemyDanger(localPos);
|
||||
UpdateEnemyDanger(localPos); UpdatePortalBeacon();
|
||||
|
||||
UpdateRemoteSwings(dt);
|
||||
UpdateHealthBars(dt, cam, localPos);
|
||||
}
|
||||
@@ -982,7 +990,45 @@ void TriggerSlash(Vector3 pos, float2 facing, float range, float halfAngle, int
|
||||
return new RemoteSlash { Go = go, Mesh = mesh, Mr = mr, Mat = mat, Active = false, Init = false };
|
||||
}
|
||||
|
||||
// Enemy attack TELEGRAPH (MC-4 clarity): while an enemy's AttackWindup counts down, paint a red ground danger
|
||||
// DR-046: the room-exit PORTAL made VISIBLE. During the RoomExplore loot window a glowing cyan pillar marks the
|
||||
// client-derived portal position so the player has an unmistakable "go here to continue" target — the HUD prompt
|
||||
// alone left the exit invisible, so players waited out the ~30s grace timeout ("nothing happens for a while").
|
||||
// Client-only, observe-only; one pooled GameObject, hidden whenever the run isn't in RoomExplore. Position
|
||||
// resolves through the SAME RegionMath.ExpeditionPortalPos authority the HUD prompt uses -> beacon + "PRESS E"
|
||||
// range always agree.
|
||||
void UpdatePortalBeacon()
|
||||
{
|
||||
if (_fxRoot == null || _portalMat == null) return;
|
||||
bool inExplore = SystemAPI.TryGetSingleton<RunInfo>(out var ri) && ri.Lifecycle == RunLifecycle.RoomExplore;
|
||||
if (!inExplore || !SystemAPI.TryGetSingleton<BaseAnchor>(out var anchor))
|
||||
{
|
||||
if (_portalBeacon != null && _portalBeacon.activeSelf) _portalBeacon.SetActive(false);
|
||||
return;
|
||||
}
|
||||
float3 pos = RegionMath.ExpeditionPortalPos(BaseGridMath.PlotCenter(anchor), (byte)(ri.CurrentRoom & 1));
|
||||
if (_portalBeacon == null)
|
||||
{
|
||||
_portalBeacon = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
|
||||
_portalBeacon.name = "~RoomPortalBeacon";
|
||||
var col = _portalBeacon.GetComponent<Collider>(); if (col != null) Object.Destroy(col); // cosmetic only
|
||||
_portalBeacon.transform.SetParent(_fxRoot, false);
|
||||
var mr = _portalBeacon.GetComponent<MeshRenderer>();
|
||||
mr.sharedMaterial = _portalMat;
|
||||
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
mr.receiveShadows = false;
|
||||
}
|
||||
if (!_portalBeacon.activeSelf) _portalBeacon.SetActive(true);
|
||||
float t = (float)SystemAPI.Time.ElapsedTime;
|
||||
float breathe = 0.5f + 0.5f * math.sin(t * 3.5f);
|
||||
var tr = _portalBeacon.transform;
|
||||
// Cylinder is 2u tall in local space -> scale.y=3 gives a 6u pillar; lift the centre so the base sits on the ground.
|
||||
tr.position = new Vector3(pos.x, pos.y + 3f, pos.z);
|
||||
tr.localScale = new Vector3(1.4f + 0.15f * breathe, 3f, 1.4f + 0.15f * breathe);
|
||||
_portalMat.color = new Color(0.5f, 2.6f, 3.4f, 0.55f + 0.35f * breathe); // glow throb (beacon-only mat, safe to mutate)
|
||||
}
|
||||
|
||||
|
||||
// Enemy attack TELEGRAPH (MC-4 clarity): while an enemy's AttackWindup counts down, paint a red ground danger
|
||||
// cone in its facing out to its reach, brightening + scaling as the strike nears -> the player reads WHERE +
|
||||
// WHEN to dodge. Client-only, observe-only; one pooled mesh per winding-up enemy, pruned each frame.
|
||||
void UpdateEnemyDanger(float3 localPos)
|
||||
|
||||
Reference in New Issue
Block a user