I am having issues with a wave spawner - c#

I am developing a Tower Defence game and I need a wave spawner. I tried to use Brackeys Wave spawner but it only supports one type of enemy per wave and I tried to make one myself like this:
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
instead of this:
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
This is the code from the 40min Brackeys video
using UnityEngine;
using System.Collections;
public class WaveSpawner : MonoBehaviour {
public enum SpawnState { SPAWNING, WAITING, COUNTING };
[System.Serializable]
public class Wave
{
public string name;
public Transform enemy;
public int count;
public float rate;
}
public Wave[] waves;
private int nextWave = 0;
public int NextWave
{
get { return nextWave + 1; }
}
public Transform[] spawnPoints;
public float timeBetweenWaves = 5f;
private float waveCountdown;
public float WaveCountdown
{
get { return waveCountdown; }
}
private float searchCountdown = 1f;
private SpawnState state = SpawnState.COUNTING;
public SpawnState State
{
get { return state; }
}
void Start()
{
if (spawnPoints.Length == 0)
{
Debug.LogError("No spawn points referenced.");
}
waveCountdown = timeBetweenWaves;
}
void Update()
{
if (state == SpawnState.WAITING)
{
state = SpawnState.COUNTING;
/*if (!EnemyIsAlive())
{
WaveCompleted();
}
else
{
return;
}*/
}
if (waveCountdown <= 0)
{
if (state != SpawnState.SPAWNING)
{
StartCoroutine( SpawnWave ( waves[nextWave] ) );
}
}
else
{
waveCountdown -= Time.deltaTime;
}
}
void WaveCompleted()
{
Debug.Log("Wave Completed!");
state = SpawnState.COUNTING;
waveCountdown = timeBetweenWaves;
if (nextWave + 1 > waves.Length - 1)
{
nextWave = 0;
Debug.Log("ALL WAVES COMPLETE! Looping...");
}
else
{
nextWave++;
}
}
bool EnemyIsAlive()
{
searchCountdown -= Time.deltaTime;
if (searchCountdown <= 0f)
{
searchCountdown = 1f;
if (GameObject.FindGameObjectWithTag("Enemy") == null)
{
return false;
}
}
return true;
}
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
for (int i = 0; i < _wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform _sp = spawnPoints[ Random.Range (0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
}
And I need to add this to the code to get what I am expecting
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] Enemy;
public float Rate = 5f;
}
But I was not able to, can anyone help me?
Thanks in advance,
Dev

You should look at his code, there will be those lines of code :
IEnumerator SpawnWave(Wave _wave)
{
state = SpawnState.SPAWNING;
for (int i = 0; i < wave.count; i++)
{
SpawnEnemy(_wave.enemy);
yield return new WaitForSeconds( 1f/_wave.rate );
}
state = SpawnState.WAITING;
yield break;
}
And also those lines of code :
void SpawnEnemy(Transform _enemy)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemy, _sp.position, _sp.rotation);
}
So what is the problem, the problem is that the SpawnEnemy() method is using only one type of enemies each wave, using _enemy from class Wave which is in [System.Serializable], so my idea is that we will make another class Wave, same same as his code :
[System.Serializable]
public class Wave
{
public string name;
public Transform[] enemies;
public int count;
public float rate;
}
And change a little bit in SpawnEnemy() method, which I will add Random.Rage() to choose a different transform ( or we can say enemy )
void SpawnEnemy(Transform[] _enemies)
{
Debug.Log("Spawning Enemy: " + _enemy.name);
int randomIndex = Random.Range(0, _enemies.Count());
Transform sp = spawnPoints[ Randon.Range(0, spawnPoints.Length) ];
Instantiate(_enemies[randomIndex], _sp.position, _sp.rotation);
}
If having any trouble when doing, just comment below, yeah

After reading through the existing comments, I think you really only need one additional edit within the SpawnWave code to achieve what you are looking for - Add a foreach loop to loop through each Wave Contents object and spawn the appropriate number and type of enemy
Given your updated objects, with one update to a field name for clarity
[System.Serializable]
public class WaveContent
{
public Transform enemy;
public int count;
}
[System.Serializable]
public class Wave
{
public string Name;
public WaveContent[] WaveContents;
public float Rate = 5f;
}
Then you just need to loop through the array of WaveContent and call SpawnEnemy for each one, using the WaveContent.Count for the inner loop.
IEnumerator SpawnWave(Wave _wave)
{
Debug.Log("Spawning Wave: " + _wave.name);
state = SpawnState.SPAWNING;
// Loop through your Wave Contents
foreach (var waveContent in Wave.WaveContents)
{
// Spawn the number and type of enemy for each content object
for (int i = 0; i < waveContent.count; i++)
{
SpawnEnemy(waveContent.enemy);
yield return new WaitForSeconds(1f / _wave.rate);
}
}
state = SpawnState.WAITING;
yield break;
}

Related

The problem is the disappearance of the player

I have one rather ambiguous problem in my C# (Unity) code. It lies in the fact that my player disappears because of the prescribed lines about the spawnpoint. Because of them, my player simply disappears when I run the game
The lines where the problem is possible are 22, 109-112. Please help me with this problem. Thank you very much in advance
But I need spawnpoint for further work. Thank you very much in advance!
Code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
public static GameManager instance;
private void Awake()
{
if(GameManager.instance != null)
{
Destroy(gameObject);
Destroy(player.gameObject);
Destroy(floatingTextManager.gameObject);
Destroy(HUD);
Destroy(menu);
return;
}
instance = this;
SceneManager.sceneLoaded += LoadState;
SceneManager.sceneLoaded += OnSceneLoaded;
}
public List<Sprite> playerSprites;
public List<Sprite> weaponSprites;
public List<int> weaponPrices;
public List<int> xpTable;
public Player player;
public Weapon weapon;
public FloatingTextManager floatingTextManager;
public Animator misMenuAnim;
public RectTransform hitpointBar;
public GameObject HUD;
public GameObject menu;
public int pesos;
public int experience;
public void ShowText(string msg, int fontSize, Color color, Vector3 position, Vector3 motion, float duration)
{
floatingTextManager.Show(msg, fontSize, color, position, motion, duration);
}
public bool TryUpgradeWeapon()
{
if (weaponPrices.Count <= weapon.weaponLevel)
return false;
if(pesos >= weaponPrices[weapon.weaponLevel])
{
pesos -= weaponPrices[weapon.weaponLevel];
weapon.UpgradeWeapon();
return true;
}
return false;
}
public void OnHitpointChange()
{
float ratio = (float)player.hitpoint / (float)player.maxHitpoint;
hitpointBar.localScale = new Vector3(1, ratio, 1);
}
public int GetCurrentLevel()
{
int r = 0;
int add = 0;
while(experience >= add)
{
add += xpTable[r];
r++;
if (r == xpTable.Count)
return r;
}
return r;
}
public int GetXpToLevel(int level)
{
int r = 0;
int xp = 0;
while(r < level)
{
xp += xpTable[r];
r++;
}
return xp;
}
public void GrantXp(int xp)
{
int currLevel = GetCurrentLevel();
experience += xp;
if (currLevel < GetCurrentLevel())
OnLevelUp();
}
public void OnLevelUp()
{
player.OnLevelUp();
OnHitpointChange();
}
public void OnSceneLoaded(Scene s, LoadSceneMode mode)
{
player.transform.position = GameObject.Find("SpawnPoint").transform.position;
}
public void Respawn()
{
misMenuAnim.SetTrigger("Hide");
UnityEngine.SceneManagement.SceneManager.LoadScene("SampleScene");
player.Respawn();
}
public void SaveState()
{
string s = "";
s += "0" + "|";
s += pesos.ToString() + '|';
s += experience.ToString() + "|";
s += weapon.weaponLevel.ToString();
}
public void LoadState(Scene s, LoadSceneMode mode)
{
SceneManager.sceneLoaded += LoadState;
if (!PlayerPrefs.HasKey("SaveState"))
return;
string[] data = PlayerPrefs.GetString("SaveState").Split('|');
pesos = int.Parse(data[1]);
experience = int.Parse(data[2]);
player.SetLevel(GetCurrentLevel());
weapon.SetWeaponLevel(int.Parse(data[0]));
}
}
I have tried many ways over the course of a month, but none of them could solve the problem...

Making a certain Element of an array interactable using a PlayerPrefs float while all of the +1 elements remain locked

So basically I have a Score/Highscore system and I also have an array which contains some of the buttons which change the color of the player's character. So my question is that if I have highscoreCount >= 20, I want a certain button to become interactable.
ScoreManager Script
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour
{
public Transform player;
public Text scoreText;
public Text highScoreText;
public float scoreCount;
public float highscoreCount;
public float pointsPerSecond;
public bool scoreIncreasing;
public Button[] CustomizeButtons;
void Start()
{
if (PlayerPrefs.HasKey("Highscore"))
{
highscoreCount = PlayerPrefs.GetFloat("Highscore");
}
int CustomizationButtonReached = PlayerPrefs.GetInt("CustomizationButtonReached", 1);
{
for (int i = 0; i < CustomizeButtons.Length; i++)
{
if (i + 1 > CustomizationButtonReached)
CustomizeButtons[i].interactable = false;
}
}
}
void Update()
{
if (scoreIncreasing)
{
scoreCount += pointsPerSecond * Time.deltaTime;
}
if(scoreCount > highscoreCount)
{
highscoreCount = scoreCount;
PlayerPrefs.SetFloat("Highscore", highscoreCount);
}
scoreText.text = "Score: " + Mathf.Round(scoreCount);
highScoreText.text = "Highscore: " + Mathf.Round(highscoreCount);
}
}
CustomizeColors Script
using UnityEngine;
public class CustomizeColors : MonoBehaviour
{
public Color[] Colors;
public Material Mat;
public void Start()
{
if (PlayerPrefs.HasKey("HeadColor"))
{
Mat.color = Colors[PlayerPrefs.GetInt("HeadColor")];
}
}
public void ChangeColor(int colorIndex)
{
Mat.color = Colors[colorIndex];
PlayerPrefs.SetInt("HeadColor", colorIndex);
PlayerPrefs.Save();
}
}
Essentially this is the code that I have made (in the ScoreManager) to make an array and to disable all of the buttons. I just want to get a specific element to be interactable when highscoreCount >= "number"
public Button[] CustomizeButtons;
void Start()
{
int CustomizationButtonReached = PlayerPrefs.GetInt("CustomizationButtonReached", 1);
{
for (int i = 0; i < CustomizeButtons.Length; i++)
{
if (i + 1 > CustomizationButtonReached)
CustomizeButtons[i].interactable = false;
}
}
}
You could go multiple ways about this, for example storing a reference for that specific button and when the condition you mentioned is true just set that one button to be interactable
[SerializeField] Button button; //Set this in the inspector
public void AddToScore(int score)
{
//Do your score adding stuff
if (highscoreCount >= number)
{
button.interactable = true
}
}

Trying to find all of the objects in my scene by a custom tag script I have created, however i continue getting a null reference exception

The Tag script has been working for me so far as an alternate to the unity tags up to this point, allowing me to assign multiple tags to an object at once. Now I want to create a method that will get all of the objects in the scene, filter them by the tag, and then return it as an array. The null reference exception refers to line 41 of the Tag.cs script. How do I fix this?
Tags.cs file
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Tags : MonoBehaviour
{
public string[] startTags;
private string[] tags;
private void Start()
{
tags = startTags;
}
public bool FindTag(string search)
{
bool results = false;
for (int i = 0; i < tags.Length; i++)
{
if(search == tags[i])
{
results = true;
break;
}
}
return results;
}
//Find objects by custom script tags
//HERE IS WHERE THE METHOD IS CREATED
public static GameObject[] ObjectsByTag(string search)
{
//Get all objects in scene
GameObject[] allObjects = FindObjectsOfType<GameObject>();
GameObject[] storedObjects = new GameObject[allObjects.Length];
GameObject[] finalObjects;
//Filter
int count = 0;
for (int i = 0; i < allObjects.Length; i++)
{
if (allObjects[i].GetComponent<Tags>().FindTag(search)) //line 41
{
storedObjects[count] = allObjects[i];
count++;
}
}
//Assign final length
finalObjects = new GameObject[count];
//Reassign to final array
for (int i = 0; i < count; i++)
{
finalObjects[i] = storedObjects[i];
}
return finalObjects;
}
}
GameController.cs file (How it is being used
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameController : MonoBehaviour
{
//SCREEN START
//Get Screen Size
private float sHeight;
private float sWidth;
//Intended Screen Size
readonly float iH = 695f;
readonly float iW = 1540f;
//Convert
private float cH;
private float cW;
public float ConvertedHeight => cH;
public float ConvertedWidth => cW;
//SCREEN END
//MOUSE CAM START
//mousePostion
private float mX;
private float mZ;
public float MouseX => mX;
public float MouseZ => mZ;
//MOUSE CAM END
//EnemySpeedModifier
private float esm;
public float ESM
{
get { return esm; }
set { esm = value; }
}
//GameOver
private bool gameOver = false;
public bool GameOver
{
get { return gameOver; }
set { gameOver = value; }
}
//game speed
public float speed;
/*
//projectile list
private GameObject[] projectiles;
public GameObject[] Projectiles()
{
return projectiles;
}
public void Projectiles(GameObject value)
{
GameObject[] tempArray = projectiles;
tempArray[projectiles.Length] = value;
projectiles = tempArray;
Debug.Log("Projectile Count: " + projectiles.Length);
}
*/
//HERE IS WHERE IT IS USED
public GameObject[] ProjectilesInScene
{
get
{
return Tags.ObjectsByTag("projectile");
}
}
// Start is called before the first frame update
void Start()
{
//CONVERT SCREEN SIZES START
sHeight = Screen.height;
sWidth = Screen.width;
cH = iH / sHeight;
cW = iW / sWidth;
//CONVERT SCREEN SIZES END
}
// Update is called once per frame
void Update()
{
if (gameOver)
{
speed /= 1 + 0.5f * Time.deltaTime;
}
//Update mose position
mX = Input.mousePosition.x;
mZ = Input.mousePosition.y;
}
}
It seems that not all of your GameObject objects have the Tags component. Per the GameObject.GetComponent documentation
Returns the component of Type type if the game object has one attached, null if it doesn't.
If you know that some objects won't have the Tags component, your line 41 can use a simple null conditional operator:
if (allObjects[i].GetComponent<Tags>()?.FindTag(search) == true)
{
...
}
Note the ? after GetComponent<Tags>().

Making an Int increase every second by x value

I'm making a game similar to the 2013 style Cookie Clicker. I'm stuck with making the auto generate over time script. I want to be able to access a script that makes "Muffins" automatically.
I've tried to make the script multiple times but I can't seem to get the muffin count to change.
public bool MakingMuffins = false;
public static int MuffinIncrease = 1;
public int InternalIncrease;
void Update () {
InternalIncrease = MuffinIncrease;
if (MakingMuffins == false)
{
MakingMuffins = true;
StartCoroutine(MakeTheMuffin());
}
}
IEnumerator MakeTheMuffin ()
{
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
and my other main file to start the method.
public void StartAutoMuffin()
{
if (InternalPlayerMuffins >=bakerycost){
playSound.Play();
StartBakery.SetActive(true);
InternalPlayerMuffins -= bakerycost;
bakerycost *= 2;
turnOffButton = true;
themps += 1;
thelevel += 1;
}else{
Debug.Log("Cant do anything");
}
}
StartBakery is a Game Object that should start the auto make after I press the button. It is set inactive as default.
Here is more code from my main script to help understand.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class GlobalGameMechanics : MonoBehaviour
{
public static bool turnOffButton = false;
public AudioSource muffinSound;
public AudioSource playSound;
public static int GlobalPlayerDiamonds;
public int InternalPlayerDiamonds = 1200;
public GameObject PlayerDiamondDisplay;
public GameObject TotalMPS;
public GameObject Cost;
public GameObject Level;
public GameObject MPS;
public GameObject FakeCost;
public GameObject FakeLevel;
public GameObject FakeMPS;
public int InternalPlayerMuffins = 15;
public int bakerycost = 25;
public int thelevel = 0;
public int themps = 0;
public int currentMuffins;
public GameObject ShopPanel;
public GameObject fakeButton;
public GameObject realButton;
public GameObject StartBakery;
void Start()
{
}
void Update()
{
GlobalPlayerDiamonds = InternalPlayerDiamonds;
PlayerDiamondDisplay.GetComponent<Text>().text = "" + InternalPlayerDiamonds;
TotalMPS.GetComponent<Text>().text = "/Sec " + themps;
GlobalMuffins.MuffinCount = InternalPlayerMuffins;
Cost.GetComponent<Text>().text = "" + bakerycost;
Level.GetComponent<Text>().text = "Level " + thelevel;
MPS.GetComponent<Text>().text = "MPS " + themps;
FakeCost.GetComponent<Text>().text = "" + bakerycost;
FakeLevel.GetComponent<Text>().text = "Level " + thelevel;
FakeMPS.GetComponent<Text>().text = "MPS " + themps;
currentMuffins = InternalPlayerMuffins;
if (currentMuffins >= bakerycost)
{
fakeButton.SetActive(false);
realButton.SetActive(true);
}
if (turnOffButton == true)
{
realButton.SetActive(false);
fakeButton.SetActive(true);
turnOffButton = false;
}
}
So far I have created 1 button that I want to increase the "InternalPlayerMuffins" By 1 every second. And then I want to have Button 2 to increase by +5 /sec. I'm just having trouble and been stuck for a few days.
I cant make the muffins increase at all on its :/
Your are close, it seems like you forgot to loop your coroutine endlessly like so:
IEnumerator MakeTheMuffin() {
while (true) {
GlobalMuffins.MuffinCount += InternalIncrease;
yield return new WaitForSeconds(1);
MakingMuffins = false;
}
}
Though I may be wrong on that, since I am not so sure if GlobalMuffins.MuffinCount is the integer that you want to increase every second.
But generally the idea is the same, you want would want a coroutine, a target interval, a target integer and the value you want to increase by, like so:
public class YourMuffinMaker : MonoBehaviour {
public float intervals;
public int increment;
private int muffinCount;
void Start() {
muffinCount = 0;
StartCoroutine(CreateMuffinEveryIntervals());
}
private IEnumerator CreateMuffinEveryIntervals() {
while (true) {
muffinCount += increment;
yield return new WaitForSeconds(intervals);
}
}
}
This answer that was posted is an alternative to Coroutine as well.
i can use InvokerRepeating whick calls method every time u need, for example
void Start()
{
InvokeRepeating("CreateNewMuffin", 0f, 1f);
}

Creating an Enemy Class

I've never done this before and my true understanding of classes are not that good. However, I plan on mastering it after this project! What I'd like to do is create a class to determine enemy type by TAG: Enemy1 or Boss. (I've already designed a system to randomize the Enemy1 stats so no two will be the same. However, here, I just want to learn how to properly setup enemies stats so here's my code)
using System.Collections;
public class Enemies : MonoBehaviour {
public float MaxHp;
public static float Hp;
GameObject enemy = GameObject.Find("Enemy1");
GameObject boss = GameObject.Find("Boss");
void Awake()
{
AssignStats(enemy, MaxHp);
}
public static void AssignStats (GameObject en, float MaxHp)
{
if (en.tag == "Enemy1")
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
if (en.tag == "Boss")
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
}
}
This code doesn't seem to work. Why?
Enemy Class : Enemy.cs (not Monobehavior)
using UnityEngine;
[System.Serializable]
public class Enemy
{
public EnemyType EnemyType;
public GameObject EnemyPrefab;
public string EnemyTag;
public int MaxHealth;
public int EnemyDamage;
public Vector3 SpawnPos;
private int _currentHealth;
public void Init()
{
_currentHealth = MaxHealth;
}
public void UpdateHealth(int newHealthValue)
{
_currentHealth = newHealthValue;
}
public void ReceiveDamage(int damage)
{
var updatedHealth = _currentHealth - damage;
UpdateHealth(updatedHealth > 0 ? updatedHealth : 0);
}
}
Enemies Class : Enemies.cs that manage all enemies, randomize between enemies
using UnityEngine;
public enum EnemyType
{
Enemy1,
Enemy2,
Enemy3,
Enemy4,
Enemy5,
Boss
}
public class Enemies : MonoBehaviour
{
public Enemy[] AllEnemies;
//Initial Value
public int NumberOfEnemies = 3;
private void Start()
{
InitEnemies(NumberOfEnemies);
}
public void InitEnemies(int howManyEnemies)
{
for(int i= 0; i < howManyEnemies; i++)
{
var randomIndex = Random.Range(0, AllEnemies.Length - 1);
SpawnEnemy(AllEnemies[randomIndex]);
}
}
public void SpawnEnemy(Enemy enemy)
{
Instantiate(enemy.EnemyPrefab, enemy.SpawnPos, Quaternion.identity);
enemy.Init();
}
}
You can see that I assigned all enemies data in the inspector that come up from the array of Enemy in enemies class, it has Enemy Prefab, position, damage etc.
If you have any question, feel free to ask :)
Cheers!
if I understand corrlcy.
you don't need to pass parameters to AssignStats method because the all you need property in the class.
I would use gameObject.tag to get the current append object tag.
if you append to Enemy1 component you will do gameObject.tag == "Enemy1" condition.
if you append to Boss component you will do gameObject.tag == "Boss" condition.
you just append the script to your role component and tag right tag.
using System.Collections;
public class Enemies : MonoBehaviour {
public float MaxHp;
public float Hp;
void Awake()
{
AssignStats();
}
public void AssignStats ()
{
if (gameObject.tag == "Enemy1")
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
if (gameObject.tag== "Boss")
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
}
}
I would do it like that :
//enum contains all your enemies
public enum EnemyType
{
Enemy1,
Boss
}
public class Enemies : MonoBehaviour
{
//This will be assigned in the inspector
public EnemyType CurrentEnemyType;
//You don't need them to be public since you are hardcoding them.
private float MaxHp;
private float Hp;
void Awake()
{
AssignStats();
}
public void AssignStats()
{
if (gameObject.CompareTag(CurrentEnemyType.ToString()))
{
if (CurrentEnemyType == EnemyType.Enemy1)
{
MaxHp = 50;
Hp = MaxHp;
Debug.Log(Hp);
}
// instead of doing separated if blocks, you need to do if else for less code execution
else if (CurrentEnemyType == EnemyType.Boss)
{
MaxHp = 500;
Hp = MaxHp;
Debug.Log(Hp);
}
/*
More simplified way instead of the if else, if you assume that all your enemies except the boss have 50 hp.
MaxHp = CurrentEnemyType == EnemyType.Boss ? 500 : 50;
Hp = MaxHp;
Debug.Log(Hp);
*/
}
}
}
Cheers!

Categories