## Sigmoid Functions in Game Design

A lot of the challenge of good procedural generation is in appropriately setting how much of something should be included as a function of something else. Getting this blending right can be a tricky business, and I’ve been slowly collecting a library of mathematical functions that have interesting properties suited to this task. I thought I’d kick off this blog with a post describing my favourite of these functions, a sigmoid function,

$$ f_{S}(x; \sigma) = \frac{x^2}{\sigma^2 + x^2}$$

Sigmoid functions are simply functions that give an S shape when plotted. There are many such functions known, but \(f_{\mathrm{S}}\) has some particularly appealing properties that allow the developer a great deal of control over its shape, whilst staying simple and efficient to evaluate.

I first came across \(f_{\mathrm{S}}\) in the context of Tikhonov Regularisation where it acts as a filter, removing noise from ill-posed matrix equations. After working with it for a while, I started to notice some of its nice characteristics and began to wonder if it can’t be put to some good use in procedural game generation…

The function \(f_{\mathrm{S}}\) is deceptively simple depending on one variable, \(x\), and one parameter, \(\sigma\), and returning a number in the range \(0 \leq f_{\mathrm{S}} < 1\). By plotting it we can see exactly why itand sigmoid functions in general is so well suited to procedural generation:

The function resembles a player’s skill within a game as it progresses over time. We can see 3 clear areas,

- An initially flat upwards curving slope (green) where an inexperienced player begins to develop familiarity with mechanics.
- Next follows a region of roughly linear progression (yellow) as the player develops a solid understanding of the mechanics.
- Finally the progression curves downwards and becomes flatter (red) as the player masters the game and approaches the skill ceiling of the game.

We can use this similarity to the player’s experience to control the difficulty and features encountered in a procedurally generated game to match the progression of our players.

Despite its simplicity a great deal of control over \(f_{\mathrm{S}}\) is present through the parameter, \(\sigma\), that defines the overall rate of the progression. Intuitively \(\sigma\) sets the value of \(x\) at which the function returns 0.5 and the progression curve begins flatten out. You can explore how changing the value of \(\sigma\) affects \(f_{\mathrm{S}}\) in the interactive plot below,

σ: 13 |

### Application and Analysis

So what are some ways we can use \(f_{\mathrm{S}}\) in procedural generation?

Most clearly we can use \(f_{\mathrm{S}}\) when we want to control the amount of one thing as a function of another numerical variable. We can do this with any pair we can express as two numbers but if both are continuous variables then some simple analysis reveals some useful relations.

Lets explore an imaginary procedural RPG called ‘Level Quest’. Unlike most RPGs, the hero in Level Quest gains experience and becomes stronger simply as time progresses, rather than through player actions, with the rate of experience gain controlled by our sigmoid function,

$$ \text{Experience per Second} = f_{S}(t)\times\text{Max Rate}$$

During the course of the game we will want to know how much experience a player has, ideally by simply by knowing how much time has passed. Whilst we could achieve this by incrementing an experience variable we are going to use a little bit of calculus to get a quick answer from the simple form of \(f_{\mathrm{S}}\).

Intuitively, the amount of experience gained by the hero over a given time period is equivalent to the area under sigmoid curve for the period of interest. Lets take as an example the question:

“How much experience will the hero gain in the time window between 7 and 23 seconds of play, given \(\sigma\) = 15 and a maximum rate of 20 exp per second?”

This is expressed mathematically as the definite integral,

$$ \text{Experience} = \int^{t_{\mathrm{Start}}}_{t_{\mathrm{End}}} \frac{x^2}{x^2+\sigma^2}\mathrm{d}t \times \text{Max Rate} $$

the solution to which is,

$$ \text{Experience} = \left ( \sigma \times \arctan\left ( \frac{\sigma(t_{\mathrm{Start}} – t_{\mathrm{End}} ) }{t_{\mathrm{Start}}t_{\mathrm{End}} + \sigma^2} \right ) – t_{\mathrm{Start}} + t_{\mathrm{End}} \right ) \times \text{Max Rate}$$

Using this equation we can quickly calculate that the hero will gain 307.58 experience points between 7s and 23s. Now we can efficiently work out how much experience a player will have at any time, even if we want to start them off at a time other than 0. This example of a hero gaining experience is admittedly crude but knowing how much of something a player has come across can be a useful tool when trying to balance a game’s design.

### Improving The Function

We already have a nice way of controlling sigmoid progression through the \(\sigma\) parameter but there is another controlling parameter hidden in \(f_{\mathrm{S}}\) that can grant us further control over the progression. This ‘hidden’ parameter is the exponent and we will label it \(\lambda\) from here on,

$$ f_{\lambda}(x; \sigma, \lambda) = \frac{x^{\lambda}}{x^{\lambda} + \sigma^{\lambda}} $$

The new parameter, \(\lambda\), controls how sharp the progression curve is, giving a flatter curve when \(\lambda\) is small and a sharper step-like like progression when \(\lambda\) is large.

With both \(\sigma\) and \(\lambda\) at our fingertips we have an enormous degree of control over the shape of \(f_{\mathrm{S}}\) and hence the variables we want it to control. You can have see the effect of both \(\lambda\) and \(\sigma\) in the plot below, have a play and see how the curve responds to different choices.

σ: 13 | |

λ: 2 |

Unfortunately the calculus we used before to find the area under the curve becomes more complicated when \(\lambda\) is not 2. For some values a relatively simple integral can still be found, but the general case is not so easy. If you’ve settled on a \(\lambda\) value you like you could run it though a tool like Wolfram Alpha and see if the result is usable, but non-integer and extreme values are unlikely to be have useful integrals.

Of course if you aren’t interested in knowing the area under your progression curve then this isn’t a problem.

### Conclusions

We’ve made a small look into some of properties that make \(f_{\mathrm{S}}\) an appealing equation for procedural generation and looked at how we can achieve even more control by varying the exponent parameter. This function is, of course, by no means the only way to achieve smooth transitions, but the simplicity and flexibility of \(f_{\mathrm{S}}\) and its calculus make it appealing for applications in game programming.

The ‘Level Quest’ example used above is nice and simple, but I think the function has much more power when applied to more abstract things like terrain generation and resource placement. In my game Probus I used \(f_{\mathrm{S}}\) to control the occurrence of asteroid objects and made them more common as the player progresses, adding some flavour and complexity to the later systems.

For such a simple thing, \(f_{\mathrm{S}}\) has earned a valued place in my tool-kit of functions, and I keep finding more uses for it. I’d love to hear ideas or other interesting functions, please feel free to suggest any of your own in the comments.

Here’s a two-parameter sigmoid-ish function I use when I want something that has flat tangents, a tunable maximum rate of change, and a tunable bias in whether the change occurs early or late.

https://www.desmos.com/calculator/3zhzwbfrxd

Looks nice, especially useful when the input variable can be bounded to the range \(0 \leq x \leq 1\).

$$

g(x; p, s) =\frac{x^c}{p^{c-1}},\;\; \mathrm{where}\;c = \frac{2}{(1-s)}

$$

$$

f_{\mathrm{S}}(x; p, s) = \left\{

\begin{array}{lr}

g(x; p, s) & x \leq p \\

1 – g(1-x; 1-p, s) & x \gt p \\

\end{array} \right.

$$

The parameter \(0 \leq p \leq 1\) controls the position of the turnover and \(0 \leq s \lt 1\) controls how step-like the function is.

Thanks for the suggestion!