Building a FSM for my Game AI class using C# in Unity3D game engine. I have 2 simple game objects right now, a Cube (AI) and a bullet (instantiates when a function is called, code below). Just building the foundation for a much more complex FSM, but early in the semester so building it as I'm learning.
My AI shoots out bullets in throws 5 bullets at a time, when bulletCount is 5 then it changes the state. So essentially I just want it to shoot 5 bullets, wait for a time I choose, reload, shoot 5 more, and continue same process. Basically what happens is perfectly what I want it to do first, as soon as it exits my IEnumerator, it shoots an infinite amount of bullets, even though the first time it did what I wanted.
AIClass
using UnityEngine;
using System.Collections;
public class AIClass : MonoBehaviour
{
public GameObject bullet;
int bulletCount;
float stunned;
public enum CombatAIStates
{
Firing = 0,
Stunned = 1,
Reloading = 2,
Following = 3,
Idle = 4
}
public CombatAIStates currentState = CombatAIStates.Firing;
void Update()
{
switch (currentState)
{
case CombatAIStates.Firing:
StartCoroutine (WaitMethod ());
if(bulletCount <= 5)
{
spawnBullets ();
Debug.Log ("Firing. ");
Debug.Log ("Bullet: ");
Debug.Log (bulletCount);
StartCoroutine (WaitMethod ());
++bulletCount;
}
if(bulletCount > 5)
{
currentState = CombatAIStates.Reloading;
}
break;
case CombatAIStates.Stunned:
Debug.Log ("Stunned.");
StartCoroutine(WaitMethod());
currentState = CombatAIStates.Firing;
//currentState = CombatAIStates.Firing;
break;
case CombatAIStates.Reloading:
Debug.Log ("Reloading.");
StartCoroutine (WaitMethod ());
currentState = CombatAIStates.Stunned;
break;
}
}
IEnumerator WaitMethod()
{
float waitTime = 10;
Debug.Log ("Before yield.");
yield return new WaitForSeconds (waitTime);
Debug.Log ("After yield.");
bulletCount = 0;
}
void spawnBullets()
{
Instantiate(bullet, transform.position, transform.rotation);
}
}
I suspect this is what you are trying to do, stripped down slightly for simplicity:
public int BulletCount = 0;
public enum CombatAIStates
{
Firing = 0,
Reloading = 1,
}
CombatAIStates currentState = CombatAIStates.Firing;
// Update is called once per frame
void Update () {
switch (currentState) {
case CombatAIStates.Firing:
if (BulletCount < 5) {
Debug.Log ("Firing: " + BulletCount);
++BulletCount;
} else {
currentState = CombatAIStates.Reloading;
StartCoroutine(Reload ());
}
break;
case CombatAIStates.Reloading:
// Nothing to do here, Reload() coroutine is handling things.
// Maybe play a 10 second animation here or twiddle thumbs
break;
}
}
IEnumerator Reload()
{
yield return new WaitForSeconds (10.0f);
BulletCount = 0;
//Now update the current combat state
currentState = CombatAIStates.Firing;
}
I didn't change much with the original code. I just moved a state change in to a Reload coroutine that switches back to Firing after 10 seconds and resets the bulletCount. Alternatively you could have the state change in the reload case of your switch. But instead of calling a coroutine just check if bulletCount >= 5, if not then reloading is done and you can switch back to firing.
Related
I tried to make a mob spawner which does followings:
1) spawns pre-defined amount of mobs with time interval of choice
2) checks if spawned gameobject destroyed if so spawns new ones till it reaches maximum amount again
Code works but i still think there can be improvements i want it to be mmo like slot spawner with pre defined maximum mob amount and intervals between every spawn
Issues im having:
1) at start works properly by 5 sec intervals between spawns but sometimes after you delete gameobject next spawn in line spawns instantly or very quickly
private void Start()
{
spawnedCount = gameObject.transform.childCount;
if (spawnedCount != 6)
isSpawning = false;
}
private void Update()
{
spawnedCount = gameObject.transform.childCount;
if (!isSpawning && spawnedCount < maxSpawnCount + 1) // check if should start spawn status and if coroutine currently working already isSpawning = false > yes you can can if you want to spawn or not isSpawning = True > no it is already spawning you cant check anymore and send requests
{
isSpawning = true; // set spawning status to true
StartCoroutine(DelayedSpawn(delayInterval));
}
}
IEnumerator DelayedSpawn(float delay)
{
yield return new WaitForSecondsRealtime(delay);
if (spawnedCount <= maxSpawnCount)
{
GetRandomZombie(); // gets random zombie prefab to instantiate
spawnedObj = Instantiate(PrefabToSpawn, new Vector3(gameObject.transform.position.x + Random.Range(-5f, 5), 0, gameObject.transform.position.z + Random.Range(-5f, 5)), transform.rotation);
spawnedObj.transform.parent = gameObject.transform;
}
if (spawnedCount <= maxSpawnCount )
StartCoroutine(DelayedSpawn(delayInterval));
else if (spawnedCount == 6)
{
isSpawning = false;
yield break;
}
}
I would do this like this. It seems cleaner.
Also I would suggest GetRandomZombie() method to return a prefab. That would be cleaner as well.
A reminder: The game object with script that has the coroutine method is disabled/deleted somehow, unity gives an error and coroutine stops. You can solve this like this:
private void OnDisable() //To prevent the error
{
StopAllCoroutines();
}
private void OnEnable() //To start the coroutine again whenever the spawner object is enabled again.
{
StartCoroutine(DelayedSpawn());
}
private IEnumerator DelayedSpawn()
{
while (true)
{
yield return new WaitForSecondsRealtime(delayInterval);
if (gameObject.transform.childCount <= maxSpawnCount)
{
GetRandomZombie(); // gets random zombie prefab to instantiate
spawnedObj = Instantiate(PrefabToSpawn, new Vector3(gameObject.transform.position.x + Random.Range(-5f, 5), 0, gameObject.transform.position.z + Random.Range(-5f, 5)), transform.rotation);
spawnedObj.transform.parent = gameObject.transform;
}
}
}
I can pause my game both during gameplay and the pre-gameplay countdown (3, 2, 1) - both are tracked inside the GameManager as UserIsPlaying, only the countdown is tracked as CountdownIsOn as well (as you will see in the script snippets).
The issue I'm facing is that whenever I'm pausing the game during the countdown and then resume, the countdown restarts (I believe this is due to the fact that I'm hiding it as a gameobject and that gameobject has the Countdown script attached to it).
Here's the relevant part of my PauseMenu script (I'm doing the opposite in the Resume public void counterpart of the same script).
public GameObject pauseMenuUI;
public GameObject hidePauseButtonPM;
public GameObject hidePlayer;
public GameObject hideCountdownPause;
public void Pause ()
{
pauseMenuUI.SetActive(true);
hidePauseButtonPM.SetActive(false);
if (GameManager.CountdownIsOn)
{
hidePlayer.SetActive(false);
hideCountdownPause.SetActive(false);
}
if (GameManager.UserIsPlaying && !GameManager.CountdownIsOn)
{
hidePlayer.SetActive(false);
}
Time.timeScale = 0f;
GameIsPaused = true;
}
This is my Countdown script.
public Color color3;
public Color color2;
public Color color1;
Text countdown;
void OnEnable()
{
countdown = GetComponent<Text>();
countdown.text = "3";
StartCoroutine("Countdown");
}
IEnumerator Countdown()
{
int count = 3;
for (int i = 0; i < count; i++)
{
countdown.text = (count - i).ToString();
if ((count - i) == 3)
{
countdown.color = color3;
}
if ((count - i) == 2)
{
countdown.color = color2;
}
if ((count - i) == 1)
{
countdown.color = color1;
}
yield return new WaitForSeconds(1);
}
StartRound();
}
So my question to you is: how do I hide the countdown during Pauseso that it won't restart on Resume?
Thought (and tried to no avail) of changing the Countdown text's alpha to 0 when the game is paused and back to 255 once resuming. Also tried to attach my Countdown script to my Main Camera and changing Text countdown; to public Text countdown; so I can feed my Countdown text to it, but nothing.
Than you are starting a new Coroutine in OnEnable so everytime the GameObject is set to active again.
You should rather check if your countdown was finished before or not and only restart it if it was finished before.
Additionally you are not stopping the Coroutine when you pause note from Coroutines:
Note: Coroutines are not stopped when a MonoBehaviour is disabled, but only when it is definitely destroyed. You can stop a Coroutine using MonoBehaviour.StopCoroutine and MonoBehaviour.StopAllCoroutines. Coroutines are also stopped when the MonoBehaviour is destroyed.
And since you are using Time.timeScale = 0f; to pause you should use WaitForSecondsRealtime for waiting which isn't affected by the Time.timeScale
public Color color3;
public Color color2;
public Color color1;
private Text countdown;
// moved this to outer scope in order to
// "share" it among routines
int count = 3;
// flag to control whether the last countdown was finished
private bool countdownFinished = true;
private void Awake()
{
// do this only once
countdown = GetComponent<Text>();
}
private void OnEnable()
{
countdown.text = count.ToString();
StartCoroutine(Countdown());
}
private void OnDisable()
{
// just to be sure cancel all running Coroutines
// should be done anyway automatically
StopAllCoroutines();
}
private IEnumerator Countdown()
{
// only restart the countdown if it was finished before
// otherwise continue the last one
if (countdownFinished) count = 3;
countdownFinished = false;
// inverting that for loop would make things a lot
// easier for you
while (count > 0)
{
countdown.text = count.ToString();
// better use a switch for that
switch (count)
{
case 3:
countdown.color = color3;
break;
case 2:
countdown.color = color2;
break;
case 1:
countdown.color = color1;
break;
}
yield return new WaitForSecondsRealtime(1);
count--;
}
countdownFinished = true;
StartRound();
}
Result: Continues if countdown didn't finish before disabling, otherwise starts a new countdown.
for the example I just did
private void StartRound()
{
countdown.text = "GO!";
}
Instead of that switch you could also simply use a
public List<Color> colors = new List<Color>();
add your colors to the list (the most top one beeing color1, the most bottom one beeing color3) and than simply use
countdown.color = colors[count -1];
in my project im trying to count the diferent objects and simulate a little animation, for example i have stars in my game, and i want to count the number of stars in the final of the game from 0 trough the number of stars the user got, so i did this:
public void youWin()
{
audio.Stop ();
StartCoroutine (activatePanel ());
}
IEnumerator activatePanel()
{
yield return new WaitForSeconds (3f);
pausePanel2.SetActive (true);
for (int i = 0; i <= stars; i++) {
yield return new WaitForSeconds (0.2f);
starText2.text = i + "";
}
}
my code worked well for 0.3f on the for loop wait for seconds, but it is too slow, i want it for 0.2f, but something strange happen sometimes it get like a bug and the first number seems to go back, it doesn't count right, someone know what is happening?
It very likely that the activatePanel function is being called from another place while it is already running or the script that contains this code is attached to multiple GameObjects and the activatePanel is again, being called by another function. You can use flag to stop this from happening.
If the coroutine function is already running, use yield break; to break out of it.
bool isRunning = false;
IEnumerator activatePanel()
{
//Exit if already running
if (isRunning)
{
yield break;
}
//Not running, now set isRunning to true then run
isRunning = true;
yield return new WaitForSeconds(3f);
pausePanel2.SetActive(true);
WaitForSeconds waitTime = new WaitForSeconds(0.2f);
for (int i = 0; i <= stars; i++)
{
yield return waitTime;
starText2.text = i.ToString();
}
//Done running, set isRunning to false
isRunning = false;
}
Well i solved it with all of you guys help, actually you all where right, i thaught i was calling the youWin function just 1 time, but i forgot this is unity and i called the youWin inside a trigerEnter function, that means that the object keep enter the triger function and called the youWin function, thank you all here is what i mean with that
Solved it with the bool entered
public class Victory : MonoBehaviour {
Manager gameManager;
// Use this for initialization
public AudioClip clip;
private AudioSource audio;
public Animator girl;
private bool entered;
void OnTriggerEnter(Collider c)
{
if (c.gameObject.tag == "Player" && !entered) {
gameManager.win = true;
audio.clip = clip;
audio.Play ();
gameManager.Ball.GetComponent<MoveBall> ().enabled = true;
girl.SetBool ("win",true);
entered = true;
gameManager.youWin ();
}
}
void Start () {
gameManager = GameObject.Find ("GameController").GetComponent<Manager> ();
audio = GetComponent<AudioSource> ();
entered = false;
}
// Update is called once per frame
void Update () {
}
}
I am writing a small game where objects arrives randomly , I need to implement a logic where I can calculate wait interval for next instantiation as per timer.(As game progresses spawn wait time should decrease).
i am trying to spawn objects in the game those object are having lifetime until next appear , player need to tab fingers as they appear and i am trying to instantiate these fingers faster as game progress
only struggling to get mathematical equation to get value for spawnWait based Time.time . i hope i have written it properly now.
i need equation for graph something like in first Quater(+x,+y) for time.time & spawn wait. although not very much sure.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class GameController : MonoBehaviour {
// Use this for initialization
public GameObject GoodThumb;
public GameObject BadThumb;
public int max = 22;
public float spanWait = 10.0f;
public static bool gameOver = false;
Vector3 theNewPos = Camera.main.ViewportToWorldPoint(new Vector3(0,0,11));
Quaternion theNewRotation= Quaternion.Euler(0,0,0);
void Start () {
StartCoroutine(spawn());
OnGameStart();
}
void Update () {
Time.deltaTime
}
// Update is called once per frame
void Update () {
// SpawnRandom ();
ChooseFinger ();
}
public int ChooseFinger()
{
return Random.Range (1, 5);
}
void OnGameStart()
{
//Debug.Log ("Game Started");
}
IEnumerator spawn() {
GameObject go;
while(true)
{
int randomLoop = Random.Range(2,5);
for (int i = 0; i < randomLoop; i++)
{
pickRandomFinger();
go = Instantiate(GoodThumb);
go.transform.position = theNewPos;
go.transform.rotation = theNewRotation;
// Debug.Log(theNewPos);
// Debug.Log(theNewRotation);
yield return new WaitForSeconds(spanWait);
}
pickRandomFinger();
go = Instantiate(BadThumb);
go.transform.position = theNewPos;
go.transform.rotation = theNewRotation;
yield return new WaitForSeconds(spanWait);
if (gameOver)
{
break;
}
}
}
public void pickRandomFinger()
{
int FingerNo = ChooseFinger();
Debug.Log (FingerNo);
switch (FingerNo)
{
case 1: // finger1 type bottom good
theNewPos = Camera.main.ViewportToWorldPoint(new Vector3(Random.Range(0.0F,1.0F),1.0F, 11));
theNewRotation = Quaternion.Euler(0,0,180);
break;
case 2: // finger4 type right good
theNewPos = Camera.main.ViewportToWorldPoint(new Vector3(1.0F,Random.Range(0.0F,1.0F), 11));
theNewRotation = Quaternion.Euler(0,0,90);
break;
case 3: // finger2 type top good
theNewPos = Camera.main.ViewportToWorldPoint(new Vector3(Random.Range(0.0F,1.0F),0.0F, 11));
theNewRotation = Quaternion.Euler(0,0,0);
break;
case 4: // finger3 type left good
theNewPos = Camera.main.ViewportToWorldPoint(new Vector3(0.0F,Random.Range(0.0F,1.0F),11));
theNewRotation = Quaternion.Euler(0,0,270);
break;
}
}
}
This questions is incredibly vague,
From what I can gather, you want there to be some sort of inverse correlation between the amount of time the user has been in the game and the time between spawns.
There are a million ways to do this, and the correct answer will be completely dependent on the kind of behavior you want the game to exhibit.
timeBetweenSpawns = minTime + max(defaultTime - (timeInGame*constant),0)
This ( very simple and generalized ) code will give you a very simple inverse relationship between time spent in game and the time between items spawning.
You're going to have to think of your own solution and find what works best for your project.
I have a problem. Now I'm developing game in Unity when the condition is score 2. I want to wait for ten seconds before do the code change scene (Application.LoadLevel) (I'm using C# for develop)
But this code when score = 2 It will change to "scenea_5"
It can't wait for ten seconds
void OnTriggerEnter( Collider collider ){
if (collider.name == front_hztleft) {
audio.Play ();
}
if (collider.name == left_hztleft) {
audio.Play ();
score ++;
Debug.Log (string.Format (scoreSyntax, score));
endtime = DateTime.Now.ToString("HH:mm:ss");
InsertResult();
}
if (score == 2) {
StartCoroutine(Waiting());
Application.LoadLevel("scene_a5");
}
}
IEnumerator Waiting()
{
yield return new WaitForSeconds (10);
}
It can run and compile not without error.
Put the scene loading inside the Coroutine.
yield return new WaitForSeconds(10);
Application.LoadLevel("scene_a5");