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);
}
Related
here is my code and i have no idea how to work it up. im a beginner in c# and this is for our project.
the project is about a balloon popper and i am having trouble to set an amount of balloons to spawn.
I am planning to set the amount of balloons to spawn at 5, 10 even 20 and after that, the spawning will stop.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnScript: MonoBehaviour
{
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 0f;
float spawnTimeLeft = 0f;
// Start is called before the first frame update
void Update()
{
if (spawnTimeLeft >= spawnTime)
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTimeLeft = 0f;
}
else
{
spawnTimeLeft = spawnTimeLeft + Time.deltaTime;
}
}
}
Add a variable to the class for the maximum amount. Have done this as public so you can set it in the inspector to the amount you wish.
Also add a counter variable and set it to 0. This is private as nothing but this class needs access to it. Do serialize this field.
public int spawnAmountMax = 5;
[SerializeField]
private int spawned = 0;
Start the Update() with the next check, return from the method when it evaluates to true.
if (spawned >= spawnAmountMax)
return;
Than after instantiating the GameObject, increase the variable:
spawned++;
The adjusted code
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TaskbarClock;
public class SpawnScript : MonoBehaviour
{
public int spawnAmountMax = 5;
private int spawned = 0;
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 0f;
float spawnTimeLeft = 0f;
// Start is called before the first frame update
void Update()
{
if (spawned >= spawnAmountMax)
return;
if (spawnTimeLeft >= spawnTime)
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTimeLeft = 0f;
spawned++;
} else
{
spawnTimeLeft = spawnTimeLeft + Time.deltaTime;
}
}
}
Try this code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SpawnScript: MonoBehaviour
{
public Transform[] spawnPoints;
public GameObject[] balloons;
public float spawnTime = 5.0f;
public float maxSpawnTime = 20.0f;
private void Start()
{
Invoke(nameof(Spawn), spawnTime); //This will start the spawning process after the initially set spawnTime, so first this will wait for 5 seconds.
}
private void Spawn()
{
int randBalloon = Random.Range(0, balloons.Length);
int randSpawnPoint = Random.Range(0, spawnPoints.Length);
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
spawnTime *= 2; //doubles the spawnTime, according to your request, so the second time this will be 10, and the third time, this will be 20 seconds
if (spawnTime <= maxSpawnTime) //check if we reached or not the maximum spawn time
{
Invoke(nameof(Spawn), spawnTime); //if we not yet reached the maximum spawn time, it will start to wait and spawn one more balloon again.
}
}
}
since you're doing this in you update, you can add a counter after your Instantiate call.
Instantiate(balloons[randBalloon], spawnPoints[randSpawnPoint].position, transform.rotation);
counter++;
And then, nest your if statement inside another if checking for the counter.
if(counter < amountToSpawn)
{
//your code here
}
For me personally, I would make a Coroutine method with delay that will have a for loop inside, rather than doing this in update.
Sample:
private IEnumerator SpawnObjects(int spawnCount)
{
for(int i = 0; i < spawnCount; i++)
{
//Instantiate here
yield return new WaitForSeconds(5);
}
}
To call the method:
StartCoroutine(SpawnObjects(10));
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");
}
}
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;
}
}
Hi I am working on game that uses random terrain and I want to spawn objects onto that terrain. To do this, I have created what I have called the Surface Populator Script.
This is the script:
public SurfaceSpawnerData spawnerData;
private float randomX;
private float randomZ;
private Renderer r;
void Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
SpawnObjects();
}
void Update()
{
}
void SpawnObjects()
{
RaycastHit hit;
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
int currentObjects = spawnerData.spawnableObjects[i].currentObjects;
int numOfObjects = spawnerData.spawnableObjects[i].numberOfObjects;
if (currentObjects != numOfObjects)
{
if (Physics.Raycast(new Vector3(randomX, r.bounds.max.y + 5f, randomZ), -Vector3.up, out hit))
{
randomX = Random.Range(r.bounds.min.x, r.bounds.max.x);
randomZ = Random.Range(r.bounds.min.z, r.bounds.max.z);
if (hit.point.y >= spawnerData.spawnableObjects[i].spawnerStartHeight && hit.point.y <= spawnerData.spawnableObjects[i].spawnerEndHeight)
{
spawnerData.SpawnedObjects.Add(Instantiate(spawnerData.spawnableObjects[i].spawnablePrefab, hit.point, Quaternion.identity));
spawnerData.spawnableObjects[i].currentObjects += 1;
}
}
}
}
}
The script also gains its data from a scriptable object:
[CreateAssetMenu]
public class SurfaceSpawnerData : ScriptableObject
{
public SpawnableObjects[] spawnableObjects;
public List<GameObject> SpawnedObjects;
[System.Serializable]
public class SpawnableObjects
{
public GameObject spawnablePrefab;
public float spawnerStartHeight = 2f;
public float spawnerEndHeight;
public int currentObjects;
public int numberOfObjects;
}
}
This script currently works perfectly fine when placed inside the update method, however I do not want to do this due to its affect on performance. Therefore I am wondering if there is a way to stop the Unity start method from exiting until my SpawnObjects() function has stopped running. If this is not possible if you have any other ideas on how I run this only once without using the update function let me know.
I am relatively new to c# as a language and I'm sorry if there is an easy fix that I have missed. Any help would be appreciated. Thanks.
Since SpawnObjects is a synchronus method Start will not return until SpawnObjects finished anyway.
As far as I understand your issue is rather that anything from Physics is not available during initialization (Awake, OnEnable, Start) but only within or after the Physics block (see ExecutionOrder) so e.g. in a method like FixedUpdate or Update.
So to answer your question: You could use a Coroutine and WaitForFixedUpdate in order to make your Instantiation:
void Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
StartCoroutine(DoInstantiate());
}
private IEnumerator DoInstantiate()
{
// wait until Physics are initialized
yield return new WaitForFixedUpdate();
SpawnObjects();
}
or as you can see in ScriptReference/Coroutine you can make this shorter by directly making the Start a routine e.g. like
IEnumerator Start()
{
r = GetComponent<Renderer>();
for (int i = 0; i < spawnerData.spawnableObjects.Length; i++)
{
spawnerData.spawnableObjects[i].currentObjects = 0;
}
spawnerData.SpawnedObjects.Clear();
// wait until Physics are initialized
yield return new WaitForFixedUpdate();
SpawnObjects();
}
I have a SceneController that's supposed to initialize a set of empty GameObject spawners, each working together at the same rhythm. The RockSpawners receive an array of time delays and wait the X seconds between spawning another rock.
I set the _nextSpawn = float.maxValue when the spawners start and plan to overwrite this after "Initializing" them (my own method), however even though my debug logs say I've overwritten my _nextSpawn value while initializing, the update loop is still reporting float.maxValue and nothing ends up spawning because _timeSinceLastSpawn hasn't exceeded float.maxValue seconds.
Is there something I'm missing with the scope of my _nextSpawn variables? It doesn't seem to be a "this" vs "local" issue, at least at first glance.
Debug output: 0 0 3 3. 0's stay the same, 3's will vary based on rng.
SceneController.cs:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SceneController : MonoBehaviour {
[SerializeField] private GameObject _rockSpawnerPrefab;
public int numRocks = 6;
public int minSpawnDelaySec = 1;
public int maxSpawnDelaySec = 3;
private bool spawnersInitialized = false;
void Start () {
InitializeSpawners();
}
void Update () {
}
void InitializeSpawners() {
float[] pattern = new float[numRocks];
for (int i = 0; i < numRocks; i++) {
// Generate delays at half second increments within bounds
float delay = Mathf.Floor(Random.value * ((float)(maxSpawnDelaySec + 0.5f - minSpawnDelaySec) / 0.5f));
delay = delay * 0.5f + minSpawnDelaySec;
pattern[i] = delay;
}
GameObject spawner = Instantiate(_rockSpawnerPrefab) as GameObject;
spawner.transform.position = new Vector3(0, 4, 0);
RockSpawner rockSpawnerScript = spawner.GetComponent<RockSpawner>();
rockSpawnerScript.Initialize(pattern);
}
}
RockSpawner.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class RockSpawner : MonoBehaviour {
[SerializeField] private GameObject _rockPrefab;
public float minSpawnDelay = 3f;
public float maxSpawnDelay = 6f;
private float[] _pattern;
private int _currentPattern;
private float _timeSinceLastSpawn;
private float _nextSpawn;
void Start () {
_currentPattern = -1;
_nextSpawn = float.MaxValue;
}
void Update () {
if (_pattern == null) return;
_timeSinceLastSpawn += Time.deltaTime;
if (_timeSinceLastSpawn > _nextSpawn) {
GameObject rock = Instantiate(_rockPrefab) as GameObject;
rock.transform.position = transform.position;
NextTimer();
}
}
public void Initialize(float[] pattern) {
_pattern = pattern;
NextTimer();
}
private void NextTimer() {
_timeSinceLastSpawn = 0;
_currentPattern += 1;
Debug.Log(_nextSpawn);
Debug.Log(this._nextSpawn);
this._nextSpawn = _pattern[_currentPattern];
Debug.Log(_nextSpawn);
Debug.Log(this._nextSpawn);
}
}
It's not about scoping, it's about call order. When you create a GameObject its Start method is called on the frame it's enabled, not when the object is created. So your code will call Initialize first, then Start which overwrites the values.
Remove the code in Start and handle everything in Initialize and it should work as you want.