Advanced iOS Summer Bundle

3 brand-new books on SwiftUI, Combine and Catalyst — $99.99 for a limited time!

Introduction to Multiplayer Games With Unity and Photon

Learn how to make your own multiplayer game with Unity and the Photon Unity Networking (PUN) library.

5/5 2 Ratings

Version

  • C# 6, Unity 2018.3, Unity

If you’ve been making games with Unity for some time, you know there’s a lot of hard work that goes into creating a game. Things such as level design, game mechanics, and progression take a lot of trial and error and careful design to get right. Even then, only a limited number of people are able to complete a full game.

Compared to that, multiplayer games such as Fortnite and PUBG have taken the world by storm. These games, which are easy to learn but hard to master, keep getting more and more popular with a record-breaking number of people. Some people even make a career out of these games as Twitch streamers, something that is very difficult to do with single player games.

In this tutorial, you’ll learn how to make your own multiplayer game with Unity and the Photon Unity Networking library (PUN for short).

Specifically, you’ll learn:

  • The key differences between Unity Networking and PUN.
  • How to create a Lobby scene where players can join.
  • How to load the game scene and synchronize the Transform values of players.

Let’s get started.

Photon vs Unity — Which is better?

Before getting on to the tutorial, let’s take a look at the key differences between Unity Networking and Photon Unity Networking.

Exploring the Architecture

Both Unity and PUN have similar low-level APIs. But the architecture required for these libraries to use these APIs is the key differentiating factor between them.

Source: https://www.youtube.com/watch?v=xLECRl1eyGk

The above diagram depicts how messages are transferred between nodes in a network in Unity and in PUN.

Unity Networking supports a server/client architecture. All messages have to go through the Host client and can’t be sent directly between nodes. For example, based on the diagram above, messages transmit from client B to client C using the following path: Client B ▸ Relay Server ▸ Host A ▸ Relay Server ▸ Client C.

As you can see, the message takes a total 4 hops from source to destination. In addition to this, if the Host gets disconnected from the network, the game stops.

PUN has a similar server/client architecture, but also supports peer-to-peer sending of messages. For example, based on the diagram above, messages transmit from Client B to Client C using the following path: Client B ▸ Relay Server ▸ Client C.

That’s a total of 2 hops compared to the 4 in Unity for the same message transfer between two nodes. In addition to this, PUN could potentially bypass the Relay Server entirely and Client B can communicate directly with Client C, thus reducing the hops to 1.

Because of this, PUN is faster than Unity.

Pricing

Another key difference between Unity and PUN is the Pricing model.

Unity offers a free number of Concurrent Users (CCU) for each license.

  • Personal: 20 Concurrent Users
  • Plus: 50 Concurrent Users
  • Professional: 200 Concurrent Users

If you need to increase the number of CCUs that your game supports, you’ll have to pay for the extra bandwidth that you use. You’ll be charged $0.49/GB of traffic that travels through the Unity infrastructure (Matchmaker and Relay Server).

Source – https://support.unity3d.com/hc/en-us/articles/209604483-How-much-does-Unity-Multiplayer-cost-

PUN also provides up to 20 CCU, 8000 Monthly Actives and 500 Messages per room for Free. In addition to the free plan, it offers a very nice $95 one-time payment option for 60 Months, which includes 100 CCUs, 40k monthly actives, and 500 messages per room. This option is great for small indie developers who are on a budget.

The combination of comparably faster performance, well written tutorials and documentation, and a healthy choice of pricing plans make PUN a very good option for developers to build multiplayer games.

Pricing – https://www.photonengine.com/en-US/PUN/pricing

Getting Started

You’ll need Unity version 2018.3 or newer to load the starter project successfully. In case you don’t have it installed on your system, you can download it from unity3d.com.

Note: This tutorial is intended for users with basic scripting knowledge and who are comfortable with the editor. If you’re new to Unity, you can check out our beginner tutorials at https://www.raywenderlich.com/unity

Once you have everything set up, download the starter project using the Download Materials link at the top or bottom or this tutorial, and open Photon Starter with Unity.

Project Overview

Take a look at the folder structure in the Project window:

Here’s what each folder contains:

  • LogViewer: Files required for the LogViewer asset.
  • Materials: Materials required for this tutorial.
  • Models: Models required for this tutorial.
  • Photon: Files required for the Photon Library.
  • PhysicsMaterial: Physics Materials used in the project.
  • Prefabs: Prefabs for the tutorial.
  • Resources: Prefabs that have to be synced by Photon.
  • Scenes: The game’s Main Menu and Arena scenes.
  • Scripts: The scripts required for the project.

Open the Launcher scene from Assets / RW / Scenes.

If you’ve played multiplayer games before, you’ll know that before you and your friends start playing the game together, you first need to create or join a Lobby (or a Game Room) where you all connect and then the lobby leader initiates the game.

In this scene you’ll make a Lobby using Photon Unity Networking. You’ll create a Room with a specific name, and then your friend can join your lobby by entering the same room “name” in their instance of the game.

Once you and your friend have joined the same room, the lobby leader can load the MainArena scene, where you both can play the game together.

Creating a Photon Account

Before you get started with building the Lobby, you’ll need to create an account at the official site of Photon Engine by going to https://dashboard.photonengine.com/en-us/account/SignUp.

  1. Once you’ve successfully signed up, you’ll be redirected to your account Dashboard.
  2. On the same page, click on the Create a new app button. Enter a name for your app, “SampleApp” for example, and click on the Create button at the bottom of the form.
  3. Finally, on the Dashboard page, you’ll see a box with the details of your “SampleApp”. Copy the AppId and store it somewhere, as you’ll use this later on in the tutorial for testing.
Note: The Photon Unity Networking library is already present in the starter project you downloaded at the beginning of this tutorial, but you can also use it in your existing projects by downloading the unitypackage from the Asset store.

Back in the Unity Editor, open the PUN Wizard by selecting Window ▸ Photon Unity Networking ▸ PUN Wizard.

In the PUN Wizard window, click Setup Project and enter the AppId that you saved while setting up a photon engine account in the previous section. Click the Setup Project button.

Now that you have Photon set up, let’s get started with building the Lobby.

Creating the Lobby

Here’s an overview of what the Launcher.cs script is going to do, in order:

  1. Connect to the Photon Network.
  2. Once connected, take two inputs from the user: The Player Name they want to use, and the Room Name they want to Create or Join.
  3. If a Room with the entered name doesn’t exist, create a Room with that name and make the current player the Lobby Leader. If the Room exists, the player will join the Room.
  4. Once both players have connected to the same room, the Lobby Leader can load the MainArena scene.

Open the Launcher.cs script in Assets / RW / Scripts.

Add the following lines of code in Launcher.cs after the comment // Start Method.

Don’t worry about the intermediate errors while adding the code. All the necessary code will be explained in sections below.

// Start Method
void Start() 
{
    //1
    PlayerPrefs.DeleteAll(); 

    Debug.Log("Connecting to Photon Network");

    //2
    roomJoinUI.SetActive(false);
    buttonLoadArena.SetActive(false);

    //3
    ConnectToPhoton();
}

void Awake()
{
    //4 
    PhotonNetwork.AutomaticallySyncScene = true;
}

Here’s a brief explanation of the code.

  1. When connecting to a server, PUN pings all available servers and stores the IP address of the server with the lowest ping as a PlayerPrefs key-value pair. This can lead to unexpected behavior during the connection stage. To avoid any anomalies, DeleteAll is called when the Launcher scene starts.
  2. The UI elements are hidden by default, and are activated once a connection to a Photon server is established.
  3. ConnectToPhoton is called to connect to the Photon network.
  4. The value of AutomaticallySyncScene is set to true. This is used to sync the scene across all the connected players in a room.

Loading the MainArena Scene

To get the Input from the TextField UI elements, you need a public method to store the value in a TextField. Add the following code after the comment // Helper Methods:

// Helper Methods
public void SetPlayerName(string name)
{
    playerName = name;
}

public void SetRoomName(string name)
{
    roomName = name;
}

Next, add the following methods after the comment // Tutorial Methods:

// Tutorial Methods
void ConnectToPhoton()
{
    connectionStatus.text = "Connecting...";
    PhotonNetwork.GameVersion = gameVersion; //1
    PhotonNetwork.ConnectUsingSettings(); //2
}

How this code works:

  1. The GameVersion parameter is set. This is the version string for your build and can be used to separate incompatible clients. For this tutorial, it will be set to 1 (set when the gameVersion field is declared).
  2. ConnectUsingSettings is called, which is used to connect to Photon as configured in the editor. You can read more in the docs.

Next, add the following lines of code:


public void JoinRoom()
{
    if (PhotonNetwork.IsConnected)
    {
        PhotonNetwork.LocalPlayer.NickName = playerName; //1
        Debug.Log("PhotonNetwork.IsConnected! | Trying to Create/Join Room " + 
            roomNameField.text);
        RoomOptions roomOptions = new RoomOptions(); //2
        TypedLobby typedLobby = new TypedLobby(roomName, LobbyType.Default); //3
        PhotonNetwork.JoinOrCreateRoom(roomName, roomOptions, typedLobby); //4
    }
}

public void LoadArena()
{
    // 5
    if (PhotonNetwork.CurrentRoom.PlayerCount > 1)
    {
        PhotonNetwork.LoadLevel("MainArena");
    }
    else
    {
        playerStatus.text = "Minimum 2 Players required to Load Arena!";
    }
}

Looking at each line of code comment-by-comment:

  1. The NickName parameter of the LocalPlayer is set from the private variable playerName. This is the name that will be available to everyone you play with on the Photon network, and is used as a unique identifier.
  2. An object of class RoomOptions is declared. This wraps up common room properties required when you create a room. It can be used to give the user control of various characteristics of the room such as maximum number of players that can join, PlayerTtl (Player Time To Live), etc. (Docs)
  3. An object of class TypedLobby is declared. This refers to a specific lobby type on the Photon server. The name and lobby type are used as an unique identifier. The Room name is set from the private variable roomName and the Lobby type is set as Default. (Docs)
  4. Finally, the JoinOrCreateRoom method of PhotonNetwork class is called with arguments — roomName, roomOptions and typedLobby that were set earlier. If the method is called by a new user with a new Room name that does not yet exist, a room is created and the user is set as the Lobby Leader. Otherwise, the other players just join the room.
  5. Once the Lobby Leader has created and joined a Room, the LoadArena button will be set active. A check is set before loading the Arena to make sure the MainArena scene is loaded only if both players have joined the room.

PUN Callback Methods

Now that you’ve added the basic building blocks of Joining and Creating a Room, all that’s left to do is add PUN Callback methods that will take care of the exception handling.

Add the following code after the comment // Photon Methods:

// Photon Methods
public override void OnConnected()
{
    // 1
    base.OnConnected();
    // 2
    connectionStatus.text = "Connected to Photon!";
    connectionStatus.color = Color.green;
    roomJoinUI.SetActive(true);
    buttonLoadArena.SetActive(false);
}

public override void OnDisconnected(DisconnectCause cause)
{
    // 3
    isConnecting = false;
    controlPanel.SetActive(true);
    Debug.LogError("Disconnected. Please check your Internet connection.");
}

public override void OnJoinedRoom()
{
    // 4
    if (PhotonNetwork.IsMasterClient)
    {
        buttonLoadArena.SetActive(true);
        buttonJoinRoom.SetActive(false);
        playerStatus.text = "You are Lobby Leader";
    }
    else
    {
        playerStatus.text = "Connected to Lobby";
    }
}

Let’s look at what each piece of code does:

  1. As the name suggests, OnConnected gets invoked when the user connects to the Photon Network. Here, the method calls the base method onConnected(). Any additional code that needs to be executed is written following this method call.
  2. These methods provide feedback to the user. When the user successfully connects to the Photon Network, the UI Text connectionStatus is set, and the roomJoinUI GameObject is set to visible.
  3. OnDisconnected gets called if the user gets Disconnected from the Photon Network. In this case, the controlPanel GameObject is set to false, and an Error type message is logged to Unity.
  4. Finally, OnJoinedRoom is called when the user joins a room. Here, it checks if the user is the Master Client (the first user to join the room). If so, the user is set as the lobby leader and is shown a message to indicate this. The lobby leader has the power to load the MainArena scene, which is a common way of creating a room in most popular multiplayer games. Otherwise, if the user is not first to join the room, a message is shown to tell that user that they’ve successfully joined the room.

Save the Launcher.cs script and head back to the Launcher scene, and click Play.

As you can see, when the Scene starts, ConnectToPhoton gets called and the Connection status UI Text shows “Connecting…”. Once successfully connected, the text changes to “Connected”, and the visibility of the roomJoinUI GameObject is set to true.

Next, the user can enter their Name and the name of the Room that they want to Create or Join by clicking on the Join Room button.

Finally, if the user is the Master Client, the playerStatus Text is set to “You are now the Lobby Leader!” and the Load Arena button is set active. Otherwise, an indication of a successful lobby join is shown.

At this point you can test joining a Room by building an executable of the project for your Operating System by selecting File ▸ Build and Run. You should be able to load the MainArena scene using both your newly built executable and the Unity Editor joined to the same room.

However, you can see only an empty arena with no players. In the next section, you’ll learn how to add player and ball prefabs to the scene for each individual client. You’ll also sync their Position, Rotation and more across the Photon Network.

Using the Photon Transform View Component

With the room joining code done, the next important Photon Unity Networking concept you need to know is the Photon View Component.

PUN makes it very easy to make a Prefab whose properties (Position, Rotation, etc.) have to be synced across the network during a multiplayer game using the Photon View Component.

An important concept to understand about using PUN is that a Prefab that should get instantiated over the network has to be inside a folder named Resources.

An important side effect of having Prefabs inside Resources folders is that you need to watch their names. You should not have two Prefabs under your Assets’ Resources paths named the same, because Unity will simply pick the first one it finds.

With that out of the way, let’s get started building the Game Manager.

Open the GameManager.cs script in Assets / RW / Scripts.

Here’s an overview of what the GameManager.cs script is going to do:

  1. When the MainArena scene is loaded, check whether the client is the Master or not. If they are, Instantiate the Car GameObject using PhotonNetwork.Instantiate, change the name of player1 GameObject and also Instantiate the ball GameObject. Otherwise just Instantiate the player2 GameObject and change its name.
  2. Add a PUN Callback method that will take care of various use cases based on network conditions and events.
  3. Helper methods will Disable UI, Quit Room, etc.

Add the following code after the comment // Start Method:

// Start Method
void Start()
{
    if (!PhotonNetwork.IsConnected) // 1
    {
        SceneManager.LoadScene("Launcher");
        return;
    }

    if (PlayerManager.LocalPlayerInstance == null) 
    {
        if (PhotonNetwork.IsMasterClient) // 2
        {
            Debug.Log("Instantiating Player 1");
            // 3
            player1 = PhotonNetwork.Instantiate("Car", 
                player1SpawnPosition.transform.position, 
                player1SpawnPosition.transform.rotation, 0);
            // 4
            ball = PhotonNetwork.Instantiate("Ball", 
                ballSpawnTransform.transform.position, 
                ballSpawnTransform.transform.rotation, 0);
            ball.name = "Ball";
        }
        else // 5
        {
            player2 = PhotonNetwork.Instantiate("Car", 
                player2SpawnPosition.transform.position, 
                player2SpawnPosition.transform.rotation, 0);
        }
    }
}

Here’s how this works:

  1. Check whether the client is connected to the Photon Network or not. In case there are some issues with the network, then the Launcher scene should get loaded so that the client can try to connect again.
  2. Get a reference to the local Player (the player that is controlling the client), and check whether it is the Master client.
  3. If it is, Instantiate the Player GameObject from the Resources folder (which you will do in the next step) using the PhotonNetwork.Instantiate, and save a reference to it in the player1 GameObject.
  4. Similarly, Instantiate the Ball GameObject so that it’s the same Ball GameObject that’s loaded on all clients connected to the current room.
  5. If the client is not the Master, load the Player GameObject from the Resources folder and save a reference to it in the player2 GameObject.

Save the file and get back to the Editor.

Now that you have the logic ready to Instantiate the Player and Ball GameObjects, the next step is to add the required components so they can be Instantiated using the PhotonNetwork.Instantiate method.

From the Project Window, double-click Car prefab in Assets / RW / Prefabs to open it in Prefab Editing mode.

In the Inspector, you should be able to see that some basic components for a Car GameObject (Rigidbody, Collider, Movements, etc.) are already part of the prefab.

Since only the Transform properties of the GameObject need to be synchronized across the network, add the Photon Transform View component to the Car GameObject.

You’ll notice that a Photon View component is also added, as the Photon Transform View component inherits a lot of properties from it.

In addition to synchronizing the position, rotation and scale of a GameObject, Photon Transform View gives you many different options to make synchronized values appear smooth even when the data is received only a couple of times per second.

In the Inspector, set the Observe option to Unreliable on change in the Photon View Component. This will ensure there’s a smooth transition between the Car Transform values.

Also, add the Car prefab to the Observed Components list in the Photon View component so that its selected Transform properties (seen as selected in Photon Transform View component) are synchronized.

Save the Car prefab.

Next, open the Ball prefab from Assets / RW / Prefabs in Prefab Editing mode and repeat the above steps:

  1. Add Photon Transform View component.
  2. Set the Observe Option to Unreliable on Change.
  3. Add the Ball prefab in the Observed Components list in the Photon View component.

Finally, move the Car and Ball prefabs from Assets / RW / Prefabs to Assets / RW / Resources, so that they can be loaded by the PhotonNetwork.Instantiate method.

Time to test!

Select File ▸ Build and Run to build an executable binary for your Operating System.

This time:

  • Enter a different player name in both clients.
  • Enter the same Room name.
  • Click the Load Arena button from the Master client.

You should see that the MainArena scene is loaded with 2 Players (cars) and a Ball. You can use the WASD or the Arrow keys on your keyboard to move the car around in the arena.

Notice only the 1 car moves in both clients (the car that belongs to the client).

You can also see that both the position of the players and the ball are synchronized in both clients.

All the work is done by Photon Transform View Component that you added to the Car and Ball prefabs.

However, if you close one of the clients, the other client is left unstable. To handle cases like this, you’ll add callback methods to perform the necessary action based on the present situation of a client (such as a network loss or the other player left).

Adding PUN Callback Methods

Add the following code in GameManager.cs after the comment // Update Method:

// Update Method
void Update()
{
    if (Input.GetKeyDown(KeyCode.Escape)) //1
    {
        Application.Quit();
    }
}

This is fairly straightforward.

At any point in the game, if the Escape button is pressed, call Application.Quit.

Next, add the following code after the comment // Photon Methods:

// Photon Methods
public override void OnPlayerLeftRoom(Player other)
{
    Debug.Log("OnPlayerLeftRoom() " + other.NickName); // seen when other disconnects
    if (PhotonNetwork.IsMasterClient)
    {
        PhotonNetwork.LoadLevel("Launcher");
    }
}

OnPlayerLeftRoom is a PUN callback method that gets called whenever a Player leaves the room, either by closing the client or getting disconnected from the network.

Finally, add the following code after the comment // Helper Methods:

// Helper Methods
public void QuitRoom()
{
    Application.Quit();
}

When Canvas / Top Menu Panel / Quit Room Button is clicked, this method will be called.

Finally set the On Click () event of Quit Room Button to GameManager.QuitRoom.

And that’s it!

You can build a final binary for your Operating System and start playing!

Where to Go From Here

The aim of this tutorial was to give you an introduction to the basic concepts of building a multiplayer game. You can use these same principles to turn your existing single player game into a multiplayer game!

You can read more about the Photon Unity Networking library at the official website.

If you have any questions or comments, or you just want to show what you experimented with in this tutorial, join the discussion below!

Average Rating

5/5

Add a rating for this content

2 ratings

Contributors

Comments