Posted On: 2021-04-12

Last week I wrote a bit about how Stamina systems are useful in multiple genres. In today's post, I'll walk through some of the considerations a game designer might explore when adding stamina to an action game. For clarity - while I don't personally have experience with this particular task*, many of the design considerations here are either applicable to a variety of problems, or are visible when analyzing the design of others' action games.

At their heart, game systems are built on mathematics, and (for better or worse) there are a number of decisions and trade-offs that go into picking what kind of mathematical system to use. In programming generally, there are two main ways of representing a number: integers and floating-point numbers*. Integers, as the name implies, cannot use decimal or fractional values, but they instead represent a numerical space separated into discrete intervals. Integers are excellent tools for any problem space that never needs to work with increments smaller than one - and simple arithmetic can enable using integers in many other fixed intervals as well (ie. multiplying everything by 10 to allow for increments of 0.1 size.)

For many systems, however, integers cannot adequately capture the range of possible values. floating-point numbers offer an alternative, promising a (pseudo-)continuous numeric space that allows for a much wider variety of supported numbers. Working with them, however, comes with some unexpected quirks, as many floating-point implementations permit calculations that are technically impossible to represent - instead providing a number that approximately matches the expected value**. Despite these quirks, floating-point is often the go-to tool for mathematics that need to support a continuous space.

For a stamina system, the choice between using integers or floating-point numbers is an important one. Integers will be more stable, making it easier to determine precise amounts, even when using multiple small increments (ie. what is the current stamina after 100 regeneration ticks). The down-side, of course, is that the size of those increments must be pre-determined, ruling out a number of possible options*. Thus, floating-point may seem like a reasonable default choice for stamina, but it comes with the wrinkle that floating-point rounding errors can be compounded if used repeatedly. If the game is recovering stamina every frame (~1/60th of a second), small errors can add up. Thus, the choice of which is best for a stamina system will largely be dependent on the kinds of multipliers/effects that one needs to support. Too much flexibility will demand floating-points, but, if the game allows it, you'll likely get a more stable result by sticking to integers.

Much like picking between integers and floating-point numbers, any mathematics-based system must be anchored to some point on the number line. Numbers are always relative, after all:
a thousand may be large when counting by ones, but it's quite small when measuring things in millions. Stamina systems are no exception to this: one will want to pick a
general scale to work with - whether that's 10s or 1000s. For floating-point, the choice is generally simple: the closer the number is to 0, the more precision is possible
(so it's common to adjust numbers to fit neatly between 0 and 1). When using integers, it's a bit trickier, as the discrete nature of the numbers means that
picking a scale that is too small will be quite limiting. For myself, I generally approach this by taking the smallest possible value (ie. the stamina gain from a single frame)
and then multiply that by 1000. I find this approach makes it simple to deal with whole number percentages, **or** allow for a bit of fine-tuning to that smallest number (though usually not both: a 10% bonus to 1000 works fine, but not if it's tuned to be 997.)

One important thing to note is that it is very likely that the exact number can't be displayed to players (ie. through in-game UI) without modification. With floating-point numbers, this is always a concern (a half-dozen repeating decimal places is never user-friendly), but achieving stamina with integers may require working with numbers that are several orders of magnitude larger than players want to read or reason about. Instead, it's useful to represent it visually (ie. as a meter), and, if the exact numbers still need to be displayed, use a consistent way of simplifying it to a scale that players can effectively reason about (ie. convert them to a percent of the maximum).

Knowing the data type enables approaching the first concrete design problem: picking "how much" for the various stamina amounts. This means picking the maximum amount of stamina available, the cost of each action*, and the recovery rate over time. To help with this, it may be useful to think of all these values in terms of number of actions: maximum stamina is "how many actions before you have to recharge", while recovery rate is "how long before being able to perform another action?"

The exact choice for each of these will vary by game, but it's important to pin down what kind of experience you want, and aim for that using the numbers (iterating as needed.) If you want every action to matter, consider keeping the maximum stamina low. If you want to give players breathing room to make mistakes and recover, a higher max may help. If you want frantic action, a fast recovery rate will minimize downtime - while a slower recovery rate is a better fit for more cautious or thoughtful play**.

The next design problem is how the system behaves when all the available stamina is consumed. As an action game, this is a state players are likely to enter into often (perhaps even by accident), so it's important that it isn't too punishing. One popular approach is to make the character enter into an "exhausted" state that prevents performing additional actions until stamina reaches a certain amount (ie. above 10%). This creates a soft failure state (disincentivizing spending all stamina) that feels bad without actually being a genuine lose state*.

Beyond exhausting the player character, one can further tune the game feel in subtle ways. One way is by making changes to how the system handles trying to spend more stamina than is currently available. One could prevent the action outright, but a player may have a difficult time judging if they have exactly enough at any given moment. When paired with an exhaustion penalty, a designer could allow players to perform actions they can't afford, and count on the exhaustion to make doing so undesirable. Even then, one can further tune the experience, by choosing whether to let the stamina go negative (and incur the full cost of the action) or stop it at zero (and assume the exhaustion penalty makes up the difference.)

A second way to tune the feel is by pausing stamina recovery. If stamina is constantly recovering, even during actions, it can be much more difficult to achieve the desired design*. Pausing recovery for the duration of an action is certainly an effective solution to that problem, but one can go further by extending the pause even after the action completes. By extending the pause in this way, it subtly increases the cost of performing any action, encouraging decisiveness with clear action/inaction boundaries. Importantly, this pause doesn't need to be long (ie. only a few milliseconds) in order for it to impact the feel and decision-making of players.

While this is still a small part of all the available options for tuning the feel for an action game's stamina system, I hope that this exploration has been interesting for you. Numerical details, like what data type to use, have subtle but important effects on the overall design and resulting feel. Perhaps more importantly, however, the game design decisions themselves, from costing to pause timing, shape the flow and feel of every moment of action. Through a well-tuned stamina system, a game can shape a player's relationship with both action and inaction - giving players control over their choices while still maintaining a tight grip on the pacing of every moment.