GML Map (remap values from one range to another)

samspade

Member
Map Script Tutorial

GM Version: All
Target Platform: All
Download: NA
Links: NA

Summary:

Map re-maps a number from one number range to another. It works with positive and negative numbers and outside of its given range.


Tutorial:

It is often very useful to be able to take a number from one range and find its equivalent in another range. That is what the script map() does. It takes five arguments, a value, the current min and max range and the destination or desired min and max range. It then takes the relation of that value to the first range and figures out what the number should be in relation to the second range and returns that number. For example,
  • 15 in range 0...10 becomes 150 in range 0...100
  • -5 in range -10...0 becomes 50 in range 0...100
  • 5 in range 0...10 becomes 0 in range -5...5
  • -5 in range 0...-10 becomes 250 in range 200...300
  • 5 in range 0...10 becomes -5 in range 0...-10

Example Uses:
  • drawing health bars to the screen (where health is the value, 0 and max health is the current range and the position for the start and end of the health bar is the desired min and max range)
  • sliders (turn the value of any slider into any other range)
  • functions or formulas that return a range (such as sin, cos, or gamepad_axis_value)
  • relative value of one thing to another (such as scaling damage within a range based on max health or scaling an image based upon another value)

Code

Expanded Version:

Code:
/// @description Maps a number from one range to another
/// @param value
/// @param current_lower_bound
/// @param current_upper_bound
/// @param desired_lower_bound
/// @param desired_upper_bound

var value = argument0; 
var current_lower_bound = argument1; 
var current_upper_bound = argument2; 
var desired_lower_bound = argument3; 
var desired_upper_bound = argument4; 

var total = current_upper_bound - current_lower_bound;
var percent = (value - current_lower_bound) / total;
var new_val = lerp(desired_lower_bound, desired_upper_bound, percent);

return new_val;
Single line version (thanks to frostycat):

Code:
/// @description Maps a number from one range to another
/// @param value
/// @param current_lower_bound
/// @param current_upper_bound
/// @param desired_lower_bound
/// @param desired_upper_bound

return (((argument0 - argument1) / (argument2 - argument1)) * (argument4 - argument3)) + argument3;
Example Use

Code:
//draw a horizontal rectangle health bar where x1, y1, x2, and y2 are its coordinates on screen
var draw_hp_at = map(current_hp, 0, max_hp, x1, x2);
draw_set_color(c_red);
draw_rectangle(x1, y1, draw_hp_at , y2, false);
draw_set_color(c_black);
draw_rectangle(x1, y1, x2, y2, true);

//translate a sliders value to 0-1 assuming a slider where
//x represents the sliders position and slider_start and slider_end represent the min and max x values
global.slider_value = map(x, slider_start, slider_end, 0, 1);
If you would like a more detailed understanding of the math behind it, watch the following three videos on YouTube (not by me).

Coding Math:
 
Last edited:

immortalx

Member
I'm only seeing this now due to a link posted in another thread. Great work as always samspade and kudos to FrostyCat too.
Sometime ago I've made a version of this script with an additional argument, for cases where as the value from the source range increases, the returned value decreases and vice-versa.

A use of that is a flashing sprite animation, where for example when a timer variable decreases the image_speed of the sprite increases.
Code:
///@description conv_range(from_min, from_max, to_min, to_max, value, inverse)
///@arg from_min
///@arg from_max
///@arg to_min
///@arg to_max
///@arg value
///@arg inverse

var f_min = argument[0];
var f_max = argument[1];
var t_min = argument[2];
var t_max = argument[3];
var val = argument[4];
var inverse = argument[5];
var result;

if (inverse)
{
    result = 1 - ((((val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min);
}
else
{
    result = (((val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min;
}

return clamp(result, t_min, t_max);
 

samspade

Member
I'm only seeing this now due to a link posted in another thread. Great work as always samspade and kudos to FrostyCat too.
Sometime ago I've made a version of this script with an additional argument, for cases where as the value from the source range increases, the returned value decreases and vice-versa.

A use of that is a flashing sprite animation, where for example when a timer variable decreases the image_speed of the sprite increases.
Code:
///@description conv_range(from_min, from_max, to_min, to_max, value, inverse)
///@arg from_min
///@arg from_max
///@arg to_min
///@arg to_max
///@arg value
///@arg inverse

var f_min = argument[0];
var f_max = argument[1];
var t_min = argument[2];
var t_max = argument[3];
var val = argument[4];
var inverse = argument[5];
var result;

if (inverse)
{
    result = 1 - ((((val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min);
}
else
{
    result = (((val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min;
}

return clamp(result, t_min, t_max);
I don't think that math is right. I believe what you want is:

Code:
result = ((1 - (val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min;

//not

result = 1 - ((((val - f_min) / (f_max - f_min)) * (t_max - t_min)) + t_min);
As you want to reverse the percent, not get one minus entire equation - e.g. 0, 1, 0, 100, 0.25, true, should result in 75 not -24.

However, even this is unnecessary as map already does that naturally. You just reverse the to min and to max.

So for example (using my argument order - value, from min, from max, to min, to max):

Code:
result = map(0.25, 0, 1, 0, 100); //result = 25
result = map(0.25, 0, 1, 100, 0); //result = 75
No need for an inverse variable.

Edit: You can also reverse the from min and max for similar results or mix and match as you want:

Code:
result = map(0.25, 0, 1, 0, 100); //result = 25
result = map(0.25, 1, 0, 0, 100); //result = 75
result = map(0.25, 1, 0, 100, 0); //result = 25
But you do bring up a good point that another version of map - perhaps name it map_clamp - is useful if you want to clamp the values returned by map as the version I posted will return values outside of the range (which is how the GML function lerp also works).
 
Last edited:
Top