Edit detail for StaminaModel revision 14 of 36

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Editor: DonovanBaarda
Time: 2012/11/03 01:22:49 GMT-4
Note:

changed:
-  H - health. The fraction of life remaining after taking damage.
-  S - stamina. The fraction of un-damaged, un-fatigued muscles available for lifting/moving/fighting/etc.
-  E - encumbrance. The fraction of max lift worth of equipment currently carried.
  H = health/healthMax
  S = stamina/staminaMax
  E = encumb/encumbMax

Where::

  H is the fraction of life remaining after taking damage.
  S is the fraction of un-damaged, un-fatigued muscles available for lifting/moving/fighting/etc.
. E is the fraction of max lift worth of equipment currently carried.
  health is the actors current "health".
  healthMax is the actors maximum "health".
  stamina is the actors current "stamina".
  staminaMax is the actors maximum "stamina".
  encumb is the actors current encumbrance in equivalent Kg.
  encumbMax is the actors maximum encumbrance in equivalent Kg.

These H, S, E values could be displayed using bars on a HUD.

Note that although encumb is in Kg, it should take into account how awkward things are to carry, not just their weight. Long clumsy or sharp objects should have a higher encumbrance than their raw weight. Putting things in convenient carrying containers like backpacks should reduce awkward objects encumbrance back towards their real weight.

changed:
-  F - stamina burn rate, stamina/second
-  R - stamina recovery rate, stamina/second
-
-Note that R/(F+R) is the "steady state" or minimum S value reached for constant activity, so the the R/F ratio is important. Studies show F is 0.008~0.033, R is 0.0026~0.013, and F/R is 0.3~0.5, so F=0.03, R=0.01, for F/R = 1/3 is realistic, and gives an average fatigue recovery rate of 0.5%/sec. However for gameplay reasons speeding it up to F=0.9, R=0.03 might be better.
  staminaBurn = F * staminaMax
  staminaReturn = R * staminaMax

Where:

  staminaBurn is the rate that stamina burns in stamina/second.
  staminaReturn is the rate stamina recovers in stamina/second.
  F = 0.09, the rate active muscles fatigue in fraction/second.
  R = 0.03, the rate fatigued muscles recover in fraction/second.

Note that R/(F+R) is the "steady state" or minimum S value reached for constant activity, so the the R/F ratio is important. Studies show F is 0.008~0.033, R is 0.0026~0.013, and R/F is 0.3~0.5, so F=0.02, R=0.008, for R/F = 0.4 is a realistic average. However for gameplay reasons speeding it up to F=0.09, R=0.03, R/F=1/3 might be better.

changed:
-  Smax is the max stamina limit applied because of wounds
  Smax is the max stamina fraction limit because of wounds

changed:
-  onHit(dH):
-    H = H + dH
  onHit(damage):
    health = health - damage
    H = health/healthMax
    S = stamina/staminaMax

added:
    S = S + dS

changed:
-    S = S + dS
-
-Where::
    stamina = S*staminaMax

Where::

changed:
-  Smin is the minimum stamina required to lift what the actor is carrying.
  Smin is the minimum stamina fraction required to lift what the actor is carrying.

changed:
-  onFrame:
  onFrame(dt):
    H = health/healthMax
    S = stamina/staminaMax
    E = encumb/encumbMax
    M = 1.0 + encumbMult*E

removed:
-    M = 1.0 + encumbMult*E

added:
    moveMult = 1.0/((climbRate/0.1)^2 + 1.0)

removed:
-    moveMult = 1.0/((climbRate/0.1)^2 + 1.0)

removed:
-    moveSpeed = moveScale * moveMax

added:
    stamina = S*staminaMax
    moveSpeed = moveScale * moveMax

changed:
-  dS = -F * W * (S - Smin)
  dS = -F * W * (S - Smin)h

The RealisticFatigue? document was a bit too messy and Oblivion specific. This is an attempt to tidy and simplify it into something that can be used for other games. In particular, "stamina" is a better name than "fatigue".

Basic Attributes

The following attributes are in the range 0.0 (empty) to 1.0 (full). Note that there can be underlying health/strength/endurance/etc attributes that translate these into different numbers, but from the fatigue models point of view, the only thing that matters is the fraction of full health/stamina/encumbrance:

H = health/healthMax
S = stamina/staminaMax
E = encumb/encumbMax

Where:

H is the fraction of life remaining after taking damage.
S is the fraction of un-damaged, un-fatigued muscles available for lifting/moving/fighting/etc.
. E is the fraction of max lift worth of equipment currently carried.
health is the actors current "health". healthMax is the actors maximum "health". stamina is the actors current "stamina". staminaMax is the actors maximum "stamina". encumb is the actors current encumbrance in equivalent Kg. encumbMax is the actors maximum encumbrance in equivalent Kg.

These H, S, E values could be displayed using bars on a HUD.

Note that although encumb is in Kg, it should take into account how awkward things are to carry, not just their weight. Long clumsy or sharp objects should have a higher encumbrance than their raw weight. Putting things in convenient carrying containers like backpacks should reduce awkward objects encumbrance back towards their real weight.

The following constants adjust how quickly you get tired and recover. They are normally constants but could be tweaked per-character based on some endurance attribute:

staminaBurn = F * staminaMax
staminaReturn = R * staminaMax

Where:

staminaBurn is the rate that stamina burns in stamina/second. staminaReturn is the rate stamina recovers in stamina/second. F = 0.09, the rate active muscles fatigue in fraction/second. R = 0.03, the rate fatigued muscles recover in fraction/second.

Note that R/(F+R) is the "steady state" or minimum S value reached for constant activity, so the the R/F ratio is important. Studies show F is 0.008~0.033, R is 0.0026~0.013, and R/F is 0.3~0.5, so F=0.02, R=0.008, for R/F = 0.4 is a realistic average. However for gameplay reasons speeding it up to F=0.09, R=0.03, R/F=1/3 might be better.

Wounds

Wounds are modelled as damaged muscles, reducing your available muscles for running/fighting/etc. It applies a constant "drain" or cap on your stamina. This could be indicated as a red max stamina marker on the top end of the stamina bar:

Smax = 1 - woundMult*(1-H)^woundExp

Where:

woundMult=1.0, but can be turned down to reduce the severity of wound effects.
woundExp=2, but can be turned up to delay the onset of wound effects.
Smax is the max stamina fraction limit because of wounds

Note setting woundMult > 1.0 means actors will run out of stamina and collapse before they die.

Whenever you are damaged/healed and your health changes, it damages/restores some of both fatigued and unfatigued muscles. There should be a change in S that reflects this. For a change in health of dH:

onHit(damage):
  health = health - damage
  H = health/healthMax
  S = stamina/staminaMax
  dSmax = 1 - woundMult*(1-H)^woundExp - Smax
  dS = dSmax * S/Smax
  S = S + dS
  Smax = Smax + dSmax
  stamina = S*staminaMax

Where:

dH is the change in health.
dSmax is the change in Smax
dS is the change in S

Note this ensures that at low stamina a hit winds you a bit, but not too much.

Encumbrance

Encumbrance preoccupies muscles, leaving less available for running/attacking/etc, and there is a minimum stamina required to lift everything you are carrying. It can be shown using a black min stamina marker on the bottom end of the stamina bar. These preoccupied muscles also should be subtracted from S when considering how fast you can run, hard you can hit etc:

Smin = liftMult*E^liftExp

Where:

liftMult = (0.5 * encumbMult)^liftExp = 0.25, but can be turned down to reduce the stamina burn from encumbrance.
liftExp = 2, but can be turned up to delay the onset of stamina burn from encumbrance.
Smin is the minimum stamina fraction required to lift what the actor is carrying.

Note RF implements this as an additional "drain" on S, but that is slightly miss-leading as it is not really damaged/fatigued muscles and thus should not affect fatigue indicators like panting etc.

Moving

This is is how stamina is recovered and burned when standing/walking/running, and movement speed varies with stamina and encumbrance. We use the following tunable constants:

encumbMult = 1.0, the "max" lift in number of your own body weights when E=1.0.
upMult=10.0, is the cost of climbing up per meter relative to other things.
downMult=upMult/5=2.0, is the cost of going down per meter relative to other things.
runMult=1.0 is the cost of running relative to other things.
walkMult=4.0 is the cost of walking relative to other things.
runSpeedMult=1.0 is the max running speed as a fraction of moveMax. Can be turned down to reduce running speed and stamina burn.
walkSpeedMult=0.2 is max walking speed as a fraction of moveMax. Can be adjusted to change walking speed and stamina burn.
moveMax=10.0 is the maximum flat running speed in meters/sec.

A real max lift is about 2x your own body weight, but anything over 1x is unsustainable. I suggest encumbMult=1.0 so that the top-half of the encumbrance bar doesn't go unused and give players an unrealistic impression of how much they can carry. If you want you can allow temporary heavy lifts where E > 1.0 (over encumbered) and have them drop it or collapse when S < Smin. Note the largest professional rugby league player is 133Kg, though 100~125Kg is probably more realistic for a big hero's bodyweight.

Studies on walking/running on gradients show going uphill/downhill is about 12.5x/2.5x as expensive/meter as running horizontally, so upMult=12.5, downMult=2.5. Record 15m speed climbing top speed is 6.26sec, or about 2m/sec, compared to 100m record top speeds of about 12m/sec, which suggests climbing vertically uses only ~6x the energy as horizontal running and half as much as uphill running. I suspect that climbing using both arms and legs allows double the power output compared to running with just legs, but the energy/meter is the same. It often surprises players how much affect climbing really has, and games tend to have exaggerated gradients. For playability I suggest lower values like upMult=10.0 and downMult=2.0, or even upMult=6.0 and downMult=1.2.

You can adjust runSpeedMult down to allow a slower run with less stamina burn. Using runSpeedMult=1.0 means run as fast as possible. Using runspeed=0.5 would be equivalent to a marathon runners fast jog at ~5m/s.

You can adjust walkSpeedMult to allow faster/slower walking with more/less stamina burn. Using walkSpeedMult=0.25 represents a realistic maximum fast walking speed of ~2.5m/sec. Realistic minimum energy walk speed is walkSpeedMult=0.125, but would probably try the patience of gamers. Using walkSpeedMult=0.2 is a brisk walking speed of ~2m/sec, and is a good gaming compromise between energy saving and speed.

For moveMax, record 100m top speeds average about 10m/sec and peak at 12m/sec. A realistic maximum running speed (for a hero) would be about 10m/sec.

We calculate the movement speeds and stamina burns each frame like this:

onFrame(dt):
  H = health/healthMax
  S = stamina/staminaMax
  E = encumb/encumbMax
  M = 1.0 + encumbMult*E
  Smax = 1 - woundMult*(1-H)^woundExp
  Smin = liftMult*E^liftExp
  Srest = (Smax - S)
  if isStanding:
    Slift = Smin
  else:
    Slift = 0.0
  climbMult = max(upMult*climbRate, -downMult*climbRate)
  moveMult = 1.0/((climbRate/0.1)^2 + 1.0)
  moveSpeedMult = 1.0/(climbMult + moveMult)
  if isRunning:
    moveScale = moveSpeedMult * runSpeedMult * (S-Smin)/M
    Smove = moveMult * runMult * M * moveScale
  elif isWalking:
    moveScale = moveSpeedMult * walkSpeedMult * sqrt((S-Smin)/M)
    Smove = moveMult * walkMult * M * moveScale^2
  else:
    moveScale = 0.0
    Smove = 0.0
  Sclimb = climbMult * M * moveScale
  dS = (R*Srest - F*(Slift + Smove + Sclimb)) * dt
  S = S + dS
  stamina = S*staminaMax
  moveSpeed = moveScale * moveMax

where:

M is the actor's total weight in bodyweights.
Srest is the fraction of all muscles that are fatigued and resting.
Slift is the fraction of all muscles applied to lifting.
Smove is the fraction of all muscles applied to moving.
Sclimb is the fraction of all muscles applied to climbing.
climbRate is vertical movement per total distance moved.
climbMult is the cost of climbing based on the gradient.
moveSpeedMult is the scaling factor for movement speeds walking/running for the gradient.
moveMult is the scaling factor for cost of walking/running for the gradient.
moveScale is movement speed as a fraction of moveMax.
moveSpeed is the movement speed in meters/sec.
dt is the time since the last frame.
dS is the change in S for this frame.

If you are not standing and are collapsed, lying down, or sitting, then you are not actually lifting anything so Slift=0.0. We don't make Smin zero in this case to make it clear what your minimum stamina is to stand. The moveSpeed and Smove depend on if you are running or walking.

The climbRate can be calculated from the surface gradient directly, or from movement over the last frame. It should probably be low-pass filtered so jitter/bob doesn't burn fatigue like crazy. This is done by actually calculating it with:

dd = sqrt(dz^2 + dy^2 + dz^2)
if dd > 0.0 and isRunning or isWalking:
  climbRateNow = dz/dd = sin(climbAngle)
else:
  climbRateNow = 0.0
climbRate = (dt*climbRateNow + climbRC*climbRate)/(dt + climbRC)

Where:

dx,dy,dz is x,y,z travel distance in the last frame.
dd is total travel distance in the last frame.
climbAngle is the elevation angle of the direction travelled.
climbRC = 0.5, is the climbRate lowpass filter time-constant. Can be tweaked to adjust smoothing.

You also need to be wary of things like lifts to ensure that dz changes from going up/down on lifts doesn't burn fatigue and make actors collapse. RF detects this by checking the actor is running/walking and the gradient is not too steep.

Jumping

This is how stamina is burned by jumping, and how jump height varies with stamina and encumbrance:

onJump:
  jumpScale = (S - Smin)/M
  jumpHeight = jumpScale * jumpMax
  jumpVelocity = sqrt(2*g*jumpHeight)
  dS = -F * jumpMult * (S - Smin)
  S = S + dS

Where:

jumpMult = 1.0, but can be tuned down to reduce encumbrance effects on jumping.
jumpScale is the jump height as a fraction of max jump height.
jumpMax=1.0 is the maximum unfatigued, unencumbered jump height in meters.
jumpHeight is the height of the jump in meters.
jumpVelocity is take-off velocity in meters/sec (for physics models).
g = 9.81m/s^2 is the gravitational constant.

Using jumpMult=1.0 means a jump burns as much stamina as running for 1 second.

The (unverified) world record for a vertical jump is around 1.5m, but anything over 1m is considered extraordinary, with 0.75m being the average for professional footballers and basketballers. Micheal Jordan could jump 1.2m. The average taken for a group of typical medical students was 0.56m for men and 0.35m for women.

Attacking/Blocking

The speed and effectiveness of attacks and blocks depends on available stamina, and also burns stamina:

dS = -F * W * (S - Smin)h