You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
11 KiB
C#
328 lines
11 KiB
C#
/**************************************
|
|
Copyright 2015 Unluck Software
|
|
www.chemicalbliss.com
|
|
***************************************/
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
public class SchoolChild:MonoBehaviour{
|
|
[HideInInspector]
|
|
public SchoolController _spawner;
|
|
Vector3 _wayPoint;
|
|
[HideInInspector]
|
|
public float _speed= 10.0f; //Fish Speed
|
|
float _stuckCounter; //prevents looping around a waypoint
|
|
float _damping; //Turn speed
|
|
Transform _model; //Model with animations
|
|
float _targetSpeed; //Fish target speed
|
|
float tParam = 0.0f; //
|
|
float _rotateCounterR; //Used to increase avoidance speed over time
|
|
float _rotateCounterL;
|
|
public Transform _scanner; //Scanner object used for push, this rotates to check for collisions
|
|
bool _scan = true;
|
|
bool _instantiated; //Has this been instantiated
|
|
static int _updateNextSeed = 0; //When using frameskip seed will prevent calculations for all fish to be on the same frame
|
|
int _updateSeed = -1;
|
|
[HideInInspector]
|
|
public Transform _cacheTransform;
|
|
|
|
#if UNITY_EDITOR
|
|
public static bool _sWarning;
|
|
#endif
|
|
|
|
public void Start(){
|
|
//Check if there is a controller attached
|
|
if(_cacheTransform == null) _cacheTransform = transform;
|
|
if(_spawner != null){
|
|
SetRandomScale();
|
|
LocateRequiredChildren();
|
|
RandomizeStartAnimationFrame();
|
|
SkewModelForLessUniformedMovement();
|
|
_speed = Random.Range(_spawner._minSpeed, _spawner._maxSpeed);
|
|
Wander(0.0f);
|
|
SetRandomWaypoint();
|
|
CheckForBubblesThenInvoke();
|
|
_instantiated = true;
|
|
GetStartPos();
|
|
FrameSkipSeedInit();
|
|
_spawner._activeChildren++;
|
|
return;
|
|
}
|
|
|
|
this.enabled = false;
|
|
Debug.Log(gameObject + " found no school to swim in: " + this + " disabled... Standalone fish not supported, please use the SchoolController");
|
|
}
|
|
|
|
public void Update() {
|
|
//Skip frames
|
|
if (_spawner._updateDivisor <=1 || _spawner._updateCounter == _updateSeed){
|
|
CheckForDistanceToWaypoint();
|
|
RotationBasedOnWaypointOrAvoidance();
|
|
ForwardMovement();
|
|
RayCastToPushAwayFromObstacles();
|
|
SetAnimationSpeed();
|
|
}
|
|
}
|
|
|
|
public void FrameSkipSeedInit(){
|
|
if(_spawner._updateDivisor > 1){
|
|
int _updateSeedCap = _spawner._updateDivisor -1;
|
|
_updateNextSeed++;
|
|
this._updateSeed = _updateNextSeed;
|
|
_updateNextSeed = _updateNextSeed % _updateSeedCap;
|
|
}
|
|
}
|
|
|
|
public void CheckForBubblesThenInvoke() {
|
|
if(_spawner._bubbles != null)
|
|
InvokeRepeating("EmitBubbles", (_spawner._bubbles._emitEverySecond*Random.value)+1 , _spawner._bubbles._emitEverySecond);
|
|
}
|
|
|
|
public void EmitBubbles(){
|
|
_spawner._bubbles.EmitBubbles(_cacheTransform.position, _speed);
|
|
}
|
|
|
|
public void OnDisable() {
|
|
CancelInvoke();
|
|
_spawner._activeChildren--;
|
|
}
|
|
|
|
public void OnEnable() {
|
|
if(_instantiated){
|
|
CheckForBubblesThenInvoke();
|
|
_spawner._activeChildren++;
|
|
}
|
|
}
|
|
|
|
public void LocateRequiredChildren(){
|
|
if(_model == null) _model = _cacheTransform.Find("Model");
|
|
if(_scanner == null){
|
|
_scanner = new GameObject().transform;
|
|
_scanner.parent = this.transform;
|
|
_scanner.localRotation = Quaternion.identity;
|
|
_scanner.localPosition = Vector3.zero;
|
|
#if UNITY_EDITOR
|
|
if(!_sWarning){
|
|
Debug.Log("No scanner assigned: creating... (Increase instantiate performance by manually creating a scanner object)");
|
|
_sWarning = true;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public void SkewModelForLessUniformedMovement() {
|
|
// Adds a slight rotation to the model so that the fish get a little less uniformed movement
|
|
Quaternion rx = Quaternion.identity;
|
|
rx.eulerAngles = new Vector3(0.0f, 0.0f , (float)Random.Range(-25, 25));
|
|
_model. rotation = rx;
|
|
}
|
|
|
|
public void SetRandomScale(){
|
|
float sc = Random.Range(_spawner._minScale, _spawner._maxScale);
|
|
_cacheTransform.localScale=Vector3.one*sc;
|
|
}
|
|
|
|
public void RandomizeStartAnimationFrame(){
|
|
foreach(AnimationState state in _model.GetComponent<Animation>()) {
|
|
state.time = Random.value * state.length;
|
|
}
|
|
}
|
|
|
|
public void GetStartPos(){
|
|
//-Vector is to avoid zero rotation warning
|
|
_cacheTransform.position = _wayPoint - new Vector3(.1f,.1f,.1f);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//Uses scanner to push away from obstacles
|
|
public void RayCastToPushAwayFromObstacles() {
|
|
if(_spawner._push){
|
|
RotateScanner();
|
|
RayCastToPushAwayFromObstaclesCheckForCollision();
|
|
}
|
|
}
|
|
|
|
public void RayCastToPushAwayFromObstaclesCheckForCollision() {
|
|
RaycastHit hit = new RaycastHit();
|
|
float d = 0.0f;
|
|
Vector3 cacheForward = _scanner.forward;
|
|
if (Physics.Raycast(_cacheTransform.position, cacheForward, out hit, _spawner._pushDistance, _spawner._avoidanceMask)){
|
|
SchoolChild s = null;
|
|
s = hit.transform.GetComponent<SchoolChild>();
|
|
d = (_spawner._pushDistance - hit.distance)/_spawner._pushDistance; // Equals zero to one. One is close, zero is far
|
|
if(s != null){
|
|
_cacheTransform.position -= cacheForward*_spawner._newDelta*d*_spawner._pushForce;
|
|
}
|
|
else{
|
|
_speed -= .01f*_spawner._newDelta;
|
|
if(_speed < .1f)
|
|
_speed = .1f;
|
|
_cacheTransform.position -= cacheForward*_spawner._newDelta*d*_spawner._pushForce*2;
|
|
//Tell scanner to rotate slowly
|
|
_scan = false;
|
|
}
|
|
}else{
|
|
//Tell scanner to rotate randomly
|
|
_scan = true;
|
|
}
|
|
}
|
|
|
|
public void RotateScanner() {
|
|
//Scan random if not pushing
|
|
if(_scan){
|
|
_scanner.rotation = Random.rotation;
|
|
return;
|
|
}
|
|
//Scan slow if pushing
|
|
_scanner.Rotate(new Vector3(150*_spawner._newDelta,0.0f,0.0f));
|
|
}
|
|
|
|
public bool Avoidance() {
|
|
//Avoidance () - Returns true if there is an obstacle in the way
|
|
if(!_spawner._avoidance)
|
|
return false;
|
|
RaycastHit hit = new RaycastHit();
|
|
float d = 0.0f;
|
|
Quaternion rx = _cacheTransform.rotation;
|
|
Vector3 ex = _cacheTransform.rotation.eulerAngles;
|
|
Vector3 cacheForward = _cacheTransform.forward;
|
|
Vector3 cacheRight = _cacheTransform.right;
|
|
//Up / Down avoidance
|
|
if (Physics.Raycast(_cacheTransform.position, -Vector3.up+(cacheForward*.1f), out hit, _spawner._avoidDistance, _spawner._avoidanceMask)){
|
|
//Debug.DrawLine(_cacheTransform.position,hit.point);
|
|
d = (_spawner._avoidDistance - hit.distance)/_spawner._avoidDistance;
|
|
ex.x -= _spawner._avoidSpeed*d*_spawner._newDelta*(_speed +1);
|
|
rx.eulerAngles = ex;
|
|
_cacheTransform.rotation = rx;
|
|
}
|
|
if (Physics.Raycast(_cacheTransform.position, Vector3.up+(cacheForward*.1f), out hit, _spawner._avoidDistance, _spawner._avoidanceMask)){
|
|
//Debug.DrawLine(_cacheTransform.position,hit.point);
|
|
d = (_spawner._avoidDistance - hit.distance)/_spawner._avoidDistance;
|
|
ex.x += _spawner._avoidSpeed*d*_spawner._newDelta*(_speed +1);
|
|
rx.eulerAngles = ex;
|
|
_cacheTransform.rotation = rx;
|
|
}
|
|
|
|
//Crash avoidance //Checks for obstacles forward
|
|
if (Physics.Raycast(_cacheTransform.position, cacheForward+(cacheRight*Random.Range(-.1f, .1f)), out hit, _spawner._stopDistance, _spawner._avoidanceMask)){
|
|
// Debug.DrawLine(_cacheTransform.position,hit.point);
|
|
d = (_spawner._stopDistance - hit.distance)/_spawner._stopDistance;
|
|
ex.y -= _spawner._avoidSpeed*d*_spawner._newDelta*(_targetSpeed +3);
|
|
rx.eulerAngles = ex;
|
|
_cacheTransform.rotation = rx;
|
|
_speed -= d*_spawner._newDelta*_spawner._stopSpeedMultiplier*_speed;
|
|
if(_speed < 0.01f){
|
|
_speed = 0.01f;
|
|
}
|
|
return true;
|
|
}else if (Physics.Raycast(_cacheTransform.position, cacheForward+(cacheRight*(_spawner._avoidAngle+_rotateCounterL)), out hit, _spawner._avoidDistance, _spawner._avoidanceMask)){
|
|
// Debug.DrawLine(_cacheTransform.position,hit.point);
|
|
d = (_spawner._avoidDistance - hit.distance)/_spawner._avoidDistance;
|
|
_rotateCounterL+=.1f;
|
|
ex.y -= _spawner._avoidSpeed*d*_spawner._newDelta*_rotateCounterL*(_speed +1);
|
|
rx.eulerAngles = ex;
|
|
_cacheTransform.rotation = rx;
|
|
if(_rotateCounterL > 1.5f)
|
|
_rotateCounterL = 1.5f;
|
|
_rotateCounterR = 0.0f;
|
|
return true;
|
|
}else if (Physics.Raycast(_cacheTransform.position, cacheForward+(cacheRight*-(_spawner._avoidAngle+_rotateCounterR)), out hit, _spawner._avoidDistance, _spawner._avoidanceMask)){
|
|
// Debug.DrawLine(_cacheTransform.position,hit.point);
|
|
d = (_spawner._avoidDistance - hit.distance)/_spawner._avoidDistance;
|
|
if(hit.point.y < _cacheTransform.position.y){
|
|
ex.y -= _spawner._avoidSpeed*d*_spawner._newDelta*(_speed +1);
|
|
}
|
|
else{
|
|
ex.x += _spawner._avoidSpeed*d*_spawner._newDelta*(_speed +1);
|
|
}
|
|
_rotateCounterR +=.1f;
|
|
ex.y += _spawner._avoidSpeed*d*_spawner._newDelta*_rotateCounterR*(_speed +1);
|
|
rx.eulerAngles = ex;
|
|
_cacheTransform.rotation = rx;
|
|
if(_rotateCounterR > 1.5f)
|
|
_rotateCounterR = 1.5f;
|
|
_rotateCounterL = 0.0f;
|
|
return true;
|
|
}else{
|
|
_rotateCounterL = 0.0f;
|
|
_rotateCounterR = 0.0f;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public void ForwardMovement(){
|
|
_cacheTransform.position += _cacheTransform.TransformDirection(Vector3.forward)*_speed*_spawner._newDelta;
|
|
if (tParam < 1) {
|
|
if(_speed > _targetSpeed){
|
|
tParam += _spawner._newDelta * _spawner._acceleration;
|
|
}else{
|
|
tParam += _spawner._newDelta * _spawner._brake;
|
|
}
|
|
_speed = Mathf.Lerp(_speed, _targetSpeed,tParam);
|
|
}
|
|
}
|
|
|
|
public void RotationBasedOnWaypointOrAvoidance(){
|
|
Quaternion rotation = Quaternion.identity;
|
|
rotation = Quaternion.LookRotation(_wayPoint - _cacheTransform.position);
|
|
if(!Avoidance()){
|
|
_cacheTransform.rotation = Quaternion.Slerp(_cacheTransform.rotation, rotation, _spawner._newDelta * _damping);
|
|
}
|
|
//Limit rotation up and down to avoid freaky behavior
|
|
float angle = _cacheTransform.localEulerAngles.x;
|
|
angle = (angle > 180) ? angle - 360 : angle;
|
|
Quaternion rx = _cacheTransform.rotation;
|
|
Vector3 rxea = rx.eulerAngles;
|
|
rxea.x = ClampAngle(angle, -50.0f , 50.0f);
|
|
rx.eulerAngles = rxea;
|
|
_cacheTransform.rotation = rx;
|
|
}
|
|
|
|
public void CheckForDistanceToWaypoint(){
|
|
if((_cacheTransform.position - _wayPoint).magnitude < _spawner._waypointDistance+_stuckCounter){
|
|
Wander(0.0f); //create a new waypoint
|
|
_stuckCounter=0.0f;
|
|
CheckIfThisShouldTriggerNewFlockWaypoint();
|
|
return;
|
|
}
|
|
_stuckCounter+=_spawner._newDelta*(_spawner._waypointDistance*.25f);
|
|
}
|
|
|
|
public void CheckIfThisShouldTriggerNewFlockWaypoint(){
|
|
if(_spawner._childTriggerPos){
|
|
_spawner.SetRandomWaypointPosition();
|
|
}
|
|
}
|
|
|
|
public static float ClampAngle(float angle,float min,float max) {
|
|
if (angle < -360)angle += 360.0f;
|
|
if (angle > 360)angle -= 360.0f;
|
|
return Mathf.Clamp (angle, min, max);
|
|
}
|
|
|
|
public void SetAnimationSpeed(){
|
|
foreach(AnimationState state in _model.GetComponent<Animation>()) {
|
|
state.speed = (Random.Range(_spawner._minAnimationSpeed, _spawner._maxAnimationSpeed)*_spawner._schoolSpeed*this._speed)+.1f;
|
|
}
|
|
}
|
|
|
|
public void Wander(float delay){
|
|
_damping = Random.Range(_spawner._minDamping, _spawner._maxDamping);
|
|
_targetSpeed = Random.Range(_spawner._minSpeed, _spawner._maxSpeed)*_spawner._speedCurveMultiplier.Evaluate(Random.value)*_spawner._schoolSpeed;
|
|
Invoke("SetRandomWaypoint", delay);
|
|
}
|
|
|
|
public void SetRandomWaypoint(){
|
|
tParam = 0.0f;
|
|
_wayPoint = findWaypoint();
|
|
}
|
|
}
|