Followed this tutorial on Flocking and advanced AI. If you fast forward to 32:51 his AI class has Boids (another class he named Boides) with an arrow and all the globals he made public.
I pretty much followed this step by step and for some reason in my Inspector Boids says:
“Boids None(Boids)”
As if I am supposed to apply a gameobject to it or something. It’s like not reading that it’s a class. I made everything public too. When I start game it gives me a Null reference exception (Object reference not set to an instance of an object) (at this line in my code:
“boids.Update(this);”
Also I get a MonoBehavior warning "using the new keyword this is not allowed. MonoBehaviours can only be added using AddComponent.
This I kind of get, but it works in his tut?
and in my globals in my AI class (original class) I have “Boids boids = new Boids();” the same as he put it.
So something is def wrong with “boids” but I don’t know why or what I am doing wrong when I pretty much followed it to a tee.
AI.cs
using UnityEngine;
using System.Collections;
[System.Serializable]
public class AI : MonoBehaviour {
/*
* ***CLEAN CODE - Naming Conventions***
*
* privates = _firstwordSecondword
*
* constants = CAPITALS_WITH_UNDERSCORE
*
* publics = lowercase
*
* properties = EveryWordFirstLetterUpperCase
*
* ***Seperate different data types***
*
* */
private AIState _state;
private float _timer = 0;
private float _currentSpeed;
public float CurrentSpeed {get { return _currentSpeed; } set { _currentSpeed = value; } }
public float turnSpeed = 10; //10 is 2-4 frames per turn, anything above 10 is too fast might crash
public float walkSpeed = 5;
public float runSpeed = 10;
public float distanceUntilFade; //fading between walking and running
public float fadeSpeed; //how quickly you fade between walking and running
//flocking algorithm
public Boids boids = new Boids();
public bool usingAStar; //if true do not go to Wander, and if false do not go to AStarWander
public bool Idle { get; set; } //public to everything, except Unity editor.
private const float DISTANCE_FROM_SELF_FOR_RANDOM = 4f;
private const float DISTANCE_UNTIL_GO_TO_IDLE = 1f;//kind of the same thing as making it public and removing private const so you only have to edit the number once in the code.
private Vector3 _randLoc = Vector3.zero; //creates new vector3 sets each axis to 0 first
// Use this for initialization
public void Start ()
{
_currentSpeed = runSpeed;
}
// Update is called once per frame
public void Update ()
{
switch (_state) //if state is...
{
case AIState.Idle:
_timer -= Time.deltaTime; //if coming into idle will reset timer
if(!Idle) //if not already idle, set timer so we are idle
{
_timer = Random.Range (0f, 2f);
Idle = true;
}
if(_timer <= 0)
{
_state = usingAStar ? AIState.AStarWander : AIState.Wander; //c code, if usingAStar is true state = AStar.Wander, if false state = Wander
Idle = false;
}
break;
case AIState.AStarWander:
break;
case AIState.Wander:
if(_randLoc == Vector3.zero)
{
_randLoc = new Vector3(Random.insideUnitCircle.x,
transform.position.y,
Random.insideUnitCircle.y)
* DISTANCE_FROM_SELF_FOR_RANDOM;
//avoid non-floor level randoom location
_randLoc.y = transform.position.y;
transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation (_randLoc - transform.position), Time.deltaTime * turnSpeed); //doesnt need normalized vector, just a direction
/*if(Vector3.Distance (_randLoc, transform.position) <= distanceUntilFade) //if our distance is less than how far we want to be away until we fade, then fade to walk, otherwise fade to run.
_currentSpeed = Mathf.Lerp (_currentSpeed, walkSpeed, Time.deltaTime * fadeSpeed); //Quat.Slerp = smooth lerp, lerp = linear interprelate
//lerp = go from this to this over this amoutn of time
//slerp = smooth lerp, think of a curve. -|_ not much of a curve, but it enters a turn and exits a turn
//Lerp gives you smoother since i am only changing 1 in the entrance/exits, creates a smoother turn.
//Lerp would be .2 .4 .5 .6 .7 .8 .9 Slerp would be .23 .3454 .356..etc
//For turning use Slerp
else
_currentSpeed = Mathf.Lerp (_currentSpeed, runSpeed, Time.deltaTime * fadeSpeed);*/
_currentSpeed = Mathf.Lerp (_currentSpeed,
Vector3.Distance (_randLoc, transform.position) <= distanceUntilFade ? walkSpeed : runSpeed,
Time.deltaTime * fadeSpeed);
if(Vector3.Distance (transform.position, _randLoc) <= DISTANCE_UNTIL_GO_TO_IDLE)
{
_randLoc = Vector3.zero;
_state = AIState.Idle;
}
}
break;
}
boids.Update(this);
//Movement Controller
if (!Idle) rigidbody.AddRelativeForce (Vector3.forward * _currentSpeed);
if(rigidbody.velocity.magnitude > _currentSpeed * .3f && _currentSpeed > walkSpeed) //if walk speed is low enough it will simply stop you without the && and after
rigidbody.velocity = transform.forward * _currentSpeed;
//Steering Controller
if(Physics.Raycast(transform.position, transform.forward, 2))
{
var direction = transform.right; //var in c# is an anonymous variable, we don't know what it is yet. kind of like a template in c++?
if(Physics.Raycast (transform.position, (transform.forward + transform.right).normalized, 2)) //if ai is going up and right (your current position and ur position going forward and right
//finds us a normalized vector when going up and right
{
direction = -direction;
if(Physics.Raycast (transform.position, (transform.forward - transform.right).normalized, 2)) //there's no transform.left, so we take negative right
direction = -transform.forward; //negative transform.forward would be at this point backward right, meaning left
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), Time.deltaTime * turnSpeed); //rotate by turn speed by timedelttime times turnSpeed
}
}
}
public enum AIState
{
Idle,
AStarWander,
Wander
};
Boids.cs
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
[System.Serializable]
public class Boids : MonoBehaviour {
/*
* ***CLEAN CODE - Naming Conventions***
*
* privates = _firstwordSecondword
*
* constants = CAPITALS_WITH_UNDERSCORE
*
* publics = lowercase
*
* properties = EveryWordFirstLetterUpperCase
*
* ***Seperate different data types***
*
* */
public string tagToSearch; //anything that has this tag, we will search for
public float searchRadius; //obviously only searching within this radius
public float seperationRadius;
public AI leader;
public List<AI> boidGroup = new List<AI>();
public BoidState boidState = BoidState.Ignore;
// Use this for initialization
void Start ()
{
}
// Update is called once per frame
public void Update (AI ai)
{
var goa = GameObject.FindGameObjectsWithTag (tagToSearch);
/* foreach(var gameObject in goa)
{
if (Vector3.Distance (gameObject.transform.position, ai.transform.position) <= searchRadius)
{
if(gameObject.GetComponent<AI>() && !boidGroup.Contains(gameObject.GetComponent<AI>()))
boidGroup.Add (gameObject.GetComponent<AI>());
}
} SAME THING BELOW */
foreach(var gameObject in goa.Where(gameObject => Vector3.Distance (gameObject.transform.position, ai.transform.position) <= searchRadius).Where(gameObject => gameObject.GetComponent<AI>() && !boidGroup.Contains(gameObject.GetComponent<AI>()))) {
boidGroup.Add(gameObject.GetComponent<AI>());
}
if(leader == null && boidGroup.Count > 0)
{
leader = boidGroup[Random.Range (0, boidGroup.Count)];
foreach(var boid in boidGroup)
{
if(boid.boids.leader != leader && boid.boids.leader != null) leader = boid.boids.leader;
}
}
if(leader == ai) //consolidating leader to 1 boid
{
boidState = BoidState.Ignore;
foreach (var boid in boidGroup.Where (boid => boid.boids.leader != ai).Where (boid => boid.boids.leader == boid || boid.boids.leader == null)) {//if were not the leader, then if the leader is himself or null we will assign
//boid.boids.leader = ai;
boid.boids.leader = ai;
return;
}
}
foreach(var boid in boidGroup) //for each boid were going to set their leader to our leader if we are not leader
boid.boids.leader = leader;
if(leader != null)
{
//ai.astar = null;
if(boidState == BoidState.Ignore)
boidState = 0;
}
//else ai.aStar = null;
ai.Idle = leader.Idle;
switch(boidState)
{
case BoidState.Alignment:
ai.transform.rotation = Quaternion.Slerp (ai.transform.rotation, leader.transform.rotation, Time.deltaTime * ai.turnSpeed);
ai.CurrentSpeed = leader.CurrentSpeed;
if(Vector3.Distance (ai.transform.position, leader.transform.position) <= seperationRadius /2)
boidState = BoidState.Seperation;
if(Vector3.Distance(ai.transform.position, leader.transform.position) >= seperationRadius)
boidState = BoidState.Cohesion;
break;
case BoidState.Cohesion:
ai.CurrentSpeed = ai.runSpeed;
ai.transform.rotation = Quaternion.Slerp (ai.transform.rotation, Quaternion.LookRotation((leader.transform.position - leader.transform.forward * -1) - ai.transform.position), Time.deltaTime * ai.turnSpeed);
//1 behind leaders position, 1 minus our position ^
if(Vector3.Distance (ai.transform.position, leader.transform.position) < seperationRadius - .5f) //making sure boids are within radius not on the line
boidState = 0;
break;
case BoidState.Seperation:
ai.CurrentSpeed = ai.runSpeed;
ai.transform.rotation = Quaternion.Slerp (ai.transform.rotation, Quaternion.LookRotation(ai.transform.position - ai.transform.position), Time.deltaTime * ai.turnSpeed);
if(Vector3.Distance (ai.transform.position, leader.transform.position) >= seperationRadius /2)
boidState = 0;
break;
case BoidState.Ignore:
break;
}
/*foreach(var boid in boidGroup)
{
if(Vector3.Distance (boid.transform.position, ai.transform.position) <= seperationRadius /2)
ai.rigidbody.AddForce ((ai.transform.position - boid.transform.position).normalized * ai.runSpeed * Time.deltaTime); //if within radius it's walking or pushing away from other boids
}*/
foreach(var boid in boidGroup.Where (boid => Vector3.Distance (boid.transform.position, ai.transform.position) <= seperationRadius / 2)){
ai.rigidbody.AddForce((ai.transform.position - boid.transform.position).normalized * ai.runSpeed * Time.deltaTime);
}
}
}
public enum BoidState
{
Alignment = 0,
Cohesion = 1,
Seperation = 2,
Ignore = -1
};