MiniGames/Assets/Scripts/CubeClash/CubeClash_CubeLid.cs

372 lines
14 KiB
C#
Raw Normal View History

using DG.Tweening;
2025-09-01 05:03:00 +05:00
using UnityEngine;
using System.Collections;
using System.Threading; // for Interlocked
2025-09-01 05:03:00 +05:00
[RequireComponent(typeof(Renderer))]
2025-09-01 05:03:00 +05:00
public class CubeClash_CubeLid : MonoBehaviour
{
[Header("Launch")]
2025-09-01 05:03:00 +05:00
public float launchForce = 20f;
[Header("Launch Direction (camera-relative)")]
[Tooltip("x = camera right, y = camera forward")]
public Vector2 camSpaceDir = new Vector2(0f, 1f);
[Tooltip("Scale applied to the forward part relative to launchForce (final = launchForce * forwardScale)")]
public float forwardScale = 10f;
[Tooltip("Flatten the camera so direction is on XZ plane")]
public bool flattenCamera = true;
[Header("Cycle Timing")]
[Tooltip("Full cycle: (rest) + (pre-blink) + (active-glow). Repeats.")]
2025-09-01 05:03:00 +05:00
public float activeInterval = 8f;
[Tooltip("Blink time BEFORE activation.")]
public float preBlinkSeconds = 1.5f;
[Tooltip("Time the cube stays steadily glowing AFTER activation.")]
public float activeGlowSeconds = 2.0f;
[Header("Blink / Glow")]
[Tooltip("One full blink (up+down) duration.")]
public float blinkPulseDuration = 0.25f;
[Tooltip("Fade times for entering/exiting steady glow.")]
public float glowFadeIn = 0.12f, glowFadeOut = 0.12f;
[Header("Emission (last material slot)")]
[Tooltip("Enable Emission on the last material of this renderer in the Inspector.")]
public Color emissionBaseColor = Color.white;
public float offEmission = 0f;
public float blinkEmission = 2.0f;
public float activeGlowIntensity = 2.0f;
public bool usePropertyBlock = true;
[Header("Detection Volume")]
[Tooltip("If true, OverlapBox uses this BoxCollider; else uses Local Box below.")]
public bool useColliderForVolume = true;
public BoxCollider volumeSource; // auto-found if null
public LayerMask detectMask = ~0;
public QueryTriggerInteraction triggerQuery = QueryTriggerInteraction.Ignore;
[Tooltip("Fallback volume if no BoxCollider is assigned.")]
public Vector3 localBoxCenter = Vector3.zero;
public Vector3 localBoxSize = new Vector3(1, 1, 1);
[Header("Gizmos")]
public bool drawGizmos = true;
public Color gizmoFill = new Color(0f, 1f, 1f, 0.15f);
public Color gizmoWire = new Color(0f, 1f, 1f, 1f);
// ─────────────────────────── Global Coordination ───────────────────────────
[Header("Global Coordination")]
[Tooltip("If true, lids coordinate so only one activates at a time.")]
public bool coordinateGlobally = true;
[Tooltip("Random extra wait added to each lids rest period to desync starts.")]
public Vector2 restJitterRange = new Vector2(0.0f, 1.0f);
[Tooltip("Random jitter while waiting for the global slot, to randomize who acquires it.")]
public Vector2 acquireJitterRange = new Vector2(0.0f, 0.2f);
[Tooltip("Minimum spacing between two different lids activating (seconds).")]
public float minGlobalSeparation = 0.25f;
2025-09-01 05:03:00 +05:00
private static int s_gateToken = 0; // 0 = free, 1 = held
private static float s_nextAllowedTime = 0f; // spacing across lids
private bool _holdingGate = false;
// ───────────────────────────────────────────────────────────────────────────
// --- internals ---
private Renderer _rend;
private int _submeshIndex = -1;
private Material _instancedMat; // used if not MPB
private MaterialPropertyBlock _mpb; // used if MPB
private float _emissionIntensity;
private Tween _glowTween; // current tween
private Coroutine _loopCo;
private static readonly int EmissionColorID = Shader.PropertyToID("_EmissionColor");
private static readonly int EmissiveColorID = Shader.PropertyToID("_EmissiveColor");
void Awake()
2025-09-01 05:03:00 +05:00
{
_rend = GetComponentInChildren<Renderer>();
if (!volumeSource) volumeSource = GetComponent<BoxCollider>();
if (!volumeSource) volumeSource = GetComponentInChildren<BoxCollider>();
SetupGlowTarget();
ApplyEmission(offEmission);
}
void OnEnable()
{
StopTweens();
if (_loopCo != null) StopCoroutine(_loopCo);
_loopCo = StartCoroutine(LidLoop());
}
void OnDisable()
{
StopTweens();
if (_loopCo != null) StopCoroutine(_loopCo);
_loopCo = null;
// Release the gate if this lid was active and got disabled mid-cycle
if (_holdingGate)
{
_holdingGate = false;
Interlocked.Exchange(ref s_gateToken, 0);
s_nextAllowedTime = Time.time + minGlobalSeparation;
}
}
// ====== Main cycle ======
private IEnumerator LidLoop()
{
while (true)
2025-09-01 05:03:00 +05:00
{
// Rest until pre-blink window, with per-lid random jitter so they don't check the gate at the same moment
float rest = Mathf.Max(0f, activeInterval - preBlinkSeconds - activeGlowSeconds);
float jitter = (restJitterRange.y > 0f || restJitterRange.x > 0f)
? Random.Range(restJitterRange.x, restJitterRange.y)
: 0f;
if (rest + jitter > 0f) yield return new WaitForSeconds(rest + Mathf.Max(0f, jitter));
// Acquire global slot (only one lid runs blink+activate at a time)
if (coordinateGlobally)
yield return StartCoroutine(AcquireGlobalGate());
// Pre-blink (warning)
yield return BlinkForSeconds(preBlinkSeconds);
// Activate: launch & steady glow
2025-09-01 05:03:00 +05:00
ActivateLid();
yield return SteadyGlow(activeGlowIntensity, activeGlowSeconds);
// Deactivate: fade to OFF
yield return FadeEmissionTo(offEmission, glowFadeOut);
// Release global slot
if (coordinateGlobally)
{
_holdingGate = false;
Interlocked.Exchange(ref s_gateToken, 0);
s_nextAllowedTime = Time.time + minGlobalSeparation;
}
2025-09-01 05:03:00 +05:00
}
}
private IEnumerator AcquireGlobalGate()
2025-09-01 05:03:00 +05:00
{
// Wait for spacing time first
while (Time.time < s_nextAllowedTime)
yield return null;
// Try to acquire with small random jitter between attempts to randomize which lid wins
while (true)
{
if (Interlocked.CompareExchange(ref s_gateToken, 1, 0) == 0)
{
_holdingGate = true;
yield break;
}
float jitter = (acquireJitterRange.y > 0f || acquireJitterRange.x > 0f)
? Random.Range(acquireJitterRange.x, acquireJitterRange.y)
: 0.05f;
yield return new WaitForSeconds(Mathf.Max(0.01f, jitter));
}
}
// ====== Gameplay ======
private void ActivateLid()
{
// Overlap volume
GetOverlapBoxWorld(out Vector3 center, out Vector3 halfExtents, out Quaternion orientation);
Collider[] cols = Physics.OverlapBox(center, halfExtents, orientation, detectMask, triggerQuery);
// Build camera-relative forward dir from inspector vector
Vector3 worldForward = GetCameraRelativeDirection(camSpaceDir, flattenCamera);
2025-09-01 05:03:00 +05:00
foreach (Collider col in cols)
{
if (!col) continue;
2025-09-01 05:03:00 +05:00
if (col.CompareTag("Zibu") && col.attachedRigidbody != null)
{
// Up + camera-relative forward
Vector3 impulse = (Vector3.up * launchForce) + (worldForward * (launchForce * forwardScale));
col.attachedRigidbody.AddForce(impulse, ForceMode.Impulse);
2025-09-01 05:03:00 +05:00
}
}
}
private static Vector3 GetCameraRelativeDirection(Vector2 camDir, bool flatten)
{
Transform cam = Camera.main ? Camera.main.transform : null;
Vector3 camFwd = cam ? cam.forward : Vector3.forward;
Vector3 camRight = cam ? cam.right : Vector3.right;
if (flatten)
{
camFwd.y = 0f; camRight.y = 0f;
if (camFwd.sqrMagnitude < 1e-6f) camFwd = Vector3.forward;
if (camRight.sqrMagnitude < 1e-6f) camRight = Vector3.right;
camFwd.Normalize(); camRight.Normalize();
}
Vector3 dir = camRight * camDir.x + camFwd * camDir.y;
if (dir.sqrMagnitude > 1f) dir.Normalize();
return dir.sqrMagnitude < 1e-6f ? (flatten ? Vector3.forward : (cam ? cam.forward : Vector3.forward)) : dir;
}
// ====== Blink / Glow visuals ======
private IEnumerator BlinkForSeconds(float seconds)
{
if (_rend == null || seconds <= 0f || blinkPulseDuration <= 0.02f)
yield break;
StopTweens();
ApplyEmission(offEmission);
int blinks = Mathf.Max(1, Mathf.RoundToInt(seconds / blinkPulseDuration));
float half = blinkPulseDuration * 0.5f;
_glowTween = DOTween
.To(() => _emissionIntensity,
v => { _emissionIntensity = v; ApplyEmission(v); },
blinkEmission,
half)
.SetEase(Ease.InOutSine)
.SetLoops(blinks * 2, LoopType.Yoyo)
.SetTarget(this);
yield return _glowTween.WaitForCompletion();
_glowTween = null;
}
private IEnumerator SteadyGlow(float targetIntensity, float holdSeconds)
{
yield return FadeEmissionTo(targetIntensity, glowFadeIn);
if (holdSeconds > 0f) yield return new WaitForSeconds(holdSeconds);
}
private IEnumerator FadeEmissionTo(float target, float duration)
{
StopTweens();
if (duration <= 0f) { ApplyEmission(target); yield break; }
_glowTween = DOTween
.To(() => _emissionIntensity,
v => { _emissionIntensity = v; ApplyEmission(v); },
target,
duration)
.SetEase(target > _emissionIntensity ? Ease.OutSine : Ease.InSine)
.SetTarget(this);
yield return _glowTween.WaitForCompletion();
_glowTween = null;
}
private void StopTweens()
{
if (_glowTween != null && _glowTween.IsActive()) _glowTween.Kill();
_glowTween = null;
}
// ====== Emission plumbing ======
private Renderer RendererOrChild()
{
if (_rend) return _rend;
_rend = GetComponentInChildren<Renderer>();
return _rend;
}
private void SetupGlowTarget()
{
if (RendererOrChild() == null) return;
_submeshIndex = Mathf.Max(0, _rend.sharedMaterials.Length - 1);
if (usePropertyBlock)
{
if (_mpb == null) _mpb = new MaterialPropertyBlock();
_rend.GetPropertyBlock(_mpb, _submeshIndex);
// NOTE: Enable Emission on the material in the Inspector.
}
else
{
var mats = _rend.materials;
if (mats != null && mats.Length > 0)
{
_instancedMat = mats[_submeshIndex];
if (_instancedMat != null) _instancedMat.EnableKeyword("_EMISSION");
}
}
}
private void ApplyEmission(float intensity)
{
_emissionIntensity = Mathf.Max(0f, intensity);
Color emissive = emissionBaseColor * _emissionIntensity;
if (usePropertyBlock)
{
if (_mpb == null || _rend == null) return;
_mpb.SetColor(EmissionColorID, emissive);
_mpb.SetColor(EmissiveColorID, emissive);
_rend.SetPropertyBlock(_mpb, _submeshIndex);
}
else
{
if (_instancedMat == null) return;
_instancedMat.SetColor(EmissionColorID, emissive);
_instancedMat.SetColor(EmissiveColorID, emissive);
}
}
// ====== Overlap box helpers + gizmo ======
private static Vector3 Abs(Vector3 v) => new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z));
private void GetOverlapBoxWorld(out Vector3 center, out Vector3 halfExtents, out Quaternion orientation)
{
if (useColliderForVolume && volumeSource != null)
{
center = volumeSource.transform.TransformPoint(volumeSource.center);
Vector3 lossy = Abs(volumeSource.transform.lossyScale);
halfExtents = Vector3.Scale(volumeSource.size * 0.5f, lossy);
orientation = volumeSource.transform.rotation;
}
else
{
center = transform.TransformPoint(localBoxCenter);
Vector3 lossy = Abs(transform.lossyScale);
halfExtents = Vector3.Scale(localBoxSize * 0.5f, lossy);
orientation = transform.rotation;
}
}
private void OnDrawGizmosSelected()
{
if (!drawGizmos) return;
GetOverlapBoxWorld(out Vector3 center, out Vector3 halfExtents, out Quaternion rotation);
Matrix4x4 m = Matrix4x4.TRS(center, rotation, Vector3.one);
Gizmos.matrix = m;
Gizmos.color = gizmoFill;
Gizmos.DrawCube(Vector3.zero, halfExtents * 2f);
Gizmos.color = gizmoWire;
Gizmos.DrawWireCube(Vector3.zero, halfExtents * 2f);
}
private void OnValidate()
{
activeInterval = Mathf.Max(0.1f, activeInterval);
preBlinkSeconds = Mathf.Max(0f, preBlinkSeconds);
activeGlowSeconds = Mathf.Max(0f, activeGlowSeconds);
blinkPulseDuration = Mathf.Max(0.05f, blinkPulseDuration);
glowFadeIn = Mathf.Max(0f, glowFadeIn);
glowFadeOut = Mathf.Max(0f, glowFadeOut);
offEmission = Mathf.Max(0f, offEmission);
blinkEmission = Mathf.Max(offEmission, blinkEmission);
activeGlowIntensity = Mathf.Max(offEmission, activeGlowIntensity);
if (!volumeSource) volumeSource = GetComponent<BoxCollider>();
if (!volumeSource) volumeSource = GetComponentInChildren<BoxCollider>();
}
2025-09-01 05:03:00 +05:00
}