I am a beginner in Libgdx and I have a simple Galaga type game setup where the player earns points through waves with different level enemies with various stats. The player can then upgrade certain ship stats with these points. Mostly everything is done all I am focused on now is making the gameplay balanced between player ship stats and the different enemy stats as the player progresses. I want the game to be infinite as in the player can go on for as long as they can last but I can't seem to figure out how to set up the enemy spawning so that as the player progresses the enemies have different/harder stats and there are more enemies.
Here is my spawnEnemies
method in my GameScreen
class which adds the EnemyShip
object to an array that is iterated through and then each ship is rendered in the render method.
public void spawnEnemies(float deltaTime) {
waveTimer += deltaTime; // sets to currentTime
if (waveTimer > timeBetweenWaves) { // if after time between waves
enemySpawnTimer += deltaTime;
if (enemySpawnTimer > timeBetweenEnemySpawns && enemiesSpawned < maxEnemies) { // after enemy spawn timer and only if its less than max enemies
enemyShipList.add(enemyType()); // adds to enemy ship list which is then iterated through and rendered
enemiesSpawned++;
enemySpawnTimer -= timeBetweenEnemySpawns;
}
}
if (enemiesDestroyed == maxEnemies) {
nextWave();
}
}
The ship added to the list is determined by the current wave in this enemyType method:
private EnemyShip enemyType() {
if (waveCounter >= 1 && waveCounter <= 2) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 25, 2, 2, 0, 34,45,0.8f, Assets.instance.enemyShips.ENEMY_BLACK_01, Assets.instance.lasers.LASER_BLUE_05,6f,.4f);
} else if (waveCounter >= 3 && waveCounter <= 6) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 50, 4, 4, 1, 38,45,0.8f, Assets.instance.enemyShips.ENEMY_BLUE_03, Assets.instance.lasers.LASER_RED_05, 6f, .4f);
}else if (waveCounter >= 7 && waveCounter <= 10) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 75, 4, 5, 1, 42,50,0.8f, Assets.instance.enemyShips.ENEMY_BLACK_02, Assets.instance.lasers.LASER_BLUE_04, 6f, .4f);
}else if (waveCounter >= 11 && waveCounter <= 14) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 100, 6, 6, 2, 45,54,0.7f, Assets.instance.enemyShips.ENEMY_GREEN_03, Assets.instance.lasers.LASER_BLUE_05, 6f, .4f);
}else if (waveCounter >= 15 && waveCounter <= 18) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 6, 6, 2, 48,58,0.7f, Assets.instance.enemyShips.ENEMY_RED_04, Assets.instance.lasers.LASER_GREEN_03, 6f, .4f);
}else if (waveCounter >= 19 && waveCounter <= 24) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 8, 7, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_GREEN_04, Assets.instance.lasers.LASER_RED_03, 6f, .4f);
}
else if (waveCounter >= 25 && waveCounter <= 28){
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 125, 8, 8, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_BLACK_02, Assets.instance.lasers.LASER_RED_05,6f,.4f);
}else if (waveCounter >= 29 && waveCounter <= 32) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 150, 8, 9, 3, 50,60,0.7f, Assets.instance.enemyShips.ENEMY_BLACK_04, Assets.instance.lasers.LASER_GREEN_13, 6f, .4f);
}else if (waveCounter >= 33 && waveCounter <= 35) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 150, 9, 10, 4, 54,64,0.6f, Assets.instance.enemyShips.ENEMY_RED_02, Assets.instance.lasers.LASER_BLUE_12, 6f, .4f);
}else if (waveCounter >= 36 && waveCounter <= 39) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 175, 9, 12, 4, 58,65,0.6f, Assets.instance.enemyShips.ENEMY_BLACK_05, Assets.instance.lasers.LASER_BLUE_10, 6f, .4f);
}else if (waveCounter >= 40 && waveCounter <= 45) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 175, 10, 12, 5, 60,68,0.6f, Assets.instance.enemyShips.ENEMY_GREEN_05, Assets.instance.lasers.LASER_RED_03, 6f, .4f);
}else if (waveCounter >= 6 && waveCounter <= 49) {
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 200, 12, 14, 5, 64,70,0.5f, Assets.instance.enemyShips.ENEMY_RED_05, Assets.instance.lasers.LASER_GREEN_12, 6f, .4f);
}
return new EnemyShip(StarshooterGame.random.nextFloat() * (WORLD_WIDTH - 10) + 5, WORLD_HEIGHT - 5, 220, 14, 1, 0, 68,80,0.1f, Assets.instance.enemyShips.ENEMY_BLUE_05, Assets.instance.lasers.LASER_BLUE_05, 6f, .4f);
}
I previously had separate EnemyShip
subclasses (i.e level01Enemy, level02Enemy) but then changed it to just the parent EnemyShip
since I thought there was no point to having separate classes as I was only changing the stats and ship/laser texture regions. I then hardcoded the stats in each. This is a temporary solution, but I want to write clean code and not have to hardcode all the stats. If I have to change my whole approach or I have terrible code let me know because, as I said, I am a beginner.
Randomize enemy spawnings
To randomize the number of enemies that spawn you could simply decrease the value of
timeBetweenEnemySpawns
and increase the value ofmaxEnemies
by some function. For example you could do this in yournextWave
method:In a similar way you could increase the damage, health or other stats of your enemy ships.
Clean Code
Writing clean code is always a good idea, because otherwhise your code will get more and more ugly with every iteration, till you can no longer handle it and have to abandon the project. That's a lesson every coder has to learn :)
Unfortunately writing clean code is not as easy as hard coding all EnemyShip stats for every wave, so don't be disappointed if you don't understand all of the following code directly.
To create objects, with different parameters it's a good idea to use a Factory Pattern. So you just call a factory method (using very view parameters) to create the objects.
A realy clean solution to configure the enemy ships would be to use a data driven approach. That means you don't configure the enemy ships in the code, but use (more structured) configuration files for this. I would recoment to use JSON for this.
Another solution (which is not that clean but easier) would be to use an enum to configure the parameters for the enemy ships (like in the EnemyShipFactory.ShipLevel enum).
Putting it all together it could result in a solution like this:
EnemyShipFactory
EnemyShipStats
EnemyShip
enemy_ship_stats.json
Now you can change your
enemyType
method like this: