Restart obj number on enable - c#

I am spawning objects on startup,(maxObj = 75) then destroying Obj's on event and disabling spawner Obj. When player wants they can re enable spawner. I need count to start at 0 on enable. Another 75 obj's spawn and are then destroyed. etc. Appreciate any help thanks.
enter code here
private static readonly float _spawnTime = 0.125f;
[SerializeField]
private GameObject _asteroidObject = null;
[SerializeField]
private int _maxObjects = 0;
private int _spawnedObjects = 0;
private float _time = 0;
private void Update()
{
if(_spawnedObjects < _maxObjects)
{
if(_time > _spawnTime)
{
Instantiate(_asteroidObject, transform.position, Quaternion.identity);
++_spawnedObjects;
_time = 0;
}
_time += Time.smoothDeltaTime;
}
}

Touch it is quite unclear how a User shall be able to start the spawn again I would recommend to rather use a Coroutine in general. They are like little temporary Update blocks but easier to control and maintain. It is also more efficient since it doesn't call the Update method every frame also when the maxObjects amount is already reached and nothing is going to happen anyway
[SerializeField]
private GameObject _asteroidPrefab;
[SerializeField]
private float _spawnInterval = 0.125f;
[SerializeField]
private int _maxObjects;
private bool _canStart = true;
// However your player calls this method
public void StartSpawn()
{
// Only start spawning if there is no other spawn routine already running
if(_canStart) StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
// Just in case ignore if another routine is already running
if(!_canStart) yield break;
// block concurrent routines
_canStart = false;
var interval = new WaitForSeconds(_spawnInterval);
for(var i = 0; i < _maxObjects; i++)
{
Instantiate(_asteroidObject, transform.position, Quaternion.identity);
// yield makes Unity pause this routine and render the frame
// and continue from here in the next frame
// Then since we yield return another IEnumerator in thi case WaitForSconds
// it results exactly in this: Holds here for given seconds and then goes to the next iteration
yield return interval;
}
// re-enable the StartSpawn
_canStart = true;
}
Then in case you additionally also want to automatically start spawning in the beginning you can simply call it in
private void Start()
{
StartSpawn();
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnObj : MonoBehaviour
{
[SerializeField]
private GameObject _asteroidPrefab;
[SerializeField]
private float _spawnInterval = 0.125f;
[SerializeField]
private int _maxObjects;
private bool _canStart = true;
private void Start()
{
StartSpawn();
}
// However your player calls this method
public void StartSpawn()
{
// Only start spawning if there is no other spawn routine already running
if (_canStart) StartCoroutine(Spawn());
}
private IEnumerator Spawn()
{
// Just in case ignore if another routine is already running
if (!_canStart) yield break;
// block concurrent routines
_canStart = false;
var interval = new WaitForSeconds(_spawnInterval);
for (var i = 0; i < _maxObjects; i++)
{
Instantiate(_asteroidPrefab, transform.position, Quaternion.identity);
// yield makes Unity pause this routine and render the frame
// and continue from here in the next frame
// Then since we yield return another IEnumerator in thi case WaitForSconds
// it results exactly in this: Holds here for given seconds and then goes to the next iteration
yield return interval;
}
// re-enable the StartSpawn
_canStart = true;
}
}

Related

How to fix Unity List error ArgumentOutOfRangeException?

I have a GameObject that adds and removes other valid GameObjects to a List as they drift through a collider trigger. Under certain conditions it will select an object in the List at random and replace it with another GameObject. It works fine for the most part but occasionally it will give an ArgumentOutOfRangeException error. Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BubbleScriptNecroGrab : MonoBehaviour
{
[SerializeField] public List<GameObject> bubbleOnes = new List<GameObject>();
[SerializeField] public List<GameObject> necroAcolytes = new List<GameObject>();
[SerializeField] GameObject activeBubble;
[SerializeField] GameObject necroAcolyte;
bool necroReady = true;
void Update()
{
if (bubbleOnes.Count > 0 && necroReady == true)
{
StartCoroutine(bubbleSignal());
}
}
// These two functions add and remove eligible bubbles from its associated list
// as they pass through the collider.
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.gameObject.tag == "Bubble1")
{
GameObject other = collision.gameObject;
bubbleOnes.Add(other);
}
}
private void OnTriggerExit2D(Collider2D collision)
{
if(collision.gameObject.tag == "Bubble1")
{
GameObject other = collision.gameObject;
bubbleOnes.Remove(other);
}
}
// When this is called, select a random bubble from the list, delete it and
// replace it with a prefab.
IEnumerator bubbleSignal()
{
necroReady = false;
yield return new WaitForSeconds(Random.Range(0.1f, 1.5f));
int randomList = Random.Range(0, bubbleOnes.Count -1); // adding -1 reduced amount of errors
Vector3 targetBubblePos = bubbleOnes[randomList].transform.position; // Code seems to break on this line
Destroy(bubbleOnes[randomList]);
GameObject necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;
necroReady = true;
}
}
What I suspect is happening is as the bubbleSignal function operates, it selects a large or largest value just as it gets removed from the list by drifting out of the collider. How do I fix this?
In general the -1 in
int randomList = Random.Range(0, bubbleOnes.Count -1);
does not do what you think.
The upper bound already is exclusive so this will never ever return the last index.
Beyond that I see nothing obviously wrong in your code (at this point) which would lead me to the assumption that bubbleOnes is empty and therefore randomList = 0 but since there is no element at all bubbleOnes[randomList] already throws the exception.
This could e.g. happen if OnTriggerExit2D is called while there is only a single element in the list before the bubbleSignal routine or more exactly the WaitForSeconds within it finished. In that case you would remove the last element from the list, leave an empty list and then WaitForSeconds would eventually finish and you reach your exception line with an empty list.
You should handle this case and do e.g.
IEnumerator bubbleSignal()
{
necroReady = false;
yield return new WaitForSeconds(Random.Range(0.1f, 1.5f));
if(bubbleOnes.Count > 0)
{
var randomList = Random.Range(0, bubbleOnes.Count);
var targetBubblePos = bubbleOnes[randomList].transform.position;
Destroy(bubbleOnes[randomList]);
var necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;
}
necroReady = true;
}
In general in your use-case I would not use a Coroutine and poll-check in Update whether to run it or not. Here it would be way more flexible to directly stay in Update and do e.g.
[SerializeField] private float minTime = 0.1f;
[SerializeField] private float maxTime = 1.5f;
private float timer;
void Start()
{
timer = Random.Range(minTime, maxTime);
}
void Update()
{
if (bubbleOnes.Count > 0)
{
timer -= Time.deltaTime;
if(timer <= 0)
{
var randomList = Random.Range(0, bubbleOnes.Count);
var targetBubblePos = bubbleOnes[randomList].transform.position;
Destroy(bubbleOnes[randomList]);
var necroAcolyteClone = Instantiate(necroAcolyte, targetBubblePos, Quaternion.identity);
necroAcolyteClone.GetComponent<AcolyteScript>().activeTarget = transform.parent.gameObject;
timer += Random.Range(minTime, maxTime);
}
}
// [optionally]
// Instead of just continuing the counter from where it stopped last time
// you could also reset it while the list is empty
else
{
timer = Random.Range(minTime, maxTime);
}
}
This way you can be sure that there is no timing issue as all the code is only actually executed if the list is not empty in that moment.

My prefabs in unity aren't running script when spawned after one another (uses XR controller)

Similar to most people on here I'm fairly new to the unity scene and I'm attempting to create a specific mechanism in my game: I want to create a bush where I can spawn fruit items from; the fruit on the bush take a specific amount of time to grow and can't be interacted with until they're fully grown. Once grown as soon as you grab the fruit, it spawns another in the position of the original to repeats the cycle.
However, how the code works is it spawns a first fruit, grows and interacts once grown but once I grab the first fruit, the plant stops growing and just shows the fully grown object(however after every time you grab that fruit it spawns another fruit as intended) ,this object doesn't react with gravity unlike the original which applies gravity after interaction.
The object is a prefab and contains the following script:
using System.Collections; using System.Collections.Generic;
using UnityEngine; using UnityEngine.XR.Interaction.Toolkit;
public class GrowthScript : MonoBehaviour { Collider ObjectCol; [SerializeField] private GameObject Item;
public int GrowTime = 6;
public float MinSize = 0.1f;
public float MaxSize = 1f;
public float Timer = 0f;
private XRGrabInteractable grabInteractable = null;
public bool IsMaxSize = false;
public bool CanRegrow = false;
// Start is called before the first frame update
void Start()
{
Debug.Log("Growing");
IsMaxSize = false;
ObjectCol = GetComponent<Collider>();
// if the plant isnt full size, it starts a routine to grow
if (IsMaxSize == false)
{
ObjectCol.enabled = !ObjectCol.enabled;
StartCoroutine(Grow());
}
}
private void Awake()
{
grabInteractable = GetComponent<XRGrabInteractable>();
grabInteractable.onSelectEntered.AddListener(Obtain);
}
private IEnumerator Grow()
{
Vector3 startScale = new Vector3(MinSize, MinSize, MinSize);
Vector3 maxScale = new Vector3(MaxSize, MaxSize, MaxSize);
do
{
transform.localScale = Vector3.Lerp(startScale, maxScale, Timer / GrowTime);
Timer += Time.deltaTime;
yield return null;
}
while (Timer < GrowTime);
IsMaxSize = true;
CanRegrow = true;
Debug.Log("Grown");
ObjectCol.enabled = !ObjectCol.enabled;
}
private void Obtain(XRBaseInteractor interactor)
{
if (CanRegrow == true)
{
GameObject instance = Instantiate(Item, transform.position, transform.rotation) as GameObject;
CanRegrow = false;
}
}
}
It would be Deeply appreciated if I could receive help on why the prefab doesn't run the code on respawn or a way to solve the problem.
Much appreciated (Good Luck)

Unity Editor freezing every time player shoots a bullet

Alright, so here's what's happening: When I hit play and left click to shoot, unity editor freezes and I have to do the old Ctrl + Alt + Del, now, I am almost certain this script is the source of the issue, because when a bullet is shot, this script is immediately added to it, so here's the script(It's called BulletLife.cs, just letting you know)
using System.Timers;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
while(hasHitTarget == false && bulletLifeEnded == false) {
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
Destroy(bullet);
Debug.Log("Finish");
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e) {
bulletLifeEnded = true;
}
}
Also, here's the Shoot.cs script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Shoot : MonoBehaviour
{
public Transform gun;
public GameObject bullet;
public LayerMask targetMask;
public float bulletSpeed = 1000f;
bool hasHitTarget = false;
// Update is called once per frame
void Update()
{
if(Input.GetButtonDown("LeftClick")) {
GameObject bulletInstance;
bulletInstance = Instantiate(bullet, gun.position, new Quaternion(gun.rotation.w, gun.rotation.x, gun.forward.y, gun.rotation.z));
bulletInstance.AddComponent<Rigidbody>();
bulletInstance.GetComponent<Rigidbody>().useGravity = false;
bulletInstance.GetComponent<Rigidbody>().AddForce(gun.up * bulletSpeed);
bulletInstance.AddComponent<BulletLife>();
bulletInstance.GetComponent<BulletLife>().bullet = bulletInstance;
}
}
}
NOTE: I am using Unity 2019.4.15f1
Well everytime you instantiate a bullet in Start you do
while(hasHitTarget == false && bulletLifeEnded == false)
{
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
this loop will never finish since none of the conditions is changed inside the loop. There either is a hit or not .. but then the parameters for the raycast are never changed, the position isn't updated since you are still in the same frame => endless loop => freeze the main thread completely.
What you rather wanted to do is move that thing to Update which is called once a frame like e.g.
//public GameObject bullet; // not needed
public double bulletLifeSpan = 3;
//bool bulletLifeEnded; // not needed
public LayerMask targetMask;
//bool hasHitTarget; // not needed
void Start()
{
var bulletAge = new System.Timers.Timer(bulletLifeSpan * 1000);
bulletAge.Elapsed += OnTimedEvent;
bulletAge.AutoReset = false;
}
private void Update()
{
if(Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
Destroy(gameObject);
Debug.Log("Finish");
}
}
private void OnTimedEvent(System.Object Source, ElapsedEventArgs e)
{
Destroy(gameObject);
Debug.Log("Finish");
}
Or make it a single Coroutine
// If Start returns IEnumerator it is automatically started as Coroutine
// So no need to start an extra routine
private IEnumerator Start()
{
// Keeps track of how long your bullet exists already
var bulletAge = 0f;
while(bulletAge < bulletLifeSpan && !Physics.CheckSphere(transform.position, transform.localScale.y, targetMask))
{
// Increase by the time passed since last frame
bulletAge += Time.deltaTime;
// "Pause" this routine, render this frame
// and continue from here in the next frame
yield return null;
}
Destroy(gameObject);
Debug.Log("Finish");
}
Btw note that in Shoot you can shorten this a lot
void Update()
{
if(Input.GetButtonDown("LeftClick"))
{
// Note that your quaternion made no sense -> simply pass in the gun.rotation
var bulletInstance = Instantiate(bullet, gun.position, gun.rotation);
var rb = bulletInstance.AddComponent<Rigidbody>();
rb.useGravity = false;
rb.AddForce(gun.up * bulletSpeed);
var life = bulletInstance.AddComponent<BulletLife>();
// Assigning the gameObject reference is completely unnecessary
// within BulletLife simply use "gameObject" as show before
}
}
You could shorten this even more by making sure these components already exist on your prefab object and are configured correctly. Then you wouldn't need any of these line but just Instantiate it.
And finally you shouldn't use thisCheckSphere at all but rather let Unity handle its Collision detection itself and use OnCollisionEnter and configure your Collision Layers according to your needs!
The issue with your solution is: If your bullet moves fast it might simply pass a target without your CheckSphere noting it namely if its velocity is higher then localScale.y * 2.
Your Start method is blocking, thus freezing your game.
You'll have to use Update or a Coroutine to make your hit tests.
public class BulletLife : MonoBehaviour
{
public GameObject bullet;
public double bulletLifeSpan = 3;
bool bulletLifeEnded;
public LayerMask targetMask;
bool hasHitTarget;
// Start is called before the first frame update
void Start()
{
StartCoroutine(CheckHit(0, bulletLifeSpan));
}
private IEnumerator CheckHit(float interval, float lifetime){
bool checkEveryFrame = interval <= 0;
WaitForSeconds wait = checkEveryFrame ? null : new WaitForSeconds(interval);
while(lifetime > 0){
yield return wait;
lifetime = lifetime - (checkEveryFrame ? Time.deltaTime : interval);
hasHitTarget = Physics.CheckSphere(bullet.transform.position, bullet.transform.localScale.y, targetMask);
}
bulletLifeEnded = true;
Destroy(bullet);
Debug.Log("Finish");
}
}

Enemy wave spawner in unity

I'm trying to create a wave spawner for a top down game that I'm creating. I have created the wave spawner script but when I hit play, nothing happens. The countdown doesn't begin. Ideally it should start from 2, once it reaches 0, the first wave should spawn with one enemy. Once that enemy is killed, the countdown should begin from 5 and once 0 is reached, the next wave with 2 enemies should begin and so on. The new wave should not begin until all the current enemies are destroyed.
public enum SpawnState { SPAWNING, WAITING, COUNTING };
public SpawnState state = SpawnState.COUNTING;
public Transform enemy;
public float timeBetweenWaves = 5f;
public float countDown = 2f;
private int waveIndex = 0;
public float searchCountdown = 1f;
void Update()
{
if (state == SpawnState.WAITING)
{
if (!EnemyisAlive())
{
WaveCompleted();
}
else
{
return;
}
}
if (countDown <= 0f)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine(SpawnWave());
countDown = timeBetweenWaves;
}
else
{
countDown -= Time.deltaTime;
}
}
}
void WaveCompleted()
{
state = SpawnState.COUNTING;
countDown = timeBetweenWaves;
SpawnWave();
}
bool EnemyisAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectsWithTag("Enemy").Length == 0)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave()
{
state = SpawnState.SPAWNING;
waveIndex++;
for (int i = 0; i < waveIndex; i++)
{
SpawnEnemy();
yield return new WaitForSeconds(0.5f);
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy()
{
Instantiate(enemy, transform.position, transform.rotation);
}
I would recommend you to use a Coroutine for all of it. That makes some things easier. You can e.g. simply wait until another Ienumertaor is finished. Then I would simply add the spawned enemies to a list, filter it for null entries and use the count. Using Find or in your case FindGameObjectsWithTag each frame is highly inefficient!
using System.Linq;
using System.Collections.Generic;
...
public Transform enemy;
public float timeBetweenWaves = 5f;
public float countDown = 2f;
//public float searchCountdown = 1f;
private List<Transform> enemies = new List<Transform>();
private int waveIndex = 0;
private void Start()
{
StartCoroutine(RunSpawner());
}
// this replaces your Update method
private IEnumerator RunSpawner()
{
// first time wait 2 seconds
yield return new WaitForSeconds(countDown);
// run this routine infinite
while(true)
{
state = SpawnState.SPAWNING;
// do the spawning and at the same time wait until it's finished
yield return SpawnWave();
state = SpawnState.WAITING;
// wait until all enemies died (are destroyed)
yield return new WaitWhile(EnemyisAlive);
state = SpawnState.COUNTING
// wait 5 seconds
yield return new WaitForSeconds(timeBetweenWaves);
}
}
private bool EnemyisAlive()
{
// uses Linq to filter out null (previously detroyed) entries
enemies = enemies.Where(e => e != null).ToList();
return enemies.Count > 0;
}
private IEnumerator SpawnWave()
{
waveIndex++;
for (int i = 0; i < waveIndex; i++)
{
SpawnEnemy();
yield return new WaitForSeconds(0.5f);
}
}
private void SpawnEnemy()
{
enemies.Add(Instantiate(enemy, transform.position, transform.rotation));
}
To be slightly more efficient you could also avoid instantiating and destroying but rather use Object Pooling - only enabling and disabling the objects and eventually spawn new ones only when needed.

Set delay time to spawn object

I have the SpawnScript (Below)
I want to create a function within the SPAWN, so I can put the minimum and maximum delay time that an object can be created by the inspector.
using System.Collections;
using System.Collections.Generic;
public class SpawnController : MonoBehaviour
{
public float maxWidth;
public float minWidth;
public float minTime;
public float maxTime;
public float rateSpawn;
private float currentRateSpawn;
public GameObject tubePrefab;
public int maxSpawnTubes;
public List<GameObject> tubes;
// Use this for initialization
void Start ()
{
for (int i = 0; i < maxSpawnTubes; i++) {
GameObject tempTube = Instantiate (tubePrefab) as GameObject;
tubes.Add (tempTube);
tempTube.SetActive (false);
}
currentRateSpawn = rateSpawn;
}
// Update is called once per frame
void Update ()
{
currentRateSpawn += Time.deltaTime;
if (currentRateSpawn > rateSpawn) {
currentRateSpawn = 0;
Spawn ();
}
}
private void Spawn ()
{
float randWitdh = Random.Range (minWidth, maxWidth);
GameObject tempTube = null;
for (int i = 0; i < maxSpawnTubes; i++) {
if (tubes [i].activeSelf == false) {
tempTube = tubes [i];
break;
}
}
if (tempTube != null)
tempTube.transform.position = new Vector3 (randWitdh, transform.position.y, transform.position.z);
tempTube.SetActive (true);
}
}
you could use Time.realtimeSinceStartup for timestamps - this kinda would fit the way you do it atm.
or you use coroutines which is very unity and one better learns to use them early than late.
or you use invoke which probably is the shortest way of doing it.
http://docs.unity3d.com/ScriptReference/Time-realtimeSinceStartup.html http://docs.unity3d.com/Manual/Coroutines.html
http://docs.unity3d.com/ScriptReference/MonoBehaviour.Invoke.html
edit:
well actually you also could just rateSpawn = Random.Range(minTime, maxTime); inside the if statement in update, this would fit your current approach most.
InvokeRepeating Method is the way to deal with code repetation. You can call your Spawn method with invoke repeating inside of Start event and specify time according to your choice as invoke repeating define:
public void InvokeRepeating(string methodName, float time, float repeatRate);
Invokes the method methodName in time seconds, then repeatedly every
repeatRate seconds.
something like this edit require in your script:
void Start(){
InvokeRepeating("Spawn",2, 0.3F);
}

Categories

Resources