Preface – I’m not a mathematician in any way, shape or form.
Shamefully, one of the things that’s always bugged me about any game I’ve ever written was the difficulty was explicitly defined. In other words, I would always have a method that would check the current level and apply random scalars to settings depending upon that check. Does it work? Yes. Is it intuitive? No. Does this make for a horrible game experience? Yes and no (more yes).
Without diving too far into the rabbit hole, I wanted a way to automatically increase the difficulty of a game while still retaining certain qualities. Simply ramping the difficulty could be achieved in a number of ways depending on how you want the game to be. If you want it to react to how the player makes decisions in the game, well that’s a whole other issue that I’m not going to dive into right now as it could get complex really quick. For the project that I’m working on now, I wanted something a little more simple. Why not a logarithm?
Logarithmic graphs are really nice to look at but present some interesting hurdles that you need to compensate for in other ways.
First of all, I decided that I’d play with the natural logarithm instead of another base. I like smaller numbers as they’re always easier to manage (plus, I’m lazy). To entangle it a bit more with the game, I decided to take the natural logarithm of the value of the current level multiplied by two, squared. So it would look something like this:
(float)Math.log((Math.pow(currentLevel * 2, 2)));
This little modification to the natural log gives a relatively nice curve.
This is just a sample of what I was getting. But why use the current level multiplied by two then using the value of that number raised to the power of two? Because it produced the nicest results in all of the testing that I did plus it ties into the current level which helps to give the impression that the difficulty scales with the levels. Levels pushing into the mid to upper 90’s produce a value that’s 10.xx and quickly ramps up from there which can cause some problems in systemic calculations (since you’re actually using 1.x (1xx.xx%) as a multiplier which will always give you at least the number you’re multiplying against which is not desired in most cases). However this fact is taken into consideration and compensation is made for it where needed.
So what can you do with this value? Well, in the simplest terms, the possibilities are endless. And, frankly, you don’t even have to calculate it the same way I did. You can do whatever you want, so long as it makes some sense. But I chose to tie it to the level and amp it up a little (the base value returned from ln(level) was a little low, even for my tastes). The great thing about this seemingly insignificant number is that it can actually hold a lot of weight for procedurally generating difficulty scalars (which is basically what this is). Variations of this value (mostly being derived from rounding functions like floor or ceiling) can be used to help determine the total number of objects to work with in a level or how many enemies to pack into a wave. If the player has a goal that’s a fraction of a total number of objects, this value can be used to help determine that percentile and you can apply it there and elsewhere. You can also use it as an abstract base in probability calculations so that you can make things happen more slowly in early levels and more quickly in later levels, helping to increase that sense of difficulty.
Another thing to keep in mind that could potentially help is that an exponential curve is the inverse of a logarithmic curve. So if you find yourself in a position where you logically need to do things that go against the logarithmic curve but still need to retain that difficulty, having a second measurement from the exponential curve of the same method that calculates difficulty could be beneficial.