418 lines
12 KiB
C#
Raw Permalink Normal View History

2025-07-23 22:07:51 +05:00
using UnityEngine;
using System.Collections;
2025-07-23 22:07:51 +05:00
[RequireComponent(typeof(LineRenderer))]
public class LaserBeam : MonoBehaviour
{
[Header("Laser Settings")]
public float maxDistance = 20f;
public LayerMask collisionMask = ~0;
2025-09-13 02:22:13 +05:00
public float chargeDuration = 3f;
public float fireDuration = 1f;
2025-07-23 22:07:51 +05:00
[Header("Laser Appearance")]
public Color laserColor = Color.red;
2025-08-19 16:59:43 +05:00
public Color warningColor = new Color(1f, 0.5f, 0f);
2025-07-23 22:07:51 +05:00
public float laserWidth = 0.05f;
public float emissionStrength = 5f;
public float scrollSpeed = 1f;
2025-08-14 20:29:09 +05:00
[Header("Player Hit")]
public string playerTag = "Player";
public float hitRadius = 0.1f;
public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide;
2025-09-13 02:22:13 +05:00
[Header("Intro Show")]
2025-08-14 20:29:09 +05:00
public bool showIntro = true;
public float initialShowDuration = 3f;
public bool introIsDeadly = true;
2025-08-19 16:59:43 +05:00
[Header("Deflection (Box side center)")]
public bool deflectFromBoxSides = true;
public string boxTag = "Box";
public float outDistance = 12f;
2025-09-13 02:22:13 +05:00
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
2025-08-19 16:59:43 +05:00
2025-08-14 20:29:09 +05:00
[Header("Debug")]
public bool debugDraw = false;
2025-07-23 22:07:51 +05:00
private LineRenderer line;
2025-08-14 20:29:09 +05:00
private float timer = 0f;
2025-08-19 16:59:43 +05:00
public enum LaserPhase { Idle, Charging, Firing }
private LaserPhase currentPhase = LaserPhase.Idle;
2025-08-14 20:29:09 +05:00
private Vector3 laserStart;
private Vector3 laserEnd;
2025-07-23 22:07:51 +05:00
2025-08-14 20:29:09 +05:00
private bool introRunning = false;
private bool hasTriggeredDeathThisBurst = false;
2025-08-19 16:59:43 +05:00
// Deflection
private bool _routeViaSide;
private Vector3 _entryPoint;
private Vector3 _midPoint;
private Collider _lastBox;
private int _sideSign = 1;
2025-08-14 20:29:09 +05:00
2025-09-13 02:22:13 +05:00
// External control hook (set by controller if used)
public bool externalControl = true;
2025-08-14 20:29:09 +05:00
void Awake()
2025-07-23 22:07:51 +05:00
{
SetupLaserRenderer();
}
2025-08-14 20:29:09 +05:00
void Start()
2025-07-23 22:07:51 +05:00
{
2025-09-13 02:22:13 +05:00
if (showIntro)
2025-08-14 20:29:09 +05:00
StartCoroutine(IntroShow());
2025-07-23 22:07:51 +05:00
}
void Update()
{
2025-08-14 20:29:09 +05:00
if (introRunning) return;
timer += Time.deltaTime;
switch (currentPhase)
2025-08-14 20:29:09 +05:00
{
case LaserPhase.Idle:
2025-08-19 16:59:43 +05:00
hasTriggeredDeathThisBurst = false;
2025-08-14 20:29:09 +05:00
if (timer >= chargeDuration)
{
timer = 0f;
SetLaserPhase(LaserPhase.Charging);
2025-08-14 20:29:09 +05:00
}
break;
2025-07-23 22:07:51 +05:00
case LaserPhase.Charging:
2025-08-14 20:29:09 +05:00
if (timer >= 1f)
{
timer = 0f;
SetLaserPhase(LaserPhase.Firing);
2025-08-14 20:29:09 +05:00
}
else
{
TickLaserDuringCharging();
2025-08-14 20:29:09 +05:00
}
break;
2025-07-23 22:07:51 +05:00
case LaserPhase.Firing:
2025-08-14 20:29:09 +05:00
if (timer >= fireDuration)
{
timer = 0f;
SetLaserPhase(LaserPhase.Idle);
2025-08-14 20:29:09 +05:00
}
else
{
TickLaserDuringFiring();
2025-08-14 20:29:09 +05:00
}
break;
}
2025-07-23 22:07:51 +05:00
2025-08-14 20:29:09 +05:00
if (debugDraw)
2025-08-19 16:59:43 +05:00
Debug.DrawLine(laserStart, laserEnd, Color.cyan);
}
public void SetLaserPhase(LaserPhase phase)
{
2025-09-13 02:22:13 +05:00
// When controller is driving, cancel any self-cycling
2025-09-09 16:46:17 +05:00
if (externalControl)
{
StopAllCoroutines();
introRunning = false;
}
2025-09-13 02:22:13 +05:00
2025-09-13 02:08:34 +05:00
if (!line) SetupLaserRenderer();
2025-09-09 16:46:17 +05:00
2025-09-13 02:22:13 +05:00
// Clear 1-kill gate whenever we enter lead-in or lethal phases
if (phase == LaserPhase.Charging || phase == LaserPhase.Firing)
2025-09-13 02:08:34 +05:00
hasTriggeredDeathThisBurst = false;
2025-09-09 16:46:17 +05:00
2025-08-19 16:59:43 +05:00
currentPhase = phase;
switch (phase)
{
case LaserPhase.Idle:
DisableLaser();
break;
case LaserPhase.Charging:
UpdateLaserPath();
line.enabled = true;
SetLineColor(warningColor);
break;
case LaserPhase.Firing:
UpdateLaserPath();
2025-09-09 16:46:17 +05:00
line.enabled = true;
2025-08-19 16:59:43 +05:00
SetLineColor(laserColor);
2025-09-13 02:22:13 +05:00
CheckHit(); // immediate check on enter
2025-08-19 16:59:43 +05:00
break;
}
}
public void TickLaserDuringFiring()
{
2025-09-09 16:46:17 +05:00
if (currentPhase != LaserPhase.Firing) return;
UpdateLaserPath();
CheckHit();
2025-08-19 16:59:43 +05:00
}
public void TickLaserDuringCharging()
{
2025-09-09 16:46:17 +05:00
if (currentPhase != LaserPhase.Charging) return;
BlinkWarning();
UpdateLaserPath();
2025-08-14 20:29:09 +05:00
}
IEnumerator IntroShow()
2025-08-14 20:29:09 +05:00
{
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);
2025-08-14 20:29:09 +05:00
introRunning = false;
}
void DisableLaser()
2025-08-14 20:29:09 +05:00
{
line.enabled = false;
2025-09-13 02:22:13 +05:00
// Optional: zero endpoints for clarity in debug
2025-09-09 16:46:17 +05:00
laserStart = transform.position;
laserEnd = laserStart;
2025-08-14 20:29:09 +05:00
}
void BlinkWarning()
2025-08-14 20:29:09 +05:00
{
float blink = Mathf.PingPong(Time.time * 5f, 1f);
Color blinkColor = Color.Lerp(Color.clear, warningColor, blink);
SetLineColor(blinkColor);
2025-08-14 20:29:09 +05:00
}
void CheckHit()
{
2025-09-13 02:22:13 +05:00
// only lethal during Firing, or during Intro if flagged as deadly
2025-09-09 16:46:17 +05:00
bool lethalNow = (currentPhase == LaserPhase.Firing) || (introRunning && introIsDeadly);
2025-09-13 02:22:13 +05:00
if (!lethalNow || hasTriggeredDeathThisBurst) return;
2025-09-09 16:46:17 +05:00
2025-09-13 02:22:13 +05:00
// local helper that can ignore a specific collider (e.g., the deflection box)
bool CheckSegment(Vector3 a, Vector3 b, Collider ignoreCol = null)
2025-08-18 17:52:40 +05:00
{
Vector3 d = b - a;
float len = d.magnitude;
if (len <= 0.0001f) return false;
d /= len;
2025-08-14 20:29:09 +05:00
2025-08-18 17:52:40 +05:00
var hits = Physics.SphereCastAll(a, hitRadius, d, len, collisionMask, queryTriggerMode);
if (hits == null || hits.Length == 0) return false;
2025-08-14 20:29:09 +05:00
2025-08-18 17:52:40 +05:00
float best = float.MaxValue;
Transform bestT = null;
2025-08-19 16:59:43 +05:00
Collider bestCol = null;
2025-08-18 17:52:40 +05:00
foreach (var h in hits)
{
2025-09-13 02:22:13 +05:00
if (!h.collider) continue;
if (ignoreCol && h.collider == ignoreCol) continue; // NEW: ignore the deflection box
if (h.collider.GetComponentInParent<LaserBeam>() == this) continue;
2025-08-19 16:59:43 +05:00
if (h.distance < best) { best = h.distance; bestT = h.collider.transform; bestCol = h.collider; }
2025-08-18 17:52:40 +05:00
}
2025-08-19 16:59:43 +05:00
2025-08-18 17:52:40 +05:00
if (!bestT) return false;
2025-08-14 20:29:09 +05:00
2025-09-13 02:22:13 +05:00
// Player?
2025-08-19 16:59:43 +05:00
if (bestT.CompareTag(playerTag) || (bestT.root && bestT.root.CompareTag(playerTag)))
2025-08-14 20:29:09 +05:00
{
2025-08-18 17:52:40 +05:00
hasTriggeredDeathThisBurst = true;
CrateEscapeGameManager.Instance?.OnPlayerHitByLaser();
return true;
2025-08-14 20:29:09 +05:00
}
2025-08-19 16:59:43 +05:00
2025-09-13 02:22:13 +05:00
// Box? (still allow box damage feedback)
2025-09-09 16:46:17 +05:00
if (bestCol && bestCol.CompareTag(boxTag))
2025-08-19 16:59:43 +05:00
{
var boxHealth = bestCol.GetComponent<LaserBoxHealth>();
if (boxHealth != null)
boxHealth.TakeLaserDamage();
}
2025-08-18 17:52:40 +05:00
return false;
2025-08-14 20:29:09 +05:00
}
2025-09-13 02:22:13 +05:00
// segmented checks if we<77>re deflecting
2025-08-18 17:52:40 +05:00
if (_routeViaSide)
2025-07-23 22:07:51 +05:00
{
2025-08-18 17:52:40 +05:00
if (CheckSegment(laserStart, _entryPoint)) return;
2025-09-13 02:22:13 +05:00
if (CheckSegment(_entryPoint, _midPoint, _lastBox)) return; // ignore the box while exiting
CheckSegment(_midPoint, laserEnd, _lastBox); // ignore it on the last leg too
2025-08-18 17:52:40 +05:00
}
else
{
CheckSegment(laserStart, laserEnd);
2025-07-23 22:07:51 +05:00
}
2025-08-14 20:29:09 +05:00
}
2025-08-14 20:29:09 +05:00
void UpdateLaserPath()
{
laserStart = transform.position;
Vector3 dir = transform.forward;
float radius = Mathf.Max(0.0001f, hitRadius);
2025-08-18 17:52:40 +05:00
RaycastHit[] hits = Physics.SphereCastAll(laserStart, radius, dir, maxDistance, collisionMask, queryTriggerMode);
Vector3 straightEnd = laserStart + dir * maxDistance;
RaycastHit bestHit = default;
2025-08-14 20:29:09 +05:00
float bestDist = float.MaxValue;
2025-08-18 17:52:40 +05:00
bool gotHit = false;
2025-07-23 22:07:51 +05:00
2025-08-18 17:52:40 +05:00
if (hits != null)
2025-08-14 20:29:09 +05:00
{
foreach (var h in hits)
{
if (!h.collider) continue;
2025-08-18 17:52:40 +05:00
if (h.collider.GetComponentInParent<LaserBeam>() == this) continue;
2025-08-14 20:29:09 +05:00
if (h.distance < bestDist)
{
bestDist = h.distance;
2025-08-18 17:52:40 +05:00
bestHit = h;
gotHit = true;
straightEnd = h.point;
2025-08-14 20:29:09 +05:00
}
}
}
2025-08-18 17:52:40 +05:00
_routeViaSide = false;
laserEnd = straightEnd;
line.positionCount = 2;
2025-08-14 20:29:09 +05:00
line.SetPosition(0, laserStart);
line.SetPosition(1, laserEnd);
2025-08-18 17:52:40 +05:00
2025-09-13 02:22:13 +05:00
// No deflect?
2025-08-19 16:59:43 +05:00
if (!deflectFromBoxSides || !gotHit || !bestHit.collider.CompareTag(boxTag)) return;
2025-08-18 17:52:40 +05:00
var box = bestHit.collider.GetComponent<BoxCollider>();
if (!box) return;
if (_lastBox != bestHit.collider)
{
_lastBox = bestHit.collider;
_sideSign = (Random.value < 0.5f) ? -1 : 1;
}
Transform t = box.transform;
2025-09-13 02:22:13 +05:00
// ensure we don<6F>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<77>re exiting from
2025-08-18 17:52:40 +05:00
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);
2025-09-13 02:22:13 +05:00
// how much distance remains for leg 2
2025-08-18 17:52:40 +05:00
float traveled = bestHit.distance + Vector3.Distance(bestHit.point, _midPoint);
float remain = Mathf.Max(0f, maxDistance - traveled);
float leg = Mathf.Min(outDistance, remain);
2025-09-13 02:22:13 +05:00
// 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 <20>good enough<67>
if (box.bounds.Contains(secondStart))
secondStart += outDir * (radius + 0.02f);
else
break;
}
2025-08-18 17:52:40 +05:00
Vector3 secondEnd = secondStart + outDir * leg;
2025-09-13 02:22:13 +05:00
// Trim second leg if it hits something early
2025-08-18 17:52:40 +05:00
if (Physics.SphereCast(secondStart, radius, outDir, out var h2, leg, collisionMask, queryTriggerMode))
{
if (!(h2.collider && h2.collider.GetComponentInParent<LaserBeam>() == 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);
2025-08-14 20:29:09 +05:00
}
2025-08-19 16:59:43 +05:00
public void ResetCycle()
{
StopAllCoroutines();
introRunning = false;
timer = 0f;
currentPhase = LaserPhase.Idle;
hasTriggeredDeathThisBurst = false;
DisableLaser();
}
2025-08-14 20:29:09 +05:00
void SetLineColor(Color c)
{
2025-08-19 16:59:43 +05:00
if (line.material.HasProperty("_Color"))
line.material.SetColor("_Color", c);
2025-08-14 20:29:09 +05:00
line.startColor = c;
line.endColor = c;
2025-07-23 22:07:51 +05:00
}
public void SetupLaserRenderer()
{
line = GetComponent<LineRenderer>();
2025-08-14 20:29:09 +05:00
if (!line) line = gameObject.AddComponent<LineRenderer>();
2025-07-23 22:07:51 +05:00
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;
}
2025-08-14 20:29:09 +05:00
line.enabled = false;
}
2025-07-23 22:07:51 +05:00
}