Jetboard Joust Devlog #47 –Having Difficulty With Difficulty

With all the core gameplay elements pretty much in place it’s time to get back to some serious gameplay testing and start thinking in more detail about how I manage the difficulty curve within the game.

I’d already put quite a bit of thought into this as discussed here, but, as is so often the case, Jetboard Joust has grown in complexity fairly significantly since I posted that and my ‘procedural difficulty’ code needed to be reworked in a major way.

I’m still starting from a similar standpoint in that I allocate a difficulty value for each level and then create random waves of enemies that total that difficulty score. Now, however, I have different RPG-style ‘character-levels’ of enemies and weaponry to consider.

Firstly what I do is allocate a series of character-level ‘stats’ to each enemy and weapon type. I set a minimum and maximum value for each stat and the values in between are calculated automatically. Some values (e.g. health, weapon range) are consistent across all enemies and weapons but not all. Every weapon and enemy has a ‘difficulty’ stat.

I then create an EnemyDefinition for each enemy/weapon combo. This is a lot of definitions as I have to have create a separate definition for every combination of each character-level of enemy and weapon.

When a level is created my first approach was to split the total difficulty score into a set of six ‘batches’ of enemies that are released at set time intervals. The enemies that make up each batch were chosen at random from the EnemyDefinition collection. If a batch of enemies is destroyed the next one is released immediately.

This worked pretty well but the combination of enemies was too random and in order to get a more playable selection I needed to implement a few restrictions…

1. Every enemy and weapon type have an ‘intro level’ so that they don’t appear until a certain level of the game has been reached.

2. Every enemy and weapon type have a’level up rate’ that affects the way their ‘character-level’ progresses throughout the game – so, for instance, an enemy with a ‘level-up rate’ of 2 and an ‘intro level’ of zero could only appear at character-level 1 for the first two levels of the game, then at character-level 1 or 2 for game levels 3 and 4 and so on.

3. Only certain enemies will try and abduct the alien babies(!) – as this is fundamental to the way the game plays I needed to ensure a certain amount of ‘baby-chasing’ enemies per batch. I know that sounds a bit dodgy!

After implementing these restrictions the enemy selection was much better but I realised the process was still flawed. As I was choosing enemies from the set of EnemyDefinitions at random (albeit with the above restrictions) the selection was skewed towards certain types of enemies. There would always be many more valid definitions for lower ‘intro-level’ enemies (especially as we have a definition for every character-level and weapon combo) resulting in far too many of certain enemy types in the game.

To solve this I needed to create a structure to store the valid EnemyDefinitions that was not simply a flat list – so I created the wonderfully-named EnemyDefinitionBucket class.

A EnemyDefinitionBucket contains a horrible-looking data structure that’s defined like this…

SortedDictionary<EnemyTypes, SortedDictionary<WeaponTypes, List>> dictionary;

..so first we have a collection of every valid EnemyType in the bucket, then for each EnemyType a collection of each valid WeaponType, and finally for each EnemyType/WeaponType combination a list of each valid character-level of enemy and weapon that fits that combination.

Now when I choose a random EnemyDefinition I first select a random EnemyType, then a random WeaponType, and finally a valid EnemyDefinition that matches that combination. This ensures that all enemies and weapons appear on equal footing.

Only that wasn’t quite good enough! In practice the enemy/weapon selection needs to be skewed towards those that have an ‘intro level’ nearest the current game level. Hence the reason I have used SortedDictionary rather than a standard Dictionary – this way the enemy and weapon type ‘keys’ can be indexed in order of ‘intro level’ and I can implement a sine-based distribution curve that favours the ‘higher’ items when choosing at random. The EnemyDefinitions are also stored in a sorted List and selected in a similar way.

Finally I think that’s done it, now on to tweaking the various ‘character stats’ which is another rabbit-hole.

And, sorry, this post doesn’t contain much to look at so I’ve just included some random gameplay footage – finally found an app Capto that grabs at 60fps!

Dev Time: 3 days
Total Dev Time: approx 74.5 days

previous|next

mockup_3x
The Current State Of Play

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: