Pixel Perfect Scaling in Game Maker Studio 2 

See you around town, friends.

This is a step by step series of instructions for making a camera and view engine in GMS2. It is intended to work similarly to the style of GMS1.4 and will be able to be used on any monitor at all, including ones not in a 16 x 9 aspect ratio. Despite this, and being that the most common monitor resolution is 1920 x 1080 at the time of writing, throughout this walkthrough I will be calculating things in a 16 x 9 aspect ratio. Use what you learn here to calculate and find out exactly what you'll need for your own game if it differs!

0. Graphical Considerations
1. Monitor Dimension
2. Greatest Common Divisor
3. Monitor Aspect Ratio
4. True Aspect Ratios
A. Licenses

Step 0: Determine Tile and Sprite Sizes


This may sound strange, but, it's crucial to do it now. See, everything in the game will be dependent on the size of the graphics it uses, and usually, games are made up of small blocks called tiles. Traditionally, tiles are made in multiples of 8. In the past, this was mostly because of hardware design, but game developers have continued to use tiles of 8 x 8, 16 x 16, 32 x 32, 64 x 64, etc. as time has gone on. Thus, in this example, the graphics for the game will be assumed to be multiples of 8. It will be made clear why this is necessary in Step 4.

Step 1: Determine the Monitor Area


This is easily done as such:

Retrieve Monitor Dimensions
    monitor_width = display_get_widthdisplay_get_width()();     
    monitor_height = display_get_heightdisplay_get_height()();    

For the example, I'll assume that monitor_width and monitor_height respectively equal 1920 and 1080.

Step 2: Determine the Greatest Common Divisor


Now that the values of the monitor are known, we can use a script called GCD(), or, greatest commmon divisor to find out what the monitor's native aspect ratio is.

Greatest Common Divisor
///@func               GCD(A, B)      
///@arg     {real}     A     first value to find the greatest common denominator of      
///@arg     {real}     B     second value to find the greatest common denominator of      
///@desc               Retrieves the greatest common denominator for 2 values.      
///@ret     {real}     The GCD for the numbers.      
var a = argument0;     
var b = argument1;     
while b != 0 {     
    var ta = b;     
    var tb = a%b;     
    a = ta;     
    b = tb;     
return a;    

This may seem slightly confusing, and it is, but it's cool too. To find the highest number that can divide into two values, the modulus % operator is used in a loop until it comes back 0. Modulo operations will only return 0 when they evenly divide into something! If monitor_width and monitor_height are passed into the script, we get a result of 120.

Store The GCD in a Variable
monitor_gcd = GCD(monitor_width, monitor_height);    

Step 3: Get the Monitor's Aspect Ratio


Using the result of 120 retrieved previously, we can divide it into the monitor_width & monitor_height. This gives us (finally) 2 values which are indeed the aspect ratio of the monitor. It's useful to store the result both as an array and a string.

Monitor Aspect Ratio Achieved
var _wratio = monitor_width/monitor_gcd;     
var _hratio = monitor_height/monitor_gcd;     
aspect_ratio_array = [_wratio, _hratio];     
aspect_ratio_string = stringstring(val)(_wratio) + " x" + stringstring(val)(_hratio);     
//  > 16x9     

As it turns out, and although it's likely you knew this already, this example monitor's aspect ratio is: 16x9 or, [16,9]. These numbers are going to prove crucial to the next steps, and it was necessary to solve this first before getting there!

Step 4: Calculate the True Aspect Ratios


As mentioned in Step 0, we will now make use of that graphical multiple, as well as the aspect ratio we just calculated. See, there's a lot of 16 x 9 aspect ratios out there. That said - you likely don't want to use a lot of them. Say our base tile size in the game we are making is a nice 16 x 16. That means, being as 16 is a multiple of 8, that if we decided to allow the user or game to resize itself/its window to some set of values that are not a multiple of 8, we'd end up with either the dreaded black bars, or, the even worse, squished and crammed graphics.

Thus, I'll show you a way to calculate a list of aspect ratios, all derived from our graphical baseline value. By doing this, we know for a certainty that the resulting list contains only resolutions that will not cause black bars or cutoffs.

Calculating the True Resolutions
///@func CalcTrueAspect(wr, hr, m)      
///@arg  {real}     wr     width ratio      
///@arg  {real}     hr     height ratio      
///@arg  {real}     gm     graphics multiple      
///@desc Calculates a list of true aspect ratios      
///@ret  {arr}     array of arrays containing true resolutions      
var _wr = argument0;     
var _hr = argument1;     
var _gm = argument2;     
var _aspects = [];     
//     Only 1 of these is needed. In this script I'll use the width max.      
var _maxw = display_get_widthdisplay_get_width()();     
var _maxh = display_get_heightdisplay_get_height()();     
var i = 0;     
while _wr * _gm * i < _maxw      
    i += 1;     
    var _tw = _wr * _gm * i;     
    var _th = _hr * _gm * i;     
    _aspects[i-1] = [_tw, _th];     
return _aspects;    

Here's the same code written in Lua, to see how it would work in a different language.

Now, to see what you've done, and to prove it to yourself that this works as intended, use a script like the following to see what's been stored in the array that was returned. This one prints to console when you press F1, logging out all the true resolutions available to you!

View True Resolutions
///@func     LogAspects(aspects)      
///@arg      {arr}     aspects     an array of arrays containing aspect ratios      
///@desc     Logs a list in console of true resolutions based on aspect ratios      
var _a = argument0;     
    for (var i = 0; i < array_length_1darray_length_1d(variable)(_a); i++)     
        var _t = _a[i];     
        var _sw = stringstring(val)(_t[0]);     
        var _sh = stringstring(val)(_t[1]);     
        var _x = " x ";     
        show_debug_messageshow_debug_message(str)(_sw + _x + _sh);     

Here's a photo of what mine looked like!

Step 5: Selecting Your Base Resolution


Lua Code Examples

Lua Example: True Aspect Ratios and Resolutions


-- Aspect Ratio Table
ar = {
	w=16,-- width ratio
	h=9, -- height ratio
	m=8  -- graphical multiple

-- Calculates a list of true aspect ratios given a width, height, and multple.
function calc_true_aspects(w,h,m)
	local w = w
	local h = h
	local m = m
	local arlist = {}

	-- Only one of the following is needed.
	local wmax = 1920
	local hmax = 1080

	local tw, th = 0, 0
	local i = 0

	while w * m * i < wmax do
		i = i + 1
		tw = w * m * i
		th = h * m * i
		arlist[i] = {tw, th}
	return arlist

-- Prints out the list calculated previously to console.
function print_true_aspects(t)
	for k,v in pairs(t) do
		if type(v) == "table" then
			print(tostring(v[1]) .. " x " .. tostring(v[2]))
			print(tostring(k) .. ": " .. tostring(v))

true_aspects = calc_true_aspects(ar.w, ar.h, ar.m)