diff --git a/Assets/Scripts/CrateEscape/LaserBeam.cs b/Assets/Scripts/CrateEscape/LaserBeam.cs index 90ccd7e..4882b8c 100644 --- a/Assets/Scripts/CrateEscape/LaserBeam.cs +++ b/Assets/Scripts/CrateEscape/LaserBeam.cs @@ -4,15 +4,11 @@ using System.Collections; [RequireComponent(typeof(LineRenderer))] public class LaserBeam : MonoBehaviour { - [Header("Control")] - [Tooltip("If true, this beam will NOT run its own timers or intro; it only responds to the controller.")] - public bool externalControl = true; - [Header("Laser Settings")] public float maxDistance = 20f; public LayerMask collisionMask = ~0; - public float chargeDuration = 3f; // used only if externalControl == false - public float fireDuration = 1f; // used only if externalControl == false + public float chargeDuration = 3f; + public float fireDuration = 1f; [Header("Laser Appearance")] public Color laserColor = Color.red; @@ -26,7 +22,7 @@ public class LaserBeam : MonoBehaviour public float hitRadius = 0.1f; public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide; - [Header("Intro Show (ignored if externalControl)")] + [Header("Intro Show")] public bool showIntro = true; public float initialShowDuration = 3f; public bool introIsDeadly = true; @@ -35,8 +31,8 @@ public class LaserBeam : MonoBehaviour public bool deflectFromBoxSides = true; public string boxTag = "Box"; public float outDistance = 12f; - public float sideExitPush = 0.02f; - public float entryPush = 0.005f; + public float sideExitPush = 0.02f; // will be lifted to >= hitRadius + epsilon at runtime + public float entryPush = 0.005f; // will be lifted to >= 0.25*hitRadius at runtime [Header("Debug")] public bool debugDraw = false; @@ -60,33 +56,22 @@ public class LaserBeam : MonoBehaviour private Collider _lastBox; private int _sideSign = 1; + // External control hook (set by controller if used) + public bool externalControl = true; + void Awake() { SetupLaserRenderer(); - // Always start safe - currentPhase = LaserPhase.Idle; - hasTriggeredDeathThisBurst = false; - DisableLaser(); - StopAllCoroutines(); } void Start() { - // Only self-run if not externally controlled - if (!externalControl && showIntro) + if (showIntro) StartCoroutine(IntroShow()); } void Update() { - if (externalControl) - { - // Passive: only draw debug line if visible - if (debugDraw) Debug.DrawLine(laserStart, laserEnd, Color.cyan); - return; - } - - // Legacy self-FSM (optional use) if (introRunning) return; timer += Time.deltaTime; @@ -131,18 +116,19 @@ public class LaserBeam : MonoBehaviour Debug.DrawLine(laserStart, laserEnd, Color.cyan); } - // LaserBeam.cs — replace SetLaserPhase with this version public void SetLaserPhase(LaserPhase phase) { + // When controller is driving, cancel any self-cycling if (externalControl) { StopAllCoroutines(); introRunning = false; } + if (!line) SetupLaserRenderer(); - // Always clear when entering lethal/lead-in phases - if (phase == LaserPhase.Firing || phase == LaserPhase.Charging) + // Clear 1-kill gate whenever we enter lead-in or lethal phases + if (phase == LaserPhase.Charging || phase == LaserPhase.Firing) hasTriggeredDeathThisBurst = false; currentPhase = phase; @@ -163,7 +149,7 @@ public class LaserBeam : MonoBehaviour UpdateLaserPath(); line.enabled = true; SetLineColor(laserColor); - CheckHit(); + CheckHit(); // immediate check on enter break; } } @@ -209,7 +195,7 @@ public class LaserBeam : MonoBehaviour void DisableLaser() { line.enabled = false; - // Also clear endpoint for safety/debug + // Optional: zero endpoints for clarity in debug laserStart = transform.position; laserEnd = laserStart; } @@ -223,16 +209,12 @@ public class LaserBeam : MonoBehaviour void CheckHit() { - // HARD GATES: never kill unless we’re allowed to - if (!isActiveAndEnabled) return; - if (!gameObject.activeInHierarchy) return; - + // only lethal during Firing, or during Intro if flagged as deadly bool lethalNow = (currentPhase == LaserPhase.Firing) || (introRunning && introIsDeadly); - if (!lethalNow) return; + if (!lethalNow || hasTriggeredDeathThisBurst) return; - if (hasTriggeredDeathThisBurst) return; - - bool CheckSegment(Vector3 a, Vector3 b) + // local helper that can ignore a specific collider (e.g., the deflection box) + bool CheckSegment(Vector3 a, Vector3 b, Collider ignoreCol = null) { Vector3 d = b - a; float len = d.magnitude; @@ -247,12 +229,15 @@ public class LaserBeam : MonoBehaviour Collider bestCol = null; foreach (var h in hits) { - if (h.collider && h.collider.GetComponentInParent() == this) continue; + if (!h.collider) continue; + if (ignoreCol && h.collider == ignoreCol) continue; // NEW: ignore the deflection box + if (h.collider.GetComponentInParent() == this) continue; if (h.distance < best) { best = h.distance; bestT = h.collider.transform; bestCol = h.collider; } } if (!bestT) return false; + // Player? if (bestT.CompareTag(playerTag) || (bestT.root && bestT.root.CompareTag(playerTag))) { hasTriggeredDeathThisBurst = true; @@ -260,6 +245,7 @@ public class LaserBeam : MonoBehaviour return true; } + // Box? (still allow box damage feedback) if (bestCol && bestCol.CompareTag(boxTag)) { var boxHealth = bestCol.GetComponent(); @@ -270,11 +256,12 @@ public class LaserBeam : MonoBehaviour return false; } + // segmented checks if we’re deflecting if (_routeViaSide) { if (CheckSegment(laserStart, _entryPoint)) return; - if (CheckSegment(_entryPoint, _midPoint)) return; - CheckSegment(_midPoint, laserEnd); + if (CheckSegment(_entryPoint, _midPoint, _lastBox)) return; // ignore the box while exiting + CheckSegment(_midPoint, laserEnd, _lastBox); // ignore it on the last leg too } else { @@ -316,6 +303,7 @@ public class LaserBeam : MonoBehaviour line.SetPosition(0, laserStart); line.SetPosition(1, laserEnd); + // No deflect? if (!deflectFromBoxSides || !gotHit || !bestHit.collider.CompareTag(boxTag)) return; var box = bestHit.collider.GetComponent(); @@ -328,21 +316,40 @@ public class LaserBeam : MonoBehaviour } Transform t = box.transform; - _entryPoint = bestHit.point + bestHit.normal * entryPush; + // ensure we don’t start the entry segment inside the box + float safeEntry = Mathf.Max(entryPush, hitRadius * 0.25f); + _entryPoint = bestHit.point + bestHit.normal * safeEntry; + + // compute the midpoint on the box side we’re exiting from Vector3 centerW = t.TransformPoint(box.center); Vector3 half = Vector3.Scale(box.size * 0.5f, t.lossyScale); Vector3 rightW = t.right.normalized; Vector3 outDir = rightW * _sideSign; _midPoint = centerW + rightW * (_sideSign * half.x); + // how much distance remains for leg 2 float traveled = bestHit.distance + Vector3.Distance(bestHit.point, _midPoint); float remain = Mathf.Max(0f, maxDistance - traveled); float leg = Mathf.Min(outDistance, remain); - Vector3 secondStart = _midPoint + outDir * sideExitPush; + // Choose a second start that is definitely OUTSIDE the box surface + float minExit = Mathf.Max(sideExitPush, radius + 0.02f); + Vector3 secondStart = _midPoint + outDir * minExit; + + // If somehow still overlapping the box bounds, nudge out a bit more (guarded) + for (int i = 0; i < 3; i++) + { + // AABBs are conservative; this is cheap and “good enough” + if (box.bounds.Contains(secondStart)) + secondStart += outDir * (radius + 0.02f); + else + break; + } + Vector3 secondEnd = secondStart + outDir * leg; + // Trim second leg if it hits something early if (Physics.SphereCast(secondStart, radius, outDir, out var h2, leg, collisionMask, queryTriggerMode)) { if (!(h2.collider && h2.collider.GetComponentInParent() == this))