Unity Multiplayer Introduction with Photon

by SlideFactory

Overview

The goal of this blog post is to introduce Unity multiplayer to created networked experiences using Photon. To begin, we will download and set up the packages we will need. Then, we will set up some basic scripts to get a feel for how we can use Photon’s Unity integration (PUN). Finally, we will build our simple game and test it locally (or with friends!).

Why Photon for Unity Multiplayer?

Photon is a company that created a Unity asset (PUN) that allows us to quickly create multiplayer games or apps. Photon Cloud also provides servers that we can connect to for testing our apps. We can use Photon on our own servers, but we will save time setting up by using Photon’s Free plan. This gives us 20 concurrent users (CCU) for free. All we need to do is set up an account.

Before we start

We’ll need a couple things before we start coding:

  • Unity (This tutorial was created with 2019.3)
  • PUN 2 – FREE asset from the Unity Asset Store
  • A Photon free account with a registered AppId (we’ll set this up now)

Free Photon Cloud Account setup

In order to get a Photon App Id, we’ll need a photon account. Go to the photon registration page, or search for Photon Unity and find your way to the registration form. Once you’ve confirmed your account and logged in, you’ll be taken to a dashboard with a Create A New App button. Click the button and on the next page, choose ‘Photon PUN’ as the Photon Type, and name your app something like ‘PhotonTutorial.’ Click Create and you should be taken back to your applications list. You should see something like this:

photon unity multiplayer app

Click on the App ID section like highlighted in yellow in the image above and copy the full string. We’ll use it later in Unity.

Creating our Unity Multiplayer Project

Now that we have our Photon App ID, we can create a new Unity project and import the PUN 2 – FREE asset. Open up the asset store in Unity and search for ‘PUN’ and the package should pop up. Once you’ve downloaded and imported the project is should ask for your app id. Enter it and you should be good to go. If you accidentally close this window, you can always find your PhotonServerSettings.asset by going to ‘Window -> Photon Unity Networking -> Highlight Server Settings.’

In fact, we’re going to go there now and take a look. If you open the server settings, you’ll notice a couple of options.

  • AppId – Required for Photon Cloud. This is how Photon connects our users to the same server. If you didn’t paste in your AppId earlier, do it now.
  • AppChatId – Optional. If we supported chat we could register a new app for Photon Chat
  • AppVersion – This is important. You should set it to 1 until you need to make breaking changes to a live game. Users on different app versions won’t connect to each other’s rooms.
  • Use Name Servers – Required for Photon Cloud. We’d uncheck this if we were hosting our own multiplayer servers.
  • PUN Logging – Optional. You can set this to Full if you’d like to see everything PUN is logging. Might help solve bugs.

We don’t need to worry about much else in there so let’s continue on to our connection code!

Connecting to Photon Servers

Create a C# script named ‘NetworkConnector.cs’ and input the following:

using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class NetworkConnector : MonoBehaviourPunCallbacks
{
    void Start()
    {
        PhotonNetwork.ConnectUsingSettings();
    }

    #region Pun Callbacks

    public override void OnConnectedToMaster()
    {
        Debug.Log("Connected to photon!");
    }

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.LogWarning($"Failed to connect: {cause}");
    }

    #endregion
}

The script is currently pretty simple. When it starts it will try to connect to Photon using the settings we’ve configured in our Server Settings file above. Below that, you’ll notice the Pun Callbacks region. You may also notice that our class extends MonoBehaviorPunCallbacks. The two sections are related. The MonoBehaviorPunCallbacks class holds all of the methods we can override to react to events in Photon. For example, here we’d like to log a message when we’ve connected to the Photon Master server. Otherwise, we’ll log a warning on disconnect (if we have no internet, for example).

Go back into Unity and create an Empty GameObject. Name it “NetworkConnector” then add our NetworkConnector.cs script onto it. Now, run the scene with or without internet and you should see the appropriate message. If you see a different message, or no message, make sure you entered your AppId or turn your Photon Server Settings PUN Logging option to Full for possible reasons.

Photon Unity Multiplayer Rooms

The code we’ve set up here only connects to the master Photon server. We still need to handle actually adding our users into a room so that they can play together. In Part II, we’ll set up a lobby to search for and create games, but for now we’ll join a random room. Let’s update the script:

public class NetworkConnector : MonoBehaviourPunCallbacks
{
    // ...
    public override void OnConnectedToMaster()
    {
        // Try to join a random room
        PhotonNetwork.JoinRandomRoom();
    }

    // ...

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        // Failed to connect to random, probably because none exist
        Debug.Log(message);

        // Create a new room
        PhotonNetwork.CreateRoom("My First Room");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log($"{PhotonNetwork.CurrentRoom.Name} joined!");
    }

    #endregion
}

Now, when you run your game, you should see a message saying ‘No match found’ (meaning there was no random room to join) followed by ‘My First Room joined!’ This means that we’re actually in a game room that can share data with other players. So, let’s make something to share!

Creating a Player Prefab with Networking

When creating multiple games, we need to think about networked objects a little differently. We can’t use Unity’s Instantiate method anymore because this will create the player object only for the local player, instead of for all players in the room. Photon solves this by letting us use its PhotonNetwork.Instantiate method. But this only creates the object in the room for all players to see. If we want to update its values (say position) and share those to other users, we need to add a PhotonView component onto our object.

Let’s create a simple capsule player with some basic movement to illustrate this. Go back to Unity and create our Player Prefab:

  • Click GameObject -> 3D Objects -> Capsule to create and name it ‘Player’.
  • Reset its position to 0, 0, 0.
  • Add a Rigibody component, under Constraints -> Freeze Rotation, check X, Y, and Z.
  • Add a PhotonView component. In the Observed Components section, drag the Player GameObject we just created. This should create a Photon Transform View which will automatically sync our object’s transform to other players in the room.
  • Uncheck ‘Rotation’ under the Photon Transform View

After you’ve completed this step, let’s create a new ‘BasicMovement.cs’ script:

using UnityEngine;
using Photon.Pun;

public class BasicMovement : MonoBehaviour
{
    public float speed;

    Rigidbody rb;
    PhotonView photonView;

    // Start is called before the first frame update
    void Start()
    {
        rb = GetComponent<Rigidbody>();
        photonView = GetComponent<PhotonView>();
    }

    void FixedUpdate()
    {
        // Only move the player object if it's the local users player
        if (photonView.IsMine)
        {
            Move();
        }
    }

    void Move()
    {
        float horizontal = Input.GetAxis("Horizontal") * speed * Time.deltaTime;
        float vertical = Input.GetAxis("Vertical") * speed * Time.deltaTime;

        Vector3 newVector = new Vector3(horizontal, 0, vertical);

        rb.position += newVector;
    }
}

The script should look pretty straight forward, based on the WASD or keyboard arrow keys we’ll move our player forward, back, left, and right. The exception is the photonView section. We’ve already added the ‘PhotonView’ component to our player object. Here we get a reference to it and use it to make sure we only move the player object that belongs to the local user (photonView.IsMine). Each player will need to see the other player’s object, so we’ll create a player object for each player when they join the room. However, we don’t want other users controlling our local character.

Saving the Prefab

Finally, we need to add the ‘BasicMovement.cs’ script to our player object and set the speed to 10. Then create a ‘Resources’ folder and drag the player object into it to create a prefab. This is a very important step. Because of the way Photon searches for GameObjects to instantiate, they must be inside of a ‘Resources’ folder. The folder could be nested within a ‘Prefab’ folder, but it must be named ‘Resources.’

Setting up the room for Unity Multiplayer

Let’s add a blank cube, reset it’s position to 0, 0, 0 and then make it’s scale 100, 1, 100. This will be our ground. Now, adjust the Main Camera GameObject’s position to 0, 2, -10. We’ll create our player prefabs when the user joins the room, so delete any you still have in the scene. Feel free to first test that the movement script is working.

Let’s go back into our NetworkConnector.cs script and add the follow to create our player objects when a new player joins the room:

using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class NetworkConnector : MonoBehaviourPunCallbacks
{
    // Add a reference to our player prefab
    public GameObject playerPrefab;

    // ...

    #region Pun Callbacks

    // ...

    public override void OnJoinedRoom()
    {
        Debug.Log($"{PhotonNetwork.CurrentRoom.Name} joined!");
        // Create our player object when we've joined the room
        PhotonNetwork.Instantiate(playerPrefab.name, new Vector3(0, 3.0f, 0), Quaternion.identity);
    }

    #endregion
}

Once this is complete, drag your player prefab that you added to a Resources folder into the NetworkConnector component and test! You should see your connection messages logged in the console and afterwards your player prefab created in the room. Using WASD or the arrow keys you should be able to move your player character.

Building the Game to Test Unity Multiplayer

You can test the multiplayer functionality by building your game locally to your computer and running it twice, once inside the Unity Editor and the other from your standalone build. To do this:

  • Go to File -> Build Settings
  • Click Add Open Scenes
  • Select the Platform PC, Mac & Linux Standalone. Choose your target platform (the Operating System you are running Unity on)
  • Check Development Build
  • Click Build And Run. We usually save our builds into a Build folder next to Assets in our unity project, but up to you!
  • Once the game loads and you see your player, tab back to the Unity Editor and enter Play Mode. You should see two players that each update on the other’s screen in real time!

Conclusion

We’ve seen how we can quickly add multiplayer functionality and test using Photon while using very little code. We’ve set up our own Photon PUN App, connected it to Unity, created a basic network connector to handle room joining, and created a basic player prefab. In Part II, we’ll look at building out a lobby for our users so that they can choose which rooms they’d like to join. See you then!

Scripts

// NetworkConnector.cs
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;

public class NetworkConnector : MonoBehaviourPunCallbacks
{
    public GameObject playerPrefab;

    void Start()
    {
        PhotonNetwork.ConnectUsingSettings();
    }

    #region Pun Callbacks

    public override void OnConnectedToMaster()
    {
        // Try to join a random room
        PhotonNetwork.JoinRandomRoom();
    }

    public override void OnDisconnected(DisconnectCause cause)
    {
        Debug.LogWarning($"Failed to connect: {cause}");
    }

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        // Failed to connect to random
        Debug.Log(message);

        // Create room
        PhotonNetwork.CreateRoom("My First Room");
    }

    public override void OnJoinedRoom()
    {
        Debug.Log($"{PhotonNetwork.CurrentRoom.Name} joined!");
        PhotonNetwork.Instantiate(playerPrefab.name, new Vector3(0, 3.0f, 0), Quaternion.identity);
    }

    #endregion
}
// BasicMovement.cs
using UnityEngine;
using Photon.Pun;

public class BasicMovement : MonoBehaviour
{
    public float speed;

    Rigidbody rb;
    PhotonView photonView;

    void Start()
    {
        rb = GetComponent<Rigidbody>();
        photonView = GetComponent<PhotonView>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        if (photonView.IsMine)
        {
            Move();
        }
    }

    void Move()
    {
        float horizontal = Input.GetAxis("Horizontal") * speed * Time.deltaTime;
        float vertical = Input.GetAxis("Vertical") * speed * Time.deltaTime;

        Vector3 newVector = new Vector3(horizontal, 0, vertical);

        rb.position += newVector;
    }
}
Share this article:

You might also like: