2025-09-04 12:13:13 +05:00
|
|
|
|
using UnityEngine;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
using System.Collections;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
using System.Collections.Generic;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
using DG.Tweening;
|
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-04 12:13:13 +05:00
|
|
|
|
public static int ActiveCount = 0;
|
|
|
|
|
|
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;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
public float pickupRadius = 1.2f; // measured vs target collider surface
|
2025-09-03 19:45:23 +05:00
|
|
|
|
public float approachHeightOffset = 2f;
|
|
|
|
|
public float followAboveOffset = 1.0f;
|
|
|
|
|
public bool faceVelocity = true;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
[Header("Lift / Carry 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;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
public float hoverTime = 0.4f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
[Tooltip("Time the Zibu is carried horizontally to the air offset before dropping.")]
|
|
|
|
|
public float travelToAirOffsetTime = 0.6f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
[Header("Drop / Physics")]
|
|
|
|
|
public float dropKickImpulse = 0f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
public bool disableGravityWhileCarried = true;
|
|
|
|
|
public float extraDrag = 2f;
|
|
|
|
|
public float extraStun = 0.1f;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
public float posGain = 12f;
|
|
|
|
|
public float velGain = 2.5f;
|
|
|
|
|
public float maxDeltaVPerFixedUpdate = 12f;
|
|
|
|
|
|
|
|
|
|
[Header("Pickup Assist / Anti-Stuck")]
|
|
|
|
|
public float freezeAIWhenWithin = 1.8f;
|
|
|
|
|
public float pickupSkin = 0.3f;
|
|
|
|
|
public float forcePickupAfter = 0.6f;
|
|
|
|
|
public float nearRangeFactor = 2.0f;
|
|
|
|
|
public float minApproachHeight = 0.15f;
|
|
|
|
|
|
|
|
|
|
[Header("Random Air Offset (world XZ)")]
|
|
|
|
|
public Vector2 airOffsetX = new Vector2(0f, 1f);
|
|
|
|
|
public Vector2 airOffsetZ = new Vector2(0f, 1f);
|
|
|
|
|
|
|
|
|
|
[Header("Safe Drop (ground, overlap avoidance)")]
|
|
|
|
|
[Tooltip("If > 0, overrides body radius for overlap tests; else derived from collider bounds.")]
|
|
|
|
|
public float zibuBodyRadiusOverride = 0f;
|
|
|
|
|
public float inflateClearance = 0.1f;
|
|
|
|
|
public int maxDropProbeSpots = 16;
|
|
|
|
|
public float probeStepDistance = 0.45f;
|
|
|
|
|
[Tooltip("Layers considered as ground for raycast placement.")]
|
|
|
|
|
public LayerMask groundMask = ~0; // set to your ground/floor layers
|
|
|
|
|
[Tooltip("Layers used to detect other Zibus (set to the Zibu layer).")]
|
|
|
|
|
public LayerMask zibuMask = ~0;
|
|
|
|
|
|
|
|
|
|
[Header("Post-Drop Unstack (layer-based)")]
|
|
|
|
|
[Tooltip("How long after drop we keep checking if the Zibu landed on another Zibu.")]
|
|
|
|
|
public float unstackCheckSeconds = 1.0f;
|
|
|
|
|
[Tooltip("How often to check (seconds).")]
|
|
|
|
|
public float unstackCheckInterval = 0.08f;
|
|
|
|
|
[Tooltip("SphereCast radius used to detect what's directly below the dropped Zibu.")]
|
|
|
|
|
public float unstackProbeRadius = 0.25f;
|
|
|
|
|
[Tooltip("Max downward probe distance from about waist height.")]
|
|
|
|
|
public float unstackProbeDown = 1.5f;
|
|
|
|
|
[Tooltip("Sideways impulse when we detect Zibu-under-Zibu.")]
|
|
|
|
|
public float unstackLateralImpulse = 3.0f;
|
|
|
|
|
[Tooltip("Optional downward impulse to help it slip off.")]
|
|
|
|
|
public float unstackDownImpulse = 0.0f;
|
|
|
|
|
[Tooltip("If still stuck, teleport this many meters sideways to nearest clear ground.")]
|
|
|
|
|
public float unstackTeleportStep = 0.5f;
|
|
|
|
|
[Tooltip("Enable the teleport fallback if impulses didn't clear it.")]
|
|
|
|
|
public bool unstackUseTeleportFallback = true;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
[Header("Lifecycle")]
|
|
|
|
|
public bool destroyOnRelease = true;
|
|
|
|
|
|
|
|
|
|
private enum SwoopState { Seeking, Carrying, Releasing, Idle }
|
|
|
|
|
private SwoopState _state = SwoopState.Idle;
|
|
|
|
|
|
|
|
|
|
private Rigidbody _targetRb;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
private Collider _targetCol;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
private CubeClash_ZibuController _targetController;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
private CubeClash_ZibuAI _targetAI;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
private Rigidbody _birdRb;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
private bool _weMadeKinematic = false;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// token bookkeeping for AI hold
|
|
|
|
|
private bool _aiHoldApplied = false;
|
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
// 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>();
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// anti-stuck timer
|
|
|
|
|
private float _nearTimer = 0f;
|
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
void Reset()
|
|
|
|
|
{
|
|
|
|
|
var col = GetComponent<Collider>();
|
|
|
|
|
if (col) col.isTrigger = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
_birdRb = GetComponent<Rigidbody>();
|
|
|
|
|
if (_birdRb != null)
|
|
|
|
|
{
|
|
|
|
|
if (!_birdRb.isKinematic)
|
|
|
|
|
{
|
|
|
|
|
_birdRb.isKinematic = true;
|
|
|
|
|
_weMadeKinematic = true;
|
|
|
|
|
}
|
|
|
|
|
_birdRb.interpolation = RigidbodyInterpolation.Interpolate;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnEnable()
|
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
ActiveCount++;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
PickRandomZibuAndSeek();
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Invoke(nameof(Destroyer), 10);
|
|
|
|
|
}
|
|
|
|
|
void Destroyer()
|
|
|
|
|
{
|
|
|
|
|
Destroy(gameObject);
|
|
|
|
|
}
|
|
|
|
|
void OnDisable()
|
|
|
|
|
{
|
|
|
|
|
// Restore anyone we were carrying or had held
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
var ai = rb.GetComponent<CubeClash_ZibuAI>();
|
|
|
|
|
if (ai) ai.EndPickupHold(this);
|
|
|
|
|
}
|
|
|
|
|
_carried.Clear();
|
|
|
|
|
_origDrag.Clear();
|
|
|
|
|
_origGrav.Clear();
|
|
|
|
|
|
|
|
|
|
if (_targetAI && _aiHoldApplied)
|
|
|
|
|
{
|
|
|
|
|
_targetAI.EndPickupHold(this);
|
|
|
|
|
_aiHoldApplied = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ActiveCount = Mathf.Max(0, ActiveCount - 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
ActiveCount = Mathf.Max(0, ActiveCount - 1);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void PickRandomZibuAndSeek()
|
|
|
|
|
{
|
|
|
|
|
_targetRb = PickRandomZibuRigidbody();
|
|
|
|
|
if (_targetRb == null)
|
|
|
|
|
{
|
|
|
|
|
if (autoDestroyIfNoTarget) Destroy(gameObject);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
_targetCol = _targetRb.GetComponent<Collider>();
|
2025-09-03 19:45:23 +05:00
|
|
|
|
_targetController = _targetRb.GetComponent<CubeClash_ZibuController>();
|
2025-09-04 12:13:13 +05:00
|
|
|
|
_targetAI = _targetRb.GetComponent<CubeClash_ZibuAI>();
|
2025-09-03 19:45:23 +05:00
|
|
|
|
_state = SwoopState.Seeking;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
_nearTimer = 0f;
|
|
|
|
|
_aiHoldApplied = false;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
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)
|
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
case SwoopState.Seeking: SeekStep(); break;
|
|
|
|
|
case SwoopState.Carrying: EscortFollowStep(); break;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
private Vector3 BirdPos => (_birdRb != null) ? _birdRb.position : transform.position;
|
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
private void MoveBirdTo(Vector3 newPos)
|
|
|
|
|
{
|
|
|
|
|
if (_birdRb != null) _birdRb.MovePosition(newPos);
|
|
|
|
|
else transform.position = newPos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RotateBirdTo(Quaternion newRot, float slerpFactor)
|
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
if (!faceVelocity) return;
|
|
|
|
|
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 float DistanceToTargetSurface(Vector3 from)
|
|
|
|
|
{
|
|
|
|
|
if (_targetCol == null) return Vector3.Distance(from, _targetRb.position);
|
|
|
|
|
Vector3 closest = _targetCol.ClosestPoint(from);
|
|
|
|
|
return Vector3.Distance(from, closest);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SeekStep()
|
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Ask Zibu to hold if we’re close-ish to make pickup reliable
|
|
|
|
|
float distSurface = DistanceToTargetSurface(BirdPos);
|
|
|
|
|
if (distSurface <= Mathf.Max(pickupRadius, freezeAIWhenWithin))
|
|
|
|
|
{
|
|
|
|
|
if (_targetAI && !_aiHoldApplied) { _targetAI.BeginPickupHold(this); _aiHoldApplied = true; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lower approach height as we get close (prevents hovering above head)
|
|
|
|
|
float tHeight = Mathf.InverseLerp(pickupRadius, pickupRadius * 3f, distSurface);
|
|
|
|
|
float dynHeight = Mathf.Lerp(minApproachHeight, approachHeightOffset, Mathf.Clamp01(tHeight));
|
|
|
|
|
Vector3 aim = _targetRb.position + Vector3.up * dynHeight;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Move toward aim
|
|
|
|
|
Vector3 to = aim - BirdPos;
|
|
|
|
|
float dist = to.magnitude;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
if (dist > 0.001f)
|
|
|
|
|
{
|
|
|
|
|
Vector3 dir = to / dist;
|
|
|
|
|
Vector3 step = dir * birdMoveSpeed * Time.fixedDeltaTime;
|
|
|
|
|
if (step.magnitude > dist) step = to;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
MoveBirdTo(BirdPos + step);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
if (faceVelocity && step.sqrMagnitude > 1e-6f)
|
|
|
|
|
{
|
|
|
|
|
Quaternion targetRot = Quaternion.LookRotation(step.normalized, Vector3.up);
|
|
|
|
|
RotateBirdTo(targetRot, birdTurnSpeed * Time.fixedDeltaTime);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Track "near" time and force pickup if we linger too long
|
|
|
|
|
if (distSurface <= pickupRadius * nearRangeFactor)
|
|
|
|
|
_nearTimer += Time.fixedDeltaTime;
|
|
|
|
|
else
|
|
|
|
|
_nearTimer = 0f;
|
|
|
|
|
|
|
|
|
|
if (distSurface <= pickupRadius + pickupSkin || _nearTimer >= forcePickupAfter)
|
2025-09-03 19:45:23 +05:00
|
|
|
|
{
|
|
|
|
|
StartCoroutine(CarryRoutine(_targetRb));
|
|
|
|
|
_state = SwoopState.Carrying;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
_nearTimer = 0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Safety: if our trigger touches the target, start carry immediately
|
|
|
|
|
void OnTriggerEnter(Collider other)
|
|
|
|
|
{
|
|
|
|
|
if (_state != SwoopState.Seeking || _targetRb == null) return;
|
|
|
|
|
if (other && other.attachedRigidbody == _targetRb)
|
|
|
|
|
{
|
|
|
|
|
StartCoroutine(CarryRoutine(_targetRb));
|
|
|
|
|
_state = SwoopState.Carrying;
|
|
|
|
|
_nearTimer = 0f;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void EscortFollowStep()
|
|
|
|
|
{
|
|
|
|
|
if (_targetRb == null) return;
|
|
|
|
|
|
|
|
|
|
Vector3 follow = _targetRb.position + Vector3.up * followAboveOffset;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Vector3 to = follow - BirdPos;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
float dist = to.magnitude;
|
|
|
|
|
|
|
|
|
|
if (dist > 0.001f)
|
|
|
|
|
{
|
|
|
|
|
Vector3 dir = to / dist;
|
|
|
|
|
Vector3 step = dir * birdMoveSpeed * Time.fixedDeltaTime;
|
|
|
|
|
if (step.magnitude > dist) step = to;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
MoveBirdTo(BirdPos + step);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Hold AI while carried
|
|
|
|
|
if (_targetAI && !_aiHoldApplied) { _targetAI.BeginPickupHold(this); _aiHoldApplied = true; }
|
|
|
|
|
|
|
|
|
|
// === Phase 1: Rise to carry height ===
|
|
|
|
|
Vector3 pickupPos = rb.position;
|
|
|
|
|
float apexY = pickupPos.y + carryHeight;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
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));
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Vector3 targetPos = new Vector3(rb.position.x, Mathf.Lerp(pickupPos.y, apexY, a), rb.position.z);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
ApplyPDStep(rb, targetPos);
|
|
|
|
|
yield return new WaitForFixedUpdate();
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Short hover to stabilize
|
2025-09-03 19:45:23 +05:00
|
|
|
|
float h = 0f;
|
|
|
|
|
while (h < hoverTime)
|
|
|
|
|
{
|
|
|
|
|
h += Time.fixedDeltaTime;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Vector3 targetPos = new Vector3(rb.position.x, apexY, rb.position.z);
|
2025-09-03 19:45:23 +05:00
|
|
|
|
ApplyPDStep(rb, targetPos);
|
|
|
|
|
yield return new WaitForFixedUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// === Phase 2: Carry horizontally to a random air offset (XZ ∈ [0..1]) ===
|
|
|
|
|
Vector3 airOffset = new Vector3(
|
|
|
|
|
Random.Range(airOffsetX.x, airOffsetX.y), 0f,
|
|
|
|
|
Random.Range(airOffsetZ.x, airOffsetZ.y)
|
|
|
|
|
);
|
|
|
|
|
Vector3 desiredAirPos = new Vector3(pickupPos.x + airOffset.x, apexY, pickupPos.z + airOffset.z);
|
|
|
|
|
|
|
|
|
|
float travel = 0f;
|
|
|
|
|
Vector3 startCarryPos = rb.position;
|
|
|
|
|
while (travel < travelToAirOffsetTime)
|
|
|
|
|
{
|
|
|
|
|
travel += Time.fixedDeltaTime;
|
|
|
|
|
float a = Mathf.Clamp01(travel / Mathf.Max(0.0001f, travelToAirOffsetTime));
|
|
|
|
|
Vector3 targetPos = Vector3.Lerp(startCarryPos, desiredAirPos, a);
|
|
|
|
|
ApplyPDStep(rb, targetPos);
|
|
|
|
|
yield return new WaitForFixedUpdate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// === Phase 3: Choose a safe ground XZ (avoid other Zibus), then drop naturally ===
|
|
|
|
|
GetZibuBodyMetrics(out float bodyRadius, out float bodyHeight);
|
|
|
|
|
bodyRadius += inflateClearance;
|
|
|
|
|
|
|
|
|
|
Vector3 safeGround = FindClearDropGround(desiredAirPos, bodyRadius, bodyHeight);
|
|
|
|
|
|
|
|
|
|
// Align only XZ to the safe spot; KEEP CURRENT Y so gravity does the fall
|
|
|
|
|
//rb.position = new Vector3(safeGround.x, rb.position.y, safeGround.z);
|
|
|
|
|
|
|
|
|
|
// Restore physics (release)
|
2025-09-03 19:45:23 +05:00
|
|
|
|
if (_origGrav.TryGetValue(rb, out bool g)) rb.useGravity = g;
|
|
|
|
|
if (_origDrag.TryGetValue(rb, out float d)) rb.drag = d;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Open parachute on drop (requires TriggerParachuteDrop on controller)
|
|
|
|
|
if (_targetController) _targetController.TriggerParachuteDrop(0.15f);
|
|
|
|
|
|
|
|
|
|
// Ensure a clear "start falling" cue
|
|
|
|
|
float kick = (dropKickImpulse > 0f ? dropKickImpulse : 0.5f);
|
|
|
|
|
rb.AddForce(Vector3.down * kick, ForceMode.Impulse);
|
|
|
|
|
|
|
|
|
|
// Release AI hold so the Zibu can move after landing
|
|
|
|
|
if (_targetAI && _aiHoldApplied) { _targetAI.EndPickupHold(this); _aiHoldApplied = false; }
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// === Phase 4: Post-drop unstack if we land on another Zibu ===
|
|
|
|
|
yield return StartCoroutine(PostDropUnstack(rb, bodyRadius, bodyHeight));
|
|
|
|
|
|
|
|
|
|
// Cleanup bookkeeping
|
2025-09-03 19:45:23 +05:00
|
|
|
|
_carried.Remove(rb);
|
|
|
|
|
_origDrag.Remove(rb);
|
|
|
|
|
_origGrav.Remove(rb);
|
|
|
|
|
|
|
|
|
|
_state = SwoopState.Releasing;
|
|
|
|
|
_targetRb = null;
|
|
|
|
|
_targetController = null;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
_targetAI = null;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
|
|
|
|
|
if (destroyOnRelease) Destroy(gameObject);
|
|
|
|
|
else _state = SwoopState.Idle;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
//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);
|
|
|
|
|
|
|
|
|
|
// // Ensure AI is held while rising/hovering/transport
|
|
|
|
|
// if (_targetAI && !_aiHoldApplied) { _targetAI.BeginPickupHold(this); _aiHoldApplied = true; }
|
|
|
|
|
|
|
|
|
|
// // === Phase 1: Rise to carry height ===
|
|
|
|
|
// Vector3 pickupPos = rb.position;
|
|
|
|
|
// float targetY = pickupPos.y + carryHeight;
|
|
|
|
|
|
|
|
|
|
// 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(pickupPos.y, targetY, a), rb.position.z);
|
|
|
|
|
// ApplyPDStep(rb, targetPos);
|
|
|
|
|
// yield return new WaitForFixedUpdate();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // Short hover to stabilize
|
|
|
|
|
// 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();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // === Phase 2: Carry horizontally to a random air offset (XZ ∈ [0..1]) ===
|
|
|
|
|
// Vector3 airOffset = new Vector3(Random.Range(airOffsetX.x, airOffsetX.y), 0f, Random.Range(airOffsetZ.x, airOffsetZ.y));
|
|
|
|
|
// Vector3 desiredAirPos = new Vector3(pickupPos.x + airOffset.x, targetY, pickupPos.z + airOffset.z);
|
|
|
|
|
|
|
|
|
|
// float travel = 0f;
|
|
|
|
|
// Vector3 startCarryPos = rb.position;
|
|
|
|
|
// while (travel < travelToAirOffsetTime)
|
|
|
|
|
// {
|
|
|
|
|
// travel += Time.fixedDeltaTime;
|
|
|
|
|
// float a = Mathf.Clamp01(travel / Mathf.Max(0.0001f, travelToAirOffsetTime));
|
|
|
|
|
// Vector3 targetPos = Vector3.Lerp(startCarryPos, desiredAirPos, a);
|
|
|
|
|
// ApplyPDStep(rb, targetPos);
|
|
|
|
|
// yield return new WaitForFixedUpdate();
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
// // === Phase 3: Choose a safe ground point (avoid other Zibus) & drop ===
|
|
|
|
|
// GetZibuBodyMetrics(out float bodyRadius, out float bodyHeight);
|
|
|
|
|
// bodyRadius += inflateClearance;
|
|
|
|
|
|
|
|
|
|
// Vector3 safeGround = FindClearDropGround(desiredAirPos, bodyRadius, bodyHeight);
|
|
|
|
|
|
|
|
|
|
// // Place at safe ground (slight lift to avoid ground clipping)
|
|
|
|
|
// //rb.position = new Vector3(safeGround.x, safeGround.y + 0.02f, safeGround.z);
|
|
|
|
|
// // replace the snap line with:
|
|
|
|
|
// //transform.DOMove(new Vector3(safeGround.x, rb.position.y, safeGround.z), 0.75f);
|
|
|
|
|
// //rb.position = new Vector3(safeGround.x, rb.position.y, safeGround.z);
|
|
|
|
|
// yield return new WaitForSeconds(0.2f);
|
|
|
|
|
// _state = SwoopState.Releasing;
|
|
|
|
|
// // (then you restore gravity and optionally add a small downward impulse)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // Restore physics
|
|
|
|
|
// 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);
|
|
|
|
|
|
|
|
|
|
// // Release AI hold so it can move right after landing
|
|
|
|
|
// if (_targetAI && _aiHoldApplied) { _targetAI.EndPickupHold(this); _aiHoldApplied = false; }
|
|
|
|
|
|
|
|
|
|
// // === Phase 4: Post-drop unstack if we landed on another Zibu (layer check) ===
|
|
|
|
|
// yield return StartCoroutine(PostDropUnstack(rb, bodyRadius, bodyHeight));
|
|
|
|
|
// _carried.Remove(rb);
|
|
|
|
|
|
|
|
|
|
// // Cleanup bookkeeping
|
|
|
|
|
// _origDrag.Remove(rb);
|
|
|
|
|
// _origGrav.Remove(rb);
|
|
|
|
|
|
|
|
|
|
// //_state = SwoopState.Releasing;
|
|
|
|
|
// _targetRb = null;
|
|
|
|
|
// _targetController = null;
|
|
|
|
|
// _targetAI = null;
|
|
|
|
|
|
|
|
|
|
// if (destroyOnRelease) Destroy(gameObject);
|
|
|
|
|
// else _state = SwoopState.Idle;
|
|
|
|
|
//}
|
|
|
|
|
|
2025-09-03 19:45:23 +05:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// ===== Safe-drop utilities =====
|
|
|
|
|
|
|
|
|
|
private void GetZibuBodyMetrics(out float bodyRadius, out float bodyHeight)
|
2025-09-03 19:45:23 +05:00
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
if (zibuBodyRadiusOverride > 0f)
|
2025-09-03 19:45:23 +05:00
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
bodyRadius = zibuBodyRadiusOverride;
|
|
|
|
|
bodyHeight = Mathf.Max(1.0f, (_targetCol ? _targetCol.bounds.size.y : 1.6f));
|
|
|
|
|
return;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
2025-09-04 12:13:13 +05:00
|
|
|
|
|
|
|
|
|
if (_targetCol is CapsuleCollider cap)
|
|
|
|
|
{
|
|
|
|
|
bodyRadius = cap.radius;
|
|
|
|
|
bodyHeight = Mathf.Max(cap.height, cap.radius * 2f);
|
|
|
|
|
}
|
|
|
|
|
else if (_targetCol is SphereCollider sph)
|
|
|
|
|
{
|
|
|
|
|
bodyRadius = sph.radius;
|
|
|
|
|
bodyHeight = sph.radius * 2f;
|
|
|
|
|
}
|
|
|
|
|
else if (_targetCol)
|
|
|
|
|
{
|
|
|
|
|
var b = _targetCol.bounds;
|
|
|
|
|
bodyRadius = Mathf.Max(b.extents.x, b.extents.z) * 0.9f;
|
|
|
|
|
bodyHeight = Mathf.Max(1.0f, b.size.y);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
bodyRadius = 0.45f;
|
|
|
|
|
bodyHeight = 1.6f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector3 FindClearDropGround(Vector3 desiredAirPos, float bodyRadius, float bodyHeight)
|
|
|
|
|
{
|
|
|
|
|
// Project desired XZ to ground
|
|
|
|
|
Vector3 desiredGround = ProjectToGround(desiredAirPos);
|
|
|
|
|
|
|
|
|
|
if (!OverlapsZibuCapsule(desiredGround, bodyRadius, bodyHeight))
|
|
|
|
|
return desiredGround;
|
|
|
|
|
|
|
|
|
|
// Golden-angle spiral search
|
|
|
|
|
const float GOLDEN_ANGLE = 2.39996323f; // radians (~137.5°)
|
|
|
|
|
for (int i = 1; i <= maxDropProbeSpots; i++)
|
|
|
|
|
{
|
|
|
|
|
float r = i * probeStepDistance;
|
|
|
|
|
float a = i * GOLDEN_ANGLE;
|
|
|
|
|
Vector3 probeXZ = desiredGround + new Vector3(Mathf.Cos(a) * r, 0f, Mathf.Sin(a) * r);
|
|
|
|
|
Vector3 probeGround = ProjectToGround(probeXZ);
|
|
|
|
|
if (!OverlapsZibuCapsule(probeGround, bodyRadius, bodyHeight))
|
|
|
|
|
return probeGround;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fallback to desired ground
|
|
|
|
|
return desiredGround;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private Vector3 ProjectToGround(Vector3 worldPos)
|
|
|
|
|
{
|
|
|
|
|
// Raycast down from above carry height to place on ground
|
|
|
|
|
Vector3 origin = new Vector3(worldPos.x, worldPos.y + 3f, worldPos.z);
|
|
|
|
|
if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, 10f, groundMask, QueryTriggerInteraction.Ignore))
|
|
|
|
|
{
|
|
|
|
|
return hit.point;
|
|
|
|
|
}
|
|
|
|
|
// Fallback: keep same XZ, reduce Y a bit
|
|
|
|
|
return new Vector3(worldPos.x, worldPos.y - 1f, worldPos.z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool OverlapsZibuCapsule(Vector3 groundCenter, float radius, float height)
|
|
|
|
|
{
|
|
|
|
|
// Capsule from feet (at ground) to top at body height
|
|
|
|
|
Vector3 p0 = groundCenter + Vector3.up * 0.05f;
|
|
|
|
|
Vector3 p1 = groundCenter + Vector3.up * Mathf.Max(0.2f, height);
|
|
|
|
|
var cols = Physics.OverlapCapsule(p0, p1, radius, zibuMask, QueryTriggerInteraction.Collide);
|
|
|
|
|
|
|
|
|
|
foreach (var c in cols)
|
|
|
|
|
{
|
|
|
|
|
if (!c || c.attachedRigidbody == null) continue;
|
|
|
|
|
if (c.attachedRigidbody == _targetRb) continue;
|
|
|
|
|
if (c.GetComponentInParent<CubeClash_ZibuController>() != null)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== Post-drop unstack (layer-based) =====
|
|
|
|
|
|
|
|
|
|
private IEnumerator PostDropUnstack(Rigidbody rb, float bodyRadius, float bodyHeight)
|
|
|
|
|
{
|
|
|
|
|
float timer = 0f;
|
|
|
|
|
bool teleportedOnce = false;
|
|
|
|
|
|
|
|
|
|
while (timer < unstackCheckSeconds)
|
|
|
|
|
{
|
|
|
|
|
if (IsStandingOnZibu(rb.position, out RaycastHit hitBelow))
|
|
|
|
|
{
|
|
|
|
|
// Push sideways away from the Zibu beneath
|
|
|
|
|
Vector3 belowCenter = hitBelow.collider.bounds.center;
|
|
|
|
|
Vector3 dir = new Vector3(rb.position.x - belowCenter.x, 0f, rb.position.z - belowCenter.z);
|
|
|
|
|
if (dir.sqrMagnitude < 1e-6f) dir = Random.insideUnitSphere; // avoid zero vector
|
|
|
|
|
dir.y = 0f; dir.Normalize();
|
|
|
|
|
|
|
|
|
|
if (unstackLateralImpulse > 0f)
|
|
|
|
|
rb.AddForce(dir * unstackLateralImpulse, ForceMode.Impulse);
|
|
|
|
|
if (unstackDownImpulse > 0f)
|
|
|
|
|
rb.AddForce(Vector3.down * unstackDownImpulse, ForceMode.Impulse);
|
|
|
|
|
|
|
|
|
|
// Optional teleport fallback if impulses didn't clear in the next check
|
|
|
|
|
if (unstackUseTeleportFallback && !teleportedOnce)
|
|
|
|
|
{
|
|
|
|
|
// Wait a tick to allow the impulse to act
|
|
|
|
|
yield return new WaitForSeconds(unstackCheckInterval * 1.25f);
|
|
|
|
|
timer += unstackCheckInterval * 1.25f;
|
|
|
|
|
|
|
|
|
|
if (IsStandingOnZibu(rb.position, out _))
|
|
|
|
|
{
|
|
|
|
|
Vector3 targetXZ = rb.position + (dir * unstackTeleportStep);
|
|
|
|
|
Vector3 ground = ProjectToGround(targetXZ);
|
|
|
|
|
rb.position = new Vector3(ground.x, ground.y + 0.02f, ground.z);
|
|
|
|
|
teleportedOnce = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yield return new WaitForSeconds(unstackCheckInterval);
|
|
|
|
|
timer += unstackCheckInterval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Final sanity: if we still overlap a Zibu capsule at end, nudge to nearest clear ground spot.
|
|
|
|
|
if (OverlapsZibuCapsule(ProjectToGround(rb.position), bodyRadius, bodyHeight))
|
|
|
|
|
{
|
|
|
|
|
Vector3 fallback = FindClearDropGround(rb.position, bodyRadius, bodyHeight);
|
|
|
|
|
rb.position = new Vector3(fallback.x, fallback.y + 0.02f, fallback.z);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// True if FIRST thing below is on zibuMask (i.e., standing on another Zibu instead of ground)
|
|
|
|
|
private bool IsStandingOnZibu(Vector3 pos, out RaycastHit hit)
|
|
|
|
|
{
|
|
|
|
|
Vector3 origin = pos + Vector3.up * Mathf.Clamp(unstackProbeDown * 0.5f, 0.4f, 1.2f);
|
|
|
|
|
// Check zibu or ground, nearest first
|
|
|
|
|
if (Physics.SphereCast(origin, unstackProbeRadius, Vector3.down, out hit, unstackProbeDown, zibuMask | groundMask, QueryTriggerInteraction.Ignore))
|
|
|
|
|
{
|
|
|
|
|
int layer = hit.collider.gameObject.layer;
|
|
|
|
|
bool firstIsZibu = (zibuMask & (1 << layer)) != 0;
|
|
|
|
|
bool firstIsGround = (groundMask & (1 << layer)) != 0;
|
|
|
|
|
return firstIsZibu && !firstIsGround;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2025-09-03 19:45:23 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void OnDrawGizmosSelected()
|
|
|
|
|
{
|
|
|
|
|
Gizmos.color = Color.cyan;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Gizmos.DrawWireSphere(BirdPos, pickupRadius);
|
|
|
|
|
Gizmos.color = Color.yellow;
|
|
|
|
|
Gizmos.DrawWireSphere(BirdPos, Mathf.Max(pickupRadius, freezeAIWhenWithin));
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
}
|