How to make Among Us in Unity | Part 2 — Make it Multiplayer

Welcome to the series that will teach you how to create a game similar to Among Us, in Unity3D Engine.

Redefine Gamedev
7 min readOct 19, 2020

This is the second part of the series. For the first part and the full index follow this link.

I have released the project presented in this tutorial series! It is in form of a Game Kit, with the progress mirroring the one in the videos. If you want a shortcut, instead of creating everything from scratch — or, want to learn — grab your copy right now.

Image depicting Among Us clone, in unity game engine
Among Us, in Unity

Also, you might want to check How to Make Among Us in Unity series on my channel, Redefine Gamedev. If you prefer the video version, I highly recommend you check that one as well.

In this part we will focus on creating the basic multiplayer part of the game. More specifically:

  • Setting up Photon Cloud in Unity
  • Connecting to a Server and Joining a room
  • Synchronizing player position
  • Synchronizing player color

Let’s get started!

Setting up Photon Cloud in Unity

In order to use Photon with Unity, you first need to attach the asset to your Unity account. This is easily done from the asset store webpage. We will use Photon Unity Network 2 (PUN 2) which is a free package.

Image showing how to get Photon Unity Network 2 Unity Package
Photon Unity Network 2 Unity Package

Next, we need to import the PUN2 package into Unity. Let’s head over to Unity (preferably 2020+) and navigate to Window > Package Manager.

Make sure you have My assets selected and search for PUN2.

Note: you need to be logged in with the same account you registered the package in the previous step otherwise, it will not appear in the search results.

Don’t forget to download first and, after, to import it in the current project.

Image showing how to import photon network in unity
Importing PUN2 in Unity

Once imported into the project, Photon will require an unique AppID. Head over to the photon website, create and account and an app.

Image showing the photon cloud applications registered
Photon Cloud Applications — Create a new App

After the app is created, you can find the AppID on the app’s card.

Image showing the current application in photon cloud that we just created
Photon App ID

Connecting to a Server and Joining a room

In order to connect to a server and join a room, photon provides us with some simple functions to get the job done fast.

We will proceed by creating a script called Network.cs. Notice that instead of the usual MonoBehaviour we are using MonoBehaviourPunCallbacks. This is needed because we will be making use of the callbacks provided by Photon.

First, we will look at the script structure and then we will go through the specific parts.

public class Network : MonoBehaviourPunCallbacks  {   public CameraFollow playerCamera;

private void Start() {
PhotonNetwork.NickName = "Player" + Random.Range(0, 5000);
PhotonNetwork.ConnectUsingSettings();
}
public override void OnConnectedToMaster() {
PhotonNetwork.JoinOrCreateRoom(
"GameRoom",
new RoomOptions() { MaxPlayers = 4 }, null);
}

public override void OnJoinedRoom() {
playerCamera.target =
PhotonNetwork.Instantiate("Player", new Vector3(
Random.Range(-10, 10),
Random.Range(-10, 10), 0),
Quaternion.identity).transform;
}
}

Start(), called when the object is created is used for initialization. Here we will set up an unique user name by generating a random number and append it to a string. It will result in something like Player4261.

PhotonNetwork.NickName = "Player" + Random.Range(0, 5000);

Next, Photon provides an easy way to connect to the server by using ConnectUsingSettings.

PhotonNetwork.ConnectUsingSettings();

From this point, we don’t know exactly when the connection is established. This is why we cannot proceed and initialize the player in Start() as well. Luckily, Photon does provide a way via callbacks. And, in this case, OnConnectedToMaster.

public override void OnConnectedToMaster() {
PhotonNetwork.JoinOrCreateRoom(
"GameRoom",
new RoomOptions() { MaxPlayers = 4 }, null);
}

If this function is called, it means that Photon successfully connected to the Server. We are still not in a room yet. For this we need to use JoinOrCreateRoom.

The callback function is OnJoinedRoom and it’s called if a room was successfully joined.

public override void OnJoinedRoom() {      
playerCamera.target =
PhotonNetwork.Instantiate("Player", new Vector3(
Random.Range(-10, 10),
Random.Range(-10, 10), 0),
Quaternion.identity).transform;
}

Now that we are in an actual game session, we need to instantiate the player into the scene. Normally, we would use Instantiate provided by Unity. This will only create a local object and the other players won’t be able to see each other.

For this we need to use the PhotonNetwork.Instantiate, provided by Photon. One thing to note here, the Player object needs to be in a folder called Resources in order for this to work.

To instantiate a Photon Object you also need to add the component called photonView to it. This is what allows different objects to “communicate” on the network.

Lastly, we need to create a Network game object in the hierarchy and attach the network script to it.

Image showing the network script attached as a unity component
Network script attached to the Network Game Object

Synchronizing the Player Position

As mentioned above, in order for objects to communicate on the network, we need to attach a photon view component to them.

To synchronize position it’s a vey simple process. Photon provides out-of-the-box a component called Photon Transform View which we can set up to be observer by the photon view.

This will automatically sync the position, rotation and scale over the network.

Image showing a photon view component, used to sync objects in a network
Photon View Component

Synchronizing the Player Color

For the player color, it is a little more complex process. This is mainly because syncing color is not a default behavior. It is something custom that we need to implement ourselves.

I will provide the script and then we will take it part by part and analyze it.

public class PlayerInfo : 
Photon.Pun.MonoBehaviourPun, IPunObservable {

public int colorIndex;
public SpriteRenderer playerBody;

public List<Color> _allPlayerColors = new List<Color>();
private void Awake() {
if (photonView.IsMine) {
colorIndex = Random.Range(0, _allPlayerColors.Count);
}
}

public void OnPhotonSerializeView(PhotonStream stream,
PhotonMessageInfo info) {

if (stream.IsWriting) {
// Owner.
stream.SendNext(colorIndex);
}
else {
// Remote.
colorIndex = (int)stream.ReceiveNext();
}
}
private void Update() {
playerBody.color = _allPlayerColors[colorIndex];
}
}

The PlayerInfo needs to be a component of the player, of course. Notice that we extend from the interface IPunObservable as well this time. In C# we can only extend from one class but from many interfaces.

Interfaces are a way to ensure that a class contains some specific elements. In this case, it will be the OnPhotonSerializeView(PhotonStream stream,
PhotonMessageInfo info).

Before proceeding with the actual implementation we need to understand that this script will run on the same objects, on both sides of the network. This means we need to think “networked mode”. If you don’t understand this right now, it will make sense very soon.

private void Awake() {
if (photonView.IsMine) {
colorIndex = Random.Range(0, _allPlayerColors.Count-1);
}
}

Starting from Awake, we assign a color to the player. Notice that the execution is prevented on other network clients via if (photonView.IsMine) — we only need the owner of this object to be able to set it’s color.

public void OnPhotonSerializeView(PhotonStream stream,
PhotonMessageInfo info) {

if (stream.IsWriting) {
// Owner.
stream.SendNext(colorIndex);
}
else {
// Remote.
colorIndex = (int)stream.ReceiveNext();
}
}

Moving on to the SerializeView, this function is called by photon many times, depending on what preferences you set in the inspector. It’s usually called when changes happen.

Here, Photon provides a different way to know if this object is handled by the owner or by the other network clients. This is done via if (stream.IsWriting).

We can send whatever parameters we want in the stream. The stream is a continuous flow of messages between multiple network clients.

stream.SendNext(colorIndex);

If you are wondering what data types can be sent via this method, this is the list Photon currently supports.

Notice that there is no support for Color. We don’t synchronize the actual color but the color Index which is an integer number. We will be using this number to find the color from a list of colors called _allPlayerColors.

Now, on the other side of the network, we need to decode the message.

colorIndex = (int)stream.ReceiveNext();

If you run this now you will see that nothing happens. This is because even though we have synced the color correctly, it is not applied to the actual object. One step is needed for that.

private void Update() {
playerBody.color = _allPlayerColors[colorIndex];
}

Want More?

You are covered! Head over to Youtube at Redefine Gamedev channel and check the video tutorial.

How to Make Among Us in Unity, Part 1

--

--