I have a wave script for my game and it works fine, however after a while I have realised it gets quite dull fighting against the same enemies over and over again, so I was wondering if I could make a list of some sort to store all enemies that will be in each wave.
Here is my 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;
public GameObject HelpText;
public GameObject Shop;
public GameObject shopfx;
public Transform ShopL;
bool shopactive = false;
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 Update()
{
if (_enemiesInWaveLeft <= 0 && Input.GetKeyDown(KeyCode.R))
{
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)
{
HelpText.SetActive(true);
Invoke("SetFalse",5.0f);
Shop.SetActive(true);
Invoke("LateUpdate",1f);
Instantiate(shopfx, new Vector3(ShopL.transform.position.x, ShopL.transform.position.y, ShopL.transform.position.z), Quaternion.identity);
shopactive = true;
}
}
void SetFalse()
{
HelpText.SetActive(false);
}
void LateUpdate()
{
if(Input.GetKeyDown(KeyCode.R) && shopactive == true)
{
Shop.SetActive(false);
Instantiate(shopfx, new Vector3(Shop.transform.position.x, Shop.transform.position.y, Shop.transform.position.z), Quaternion.identity);
shopactive = false;
}
}
}
Related
i am trying to develop a unity game which uses prefabs spawn at random locations from an array. The problem i am having is prefabs are spawning on the top of each other too many times. I have tried to prevent this from happening with the help of other topics not only here but also from google but i couldnt apply some methods to my code. So my goal is keep tracking last spawned object position and spawn next object at diffrent position from the array i have created within the obstacle script. Is there anyone who can help me?
This is my obstacle scripts which attached to prefabs.
public class obstacle : MonoBehaviour
{
private Rigidbody targetRb;
// Start is called before the first frame update
private float minSpeed = 12;
private float maxSpeed = 16;
private float ySpawnPos = 6;
private float NewPosition = -1.87f;
private List<Vector3> spawnPositions = new List<Vector3>();
private int index;
public int offset = 1;
void Start()
{
index = Random.Range(0, spawns.Length);
transform.position = spawns[index];
}
private void OnTriggerEnter(Collider other)
{
Destroy(gameObject);
}
Vector3 RandomForce()
{
return Vector3.down * Random.Range(minSpeed, maxSpeed);
}
Vector3[] spawns = new[]
//spawns = new Vector3[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
}
This is my game manager script which spawn the prefabs.
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class GameManager : MonoBehaviour
//spawnera alternatif daha güzel
{
public List<GameObject> targets;
// Start is called before the first frame update
public int score;
public TextMeshProUGUI scoreText;
public TextMeshProUGUI gameOverText;
public Button restartButton;
public bool isGameActive;
public float spawnRate = 3.0f;
public Text highScore;
public Text highestScore;
void Start()
{
isGameActive = true;
score = 0;
UpdateScore(0);
StartCoroutine(SpawnTarget());
highScore.text = PlayerPrefs.GetInt("HighScore", 0).ToString();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
Instantiate(targets[index]);
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
// Update is called once per frame
public void UpdateScore(int scoreToAdd)
{
score += scoreToAdd;
scoreText.text = "Score: " + score;
}
public void GameOver()
{
restartButton.gameObject.SetActive(true);
gameOverText.gameObject.SetActive(true);
isGameActive = false;
Time.timeScale = 0;
highScore.gameObject.SetActive(true);
highestScore.gameObject.SetActive(true);
}
public void RestartGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
Time.timeScale = 1;
}
}
A stack or queue might be a bit overkill, so instead, I'll just keep an iterator and check if we are exceeding the bounds of your array. I am also going to move the initial placement to the GameManager so it can manage which positions are left in the spawn list locations.
// move the array of locations to the GameManager
Vector3[] spawns = new[]
{
new Vector3(-2.16f,7,0),
new Vector3(-0.67f,7,0),
new Vector3(0.75f,7,0),
new Vector3(2.22f,7,0)
};
private int currentSpawnItr = 0;
private int maxSpawnItr = 0;
private System.Random rndm;
private void Start()
{
rndm = new System.Random();
maxSpawnItr = spawns.Length;
ShufflePositions();
}
IEnumerator SpawnTarget()
{
while (isGameActive)
{
yield return new WaitForSeconds(spawnRate);
int index = Random.Range(0, targets.Count);
GameObject tmpObstacle = Instantiate(targets[index]);
tmpObstacle.transform.position = GetNextPosition();
UpdateScore(5);
if(score > PlayerPrefs.GetInt("HighScore",0))
{
PlayerPrefs.SetInt("HighScore", score);
highScore.text = score.ToString();
}
}
}
private Vector3 GetNextPosition()
{
// when we reach the bounds of our spawn array length, shuffle it again
// and reset our iterator
if(currentSpawnItr == maxSpawnItr)
{
currentSpawnItr = 0;
ShufflePositions();
}
++currentSpawnItr;
return spawns[currentSpawnItr-1];
}
private void ShufflePositions()
{
Vector3 prevLast = spawns[maxSpawnItr-1];
spawns = spawns.OrderBy(spawn => rndm.Next()).ToArray();
// when our new first spawn was our old last spawn
if(spawns[0] == prevLast)
{
// randomly pick an index to swap our first element to
int randomIdx = rndm.Next(1, maxSpawnItr);
spawns[0] = spawns[randomIdx];
spawns[randomIdx] = prevLast;
}
}
I have not tested the above snippet. It is more the direction you could take this. If you have issues implementing this let me know.
I am trying to implement a waves of enemies, where you need to create a new wave when the current wave has been destroyed.
Wave Code
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
WaveConfig waveConfig;
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
GameObject newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(),
waveConfig.GetEnemyWaiponts()[0].transform.position,
Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
}
If I understand your point correctly.
this code not help you to wait previous wave was eliminate, it work flow is spawn next wave instantly after previous wave spawn completed.
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IF you need to wait until current wave eliminate.
When wave begin, you should track all enemies in the wave (eg. amount, alive data, or something) and check it for start next wave.
Psudo :
void Update(){
if(enemyInWave == 0){ // Or Condition is fullfill to start next wave
NextWave();
}else {
// Do Nothing
}
}
void NextWave(){
var waveConfig = WaveConfig[currentWaveIndex]
enemyInWave = waveConfig.monsters.Amount
SpanwEnemy(waveConfig.monsters)
}
or something like that
You could use a static HashSet/List where you store all instances of created enemies. The enemies simply add and remove themselves to/from the HastSet during their lifetime.
Then you can simply wait until no enemies are left before going into the next wave.
Something like
public class YourWaveClass : MonoBehaviour
{
[SerializeField] List<WaveConfig> waveConfigs;
[SerializeField] int startingWave = 0;
private static HastSet<Enemy> currentlyAliveEnemies = new HashSet<Enemy>();
public static void Add(Enemy enemy)
{
if(!currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Add(enemy);
}
public static void Remove(Enemy enemy)
{
if(currentlyAliveEnemies.Contains(enemy)) currentlyAliveEnemies.Remove(enemy);
}
void Start()
{
StartCoroutine(SpawnAllEnemyInWawe());
}
IEnumerator SpawnAllEnemyInWawe()
{
for(int waweIndex = startingWave; waweIndex < waveConfigs.Count; waweIndex++)
{
var currentWave = waveConfigs[waweIndex];
yield return StartCoroutine(SpawnAllEnemyInWawe(currentWave));
}
}
IEnumerator SpawnAllEnemyInWawe(WaveConfig waveConfig)
{
for(int enemyCount = 0; enemyCount<waveConfig.GetEnemyNumberOfParh(); enemyCount++)
{
// NOTE: It would be better/saver if you make your prefab directly of type Enemy then
var newEnemy = Instantiate(waveConfig.GetEnemyPrefabs(), waveConfig.GetEnemyWaiponts()[0].transform.position, Quaternion.identity);
newEnemy.GetComponent<EnemyPath>().SetWaveConfig(waveConfig);
yield return new WaitForSeconds(waveConfig.GetTimeMegduSpawnomEnemy());
}
// Now wait until the HashSet is empty => all enemies are dead
yield return new WaitUntil(() => currentlyAliveEnemies.Count == 0);
}
}
So what you need to have on your enemy prefabs would be a component like
public class Enemy : MonoBehaviour
{
// This is automatically called during Instantiate
private void Awake()
{
YourWaveClass.Add(this);
}
// this is called automatically when your object gets destroyed/dies
private void OnDestroy()
{
YourWaveClass.Remove(this);
}
}
I am creating a laser defender game in unity and I have this problem where when I shoot the first enemy it takes me directly to the NextLevelMenu scene but I want it to load when all the enemies are killed(on this level I have 5 enemies to kill). I have been told that I need to send a reference to the instance of its spawner to every spawned enemy but I did not quite understand. Can someone help, please?
EnemySpawner Script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemySpawner : MonoBehaviour {
[SerializeField] GameObject EnemyPreFab;
[SerializeField] int MaxEnemies = 30;
[SerializeField] float EnemySpawnTime = 1.00001f;
[SerializeField] GameObject FirstWaypoint;
int CurrentNumOfEnemies = 0;
public LevelManager myLevelManager;
public int maximumnumberofhits = 0;
public static EnemySpawner Instance = null;
int timesEnemyHit;
IEnumerator SpawningEnemies()
{
while(CurrentNumOfEnemies <= MaxEnemies)
{
GameObject Enemy = Instantiate(EnemyPreFab, this.transform.position, Quaternion.identity);
CurrentNumOfEnemies++;
yield return new WaitForSeconds(EnemySpawnTime);
}
}
void Start()
{
if (Instance == null)
Instance = this;
StartCoroutine(SpawningEnemies());
timesEnemyHit = 0;
if (this.gameObject.tag == "EnemyHit")
{
CurrentNumOfEnemies++;
}
}
public void OnEnemyDeath()
{
CurrentNumOfEnemies--;
if (CurrentNumOfEnemies < 1)
{
// You killed everyone, change scene:
LaserLevelManager.LoadLevel("NextLevelMenu");
}
}
}
EnemyShooting script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EnemyShooting : MonoBehaviour {
[SerializeField] float EnemyLaserSpeed = 10f;
[SerializeField] float EnemyLaserFireTime;
[SerializeField] GameObject LaserBulletEnemyPreFab;
[SerializeField] int MaxNumberOfHits = 1;
int CurrentNumberOfHits = 0;
Coroutine FireCoroutine;
void OnTriggerEnter2D(Collider2D collider)
{
if(collider.gameObject.tag == "PlayerLaser")
{
if(CurrentNumberOfHits < MaxNumberOfHits)
{
CurrentNumberOfHits++;
Destroy(collider.gameObject);
Score.ScoreValue += 2;//The user will be rewarded 1 point
}
}
}
void DestroyEnemy()
{
if(CurrentNumberOfHits >= MaxNumberOfHits)
{
Destroy(gameObject);
EnemySpawner.Instance.OnEnemyDeath(); // Tell the EnemySpawner that someone died
}
}
private void Fire()
{
FireCoroutine = StartCoroutine(ShootContinuously());
}
void BecomeVisible()
{
Fire();
}
IEnumerator ShootContinuously()
{
while (true)
{
GameObject LaserBulletEnemy = Instantiate(LaserBulletEnemyPreFab, this.transform.position, Quaternion.identity) as GameObject;
LaserBulletEnemy.GetComponent<Rigidbody2D>().velocity = new Vector2(0, EnemyLaserSpeed);
EnemyLaserFireTime = Random.Range(0.5f, 0.9f);
yield return new WaitForSeconds(EnemyLaserFireTime);
}
}
// Use this for initialization
void Start () {
BecomeVisible();
}
// Update is called once per frame
void Update () {
DestroyEnemy();
}
}
I would add a two fields to the spawner script. EnemiesToNextLevel and KilledEnemies. Then in the OnEnemyDeath() of your spawner, you may increase KilledEnemies everytime it is called, and then ask if KilledEnemies >= EnemiesToNextLevel, before changing the scene.
Sure there are a lot of other ways to do it, but for me thats the easiest.
I have a script that currently spawns enemies by set positions (point spawn). I am wondering how am I able to make them spawn ONLY when player is, for example, within 50 meters from spawn-point.
Wander Manager:
public class WanderingManager : MonoBehaviour {
public Transform[] wanderingPoints;
void getNewPos(GameObject target){
target.SendMessage("setNewWanderPos", wanderingPoints[Random.Range(0, wanderingPoints.Length)].position, SendMessageOptions.DontRequireReceiver);
}
}
Enemy Manager script:
[RequireComponent (typeof (WanderingManager))]
public class EnemyManager: MonoBehaviour {
public int maxZombies = 7;
public float spawnInterval = 5.0f;
public string zombiesTag = "Zombie", playerTag = "Player";
public GameObject[] ZombiePrefabs = null;
private GameObject player = null;
private ArrayList Zombies = new ArrayList();
private float lastTime = -10.0f;
private bool loaded = false;
private WanderingManager manager;
void Start () {
manager = GetComponent<WanderingManager>();
findPlayer();
}
// Update is called once per frame
void LateUpdate () {
if(player == null && loaded){
StartCoroutine(Restart());
}
if(Time.time > lastTime){
for(int i=0; i<Zombies.Count; i++){
if(Zombies[i] == null){
Zombies.RemoveAt(i);
}
}
if(Zombies.Count > maxZombies){
Zombies.RemoveAt(Zombies.Count - 1);
}else{
Transform point = manager.wanderingPoints[Random.Range(0, manager.wanderingPoints.Length)];
GameObject Z = Instantiate(ZombiePrefabs[Random.Range(0, ZombiePrefabs.Length)], point.position, point.rotation * Quaternion.Euler(0.0f, Random.Range(0.0f, 180.0f), 0.0f)) as GameObject;
Zombies.Add(Z);
}
lastTime = Time.time + spawnInterval;
}
}
void findPlayer(){
GameObject newPlayer = GameObject.FindWithTag(playerTag);
if(newPlayer != null && !newPlayer.name.Contains("Clone")){
player = newPlayer;
loaded = true;
}else if(newPlayer != null && newPlayer.name.Contains("Clone")){
Destroy(newPlayer);
retrySearch();
}
}
void retrySearch(){
findPlayer();
}
IEnumerator Restart(){
yield return new WaitForSeconds(10.0f);
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}
}
Any help will be appreciated. Thanks!
Just do a distance check. Something like this:
if (Vector3.Distance(player.transform.position,enemySpawn.transform.position) < 50):
{
spawnEnemyLogic();
}
I currently have a 2d platformer, where the player is given 9 lives on start, and loses a life everytime they hit an obstacle.
The GameManager script stores and displays the health and the Entity script updates the health in GameManager if the player loses a life.
If the player loses a life, they can respawn in that level.
On completion of a level, the player goes to a new scene "Level Complete", where they can choose to continue to the next level or quit the game.
However, when the player continues to the next level, the currentHealth variable does not persist and resets to 0.
I know WHY this problem is occurring, but I do not know how to fix it.
currentHealth is not being called anywhere in LevelComplete, so on Start in GameManager, it has lost the value of currentHealth.
I can't call GameManager on LevelComplete because it will spawn the player. So I'm not sure how to pass the variable.
Here are my scripts.
Game Manager
using UnityEngine.UI;
using System.Collections;
public class GameManager : MonoBehaviour {
public GameObject player;
private GameCamera cam;
private GameObject currentPlayer;
private Vector3 checkpoint;
public Text livesText;
private Entity livesCount;
public static int startHealth = 9;
public int currentHealth;
public bool playerDeath = false;
public static int levelCount = 2;
public static int currentLevel = 1;
void Start () {
cam = GetComponent<GameCamera> ();
livesText.text = currentHealth.ToString ();
if(GameObject.FindGameObjectWithTag("Spawn")){
checkpoint = GameObject.FindGameObjectWithTag("Spawn").transform.position;
}
SpawnPlayer (checkpoint);
}
private void SpawnPlayer(Vector3 spawnPos) {
currentPlayer = Instantiate (player, spawnPos, Quaternion.identity) as GameObject;
cam.setTarget(currentPlayer.transform);
}
public void Update(){
if(!currentPlayer){
if (Input.GetButtonDown ("Respawn")) {
playerDeath = false;
SpawnPlayer (checkpoint);
}
}
if (playerDeath) {
Destroy (currentPlayer);
}
if (currentPlayer) {
currentHealth = currentPlayer.GetComponent<Entity> ().GetHealth ();
}
livesText.text = currentHealth.ToString ();
}
public void SetCheckPoint(Vector3 cp){
checkpoint = cp;
}
public void EndLevel(){
if(currentLevel < levelCount){
currentLevel++;
Application.LoadLevel("Level Complete");
}
else{
Application.LoadLevel("Game Complete");
}
}
}
Entity
using UnityEngine;
using System.Collections;
public class Entity : MonoBehaviour {
public static int currHealth;
private int checkHealth; //Make sure 0 < health < 9
public void ModifyHealth(int amount){
checkHealth = currHealth + amount;
if (checkHealth >= 9) {
currHealth = 9; //Health can't be greater than 9
}
else if(checkHealth <= 0){
Die (); //End game if health is less than or equal to 0
}
else{
currHealth = checkHealth; //Otherwise, update Health
}
}
public int GetHealth(){
return currHealth;
}
public void SetHealth(int currentHealth){
currHealth = currentHealth;
}
public void Die(){
Application.LoadLevel ("Game Over");
}
}
Level Complete
using UnityEngine;
using System.Collections;
public class LevelComplete : MonoBehaviour {
public GUISkin gSkin;
void OnGUI(){
GUI.skin = gSkin;
if(GUI.Button(new Rect((Screen.width/2) - Screen.width *0.15f,Screen.height/2 - Screen.height *0.05f,Screen.width*0.3f,Screen.height*0.1f),"Next Level")){
Application.LoadLevel ("Level 2");
}
if(GUI.Button(new Rect((Screen.width/2) - Screen.width *0.15f,Screen.height/2 + 20.0f,Screen.width*0.3f,Screen.height*0.1f),"Quit")){
Application.Quit ();
}
}
}