Hysteresis

Hysteresis

  • Comments 10

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:

    DrawThreeClosestEnemiesAtHighDetail();
    DrawAllOtherEnemiesAtLowerDetail();

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.

  • Now you are talking my language.  I am an engineer, although not a control theory expert.  Your post made me wonder, how much control theory is used in the gaming field currently?

  • Your idea makes sense but the implementation doesn't.

    Say threshold = .7

    const float hysteresisAmount = 0.1;

       if (inputValue > threshold + hysteresisAmount)

    All you've done here is move the threshold to .8 so now the gamer holds the joystick at that point and still catches the break between two animations.

    To work like your example of a thermostat, you 'd have to modify the inputValue to something greater than your threshold.  As gamers generally don't like their input being modified that's probably not a good idea.

    The only way I see to handle the problem your suggesting is to do a moving average of the input value and test to see if that is greater than the threshold.

  • Doh!  It might help if made sure I understood what Hysteresis was first.  Then I would have seen more in your code than I did.  The implementation can't be reduced to one line like I did.

  • Hah, I was going to call you on that, but you beat me to it. :P

  • Ah but Greg and Zombie you have missed the point.    If you hold the input at 0.79-0.81 it will only run the DoSomething() function until you get an input lower than 0.6.  So you don't get a situation where your bouncing back and forth between DoSomething() and OtherThing().  If the input is bouncing between 0.79 and 0.81 there is no transition.

  • Thanks Trayle.  I saw that after I actually went to wikipedia and looked up hysteresis.  On my initial read through of the sample code I effectively reduced the algorithm to one line - which doesn't work with this implementation.

    Still another idea to achieve the same effect is to use a moving average of the input.  The moving average would add the element of time into the calculation in much the same was as the hysteresis value. A moving average seems to be a slightly more complex solution though.

  • The implementation shown doesn't clearly show that there was some hidden state added, that of the current action being performed. At first I also thought it wasn't demonstrating hysteresis. Without it, the action can be determined by the current input alone; with it, the previous action must also be taken into account. Here's another implementation that seems more direct to me (with concrete values for clarity):

    [code]static float threshold = 0.5;

    if ( input < threshold )

    {

       threshold = 0.6;

       below();

    }

    else

    {

       threshold = 0.4;

       above();

    }[/code]

  • This bit seems like a pretty clear persistence of existing state to me:

       else

           KeepOnDoingWhateverYouAlreadyAre();

  • Shawn Hargreaves Blog announces the release of the Game State Management sample . His blog entry on Transitions

  • Interessting, i will remember this!

    But your water heater example reminded me of a documentation video i saw about F-16's (Airplane) it has such an unstable non-aerodynamic form that only a computer can keep it in the air by chaning the position of the wings(the little things that go up and down at the end of a wing) all the time! The pilot's motion are then added to that. That way the whole thing stays stable... hmmm...

Page 1 of 1 (10 items)
Leave a Comment
  • Please add 7 and 6 and type the answer here:
  • Post