Isis, the god that tricked Ra into revealing his true name.

In Egyptian mythology there's a story of how the goddess Isis gained power by tricking Ra into revealing his true name. Know something's true name you have power over it. There's a similar secret in the world of numbers, get a quantity in the range 0 to 1 and you control it.

Read on to gain these powers and use them when creating games!

To Range the Percent Plains

I'm sure we're all familiar with the concept of percent. A percentage is a number between 0 and 100 representing how much of a quantity we have. Another way to think of it is how many parts out of 100 we have.

A percent doesn't exist in isolation, it's associated with a special total number representing an upper bound. If I'm 6ft tall, we can say 50% of my height is 3ft, we can also say 200% of my height is 12ft. To make use of a percentage we need that little piece of information that links 100% to some quantity we're measuring. Without knowing I'm 6ft we can't say what 50% of my height is! A percentage must be anchored to a total amount.

In code it's more useful to represent percentages as numbers from 0 to 1 rather than 0 to 100. In this way 0.5 is 50%, 2 is 200% and -0.25 is -25%. We can do some neat things with numbers stored in this way.

Isn't this all a bit basic?

Yes! This is old hat to a lot of people. But I know when I started programming it didn't occur to me to store numbers in this way or how useful it was. This is a nice gamedev 101 article, explaining some of the simpler but perhaps non-obvious tricks of game development.

Why Use Percentage Numbers?

Percentage numbers help make code simpler. Simpler code is easier to understand, reason about and modify.

Multiplying Gives the Percentage Value

Multiply a number in the 0 to 1 range with a value and it gives you the percentage of that value. Let's look at an example; centering an image in the screen.

Make a multi-platform game and it's likely each platform has a different resolution.

Three example resolutions.

We want to draw a logo in the center of the screen. We're developing on a 1080p screen and we've hard coded the logo position to 960, 540 which centers it nicely in the screen.

x = 960
y = 540

Centering a logo using absolute positions.

When we run the game on other platforms the logo appear off center! Using percentage numbers rather than absolute numbers to the rescue. Rather than talking in absolutes we just say 50% of whatever the width is and 50% of whatever the height is.

x = width * 0.5
y = height * 0.5

Centering a logo using percentage positions.

Using percentages we can center our logo for all resolutions. (Of course resolutions smaller than the logo are going to cut it off - but even then, we could use percentages to scale the logo! This is left as an exercise for the reader :))

Normalizing Makes Numbers Uniform

Say we have a large group of disparate values that we want to apply the same operation to. We control the operation using one number the changes from 0 to 1 over time, like the figure below.

Using a percent to change values over time.

For instance, when the game finishes; it's time to fade to black, turn down the sound and fade in some text saying "Game Over". We might have starting variables like those below.

fade_quad = { color_rgb = {0, 0, 0, 0} } -- range of 0 .. 255
sound.volume = 5 -- range of 0 to 11
game_over_text = { opacity = 0 } -- range of 0 .. 1

We want to apply the fade out and fade in operations to these values. We control the transition with a variable fade01 that goes from 0 to 1. We can get this to work as below.

fadeout01 = GetFadeOut01(Time.deltaTime); -- returns 0 to 1 over a few frames
fade_quad.color_rgb.alpha = fadeout01 * 255
sound.volume = sound.volume + (11 - sound.volume) * fadeout01
game_over_text.opacity = fadeout01

This code fades out the screen by fading in a black quad that covers the screen, it fades in some "Game Over" text and fades out the volume.

The fade code is a little convoluted but soon we'll write some functions to make it easier and clearer!

Percentages as Weights

Imagine an AI that's trying to decide if it should stop and pick up a piece of food it's seen on the ground. The decision to stop is represented by simple 0 - 1 number, if it's over 0.5 it the AI stops, otherwise it ignores it.

This can be a complicated problem! What if they enemy is being shot at? What if it's really hungry? What if it's going somewhere? You can use percentages to weigh these functions together and make a decision.

-- AI Brain
hunger01 = 0.8 -- pretty hungry
following_orders01 = 1

stop_for_food = ((1 - following_orders01) + hunger01) / 2

print("Stop for food:", stop_for_food > 0.5) -- false!

In this example the AI is following orders so despite being pretty hungry, it doesn't stop. It weighs the hungry01 and following_orders01 components equally. But we can add custom weightings to make things more interesting.

-- AI Brain
hunger01 = 0.8 -- pretty hungry
following_orders01 = 1

-- Weightings, digital feelings
how_much_ai_cares_about_food = 0.5
how_much_ai_cares_about_job = 0.25

weighed_hunger01 = hunger01 * how_much_ai_cares_about_food
weighed_order01 = following_orders01 * how_much_ai_cares_about_job

stop_for_food = ((1 - weighed_order01) + weighed_hunger01)

print("Stop for food:", stop_for_food > 0.5) -- true!

Previously the weights were equal an 0.5 each. Dividing by the number of weights (in this case 2) is the equivalent to giving each an equal weighting.

In our new version of the code we've added explicit weights about how much the AI cares about the job and eating. These weights are simple 0 to 1 percentages numbers. We're saying he cares about the job 25%, meaning he does not really care about the job that much. As he's a game NPC I imagine there's a high mortality rate in his kind of work so dissatisfaction with his current role is to be expected.

When it comes to food he's ambivalent, an ordinary guy not a glutton nor an ascetic. So he's on his guard patrol and sees some delicious food on the ground, he's got orders to keep patrolling but he's pretty hungry and hates his job! Therefore he takes a bit of break and picks up the food.

Weights are only used in AI, they can be used anywhere were there are a number of inputs to make a choice.

How to "Normalize" a Number?

Here's a Normalize function written in Lua.

function Normalize(value, min, max)
    return (value - min) / (max - min);
end

Normalize(0, -1, 1)       -- 0.5
Normalize(250, 0, 1000)   -- 0.25

A related function that's a little more useful is Lerp. It takes in a number from 0 to 1 as value and returns the percentage difference between a start and end value.

function Lerp(v, in0, in1)
    return in0 + v * (in1 - in0)
end

Lerp(0.5, -1, 1)    -- 0
Lerp(0, -1, 1)      -- -1
Lerp(1, -1, 1)      -- 1
Lerp(0.25, 0, 1000) -- 250

A lot of the examples we've considered so far could be better expressed using the Lerp.

Finally there's another useful function that lets you specify the output range as well as the input range. I don't know a good name for this function but in my codebase it's called LerpF (for historical reasons). Here it is:

function LerpF(value, in0, in1, out0, out1)
    local normed = (value - in0) / (in1 - in0)
    local result = out0 + (normed * (out1 - out0))
    return result
end

LerpF(0.5, 0, 1, 0, 100)        -- 50.0
LerpF(0.5, 0, 1, 0, 1000)       -- 500.0
LerpF(0.5, 0, 1, -100, 100)     -- 0.0
LerpF(50, 0, 70, -3000, 3000)   -- 1285.71
LerpF(0.25, -1, 1, 0, 100)      -- 62.5

Hopefully a few use cases have occurred to you for these new functions but if no worries, we'll consider a few examples next!

Examples

Let's do some examples.

A quick note. So far we've looked at Normalize, Lerp and LerpF for numbers but they also work on vectors (and colors and various other things)! Here's an example.

the_moon = Vector3.Create(0, 384400, 0)
the_earth = Vector3.Create(0, 0, 0) -- so it is the center of the universe after all
half_way_to_the_moon = Vector3.Lerp(0.5, the_earth, the_moon) -- (0, 192200, 0)

Minimaps

A mini-map takes world space positions and translates them to screen space positions at a much smaller scale. The translations for the mini-map use percentages rather than absolute numbers.

Let's say you want to make a map. You have a FPS game and the level is no bigger that a circle of 7.6km. You can go through the areas of interest on the map and get their distance from the center of the map, confident they're all less than 7.6km. To convert them to 0 - 1 we know our upper bound is 7.6 and our lower bound is 0, you can't go below 0 that means you're right at the center of the map. So let's say the player base is 0.5 km north from the center of the map. We can convert that to 0 - 1 and this operation is sometimes called normalizing. Then we can multiple the normalized distance by the radius of out mini map and with a few other variables such as the direction we can draw it's corresponding location on the map!

Changing Values Over Time

As we've already seem percentages are great for changing values over time. If you use the Lerp functions you can even easily handle the case where the target values are also changing over time. For example you could make a simple function to chase another entity like so.

predator.position = Lerp(Time.deltaTime * predator.speed, predator.position, prey.position)

This simple one liner called every frame moves a predator closer and closer to some prey, in a straight line. The deltaTime is the number of milliseconds that have passed since the last frame so it's going to be really small like 0.0166 and this is modified by a speed value, the result should still fall between 0 and 1. This works even if the prey is moving! But it's not an ideal function to model this kind of behaviour because it doesn't reflect reality well. The further the prey is away from the predator the greater the distance the predator moves in a frame.

Here are some values that can be changed over time using percentages.

  • Position
  • Scale
  • Color
    • Fades
    • Flashing
    • Color mixing
  • Animation frames
  • Volume
    • Fades in and out
    • Sounds volume based on distance to player

Let's consider the animation frames a little closer and assume we're animating some sprite that has 10 frames.

frames = {1,2,3,4,5,6,7,8,9,10}
value01 = GetValue() -- goes from 0 to 1 over a few seconds
local index = math.max(1, math.ceil(value01 * frames.length))
sprite:SetFrame(index)

As value01 changes from 0 to 1 the sprite's frame goes through the discrete frame list nice and smoothly. The pseudo code is based on Lua so there's a little massaging to get the frame range from 1 to 10.

Positioning Things In Space

Percentages and lerps work well with time but they also work with space! Here are some places you can use them.

  • UI
    • Offsets (like we saw above when centering the logo)
  • 3d / 2d model
    • Want to put a name over the player's head?
      • name.y = player.height * 1.1 -- 10% over the player's head
    • Firing off special effects
      • Directly between your sword and the enemy for instance
  • Handling Buffs and Debuffs
    • See this article on Stats.
  • Counting up Numbers
    • XP, Gold etc in level up screens
    • display_xp = Lerp(deltaTime * speed, display_xp, actual_xp)
    • (Though there are a number of tricks to make this type of count-up animation more polished.)

Thanks For Reading

Hopefully this article has sparked off some new ideas or helped old concepts click together! I feel these common techniques aren't well described on the internet (and I certainly don't think this is as clear as it could be) so I hope this article helps you out! Let me know if you enjoyed it and would like more of this type of writing at @HowToMakeAnRPG.