Split Screen Multiplayer with Gamepad Input

Hey there! This tutorial covers taking input for multiple players and displaying them through separate cameras.

I will be creating two players in this tutorial, but you can use this flexible system for any number of players!

This tutorial is for GameMaker Studio 2. You will have to make minor changes to the code if you want to use it in 1.4.

Player Input

First, we’ll set up the players & their input. So we’ll make an object called oPlayer, and in its Variable Definitions section, create an Integer variable called pID.


This variable will store the player number of that object; for example, 0 for Player 1, 1 for Player 2, and so on. You can change this for specific instances when you place them in the room.


Next we’ll add an object called oMain; this will manage the input and cameras.

First up, I’ll code its Create event.

players = instance_number(oPlayer);

    other.player[pID] = id;

The first line will create a variable called players with the total number of players present in the room.

The next part will create an array called player with the IDs of the players. So player[0] will be Player 1, player[1] will be Player 2, and so on. We’ll use this array later for the cameras.

More code:

sprite[0] = sPlayer1;
sprite[1] = sPlayer2;

Here I’m initializing an array with the sprites for each player.

Optional: If your players need multiple sprites, you can just make it a 2D array:

sprite[0, 0] = sPlayer1_Idle;
sprite[0, 1] = sPlayer1_Run;
sprite[1, 0] = sPlayer2_Idle;
sprite[1, 1] = sPlayer2_Run;


Next we’ll create a list and add all the active gamepads inside it.

gpList = ds_list_create();

This list will store the IDs of the gamepads connected.

For adding the gamepads, we’ll need to add the Asynchronous System event.


Inside it, we’ll add this:

//Add gamepads
if (async_load[? "event_type"]=="gamepad discovered"){
    ds_list_add(gpList, async_load[? "pad_index"]);

//Remove gamepad
if (async_load[? "event_type"]=="gamepad lost"){
    var gID = ds_list_find_index(gpList, async_load[? "pad_index"]);
    ds_list_delete(gpList, gID);

For context, here’s what the docs say about this event:

When this event is triggered for a gamepad being connected or disconnected it will return one of the following key/value pairs in the async_load map:

  • event_type” – the type of system event received, which will be one of the following strings:
    • gamepad discovered” – happens when the system reports a new gamepad has been connected
    • gamepad lost” – happens when the system has lost connection to a gamepad
  • pad_index” – the index of the pad that has been added or removed

The first part of the code detects whether a gamepad has been connected. If it has, it adds its id to the list (gpList).

The second part detects whether a gamepad has been removed. If it has, it gets the position of the gamepad inside the list (gID), then removes it.


Now we’ll set up the controls.

In oMain’s Create event, I’ll add this:

enum cr{

These are the controls we’ll need. They’re pretty self-explanatory. Enums are nothing hard to understand either; they’re just a group of numbers. Read more about Enums on this page.


hor: Horizontal axis
ver: Vertical axis
A: A Button
B: B Button
Apressed: A Button Pressed
Bpressed: B Button Pressed

We’ll do the control detection in the Begin Step event:

//Player 1
global.controls[0, cr.hor] = keyboard_check(vk_right) - keyboard_check(vk_left);
global.controls[0, cr.ver] = keyboard_check(vk_down) - keyboard_check(vk_up);
global.controls[0, cr.A] = keyboard_check(vk_rshift);
global.controls[0, cr.B] = keyboard_check(vk_rcontrol);
global.controls[0, cr.Apressed] = keyboard_check_pressed(vk_rshift);
global.controls[0, cr.Bpressed] = keyboard_check_pressed(vk_rcontrol);

As you can see, we’re assigning the controls to a 2D array with the following format:

global.controls[player_number, control_type]

So, in the code above, we’re getting the input for Player 1 (0).

Similarly, we’ll get the input for Player 2 (1):

//Player 2
if (players>=2){
    global.controls[1, cr.hor] = keyboard_check(ord("D")) - keyboard_check(ord("A"));
    global.controls[1, cr.ver] = keyboard_check(ord("S")) - keyboard_check(ord("W"));
    global.controls[1, cr.A] = keyboard_check(vk_lshift);
    global.controls[1, cr.B] = keyboard_check(vk_lcontrol);
    global.controls[1, cr.Apressed] = keyboard_check_pressed(vk_lshift);
    global.controls[1, cr.Bpressed] = keyboard_check_pressed(vk_lcontrol);

You can see that we have a condition that makes sure that there are at least 2 players.

The controls for Player 1 are the arrow keys and right shift/control, and the controls for Player 2 are the WASD keys and left shift/control.

Now, let’s add gamepad input (after the keyboard controls):

for(var i=0; i<ds_list_size(gpList); i++){
    var gp = gpList[| i];
    if (gamepad_is_connected(gp)){
        global.controls[i, cr.hor] += gamepad_axis_value(gp, gp_axislh);
        global.controls[i, cr.ver] += gamepad_axis_value(gp, gp_axislv);
        global.controls[i, cr.A] += gamepad_button_check(gp, gp_face1);
        global.controls[i, cr.B] += gamepad_button_check(gp, gp_face2);
        global.controls[i, cr.Apressed] += gamepad_button_check_pressed(gp, gp_face1);
        global.controls[i, cr.Bpressed] += gamepad_button_check_pressed(gp, gp_face2);

        global.controls[i, cr.hor] = clamp(global.controls[i, cr.hor], -1, 1);
        global.controls[i, cr.ver] = clamp(global.controls[i, cr.ver], -1, 1);
        global.controls[i, cr.A] = clamp(global.controls[i, cr.A], 0, 1);
        global.controls[i, cr.B] = clamp(global.controls[i, cr.B], 0, 1);
        global.controls[i, cr.Apressed] = clamp(global.controls[i, cr.Apressed], 0, 1);
        global.controls[i, cr.Bpressed] = clamp(global.controls[i, cr.Bpressed], 0, 1);

This loop will go through all the gamepads in the list (gpList). The gamepad ID will be stored in gp. If that gamepad is connected:

This part will add the input values to the controls.

This part will limit the the axis controls to (-1, 1) and the button controls to (0, 1). That’s because if, say, the keyboard right and gamepad axis right are pressed at the same time, that input will become 2, and the player will be able to move at double speed. We don’t want that, so we’ll limit the controls using clamp.


Now we’ll make our player. How you make it is up to you; but here’s the important stuff.


For the input, you’ll have to read the controls from the global.controls array using the pID variable.

var hor, ver, A, B;
hor = global.controls[pID, cr.hor];
ver = global.controls[pID, cr.ver];
A = global.controls[pID, cr.Apressed];
B = global.controls[pID, cr.Bpressed];

This’ll automatically get the right controls based on the player number (pID). So you can use these variables in your player for all the behavior.


You will also have to set the sprite from the sprite array using pID:

sprite_index = oMain.sprite[pID];

If you want to use the player code that I’m using, look here. Note that you will also need an oBullet object since my code spawns one when A is pressed.


Now you can place two instances of oPlayer in a room, and in the second one, set pID to 1 (to set it as Player 2):


To make sure this system works correctly, you’ll have to open the Instance Creation Order menu and move oMain to the bottom (this menu can be found at the bottom left of the Room Editor):


If you don’t do this, then you might get an error saying that the variable pID hasn’t been set.

You can run the game and see that both of the player instances are functional.



Now we’ll set up cameras to follow each player around.

In the Create event of oMain, we’ll add this:

view_enabled = true;

var width = 960, height = 540, scale = 1;

for(var i=0; i<players; i++){
    view_visible[i] = true;

    //Create camera
    var w = width/players;
    var cam = camera_create_view(0, 0, w, height, 0, player[i], -1, -1, w/2, height/2);
    view_set_camera(i, cam);
    //Set view position
    view_xport[i] = w*i;
    view_wport[i] = w;

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

The first line will enable the views.

The second line will initialize the width & height of the complete view, with the scale of the window (So, the window width will be width*scale).

Then the for loop will loop through the number of players, and create the camera for each. First we’ll make the view for that player visible.

Then we’ll create the camera. w will be the width of the camera, which is the total width divided by the number of players:

var w = width/players;

Then we’ll create that camera using camera_create_view(), and in the object argument, use player[i], to attach that camera to the appropriate player instance. The camera will then be set to the right view.

Then the view_xport will be set to w*i. This is the view’s position on the screen. If you don’t set this, all the views will be drawn at the same position. view_wport, which is the width of a view, is set to w.

Then at last, the window’s and the application surface‘s sizes will be set to width*scale and height*scale.


Now if you run the game, you’ll see that each player has its own camera!

2 Players

Adding More Players

If you want to add more players, make sure you:

  • Add the sprites to the sprite array in oMain‘s Create event
  • Add the keyboard controls in oMain‘s Begin Step event

Then you can add as many as you like!

4 Players

Download This Project (GMS2)


Buy my merch lol

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 you later, and till then, happy dev’ing!


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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s