[Tutorial] nineSlice script for GameMaker

Hi! My name is Lucas and I’m a hobbyist gamedev using GameMaker Studio 2 (GMS2). This is a tutorial on how to create a script for nine slice scaling in GMS.

I have an example project in my itch.io page that can be download for free and shows how the script works, also if you want to support me, you can purchase the script.

Example Project available for free at my itch.io page.

**Don’t want to read all this? Just copy/paste the code blocks below into a single script in GMS and start using it!**


Introduction

First of all though, what is nine slice scaling? This is a technique commonly used for drawing panels and boxes, usually for UI design, where an image is separated in 9 different parts, which allow you to resize the panel without creating any distortions. The image below exemplifies the concept better:

The four corners remain the same, while the other parts are stretched vertically/horizontally to fit the desired size. Obviously this can be a very useful tool when creating a game. One of the limitations of this script is that the width and height of all slices must be the same.

So, the script we are going to create is this one:

nineSlice(sprite, x, y, width, height, stretched);

We are going to choose the place and size of the panel and also if we are going to stretch the middle parts or tile them. And it is actually a very simple script, all we are going to do is draw each of the slices in the correct place, but first we need to import the sprites in GMS2 in the correct manner.

The nine slices must be imported into GMS2 as a single sprite resource, but each one of them as a different frame, following the order: top left, top center, top right, middle left, middle center, middle left, bottom right, bottom center and bottom left. And the origion of the sprites must be the top left corner (0, 0), which is the default.

An important thing to remember is that the first frame (subimg) of a sprite in GMS2 is always 0.


Drawing each slice

Now we are going to draw the corners using the functions draw_sprite and all the others slices using draw_sprite_stretched, and everything we have to do is calculate the correct place to draw them in. So these will be our script arguments:

/// @function nineSlice(sprite, x, y, width, height, stretched)
/// @arg sprite
/// @arg x
/// @arg y
/// @arg width
/// @arg height
/// @arg stretched
// Initialize variables
var _sprite, _x, _y, _width, _height, _stretched;
// Define arguments
_sprite = argument0; // Choose the panel sprite
_x = argument1; // x coordinate for the top left corner
_y = argument2; // y coordinate for the top left corner
_width = argument3; // Width of the panel
_height = argument4; // Height of the panel
_stretched = argument5; // Stretch the panel (true) or tile (false)

But as mentioned above we will also have the option to tile the center slices instead of stretching them. For that we have to set the width of the panel to be a multiple of the sprite width, the same goes for the height.

Left: stretched sprite. / Right: Tiled sprite.

This way we avoid drawing sprites one over the other. With this code block we are going to set the panel width and height to be multiples of the sprite width and height:

// Initialize variables
var _snapWidth, _snapHeight, _spriteWidth, _spriteHeight, _tileWidth, _tileHeight;
// Calculate sprite width and height
_spriteWidth = sprite_get_width(_sprite);
_spriteHeight = sprite_get_height(_sprite);
// If not stretched, calculate number of sprite repetitions
if (!_stretched) {
_width += _width mod _spriteWidth;
_height += _height mod _spriteHeight;

_tileWidth = ((_width - _spriteWidth * 2) / _spriteWidth);
_tileHeight = ((_height - _spriteHeight * 2) / _spriteHeight);
}

If the panel width provided is 95 and the sprite width is 10, this code will add 5 to the total width. Following that, it’s then calculated the number of times the center slices have to be repeated to complete the panel ( _tileWidth and _tileHeight).

Now we can actually start drawing the slices. I’m not going to explain each one of the slices in detail, but basically if the panel is stretched we are just calculating the x and y and the distance that the slice is going to be stretched over.

If the panel is tiled we use a for loop to draw the same slice repeatedly in the correct place.

Top Left

// Top Left
draw_sprite(_sprite, 0, _x, _y);

Top Center

// Top Center
if (_stretched) {
draw_sprite_stretched(_sprite, 1, _x + _spriteWidth, _y, _width - _spriteWidth*2, _spriteHeight);
} else {
for (var _xPos = 1; _xPos <= _tileWidth; _xPos++) {
draw_sprite(_sprite, 1, _x + (_xPos * _spriteWidth), _y);
}
}

Top Right

// Top Right
draw_sprite(_sprite, 2, _x + _width - _spriteWidth, _y);

Middle Left

// Middle Left
if (_stretched) {
draw_sprite_stretched(_sprite, 3, _x, _y + _spriteHeight, _spriteWidth, _height - _spriteHeight*2);
} else {
for (var _yPos = 1; _yPos <= _tileHeight; _yPos++) {
draw_sprite(_sprite, 3, _x, _y + (_yPos * _spriteHeight));
}
}

Middle Center

// Middle Center
if (_stretched) {
draw_sprite_stretched(_sprite, 4, _x + _spriteWidth, _y + _spriteHeight, _width - _spriteWidth*2, _height - _spriteHeight*2);
} else {
for (var _xPos = 1; _xPos <= _tileWidth; _xPos++) {
for (var _yPos = 1; _yPos <= _tileHeight; _yPos++) {
draw_sprite(_sprite, 4, _x + (_xPos * _spriteWidth), _y + (_yPos * _spriteHeight));
}
}
}

Middle Right

// Middle Right
if (_stretched) {
draw_sprite_stretched(_sprite, 5, _x + _width - _spriteWidth, _y + _spriteHeight, _spriteWidth, _height - _spriteHeight*2);
} else {
for (var _yPos = 1; _yPos <= _tileHeight; _yPos++) {
draw_sprite(_sprite, 5, _x + _width - _spriteWidth, _y + (_yPos * _spriteHeight));
}
}

Bottom Left

// Bottom Left
draw_sprite(_sprite, 6, _x, _y + _height - _spriteHeight);

Bottom Center

// Bottom Center
if (_stretched) {
draw_sprite_stretched(_sprite, 7, _x + _spriteWidth, _y + _height - _spriteHeight, _width - _spriteWidth*2, _spriteHeight);
} else {
for (var _xPos = 1; _xPos <= _tileWidth; _xPos++) {
draw_sprite(_sprite, 7, _x + (_xPos * _spriteWidth), _y + _height - _spriteHeight);
}
}

Bottom Right

// Bottom Right
draw_sprite(_sprite, 8, _x + _width - _spriteWidth, _y + _height - _spriteHeight);

Optimizing the script

Thanks to Michael Cazzarolli, who left a comment below, we have a greatly optimized version of the script, putting a bunch of IFs and FORs together.

Follow this link to get the optimized version: https://pastebin.com/nCSNm6Sz


So, this is the entire script. Pretty simple, right? If you have any questions feel free to contact me at my Twitter or Itch.io page.

If you’re interested in more tutorials I have another about creating a Random Maze Generator. Check it out!

Thanks!