MiniGames/Assets/Scripts/CubeClash/CubeClash_CubeLid.cs

395 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using DG.Tweening;
using UnityEngine;
using System.Collections;
using System.Threading; // for Interlocked
[RequireComponent(typeof(Renderer))]
public class CubeClash_CubeLid : MonoBehaviour
{
[Header("Launch")]
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.")]
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;
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()
{
_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)
{
// 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
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;
}
}
}
private IEnumerator AcquireGlobalGate()
{
// 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 ======
[Header("Spring Animation")]
[Tooltip("How high the cube jumps visually when launching a player.")]
public float springHeight = 0.5f;
[Tooltip("How long the up and down tween takes (seconds).")]
public float springDuration = 0.2f;
[ContextMenu("Activate Lid")]
private void ActivateLid()
{
GetOverlapBoxWorld(out Vector3 center, out Vector3 halfExtents, out Quaternion orientation);
Collider[] cols = Physics.OverlapBox(center, halfExtents, orientation, detectMask, triggerQuery);
Vector3 worldForward = GetCameraRelativeDirection(camSpaceDir, flattenCamera);
foreach (Collider col in cols)
{
if (!col) continue;
if (col.CompareTag("Zibu") && col.attachedRigidbody != null)
{
Vector3 impulse = (Vector3.up * launchForce) + (worldForward * (launchForce * forwardScale));
col.attachedRigidbody.AddForce(impulse, ForceMode.Impulse);
}
}
// --- NEW spring animation ---
DoSpringAnimation();
}
private void DoSpringAnimation()
{
// kill any existing spring
DOTween.Kill("CubeSpring_" + GetInstanceID());
Vector3 startPos = transform.position;
Vector3 upPos = startPos + Vector3.up * springHeight;
Sequence seq = DOTween.Sequence();
seq.Append(transform.DOMoveY(upPos.y, springDuration).SetEase(Ease.OutQuad));
seq.Append(transform.DOMoveY(startPos.y, springDuration).SetEase(Ease.InQuad));
seq.SetId("CubeSpring_" + GetInstanceID());
}
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>();
}
}