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(); 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(); } 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(); 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(); // 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(); // } // if (candidate == null) // { // if (sfx && sfxEmpty) sfx.PlayOneShot(sfxEmpty); // return; // } // // Grab: parent + freeze physics // heldObject = candidate.gameObject; // heldRb = heldObject.GetComponent(); // 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 // ========= /// Bomb: disable all control for 'seconds'. private IEnumerator _Disable(float s) { bool prev = canControl; canControl = false; yield return new WaitForSeconds(s); canControl = prev; } /// Shock: jam one rotation direction for 'seconds'. 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); } } }