I'm working on a 2D top-down shooter game in GameMaker Studio 2, featuring different weapons and obstacles all around the levels.
Since I don't need a high degree of simulation, I'm implementing simple physics for character movement and interaction (e.g. weapons, bullets, doors...) myself via GML rather than using the dedicated physics library.
Currently, shooting works as follows:
- The player presses the Left Mouse Button.
- A bullet object spawns, according to the current weapon.
- The bullet starts moving towards the mouse position at a given velocity.
Right now, the gameplay works quite well. However, I wanted to create different weapons with unique features, including different bullet speeds. And here's my problem.
I tried to increase bullet velocity values, but they started behaving strangely: When shooting against objects, hits occur slightly before visual contact. Sometimes, bullets 'warp' beyond small objects or corners as if a collision hasn't been detected at all.
There are two schematics, one for the general case:
And one for the collision with thin objects:
I think this is due to my code checking for collisions for given positions rather than sweeping along the expected motion trajectory. And, this doesn't work well with laser weapons and sniper rifles, since players expect them to be precise when shooting.
My initial implementation was the following:
Create Event:
b_damage = 25;
b_velocity = 100;
b_direction = point_direction(x, y, mouse_x, mouse_y);
Step Event:
var _xvel = lengthdir_x(b_velocity, b_direction);
var _yvel = lengthdir_y(b_velocity, b_direction);
var _coll = instance_place(x + _xvel, y + _yvel, obj_CollisionParent);
if ( _coll == noone )
{
// No collision occurred, bullet can travel
x += _xvel;
y += _yvel;
}
else
{
// Destroy bullet (shows sparks)
instance_destroy();
// Deal damage if an enemy was hit
if ( _coll.object_index == obj_Enemy )
{
scr_DealDamage(_coll, b_damage);
}
}
I then tried to increase precision by subdividing the collision check into smaller steps:
Step Event:
var _xvel = lengthdir_x(b_velocity, b_direction);
var _yvel = lengthdir_y(b_velocity, b_direction);
var _coll;
var _steps = 10;
for (var i = 0; i < _steps; i++)
{
_coll = instance_place(x + _xvel/_steps, y + _yvel/_steps, obj_CollisionParent);
if ( _coll == noone )
{
// No collision occurred, bullet can travel for 1/_steps distance
x += _xvel / _steps;
y += _yvel / _steps;
}
else
{
// Destroy bullet (shows sparks)
instance_destroy();
// Deal damage if an enemy was hit
if ( _coll.object_index == obj_Enemy )
{
scr_DealDamage(_coll, b_damage);
}
}
}
Now, collision is more precise, yet it's far from perfect. I still cannot use high velocity values without increasing the number of _steps
. Also, 100 collision checks per step for dozens of bullets seem too much for my CPU. I would like to improve this aspect of my game.
I would like to use a raycast-like function, similar to Unity's Physics.Raycast
. I need to find the final impact position with a low computational effort so that I can make bullets travel instantly, draw impact particles at the right position, and show realistic bullet trails.