Physics Racing Game – Track (Part 1)

Hey there! In this tutorial, we’ll build a simple physics racing game example. We’ll start with the car & the track.

2
This is what we’ll achieve in this tutorial

Car

First we’ll create an object called oCar. Enable “Uses Physics”, and set the physics settings:

0

You will have to adjust these settings if your car is of a different size. Mine is 32×32.

Create event

//Car
topspeed = 20;
accspeed = 10;
toprot = 200;
accrot = 80;

//Camera
view_enabled = true;
view_visible[0] = true;

var width = 480, height = 270, scale = 2;

var cam = camera_create_view(0, 0, width, height, 0, id, -1, -1, width/2, height/2);
view_set_camera(0, cam);

window_set_size(width*scale, height*scale);
surface_resize(application_surface, width*scale, height*scale);

window_set_position(display_get_width()/2-(width*scale)/2, display_get_height()/2-(height*scale)/2);

The //Car part initializes variables for the car. topspeed is the maximum speed, accspeed is the acceleration, toprot is the maximum rotation, and accrot is the rotation acceleration.

The //Camera part creates a camera of 480×270 size with a window scale of 2, and positions the window in the center of the display.

Step event

//Input
var hor, ver;
hor = (keyboard_check(ord("D")) || keyboard_check(vk_right))-(keyboard_check(ord("A")) || keyboard_check(vk_left));
ver = (keyboard_check(ord("S")) || keyboard_check(vk_down))-(keyboard_check(ord("W")) || keyboard_check(vk_up));

//Movement
if (phy_speed<topspeed){
    physics_apply_local_force(0, 0, -(accspeed*sprite_width*ver), 0);
}

//Rotation
if (abs(phy_angular_velocity)<toprot){
    physics_apply_torque(hor*accrot);
}

The //Input part will get the vertical input in ver and the horizontal input in hor. The former will be used to accelerating or stopping the car, and the latter will be used for turning it.

The //Movement part will check whether the speed (phy_speed) is smaller than topspeed. If it is, it will apply a force based on the sprite’s width, and ver. Read more about that function here.

The //Rotation part checks whether the absolute rotation (angular velocity) is smaller than toprot. If it is, it applies a torque based on horRead more about that function here.

Now you can place the car in a room and it will work.

In the room, make sure you enable Physics under Room Properties and set Gravity Y to 0.

1

Here’s how it looks:

0

Track

Now we’ll come to the main part: the track.

Path

We’ll make the track in a path. So, create a path resource and design the track.

2

If your path doesn’t smooth like mine does, make sure you enable Smooth Curve (can be seen in the screenshot).

In this project, we’ll be giving the related path & rooms the same number. So for the first level, the room is named room0 and the path is named path0.

You can create a path layer in the room and place it there to visualize where the track will appear in the room:

3

Also, remove the player instance from the room, since we’ll be placing it through code.

oMain

Now we’ll create an object called oMain. We’ll need to place it in a new instance layer below the one with the player (where the player will be placed through code), since the track will need to appear below the player. So, I’ll name that layer Track, and the one with the player will be Instances.

4

Make sure you place this object at (0, 0) or collisions won’t work properly.

Now inside oMain, make sure you enable Uses Physics.

Create Event

//Level
levelPath = asset_get_index("path" + string_digits(room_get_name(room)));
pathWidth = 128;
pathColor = c_white;

//Cars
instance_create_layer(path_get_point_x(levelPath, 0), path_get_point_y(levelPath, 0), "Instances", oCar);

//Vertex
vertex_format_begin();

vertex_format_add_position();
vertex_format_add_color();

vFormat = vertex_format_end();

vBuff = vertex_create_buffer();

vertex_begin(vBuff, vFormat);

var pathSize = path_get_number(levelPath);
var incr = (1/pathSize)/8;
for(var i=0; i<=1; i+=incr){
 var px = path_get_x(levelPath, i mod 1);
 var py = path_get_y(levelPath, i mod 1);
 
 var pnext = (i+incr) mod 1;
 
 var pxn = path_get_x(levelPath, pnext);
 var pyn = path_get_y(levelPath, pnext);
 var pdir = point_direction(px, py, pxn, pyn);
 
 var px1 = px + lengthdir_x(pathWidth/2, pdir-90);
 var py1 = py + lengthdir_y(pathWidth/2, pdir-90);
 var px2 = px + lengthdir_x(pathWidth/2, pdir+90);
 var py2 = py + lengthdir_y(pathWidth/2, pdir+90);
 
 vertex_position(vBuff, px1, py1);
 vertex_colour(vBuff, pathColor, 1);
 vertex_position(vBuff, px2, py2);
 vertex_colour(vBuff, pathColor, 1);
}

vertex_end(vBuff);
vertex_freeze(vBuff);

//Level
This part initializes levelPath, which contains the path to be used for the current room. It uses the pathN format to detect the paths, where N is the room number (room0). It uses the function string_digits to extract that number from the room name.

pathWidth is the width of the path and pathColor is the color.

//Cars
This part creates the car instance in the Instances layer. It gets the position of the first point in the levelPath, and places the car there.

//Vertex
This part creates a vertex buffer for drawing the level. A vertex buffer is basically a collection of triangles. Read this to know more about them.

First we’ll will create a vertex format (vFormat) with a position and a color. Then we’ll create a vertex buffer called vBuff, and begin writing to it using vertex_begin.

Then we’ll run a loop from 0 to 1 with incr being the increment. The smaller that number, the more triangles there will be; and the path will look smoother.

px and py are the coordinates of a point on the path. pnext is the next step for i. You can see “mod 1” with both i and pnext. That’s done so that when i/pnext reaches 1, it becomes 0, to end where it began (so that the last and the first points on the path meet).

pdir is the direction from the current point towards the next point (pxn, pyn). Using that direction, two points (four coordinates: px1, py1, px2, py2) will be calculated. Those two points will be the edges of the path at that point. Here’s a visualization:

nodes

The blue points are the path points (px, py). The red points are the edges (px1, py1, px2, py2). The light blue arrow is the direction towards the next point (pdir).

So, using vertex_position those points will be added to the vertex buffer, along with their colors, which will be added using vertex_color.

When the loop ends, writing to the vertex buffer will be ended using vertex_end, and it will be frozen using vertex_freeze, because we won’t need to modify it again.

Now inside the Draw event, we’ll add this:

vertex_submit(vBuff, pr_trianglestrip, -1);

We’re using pr_trianglestrip so that it draws a strip of triangles using our points (like shown in the picture above). These are all the types of primitive types we can use:

Now, if you run the game, you can see the path:

1

Collisions

We’ll build the collision fixtures in the same for loop we built the vertex buffers in. So, before that loop starts, create the fixtures:

levelFix1 = physics_fixture_create();
physics_fixture_set_chain_shape(levelFix1, 1);
levelFix2 = physics_fixture_create();
physics_fixture_set_chain_shape(levelFix2, 1);

This will create two fixtures, and set them to be chain shaped. This means that these fixtures will have points connected with lines. One fixture will go through one edge of the path, and the other will go through the other edge.

So, inside the for loop (where the vertex buffers were built), add this (at the end):

//Fixture
physics_fixture_add_point(levelFix1, px1, py1);
physics_fixture_add_point(levelFix2, px2, py2);

This will add the point on one edge to the fixture levelFix1, and the point on the other edge to the fixture levelFix2.

After the loop (outside of it), add this:

physics_fixture_bind(levelFix1, id);
physics_fixture_delete(levelFix1);
physics_fixture_bind(levelFix2, id);
physics_fixture_delete(levelFix2);

This will bind (set) the fixtures to the oMain instance, and delete them from memory since they’re no longer needed.

So the collisions have been set up, but you can’t collide with anything yet. To enable that, create an object called oPhyParent, and inside it, add a collision event with itself. If you’re using GM:S 1.4, you will have to add a blank code action inside it.

5

Now you can set all the physics objects (oCar, oMain) to have that object as its parent; and the collisions will work. If you run the game, you can collide with the path edges:

2

Conclusion

You can download the project here (GMS2)

Make sure you follow this blog (just click Follow at the bottom of this page) to receive an email whenever I post new tutorials. I will be adding more stuff to this example, so make sure you follow so that you don’t miss it!

If you need free GML help or want to provide GML help to those who need it, join our Discord server! We have a fun little community there (and a fun bot), it’ll be good to have you too!

See ya later, and till then, happy dev’ing!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s