Turn Based Multiplayer – Part 4

Networking in Unity is a large and probably confusing topic, so this lesson is designed to help introduce some of the basic requirements that will be needed to complete our muliplayer game. We will create a few specialized assets and scripts and make use of new classes and tags you may not have seen before. By the time we are done, you should understand how to get two players joined in a match, as well as determine who is who.

Quick Overview

I made a lot of incorrect assumptions while learning Networking with Unity that I hope to spare my readers from. I created the following diagram to help summarize the important stuff we will need for this project:

Networking Overview photo NetworkingOverview_zpswhd1wvyd.png

In this diagram I have created two boxes, one marked as “Host” and one marked as “Client”. These boxes represent separate running instances of our game. In the future this could be instances running on different computers or phones, etc, but while we are testing one will be a built executable and the other will be the Unity editor in play mode. Having both running instances on a single machine helps greatly in learning what’s going on behind the scenes and being able to debug issues.

Unity provides a script that configures a HUD and will allow you to determine which running instance is registered as the Host and which is registered as the Client. It is important to note though that Unity allows a Host to be both a server and a client simultaneously, and this makes a lot of things easier and cheaper to operate.

The two running game instances communicate with each other via specially marked methods. “Rpc” methods are called from the server and run on all the clients (this can include itself). “Cmd” methods are called from any client but run on the server (this can also include itself). The great thing is that you dont have to know if you are the client or the server, you just call the the method and it will handle the rest. For this project I will use pairs of these messages whenever I want to keep game state in sync between them. For example, if a move is being made on the board, then I would use a Cmd which would then call an Rpc so then the move would be made on all game instances at the same time.

Each running game instance will have a special “Player” GameObject that represents it and all of these player objects will load for each game instance. In other words, because there are two game instances joined in a match, there will be two players loaded in each. The “Player” GameObjects are a new type of class that has fields such as “isServer”, “isClient”, and “isLocalPlayer” which are designed to help differentiate what is what in your code.

One of the fields, “isLocalPlayer” will only be true on the Player instance which represents the game instance it was intantiated on. In the diagram I show that code running on the Host’s game will see that Player1 is the local player, and code running on the Client’s game will see that Player2 is the local player.

The other two fields, “isServer” and “isClient”, I repeatedly made wrong assumptions about. Unlike “isLocalPlayer”, these fields relate to the game instance that a player is running on, and NOT the player object itself. For example, I originally had believed that Player1 would return true for “isServer” regardless of whether it was accessed on the Host or Client game instance. Instead, I discovered that both Player objects return true for the “isServer” property if checked on the Host game, and both Players return false if checked on the Client game.

To make it more confusing, in my project, all players on all game instances return true for “isClient” because my Host also acted as a Client. Some of my early mistakes included logic where I would check if the player was “not” the client, but that code would never execute.

Scene Setup

There is more setup we will need to do in order to add networking to our game. First, we will need to create a prefab to represent the players of our game:

  1. Create an empty GameObject named “Player”
  2. Add the “Network Identity” component
  3. Create and add a new C# script named “PlayerController” – we will implement it later. Save the script in the “Scripts/Controller” folder
  4. Create a prefab from the Player
  5. Delete the instance of the Player in the scene

Unity provided scripts which manage the network for us, so let’s create an object for our manager:

  1. Create an empty GameObject named “Network Manager”
  2. Add the “Network Manager” component
  3. Expand the “Spawn Info” group in the Inspector for this component
  4. Set the “Player Prefab” to use the “Player” prefab we created earlier
  5. Add the “Network Manager HUD” component
  6. Save the scene and project

Player Controller

The creation and destruction of player objects is pretty important. In our finished implementation we will need to make sure two players have joined before we begin a game. Likewise it would be important to have a means for responding to events such as when a player loses their connection. Go ahead and open the “PlayerController” script for editing and replace the template code with the following:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections;
using TicTacToe;

public class PlayerController : NetworkBehaviour 
{
	// Add Code Here
}

Take a moment and notice that this is not inheriting directly from “MonoBehaviour” but is a subclass of a new type called “NetworkBehaviour” instead. This class will provide us a means of communication between player’s over the network.

Tip:

When I created this class initially, I chose the name PlayerController partially because it was intuitive, but probably also because the Unity Networking Tutorial I followed along with did the same. After I spent a little more time in the networking docs I noticed that Unity has a class by the same name in its Networking namespace. Technically this is still not a problem, because the different namespace allows us a way to work around conflicts, although I still am not happy about it because it might lead to confusion later.

public const string Started = "PlayerController.Start";
public const string StartedLocal = "PlayerController.StartedLocal";
public const string Destroyed = "PlayerController.Destroyed";
public const string CoinToss = "PlayerController.CoinToss";
public const string RequestMarkSquare = "PlayerController.RequestMarkSquare";

Our class will post a variety of notifications. The first three are related to its object life-cycle. The coin toss notification is used to indicate when I have decided which player will go first, and the request mark square notification indicates when a player is attempting to take a turn by clicking on the game board.

public int score;
public Mark mark;

I will also go ahead and add two public fields. I want each player to keep track of its own score – which is the number of times they have won a game. I also want to keep track of which kind of mark the player is using – either an ‘X’ or an ‘O’. Unity provides something called a SyncVar attribute which could automatically keep values synchronized, but for some reason I found it very confusing to work with and ended up managing state myself.

public override void OnStartClient ()
{
	base.OnStartClient ();
	this.PostNotification(Started);
}

public override void OnStartLocalPlayer ()
{
	base.OnStartLocalPlayer ();
	this.PostNotification(StartedLocal);
}

void OnDestroy ()
{
	this.PostNotification(Destroyed);
}

All NetworkBehaviour scripts will invoke “OnStartClient” when they become active – in our case this includes both player obejcts which are instantiated by the Network Manager. Another method exists named “OnStartLocalPlayer” to help differentiate the player which represents the “local” player. Finally we use the MonoBehaviour method “OnDestroy” to be able to post a notification when a player has left the game.

[Command]
public void CmdCoinToss ()
{
	RpcCoinToss(Random.value < 0.5);
}

[ClientRpc]
void RpcCoinToss (bool coinToss)
{
	this.PostNotification(CoinToss, coinToss);
}

Here is the first example of a paired “Cmd” to “Rpc” call which I use to make sure that both Games stay in sync. You can tell when a method is a “Command” method because it has the [Command] tag as well as a prefix of “Cmd” on the method name itself. Likewise a “ClientRpc” method uses the [ClientRpc] tag and an “Rpc” prefix on its own method name. According to Unity’s documentation, both the tags and the prefixes are required.

In this example, my game will use a command on a certain player to “flip a coin” to decide who goes first. The command method is actually executed on the equivalent player on the Host game regardless of if the method was actually invoked on the Client or the Host. While there I generate a random true or false value and pass that same value to all clients via an Rpc call. Only the Host (server) can call Rpc methods, and only the Rpc method is applied to all client instances. The Rpc method in this example takes the result of the coin flip and posts a notification of the result.

[Command]
public void CmdMarkSquare (int index)
{
	RpcMarkSquare(index);
}

[ClientRpc]
void RpcMarkSquare (int index)
{
	this.PostNotification(RequestMarkSquare, index);
}

Here we have another example of the Cmd to Rpc logic which will be used when a player needs to take a turn. The player uses a command so that the server can keep all the client game instances synched with the Rpc method.

Match Controller

You’ve already gotten a hint on how to differentiate one player from another. However, this by itself isn’t sufficient for our needs. I want to have a single place which can keep track of all of my players, and which can provide convenient references for me of which player is local and which is remote, as well as which player is the host of the match and which player joined (the client). Some of this was a little tricky because the Host is actually both a server and client simultaneously.

Tip:

When I first created this class I had not encountered the ClientScene class provided by Unity. I still haven’t worked with this class, but it does appear that it would handle at least some of my issues like keeping track of all the players.

  1. Create a new GameObject in the scene called “Match Controller”
  2. Create and add a new C# script named “MatchController”. Save the script in the “Scripts/Controller” folder
  3. Open the script for editing and replace the template code with the followng
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class MatchController : MonoBehaviour 
{
	// Add Code Here
}

I’m not doing anything special with this class regarding networking, I am simply providing a place to keep track of networked items. Therefore, I was able to make it a MonoBehaviour.

public const string MatchReady = "MatchController.Ready";

The class will only post a single notification which let’s listeners know when a match is ready. This will be fired when it becomes aware of both Players and can differentiate which is which regarding who is local, who is remote, etc.

public bool IsReady { get { return localPlayer != null && remotePlayer != null; }}
public PlayerController localPlayer;
public PlayerController remotePlayer;
public PlayerController hostPlayer;
public PlayerController clientPlayer;
public List<PlayerController> players = new List<PlayerController>();

For classes which miss the notification, or don’t want to store some sort of state regarding it, they will be able to check the “IsReady” property of the MatchController. This will be true as long as we have a player assigned to both the local and remote player fields.

For convenience I have four fields regarding the players: local, remote, host and client. These four fields will be filled out using the two actual players, which means that two of the fields will be duplicates of another reference. Sometimes it is convenient to think of the players in a different way, and that is why I cache it.

Before I have both players registered, I may not know which player is the local player and which is the remote player. Anytime I get an event that a player was created I store it in a list of players so I can check it later and update the conveniently named fields to access them by.

void OnEnable ()
{
	this.AddObserver(OnPlayerStarted, PlayerController.Started);
	this.AddObserver(OnPlayerStartedLocal, PlayerController.StartedLocal);
	this.AddObserver(OnPlayerDestroyed, PlayerController.Destroyed);
}

void OnDisable ()
{
	this.RemoveObserver(OnPlayerStarted, PlayerController.Started);
	this.RemoveObserver(OnPlayerStartedLocal, PlayerController.StartedLocal);
	this.RemoveObserver(OnPlayerDestroyed, PlayerController.Destroyed);
}

Rather than making the “PlayerController” be tightly coupled to the “MatchController” and notify it upon creation and destruction, I simply caused it to post notifications. Here, we register as an observer for each of the relevant notifications.

void OnPlayerStarted (object sender, object args)
{
	players.Add((PlayerController)sender);
	Configure();
}

Both “PlayerController” instances will trigger the “OnPlayerStarted” method, so I use that handler to add the sender to my list of players. Afterwards I call Configure – which tries to sort the list of players into the named fields.

void OnPlayerStartedLocal (object sender, object args)
{
	localPlayer = (PlayerController)sender;
	Configure();
}

Only one of the “PlayerController” instances will trigger the “OnPlayerStartedLocal” method, so with this I can go ahead and assign one of my cached convenience fields. On the Host game, the local player method will be invoked before the other player connects, and I wont be able to finish configuration, but on the Client game it wont be invoked until both players have started. In this case I need to call “Configure” because I will finally have all of the data needed to finish setup.

void OnPlayerDestroyed (object sender, object args)
{
	PlayerController pc = (PlayerController)sender;
	if (localPlayer == pc)
		localPlayer = null;
	if (remotePlayer == pc)
		remotePlayer = null;
	if (hostPlayer == pc)
		hostPlayer = null;
	if (clientPlayer == pc)
		clientPlayer = null;
	if (players.Contains(pc))
		players.Remove(pc);
}

When a player disconnects or a match is quit, the players will be destroyed. I listen for this notification so that I can clear out any references I might have had, and will know that the match is no longer playable.

void Configure ()
{
	if (localPlayer == null || players.Count < 2)
		return;

	for (int i = 0; i < players.Count; ++i)
	{
		if (players[i] != localPlayer)
		{
			remotePlayer = players[i];
			break;
		}
	}

	hostPlayer = (localPlayer.isServer) ? localPlayer : remotePlayer;
	clientPlayer = (localPlayer.isServer) ? remotePlayer : localPlayer;

	this.PostNotification(MatchReady);
}

In order to finish setup, we need to have two players and know which of the players is the “local” player. If I dont have the needed bits of information I just quit early. Otherwise I loop through the list of players to try to find the player which isn’t the local player and then I will mark that player as the remote player.

Next I want to be able to think of the local and remote players in a slightly different way. I want to know which player is local on the Host game, and which player is local on the Client game. I can determine this value by looking at the “isServer” field. If my local player’s “isServer” is true, then I know that I am on the Host game, and therefore the host player is the local player, otherwise it will be the remote player. To find the client player I used the same check but flipped the order of the players.

Testing Pipeline

The network manager will handle the creation and destruction of “Player” instances based on real players connecting to our game. You can test this out using a process which you will need to get in the habit of doing so you can test your networked game:

  1. Choose the menu bar option “File -> Build & Run”. Choose a location to save your file, and then on the configuration screen choose a small resolution and check the box for “Windowed” so you can still see Unity’s IDE simultaneously.
  2. Play the built game and choose “LAN Host (H)” from the HUD on screen.
  3. Hit play on Unity’s editor window as well, and this time choose “LAN Client (C)” from the HUD screen.

While the match is connected you can look at the hierarchy pane in the Unity Editor and see that two “Player” prefabs have been instantiated. If you use the HUD to “Stop” the game then the two “Player” prefabs will be destroyed. Before you quit, select the MatchController and verify that all four player references have been filled in. In case you didn’t know, you can left click the player references in the inspector and it will temporarily highlight the same object in the hierarchy pane. One of the references should be local, and the other should be remote. Now you have a convenient way to find out which is which.

Tip:

Note that it doesn’t actually matter which running instance is the Host and which is the Client, but you do need exactly one of each to test with. You probably should try it both ways, particularly if you are experiencing a bug that appears exclusively on either type of connection. Make the unity editor use whichever connection type experiences the bug so you can access information in the inspector and utilize Debug Logs etc.

Summary

In this lesson I provided a quick primer on Networking in Unity to help you avoid making some of the wrong assumptions and mistakes that I struggled with. Afterward, we dove right in and began creating player and network manager assets. We created a player controller script to manage communication across the match and make sure that both game instances remained in synch, and we also created a match controller to keep track of the players and identify them.

With all of the new setup in place, you are now able to create and join a match and see “player” objects get created in the scene as players join. We disucessed how this workflow could be achieved on a single machine in order to help speed up develepment and testing.

Don’t forget that if you get stuck on something, you can always check the repository for a working version here.

Advertisements

14 thoughts on “Turn Based Multiplayer – Part 4

    1. Hey Kim, yeah I’m familiar with a variant on that – Chutes and Ladders (by Milton Bradley). This should definitely be able to be implemented using Turn Based Multiplayer if you follow the example in my tutorial. Unfortunately, I still don’t know what exactly you need help with. It might be as simple as passing the dice roll along instead of passing a square index that a user clicked on.

      Also, I would prefer to keep the comments here to be something that is relevant to everyone, so if you want to start a more specific thread on my Forum then I would be happy to continue to work with you there.

      Like

      1. Okay sir, I’m having a problem on how can I set a player and how can I choose who is next to roll the dice. I got 4 players to play with. I followed your tutorial and I got some idea but I don’t know where to start. Sorry I’m not being specific. Thank you.

        Like

      2. Okay, so the challenge you are facing is how to make a turn based game with more than 2 players. Here are my thoughts (though I haven’t actually created such a game so you may have to experiment):

        The match controller is currently responsible for making sure all of the players have joined the match. Currently I only have the concept of a host player and a client player which wont be as helpful because your scenario will have a host player and client players (plural). I think what I would do is to have the match controller expect a certain number of players to join. As each player connects, I could then take some sort of ID, such as the “NetworkBehaviour.netID” property as a way to identify the different players. I would add each player’s id to an array of player ids in the match controller as they join until I have the number of players I want. When the game begins, I would pass along the array of player ids from the host to all of the clients so they all know the order of players. You can of course shuffle the array before passing it along so that the player order is random.

        During gameplay, I would have the game store an index for the player whose turn it should be. Each client could compare the player id at that index against their own id to see if it is their turn or not. Does that help?

        Like

  1. Hey, thank you so much for your tutorial! It helped me a lot!
    Though, I still have a problem. In the Show() method in the Board.cs file, instead of using a Collection, I am instantiating the GameObjects. It also works, that I can see the Objects on both clients. The thing is however, when I am trying to access those GameObjects in script, it tells me that there is no GameObject component attached to it. I can’t even change the name of the instantiated GameObject. Can you help me?
    Thanks!!

    Like

  2. Great tutorial. Thank you so much! I am currently working on an implementation of 9 Man Morris, and found this tutorial very helpful. However, I would need to send RPCs containing two integers (move from and move to). This would also mean I’d have to edit your NotificationExtensions. I have yet to go though the first few parts of the tutorial, but I was wondering if you would recommend that I edit the NotificationExtensions, or rework the project with only RPCs.

    Like

    1. Hey Russell, you’re welcome – I’m glad you enjoyed it. You don’t actually need to modify anything. Rather than sending two parameters, simply send a structure which is made up of the two integers you need. In C# everything can be passed as an object, even a simple value type like an integer, so you would also be able to reuse the structure with the notification system.

      You can see what parameter types are allowed on networking calls here at the bottom (Arguments to Remote Actions):
      https://docs.unity3d.com/Manual/UNetActions.html

      Liked by 1 person

  3. I’m following along with the Pokemon Tutorial while implementing a personal project, and jumping back to this one, because I’m trying to put in the initial networking components. I hit a wall, and I’m hoping you might have some insight. I’m currently just trying to create the equivalent of pressing the LAN Host(H) or LAN Client (C)-localhost buttons.

    I have have the Network Manager HUD displayed for visual feedback, but I’m trying to handle the actual network connection completely via code.

    Getting the server up and running seems as simple as:
    NetworkServer.Listen(7777);

    but I’m having a devil of a time getting the client-side up and running.
    From what I’ve read elsewhere it should be as simple as:

    NetworkClient myClient;

    myClient = ClientScene.ConnectLocalServer();

    sadly, this seems to have no effect. Any ideas?

    Like

    1. Okay, found my issue, or at least the proper way to handle it for where I am in the process right now.
      I was going about it all wrong. What I needed was to grab a reference to the NetworkManager component via:

      manager = GetComponent();

      Then use that to trigger the NetworkManager methods:

      manager.StartHost();
      or
      manager.StartClient();

      Like

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