375 lines
12 KiB
C#
375 lines
12 KiB
C#
![]() |
/**************************************
|
||
|
Copyright Unluck Software
|
||
|
www.chemicalbliss.com
|
||
|
***************************************/
|
||
|
|
||
|
using UnityEngine;
|
||
|
|
||
|
public class FlockChild : MonoBehaviour
|
||
|
{
|
||
|
[HideInInspector] public FlockController _spawner; // Reference to the flock controller that spawned this bird
|
||
|
[HideInInspector] public Vector3 _wayPoint; // Waypoint used to steer towards
|
||
|
public float _speed; // Current speed of bird
|
||
|
[HideInInspector] public bool _dived = true; // Has recently performed a dive movement
|
||
|
[HideInInspector] public float _stuckCounter; // Prevents looping around a waypoint by increasing minimum distance to waypoint
|
||
|
[HideInInspector] public float _damping; // Damping used for steering (steer speed)
|
||
|
[HideInInspector] public bool _soar = true; // Indicates if this is soaring
|
||
|
[HideInInspector] public bool _landing; // Indicates if bird is landing or sitting idle
|
||
|
[HideInInspector] public float _targetSpeed; // Max bird speed
|
||
|
[HideInInspector] public bool _move = true; // Indicates if bird can fly
|
||
|
|
||
|
public GameObject _model; // Reference to bird model
|
||
|
public Transform _modelT; // Cached model transform
|
||
|
public Transform _thisT; // Cached this transform
|
||
|
|
||
|
// --- Animator replacement for Legacy Animation ---
|
||
|
private Animator _anim; // Animator on _model
|
||
|
// We assume the spawner provides these state names (strings):
|
||
|
// _spawner._flapAnimation, _spawner._soarAnimation, _spawner._idleAnimation
|
||
|
|
||
|
[HideInInspector] public float _avoidValue; // Randomized to reduce uniform avoidance
|
||
|
[HideInInspector] public float _avoidDistance; // Distance from obstacle before starting to avoid
|
||
|
|
||
|
float _soarTimer;
|
||
|
bool _instantiated;
|
||
|
static int _updateNextSeed = 0;
|
||
|
int _updateSeed = -1;
|
||
|
|
||
|
[HideInInspector] public bool _avoid = true;
|
||
|
|
||
|
public Vector3 _landingPosOffset;
|
||
|
|
||
|
public void Start()
|
||
|
{
|
||
|
FindRequiredComponents();
|
||
|
Wander(0.0f);
|
||
|
SetRandomScale();
|
||
|
_thisT.position = findWaypoint();
|
||
|
RandomizeStartAnimationFrame();
|
||
|
InitAvoidanceValues();
|
||
|
_speed = _spawner._minSpeed;
|
||
|
_spawner._activeChildren++;
|
||
|
_instantiated = true;
|
||
|
|
||
|
if (_spawner._updateDivisor > 1)
|
||
|
{
|
||
|
int _updateSeedCap = _spawner._updateDivisor - 1;
|
||
|
_updateNextSeed++;
|
||
|
this._updateSeed = _updateNextSeed;
|
||
|
_updateNextSeed = _updateNextSeed % _updateSeedCap;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Update()
|
||
|
{
|
||
|
// Skip frames (performance batching from original)
|
||
|
if (_spawner._updateDivisor <= 1 || _spawner._updateCounter == _updateSeed)
|
||
|
{
|
||
|
SoarTimeLimit();
|
||
|
CheckForDistanceToWaypoint();
|
||
|
RotationBasedOnWaypointOrAvoidance();
|
||
|
LimitRotationOfModel();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void OnDisable()
|
||
|
{
|
||
|
CancelInvoke();
|
||
|
_spawner._activeChildren--;
|
||
|
}
|
||
|
|
||
|
public void OnEnable()
|
||
|
{
|
||
|
if (_instantiated)
|
||
|
{
|
||
|
_spawner._activeChildren++;
|
||
|
if (_landing)
|
||
|
{
|
||
|
PlayStateSafe(_spawner._idleAnimation, 0.1f);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
PlayStateSafe(_spawner._flapAnimation, 0.1f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void FindRequiredComponents()
|
||
|
{
|
||
|
if (_thisT == null) _thisT = transform;
|
||
|
if (_model == null)
|
||
|
{
|
||
|
var modelTF = _thisT.Find("Model");
|
||
|
if (modelTF != null) _model = modelTF.gameObject;
|
||
|
else _model = gameObject; // fallback if hierarchy differs
|
||
|
}
|
||
|
if (_modelT == null) _modelT = _model.transform;
|
||
|
|
||
|
// Animator (replace Legacy Animation)
|
||
|
_anim = _model.GetComponent<Animator>();
|
||
|
if (_anim == null)
|
||
|
{
|
||
|
// Add a gentle fallback so script doesn't null-ref
|
||
|
_anim = _model.AddComponent<Animator>();
|
||
|
// NOTE: You still need to assign a RuntimeAnimatorController to this Animator in the prefab/inspector
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void RandomizeStartAnimationFrame()
|
||
|
{
|
||
|
// Randomize normalized time (0..1) for current/idle state
|
||
|
string state = string.IsNullOrEmpty(_spawner._flapAnimation) ? _spawner._idleAnimation : _spawner._flapAnimation;
|
||
|
if (!string.IsNullOrEmpty(state))
|
||
|
{
|
||
|
float n = Random.value; // normalized
|
||
|
_anim.Play(state, 0, n);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void InitAvoidanceValues()
|
||
|
{
|
||
|
_avoidValue = Random.Range(.3f, .1f);
|
||
|
if (_spawner._birdAvoidDistanceMax != _spawner._birdAvoidDistanceMin)
|
||
|
{
|
||
|
_avoidDistance = Random.Range(_spawner._birdAvoidDistanceMax, _spawner._birdAvoidDistanceMin);
|
||
|
return;
|
||
|
}
|
||
|
_avoidDistance = _spawner._birdAvoidDistanceMin;
|
||
|
}
|
||
|
|
||
|
public void SetRandomScale()
|
||
|
{
|
||
|
float sc = Random.Range(_spawner._minScale, _spawner._maxScale);
|
||
|
_thisT.localScale = new Vector3(sc, sc, sc);
|
||
|
}
|
||
|
|
||
|
// Soar Timeout - Limits how long a bird can soar
|
||
|
public void SoarTimeLimit()
|
||
|
{
|
||
|
if (this._soar && _spawner._soarMaxTime > 0)
|
||
|
{
|
||
|
if (_soarTimer > _spawner._soarMaxTime)
|
||
|
{
|
||
|
this.Flap();
|
||
|
_soarTimer = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_soarTimer += _spawner._newDelta;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void CheckForDistanceToWaypoint()
|
||
|
{
|
||
|
if (!_landing && (_thisT.position - _wayPoint).magnitude < _spawner._waypointDistance + _stuckCounter)
|
||
|
{
|
||
|
Wander(0.0f);
|
||
|
_stuckCounter = 0.0f;
|
||
|
}
|
||
|
else if (!_landing)
|
||
|
{
|
||
|
_stuckCounter += _spawner._newDelta;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_stuckCounter = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void RotationBasedOnWaypointOrAvoidance()
|
||
|
{
|
||
|
Vector3 lookit = _wayPoint - _thisT.position;
|
||
|
if (_targetSpeed > -1 && lookit != Vector3.zero)
|
||
|
{
|
||
|
Quaternion rotation = Quaternion.LookRotation(lookit);
|
||
|
_thisT.rotation = Quaternion.Slerp(_thisT.rotation, rotation, _spawner._newDelta * _damping);
|
||
|
}
|
||
|
|
||
|
if (_spawner._childTriggerPos)
|
||
|
{
|
||
|
if ((_thisT.position - _spawner._posBuffer).magnitude < 1)
|
||
|
{
|
||
|
_spawner.SetFlockRandomPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_speed = Mathf.Lerp(_speed, _targetSpeed, _spawner._newDelta * 2.5f);
|
||
|
|
||
|
// Position forward based on object rotation
|
||
|
if (_move)
|
||
|
{
|
||
|
_thisT.position += _thisT.forward * _speed * _spawner._newDelta;
|
||
|
if (_avoid && _spawner._birdAvoid)
|
||
|
Avoidance();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool Avoidance()
|
||
|
{
|
||
|
RaycastHit hit = new RaycastHit();
|
||
|
Vector3 fwd = _modelT.forward;
|
||
|
bool r = false;
|
||
|
Quaternion rot = Quaternion.identity;
|
||
|
Vector3 rotE = Vector3.zero;
|
||
|
Vector3 pos = Vector3.zero;
|
||
|
|
||
|
pos = _thisT.position;
|
||
|
rot = _thisT.rotation;
|
||
|
rotE = _thisT.rotation.eulerAngles;
|
||
|
|
||
|
if (Physics.Raycast(_thisT.position, fwd + (_modelT.right * _avoidValue), out hit, _avoidDistance, _spawner._avoidanceMask))
|
||
|
{
|
||
|
rotE.y -= _spawner._birdAvoidHorizontalForce * _spawner._newDelta * _damping;
|
||
|
rot.eulerAngles = rotE;
|
||
|
_thisT.rotation = rot;
|
||
|
r = true;
|
||
|
}
|
||
|
else if (Physics.Raycast(_thisT.position, fwd + (_modelT.right * -_avoidValue), out hit, _avoidDistance, _spawner._avoidanceMask))
|
||
|
{
|
||
|
rotE.y += _spawner._birdAvoidHorizontalForce * _spawner._newDelta * _damping;
|
||
|
rot.eulerAngles = rotE;
|
||
|
_thisT.rotation = rot;
|
||
|
r = true;
|
||
|
}
|
||
|
|
||
|
if (_spawner._birdAvoidDown && !this._landing && Physics.Raycast(_thisT.position, -Vector3.up, out hit, _avoidDistance, _spawner._avoidanceMask))
|
||
|
{
|
||
|
rotE.x -= _spawner._birdAvoidVerticalForce * _spawner._newDelta * _damping;
|
||
|
rot.eulerAngles = rotE;
|
||
|
_thisT.rotation = rot;
|
||
|
pos.y += _spawner._birdAvoidVerticalForce * _spawner._newDelta * .01f;
|
||
|
_thisT.position = pos;
|
||
|
r = true;
|
||
|
}
|
||
|
else if (_spawner._birdAvoidUp && !this._landing && Physics.Raycast(_thisT.position, Vector3.up, out hit, _avoidDistance, _spawner._avoidanceMask))
|
||
|
{
|
||
|
rotE.x += _spawner._birdAvoidVerticalForce * _spawner._newDelta * _damping;
|
||
|
rot.eulerAngles = rotE;
|
||
|
_thisT.rotation = rot;
|
||
|
pos.y -= _spawner._birdAvoidVerticalForce * _spawner._newDelta * .01f;
|
||
|
_thisT.position = pos;
|
||
|
r = true;
|
||
|
}
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
public void LimitRotationOfModel()
|
||
|
{
|
||
|
// Keep model's local X tilt limited; original behavior retained
|
||
|
Quaternion rot = Quaternion.identity;
|
||
|
Vector3 rotE = Vector3.zero;
|
||
|
rot = _modelT.localRotation;
|
||
|
rotE = rot.eulerAngles;
|
||
|
|
||
|
if (((_soar && _spawner._flatSoar) || (_spawner._flatFly && !_soar)) && _wayPoint.y > _thisT.position.y || _landing)
|
||
|
{
|
||
|
rotE.x = Mathf.LerpAngle(_modelT.localEulerAngles.x, -_thisT.localEulerAngles.x, _spawner._newDelta * 1.75f);
|
||
|
rot.eulerAngles = rotE;
|
||
|
_modelT.localRotation = rot;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
rotE.x = Mathf.LerpAngle(_modelT.localEulerAngles.x, 0.0f, _spawner._newDelta * 1.75f);
|
||
|
rot.eulerAngles = rotE;
|
||
|
_modelT.localRotation = rot;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Wander(float delay)
|
||
|
{
|
||
|
if (!_landing)
|
||
|
{
|
||
|
_damping = Random.Range(_spawner._minDamping, _spawner._maxDamping);
|
||
|
_targetSpeed = Random.Range(_spawner._minSpeed, _spawner._maxSpeed);
|
||
|
Invoke("SetRandomMode", delay);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void SetRandomMode()
|
||
|
{
|
||
|
CancelInvoke("SetRandomMode");
|
||
|
if (!_dived && Random.value < _spawner._soarFrequency)
|
||
|
{
|
||
|
Soar();
|
||
|
}
|
||
|
else if (!_dived && Random.value < _spawner._diveFrequency)
|
||
|
{
|
||
|
Dive();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Flap();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Flap()
|
||
|
{
|
||
|
if (_move)
|
||
|
{
|
||
|
PlayStateSafe(_spawner._flapAnimation, 0.5f);
|
||
|
_soar = false;
|
||
|
animationSpeed();
|
||
|
_wayPoint = findWaypoint();
|
||
|
_dived = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Vector3 findWaypoint()
|
||
|
{
|
||
|
Vector3 t = Vector3.zero;
|
||
|
t.x = Random.Range(-_spawner._spawnSphere, _spawner._spawnSphere) + _spawner._posBuffer.x;
|
||
|
t.z = Random.Range(-_spawner._spawnSphereDepth, _spawner._spawnSphereDepth) + _spawner._posBuffer.z;
|
||
|
t.y = Random.Range(-_spawner._spawnSphereHeight, _spawner._spawnSphereHeight) + _spawner._posBuffer.y;
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
public void Soar()
|
||
|
{
|
||
|
if (_move)
|
||
|
{
|
||
|
PlayStateSafe(_spawner._soarAnimation, 1.5f);
|
||
|
_wayPoint = findWaypoint();
|
||
|
_soar = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Dive()
|
||
|
{
|
||
|
// Use soar animation (as original) then offset waypoint down
|
||
|
if (!string.IsNullOrEmpty(_spawner._soarAnimation))
|
||
|
{
|
||
|
PlayStateSafe(_spawner._soarAnimation, 1.5f);
|
||
|
}
|
||
|
// Original per-clip speed tweak removed (Animator replacement)
|
||
|
_wayPoint = findWaypoint();
|
||
|
_wayPoint.y -= _spawner._diveValue;
|
||
|
_dived = true;
|
||
|
}
|
||
|
|
||
|
public void animationSpeed()
|
||
|
{
|
||
|
// Global animator speed to imitate original random clip speeds
|
||
|
if (!_dived && !_landing)
|
||
|
{
|
||
|
_anim.speed = Random.Range(_spawner._minAnimationSpeed, _spawner._maxAnimationSpeed);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_anim.speed = _spawner._maxAnimationSpeed;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// --- Helper to safely crossfade/enter a state by name on Animator ---
|
||
|
void PlayStateSafe(string stateName, float crossfadeDuration)
|
||
|
{
|
||
|
if (_anim == null || string.IsNullOrEmpty(stateName)) return;
|
||
|
|
||
|
// CrossFade expects a hashed state; but using name is fine (Unity hashes internally)
|
||
|
if (crossfadeDuration > 0f)
|
||
|
_anim.CrossFadeInFixedTime(stateName, crossfadeDuration);
|
||
|
else
|
||
|
_anim.Play(stateName, 0, 0f);
|
||
|
}
|
||
|
}
|