299 lines
9.4 KiB
C#
299 lines
9.4 KiB
C#
using UnityEngine;
|
||
using System.Collections;
|
||
using System.Collections.Generic;
|
||
|
||
[RequireComponent(typeof(Collider))]
|
||
public class CubeClash_BirdSwoop : MonoBehaviour
|
||
{
|
||
[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")]
|
||
public float carryHeight = 5f;
|
||
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’s own body (optional)
|
||
private Rigidbody _birdRb; // if present, we’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’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;
|
||
}
|
||
|
||
private Rigidbody PickRandomZibuRigidbody()
|
||
{
|
||
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
|
||
{
|
||
foreach (var go in GameObject.FindGameObjectsWithTag(zibuTag))
|
||
{
|
||
if (!go || !go.activeInHierarchy) continue;
|
||
var rb = go.GetComponent<Rigidbody>();
|
||
if (rb) candidates.Add(rb);
|
||
}
|
||
}
|
||
|
||
if (candidates.Count == 0) return null;
|
||
return candidates[Random.Range(0, candidates.Count)];
|
||
}
|
||
|
||
void FixedUpdate()
|
||
{
|
||
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
|
||
float t = 0f;
|
||
while (t < riseTime)
|
||
{
|
||
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();
|
||
}
|
||
|
||
// 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);
|
||
}
|
||
}
|