My game consists of 'waves' of objects called 'spawners', which once every certain amount of time (their firetime), move to a new place on the screen and spawn an enemy. Each wave has 4 important properties:
1: the amount of spawners the wave has
2: the interval of time between creating new spawners
3: the total length of the wave
4: the types of spawners that can appear in a wave (represented as a std::vector<std::pair<std::string, int>>
where the string is the spawner name, and the integer is its spawn weight.
The game works by picking a random spawner from the possible spawner types (with a weighted rng) every new spawner interval. Currently waves are set and are loaded from a file at runtime.
My problem is that I cannot find a good way to randomly generate waves that feel balanced and natural. Currently, I am trying to generate waves based on a difficulty value, mostly using weighted random number generation. However, this does not produce balanced waves that correspond well to the target difficulty. Even after trying several different techniques, I am unable to get a system that generates waves that fell balanced and natural (like the ones hand made).
Is there any way to generate waves that feel natural, based off of the difficulty value? How should I approach this problem?
Also if it's of any help, each spawner also defines its own difficulty value.
Example Waves:
Easier wave:
SpawnerTypes = {'BasicSpawner': 100} (basic spawner has a difficulty of 10)
SpawnerAmount = 5
NewSpawnerInterval = 4
WaveLength = 30
This is a good easy wave because it the spawner it has is simply shoots bullets that move in one direction at a slow speed, which are easy for the player to dodge. It also does not overwhelm the player with large amounts of them, and the wave is not too long
Medium Wave
SpawnerTypes = {'LaserSpawner': 60, 'BasicSpawner': 40} (basic spawner has a difficulty of 10, laser spawner has a difficulty of 20)
SpawnerAmount = 8
NewSpawnerInterval = 3
WaveLength = 30
This is a good medium wave because its combination of spawners requires the player to be paying close attention to multiple parts of the screen other than just right near the character, while still remaining relatively easy. It also spawners more spawners, but balances that by having a small interval between spawners and a small wave length.
Hard Wave
SpawnerTypes = {'RicochetSpawner': 100} (ricochet spawner has a difficulty of 60)
SpawnerAmount = 2
NewSpawnerInterval = 10
WaveLength = 30
Note that this wave has only one spawner type and only 2 of them, but is still much harder than the other listed waves
This wave is hard because the spawner it contains is challenging to keep track of, and in large numbers quickly because almost impossible, so it only spawners two spawners. However, the wave still lasts for 30 seconds, which provides the player with a decent challenge while not completely overwhelming them.
Extremely Challenging Wave
SpawnerTypes = {'RandomSpawner': 30, 'LoopSpawner
: 70} (random spawner has a difficulty of 90, loop spawner has a difficulty of 60)
SpawnerAmount = 4
NewSpawnerInterval = 5
WaveLength = 40
This wave is a perfect example of a wave which is extremely challenging, but is possible for a skilled player. It has two very hard spawners, one of which is very unpredictable (hence the name 'random'). It also spawns 4 spawners, and the wave lasts a total of 20 seconds longer than when the last spawners is created