Rumble Crumble Logo

Devlog 4

Powerup Generation Changes


Powerup Generation Problem

I touched on this briefly in the previous devlog, but just to reiterate the issue at hand, powerup spawning is quite imbalanced. The way powerups spawn is, every x rows of tiles (+/- some small random amount), an unlocked powerup is chosen at random (literally just random indexed list of all the unlocked powerups) and placed in an unused tile in that row. There’s also a buffer at the top to stop powerups spawning too close to the end. x is just set to 20 with a +/- of up to 10% of that, so on a 50m run, you will always get 2 powerups spawning, a 100m run will get 4 or 5 powerups depending on the random offsets, and a 250m run will always have 12 powerups. Then endless works in exactly the same way, just when more blocks are generated, more powerups generate from the last set upwards.

This might not seem like an issue at first glance, and it’s not, it actually works quite well; the issue only arises when you play through the game from the start and actually start unlocking things. As soon as you unlock the first powerup (Rhino Beetle), that is the only powerup that can get chosen at random, and so every 20 blocks you will get a Rhino Beetle spawn! This means that until you unlock another powerup, the whole time you are playing, you are powered up as Rhino Rumble! Now this is quite fun (and we might make some sort of special game mode where you can choose what powerups you want all the time or something) but not really intended, it makes the difficulty drop off a cliff (to zero because it’s impossible to die as Rhino Rumble) when you unlock the first powerup, and just generally it forces you to play in this way, which we don’t want.

The Test Scene

This has been in the back of my mind for a long time, basically since we first had people playtest the game (that’s the only reason we found it by the way, playtesting is so important!).

Just changing to exactly the same spawning mechanics as items didn’t work very well, because they are technically rarer than items, there was a high probability of none spawning on 50m runs when you only had one or two unlocked. And sometimes you would get like 10 of the same powerup spawning on a 50m run! Now that could be fudged, just for the lower depths, but again not really ideal, I wanted to come up with an agnostic solution that would stand up to anything, and still work well on endless mode as well.

So I created a whole test scene for running simulations on powerup generation. This involved a bit of refactoring which is always nice, and once it was set up I almost immediately saw the solution through all the numbers. Here’s a video of the test scene in action, basically there is an Editor GUI in the inspector, which allows me to choose the generation parameters, like what the current score is (for calculating which powerups are “unlocked”), and the difficulty and depth. Then a button which simulates generationCount number of levels with those parameters and prints out a load of information about the powerup generation in the console.

Now this is with the powerup generation logic just switched to the same as the item generation, with some base generation weights of about once per 50 blocks (you can see the exact numbers for each powerup in the baseSpawnChance console log).

This prints numbers in the format (minSpawned - maxSpawned) --- averageSpawned once for each individual powerup (so how many Ginger Roots were spawned over all the generations) and then afterwards it prints a summary in the same format, for all powerups across all generations.

Now I spotted the problem almost straight away when I was playing around with this, look at the totals for the HARD - ONE (50m) runs over 1000 generations. It shows an average powerup spawn count (across ALL powerups) of just below 1, with a min of 0 and a max of 13! Then when moving up to 250m on hard, it has a similar average of around 2 powerups, but a max of 34!

So the variance was just way too high, not only was the average spawn rate really quite low, but the variance was absolutely massive.

The Solution

The idea then was to tighten the range on all of these, and have it all slightly more controllable (and not ever have ZERO powerups spawn once you’ve unlocked them, that’s the whole point in having them unlocked!).

We needed some sort of lower bound for the total number of powerups, and an upper bound for each powerup, but only when you haven’t unlocked many, because too much repetition early on is weird as stated before.

So here are the steps I now use to generate powerups:

  1. Create a dictionary where the key is each unlocked powerup, and the value is the total number of all unlocked powerups like so PowerupCountMap
    • This will create a map like this:
      {
        rhino: 4,
        ginger: 4,
        starblock: 4,
        starshroom: 4,
      }
      

      Where in this scenario there are 4 powerups unlocked, and so each one will have a cap of 4 of itself being spawned in a level

  2. Calculate a maximum random amount of powerups to spawn (1 per 15-24 rows), store this number as the absolute maximum number of powerups to spawn
    • This is dependant on the Difficulty only, and set in a ScriptableObject which stores settings about each difficulty
      • So Easy is 24 and Extreme is 15 for example
    • Along with a slight variance, also stored in the same SO, so each one can go up or down by anything in the range of the variance
      • So Easy might turn out to be one powerup every 22, 23, 24, 25 or 26
  3. Loop that number of times, for each iteration:
    1. Pick a random y coordinate in the range provided (bottom to top of the current level, or bottom -> top of the next generated “chunk” in endless)
    2. Pick a random x coordinate at that y coordinate that is unoccupied by another powerup or item
      1. If there are none (very very very unlikely) then pick another y value and go again
    3. Pick a powerup based on the spawn weight and if that powerup still has a count in the dictionary then subtract one from there, and spawn the powerup at that coordinate
      • If it’s finished and there are still powerups with max values left, pick another powerup
      • Otherwise exit out of the loop as we’ve exhausted the dictionary cap for powerups

So there are two max limits here, one is an absolute maximum number of powerups spawned, and the other is the max per powerup that can be spawned. The per powerup maximum also works as a minimum in cases where the total of all of those is smaller than the absolute maximuim spawn number.

Here are a few screenshots of the same console logs from the test scene but with this new generation logic implemented:

powerup generation console log at easy 50m Firstly on Easy 50m with only one powerup unlocked

powerup generation console log at easy 50m Then Hard 100m with 5 powerups unlocked

powerup generation console log at easy 50m Finally Extreme 250m with 8 powerups unlocked

So you can see that there is a good amount of variation, where each powerup’s weight is taken into account for how often it spawns, but also we have good min/max values for total powerups spawned, and at the most basic of one powerup available, only 1 spawns.

Conclusion

From my current playtesting of this it does seem much more enjoyable than before, it gets progressively more interesting (or chaotic!) as you unlock more and more powerups, as opposed to having a sudden spike in powerups, then plateauing as it did previously. I’m much more happy with this solution than before, might just need some tweaks to those settings values as we play and test more and more.