So, as part of my C++ tutelage, I have been brainstorming and slowly implementing a Wave Spawning System similar to that used in Nazi Zombies. I want to release this as a free example/tutorial (along with the rest of my tps shooter stuff) but I am having a bit of trouble putting it all together. I am going to break a few of the code pieces apart and ask questions or explain why I am using them(I will post the full code ,h & .cpp at the bottom. Feel free to comment on everything as I don’t pretend to know the right way to do stuff (I come from C# so I still sort of think in C# ways unfortunately).
First off I will create a struct that can use data from an excel file.
ZombieSpawnTutorial.h
//creation of enemy information
USTRUCT()
struct FEnemyInfo : FTableRowBase
{
GENERATED_USTRUCT_BODY()
UPROPERTY()
float percentage;
UPROPERTY()
APawn* BotPawnClass; // this will tell the spawner what Pawn to spawn
};
And we reach my first question: basically I am trying to figure out how to format the excel file. The data i need would be stored in type of enemy and waves. So for example:
Wave 1
Zombie, 1.0 chance
Bloater, 0.0 chance
Boss, 0.0 chance
Wave 2
Zombie, .7 chance
Bloater, .3 chance
Boss, 0.0 chance
Etc.
Now, should i have separate excel files for each wave, or is there a way to condense this data so finding a row (or rows) gets the percentages needed to decide what Enemy to spawn.
Anyways, here is the full .cpp for those interested. If some of this looks familiar it is because I am borrow some basic stuff from the free SurvivalGame Template by Tom Looman (of which I am upgrading into a full TPS system in order to learn c++):
ZombieSpawnTutorial.cpp (comments should tell you what is going on and why).
#include "SurvivalGame.h"
#include "SZombieAIController.h"
#include "ZombieSpawnTutorial.h"
AZombieSpawnTutorial::AZombieSpawnTutorial(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
WaveNumber = 1; //current wave number, initialize at 1 for ease of use.
MaxSpawnsPerWave = 0; //The number of enemies spawned during the entire wave, used to end a wave when all are dead
CurrentSpawns = 0; //current number of zombies in the world
MaxSpawnsAtTime = 0; //max number of zombies allowed in the level at one time
CanSpawn = false; //can zombies be spawned?, used to stop spawning during "rest time"
BotSpawnInterval = 2.0f; //intervalbetween spawns at a spawner.
TimeBetweenWaves = 30.0f; //how much time there is between the end of one wave and the start of another
}
void AZombieSpawnTutorial::StartMatch()
{
/* Spawn a new bot every n seconds (bothandler will opt-out based on if CanSpawn is true) */
GetWorldTimerManager().SetTimer(TimerHandle_BotSpawns, this, &AZombieSpawnTutorial::SpawnBotHandler, BotSpawnInterval, true);
}
void AZombieSpawnTutorial::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
/* Immediately start the match while playing in editor */
if (GetWorld()->IsPlayInEditor())
{
StartMatch();
}
//Has the Current number of zombies spawned from the begining of the wave reached the Max Total for that wave
if (CurrentSpawns >= MaxSpawnsPerWave)
{
if (CanSpawn) CanSpawn = false; //stop spawning zombies
//Are all zombies dead? If so End the wave.
if (GetWorld()->GetNumPawns() <= 0) /*BUG: Need to find all of the current players and subtract them otherwise this will never return true*/
{
EndWave();
}
}
}
//Randomly select a Bot from the WaveSpawn Data and Spawn it
void AZombieSpawnTutorial::SpawnNewBot(FEnemyInfo WaveSpawnData) /*Missing:Needs to bring in the WaveSpawnDataArray*/
{
/*MISSING: Need code to randomly select Bot to spawn and Spawn it*/
//After bot is Spawned increment current spawns by one
CurrentSpawns = CurrentSpawns + 1;
}
//Decide when to spawn bots
void AZombieSpawnTutorial::SpawnBotHandler()
{
if (!CanSpawn) return; //if we cannot spawn leave the function
//Check number of pawns in the world and how many have been spawn during the wave
if (GetWorld()->GetNumPawns() < MaxSpawnsAtTime && CurrentSpawns < MaxSpawnsPerWave) /*BUG:Need to subtract the players from the GetNumPawns()*/
{
//Start Spawning
SpawnNewBot(WaveSpawnData);
}
}
//Start a wave
void AZombieSpawnTutorial::StartWave()
{
//clear the Rest Timer so it can be used again later and doesn't continueally update
GetWorldTimerManager().ClearTimer(TimerHandle_RestTimer);
//set current spawns to zero
CurrentSpawns = 0;
//tell spawnhandler it can start spawning again
CanSpawn = true;
/*MISSING: Code to update UI with current wavenumber*/
//this switch initializes the data neeeded for the SpawnHander and SpawnNewBotFunctions, set increments here
/*MISSING: Need a way to store WaveSpawnData*/
/*It Should look like this:
const EnemyInfo enemyData] = {
{ Enemy::ZombieEasy, 100, PawnToSpawn },
{ Enemy::ZombieMedium, 0, PawnToSpawn },
{ Enemy::ZombieHard, 0, PawnToSpawn },
{ Enemy::Exploder, 0, PawnToSpawn },
{ Enemy::Ranged, 0, PawnToSpawn },
{ Enemy::Fast, 0, PawnToSpawn },
{ Enemy::Boss, 0, PawnToSpawn }
};
the first item is the percentage, the second is a reference to the Pawn to be spawned
Should it be hard coded? Or should it be held in a text file somewhere? Needs exploration
*/
switch (WaveNumber)
{
case 0:
///do nothing
break;
case 1:
MaxSpawnsPerWave = 10;
MaxSpawnsAtTime = 10;
//WaveSpawnData ==
break;
case 2:
MaxSpawnsPerWave = 20;
MaxSpawnsAtTime = 15;
//WaveSpawnData ==
break;
case 3:
MaxSpawnsPerWave = 30;
MaxSpawnsAtTime = 20;
//WaveSpawnData ==
break;
case 4:
MaxSpawnsPerWave = 40;
MaxSpawnsAtTime = 25;
//WaveSpawnData ==
break;
case 5:
MaxSpawnsPerWave = 50;
MaxSpawnsAtTime = 30;
//WaveSpawnData ==
break;
case 6:
MaxSpawnsPerWave = 60;
MaxSpawnsAtTime = 35;
//WaveSpawnData ==
break;
case 7:
MaxSpawnsPerWave =70;
MaxSpawnsAtTime = 40;
//WaveSpawnData ==
break;
case 8:
MaxSpawnsPerWave = 80;
MaxSpawnsAtTime = 45;
//WaveSpawnData ==
break;
case 9:
MaxSpawnsPerWave =90;
MaxSpawnsAtTime = 50;
//WaveSpawnData ==
break;
case 10:
MaxSpawnsPerWave = 100;
MaxSpawnsAtTime = 55;
//WaveSpawnData ==
break;
}
}
//End the wave
void AZombieSpawnTutorial::EndWave()
{
//increment wavenumber
WaveNumber = WaveNumber + 1;
//Start the rest timer
StartRestTimer();
}
void AZombieSpawnTutorial::StartRestTimer()
{
//Rest timer will start the next wave after interval
GetWorldTimerManager().SetTimer(TimerHandle_RestTimer, this, &AZombieSpawnTutorial::StartWave, TimeBetweenWaves, true);
}
Any comments are appreciated (particularly if I am doing something wrong or not using the best method). Again, once I get all this working I want to release a full tutorial explaining how to do it.