How to Make a Multiplayer Game


How to Make a Multiplayer Game

Create your next big game with GameMaker’s built-in multiplayer.

In this tutorial, create a quick online action game, and play it with your friends:


Watch the video version here:

Start a Project

You require a 2022.600+ beta version to use multiplayer. Download it here.

Create a new project from a blank template. Give it a name, and hit “Let’s Go”.

undefined

Create Assets

This game will use three sprites:

  1. spr_player: The player
  2. spr_projectile: Released by the player
  3. spr_background: An image tiled across the level

undefined

Download this .yymps package, and drag it into your GameMaker window to import the sprites I’m using.

Or, create your own.

For each sprite, set the origin to Middle-Centre.

undefined

When the player or projectile rotates, it will rotate from the centre, just as you would expect in real-life.

Game Objects

Create the following objects:

  1. obj_game: This manages the multiplayer game. It doesn’t have a sprite.
  2. obj_player: This is used for each player instance. Assign spr_player to it.
  3. obj_projectile: This is the projectile that the player releases. Assign spr_projectile to it.

undefined

Create Your Level

From the Asset Browser, open the “Rooms” group, and double-click on “Room1” to edit it.

undefined

Let’s prepare this as our main level.

Open the Room Settings from the bottom-left part of the Room Editor. Set the Width and Height to 320 and 180 respectively.

undefined

Now in the Layers panel, select the “Background” layer.

Set the background sprite to spr_background, and enable Horizontal Tile and Vertical Tile.

undefined

That’s our background done – only objects are left.

Objects

Unlike a usual GameMaker game, we don’t want to place obj_player into the room. GameMaker’s multiplayer system will take care of that.

We only need to place obj_game so it can start the game.

Select the “Instances” layer, and from the Asset Browser, drag obj_game into the room.

undefined

Window Size

Run the game, and it will appear in a very small window.

undefined

If you run it through the GX.games target, it will get scaled up, but will be very low-resolution.

Let’s fix this, by upscaling our game from 320x180 to 1280x720.

In the Room Editor, go into the Room Settings at the bottom-left, and expand “Viewports and Cameras”.

undefined

Modify four options:

  1. Check the “Enable Viewports” option
  2. Expand “Viewport 0”, and check “Visible
  3. Under “Camera Properties”, set Width and Height to 320 and 180
  4. Under “Viewport Properties”, set Width and Height to 1280 and 720

Run the game now, and if you’re using Windows or Mac, the window will be larger:

undefined

On GX.games, it will now be higher resolution, though that may not make a visible difference at the moment.

With the boring set-up parts out of the way, we can finally start making our gameplay!

How to Start Multiplayer?

Open obj_game, and add the Create event.

undefined

You may be asked to choose between GML Code and GML Visual, so select what you want to use, and enable “Don’t ask again for this project”.

In this event, add the following code or actions:

rollback_define_player(obj_player, "Instances");

if (!rollback_join_game())
{
        rollback_create_game(2, true);
}

undefined

In this event, we are:

  1. Telling GameMaker which object to use for players: obj_player, and the layer where the players will be created ("Instances")
  2. Attempting to join a game if we were invited
  3. If we were not invited, then we create a new game

The new game is created with 2 players. It will only start when both players have joined.

The second argument for rollback_create_game() is true, which enables offline testing. We will later set this to false when taking our game online.

Development Begins

Run the game, and you will see a player peeking from the top-left corner:

undefined

These are actually two players. We need to place them in individual spots so they’re distinctly visible.

How to Position Players in Multiplayer?

Open obj_player, and add the Create event.

undefined

Add this code:

y = room_height / 2;

if (player_id == 0)
{
        x = 50;
}
else if (player_id == 1)
{
        x = room_width - 50;
}

undefined

In this event, we set the vertical (y) position of the player to the vertical centre of the room.

Then we set the horizontal (x) position depending on the player ID:

  1. The first player gets the ID 0. We set its x to 50.
  2. The second player gets the ID 1. We set its x to room_width - 50.

Run the game, and your players should now be in different places:

undefined

It’s time for a face-off.

Multiplayer Movement

In any game, you move the main character by pressing a button. So the first step in programming movement is reading input.

The usual ways of checking input, using a Key event or calling keyboard_check(), won’t work in multiplayer.

GameMaker automatically manages input between online players. You just call rollback_get_input() inside a player to read its input.

How to Read Input in Multiplayer?

Go to obj_player, and add the Step event.

undefined

Add this code here:

var _input = rollback_get_input();

if (_input.left)
{
        x -= 1;
}
if (_input.right)
{
        x += 1;
}
if (_input.down)
{
        y += 1;
}
if (_input.up)
{
        y -= 1;
}

undefined

We first get the input struct by calling rollback_get_input().

From that struct, we check for left, right, down and up, which are part of the default inputs for multiplayer.

For each key, we move the player on its corresponding axis. For example, pressing left reduces x, which moves the player left.

Going Crazy

Run the game, and you can now move with the arrow keys.

However, the second player is absolutely losing its mind.

This is happening because we enabled offline testing earlier during the tutorial.

The second player, which is not an actual player but a placeholder, gets random inputs every frame. That’s a far-from-ideal AI for sure, but it enables some very basic testing.

How to Enable Online Multiplayer?

Let’s enable and test online multiplayer first. We’ll add the rest of the features, such as aiming, firing and respawning later.

First, go to GX.games and sign in.

undefined

Go into your profile settings and change the “Default region” to your closest region. This region will be used for your multiplayer games, reducing lag.

Back in GameMaker, let’s disable offline testing.

Go to obj_game and open the Create event. Inside the rollback_create_game() call, set the second argument to false:

undefined

In GML Visual, just disable Sync test in the Create Rollback Game action:

undefined

Select the GX.games target, if you haven’t already.

undefined

You'll find this in the upper-right corner of your GameMaker window.

Run the game:

undefined

If you get a sign_in_required error in the game, you need to sign in on GX.games.

How to Test Multiplayer?

Now, test online multiplayer on your own device, by running two instances of the game.

Your game will have opened in Opera GX. Scroll down, and select “Copy Share Url”:

undefined

This link is used to invite players. Currently you’re testing locally, so you can’t send this to a friend yet.

Open this link in a second tab, and drag it out into its own window.

undefined

Arrange the windows on your screen so they’re both visible at the same time.

Both games will now start synchronising:

undefined

When that’s done, you’ll have control of both players: select a browser window to control its side.

Even though you’re testing locally, both games are connected through GX.games servers – so they’re actually communicating through the internet.

If you get an error, such as “room not found”, your first client may have timed out. Try reloading your game pages.

How to Upload to GX.games?

So you want to play this game with your friend: you can.

With the GX.games target selected, click on “Create Executable”:

undefined

Sign into Opera, and let the compiling process complete.

undefined

When it’s finished, you will see an “Edit Game on Opera” button: click it.

undefined

Your game will open on DevCloud. You may need to log in again.

On your game’s details page, enable Multiplayer, and set the number of players. For this tutorial, use 2.

undefined

Hit Save.

Now, open the Publishing menu.

Enable the Private version, and click on “Open Private Game”:

undefined

How to Invite Friends in Multiplayer?

Once your game opens, hit Play.

On the right side of your game, you will see a Multiplayer section.

A “Copy” button will appear once a room is hosted.

undefined

Click on it, and share the copied link with your friend. When they open it in Opera GX, they’ll be in your game in an instant!

You can also make use of GX.games’s “Invite friends” and “Match with other players” buttons, which appear before your game starts.

undefined

And with this, your game is now on the internet – anyone can connect and play! 🥳🎉

Now, let’s get back into our game and continue to develop features.

In obj_game’s Create event, re-enable offline testing:

undefined
GML Code

undefined
GML Visual

How to Create Custom Inputs?

We need mouse inputs for aiming, so let’s define some custom inputs.

This will also give you an opportunity to change controls, for example, use WASD instead of the arrow keys.

Open obj_game’s Create event. At the top, add this code:

rollback_define_input(
{
        left: vk_left,
        right: vk_right,
        up: vk_up,
        down: vk_down,
        mb_x: m_axisx,
        mb_y: m_axisy,
        fire: mb_left
});

undefined

You need to add this at the top of the event, before a multiplayer game is joined or created. You can’t change inputs after a game has been initiated.

This defines the left, right, up and down inputs as the arrow keys, which we already had as defaults.

Then it defines mb_x and mb_y inputs, which store the X and Y coordinates of the mouse in the room. Finally it defines a fire input, which corresponds to the left mouse button.

Here, you have the opportunity to swap the arrow keys with WASD input:

        left: ord("A"),
        right: ord("D"),        
        // or, use both:
        up: [vk_up, ord("W")],
        down: [vk_down, ord("S")],

undefined

As you can see for up and down, you’re able to assign multiple keys to one input, using an array.

So you can either use just WASD/arrow keys, or use both arrow keys and WASD.

How to Rotate The Player?

Our game now supports mouse input, so let’s make the player turn to look at the mouse cursor.

Open obj_player, and open its Step event.

Add this code at the end:

image_angle = point_direction(x, y, _input.mb_x, _input.mb_y);

undefined

This gets the direction from the player’s position, to the mouse cursor.

The mouse’s position is retrieved from the input struct, using the mb_x and mb_y inputs we defined earlier.

The direction value is applied to image_angle, which is the angle of the sprite’s rotation.

Run the game, and the players can now aim at the mouse!

As always, the “AI” player (pff) continues to receive random input.

So Anyway, I Started Blasting

You already created obj_projectile earlier in the tutorial. Now, spawn it when the player presses fire.

Open obj_player’s Step event.

At the end, add this:

if (_input.fire)
{
        var _proj = instance_create_layer(x, y, layer, obj_projectile);
        
        _proj.speed = 10;
        _proj.direction = image_angle;
        _proj.image_angle = image_angle;
        _proj.player = self;
}

undefined

If the fire input is held, this will:

  1. Create an instance of obj_projectile, at the player’s position and in the same layer
  2. Store its ID in a variable
  3. Through that ID, it will set some variables in the projectile:
  • speed, how fast it moves
  • direction, where it moves
  • image_angle, where it faces
  • player, which is a reference of the player that created it
The player variable will be used to ensure a projectile doesn’t damage its own player.

Run the game, press LMB and see the bullets go:

It’s, uhh… a bit like firing a water gun. Bullets should be a bit more controlled.

Let’s make it fire only when the mouse button is hit, and to fire a second time, you have to release and press it again.

Press it. Hold it. Release it.

Each defined input checks if the key is held down, like fire or left.

However, GameMaker also provides a *_pressed variant, which checks if the key was just pressed in the same frame.

It also provides a *_released variant to check if the key was just released.

Fix It

In your player’s Step event, go to the code you added last. The one that handles firing.

The condition there checks if for _input.fire:

undefined

In GML Code, change this to:

if (_input.fire_pressed)

In GML Visual, change the action to "If Key Pressed (Rollback)":

undefined

Run the game, and you can now only fire when you press LMB – holding does nothing.

Respawning

Where’s the action?!

Projectiles ain’t worth nothing if there’s no one to defeat.

It’s time to program collisions between a player and a projectile.

When the projectile hits a player, that player should:

  1. Show a particle effect
  2. Jump to a random position in the room, effectively “respawning”
  3. Destroy the projectile

And, it should only do all this if the projectile belongs to a different player.

You don’t want your own projectile to hit yourself, do you?

What’s That In Code?

Open obj_player, then add a Collision event with obj_projectile.

undefined

This event runs when a player and a projectile overlap in the room.

Add this code here:

if (other.player == self) exit;

effect_create_above(ef_ring, x, y, 0, c_white);
        
x = irandom_range(40, room_width - 40);

y = irandom_range(40, room_height - 40);
        
instance_destroy(other);

undefined

When creating a projectile, you’ll remember that we set the player variable to the self of the firing player.

undefined
GML Code

undefined
GML Visual

The condition in your new event checks if the projectile that hit you, is the same projectile that you fired.

Obviously, that shouldn’t hurt you. So, we exit the event, stopping the code’s execution.

The rest of the code then:

  1. Creates a “ring” effect at the player’s position, with a white colour.
  2. Sets the X position to a random value between 40 and room_width - 40.
  3. Sets the Y position to a random value between 40 and room_height - 40.
  4. Destroys the other instance, which is the projectile.

Scoring

You can now fire at the other player, and upon hitting, that player gets respawned.

But it’s not really fun until you can count it, is it?

Let’s add scoring: each hit increases a player’s points, which are displayed on the player.

A 3-Step Plan

Follow the three quick steps below to add scoring to the player object.

All of these are done in obj_player.

Step 1

Open the Create event, and create a points variable:

points = 0;

undefined

Step 2

Add the Draw End event, which runs after the game has been drawn.

undefined

In this event, draw the player’s points:

draw_text(x, y - 24, points);

undefined

The points text is drawn 24 pixels above the player.

Step 3

When a projectile hits you, increase the points in the attacking player.

Open the Collision event with obj_projectile.

At the end, add this:

other.player.points ++;

undefined

This gets the player from the projectile, and from that player, gets the points variable. It increases that variable by 1.

Are We There Yet?

We are.

Run the game, and with each player you take down, you get one point.

Hopefully real players aren’t as good as this AI, wow. Sweaty players smh.

Drawing Names

Each player gets a player_name variable, which you can draw to the screen.

For example, I’ll change the player's Draw End event to this:

draw_text(x, y - 24, player_name + ": " + string(points));

undefined

This will draw the player’s username along with their points:

undefined

To use a different font, create a new font asset, and apply it using draw_set_font() before you draw your text.

How to Mark The Local Player?

It might become hard to tell which player you are.

The solution is simple: use the player_local variable to draw something that tells you which player you’re controlling.

In obj_player’s Draw End event, I’ll add this:

if (player_local)
{
        draw_sprite(spr_arrow, 0, x, y - 12);
}

undefined

If the player is local, it draws an arrow sprite above it – you can go ahead and quickly make this sprite in GameMaker.

The player_local variable should only be used for visual effects, like we did above. Don’t use it to program different behaviour for your local player: all players should have the same behaviour given the same input.

In-game, you will now see that arrow on your player:

undefined

Note on State

You can’t change your game’s “state” before all players have joined.

The state consists of all objects that contribute to gameplay.

For this purpose, you can use the Rollback Start event, which runs when the game begins – after all players have joined.

undefined

For example, if you have an alarm that spawns enemies every second, start that alarm in the Rollback Start event.

You can also use the rollback_game_running variable, which becomes true after the game starts.

This ensures that you’re not creating any managed instances before the game starts, and getting yourself a fatal error.

What’s Next?

Read the manual to discover all you can do with Rollback.

Add multiplayer to an existing game.

Most importantly, play around with GameMaker. Get in there, make something, no matter how small.

Happy GameMaking!