- Home /
Coroutine sequence not running properly
Hi hi,
I have a basic Enemy AI script for moving and firing using vector3 coordinates and bools. I'm trying to mangle coroutine sequences so they run in the right order and after a lot of consulting these forums (thanks all!)
I've managed to get the movement working in sequence (and the sequence does loop successfully), however the fire coroutine is not instantiating a new bullet after first initiation (the second one will just return nothing, etc). I'm thinking it's because my sequence isn't resetting the bool to make sure the bullet only fires once. Can't for the life of me figure out why though!
Any help would be much appreciated. I've tried to annotate it where possible (as I am a C# newbie so I need to remind myself constantly!), so apologies if this code is a bit scrappy.
using UnityEngine;
using System.Collections;
public class EnemyAI001 : MonoBehaviour
{
//Me and you
public GameObject iEnemy;
public GameObject youPlayer;
//So I can face you
public Vector3 playernose;
//How fast I travel to my destination
public float approachSpeed;
//How fast I travel on my pre-scripted path after arriving from my origin
public float movementSpeed;
//My Gun
public GameObject weapon;
//My Bullet
public Rigidbody enemyBullet;
//Control the amount I shoot
public bool bulletAlive = false;
//Control how fast I shoot
public float bulletSpeed = 1f;
//Vectors for travel, and bools to dictate which sequence to run
//Origin Vector3 and Bools
Vector3 foclc = new Vector3(0, 0, 150);
bool originBool = true;
bool originBoolCapsule()
{
return originBool && !firstBool && !secondBool && !finalBool;
}
//Destination Vector3 and Bools
Vector3 x0y5z30 = new Vector3(0, 5, 30);
bool firstBool = false;
bool firstBoolCapsule()
{
return !originBool && firstBool && !secondBool && !finalBool;
}
//Second Destination Vector3 and Bools
Vector3 x20y10z15 = new Vector3(20, 10, 15);
bool secondBool = false;
bool secondBoolCapsule()
{
return !originBool && !firstBool && secondBool && !finalBool;
}
//Final Destination Vector3 and Bools
Vector3 xm20y0z45 = new Vector3(-20, 0, 45);
bool finalBool = false;
bool finalBoolCapsule()
{
return !originBool && !firstBool && !secondBool && finalBool;
}
// Assign my identity on Start
void Start ()
{
iEnemy = this.gameObject;
}
public void Update()
{
//Keep Looking at Player
playernose = new Vector3(youPlayer.transform.position.x, youPlayer.transform.position.y, youPlayer.transform.position.z);
iEnemy.transform.LookAt(playernose);
//If I've just spawned
if (originBoolCapsule())
{
StartCoroutine(OriginRunSquence());
}
//If I've reached my first destination, fire and move to my next one
else if (firstBoolCapsule())
{
StartCoroutine(RunSquence1());
}
//If I've reached my second destination, fire and move to my next one
else if (secondBoolCapsule())
{
StartCoroutine(RunSquence2());
}
//If I've reached my final destination, fire and loop back to the start
else if (finalBoolCapsule())
{
StartCoroutine(RunSquence3());
}
}
//Used for movement from spawn tofirst destination
public IEnumerator OriginRunSquence()
{
yield return StartCoroutine(moveToDestination());
}
//Used for sequencing first movement and firing and any other coroutines.
public IEnumerator RunSquence1()
{
yield return StartCoroutine(fire());
yield return new WaitForSeconds(1.0f);
yield return StartCoroutine(firstMove());
}
//Used for sequencing second movement and firing and any other coroutines.
public IEnumerator RunSquence2()
{
yield return StartCoroutine(fire());
yield return new WaitForSeconds(1.0f);
yield return StartCoroutine(secondMove());
}
//Used for sequencing final movement and firing and any other coroutines.
public IEnumerator RunSquence3()
{
yield return StartCoroutine(fire());
yield return new WaitForSeconds(1.0f);
yield return StartCoroutine(finalMove());
}
//Fire bool and fire coroutine
private void reloadBullet()
{
bulletAlive = false;
}
public IEnumerator fire()
{
if (bulletAlive == false)
{
//I'm loading up a bullet
Rigidbody newenemyBullet = Instantiate(enemyBullet, weapon.transform.position, weapon.transform.rotation) as Rigidbody;
//I'm firing a bullet
newenemyBullet.AddForce(iEnemy.transform.forward * bulletSpeed, ForceMode.VelocityChange);
bulletAlive = true;
yield return new WaitForSeconds(0.5f);
}
else if (bulletAlive == true)
{
yield break;
}
}
//Coroutine sequences
//Move to the destination from spawn
public IEnumerator moveToDestination()
{
if (iEnemy.transform.position != x0y5z30)
{
//Move me
iEnemy.transform.position = Vector3.Lerp(iEnemy.transform.position, x0y5z30, approachSpeed * Time.deltaTime);
yield return new WaitForSeconds(0.5f);
}
if (iEnemy.transform.position.z <= 30.5)
{
//Snap me
iEnemy.transform.position = x0y5z30;
yield return new WaitForSeconds(1);
originBool = false;
firstBool = true;
yield break;
}
}
//My first move after arriving at my destination
public IEnumerator firstMove()
{
if (iEnemy.transform.position != x20y10z15)
{
//Move me
iEnemy.transform.position = Vector3.Lerp(iEnemy.transform.position, x20y10z15, movementSpeed * Time.deltaTime);
yield return new WaitForSeconds(0.5f);
}
if (iEnemy.transform.position.z <= 15.2)
{
//Snap me
iEnemy.transform.position = x20y10z15;
yield return new WaitForSeconds(1);
originBool = false;
firstBool = false;
secondBool = true;
finalBool = false;
yield break;
}
}
//My second move
public IEnumerator secondMove()
{
if (iEnemy.transform.position != xm20y0z45)
{
//Move me
iEnemy.transform.position = Vector3.Lerp(iEnemy.transform.position, xm20y0z45, movementSpeed * Time.deltaTime);
yield return new WaitForSeconds(0.5f);
//Debug.Log("Working");
}
if (iEnemy.transform.position.z >= 44.8)
{
//Snap me
iEnemy.transform.position = xm20y0z45;
yield return new WaitForSeconds(1);
originBool = false;
firstBool = false;
secondBool = false;
finalBool = true;
yield break;
}
}
//The last scripted move in my behaviour pattern
public IEnumerator finalMove()
{
if (iEnemy.transform.position != x0y5z30)
{
//Move me
iEnemy.transform.position = Vector3.Lerp(iEnemy.transform.position, x0y5z30, movementSpeed * Time.deltaTime);
yield return new WaitForSeconds(0.5f);
}
if (iEnemy.transform.position.z <= 30.2)
{
//Snap me
iEnemy.transform.position = x0y5z30;
yield return new WaitForSeconds(1);
originBool = true;
firstBool = false;
secondBool = false;
finalBool = false;
yield break;
}
}
}
Answer by tpusch · May 08, 2016 at 07:08 PM
In answer to your question it appears that you never call the function reloadBullet()
, so your fire function always goes through to the else if portion.
public IEnumerator fire()
{
if (bulletAlive == false)
{
....
//Only gets called the first time
}
else if (bulletAlive == true)
{
//Gets called all remaining times
yield break;
}
}
One note, I would suggest using enums and a switch case in your update instead of your complex booleans. It would also make it easier to add more moves in the future if you wanted.
public enum Sequence{origin, first, second, final}
public class EnemyAI001 : MonoBehaviour
{
public Sequence currentStep = Sequence.origin;
...
Update()
{
...
switch(currentStep)
{
case(Sequence.origin):
StartCoroutine(OriginRunSquence());
break;
case(Sequence.first);
...
}
}
Then instead of having to change 4 bools just assign the next enum in the switch case you want to run.
public IEnumerator firstMove()
{
...
if (iEnemy.transform.position.z <= 15.2)
{
//Snap me
iEnemy.transform.position = x20y10z15;
yield return new WaitForSeconds(1);
// instead of these:
//originBool = false;
//firstBool = false;
//secondBool = true;
//finalBool = false;
// it would just be this:
currentStep = Sequence.second;
yield break;
}
}
Ah! Thanks for the advice! Can't believe I missed something so obvious.
I've found a working solution that changes the Coroutines back to standard functions, adds a new fire function (duplicating the old one) for each step and then uses Invoke to trigger them in a row. So ins$$anonymous$$d of the coroutine it now looks like (where fire, first$$anonymous$$ove etc were the previous coroutines):
if (firstBoolCapsule())
{
Invoke("fire1", 1f);
Invoke("reload3", 0.5f);
Invoke("first$$anonymous$$ove", 2f);
//Debug.Log("I'm Still Standing");
}
While I have you, can I just ask quickly if the Enum / Switch & Case route would be better for performance down the line? The game I'm making doesn't have hugely AI patterns (space shooter) so I don't $$anonymous$$d lengthy copy and paste jobs, but if I'm hamstringing performance with complex bool switching, then I'll bear that in $$anonymous$$d too while I implement your suggestion and future coding. :)
Thanks again, tpusch.
As far as performance goes you would need a lot of boolean comparisons before it started making an impact, so even if switch case is faster (which I don't know it it is) it would be negligible.
The main benefit would be if you wanted to say, add a third move between second and final, with the bools you would have to add a new thirdBool = false
in every function as well as an && !thirdBool
in every capsule function. Whereas with a swtich/case you would just need a new case, and change the transition in second$$anonymous$$ove. $$anonymous$$aybe not something worth re-writing what you have, but something to think about in future projects.
Your answer
Follow this Question
Related Questions
Script gets stuck on WaitForSeconds() 1 Answer
Another yield not working problem 2 Answers
Is there any way to stop WaitForSeconds() ? 1 Answer
Understanding yield inside a for or a while loop 2 Answers
Waypoint / Yield help 1 Answer