I have this simulation where the "Hunter" scans the area with the scannereyes object inside the hunter for an object called "Apple" and whenever it sees the apple its supposed to hunt after it. This is the same with other hunters from the same prefab as there are more hunters on the same platform.
The scanner eyes is supposed to send the data of the apple's position so the hunter can run for it. Ive tried putting the found apple object into static but that would resolve into every hunter hunting for the same apple so I tried instead doing this method:
Target = GetComponent<ScannerEyes>().foundobject.transform;
And instead of giving the Hunter the data it needed the whole engine just crashes whenever it tries to compile the C sharp code.
Yes everything is well updated and so is my computer. I have a strong enough computer to compile.
//In the Hunter CS file
void Update()
{
if (reproducerate < 1)
{
if (GameObject.FindGameObjectsWithTag("Food") != null ) //All apple objects have the tag "Food"
{
Target = GetComponent<ScannerEyes>().foundobject.transform;
directiontolookat = (Target.position - transform.position).normalized;
if ((transform.position - Target.position).magnitude > Distans)
{
transform.Translate(directiontolookat * Time.deltaTime * Speed);
}
}
}
}
public void OnCollisionEnter(Collision collision)
{
if (collision.gameObject.tag == "Food")
{
reproducerate += 1;
Destroy(collision.gameObject);
Debug.Log(reproducerate);
}
}
//In the ScannerEyes CS file:
public bool found = false;
public GameObject foundobject;
void Update()
{
//This is only for the scanning sphere to grow overtime cuz thats how scanners work
if (currentsize != maxsize && !found) {
transform.localScale = new Vector3(currentsize, currentsize, currentsize);
currentsize += scanningspeed;
}
if (found)
{
transform.localScale = new Vector3(0f, 0f, 0f);
}
}
private void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Food")
{
Debug.Log("WE FOUND IT");
foundobject = other.gameObject;
Debug.Log("found at" + foundobject.transform.position);
found = true;
}
}
//In the Food behaviour CS file (This is just to make it spawn in the world between -4, 4)
private GameObject foodobject;
public GameObject Foodprefab;
private float Xspawncoords;
private float Zspawncoords;
void Start()
{
Debug.Log("Food is working");
for (int i = 0; i < 2; i++){
Xspawncoords = Random.Range(-4, 4);
Zspawncoords = Random.Range(-4, 4);
Debug.Log(Xspawncoords + " and " + Zspawncoords);
foodobject = Instantiate(Foodprefab,
new Vector3(Xspawncoords, 1.4f, Zspawncoords),
Quaternion.identity);
foodobject.name = "AppleObject";
}
}
I don't understand if the engine crashes when you start the game or when you just save the scripts.
As far as I can see the Hunter checks that there is at least one GameObject with the "food" tag on it, but you are doing this in the Update() method.
This is very bad for performance since you are calling it every frame, and it's pretty expensive as it's constantly checking for every object in the scene.
You can try removing that call and move it in the Start() method, that would call the method only once!
Related
I'm currently working on an N-Body Simulation to try out some new things, one of which being the event system in Unity.
The problem I'm facing is, although it once worked ( I believe perfectly ), it now only activates around 60 times.
What I'm doing is spawning a given amount of Planets in a loop, each time this happens an Event is triggered which sends the current planet's GameObject to a different script which adds it to a List so that it knows on which GameObjects it needs to do calculations.
Here are the two important snippets of code:
public class PlanetSpawnHandler : MonoBehaviour
{
public int startPlanetCount;
public float spawnRadius;
public GameObject planetPrefab;
public float startForce;
GameObject[] sunArray;
//Event and EventArgs
public event EventHandler<OnPlanetSpawnedArgs> OnPlanetSpawned;
public class OnPlanetSpawnedArgs : EventArgs
{
public GameObject planetArg;
}
//Start is called before the first frame update
void Start()
{
sunArray = GameObject.FindGameObjectsWithTag("Sun");
foreach (GameObject sun in sunArray)
{
OnPlanetSpawned?.Invoke(this, new OnPlanetSpawnedArgs { planetArg = sun });
}
for (int o = 0; o < sunArray.Length; o++)
{
float x = sunArray[o].transform.position.x;
float y = sunArray[o].transform.position.y;
float z = sunArray[o].transform.position.z;
for (int i = 0; i < startPlanetCount; i++)
{
//Calculations for Planet Spawning
Vector3 instantiatePos = new Vector3(x - UnityEngine.Random.Range(-spawnRadius, spawnRadius), y - UnityEngine.Random.Range(-spawnRadius, spawnRadius), z - UnityEngine.Random.Range(-spawnRadius, spawnRadius));
GameObject planet = Instantiate(planetPrefab, instantiatePos, Quaternion.identity);
Rigidbody rb = planet.GetComponent<Rigidbody>();
rb.AddForce(startForce * rb.mass, startForce * rb.mass, startForce * rb.mass);
//Triggering the Event
OnPlanetSpawned?.Invoke(this, new OnPlanetSpawnedArgs { planetArg = planet });
//Debug.Log("Spawned Planet");
}
}
}
And the listener:
public class Attraction: MonoBehaviour
{
List<GameObject> planetList = new List<GameObject>();
private void OnEnable()
{
//Subscribing to Event
PlanetSpawnHandler planetSpawnHandler = GetComponent<PlanetSpawnHandler>();
planetSpawnHandler.OnPlanetSpawned += AddPlanetToList;
//Debug.Log(planetSpawnHandler);
}
// Update is called once per frame
private void FixedUpdate()
{
//Shitty code, pls ignore
foreach (GameObject planet in planetList)
{
if (planet == null)
continue;
else
{
foreach (GameObject otherPlanet in planetList)
{
if (planet == otherPlanet)
continue;
else
{
if (otherPlanet == null)
continue;
else
{
Rigidbody planetRb = planet.GetComponent<Rigidbody>();
Rigidbody otherPlanetRb = otherPlanet.GetComponent<Rigidbody>();
Vector3 direction = planetRb.position - otherPlanetRb.position;
float distance = direction.magnitude;
float forceMagnitude = (planetRb.mass * otherPlanetRb.mass) / Mathf.Pow(distance, 2) * 1000f;
Vector3 force = direction.normalized * forceMagnitude;
otherPlanetRb.AddForce(force);
}
}
}
}
}
}
//Function for Event
public void AddPlanetToList(object sender, PlanetSpawnHandler.OnPlanetSpawnedArgs planet )
{
Debug.Log("AddPlanet called");
planetList.Add(planet.planetArg);
Debug.Log(planetList.Count + " In List");
}
As I said, the problem is that the listener only receives 60 messages from the Publisher, although I'm sure it once worked perfectly.
If you need any other information, tell me and I will provide it
The problem has been resolved, issue was with a different script for some reason
Your listener has a function, FixedUpdate, with a comment above it saying Update is called once per frame. Your comment no longer matches the function you're using.
You want to change that function to Update instead:
// Update is called once per frame
private void Update()
{
...
}
FixedUpdate is for frame-independent calculations, and is called 60 times per second. Note that physics calculations often make use of Time.fixedDeltaTime in this function, if that's relevant to you.
Update is called once per frame and usually uses Time.deltaTime.
I work on a game. A part is offline and the other one using network.
I've create a copy of my script to work offline. There is no error on my script (monobehaviour) but when I tried to add the component on my GameObject unity throw me an error :
"Can't add script component 'RocketOffline' because the script class
cannot be found. Make sure that there are no compile errors and that
the file name and class name match"
I don't see any error in my script and names match (RocketOffline.cs VS RocketOffline : MonoBehaviour)
I've tried to re-import my script, re-import all assets and copy all of my code in another script but nothing seem to works...
I've same problem on other script but I'll only show one of them
public class RocketOffline : MonoBehaviour
{
public GameObject Explosion;
private Vector3 Direction;
private Color Couleur;
private float vitesse;
private float multiplieur;
int NumPlayer;
void Explose(Vector3 position)
{
ExplosionOffline instance = Instantiate(Explosion, position, new Quaternion()).GetComponent<ExplosionOffline>();
instance.NumPlayer = NumPlayer;
foreach (var SmokeEffect in GetComponentsInChildren<ParticleSystem>())
{
SmokeEffect.transform.parent = null;
Destroy(SmokeEffect.gameObject, SmokeEffect.startLifetime);
SmokeEffect.Stop();
}
Destroy(gameObject);
}
private void OnCollisionEnter(Collision collision)
{
Explose(transform.position);
}
private void OnTriggerExit(Collider other)
{
GetComponent<Collider>().isTrigger = false;
}
public void SetSpeed(Vector3 _Direction, float _Vitesse, JoueurOffline _Player)
{
Direction = _Direction;
vitesse = _Vitesse;
Couleur = _Player.Couleur;
NumPlayer = _Player.NumPlayer;
}
void Start()
{
Rigidbody rigid = GetComponent<Rigidbody>();
transform.eulerAngles = Vector3.forward * Mathf.Atan2(-Direction.x, Direction.y) * Mathf.Rad2Deg;
rigid.velocity = Direction * vitesse;
multiplieur = transform.localScale.y / 10;
GetComponentInChildren<MeshRenderer>().material.color = Couleur;
}
void Update()
{
GetComponent<Rigidbody>().velocity = transform.up * vitesse;
RaycastHit info;
Physics.Raycast(new Ray(transform.position, GetComponent<Rigidbody>().velocity * Time.deltaTime), out info);
if (info.distance < vitesse * Time.deltaTime * multiplieur/* && info.collider.gameObject != gameObject && info.collider.gameObject != player.gameObject*/)
{
Explose(info.point);
}
}
}
I only want to add my script on my GameObject like usually on Unity.
But there is always the same error.
My script was tested on other project and work perfectly. I think it's probably an internal bug of unity.
Yep, this is potentially a bug of Unity.
If you're using the new 2019 beta, then just do a Bug Report :)
i think you can try to reimport the script or rm the library folder of your project.
First off, I am quite new to scripting so there's probably going to be a few flaws in my script.
So basically, I've made a script for the power up, but once my shot or the player touches the power up coin the fire rate does increase however it won't go back to the normal fire rate after 5 seconds... I have no idea what might be the cause, any advice would be helpful!
using UnityEngine;
using System.Collections;
public class FireRatePowerUp : MonoBehaviour {
private bool isPowerUp = false;
private float powerUpTime = 5.0f;
private PlayerShoot playerShoot;
private void Start()
{
playerShoot = PlayerShoot.FindObjectOfType<PlayerShoot>();
}
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Player" || collision.gameObject.tag == "Projectile")
{
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject);
if (collision.gameObject.tag == "Projectile")
{
Destroy(collision.gameObject);
}
}
}
IEnumerator PowerUpTime()
{
playerShoot.fireRate -= 0.13f;
yield return new WaitForSeconds(powerUpTime);
playerShoot.fireRate += 0.13f;
}
}
I think the issue here is that you're destroying the gameobject this script is attached to (the coin) and by so doing, the script itself is destroyed, therefor its code, coroutine or otherwise won't execute.
StartCoroutine(PowerUpTime());
isPowerUp = true;
Destroy(gameObject); //oops, our script has been destroyed :(
You would have to do this very differently, basically moving the bulk of the code to the PlayerShoot class.
Something like this (this being in PlayerShoot.cs)
public void ActivatePowerupFireRate(float time, float amt) {
StartCoroutine(DoActivatePowerupFireRate(time, amt));
}
public IEnumerator ActivatePowerupFireRate(float time, float amt) {
fireRate -= amt;
yield return WaitForSeconds(time);
fireRate += amt;
}
IEumerator is definately one of the ways you can solve this issue.
However I'm not a fan of them here's my solution if you have a timer in game.
public int timePassed = 0;
public int gotPowerUp = 0;
void Start(){
InvokeRepeating("Timer", 0f, 1.0f);
//Starting at 0 seconds, every second call Timer function.
}
void Timer(){
timePassed++; // +1 second.
}
That way when you obtained the powerup you can set gotPowerUp = timePassed. So you have the exact time when powerup is activated.
then you do something like
if( (gotPowerUp + 5) == timePassed ){
//5 seconds have passed.
//deactivate powerup here
}
The way this game works is that the game starts off with a Largest Ball. When the rocket hits the large ball it splits into two medium balls and then into two small balls. When the rocket hits the smallest ball it gets destroyed.
The problem I'm having is that when the rocket collides with the ball. The rocket gets destroyed, but the ball does "not" divide into two Large balls and so forth.
I just noticed this and I'm wondering if my problem will be fixed if I turn this code statement to == "smallest ball" instead of !=.
if (target.tag == "Rocket")
{
if (gameObject.tag != "Smallest Ball")
{
InstantializeBallsonoff();
}
else {
AudioSource.PlayClipAtPoint(popsounds[Random.Range(0, popsounds.Length)], transform.position);
//play random audio in the popsounds array at current position of ball
gameObject.SetActive(false); //deactivate the gameobject
}
}
}//ontriggerenter
This is the full code for my ball Script
using UnityEngine;
using System.Collections;
public class Ball : MonoBehaviour {
private float forceX, forceY;
private Rigidbody2D ball;
[SerializeField]
private bool moveLeft, moveRight;
[SerializeField]
private GameObject originalBall;
private GameObject ball1, ball2;
private Ball ball1script, ball2script;
[SerializeField]
private AudioClip[] popsounds; //array
// Use this for initialization
void Awake () {
ball = GetComponent<Rigidbody2D>();
ballspeed();
}
// Update is called once per frame
void Update () {
ballmovement();
}
void InstantiatingBalls()
{
if (this.gameObject.tag != "Smallest Ball")
{
ball1 = Instantiate(originalBall); //create copy of originalball into ball1
ball2 = Instantiate(originalBall);
ball1.name = originalBall.name;
ball2.name = originalBall.name;
ball1script = ball1.GetComponent<Ball>(); //get the ball script
ball2script = ball2.GetComponent<Ball>();
}
}//InstantiatingBalls
void InstantializeBallsonoff() {
InstantiatingBalls();
Vector3 temp = transform.position; //start from current ball location
ball1.transform.position = temp;
ball1script.setmoveLeft(true);
ball2.transform.position = temp;
ball2script.setmoveRight(true);
ball1.GetComponent<Rigidbody2D>().velocity = new Vector2(0, 2.5f); //x,y
ball2.GetComponent<Rigidbody2D>().velocity = new Vector2(0, 2.5f); //x,y
AudioSource.PlayClipAtPoint(popsounds[Random.Range(0, popsounds.Length)], transform.position);
//play random audio in the popsounds array at current position of ball
gameObject.SetActive(false); //deactivate the gameobject
}//InstantializeBallsonoff
public void setmoveLeft(bool moveLeft) { //canMoveLeft
this.moveLeft = moveLeft;
this.moveRight = !moveLeft; //moveRight is now false b/c we set moveLeft to true
}
public void setmoveRight(bool moveRight) {//canMoveRight
this.moveRight = moveRight;
this.moveLeft = !moveRight;
}
void ballmovement() {
if (moveLeft) {
Vector3 temp = transform.position; //current position of ball
temp.x -= Time.deltaTime; // represent time per frame
transform.position = temp;
}
if (moveRight) {
Vector3 temp = transform.position; //current position of ball
temp.x += Time.deltaTime; // represent time per frame
transform.position = temp;
}
}
void ballspeed() {
forceX = 2.5f;
switch (this.gameObject.tag) {
//this refers to gameobject that holds this script
case "Largest Ball":
forceY = 11.5f;
break;
case "Large Ball":
forceY = 10.5f;
break;
case "Medium Ball":
forceY = 9f;
break;
case "Small Ball":
forceY = 8f;
break;
case "Smallest Ball":
forceY = 7f;
break;
}//switch
}//ballspeed
void OnTriggerEnter2D (Collider2D target) {
if (target.tag == "Ground") {
ball.velocity = new Vector2(0, forceY);
}
if (target.tag == "Right Wall") {
setmoveLeft(true);
/*moveRight = false;
moveLeft = true;*/
}
if (target.tag == "Left Wall")
{
setmoveRight(true);
/*moveRight = true;
moveLeft = false;*/
}
if (target.tag == "Rocket")
{
if (gameObject.tag != "Smallest Ball")
{
InstantializeBallsonoff();
}
else {
AudioSource.PlayClipAtPoint(popsounds[Random.Range(0, popsounds.Length)], transform.position);
//play random audio in the popsounds array at current position of ball
gameObject.SetActive(false); //deactivate the gameobject
}
}
}//ontriggerenter
}//ball
This is partial of my code where the rocket gets destroyed when it collides with the large ball & the top. This is the other part that I am having trouble with.
void OnTriggerEnter2D(Collider2D target) {
if (target.tag == "Top") {
Destroy(gameObject);
}
string[] ballhit = target.name.Split();
/*array ballhit
split = deletes the space between two words and make it so it takes 2 spaces in the array*/
for (int s = 0; s < ballhit.Length; s++) {
Debug.Log("The array contains: " +ballhit [s]);
if (ballhit.Length > 1)
{ //ball names will always be more than 1 length "Largest Ball"
if (ballhit[1] == "Ball")
{
Destroy(gameObject);
}//destroy object
}//ballhit name length
}// name increments
}//triggerCollider
This is my full Rocket Script
using UnityEngine;
using System.Collections;
public class Rocket : MonoBehaviour {
private Rigidbody2D rocket;
private float speed = 5f;
// Use this for initialization
void Awake () {
rocket = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update () {
rocket.velocity = new Vector2(0, speed); //x, y rocket movement
}
void OnTriggerEnter2D(Collider2D target) {
if (target.tag == "Top") {
Destroy(gameObject);
}
string[] ballhit = target.name.Split();
/*array ballhit
split = deletes the space between two words and make it so it takes 2 spaces in the array*/
for (int s = 0; s < ballhit.Length; s++) {
Debug.Log("The array contains: " +ballhit [s]);
if (ballhit.Length > 1)
{ //ball names will always be more than 1 length "Largest Ball"
if (ballhit[1] == "Ball")
{
Destroy(gameObject);
}//destroy object
}//ballhit name length
}// name increments
}//triggerCollider
}//rocket
Your code is much more complicated than it needs to be an therefore the actual error is difficult to find.
First off, your moveLeft and moveRight bools are mutually exclusive. You need only one of them. I would event prefer to replace both with a public int currentDirection which you set to 1 for right and -1 for left.
Your new ballMovement method could then simply be:
void MoveBall() // C# methods are upper-case by convention
{
transform.position += Vector3.right * currentDirection * Time.deltaTime;
}
The way you check with which type of ball you are colliding is not very safe. I would suggest you use an enum to differentiate the ball sizes.
public enum BallSizes { Largest, Large, Medium, Small, Smallest }; // The different possible values
public BallSize size = BallSizes.Largest; // The balls current size, the biggest size by default
This also allows you to store your y-axis force values in a matching array and access them easily without the use of a switch or else-if:
private float[] forcesY = new float[]{ 11.5f, 10.5f, 9f, 8f, 7f };
void SetBallSpeed()
{
forceX = 2.5f;
forceY = forcesY[(int)size];
}
I think the problem you had with the ball not splitting is is connected to this.
Upon collision, you detected the type of ball you collided with, by examining its tag. But you always Instantiate the new balls using the same prefab, and I never saw you change the tag. So what type of ball are you creating?
If you use the method I described above, you can simply assign a general tag "Ball" and handle the rest with the BallSizes-enum. When creating the new balls you can do the following:
[SerializedField] // The SerializedField property makes it show up in the inspector, even though it is private
private Ball ballPrefab; // Attention: Type "Ball"
public void IsHit() // Method to execute upon rocket collision
{
if(ball.size != BallSizes.Smallest)
{
Ball leftBall = Instantiate(ballPrefab). // Notice that, when you assign you prefab as Type "Ball", you directly get the Ball-Component as the return value.
leftball.direction = -1;
leftball.size = (BallSizes)(size + 1); // Add one to size, to get get the next enum value, which is the next smaller size.
Ball rightBall = .... // Do same stuff for the right ball
}
Destroy(this.gameObject);
}
I don't know how you plan to visualise the type of ball, but you might want to add a float[] ballScales, which you use to shrink the ball's actual size, something like: leftBall.transform.localScale = Vector3.one * ballScales[(int)size].
Finally, another reason your collision does not work might well be, that you handle it in two different places. If you destroy the rocket after it detects collision with the ball, nothing has happend to the ball yet. If the ball is checking for collision afterward it will most likely not find the rocket, because you already destroyed it. A clean way to solve this, would be to let the rocket notify the ball of the collision:
void OnTriggerEnter2D(Collider2D target)
{
if(target.tag == "Top")
Destroy(gameObject);
else if(target.tag == "Ball")
{
target.GetComponent<Ball>().IsHit(); // We know this component should exist when the object is tagged "Ball"
Destroy(gameobject);
}
}
You can then remove that from you ball collision and shorten it to this:
void OnTriggerEnter2D (Collider2D target)
{
if(target.tag == "Ground")
ball.velocity = new Vector2(0, forceY);
if(target.tag == "Right Wall" || target.tag == "Left Wall")
direction *= -1; // Inverts the direction, + <-> -
}
Phew, that was a lot.
I hope this is still relevant and answers more questions than it raises.
Happy coding!
I'm working on an AI script in Unity 5 mostly constructed of coroutines to move around an AI object in my game. It actually works pretty well and is frame independent that way. I'm trying to avoid cluttering the Update function of the class.
However, I'm trying to create a function called wander, where the AI hangs around and randomly chooses a couple of Vector3s in the area of the waypoint and travel to each of them. After that the AI should go to the next waypoint and do the same thing into infinity basically. Collision detection and all that sort is for later parts. I want to get the navigating part fixed first.
void Start(){
agent = GetComponent<NavMeshAgent> ();
collisionRange = GetComponent<CapsuleCollider> ();
collisionRange.radius = detectionRadius;
if (waypoints.Length > 0) {
StartCoroutine (patrol ());
} else {
StartCoroutine (idle (idleTime));
}
}
IEnumerator patrol(){
agent.SetDestination(waypoints[waypointIndex].position);
Debug.Log ("Patrol started, moving to " + agent.destination);
while (agent.pathPending) {
Debug.Log ("not having path");
yield return null;
}
Debug.Log ("I have a path, distance is " + agent.remainingDistance);
while (float.Epsilon < agent.remainingDistance) {
//Debug.Log ("Moving...");
yield return null;
}
StartCoroutine (nextWaypoint ());
}
IEnumerator idle(int time){
Debug.Log ("Idleing for "+ time + " seconds");
agent.Stop ();
yield return new WaitForSeconds(time);
if(waypoints.Length > 2){
agent.Resume ();
StartCoroutine(patrol());
}
}
IEnumerator wander(){
agent.Stop ();
Debug.Log ("Going to look around here for a while.");
Vector3[] points = new Vector3[wanderPoints];
for (int i = 0; i < wanderPoints; i++) {
agent.Stop ();
Vector3 point = Random.insideUnitSphere * wanderRadius;
point.y = transform.position.y;
points [i] = point;
}
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
while (float.Epsilon < agent.remainingDistance) {
Debug.Log ("Moving...");
yield return null;
}
//StartCoroutine(patrol());
yield return null;
}
IEnumerator nextWaypoint(){
Debug.Log ("Arrived at my waypoint at " + transform.position);
if (waypointIndex < waypoints.Length -1) {
waypointIndex +=1;
} else {
waypointIndex = 0;
}
StartCoroutine(wander ());
yield return null;
}
If I swap the wander function with the idle function in nextWaypoint, everything works as expected, but this bit will never work:
agent.ResetPath ();
agent.SetDestination(points[0]);
agent.Resume ();
Debug.Log ("point: " + points [0]);
Debug.Log ("Destination: " + agent.destination);
This is a bit of test code (manually setting only 1 position to go point[0], but it never will travel to that destination. SetDestination will never get updated to the points I want to set them. I've tried calculating paths (NavMeshPath) up front and everything but the destination path will not change or reset weirdly enough. I've also had the while (float.Epsilon < agent.remainingDistance) loop in the wander function as well, but with no luck since it will remain in that loop forever since there's no path to go to.
I might be missing something here, or my logic is wrong in this case. Hopefully someone could give me a little push or maybe some extra debugging options, because I have no idea why the destination doesn't get updated in my wander function.
Consider using triggers on waypoints instead, as this will be even less taxing for the system. Below is an example that will allow you to have a variable number of waypoints easily, by getting all WayPoint type children from a parent transform. The only Coroutine that is used is for idle as you said you didn't want your Update function cluttered.
I've also exposed a couple of additional variables, including a min and max idle time, and a patrol chance, which should be set to a value less than or equal to 1, representing a percentage chance of patrolling vs idle. You can also choose a min and max number of patrol points for the agent to navigate to.
In RandomActivity you can also see that there is error handling for infinitely idle (no WayPoint children under parent). The agent will also not include the current Waypoint in it's list of points to navigate to, and will not navigate to a point already navigated to during it's current patrol (every time an element is added to m_targets, it's removed from m_availableTargets).
AgentController.cs
[RequireComponent(typeof(NavMeshAgent))]
public class AgentController : MonoBehaviour
{
[SerializeField]
private Transform m_waypointParent;
[SerializeField]
private float m_minIdle;
[SerializeField]
private float m_maxIdle;
[SerializeField]
private float m_patrolChance;
[SerializeField]
private int m_minPatrolPoints;
[SerializeField]
private int m_maxPatrolPoints;
private Waypoint[] m_waypoints;
private List<Waypoint> m_availableTargets;
private List<Waypoint> m_targets;
private Waypoint m_tempWaypoint;
private int m_currentTargetIndex;
private NavMeshAgent m_navMeshAgent;
public void Start()
{
m_waypoints = m_waypointParent.GetComponentsInChildren<Waypoint>();
m_targets = new List<Waypoint>();
m_navMeshAgent = GetComponent<NavMeshAgent>();
RandomActivity();
}
private void RandomActivity()
{
if (m_waypoints.Length == 0)
{
Debug.Log("Enemy will idle forever (no waypoints found)");
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
return;
}
if(Random.Range(0f, 1f) <= m_patrolChance)
{
//Available waypoints
m_availableTargets = new List<Waypoint>(m_waypoints);
//Remove currentpoint
if(m_targets.Count > 0)
m_availableTargets.Remove(m_targets[m_targets.Count - 1]);
//Reset list
m_targets.Clear();
m_currentTargetIndex = -1;
//Add patrol points
for (int i = 0; i < Random.Range(m_minPatrolPoints, m_maxPatrolPoints + 1); i++)
{
m_tempWaypoint = m_availableTargets[Random.Range(0, m_availableTargets.Count)];
m_targets.Add(m_tempWaypoint);
m_availableTargets.Remove(m_tempWaypoint);
}
NextWaypoint(null);
}
else
StartCoroutine(Idle(Random.Range(m_minIdle, m_maxIdle)));
}
public void NextWaypoint(Waypoint p_waypoint)
{
//Collided with current waypoint target?
if ((m_currentTargetIndex == -1) || (p_waypoint == m_targets[m_currentTargetIndex]))
{
m_currentTargetIndex++;
if (m_currentTargetIndex == m_targets.Count)
RandomActivity();
else
{
Debug.Log("Target: " + (m_currentTargetIndex + 1) + "/" + m_targets.Count + " (" + m_targets[m_currentTargetIndex].transform.position + ")");
m_navMeshAgent.SetDestination(m_targets[m_currentTargetIndex].transform.position);
}
}
}
private IEnumerator Idle(float p_time)
{
Debug.Log("Idling for " + p_time + "s");
yield return new WaitForSeconds(p_time);
RandomActivity();
}
}
Note that for this to work, I have created a tag called Enemy so as to easily differentiate between enemies and any other tiggers in your game.
WayPoint.cs
[RequireComponent(typeof(BoxCollider))]
public class Waypoint : MonoBehaviour
{
public void OnTriggerEnter(Collider p_collider)
{
if (p_collider.tag == "Enemy")
p_collider.GetComponent<AgentController>().NextWaypoint(this);
}
}
I know this is an old post but hope this helps someone.