2025-09-01 05:03:00 +05:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
using System.Collections;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
using System.Collections.Generic;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
[RequireComponent(typeof(Collider))]
|
2025-09-01 05:03:00 +05:00
|
|
|
|
public class CubeClash_BirdSwoop : MonoBehaviour
|
|
|
|
|
{
|
2025-09-03 19:45:23 +05:00
|
|
|
|
[Header("Targeting")]
|
|
|
|
|
public bool useStaticList = true;
|
|
|
|
|
public string zibuTag = "Zibu";
|
|
|
|
|
public bool autoDestroyIfNoTarget = true;
|
|
|
|
|
|
|
|
|
|
[Header("Bird Movement (Seek)")]
|
|
|
|
|
public float birdMoveSpeed = 8f;
|
|
|
|
|
public float birdTurnSpeed = 10f;
|
|
|
|
|
public float pickupRadius = 1.2f;
|
|
|
|
|
public float approachHeightOffset = 2f;
|
|
|
|
|
public float followAboveOffset = 1.0f;
|
|
|
|
|
public bool faceVelocity = true;
|
|
|
|
|
|
|
|
|
|
[Header("Lift Profile")]
|
2025-09-01 05:03:00 +05:00
|
|
|
|
public float carryHeight = 5f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
public float riseTime = 0.8f;
|
|
|
|
|
public float hoverTime = 1.0f;
|
|
|
|
|
public float dropKickImpulse = 0f;
|
|
|
|
|
|
|
|
|
|
[Header("Force Tuning (Physics)")]
|
|
|
|
|
public float posGain = 12f;
|
|
|
|
|
public float velGain = 2.5f;
|
|
|
|
|
public float maxDeltaVPerFixedUpdate = 12f;
|
|
|
|
|
|
|
|
|
|
[Header("While Carried")]
|
|
|
|
|
public bool disableGravityWhileCarried = true;
|
|
|
|
|
public float extraDrag = 2f;
|
|
|
|
|
public float extraStun = 0.1f;
|
|
|
|
|
|
|
|
|
|
[Header("Lifecycle")]
|
|
|
|
|
public bool destroyOnRelease = true;
|
|
|
|
|
|
|
|
|
|
private enum SwoopState { Seeking, Carrying, Releasing, Idle }
|
|
|
|
|
private SwoopState _state = SwoopState.Idle;
|
|
|
|
|
|
|
|
|
|
private Rigidbody _targetRb;
|
|
|
|
|
private CubeClash_ZibuController _targetController;
|
|
|
|
|
|
|
|
|
|
// Bird<72>s own body (optional)
|
|
|
|
|
private Rigidbody _birdRb; // if present, we<77>ll MovePosition on it
|
|
|
|
|
private bool _weMadeKinematic = false;
|
|
|
|
|
|
|
|
|
|
// Carry bookkeeping
|
|
|
|
|
private readonly HashSet<Rigidbody> _carried = new HashSet<Rigidbody>();
|
|
|
|
|
private readonly Dictionary<Rigidbody, float> _origDrag = new Dictionary<Rigidbody, float>();
|
|
|
|
|
private readonly Dictionary<Rigidbody, bool> _origGrav = new Dictionary<Rigidbody, bool>();
|
|
|
|
|
|
|
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
var col = GetComponent<Collider>();
|
|
|
|
|
if (col) col.isTrigger = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
_birdRb = GetComponent<Rigidbody>();
|
|
|
|
|
if (_birdRb != null)
|
|
|
|
|
{
|
|
|
|
|
// Ensure physics doesn<73>t fight our manual movement
|
|
|
|
|
if (!_birdRb.isKinematic)
|
|
|
|
|
{
|
|
|
|
|
_birdRb.isKinematic = true;
|
|
|
|
|
_weMadeKinematic = true;
|
|
|
|
|
}
|
|
|
|
|
_birdRb.interpolation = RigidbodyInterpolation.Interpolate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnEnable()
|
|
|
|
|
{
|
|
|
|
|
PickRandomZibuAndSeek();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PickRandomZibuAndSeek()
|
|
|
|
|
{
|
|
|
|
|
_targetRb = PickRandomZibuRigidbody();
|
|
|
|
|
if (_targetRb == null)
|
|
|
|
|
{
|
|
|
|
|
if (autoDestroyIfNoTarget) Destroy(gameObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_targetController = _targetRb.GetComponent<CubeClash_ZibuController>();
|
|
|
|
|
_state = SwoopState.Seeking;
|
|
|
|
|
}
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
private Rigidbody PickRandomZibuRigidbody()
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 19:45:23 +05:00
|
|
|
|
List<Rigidbody> candidates = new List<Rigidbody>();
|
|
|
|
|
|
|
|
|
|
if (useStaticList && CubeClash_ZibuController.AllZibus != null && CubeClash_ZibuController.AllZibus.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
foreach (var z in CubeClash_ZibuController.AllZibus)
|
|
|
|
|
{
|
|
|
|
|
if (!z || !z.gameObject.activeInHierarchy) continue;
|
|
|
|
|
var rb = z.GetComponent<Rigidbody>();
|
|
|
|
|
if (rb) candidates.Add(rb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 19:45:23 +05:00
|
|
|
|
foreach (var go in GameObject.FindGameObjectsWithTag(zibuTag))
|
|
|
|
|
{
|
|
|
|
|
if (!go || !go.activeInHierarchy) continue;
|
|
|
|
|
var rb = go.GetComponent<Rigidbody>();
|
|
|
|
|
if (rb) candidates.Add(rb);
|
|
|
|
|
}
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
if (candidates.Count == 0) return null;
|
|
|
|
|
return candidates[Random.Range(0, candidates.Count)];
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
void FixedUpdate()
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 19:45:23 +05:00
|
|
|
|
if (_targetRb == null) return;
|
|
|
|
|
|
|
|
|
|
switch (_state)
|
|
|
|
|
{
|
|
|
|
|
case SwoopState.Seeking:
|
|
|
|
|
SeekStep();
|
|
|
|
|
break;
|
|
|
|
|
case SwoopState.Carrying:
|
|
|
|
|
EscortFollowStep();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Movement helpers that work with or without a Rigidbody on the bird ---
|
|
|
|
|
private void MoveBirdTo(Vector3 newPos)
|
|
|
|
|
{
|
|
|
|
|
if (_birdRb != null) _birdRb.MovePosition(newPos);
|
|
|
|
|
else transform.position = newPos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RotateBirdTo(Quaternion newRot, float slerpFactor)
|
|
|
|
|
{
|
|
|
|
|
if (faceVelocity)
|
|
|
|
|
{
|
|
|
|
|
var current = (_birdRb != null) ? _birdRb.rotation : transform.rotation;
|
|
|
|
|
var next = Quaternion.Slerp(current, newRot, slerpFactor);
|
|
|
|
|
if (_birdRb != null) _birdRb.MoveRotation(next);
|
|
|
|
|
else transform.rotation = next;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SeekStep()
|
|
|
|
|
{
|
|
|
|
|
Vector3 aim = _targetRb.position + Vector3.up * approachHeightOffset;
|
|
|
|
|
Vector3 to = aim - ((_birdRb != null) ? _birdRb.position : transform.position);
|
|
|
|
|
float dist = to.magnitude;
|
|
|
|
|
|
|
|
|
|
if (dist > 0.001f)
|
|
|
|
|
{
|
|
|
|
|
Vector3 dir = to / dist;
|
|
|
|
|
Vector3 step = dir * birdMoveSpeed * Time.fixedDeltaTime;
|
|
|
|
|
if (step.magnitude > dist) step = to;
|
|
|
|
|
|
|
|
|
|
MoveBirdTo(((_birdRb != null) ? _birdRb.position : transform.position) + step);
|
|
|
|
|
|
|
|
|
|
if (faceVelocity && step.sqrMagnitude > 1e-6f)
|
|
|
|
|
{
|
|
|
|
|
Quaternion targetRot = Quaternion.LookRotation(step.normalized, Vector3.up);
|
|
|
|
|
RotateBirdTo(targetRot, birdTurnSpeed * Time.fixedDeltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Vector3.Distance(((_birdRb != null) ? _birdRb.position : transform.position), _targetRb.position) <= pickupRadius)
|
|
|
|
|
{
|
|
|
|
|
StartCoroutine(CarryRoutine(_targetRb));
|
|
|
|
|
_state = SwoopState.Carrying;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EscortFollowStep()
|
|
|
|
|
{
|
|
|
|
|
if (_targetRb == null) return;
|
|
|
|
|
|
|
|
|
|
Vector3 follow = _targetRb.position + Vector3.up * followAboveOffset;
|
|
|
|
|
Vector3 to = follow - ((_birdRb != null) ? _birdRb.position : transform.position);
|
|
|
|
|
float dist = to.magnitude;
|
|
|
|
|
|
|
|
|
|
if (dist > 0.001f)
|
|
|
|
|
{
|
|
|
|
|
Vector3 dir = to / dist;
|
|
|
|
|
Vector3 step = dir * birdMoveSpeed * Time.fixedDeltaTime;
|
|
|
|
|
if (step.magnitude > dist) step = to;
|
|
|
|
|
|
|
|
|
|
MoveBirdTo(((_birdRb != null) ? _birdRb.position : transform.position) + step);
|
|
|
|
|
|
|
|
|
|
if (faceVelocity && step.sqrMagnitude > 1e-6f)
|
|
|
|
|
{
|
|
|
|
|
Quaternion targetRot = Quaternion.LookRotation(step.normalized, Vector3.up);
|
|
|
|
|
RotateBirdTo(targetRot, birdTurnSpeed * Time.fixedDeltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerator CarryRoutine(Rigidbody rb)
|
|
|
|
|
{
|
|
|
|
|
_carried.Add(rb);
|
|
|
|
|
|
|
|
|
|
float plannedStun = Mathf.Max(0.05f, riseTime + hoverTime + extraStun);
|
|
|
|
|
if (_targetController) _targetController.ApplyPushStun(plannedStun);
|
|
|
|
|
|
|
|
|
|
if (!_origDrag.ContainsKey(rb)) _origDrag[rb] = rb.drag;
|
|
|
|
|
if (!_origGrav.ContainsKey(rb)) _origGrav[rb] = rb.useGravity;
|
|
|
|
|
|
|
|
|
|
if (disableGravityWhileCarried) rb.useGravity = false;
|
|
|
|
|
rb.drag = _origDrag[rb] + Mathf.Max(0f, extraDrag);
|
|
|
|
|
|
|
|
|
|
Vector3 startPos = rb.position;
|
|
|
|
|
float targetY = startPos.y + carryHeight;
|
|
|
|
|
|
|
|
|
|
// Rise
|
2025-09-01 05:03:00 +05:00
|
|
|
|
float t = 0f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
while (t < riseTime)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 19:45:23 +05:00
|
|
|
|
t += Time.fixedDeltaTime;
|
|
|
|
|
float a = Mathf.Clamp01(t / Mathf.Max(0.0001f, riseTime));
|
|
|
|
|
Vector3 targetPos = new Vector3(rb.position.x, Mathf.Lerp(startPos.y, targetY, a), rb.position.z);
|
|
|
|
|
ApplyPDStep(rb, targetPos);
|
|
|
|
|
yield return new WaitForFixedUpdate();
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
// Hover
|
|
|
|
|
float h = 0f;
|
|
|
|
|
while (h < hoverTime)
|
|
|
|
|
{
|
|
|
|
|
h += Time.fixedDeltaTime;
|
|
|
|
|
Vector3 targetPos = new Vector3(rb.position.x, targetY, rb.position.z);
|
|
|
|
|
ApplyPDStep(rb, targetPos);
|
|
|
|
|
yield return new WaitForFixedUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release
|
|
|
|
|
if (_origGrav.TryGetValue(rb, out bool g)) rb.useGravity = g;
|
|
|
|
|
if (_origDrag.TryGetValue(rb, out float d)) rb.drag = d;
|
|
|
|
|
|
|
|
|
|
if (dropKickImpulse > 0f)
|
|
|
|
|
rb.AddForce(Vector3.down * dropKickImpulse, ForceMode.Impulse);
|
|
|
|
|
|
|
|
|
|
_carried.Remove(rb);
|
|
|
|
|
_origDrag.Remove(rb);
|
|
|
|
|
_origGrav.Remove(rb);
|
|
|
|
|
|
|
|
|
|
_state = SwoopState.Releasing;
|
|
|
|
|
_targetRb = null;
|
|
|
|
|
_targetController = null;
|
|
|
|
|
|
|
|
|
|
if (destroyOnRelease) Destroy(gameObject);
|
|
|
|
|
else _state = SwoopState.Idle;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ApplyPDStep(Rigidbody rb, Vector3 targetPos)
|
|
|
|
|
{
|
|
|
|
|
Vector3 to = targetPos - rb.position;
|
|
|
|
|
Vector3 toXZ = new Vector3(to.x, 0f, to.z);
|
|
|
|
|
Vector3 toY = new Vector3(0f, to.y, 0f);
|
|
|
|
|
|
|
|
|
|
Vector3 dvXZ = posGain * toXZ - velGain * new Vector3(rb.velocity.x, 0f, rb.velocity.z);
|
|
|
|
|
Vector3 dvY = posGain * toY - velGain * new Vector3(0f, rb.velocity.y, 0f);
|
|
|
|
|
Vector3 deltaV = (dvXZ + dvY) * Time.fixedDeltaTime;
|
|
|
|
|
|
|
|
|
|
if (maxDeltaVPerFixedUpdate > 0f && deltaV.magnitude > maxDeltaVPerFixedUpdate)
|
|
|
|
|
deltaV = deltaV.normalized * maxDeltaVPerFixedUpdate;
|
|
|
|
|
|
|
|
|
|
rb.AddForce(deltaV, ForceMode.VelocityChange);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void OnDisable()
|
|
|
|
|
{
|
|
|
|
|
foreach (var rb in _carried)
|
|
|
|
|
{
|
|
|
|
|
if (!rb) continue;
|
|
|
|
|
if (_origGrav.TryGetValue(rb, out bool g)) rb.useGravity = g;
|
|
|
|
|
if (_origDrag.TryGetValue(rb, out float d)) rb.drag = d;
|
|
|
|
|
}
|
|
|
|
|
_carried.Clear();
|
|
|
|
|
_origDrag.Clear();
|
|
|
|
|
_origGrav.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Debug gizmo to see pickup radius in Scene view
|
|
|
|
|
void OnDrawGizmosSelected()
|
|
|
|
|
{
|
|
|
|
|
Gizmos.color = Color.cyan;
|
|
|
|
|
Vector3 pos = (_birdRb != null) ? _birdRb.position : transform.position;
|
|
|
|
|
Gizmos.DrawWireSphere(pos, pickupRadius);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
}
|