This is part 2 of a series of occasional articles on procedural narrative — see part 1 for the explanation, but in short, these are some ideas I’ve been throwing about for controlling or generating the player’s narrative experience of a game procedurally. For a given value of narrative, which not everyone might agree with.
I talked in the last article about using an Experience Director to control various things, including the stress the player is under; the ebb and flow of the game. Here’s a really simple set of ideas to illustrate what I mean.
Let’s say, for argument’s sake, that we have a thing called a stress level. Which is a number. Let’s say 0-100, where 0 is totally relaxed and 100 is panicking. Unrealistic, sure, because who knows the limits to panic, but let’s use that as a starting point.
Now let’s pretend that we can measure it. I’m not advocating sticking an ECG on a player — I’ll come on to how to measure it later — but for now let’s say we can measure what this stress level for our player is at any point.
Say we also had a target stress level for any point in the game. After 5 minutes play, we might set an arbitrary target stress level of, say, 35. There’s obviously an opportunity to write a formula like:
target stress level – player’s current stress level = stress we need to apply to make the player hit the target stress level
If our target level is 35, and we’ve measured the player’s stress is 15, we need to make stuff happen that ups the player’s stress by 20.
Well, sure, that’s pretty obvious.
Likewise, if we’d measured the player’s stress as 40, we need to cool it down by 5.
What should our target stress level be at any point? Anything, really, but let’s think of an example — in an action game, you probably want the stress to slowly rise until it hits a peak, and then drop rapidly to give the player a breathing space — but maybe not all the way to the bottom — and then rise again. Repeat until the stress is maxed out. (Or forever, if you’re feeling really nasty.) So the graph of our target stress might look like this, where x is time and y is our target stress level:
(Note for those interested: the function here is tan( fmod(x/2,1) )*32 + x*2 – which gives us this gradually rising series of curves.)
Generating it from a formula like this is probably easiest — but we could also lay down some bezier curves in an editor, or do anything, really, but let’s take as a given that we will have a number between 0 and 100 as our target for each frame.
Now let’s work out the other bits.
How do we measure stress?
This entirely depends on the game, and is pretty arbitrary, but let’s have a stab at doing it for a hypothetical game.
Let’s say we have a top-down game where we control the main character and can be attacked by enemies on all sides. For simplicity, let’s call the enemies zombies. And that we can shoot them. If anyone breaks my copyright on this totally original idea, I will sue! 🙂
We need to figure out a stress level. It’s pretty arbitrary how we do that, but we could calculate it from a whole bunch of factors, including these:
- How low is the player’s health?
- How much ammo does the player have left?
- How many enemies are nearby?
- How many enemies are really, really close?
- Is the player surrounded on all sides?
- Is the player running, walking or staying still?
- How frantically is the player changing direction?
- How frantically is the player mashing the fire button?
- Is the player running in the opposite direction from a bunch of zombies? i.e. is the player fleeing?
Each of those things is entirely measurable by a game. Each can either have a yes/no value applied, or a number, and with some adjustment you could easily come up with a value for each line that goes from 0-100.
health stress factor = current health / ideal health * 100
ammo stress factor = current ammo / ideal ammo * 100
And then we can combine all those numbers depending on how important we think each factor is, into one big number ranging from 0-100, using the power of Maths. e.g.
stress level = (health stress factor * proportion we care about health) + (ammo stress factor * proportion we care about ammo) …
And so on, where the proportions are fractions that all add up to 1. That should give us a number between 0 – 100 for our player’s stress level. Neat!
Note that we can endlessly tweak and make any of those formulas more complex. Perhaps the relationships aren’t linear, but you need curves — if you’re very low on health, you probably are 10x more stressed than if you’re at a quarter of your health, for example. If you’re actually out of ammo, you’re probably massively stressed. Everything can be tweaked, and you can add as many more factors as you can dream up. Is the player in a dead-end corridor? Is the player’s co-op mate nearby, or far away? Does the player have a bazooka?
How do we adjust stress?
Fairly obviously from our measurement example above, we can see some factors we could adjust to raise or lower the level of stress we measure.
To raise the stress, we can throw in more enemies. Or we can speed them up, or direct them towards the player. Or move to surround the player. Or bring in a tougher enemy.
To lower the stress, we could drop an ammo pack if the player is low on ammo. Or the same for health. Or slow down some enemies. Or quietly remove them, if they’re off-screen. Or lower enemies’ health levels. Or all sorts, really.
That’s pretty obvious, but it’s not entirely clear how to remove stress accurately — there’s a certain amount of guesswork in doing any of those things.
Or is there?
Well, we have formula, exact formula, above, for figuring out how much adding a health pack changes our measured stress level. But it isn’t a concrete number, because it all depends on the rest of the state. But we should have a fair idea which of our factors have the biggest effect (based on the proportions above). Here’s a naïve implementation:
- Every few frames, measure the current state using the formulas above.
- Look at how much we need to adjust it to hit our target level.
- Pick an appropriate factor to lower / raise, depending on how big the adjustment is. Say, drop a health pack. Or add an enemy.
- Apply that change to the current state, recalculate the measured stress level based on that, see if that’s adjusted it to our target level.
- If we’re still under or over, pick a less important factor and throw that in to the mix.
- Repeat until we’re within a rough tolerance of what we want. (If we want to be 100% accurate we’ll be here all night — all our factors will change the values in lumps, not in very small granularities. This doesn’t matter, we’ll adjust back the other way in a couple of frames.)
- Let the game play on for a few frames, after which go through the whole process again.
Of course, it’s not really that simple
There are a bunch of other things you probably want to do, if running this in a real game. One is to smooth the values of the stress measurement, as it’ll change wildly from frame to frame — so average it over a bunch of frames. Another is to look into the future — dropping a health pack won’t affect the stress level now, it’ll affect it in a couple of seconds when the player has picked it up. Same with adding an enemy, the player won’t notice it for a bit. So actually we should be looking at the overall trajectory of the stress level, and working a couple of seconds behind that. But you get the general idea… with the sort of things I’m talking about above, we can put in simple rules to control the ebb and flow of the game and then play it forever without having to figure out another monster spawn.
Right, that’s enough for now. The next article will go into ramblings about creating better quests for open-world RPGs.