How To Use Physics Raycast In GameMaker


GameMaker 2023.6 added a physics_raycast() function.

When Physics is enabled (in your room and for your objects), use this function to check for collisions in a straight line.

The function will check for collisions on a line from point A to point B. It will return an array with the collisions it found.

By default the function only looks for one collision, but you can ask it to return all collisions (if there were multiple).

undefined

We’re going to program this laser using raycasting and use it to destroy enemy ships. The laser’s range increases as you destroy more ships.

Project Set Up

You can download the starting project here. This contains a player ship with movement and enemy ships that spawn.

undefined

If you’re making your own project, make sure of the following:

  1. Your room should have Physics enabled (click on the room in your Asset Browser and find this setting in the Inspector).
  2. The objects you are using should have Physics enabled and their collision shape property set up.

Variables

Let’s define some variables in the player object’s Create event:

// Laser
laser_length = 100;
laser_end_x = x;
laser_end_y = y;

laser_length is how far the laser extends, which is also the length of the ray we’re going to cast.

laser_end_x and laser_end_y are the coordinates where the laser ends. We’re going to cast a ray from the instance’s x, y to this position.

Raycast Function

In the Step event, let’s detect the ending position of the laser:

// Laser
var _laser_dir = point_direction(x, y, mouse_x, mouse_y);
laser_end_x = x + lengthdir_x(laser_length, _laser_dir);
laser_end_y = y + lengthdir_y(laser_length, _laser_dir);

This will first get the direction from the instance toward the mouse cursor, so we can point the laser at the mouse. Then it will calculate the x, y position extending laser_length pixels from the instance in the direction of the mouse.

For example, if the laser_length is 100, then this x, y position will be 100 pixels away from the instance, in the direction of the mouse.

This will happen every frame so we can draw the laser. It will only activate on a click, which will destroy any enemy ships it finds:

if (mouse_check_button(mb_left))
{
        var _hits = physics_raycast(x, y, laser_end_x, laser_end_y, obj_enemy, true);

        if (array_length(_hits) > 0)
        {
                array_foreach(_hits, function(_inst)
                {
                        instance_destroy(_inst.instance);
                        laser_length += 20;
                });
        }
}

On a left click, this performs a raycast from x, y to laser_end_x, laser_end_y. It looks for obj_enemy and passes true for the last argument, meaning the function will return all the collisions it found instead of just one.

If the returned array is not empty (i.e. its length is >0), it calls array_foreach() to destroy every enemy ship it finds.

This function takes an array and a callback function. It then runs that function on every item in the array, passing the item itself into the function (see the _inst argument).

In the array returned by physics_raycast(), each entry is a struct with data on the collision. The instance variable in the struct is the instance that was hit (i.e. the enemy ship).

It then raises the laser length by 20 for each enemy destroyed.

NOTE: Because the callback function was defined in the scope of the player instance, its laser_length variable is accessible from within the function.

Finally, add this to your Draw event to draw the laser and the player ship:

// Draw laser
draw_line_width(x, y, laser_end_x, laser_end_y, 4);

draw_self();

The player can now destroy enemy ships with its laser. Try to get as many as possible in one shot!

undefined

There’s a lot more you can do with raycasting, as it also returns information on exactly where the ray hit an instance and the normal vector of the edge that it hit.

Normal Vector

Let’s use the normal vector information to create another ray bouncing off the hit enemy. This way you can hit more enemies!

In the Create event of the player, I’ll create an array to hold info on the extra laser shots, so I can draw them:

extra_lasers = [];

Now let’s go back into the Step event, inside the callback function that runs for each hit enemy ship. From each hit, we want to create a ray going into the direction of its normal, which can hit additional ships.

Each extra hit will also store its ray data in the array we just created, so they can be drawn. That array will be emptied with an alarm that runs 20 steps after the hit.

// Use normal to create extra laser
var _x1 = _inst.hitpointX;
var _y1 = _inst.hitpointY;
var _x2 = _x1 + _inst.normalX * 1000;
var _y2 = _y1 + _inst.normalY * 1000;

var _extra_hits = physics_raycast(_x1, _y1, _x2, _y2, obj_enemy, true);
if (array_length(_extra_hits) > 0) 
{
        array_foreach(_extra_hits, function(_inst2) { instance_destroy(_inst2.instance); } );
}

// Push to array
array_push(extra_lasers, {x1: _x1, y1: _y1, x2: _x2, y2: _y2});
alarm[0] = 20;

Using the normal vector, it creates a ray that extends 1000 pixels. For each hit, it destroys the instance it finds.

It pushes the ray data to the array as a struct.

Now in the Draw event you can draw these extra lasers:

// Draw extra lasers
var _len = array_length(extra_lasers);
for (var i = 0; i < _len; i++)
{
        var _laser = extra_lasers[i];
        draw_line_width(_laser.x1, _laser.y1, _laser.x2, _laser.y2, 4);
}

And all that… gives you this!

undefined

Of course, this tutorial was only one example. There’s a lot that you can do with raycasting, however don't forget that it's only for projects that use Physics, and not every project will need to use the Physics system.

Happy GameMaking!


Back to Tutorials