218 lines
6.8 KiB
C#
218 lines
6.8 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
[RequireComponent(typeof(CubeClash_ZibuController))]
|
|
public class CubeClash_ZibuAI : MonoBehaviour
|
|
{
|
|
[Header("AI Settings")]
|
|
[Tooltip("Each NPC rolls a speed in this range so they don't mirror each other.")]
|
|
public Vector2 moveSpeedRange = new Vector2(2.5f, 4.5f);
|
|
[Tooltip("Small extra randomization applied when retargeting to keep desynced.")]
|
|
public float retargetSpeedJitter = 0.3f;
|
|
|
|
[HideInInspector] public float moveSpeed; // current per-agent speed (rolled at runtime)
|
|
|
|
public float decisionInterval = 2f;
|
|
public float bumpRange = 2.5f;
|
|
public float roamRadius = 5f;
|
|
|
|
[Header("Arena Bounds")]
|
|
public Transform arenaCube;
|
|
private Bounds arenaBounds;
|
|
|
|
private CubeClash_ZibuController zibuController;
|
|
private Rigidbody rb;
|
|
private CubeClash_ZibuController target;
|
|
private Vector3 roamTarget;
|
|
private float decisionTimer;
|
|
|
|
private bool isBumpCooling = false;
|
|
private CubeClash_ZibuController lastTarget;
|
|
|
|
// ==== external hold via token set (e.g., bird pickup) ====
|
|
private readonly HashSet<object> _externalHolders = new HashSet<object>();
|
|
public bool IsExternallyHeld => _externalHolders.Count > 0;
|
|
|
|
public void BeginPickupHold(object holder)
|
|
{
|
|
if (holder == null) return;
|
|
if (_externalHolders.Add(holder) && rb != null)
|
|
{
|
|
// freeze horizontal immediately, keep gravity
|
|
rb.velocity = new Vector3(0f, rb.velocity.y, 0f);
|
|
}
|
|
}
|
|
|
|
public void EndPickupHold(object holder)
|
|
{
|
|
if (holder == null) return;
|
|
_externalHolders.Remove(holder);
|
|
}
|
|
|
|
public void ForceReleaseAllExternalHolds()
|
|
{
|
|
_externalHolders.Clear();
|
|
}
|
|
// =========================================================
|
|
|
|
void Awake()
|
|
{
|
|
zibuController = GetComponent<CubeClash_ZibuController>();
|
|
rb = GetComponent<Rigidbody>();
|
|
zibuController.isPlayerControlled = false;
|
|
|
|
if (arenaCube != null)
|
|
arenaBounds = new Bounds(arenaCube.position, arenaCube.localScale);
|
|
|
|
moveSpeed = Random.Range(moveSpeedRange.x, moveSpeedRange.y);
|
|
decisionTimer = Random.Range(0f, decisionInterval);
|
|
|
|
PickRoamTarget();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// --------- IMPORTANT CHANGE ---------
|
|
// If externally held by the bird: freeze XZ (so pickup is clean)
|
|
if (IsExternallyHeld)
|
|
{
|
|
if (rb) rb.velocity = new Vector3(0f, rb.velocity.y, 0f);
|
|
return;
|
|
}
|
|
|
|
// If control is locked (e.g., push stun): DO NOTHING, but DON'T touch velocity.
|
|
// This allows physics impulses from bumps to carry the NPC naturally.
|
|
if (zibuController.ControlLocked)
|
|
return;
|
|
// ------------------------------------
|
|
|
|
decisionTimer += Time.deltaTime;
|
|
if (decisionTimer >= decisionInterval)
|
|
{
|
|
decisionTimer = 0f;
|
|
PickTargetOrRoam();
|
|
}
|
|
|
|
if (target != null)
|
|
{
|
|
MoveTowards(target.transform.position);
|
|
TryBumpTarget();
|
|
}
|
|
else
|
|
{
|
|
MoveTowards(roamTarget);
|
|
if (Vector3.Distance(transform.position, roamTarget) < 1f)
|
|
PickRoamTarget();
|
|
}
|
|
}
|
|
|
|
void PickTargetOrRoam()
|
|
{
|
|
List<CubeClash_ZibuController> allZibus = CubeClash_ZibuController.AllZibus;
|
|
if (allZibus.Count > 1)
|
|
{
|
|
List<CubeClash_ZibuController> candidates = new List<CubeClash_ZibuController>();
|
|
foreach (var z in allZibus)
|
|
if (z != zibuController)
|
|
candidates.Add(z);
|
|
|
|
if (candidates.Count > 0)
|
|
{
|
|
if (candidates.Count > 1 && lastTarget != null)
|
|
candidates.Remove(lastTarget);
|
|
|
|
int index = Random.Range(0, candidates.Count);
|
|
target = candidates[index];
|
|
ReRollSpeedSlightly();
|
|
return;
|
|
}
|
|
}
|
|
|
|
target = null;
|
|
PickRoamTarget();
|
|
}
|
|
|
|
void MoveTowards(Vector3 destination)
|
|
{
|
|
// Respect holds/stuns (these are already early-returned in Update)
|
|
Vector3 dir = (destination - transform.position).normalized;
|
|
Vector3 move = new Vector3(dir.x, 0, dir.z) * moveSpeed;
|
|
rb.velocity = new Vector3(move.x, rb.velocity.y, move.z);
|
|
|
|
if (dir.magnitude > 0.1f)
|
|
{
|
|
dir.y = 0;
|
|
Quaternion targetRot = Quaternion.LookRotation(dir, Vector3.up);
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, Time.deltaTime * 5f);
|
|
}
|
|
}
|
|
|
|
void PickRoamTarget()
|
|
{
|
|
if (arenaCube != null)
|
|
{
|
|
arenaBounds = new Bounds(arenaCube.position, arenaCube.localScale);
|
|
float x = Random.Range(arenaBounds.min.x + 1f, arenaBounds.max.x - 1f);
|
|
float z = Random.Range(arenaBounds.min.z + 1f, arenaBounds.max.z - 1f);
|
|
roamTarget = new Vector3(x, transform.position.y, z);
|
|
}
|
|
else
|
|
{
|
|
Vector3 randomOffset = new Vector3(Random.Range(-roamRadius, roamRadius), 0, Random.Range(-roamRadius, roamRadius));
|
|
roamTarget = transform.position + randomOffset;
|
|
}
|
|
|
|
ReRollSpeedSlightly();
|
|
}
|
|
|
|
void TryBumpTarget()
|
|
{
|
|
if (isBumpCooling || target == null) return;
|
|
|
|
float dist = Vector3.Distance(transform.position, target.transform.position);
|
|
if (dist <= bumpRange)
|
|
{
|
|
Vector3 toTarget = (target.transform.position - transform.position).normalized;
|
|
float dot = Vector3.Dot(transform.forward, toTarget);
|
|
if (dot > 0.7f)
|
|
StartCoroutine(BumpWithDelay());
|
|
}
|
|
}
|
|
|
|
IEnumerator BumpWithDelay()
|
|
{
|
|
isBumpCooling = true;
|
|
yield return new WaitForSeconds(Random.Range(0.15f, 0.6f));
|
|
|
|
if (!zibuController.ControlLocked && !IsExternallyHeld)
|
|
zibuController.PerformBump(transform.forward);
|
|
|
|
lastTarget = target;
|
|
target = null;
|
|
PickRoamTarget();
|
|
|
|
yield return new WaitForSeconds(Random.Range(1.2f, 2.5f));
|
|
isBumpCooling = false;
|
|
}
|
|
|
|
void ReRollSpeedSlightly()
|
|
{
|
|
float baseRoll = Random.Range(moveSpeedRange.x, moveSpeedRange.y);
|
|
float jitter = Random.Range(-retargetSpeedJitter, retargetSpeedJitter);
|
|
moveSpeed = Mathf.Clamp(baseRoll + jitter, moveSpeedRange.x, moveSpeedRange.y);
|
|
}
|
|
|
|
void OnDrawGizmosSelected()
|
|
{
|
|
Gizmos.color = Color.yellow;
|
|
Gizmos.DrawWireSphere(transform.position, roamRadius);
|
|
|
|
if (arenaCube != null)
|
|
{
|
|
Gizmos.color = Color.cyan;
|
|
Gizmos.DrawWireCube(arenaCube.position, arenaCube.localScale);
|
|
}
|
|
}
|
|
}
|