using UnityEngine; [RequireComponent(typeof(LineRenderer))] public class LaserBeam : MonoBehaviour { [Header("Laser Settings")] public float maxDistance = 20f; public LayerMask collisionMask = ~0; public float chargeDuration = 3f; public float fireDuration = 1f; [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")] 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; private enum LaserState { Idle, Charging, Firing } public enum LaserPhase { Idle, Charging, Firing } private LaserPhase currentPhase = LaserPhase.Idle; private LaserState currentState = LaserState.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(); } void Start() { if (showIntro) StartCoroutine(IntroShow()); } void Update() { if (introRunning) return; timer += Time.deltaTime; switch (currentState) { case LaserState.Idle: hasTriggeredDeathThisBurst = false; if (timer >= chargeDuration) { timer = 0f; currentState = LaserState.Charging; StartCharging(); } break; case LaserState.Charging: if (timer >= 1f) { timer = 0f; currentState = LaserState.Firing; FireLaser(); } else { BlinkWarning(); } break; case LaserState.Firing: if (timer >= fireDuration) { timer = 0f; currentState = LaserState.Idle; DisableLaser(); } else { UpdateLaserPath(); CheckHit(); } break; } if (debugDraw) Debug.DrawLine(laserStart, laserEnd, Color.cyan); } public void SetLaserPhase(LaserPhase phase) { currentPhase = phase; switch (phase) { case LaserPhase.Idle: DisableLaser(); break; case LaserPhase.Charging: UpdateLaserPath(); line.enabled = true; SetLineColor(warningColor); break; case LaserPhase.Firing: UpdateLaserPath(); SetLineColor(laserColor); hasTriggeredDeathThisBurst = false; CheckHit(); break; } } public void TickLaserDuringFiring() { if (currentPhase == LaserPhase.Firing) { UpdateLaserPath(); CheckHit(); } } public void TickLaserDuringCharging() { if (currentPhase == LaserPhase.Charging) { BlinkWarning(); UpdateLaserPath(); } } System.Collections.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; currentState = LaserState.Idle; introRunning = false; } void StartCharging() { UpdateLaserPath(); line.enabled = true; SetLineColor(warningColor); } void FireLaser() { UpdateLaserPath(); SetLineColor(laserColor); hasTriggeredDeathThisBurst = false; CheckHit(); } void DisableLaser() { line.enabled = false; } void CheckHit() { 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; // Hit player if (bestT.CompareTag(playerTag) || (bestT.root && bestT.root.CompareTag(playerTag))) { hasTriggeredDeathThisBurst = true; Debug.Log("Laser hit player: " + bestT.name); CrateEscapeGameManager.Instance?.OnPlayerHitByLaser(); return true; } // Hit box if (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 BlinkWarning() { float blink = Mathf.PingPong(Time.time * 5f, 1f); Color blinkColor = Color.Lerp(Color.clear, warningColor, blink); SetLineColor(blinkColor); } 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); // Side-deflection logic 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; currentState = LaserState.Idle; 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; } }