915 lines
29 KiB
C#
915 lines
29 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;
|
|||
|
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;
|
|||
|
|
|||
|
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;
|
|||
|
|
|||
|
// 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;
|
|||
|
}
|
|||
|
|
|||
|
// =========
|
|||
|
// FX Hooks
|
|||
|
// =========
|
|||
|
/// <summary>Bomb: disable all control for 'seconds'.</summary>
|
|||
|
public void ApplyDisable(float seconds)
|
|||
|
{
|
|||
|
if (!gameObject.activeInHierarchy) return;
|
|||
|
StartCoroutine(_Disable(seconds));
|
|||
|
}
|
|||
|
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>
|
|||
|
public void ApplyJam(float seconds)
|
|||
|
{
|
|||
|
if (!gameObject.activeInHierarchy) return;
|
|||
|
StartCoroutine(_Jam(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);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
//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 that ROTATES around Y with joystick
|
|||
|
// public Transform craneB;
|
|||
|
// public Transform craneC;
|
|||
|
// public Transform craneD;
|
|||
|
// public Transform craneE; // telescopes by scaling Z
|
|||
|
// public Transform clawPivot; // 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: block +X when true
|
|||
|
|
|||
|
// [Header("Drop / Grab")]
|
|||
|
// [Tooltip("Seconds the claw takes to descend (doc: 1.5s).")]
|
|||
|
// public float descendDuration = 1.5f;
|
|||
|
// [Tooltip("Cooldown between drops.")]
|
|||
|
// public float dropCooldown = 2.0f;
|
|||
|
// [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("Delivery")]
|
|||
|
// [Tooltip("Optional: where to toss/deliver. If null, deliver in place.")]
|
|||
|
// public Transform rewardsChute;
|
|||
|
// public float liftDuration = 0.5f;
|
|||
|
// public float deliverMoveDuration = 0.4f;
|
|||
|
// public float deliverArcHeight = 0.6f;
|
|||
|
|
|||
|
// [Header("SFX (optional)")]
|
|||
|
// public AudioSource sfx;
|
|||
|
// public AudioClip sfxDropStart;
|
|||
|
// public AudioClip sfxGrab;
|
|||
|
// public AudioClip sfxEmpty;
|
|||
|
|
|||
|
// // ---- State ----
|
|||
|
// private bool canControl = true;
|
|||
|
// private bool jammed = false; // Shock effect flag
|
|||
|
// private float lastDropTime = -999f;
|
|||
|
|
|||
|
// private GameObject heldObject;
|
|||
|
// private Rigidbody heldRb;
|
|||
|
|
|||
|
// private Vector3 craneEUpScale;
|
|||
|
// public Joystick joystick;
|
|||
|
|
|||
|
// void Awake()
|
|||
|
// {
|
|||
|
// if (craneE != null) craneEUpScale = craneE.localScale;
|
|||
|
// if (descendDuration < 1.5f) descendDuration = 1.5f; // enforce doc spec minimum
|
|||
|
// }
|
|||
|
|
|||
|
// void Update()
|
|||
|
// {
|
|||
|
// if (clawPivot)
|
|||
|
// Debug.DrawRay(clawPivot.position, Vector3.down * grabRayLength, heldObject ? Color.cyan : Color.yellow);
|
|||
|
|
|||
|
// HandleMove();
|
|||
|
// HandleDropInput();
|
|||
|
// }
|
|||
|
|
|||
|
// // =======================
|
|||
|
// // ROTATION-BASED MOVEMENT
|
|||
|
// // =======================
|
|||
|
// private void HandleMove()
|
|||
|
// {
|
|||
|
// 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
|
|||
|
// 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;
|
|||
|
|
|||
|
// // Get 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;
|
|||
|
// }
|
|||
|
|
|||
|
// // ==============
|
|||
|
// // DROP-TO-GRAB IO
|
|||
|
// // ==============
|
|||
|
// private void HandleDropInput()
|
|||
|
// {
|
|||
|
// if (!canControl) return;
|
|||
|
|
|||
|
// bool pressed = false;
|
|||
|
//#if UNITY_INPUT_SYSTEM
|
|||
|
// if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame) pressed = true;
|
|||
|
// if (Gamepad.current != null && Gamepad.current.buttonSouth.wasPressedThisFrame) pressed = true;
|
|||
|
//#else
|
|||
|
// if (Input.GetKeyDown(KeyCode.Space)) pressed = true;
|
|||
|
//#endif
|
|||
|
// if (!pressed) return;
|
|||
|
|
|||
|
// if (Time.time - lastDropTime < dropCooldown) return;
|
|||
|
|
|||
|
// lastDropTime = Time.time;
|
|||
|
// StartCoroutine(DropToGrab()); // << keep the original coroutine name
|
|||
|
// }
|
|||
|
|
|||
|
// /// <summary>
|
|||
|
// /// Descend (DOTween scale Z), attempt grab at bottom, rise, deliver.
|
|||
|
// /// Matches your previous DropToGrab-style flow.
|
|||
|
// /// </summary>
|
|||
|
// private IEnumerator DropToGrab()
|
|||
|
// {
|
|||
|
// // ---- Go Down ----
|
|||
|
// if (sfx && sfxDropStart) sfx.PlayOneShot(sfxDropStart);
|
|||
|
|
|||
|
// Tween downTween = null;
|
|||
|
// if (craneE)
|
|||
|
// {
|
|||
|
// Vector3 to = craneE.localScale; to.z = downZ;
|
|||
|
// downTween = craneE.DOScale(to, descendDuration).SetEase(Ease.Linear);
|
|||
|
// }
|
|||
|
// if (downTween != null) yield return downTween.WaitForCompletion();
|
|||
|
// else yield return new WaitForSeconds(descendDuration);
|
|||
|
|
|||
|
// // ---- Try Grab at Bottom ----
|
|||
|
// TryGrabAtBottom();
|
|||
|
|
|||
|
// // tiny settle delay for visuals if we grabbed something
|
|||
|
// if (heldObject != null)
|
|||
|
// yield return new WaitForSeconds(0.05f);
|
|||
|
|
|||
|
// // ---- Rise Up ----
|
|||
|
// Tween upTween = null;
|
|||
|
// if (craneE)
|
|||
|
// {
|
|||
|
// Vector3 to = craneE.localScale; to.z = upZ;
|
|||
|
// upTween = craneE.DOScale(to, 0.5f).SetEase(Ease.Linear);
|
|||
|
// }
|
|||
|
// if (upTween != null) yield return upTween.WaitForCompletion();
|
|||
|
// else yield return new WaitForSeconds(0.5f);
|
|||
|
|
|||
|
// // ---- Deliver / Score ----
|
|||
|
// if (heldObject != null)
|
|||
|
// yield return DeliverHeld();
|
|||
|
// }
|
|||
|
|
|||
|
// private void TryGrabAtBottom()
|
|||
|
// {
|
|||
|
// if (heldObject != null) return;
|
|||
|
|
|||
|
// ClawBubble candidate = null;
|
|||
|
// float bestSqr = float.MaxValue;
|
|||
|
|
|||
|
// // Sphere overlap first (tolerant)
|
|||
|
// 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: straight ray down
|
|||
|
// 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 to pivot, 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(clawPivot, worldPositionStays: true);
|
|||
|
// t.DOLocalMove(Vector3.zero, liftDuration).SetEase(Ease.OutQuad);
|
|||
|
|
|||
|
// if (sfx && sfxGrab) sfx.PlayOneShot(sfxGrab);
|
|||
|
// }
|
|||
|
|
|||
|
// private IEnumerator DeliverHeld()
|
|||
|
// {
|
|||
|
// var cb = heldObject.GetComponent<ClawBubble>();
|
|||
|
// var type = cb ? cb.type : ClawBubbleType.Wolf;
|
|||
|
|
|||
|
// // Optional arc to chute
|
|||
|
// if (rewardsChute != null)
|
|||
|
// {
|
|||
|
// Vector3 start = heldObject.transform.position;
|
|||
|
// Vector3 end = rewardsChute.position;
|
|||
|
// Vector3 peak = (start + end) * 0.5f + Vector3.up * deliverArcHeight;
|
|||
|
|
|||
|
// var seq = DOTween.Sequence();
|
|||
|
// seq.Append(heldObject.transform.DOMove(peak, deliverMoveDuration * 0.5f).SetEase(Ease.OutQuad));
|
|||
|
// seq.Append(heldObject.transform.DOMove(end, deliverMoveDuration * 0.5f).SetEase(Ease.InQuad));
|
|||
|
// yield return seq.WaitForCompletion();
|
|||
|
// }
|
|||
|
|
|||
|
// // Score & effects
|
|||
|
// if (ClawScoreManager.Instance != null)
|
|||
|
// ClawScoreManager.Instance.EvaluateBubble(type);
|
|||
|
|
|||
|
// // Safety notify to spawner (in case you prefer it here)
|
|||
|
// if (ClawBubbleSpawner.Instance != null)
|
|||
|
// ClawBubbleSpawner.Instance.NotifyBubbleGrabbed(type);
|
|||
|
|
|||
|
// // Cleanup grabbed object
|
|||
|
// if (heldObject != null)
|
|||
|
// {
|
|||
|
// heldObject.transform.SetParent(null, true);
|
|||
|
// Destroy(heldObject);
|
|||
|
// }
|
|||
|
// heldObject = null;
|
|||
|
// heldRb = null;
|
|||
|
// }
|
|||
|
|
|||
|
// // =========
|
|||
|
// // FX Hooks
|
|||
|
// // =========
|
|||
|
// /// <summary>Bomb: disable all control for 'seconds'.</summary>
|
|||
|
// public void ApplyDisable(float seconds)
|
|||
|
// {
|
|||
|
// if (!gameObject.activeInHierarchy) return;
|
|||
|
// StartCoroutine(_Disable(seconds));
|
|||
|
// }
|
|||
|
// 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>
|
|||
|
// public void ApplyJam(float seconds)
|
|||
|
// {
|
|||
|
// if (!gameObject.activeInHierarchy) return;
|
|||
|
// StartCoroutine(_Jam(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);
|
|||
|
// }
|
|||
|
// }
|
|||
|
//}
|
|||
|
|
|||
|
//using UnityEngine;
|
|||
|
//using System.Collections;
|
|||
|
//using DG.Tweening;
|
|||
|
|
|||
|
//#if UNITY_INPUT_SYSTEM
|
|||
|
//using UnityEngine.InputSystem;
|
|||
|
//#endif
|
|||
|
|
|||
|
//public class ClawController : MonoBehaviour
|
|||
|
//{
|
|||
|
// [Header("Hierarchy")]
|
|||
|
// public Transform craneA; // Crane_A (rotates as base)
|
|||
|
// public Transform craneB;
|
|||
|
// public Transform craneC;
|
|||
|
// public Transform craneD;
|
|||
|
// public Transform craneE; // Scale Z to drop
|
|||
|
// public Transform clawPivot; // Ray origin (tip/pivot of claw)
|
|||
|
// public Transform grabPoint; // Where held ball sits (keep scale ~1,1,1 ideally)
|
|||
|
|
|||
|
// [Header("Movement/Input")]
|
|||
|
// public float rotateSpeed = 90f;
|
|||
|
// public string horizontalAxis = "Horizontal"; // legacy axis (keyboard/gamepad)
|
|||
|
// public string dropButtonName = "Fire1"; // legacy button
|
|||
|
// public KeyCode dropKey = KeyCode.Space; // keyboard fallback
|
|||
|
// public Joystick virtualJoystick; // optional on-screen joystick
|
|||
|
|
|||
|
// [Header("Extend/Retract")]
|
|||
|
// public float descendTime = 0.6f;
|
|||
|
// public float dropZ = 50f; // craneE localScale.z when extended
|
|||
|
// public float upZ = 40f; // craneE localScale.z when idle
|
|||
|
// public float dropCooldown = 0.5f;
|
|||
|
|
|||
|
// [Header("Raycast & Layers")]
|
|||
|
// public float grabRayLength = 2f;
|
|||
|
// public LayerMask ballLayerMask; // set in Inspector (Ball layer)
|
|||
|
// [Tooltip("Layer index to assign while the ball is held to avoid re-grab; set -1 to leave unchanged.")]
|
|||
|
// public int grabbedBallLayer = -1;
|
|||
|
// [Tooltip("Layer index to assign on release; set -1 to leave unchanged.")]
|
|||
|
// public int releasedBallLayer = -1;
|
|||
|
|
|||
|
// private bool canDrop = true;
|
|||
|
// private GameObject heldObject;
|
|||
|
// [Header("Drop Tuning")]
|
|||
|
// public float descendDuration = 1.5f; // doc requirement
|
|||
|
|
|||
|
// private bool canControl = true;
|
|||
|
// private bool jammed = false;
|
|||
|
// private float lastDropTime = -999f;
|
|||
|
|
|||
|
|
|||
|
// void Reset()
|
|||
|
// {
|
|||
|
// if (craneE)
|
|||
|
// {
|
|||
|
// var s = craneE.localScale;
|
|||
|
// craneE.localScale = new Vector3(s.x, s.y, upZ);
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// void Update()
|
|||
|
// {
|
|||
|
// // Visualize the ray (Scene view)
|
|||
|
// Debug.DrawRay(clawPivot.position, Vector3.down * grabRayLength, heldObject ? Color.cyan : Color.yellow);
|
|||
|
|
|||
|
// // Rotation input (use Vertical from joystick per your previous setup; change to Horizontal if you prefer)
|
|||
|
// float x = 0f;
|
|||
|
// if (virtualJoystick != null) x = virtualJoystick.Vertical;
|
|||
|
// else x = Input.GetAxis(horizontalAxis);
|
|||
|
|
|||
|
// // Rotate around Crane_A's local forward axis (your setup)
|
|||
|
// craneA.Rotate(craneA.forward, x * rotateSpeed * Time.deltaTime, Space.World);
|
|||
|
|
|||
|
// // Drop/Action input
|
|||
|
// //bool dropPressed = Input.GetButtonDown(dropButtonName) || Input.GetKeyDown(dropKey);
|
|||
|
//#if UNITY_INPUT_SYSTEM
|
|||
|
// if (!dropPressed && Gamepad.current != null)
|
|||
|
// dropPressed = Gamepad.current.buttonSouth.wasPressedThisFrame; // A/Cross
|
|||
|
//#endif
|
|||
|
|
|||
|
// //if (dropPressed && canDrop)
|
|||
|
// //{
|
|||
|
// // if (heldObject != null)
|
|||
|
// // {
|
|||
|
// // // Drop wherever it is
|
|||
|
// // StartCoroutine(ReleaseHeldHere());
|
|||
|
// // }
|
|||
|
// // else
|
|||
|
// // {
|
|||
|
// // // Try to grab a ball under the claw
|
|||
|
// // if (Physics.Raycast(clawPivot.position, Vector3.down, out RaycastHit ballHit, grabRayLength, ballLayerMask))
|
|||
|
// // {
|
|||
|
// // StartCoroutine(DropToGrab(ballHit.collider.gameObject));
|
|||
|
// // }
|
|||
|
// // // else: nothing under → do nothing (or add a small poke if you want)
|
|||
|
// // }
|
|||
|
// //}
|
|||
|
// }
|
|||
|
|
|||
|
// // UI Button hook for mobile
|
|||
|
// public void TriggerDrop()
|
|||
|
// {
|
|||
|
// if (!canDrop) return;
|
|||
|
|
|||
|
// if (heldObject != null)
|
|||
|
// {
|
|||
|
// StartCoroutine(ReleaseHeldHere());
|
|||
|
// }
|
|||
|
// else if (Physics.Raycast(clawPivot.position, Vector3.down, out RaycastHit ballHit, grabRayLength, ballLayerMask))
|
|||
|
// {
|
|||
|
// StartCoroutine(DropToGrab(ballHit.collider.gameObject));
|
|||
|
// }
|
|||
|
// else
|
|||
|
// {
|
|||
|
// StartCoroutine(DropToGrab());
|
|||
|
// }
|
|||
|
// }
|
|||
|
|
|||
|
// IEnumerator DropToGrab(GameObject targetBall=null)
|
|||
|
// {
|
|||
|
// canDrop = false;
|
|||
|
|
|||
|
// // Extend
|
|||
|
// var s = craneE.localScale;
|
|||
|
// var start = new Vector3(s.x, s.y, upZ);
|
|||
|
// var end = new Vector3(s.x, s.y, dropZ);
|
|||
|
// craneE.localScale = start;
|
|||
|
// craneE.DOScale(end, descendTime);
|
|||
|
// yield return new WaitForSeconds(descendTime);
|
|||
|
|
|||
|
// if (targetBall != null)
|
|||
|
// {
|
|||
|
// ClawScoreManager.Instance.EvaluateBubble(targetBall.GetComponent<ClawBubble>().type);
|
|||
|
// // Disable physics while held
|
|||
|
// var rb = targetBall.GetComponent<Rigidbody>();
|
|||
|
// if (rb != null)
|
|||
|
// {
|
|||
|
// rb.isKinematic = true;
|
|||
|
// rb.constraints = RigidbodyConstraints.None;
|
|||
|
// rb.velocity = Vector3.zero;
|
|||
|
// rb.angularVelocity = Vector3.zero;
|
|||
|
// }
|
|||
|
|
|||
|
// // Parent to grabPoint without visual scale change
|
|||
|
// PreserveWorldScaleParent(targetBall.transform, grabPoint);
|
|||
|
|
|||
|
// targetBall.transform.localPosition = Vector3.zero;
|
|||
|
// targetBall.transform.localRotation = Quaternion.identity;
|
|||
|
|
|||
|
// // Move to 'grabbed' layer if requested
|
|||
|
// if (grabbedBallLayer >= 0)
|
|||
|
// SetLayerRecursively(targetBall, grabbedBallLayer);
|
|||
|
|
|||
|
// heldObject = targetBall;
|
|||
|
// }
|
|||
|
|
|||
|
// // Retract
|
|||
|
// craneE.DOScale(start, descendTime);
|
|||
|
// yield return new WaitForSeconds(descendTime);
|
|||
|
|
|||
|
// yield return new WaitForSeconds(dropCooldown);
|
|||
|
// canDrop = true;
|
|||
|
// }
|
|||
|
|
|||
|
// IEnumerator ReleaseHeldHere()
|
|||
|
// {
|
|||
|
// canDrop = false;
|
|||
|
|
|||
|
// if (heldObject != null)
|
|||
|
// {
|
|||
|
// // Unparent at current position
|
|||
|
// heldObject.transform.SetParent(null, true);
|
|||
|
|
|||
|
// // Re-enable full physics
|
|||
|
// var rb = heldObject.GetComponent<Rigidbody>();
|
|||
|
// if (rb != null)
|
|||
|
// {
|
|||
|
// rb.isKinematic = false;
|
|||
|
// rb.constraints = RigidbodyConstraints.None; // no restrictions
|
|||
|
// rb.velocity = Vector3.zero;
|
|||
|
// rb.angularVelocity = Vector3.zero;
|
|||
|
// }
|
|||
|
|
|||
|
// // Optionally restore/change layer on release
|
|||
|
// if (releasedBallLayer >= 0)
|
|||
|
// SetLayerRecursively(heldObject, releasedBallLayer);
|
|||
|
|
|||
|
// heldObject = null;
|
|||
|
// }
|
|||
|
|
|||
|
// yield return new WaitForSeconds(dropCooldown);
|
|||
|
// canDrop = true;
|
|||
|
// }
|
|||
|
|
|||
|
// // --- Utilities ---
|
|||
|
|
|||
|
// // Keep visual scale identical after parenting into a scaled hierarchy
|
|||
|
// private void PreserveWorldScaleParent(Transform child, Transform newParent)
|
|||
|
// {
|
|||
|
// Vector3 worldScale = child.lossyScale;
|
|||
|
// child.SetParent(newParent, true); // keep world pos/rot
|
|||
|
// Vector3 parentScale = newParent.lossyScale;
|
|||
|
// child.localScale = new Vector3(
|
|||
|
// SafeDiv(worldScale.x, parentScale.x),
|
|||
|
// SafeDiv(worldScale.y, parentScale.y),
|
|||
|
// SafeDiv(worldScale.z, parentScale.z)
|
|||
|
// );
|
|||
|
// }
|
|||
|
|
|||
|
// private float SafeDiv(float a, float b) => Mathf.Approximately(b, 0f) ? 0f : a / b;
|
|||
|
|
|||
|
// private void SetLayerRecursively(GameObject go, int layerIndex)
|
|||
|
// {
|
|||
|
// //go.layer = layerIndex;
|
|||
|
// //foreach (Transform t in go.transform)
|
|||
|
// // SetLayerRecursively(t.gameObject, layerIndex);
|
|||
|
// }
|
|||
|
//}
|