Games often have to make discrete decisions based on continuously varying analog input values.
This happens a lot when making animation systems respond to controller inputs:
if (speed > 0.7) ShowRunAnimation(); else if (speed > 0.1) ShowWalkAnimation(); else ShowStandStillAnimation();
Also when making AI decisions:
if (health > 0.5) AttackEnemy(); else RunAway();
And when playing sounds in response to the state of a physics simulation:
if (sidewaysVelocity > frictionFactor) PlaySkidSound(); else StopSkidSound();
And when rendering graphics:
In fact this sort of thing happens all over the place. Games are complex simulations that contain a lot of chaotically varying information, and they often need to examine the simulation state and make decisions based on specific values.
The problem is, game simulations tend to produce a lot of small and basically random fluctuations from one frame to the next. When these values are used to control a discrete decision, and when the input value is close to the decision threshold, the results can look pretty silly:
- If the player holds the thumbstick right on the dividing line between the run and walk speeds, every little twitch of their fingers will make the animation flicker back and forth between two different states.
- An AI character might decide to attack you, only to change his mind on the next frame and run away, only to heal slightly and decide to attack you again, with the end result that he just spins in a circle.
- If your vehicle is right on the edge of loosing traction, the skid sound may flutter on and off because of tiny variations in the amount of friction.
This stuff is distracting and breaks the suspension of disbelief.
The solution is to add hysteresis in your decision making code. Any time you find yourself making a boolean decision by testing a floating point value:
if (inputValue > threshold) DoSomething(); else OtherThing();
you should think about including a hysteresis region:
const float hysteresisAmount = 0.1; if (inputValue > threshold + hysteresisAmount) DoSomething(); else if (inputValue < threshold - hysteresisAmount) OtherThing(); else KeepOnDoingWhateverYouAlreadyAre();
This prevents tiny fluctuations from causing unwanted flickering in the decision result. If the input value is significantly above or below the decision threshold the game will change state, but when the input is near the threshold it just keeps on doing whatever it had previously decided.
Smaller hysteresis amounts make the decision change more easily (increasing the danger of flickering), while larger values make it more stable and less inclined to change state.
The need for hysteresis is not unique to games. Similar situations crop up all the time in real life engineering. Consider a water heater hooked up to a thermostat, for instance. If the thermostat was too eager to turn the heater on and off, the water temperature would end up incredibly close to the thermostat setting, and the heater would constantly be switching on and off in response to every tiny temperature fluctuation. This would be ridiculous, so thermostats don’t actually work that way. Instead, they wait until the water drops to maybe 4 degrees below the thermostat setting. When that happens they switch the heater on, and leave it on until the water reaches 4 degrees above the desired temperature, at which point they turn the heater off and let the water start cooling down again. The resulting temperature is less precise, but now the heater runs in long bursts at occasional intervals rather than irritating you by constantly flickering on and off.
That is hysteresis in action, and games need it every bit as badly as water heaters do.