MiniGames/Assets/Scripts/ClawGrab/ClawController.cs
2025-09-06 23:30:06 +05:00

482 lines
14 KiB
C#

using UnityEngine;
using System.Collections;
using DG.Tweening;
#if UNITY_INPUT_SYSTEM
using UnityEngine.InputSystem;
#endif
public class ClawController : MonoBehaviour
{
[Header("Hierarchy (Crane)")]
public Transform craneA; // base rotates around Y
public Transform craneB;
public Transform craneC;
public Transform craneD;
public Transform craneE; // telescopes by scaling Z
public Transform clawPivot; // tip / grab point
public Transform grabPoint; // tip / grab point
[Header("Rotation (CraneA)")]
public float rotateSpeed = 90f; // deg/sec
public float minYaw = -75f; // clamp local Y (signed)
public float maxYaw = 75f; // clamp local Y (signed)
public bool invertX = false; // flip stick if needed
public bool jamBlocksClockwise = true; // Shock blocks +X when true
[Header("Vertical Adjust (localPosition.y)")]
public bool enableVerticalAdjust = true;
public Transform verticalTarget; // defaults to grabPoint in Awake()
public float verticalSpeed = 0.004f; // units/second (range is 0.002, so this moves end-to-end in ~0.5s)
public float minLocalY = -0.004f; // lower limit
public float maxLocalY = -0.002f; // upper limit
public bool invertY = false;
[Header("Drop / Grab")]
[Tooltip("Seconds the claw takes to descend.")]
public float descendDuration = 1.5f;
[Tooltip("Cooldown between button presses.")]
public float pressCooldown = 0.35f;
[Tooltip("Claw down scale (Z) for craneE; larger = extend more (go down).")]
public float downZ = 2.5f;
[Tooltip("Claw up/resting scale (Z) for craneE.")]
public float upZ = 1.0f;
[Tooltip("Detection around clawPivot at the bottom position.")]
public float grabRadius = 0.25f;
public float grabRayLength = 1.0f;
public LayerMask bubbleMask = ~0;
[Header("Lift Visuals")]
public float snapToPivotDuration = 0.25f;
public Ease snapEase = Ease.OutQuad;
public float raiseDuration = 0.5f;
[Header("SFX (optional)")]
public AudioSource sfx;
public AudioClip sfxDropStart;
public AudioClip sfxGrab;
public AudioClip sfxDropRelease;
public AudioClip sfxEmpty;
// ---- State ----
private bool canControl = true;
private bool jammed = false; // Shock effect flag
private bool busy = false; // block re-entrancy during tween/coroutine
private float lastPressTime = -999f;
private GameObject heldObject;
private Rigidbody heldRb;
public Joystick joystick;
private int _disableLocks = 0;
private int _jamLocks = 0;
void Awake()
{
if (descendDuration < 1.5f) descendDuration = 1.5f; // per spec
if (craneE != null) craneE.localScale = new Vector3(craneE.localScale.x, craneE.localScale.y, upZ);
if (verticalTarget == null) verticalTarget = grabPoint; // default
}
void Update()
{
HandleRotate();
HandleVerticalAdjust();
#if !UNITY_INPUT_SYSTEM
// (Optional) Space/gamepad as a shortcut for testing alongside UI button:
if (Input.GetKeyDown(KeyCode.Space)) OnDropButtonClicked();
#endif
// Debug ray
if (clawPivot)
Debug.DrawRay(clawPivot.position, Vector3.down * grabRayLength, heldObject ? Color.cyan : Color.yellow);
}
// =======================
// ROTATION-BASED MOVEMENT
// =======================
private void HandleRotate()
{
if (!canControl || craneA == null) return;
float h = 0f;
#if UNITY_INPUT_SYSTEM
if (Gamepad.current != null)
h = Gamepad.current.leftStick.x.ReadValue();
if (Keyboard.current != null)
{
if (Keyboard.current.leftArrowKey.isPressed || Keyboard.current.aKey.isPressed) h -= 1f;
if (Keyboard.current.rightArrowKey.isPressed || Keyboard.current.dKey.isPressed) h += 1f;
}
#else
//h = Input.GetAxisRaw("Horizontal");
if (Input.GetAxisRaw("Horizontal") > 0 || Input.GetAxisRaw("Horizontal") < 0) h = Input.GetAxisRaw("Horizontal");
else h = joystick.Horizontal;
#endif
if (invertX) h = -h;
// Shock jam: block one rotation direction
if (jammed)
{
if (jamBlocksClockwise && h > 0f) h = 0f;
if (!jamBlocksClockwise && h < 0f) h = 0f;
}
if (Mathf.Approximately(h, 0f)) return;
// Current signed local Y (-180..180)
Vector3 e = craneA.localEulerAngles;
float currYaw = Mathf.DeltaAngle(0f, e.y);
// Apply delta and clamp
float yawDelta = h * rotateSpeed * Time.deltaTime;
float newYaw = Mathf.Clamp(currYaw + yawDelta, minYaw, maxYaw);
// Write back as 0..360
e.y = Mathf.Repeat(newYaw + 360f, 360f);
craneA.localEulerAngles = e;
}
private void HandleVerticalAdjust()
{
if (!enableVerticalAdjust || verticalTarget == null) return;
float v = 0f;
#if UNITY_INPUT_SYSTEM
// Gamepad + keyboard (W/S or Up/Down)
if (Gamepad.current != null)
v = Gamepad.current.leftStick.y.ReadValue();
if (Keyboard.current != null)
{
if (Keyboard.current.upArrowKey.isPressed || Keyboard.current.wKey.isPressed) v += 1f;
if (Keyboard.current.downArrowKey.isPressed || Keyboard.current.sKey.isPressed) v -= 1f;
}
#else
// Legacy Input: prefer axis if present, else use on-screen joystick
float axisV = Input.GetAxisRaw("Vertical");
if (Mathf.Abs(axisV) > 0.0001f) v = axisV;
else if (joystick != null) v = joystick.Vertical;
#endif
if (invertY) v = -v;
if (Mathf.Abs(v) < 0.0001f) return;
// incremental move, clamped to [-0.004, -0.002]
Vector3 lp = verticalTarget.localPosition;
lp.y = Mathf.Clamp(lp.y + v * verticalSpeed * Time.deltaTime, minLocalY, maxLocalY);
verticalTarget.localPosition = lp;
}
// ==========================
// UI BUTTON CLICK ENTRYPOINT
// ==========================
public void OnDropButtonClicked()
{
if (!canControl || busy) return;
if (Time.time - lastPressTime < pressCooldown) return;
lastPressTime = Time.time;
// NEW: tell the mode manager a drop attempt is happening now
if (heldObject == null)
{
ClawGameModeManager.Instance?.NotifyDropAttempt();
StartCoroutine(Press_GrabFlow());
}
else
StartCoroutine(Press_DropFlow());
}
//public void OnDropButtonClicked()
//{
// if (!canControl || busy) return;
// if (Time.time - lastPressTime < pressCooldown) return;
// lastPressTime = Time.time;
// if (heldObject == null)
// StartCoroutine(Press_GrabFlow());
// else
// StartCoroutine(Press_DropFlow());
//}
// =================================
// PRESS → GRAB FLOW (no object held)
// =================================
private IEnumerator Press_GrabFlow()
{
busy = true;
// Descend
// if (sfx && sfxDropStart) sfx.PlayOneShot(sfxDropStart);
yield return TweenCraneZ(downZ, descendDuration);
// Try grab at bottom
TryGrabAtBottom();
// Small settle if grabbed for nicer snap
if (heldObject != null)
yield return new WaitForSeconds(0.05f);
// Ascend
yield return TweenCraneZ(upZ, raiseDuration);
busy = false;
}
// =================================
// PRESS → DROP FLOW (object is held)
// =================================
private IEnumerator Press_DropFlow()
{
busy = true;
// Descend
if (sfx && sfxDropStart) sfx.PlayOneShot(sfxDropStart);
yield return TweenCraneZ(downZ / 2, descendDuration / 2);
// Drop in place (release physics)
ReleaseHeldAtBottom();
// Ascend
yield return TweenCraneZ(upZ, raiseDuration);
busy = false;
}
// -------------
// Tween helpers
// -------------
private IEnumerator TweenCraneZ(float targetZ, float duration)
{
if (craneE == null)
{
Debug.LogError("[ClawController] craneE is not assigned.");
yield break;
}
if (duration <= 0f)
{
// Set instantly if duration is 0
var s = craneE.localScale;
s.z = targetZ;
craneE.localScale = s;
yield break;
}
// Kill any existing scale tween on craneE so it doesn't conflict
DOTween.Kill(craneE, complete: false);
// Do the tween on Z only
Tween t = craneE.DOScaleZ(targetZ, duration)
.SetEase(Ease.Linear)
.SetTarget(craneE); // so Kill(craneE) finds it next time
yield return t.WaitForCompletion();
}
// -------------
// Grab / Drop
// -------------
private void TryGrabAtBottom()
{
if (heldObject != null) return;
ClawBubble candidate = null;
float bestSqr = float.MaxValue;
var hits = Physics.OverlapSphere(clawPivot.position, grabRadius, bubbleMask, QueryTriggerInteraction.Ignore);
foreach (var c in hits)
{
var cb = c.GetComponentInParent<ClawBubble>();
if (!cb) continue;
float d = (cb.transform.position - clawPivot.position).sqrMagnitude;
if (d < bestSqr) { bestSqr = d; candidate = cb; }
}
if (candidate == null)
{
if (Physics.Raycast(clawPivot.position, Vector3.down, out var hit, grabRayLength, bubbleMask, QueryTriggerInteraction.Ignore))
candidate = hit.collider.GetComponentInParent<ClawBubble>();
}
if (candidate == null)
{
// NEW: precision miss should penalize
ClawGameModeManager.Instance?.NotifyMiss();
if (sfx && sfxEmpty) sfx.PlayOneShot(sfxEmpty);
return;
}
// NEW: notify what we grabbed (mode may use this info)
ClawGameModeManager.Instance?.NotifyGrabbed(candidate.type);
// (existing grab logic)
heldObject = candidate.gameObject;
heldRb = heldObject.GetComponent<Rigidbody>();
if (heldRb)
{
heldRb.velocity = Vector3.zero;
heldRb.angularVelocity = Vector3.zero;
heldRb.isKinematic = true;
}
var t = heldObject.transform;
t.SetParent(grabPoint, worldPositionStays: true);
t.DOLocalMove(Vector3.zero, snapToPivotDuration).SetEase(snapEase);
if (sfx && sfxGrab) sfx.PlayOneShot(sfxGrab);
}
//private void TryGrabAtBottom()
//{
// if (heldObject != null) return;
// ClawBubble candidate = null;
// float bestSqr = float.MaxValue;
// // Sphere overlap
// var hits = Physics.OverlapSphere(clawPivot.position, grabRadius, bubbleMask, QueryTriggerInteraction.Ignore);
// foreach (var c in hits)
// {
// var cb = c.GetComponentInParent<ClawBubble>();
// if (!cb) continue;
// float d = (cb.transform.position - clawPivot.position).sqrMagnitude;
// if (d < bestSqr)
// {
// bestSqr = d;
// candidate = cb;
// }
// }
// // Fallback ray
// if (candidate == null)
// {
// if (Physics.Raycast(clawPivot.position, Vector3.down, out var hit, grabRayLength, bubbleMask, QueryTriggerInteraction.Ignore))
// candidate = hit.collider.GetComponentInParent<ClawBubble>();
// }
// if (candidate == null)
// {
// if (sfx && sfxEmpty) sfx.PlayOneShot(sfxEmpty);
// return;
// }
// // Grab: parent + freeze physics
// heldObject = candidate.gameObject;
// heldRb = heldObject.GetComponent<Rigidbody>();
// if (heldRb)
// {
// heldRb.velocity = Vector3.zero;
// heldRb.angularVelocity = Vector3.zero;
// heldRb.isKinematic = true;
// }
// var t = heldObject.transform;
// t.SetParent(grabPoint, worldPositionStays: true);
// t.DOLocalMove(Vector3.zero, snapToPivotDuration).SetEase(snapEase);
// if (sfx && sfxGrab) sfx.PlayOneShot(sfxGrab);
//}
private void ReleaseHeldAtBottom()
{
if (heldObject == null) return;
// Unparent
heldObject.transform.SetParent(null, true);
// Restore physics so it falls freely
if (heldRb)
{
heldRb.isKinematic = false;
heldRb.useGravity = true;
heldRb.constraints = RigidbodyConstraints.None; // no rotation/position restrictions
heldRb = null;
}
if (sfx && sfxDropRelease) sfx.PlayOneShot(sfxDropRelease);
heldObject = null;
}
public void ApplyDisable(float seconds)
{
if (!gameObject.activeInHierarchy) return;
StartCoroutine(DisableLock(seconds));
}
private IEnumerator DisableLock(float s)
{
_disableLocks++;
canControl = false;
if (s > 0f) yield return new WaitForSeconds(s);
_disableLocks = Mathf.Max(0, _disableLocks - 1);
if (_disableLocks == 0) canControl = true;
}
public void ApplyJam(float seconds)
{
if (!gameObject.activeInHierarchy) return;
StartCoroutine(JamLock(seconds));
}
private IEnumerator JamLock(float s)
{
_jamLocks++;
jammed = true;
if (s > 0f) yield return new WaitForSeconds(s);
_jamLocks = Mathf.Max(0, _jamLocks - 1);
if (_jamLocks == 0) jammed = false;
}
// === new hard overrides used by the mode manager ===
public void ForceEnableControl()
{
_disableLocks = 0;
canControl = true;
}
public void ForceClearJam()
{
_jamLocks = 0;
jammed = false;
}
// =========
// FX Hooks
// =========
/// <summary>Bomb: disable all control for 'seconds'.</summary>
private IEnumerator _Disable(float s)
{
bool prev = canControl;
canControl = false;
yield return new WaitForSeconds(s);
canControl = prev;
}
/// <summary>Shock: jam one rotation direction for 'seconds'.</summary>
private IEnumerator _Jam(float s)
{
jammed = true;
yield return new WaitForSeconds(s);
jammed = false;
}
// =====
// Gizmo
// =====
void OnDrawGizmosSelected()
{
if (clawPivot)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(clawPivot.position, grabRadius);
}
}
}