2025-07-23 22:07:51 +05:00
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
[RequireComponent(typeof(LineRenderer))]
|
|
|
|
public class LaserBeam : MonoBehaviour
|
|
|
|
{
|
|
|
|
[Header("Laser Settings")]
|
|
|
|
public float maxDistance = 20f;
|
|
|
|
public LayerMask collisionMask = ~0;
|
2025-08-14 20:29:09 +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-14 20:29:09 +05:00
|
|
|
public Color warningColor = new Color(1f, 0.5f, 0f); // Orange
|
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";
|
|
|
|
[Tooltip("Approximate beam thickness for hit tests.")]
|
|
|
|
public float hitRadius = 0.1f;
|
|
|
|
[Tooltip("Should raycasts consider trigger colliders? (Player often uses triggers)")]
|
|
|
|
public QueryTriggerInteraction queryTriggerMode = QueryTriggerInteraction.Collide;
|
|
|
|
|
|
|
|
[Header("Intro Show")]
|
|
|
|
public bool showIntro = true;
|
|
|
|
public float initialShowDuration = 3f;
|
|
|
|
[Tooltip("If true, the intro beam can kill the player too.")]
|
|
|
|
public bool introIsDeadly = true;
|
|
|
|
|
|
|
|
[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;
|
|
|
|
|
|
|
|
private enum LaserState { Idle, Charging, Firing }
|
|
|
|
private LaserState currentState = LaserState.Idle;
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// cache root to ignore self-hits
|
|
|
|
Transform _selfRoot;
|
|
|
|
|
|
|
|
void Awake()
|
2025-07-23 22:07:51 +05:00
|
|
|
{
|
2025-08-14 20:29:09 +05:00
|
|
|
_selfRoot = transform.root;
|
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-08-14 20:29:09 +05:00
|
|
|
if (showIntro)
|
|
|
|
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 (currentState)
|
|
|
|
{
|
|
|
|
case LaserState.Idle:
|
|
|
|
hasTriggeredDeathThisBurst = false; // reset per cycle
|
|
|
|
if (timer >= chargeDuration)
|
|
|
|
{
|
|
|
|
timer = 0f;
|
|
|
|
currentState = LaserState.Charging;
|
|
|
|
StartCharging();
|
|
|
|
}
|
|
|
|
break;
|
2025-07-23 22:07:51 +05:00
|
|
|
|
2025-08-14 20:29:09 +05:00
|
|
|
case LaserState.Charging:
|
|
|
|
if (timer >= 1f)
|
|
|
|
{
|
|
|
|
timer = 0f;
|
|
|
|
currentState = LaserState.Firing;
|
|
|
|
FireLaser();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
BlinkWarning();
|
|
|
|
}
|
|
|
|
break;
|
2025-07-23 22:07:51 +05:00
|
|
|
|
2025-08-14 20:29:09 +05:00
|
|
|
case LaserState.Firing:
|
|
|
|
if (timer >= fireDuration)
|
|
|
|
{
|
|
|
|
timer = 0f;
|
|
|
|
currentState = LaserState.Idle;
|
|
|
|
DisableLaser();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
UpdateLaserPath();
|
|
|
|
CheckHit();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2025-07-23 22:07:51 +05:00
|
|
|
|
2025-08-14 20:29:09 +05:00
|
|
|
if (debugDraw)
|
2025-07-23 22:07:51 +05:00
|
|
|
{
|
2025-08-14 20:29:09 +05:00
|
|
|
Debug.DrawLine(laserStart, laserEnd, Color.cyan, 0f, false);
|
2025-07-23 22:07:51 +05:00
|
|
|
}
|
2025-08-14 20:29:09 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------- Intro show ----------------
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------- States ----------------
|
|
|
|
void StartCharging()
|
|
|
|
{
|
|
|
|
UpdateLaserPath();
|
|
|
|
line.enabled = true;
|
|
|
|
SetLineColor(warningColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void BlinkWarning()
|
|
|
|
{
|
|
|
|
float blink = Mathf.PingPong(Time.time * 5f, 1f);
|
|
|
|
Color blinkColor = Color.Lerp(Color.clear, warningColor, blink);
|
|
|
|
SetLineColor(blinkColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void FireLaser()
|
|
|
|
{
|
|
|
|
UpdateLaserPath();
|
|
|
|
SetLineColor(laserColor);
|
|
|
|
|
|
|
|
hasTriggeredDeathThisBurst = false;
|
|
|
|
CheckHit(); // initial frame
|
|
|
|
}
|
|
|
|
|
|
|
|
void DisableLaser()
|
|
|
|
{
|
|
|
|
line.enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------- Hit Detection (robust) ----------------
|
|
|
|
void CheckHit()
|
|
|
|
{
|
|
|
|
if (hasTriggeredDeathThisBurst) return;
|
|
|
|
|
|
|
|
Vector3 dir = (laserEnd - laserStart).normalized;
|
|
|
|
float distToEnd = Vector3.Distance(laserStart, laserEnd);
|
|
|
|
|
|
|
|
// Cast along visible beam, then pick the nearest valid hit
|
|
|
|
RaycastHit[] hits = Physics.SphereCastAll(
|
|
|
|
laserStart, hitRadius, dir,
|
|
|
|
distToEnd, collisionMask, queryTriggerMode
|
|
|
|
);
|
|
|
|
|
|
|
|
if (hits == null || hits.Length == 0) return;
|
|
|
|
|
|
|
|
float bestDist = float.MaxValue;
|
|
|
|
Transform best = null;
|
|
|
|
|
|
|
|
foreach (var h in hits)
|
|
|
|
{
|
|
|
|
// Ignore self (any collider in our own hierarchy)
|
|
|
|
if (h.collider && h.collider.GetComponentInParent<LaserBeam>() == this)
|
|
|
|
continue;
|
|
|
|
// Keep the nearest hit under our visible segment
|
|
|
|
if (h.distance < bestDist)
|
|
|
|
{
|
|
|
|
bestDist = h.distance;
|
|
|
|
best = h.collider.transform;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (best == null) return;
|
|
|
|
|
|
|
|
// Player detection: check both object and its root
|
|
|
|
bool isPlayer =
|
|
|
|
best.CompareTag(playerTag) ||
|
|
|
|
(best.root != null && best.root.CompareTag(playerTag));
|
|
|
|
|
|
|
|
if (isPlayer)
|
2025-07-23 22:07:51 +05:00
|
|
|
{
|
2025-08-14 20:29:09 +05:00
|
|
|
Debug.Log("Laser hit player: " + best.name);
|
|
|
|
hasTriggeredDeathThisBurst = true;
|
|
|
|
CrateEscapeGameManager.Instance?.OnPlayerHitByLaser();
|
2025-07-23 22:07:51 +05:00
|
|
|
}
|
2025-08-14 20:29:09 +05:00
|
|
|
}
|
|
|
|
|
|
|
|
void UpdateLaserPath()
|
|
|
|
{
|
|
|
|
laserStart = transform.position;
|
|
|
|
Vector3 dir = transform.forward;
|
|
|
|
|
|
|
|
// SphereCast to find the FIRST valid hit along the visible beam,
|
|
|
|
// ignoring ONLY this laser's own colliders.
|
|
|
|
float radius = Mathf.Max(0.0001f, hitRadius);
|
|
|
|
RaycastHit[] hits = Physics.SphereCastAll(
|
|
|
|
laserStart, radius, dir, maxDistance, collisionMask, queryTriggerMode
|
|
|
|
);
|
|
|
|
|
|
|
|
Vector3 end = laserStart + dir * maxDistance;
|
|
|
|
float bestDist = float.MaxValue;
|
2025-07-23 22:07:51 +05:00
|
|
|
|
2025-08-14 20:29:09 +05:00
|
|
|
if (hits != null && hits.Length > 0)
|
|
|
|
{
|
|
|
|
foreach (var h in hits)
|
|
|
|
{
|
|
|
|
if (!h.collider) continue;
|
|
|
|
|
|
|
|
// Ignore ONLY this LaserBeam's colliders (not the whole root/level)
|
|
|
|
if (h.collider.GetComponentInParent<LaserBeam>() == this)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (h.distance < bestDist)
|
|
|
|
{
|
|
|
|
bestDist = h.distance;
|
|
|
|
end = h.point;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
laserEnd = end;
|
|
|
|
|
|
|
|
line.SetPosition(0, laserStart);
|
|
|
|
line.SetPosition(1, laserEnd);
|
|
|
|
}
|
|
|
|
|
|
|
|
// ---------------- Utils ----------------
|
|
|
|
void SetLineColor(Color c)
|
|
|
|
{
|
|
|
|
line.material.color = c;
|
|
|
|
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
|
|
|
|
{
|
2025-08-14 20:29:09 +05:00
|
|
|
Debug.LogWarning("Custom/EmissiveLaser shader not found. Using fallback.");
|
2025-07-23 22:07:51 +05:00
|
|
|
line.material = new Material(Shader.Find("Sprites/Default"));
|
|
|
|
line.material.color = laserColor;
|
|
|
|
}
|
|
|
|
|
2025-08-14 20:29:09 +05:00
|
|
|
line.enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OnDrawGizmosSelected()
|
|
|
|
{
|
|
|
|
if (!debugDraw) return;
|
|
|
|
Gizmos.color = Color.magenta;
|
|
|
|
Gizmos.DrawWireSphere(transform.position, hitRadius);
|
2025-07-23 22:07:51 +05:00
|
|
|
}
|
|
|
|
}
|