using UnityEngine; 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 [Header("Laser Appearance")] public Color laserColor = Color.red; public Color warningColor = new Color(1f, 0.5f, 0f); public float laserWidth = 0.05f; public float emissionStrength = 5f; public float scrollSpeed = 1f; [Header("Player Hit")] public string playerTag = "Player"; public float hitRadius = 0.1f; public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide; [Header("Intro Show (ignored if externalControl)")] public bool showIntro = true; public float initialShowDuration = 3f; public bool introIsDeadly = true; [Header("Deflection (Box side center)")] public bool deflectFromBoxSides = true; public string boxTag = "Box"; public float outDistance = 12f; public float sideExitPush = 0.02f; public float entryPush = 0.005f; [Header("Debug")] public bool debugDraw = false; private LineRenderer line; private float timer = 0f; public enum LaserPhase { Idle, Charging, Firing } private LaserPhase currentPhase = LaserPhase.Idle; private Vector3 laserStart; private Vector3 laserEnd; private bool introRunning = false; private bool hasTriggeredDeathThisBurst = false; // Deflection private bool _routeViaSide; private Vector3 _entryPoint; private Vector3 _midPoint; private Collider _lastBox; private int _sideSign = 1; 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) 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; switch (currentPhase) { case LaserPhase.Idle: hasTriggeredDeathThisBurst = false; if (timer >= chargeDuration) { timer = 0f; SetLaserPhase(LaserPhase.Charging); } break; case LaserPhase.Charging: if (timer >= 1f) { timer = 0f; SetLaserPhase(LaserPhase.Firing); } else { TickLaserDuringCharging(); } break; case LaserPhase.Firing: if (timer >= fireDuration) { timer = 0f; SetLaserPhase(LaserPhase.Idle); } else { TickLaserDuringFiring(); } break; } if (debugDraw) Debug.DrawLine(laserStart, laserEnd, Color.cyan); } // LaserBeam.cs — replace SetLaserPhase with this version public void SetLaserPhase(LaserPhase phase) { if (externalControl) { StopAllCoroutines(); introRunning = false; } if (!line) SetupLaserRenderer(); // Always clear when entering lethal/lead-in phases if (phase == LaserPhase.Firing || phase == LaserPhase.Charging) hasTriggeredDeathThisBurst = false; currentPhase = phase; switch (phase) { case LaserPhase.Idle: DisableLaser(); break; case LaserPhase.Charging: UpdateLaserPath(); line.enabled = true; SetLineColor(warningColor); break; case LaserPhase.Firing: UpdateLaserPath(); line.enabled = true; SetLineColor(laserColor); CheckHit(); break; } } public void TickLaserDuringFiring() { if (currentPhase != LaserPhase.Firing) return; UpdateLaserPath(); CheckHit(); } public void TickLaserDuringCharging() { if (currentPhase != LaserPhase.Charging) return; BlinkWarning(); UpdateLaserPath(); } IEnumerator IntroShow() { introRunning = true; float t = 0f; line.enabled = true; SetLineColor(laserColor); hasTriggeredDeathThisBurst = false; while (t < initialShowDuration) { UpdateLaserPath(); if (introIsDeadly && !hasTriggeredDeathThisBurst) CheckHit(); t += Time.deltaTime; yield return null; } line.enabled = false; timer = 0f; SetLaserPhase(LaserPhase.Idle); introRunning = false; } void DisableLaser() { line.enabled = false; // Also clear endpoint for safety/debug laserStart = transform.position; laserEnd = laserStart; } void BlinkWarning() { float blink = Mathf.PingPong(Time.time * 5f, 1f); Color blinkColor = Color.Lerp(Color.clear, warningColor, blink); SetLineColor(blinkColor); } void CheckHit() { // HARD GATES: never kill unless we’re allowed to if (!isActiveAndEnabled) return; if (!gameObject.activeInHierarchy) return; bool lethalNow = (currentPhase == LaserPhase.Firing) || (introRunning && introIsDeadly); if (!lethalNow) return; if (hasTriggeredDeathThisBurst) return; bool CheckSegment(Vector3 a, Vector3 b) { Vector3 d = b - a; float len = d.magnitude; if (len <= 0.0001f) return false; d /= len; var hits = Physics.SphereCastAll(a, hitRadius, d, len, collisionMask, queryTriggerMode); if (hits == null || hits.Length == 0) return false; float best = float.MaxValue; Transform bestT = null; Collider bestCol = null; foreach (var h in hits) { if (h.collider && h.collider.GetComponentInParent() == this) continue; if (h.distance < best) { best = h.distance; bestT = h.collider.transform; bestCol = h.collider; } } if (!bestT) return false; if (bestT.CompareTag(playerTag) || (bestT.root && bestT.root.CompareTag(playerTag))) { hasTriggeredDeathThisBurst = true; CrateEscapeGameManager.Instance?.OnPlayerHitByLaser(); return true; } if (bestCol && bestCol.CompareTag(boxTag)) { var boxHealth = bestCol.GetComponent(); if (boxHealth != null) boxHealth.TakeLaserDamage(); } return false; } if (_routeViaSide) { if (CheckSegment(laserStart, _entryPoint)) return; if (CheckSegment(_entryPoint, _midPoint)) return; CheckSegment(_midPoint, laserEnd); } else { CheckSegment(laserStart, laserEnd); } } void UpdateLaserPath() { laserStart = transform.position; Vector3 dir = transform.forward; float radius = Mathf.Max(0.0001f, hitRadius); RaycastHit[] hits = Physics.SphereCastAll(laserStart, radius, dir, maxDistance, collisionMask, queryTriggerMode); Vector3 straightEnd = laserStart + dir * maxDistance; RaycastHit bestHit = default; float bestDist = float.MaxValue; bool gotHit = false; if (hits != null) { foreach (var h in hits) { if (!h.collider) continue; if (h.collider.GetComponentInParent() == this) continue; if (h.distance < bestDist) { bestDist = h.distance; bestHit = h; gotHit = true; straightEnd = h.point; } } } _routeViaSide = false; laserEnd = straightEnd; line.positionCount = 2; line.SetPosition(0, laserStart); line.SetPosition(1, laserEnd); if (!deflectFromBoxSides || !gotHit || !bestHit.collider.CompareTag(boxTag)) return; var box = bestHit.collider.GetComponent(); if (!box) return; if (_lastBox != bestHit.collider) { _lastBox = bestHit.collider; _sideSign = (Random.value < 0.5f) ? -1 : 1; } Transform t = box.transform; _entryPoint = bestHit.point + bestHit.normal * entryPush; 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); 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; Vector3 secondEnd = secondStart + outDir * leg; if (Physics.SphereCast(secondStart, radius, outDir, out var h2, leg, collisionMask, queryTriggerMode)) { if (!(h2.collider && h2.collider.GetComponentInParent() == this)) secondEnd = h2.point; } _routeViaSide = true; laserEnd = secondEnd; line.positionCount = 4; line.SetPosition(0, laserStart); line.SetPosition(1, _entryPoint); line.SetPosition(2, _midPoint); line.SetPosition(3, laserEnd); } public void ResetCycle() { StopAllCoroutines(); introRunning = false; timer = 0f; currentPhase = LaserPhase.Idle; hasTriggeredDeathThisBurst = false; DisableLaser(); } void SetLineColor(Color c) { if (line.material.HasProperty("_Color")) line.material.SetColor("_Color", c); line.startColor = c; line.endColor = c; } public void SetupLaserRenderer() { line = GetComponent(); if (!line) line = gameObject.AddComponent(); line.positionCount = 2; line.useWorldSpace = true; line.loop = false; line.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; line.receiveShadows = false; line.widthMultiplier = laserWidth; Shader laserShader = Shader.Find("Custom/EmissiveLaser"); if (laserShader != null) { Material laserMat = new Material(laserShader); laserMat.SetColor("_Color", laserColor); laserMat.SetFloat("_Emission", emissionStrength); laserMat.SetFloat("_ScrollSpeed", scrollSpeed); line.material = laserMat; } else { line.material = new Material(Shader.Find("Sprites/Default")); line.material.color = laserColor; } line.enabled = false; } }