The latest GameMaker Studio 2 update (2.1.5) has added a new collision mask kind to the Sprite Editor. This addition is the rotated rectangle collision mask and in this short tech blog we're going to show you how it works and at the same time revise the already existing collision masks, showing their differences through the use of a small test project that you can then build on and play with later. So, to get started, you'll want to create a new project in GameMaker Studio 2 (it can be GML or DnD™, as the article will cover both)...
SETTING UP SPRITES
For the sake of this article, we'll be using some very simple sprites of different shapes:
You can download those (each one is 64x64 px) or make your own, but it's important to note that if you make your own then they should be appropriate for the different mask kinds that we'll be using. In total we want to create six new sprites:
spr_Rectangle
- Uses the square spritespr_Rotated_Rectangle
- Uses the rectangular spritespr_Ellipse
- Uses the circular or elliptical spritespr_Diamond
- Uses the diamond spritespr_Precise
- Uses the "blob" spritespr_Precise_Animated
- Uses each of the above mentioned single sprites as different sub-images
Go ahead and make these now, and for each sprite added, you will need to set the origin to the middle/center and then apply the correct collision mask, as illustrated in the GIF below:
Once you have done that your resource tree should look something like this:
ADDING OBJECTS
To match our sprites, we'll also need some objects, so now add 6 new objects into the project, and assign a separate sprite to each of them. Keep in mind, that for an instance of an object to detect a collision, it must have a sprite assigned to it, as the collision events and most functions will work off of the collision mask of the assigned sprite. When you've done that, and named each of the objects appropriately, you'll need to add another object to be used as the Parent object for collision detection. Call this object obj_Collision_Parent
or something similar and then assign it as the parent to all 6 of the shape objects (if you are unsure of how to do this, please see the manual).
We need to add one more object now, which will act as the "controller" object for the project, so add another object now and call it something like obj_Control
. Your resource tree should now look like this:
We'll come back to the controller object in a bit but for now, we need to set some things in the shapes. To start with open the obj_Rectangle
and add the following into the Create Event:
image_angle = irandom(360);
And using DnD™:
Still in obj_Rectangle
, add a Right Mouse Down event. Here we will make the instance rotate when clicked with the right mouse button (so that you can compare what happens when using a regular rectangle mask with a rotated rectangle mask). To rotate the sprite we need:
image_angle += 5;
And the DnD™ would be:
Close the rectangle object now and open obj_Rotated_Rectangle
. Here we want to add a Step Event with the following to rotate the rectangle automatically:
image_angle += 1;
The DnD™ would be:
Finally, we want to slow down the animation of the object obj_Precise_Animated
, so open that now and add a Create Event with this:
image_speed = 0.1;
And using DnD™ it would be:
ADDING A SCRIPT
For this example, we're going to use a script resource to check for collisions. We want to try and get an exact position for where a line intersects the mask of the shape objects, and that's easiest to do using a small script. So, create a new Script resource, call it collision_line_first
, and in it add this:
/// @function collision_line_first(x1, y1, x2, y2, obj, prec, notme)
/// @param {real} x1 The X coordinate to start the line check from
/// @param {real} y1 The Y coordinate to start the line check from
/// @param {real} x2 The X coordinate to end the line check at
/// @param {real} y2 The Y coordinate to end the line check at
/// @param {id} obj The object index to check for a collision with
/// @param {bool} prec Whether to use precise collision checking or not
/// @param {bool} notme Whether to exclude the calling instance from the check or not
/// @description This script works the same as the collision_line function/action
/// only it will return the ID of the first instance found to be in
/// collision as well as the X/Y position of the actual collision point.
/// This information is returned as an array where:
///
/// [0] = Instance ID of the found instance, or -1 if none are found
/// [1] = The x position of the collision
/// [2] = The y position of the collision
///
// Declare arguments
var x1 = argument0;
var y1 = argument1;
var x2 = argument2;
var y2 = argument3;
var obj = argument4;
var prec = argument5;
var notme = argument6;
// Declare internals
var dx = 0;
var dy = 0;
var return_array = array_create(3, -1);
// Get the first hit
var first_instance = collision_line(x1, y1, x2, y2, obj, prec, notme);
// If hit find the exact hit
if instance_exists(first_instance)
{
// Get x and y segment lengths
dx = x2 - x1;
dy = y2 - y1;
// Perform check while distances are greater or equal to 1
while (abs(dx) >= 1 or abs(dy) >= 1)
{
// Divide the modifier distance by 2 every iteration
dx /= 2;
dy /= 2;
// Check the new collision line modified by pulling back the end of the hit line by half the distance each loop.
var new_instance = collision_line(x1, y1, x2 - dx, y2 - dy, obj, prec, notme);
// If we still hit the instance we didn't move back far enough to get outside of it.
if (new_instance != noone)
{
//set the found instance to what we hit, and pull back the line end by the current modifier
first_instance = new_instance;
x2 -= dx;
y2 -= dy;
}
}
}
else first_instance = -1;
// Set return array
return_array[0] = first_instance;
return_array[1] = x2 - dx;
return_array[2] = y2 - (dy * 2);
return return_array;
If you are using DnD™ then simply create the new script resource and then drag an Execute Code action in and copy/paste the above.
All this script does is check for a collision along a line in "chunks" and then when one is found, try to find the precise point where the collision line intersects the mask of the instance in the collision. The script will return an array of three values: the ID of the instance in the collision, and the x/y coordinates of the mask/line intersection.
With that done, we can set up the controller object.
THE CONTROLLER OBJECT
Open the controller object now and add a Draw Event to it. In this event we'll be using the following code:
draw_set_colour(c_blue);
with (obj_Collision_Parent)
{
draw_line(bbox_left, bbox_top, bbox_left, bbox_bottom);
draw_line(bbox_left, bbox_top, bbox_right, bbox_top);
draw_line(bbox_right, bbox_top, bbox_right, bbox_bottom);
draw_line(bbox_left, bbox_bottom, bbox_right, bbox_bottom);
image_blend = c_white;
}
draw_set_colour(c_white);
var _array = collision_line_first(x, y, mouse_x, mouse_y, obj_Collision_Parent, true, true);
if _array[0] != -1
{
draw_line(x, y, _array[1], _array[2]);
_array[0].image_blend = c_red;
}
else
{
draw_line(x, y, mouse_x, mouse_y);
}
For DnD™ users you'd need to have this:
That's our controller all set up and all that's left is to add a few of instances of each shape object (except the parent object!) into a room along with an instance of the controller object. So, open the default room that was added when the project was created, and drag a number of each instance into the room on the "Instances" layer. 2 or 3 of each will be fine, and then add in an instance of the controller object, placing it in the center of the room:
START PLAYING!
You should run the project now and move the mouse around the screen, checking to see that each collision mask kind behaves as it should. This means that the instances should only turn red when the mouse is over the collision mask, and the line being drawn should stop at the intersection. However, if you put the mouse over one of the instances with the regular rectangular mask and then click the right mouse button to rotate it, what happens? You can see that the mask does not rotate but instead expands to "fit" within the bounding box (the blue lines):
In the GIF above, on the left we have the rotated rectangular mask and on the right the normal rectangular mask. As you can see, the regular mask does not give the same results when rotated, since the mask is expanded to fit the bounding box rather than rotating. One thing about all this that's important to note, is that when using instance/instance collision events and functions (ie: those things that require both instances to have a collision mask), then if both instances do NOT have the same mask type, the collisions will default to precise checking,
You can now start to edit the project and make changes to see what happens. Here are some suggestions for you to start playing with:
- change the shape of the collision mask (EG: give the ellipse sprite a diamond mask) and see how that affects things.
- change the script being used for one of the other collision functions or actions and see how they work.
- change the bounding box settings in the sprite editor to "clip" the collision masks and see how that changes things.
- change the image X/Y scale and see how that affects the collision detection for the different masks.
- switch off the "precise" argument for the script being used (set it to false) and see how the collisions are now all based on the bounding box rather than the mask.
The important thing here is to have fun experimenting and learn how the different collision masks interact with the different collision functions and with each other!