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;
}
}
}
Related
Okay, this is probably a dumb question but I'm new to this. I have an enemy AI that walks toward the player only when the enemy is visible to the player and the space key is pressed. I want to make a second if statement that makes the enemy run if the player presses the space bar a second time while the enemy is walking or if the enemy is within 2 meters of the of the players current position.
{
public NavMeshAgent enemy;
public Transform player;
public float speedWalk = 6f;
public float speedRun = 60f;
public float groundDrag;
public float playerHeight;
bool isWalking;
Renderer m_Renderer;
void Move(float speed)
{
enemy.speed = speed;
}
private void Start()
{
m_Renderer = GetComponent<Renderer>();
isWalking = false;
enemy.speed = speedWalk;
}
private void OnBecomeInvisible()
{
enabled = false;
}
//DelayEnemyChase
IEnumerator delayChase()
{
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
}
//Visible by camera
void OnBecameVisible()
{
enabled = true;
//starts walking towards player position
if ((Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible) && (isWalking == false))
{
StartCoroutine(delayChase());
isWalking = true;
}
//starts walking towards player position
else if ((isWalking == true) && (Input.GetKey(KeyCode.Space)) && (m_Renderer.isVisible))
{
Move(speedRun);
enemy.SetDestination(player.position);
isWalking = false;
}
}
private void Update()
{
//sees if enemy is visible + space bar is pressed
OnBecameVisible();
}
}
This is confusing the heck out of me, this is what I have and it's not workign at all. Any help is appreciated!!!!
The main issue is that GetKey is fired every frame as long as the button stays pressed!
You rather want to use GetKeyDown in order to track only the first key press.
Then you currently also start and run multiple concurrent Coroutines!
I would rather use a kind of state routine and do e.g.
private void OnBecomeInvisible()
{
StopAllCoroutines();
enemy.enabled = false;
}
private void OnBecameVisible()
{
enemy.enabled = true;
Move(0f);
enemy.SetDestination(enemy.transform.position);
StartCoroutine (StatesRoutine());
}
private IEnumerator StatesRoutine ()
{
// wait until the space is pressed the first time
// here it depends on what exactly you want to do
// you can either already track if the key is still pressed already
yield return new WaitUntil (() => Input.GetKey(KeyCode.Space));
// or rather wait until the key goes down the first time after having become visible
//yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
yield return new WaitForSeconds(2);
Move(speedWalk);
enemy.SetDestination(player.position);
// Then for the second press we definitely wait until it gets down again instead of
// only checking if the button is still pressed
// except again your use case actually wants that behavior
yield return new WaitUntil (() => Input.GetKeyDown(KeyCode.Space));
Move(speedRun);
enemy.SetDestination(player.position);
}
Some things still depend on your exact needs though, in particular what shall happen if the enemy becomes invisible. For now I assume you wanted to reset the behavior and start the process of handling space clicks from scratch.
I'm making a rhythm game and and working on a script that spawns an object on every beat of a song. Thus, it spawns a single object, waits for the length of the beat, and then spawns the next one. It's supposed to do this until the song finishes. The issue is that the script seems to be spawning everyone of the objects at once, rather than waiting for the specified amount of time. The script compiles fine and I can't figure out what the issue is. Here's my script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class spawnState : MonoBehaviour
{
public float waitTime = 0.483870968f;
public bool running = true;
public int beats = 504;
private int count = 0;
public GameObject spawn;
// Update is called once per frame
void Update()
{
//Generate object
while (running == true)
{
//Wait
StartCoroutine(Wait());
//Count & Stop
count += 1;
if (count >= beats)
{
running = false;
}
}
}
//Wait function
IEnumerator Wait()
{
//Wait
yield return new WaitForSeconds(waitTime);
//Spawn
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
Thank you!
For starters you don't want any possibly blocking while loop within Update!
Update is called once every frame.
Do you really want to instantiate 504 objects within one frame?
And then StartCoroutine does not delay the method which calls it. It rather registers a Coroutine to be running and continues immediately with the rest of the code. So you are starting 504 "parallel" running Coroutines and after 0.48.. seconds you suddenly get all 504 objects instantiated at the same time.
What you rather want to do would e.g. be either directly using a timer field without a Coroutine
private float timer;
void Update()
{
if(!running) return;
timer += Time.deltaTime;
if(timer >= waitTime)
{
timer = 0;
Instantiate(spawn, transform.position, spawn.transform.rotation);
count += 1;
if (count >= beats)
{
running = false;
}
}
}
Or if you want to use a Coroutine you could e.g. simply do
private void Start ()
{
StartCorouine (SpawnRoutine());
}
private IEnumerator SpawnRoutine ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
or if Start is anyway the only place where you trigger this it can be that routine itself
private IEnumerator Start ()
{
for(var count = 0; count < beats; count++)
{
yield return new WaitForSeconds (waitTime);
Instantiate(spawn, transform.position, spawn.transform.rotation);
}
}
I have this code, to Instantiate a sprite
AppleSpawner.cs
public class appleSpawner : MonoBehaviour
{
private int isRunning = 1;
private readonly int[] positions = { -10, -5, 0, 5, 10 };
public int NumberOfSeconds;
System.Random rand = new System.Random();
private void Update()
{
if (isRunning == 1) StartCoroutine(Wait());
}
public IEnumerator Wait()
{
int randomX = rand.Next(5);
isRunning = 0;
yield return new WaitForSeconds(NumberOfSeconds);
Instantiate(this, new Vector3(randomX, 5, 0), transform.rotation);
}
}
Here comes question#1: Is this even the best/easiest way to wait X seconds between instantiating a new copy?
I have a character too, with an oncollision event, if it collides with one of the copys, it should destroy it. This works fine, problem is, if I collide with the last generated copy, it destroys it and then the copys stop spawning.
character.cs
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.tag == "apple")
{
Destroy(collision.gameObject);
points++;
text.text = "points: " + pont;
}
}
Do not mix different responsibilities in one class.
It makes your code bug-prone and increasingly harder to maintain as that one class grows bigger. You should have a spawner, which spawns apples, and an apple prefab with collider. Two different scripts.
public class Spawner : MonoBehaviour {
private static readonly int[] positions = { -10, -5, 0, 5, 10 };
public int NumberOfSeconds;
private System.Random rand = new System.Random();
public GameObject Prefab;
public IEnumerator Start() {
while (true) {
int randomX = positions[rand.Next(5)];
Instantiate(Prefab, new Vector3(randomX, 5, 0), transform.rotation);
yield return new WaitForSeconds(NumberOfSeconds);
}
}
}
Objects with that component should not have colliders or sprite in your case, so they will never be destroyed on collision with player. Prefabs which are spawned are your regular apples which player collect or avoid.
Since the spawner does not produce another spawners, we put spawn logic in a loop.
You probably want random position from array instead of raw random number, so I fixed it.
Generalize.
Now your spawner does the only function - spawning. It's no longer bound to apples, so you can spawn carrots with another spawner. The logic stays the same.
What is this magic in Start?
You can make Start a coroutine. It works just like any other coroutine: runs until yield return, yield break or end of function. The latter two end the coroutine, otherwise it waits, and then continues running. It's better then flags in Update controlling coroutines.
If you need a script that should run every frame, then use Update.
First, Create a new empty game object called "Applespawner". Put Applespawner.cs in it.
Change this
private void Update()
{
if (isRunning == 1) StartCoroutine(Wait());
}
to
private void Start()
{
if (isRunning == 1) StartCoroutine(Wait());
}
You set isRunning to 0 in Awake(), makes no sense to call IEnumerator in Update function.
You should add this line to your Applespawner.cs public GameObject Apple = null;
And attach your Apple prefab to Applespawner.cs's Apple slot in the editor. If you do not know how to make a prefab, google it.
I think there are 2 best way that you can initiate gameobject every X seconds.
First way
public IEnumerator Wait()
{
while(true){
yield return new Waitforseconds(X seconds);
Instantiate an object.
}
}
second way
float spawnRate = 1f;
float nextTimeToSpawn = 0;
private void Update()
{
if (Time.time >= nextTimeToSpawn){
nextTimeToSpawn = Time.time + 1f / spawnRate;
//Spawn something
}
}
I personally prefer the second way. Hope this helps.
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 () {
}
}
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.