Unity Multiplayer | 2 players per device in a game (simple version)

Unity Multiplayer | 2 players per device in a game (simple version)

Context

A few days ago, I attended a game development meetup in Bogotá, Colombia. At the event, I was invited to come and give a talk about Unity’s new multiplayer features in Unity 6, using Netcode for GameObjects as the netcode solution. During the Q&A portion of the talk, 1 attendee gave me a very interesting question. They asked about how can a multiplayer game have 2 players on 1 device, and play with 2 other players that are playing on another device

This is a very useful question, because many online multiplayer games allow multiple people to play from 1 device (like game consoles). A good example of this is Overcooked! 2, developed by Ghost Town Games Ltd., & Team 17!

The Overcooked! games (made with Unity) are fun and chaotic “cooking simulators” where up to 4 players work together in increasingly crazy kitchen environments.

One initial solution we discussed is simply having 2 player objects being controlled by different sets of keys on the computer’s keyboard:

I knew it could theoretically be true, but I’ve never tried it. So I decided to go for it, and here’s how it looks:

The 2 blue cubes are owned and controlled by the left screen(device 1), and the 2 red cubes are owned and controlled by the right screen (device 2)

So let’s see what changes you’d need to do in order to get to this goal as well!

Starting point

In order to follow along this article, I’m assuming that you’re at least familiar with Unity 6’s new multiplayer features which include the Multiplayer Center, and the Quickstart Content. If you haven’t used them before, I recommend checking out this webinar we delivered on August 2024:

For this article, you only need to get to the part where you have the 2 player prefabs spawning on the localhost session (around the 32 min. mark):

When you get to this point, you’ll be spawning 1 player prefab per device/screen, and ready to continue with today’s goal.

Adjust the Network Manager

We need to do some quick adjustments to the NetworkManager object. We need to first clear the Player Prefab field in this component. For our goal, we’ll need to spawn the 2 players at the correct time, and not necessarily when the network manager connects. And in the end, this field is still an optional field:

Now this means that the network manager will have no knowledge of the player prefab, so let’s fix that by adding it in the Network Prefabs list. This is the list of prefabs that the Network Manager expects to be instantiated and spawned at runtime (at some point during the networked session):

After you double click on the list asset, the inspector will show the values in the list, which are initially empty:

Let’s add the provided player prefab into the network prefabs list. This will not make the player prefab spawn when you start the session though. We’ll need to later do it at runtime through code.

Create the Player 2 prefab

Thankfully, the multiplayer center’s quickstart content comes with pre-made assets that you can use and/or modify for you to reach your goals. I created a copy of the PlayerPrefab asset, and called it PlayerPrefab_P2:

One quick way of duplicating an asset is by clicking on it, and then press Ctrl+ D, (Cmd + D on Mac)

Now since our goal is to control our 2nd player with another set of keys, we need to remove the default Client Authoritative Movement script, which uses the WASD keys for movement:

This means that our P2 prefab will not be able to be moved, so let’s add a new script C# file to use the arrow keys for player 2 movement, called P2clientAuthoritativeMovement:

using Unity.Netcode;
using UnityEngine;

public class P2clientAuthoritativeMovement : NetworkBehaviour
{
    [SerializeField]
    private float Speed = 5f;

    private void Update()
    {
        // IsOwner will also work in a distributed-authoritative 
        // scenario as the owner has the Authority to update the object.
        if (!IsOwner || !IsSpawned)
            return;

        var multiplier = Speed * Time.deltaTime;

        // Same logic as the pre-made code from the quickstart content
        //   menu, but with arrow-keys control, instead of WASD
        if (Input.GetKey(KeyCode.LeftArrow))
        {
            transform.position += new Vector3(-multiplier, 0, 0);
        }
        else if (Input.GetKey(KeyCode.RightArrow))
        {
            transform.position += new Vector3(multiplier, 0, 0);
        }
        else if (Input.GetKey(KeyCode.UpArrow))
        {
            transform.position += new Vector3(0, 0, multiplier);
        }
        else if (Input.GetKey(KeyCode.DownArrow))
        {
            transform.position += new Vector3(0, 0, -multiplier);
        }
    }
}

It’s essentially the same logic as the default client authoritative movement code, just using the arrow keys instead.

Here’s how it’ll look in the inspector:

Now let’s add our new prefab into the Network Prefabs list we modified before:

Creating the player spawner object

Now that we have prepared our player 2 prefab, let’s crate a new empty game object to handle the spawning of the 2 players in the scene:

Let’s now add some code to our new object to handle the spawning. The code will store the value of the 2 player prefabs, and spawn the 2 of them when a successful connection is established between client and server:

Now copy and paste the following code:

using UnityEngine;
using Unity.Netcode;

public class Spawn2PlayersOnDevice : NetworkBehaviour
{
    [SerializeField]
    private GameObject m_player1Prefab;

    [SerializeField]
    private GameObject m_player2Prefab;

    private void Start()
    {
        // register to the client connected event
        NetworkManager.Singleton.OnClientConnectedCallback +=
            OnClientConnected;
    }

    private void OnClientConnected(ulong clientId)
    {
        if (!NetworkManager.Singleton.IsClient)
            return;

        // Tell the server to spawn the 2 player objects, and assign
        //   give ownership to this specific client
        Spawn2PlayersOnNetworkSessionStartRpc(clientId);
    }

    [Rpc(target:SendTo.Server, Delivery = RpcDelivery.Reliable)]
    private void Spawn2PlayersOnNetworkSessionStartRpc(ulong clientId)
    {
        if (!IsServer)
            return;

        SpawnPlayerPrefab(m_player1Prefab, clientId);
        SpawnPlayerPrefab(m_player2Prefab, clientId);
    }

    private void SpawnPlayerPrefab(
        GameObject playerPrefab,
        ulong clientId)
    {
        var newPlayer = Instantiate(playerPrefab);

        // Now spawning the player on the server, and assigning 
        //   the ownership to the specified client
        var networkObject = newPlayer.GetComponent<NetworkObject>();
        networkObject.SpawnWithOwnership(clientId);
    }
}

Since this script inherits from the NetworkBehaviour class, it means that it’s using functionality from the netcode layer, and we need to add a NetworkObject component to the PlayersSpawners object. Thankfully, once you select the object, the netcode layer recognizes this missing component and it explicitly asks you to add it:

Click on “Yes“, and the Network Object component will be automatically added:

And last but not least, make sure to add the 2 player prefabs to the serialized fields:

Testing our gameplay

Let’s bring in our 2nd device, being helpfully simulated by the Play Mode tools:

And in the window, click on the Player 2 checkbox to activate your 2nd player:

And here we go, We’ve now reached our established goal at the beginning of this article!

Additionally, one of the new tools in Unity 6 is the scene view’s network visualization tool. It helps us to determine that each pair of players is owned by their respective devices:

Device 0 is the host of the session (players 1 &2), and device 1 is the client (players 3 & 4)

Conclusion

In the end, this is a very simplified solution of the problem. For this article, I wanted to show that on a very basic level, you can indeed have 2 players playing in a multiplayer game from 1 device.

Modifications and improvements to this solution can include:

  • Adding the ability to choose how many players are going to join from 1 device.

  • Differentiating the player count from the connected device count in order that you do not exceed any game-specific limit of players.

  • Using more complex input detection like being able to use a mix of multiple devices (keyboard, game controllers, etc.), instead of just sharing 1 keyboard.

  • Differentiating what text or voice chat came from which player, or perhaps have 2 players share that communication channel

Lessons learned

  • The only real requirement for being able to control one player object, is that the ownership is assigned to the correct device.

  • After making sure your player objects are owned by the correct device, assigning input for different players is essentially the same as single-player offline games

Please let me know what you think!

  • What kind of game comes to mind when you think of applying this feature?

  • Are there any particular game mechanics that you’d like to see shown in an online multiplayer game?

  • Have you tried Unity’s new multiplayer tools, and are you considering making your own online multiplayer game?

🎉🎉🎉 Happy building! 🎉🎉🎉