I want some enemies to spawn in 4 different locations for a survival-type game. The problem is, they all spawn in the same place. Why is this? This is in Unity by the way.
C# script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Spawner : MonoBehaviour {
public int spawnHere = 0;
public int spawnTimer = 0;
public int spawnRate = 1;
public Transform spawner1;
public Transform spawner2;
public Transform spawner3;
public Transform spawner4;
public GameObject melee1;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
spawnTimer = spawnTimer + spawnRate;
spawnHere = Random.Range (1, 5);
if(spawnTimer >= 120) {
if(spawnHere == 1) {
Instantiate (melee1, spawner1);
}
if(spawnHere == 2) {
Instantiate (melee1, spawner2);
}
if(spawnHere == 3) {
Instantiate (melee1, spawner3);
}
if(spawnHere == 4) {
Instantiate (melee1, spawner3);
}
spawnTimer = 0;
}
}
}
Have you connected the spawners correctly in the UI?
Watch this: Unity - Survival Shooter - Spawning Enemies
This worked pretty good for my project.
You should also use Time.deltaTime for timing the spawns. Not every system outputs the same amount of frames / second.
Unity - docs - Time.deltaTime
BTW:
Random.rand(min, max)
Unity - docs - Random.Rand
includes max as a possible value.
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));
im making an enemy wave - based game and it works fine, but i want to make it so before it continues on to the next wave, the player has to press a key to let the game know they're ready, the reason i want to do this is because i want to implement a shop to purchase upgrades after waves, here is my wave script:
SpawnManager.cs
using System.Collections;
using UnityEngine;
[System.Serializable]
public class Wave
{
public int EnemiesPerWave;
public GameObject Enemy;
}
public class SpawnManager : MonoBehaviour
{
public Wave[] Waves; // class to hold information per wave
public Transform[] SpawnPoints;
public float TimeBetweenEnemies = 2f;
private int _totalEnemiesInCurrentWave;
private int _enemiesInWaveLeft;
private int _spawnedEnemies;
private int _currentWave;
private int _totalWaves;
void Start ()
{
_currentWave = -1; // avoid off by 1
_totalWaves = Waves.Length - 1; // adjust, because we're using 0 index
StartNextWave();
}
void StartNextWave()
{
_currentWave++;
// win
if (_currentWave > _totalWaves)
{
return;
}
_totalEnemiesInCurrentWave = Waves[_currentWave].EnemiesPerWave;
_enemiesInWaveLeft = 0;
_spawnedEnemies = 0;
StartCoroutine(SpawnEnemies());
}
// Coroutine to spawn all of our enemies
IEnumerator SpawnEnemies()
{
GameObject enemy = Waves[_currentWave].Enemy;
while (_spawnedEnemies < _totalEnemiesInCurrentWave)
{
_spawnedEnemies++;
_enemiesInWaveLeft++;
int spawnPointIndex = Random.Range(0, SpawnPoints.Length);
// Create an instance of the enemy prefab at the randomly selected spawn point's
// position and rotation.
Instantiate(enemy, SpawnPoints[spawnPointIndex].position,
SpawnPoints[spawnPointIndex].rotation);
yield return new WaitForSeconds(TimeBetweenEnemies);
}
yield return null;
}
// called by an enemy when they're defeated
public void EnemyDefeated()
{
_enemiesInWaveLeft--;
// We start the next wave once we have spawned and defeated them all
if (_enemiesInWaveLeft == 0 && _spawnedEnemies == _totalEnemiesInCurrentWave)
{
StartNextWave();
}
}
}
Set a Boolean like waitForKey=true, then in something like Update:
if( waitForKey && Input.GetKeyDown("space") ) {
waitForKey=false;
StartNextWave();
}
Listen to a button input in the Update function.
void Update()
{
if (_enemiesInWaveLeft <= 0 && InputGetKeyDown(KeyCode.Space))
{
StartNewWave();
}
}
This will cause the wave to spawn when there are no enemies left and Space is pressed down. You will need to add some UI separately to this to give the player some feedback as to when it is Ok to press the Space Button.
Also remove the StartNextWave from your EnemyDefeated funciton. That can be used to enable and disable UI for the player to let them know to press a button to continue.
I'm making a script in Unity using C#. I'm trying to use the Update() method to detect once the Camera position is past a certain point and then Instantiate an object into the scene and overwrite the variable "x" to something else so this only happens once.
The problem is I cant overwrite this "x" variable.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour{
public GameObject GroundSprite;
public int x = 1;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
int x = 2;
}
}
}
Please remove int from below code, your value will be overwritten.
// Update is called once per frame
void Update()
{
if (((Camera.main.transform.position.x) < -4) && ( x == 1))
{
Instantiate(GroundSprite, transform.position, Quaternion.identity);
//int x = 2;
x=2;
}
}
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.
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);
}