Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / Windows-Phone-7

UNITY 3D – NETWORK GAME PROGRAMMING

5.00/5 (14 votes)
26 May 2015CPOL21 min read 85K   2.3K  
This article will cover the basics of network programming using Network View in Unity 3D. We will be creating an Authoritative Server based networking environment showcasing the basics functions of network programming using Unity 3D and C#.

Introduction

This article is a tangent from our article series to cover the basics of network programming in Unity 3D. The purpose of the standalone article is to simplify the explanation and the code base for the general topic. This approach will also allow me to publish something generic which can be then utilized by you for your own projects.

Just in case if this is the first time reading the Unity 3D articles, I have listed the links to the series below:

  1. Unity 3D – Game Programming – Part 1
  2. Unity 3D – Game Programming – Part 2

  3. Unity 3D – Game Programming – Part 3

  4. Unity 3D – Game Programming – Part 4

  5. Unity 3D – Game Programming – Part 5

  6. Unity 3D – Game Programming – Part 6

  7. Unity 3D – Game Programming – Part 7

  8. Unity 3D – Game Programming – Part 8

  9. Unity 3D – Game Programming – Part 9

  10. Unity 3D – Game Programming – Part 10

Unity 3D Networking Article(s):

  1. Unity 3D - Network Game Programming

Unity 3D Leap Motion and Oculus Rift Article(s):

  1. Unity 3D - Leap Motion Integration

The articles listed above will give you a good starting foundation regarding Unity 3D.

This particular article is intended to demonstrate and hopefully explain the networking capabilities of Unity 3D using the built in networking capabilities.

NOTE: There are many other third party solutions such a Photon that will expand and extend the networking features of Unity 3D.

And the reason to cover and start with Network View is to first and fore most not to depend on third parties, and secondly even if you do decide to go with a third party solution, it would be very easy for your to upgrade and also understand the library.

Introduction to Game Programing: Using C# and Unity 3D (Paperback) or (eBook) is designed and developed to help individuals that are interested in the field of computer science and game programming. It is intended to illustrate the concepts and fundamentals of computer programming. It uses the design and development of simple games to illustrate and apply the concepts.
Image 1
Paperback
ISBN: 9780997148404
Edition: First Edition
Publisher: Noorcon Inc.
Language: English
Pages: 274
Binding: Perfect-bound Paperback (Full Color)
Dimensions (inches): 6 wide x 9 tall
Support independent publishing: Buy this book on Lulu.
Image 3
eBook (ePUB)
ISBN: 9780997148428
Edition: First Edition
Publisher: Noorcon Inc.
Language: English
Size: 9.98 MB
Support independent publishing: Buy this e-book on Lulu.
Available From:

Background

It is assumed that the reader of this article is familiar with programming concepts in general. It is also assumed that the reader has an understanding and experience of the C# language. It is also recommended that the reader of the article is familiar with Object-Oriented Programming and Design Concepts as well. We will be covering them briefly throughout the article as needed, but we will not get into the details as they are separate topics altogether. We also assume that you have a passion to learn 3D programming and have the basic theoretical concepts for 3D Graphics and Vector Math.

Lastly, the article uses Unity 3D version 4.6.1 which is the latest public release as of the initial publication date. Most of the topics discussed in the series will be compatible with older versions of the game engine, and perhaps also the new version which is supposed to be release sometime this year. There is however, one topics which is significantly different in the current 4.6.1 version compared to the older version of the game engine, and that is the UI (User Interface) pipeline. This is due to the new UI architecture in the engine which is far superior to what we had prior to this release. I for one, am very happy with the new UI architecture.

Using the code

Downloading the project/source code for server: Download Server.zip.

Downloading the project/source code for client: Download Client.zip.

With each consecutive article that is submitted, the project/source code will be also expanding. The new project files and source files will be inclusive of older parts in the series.

Steps towards a Multi-Player Game

Networking in general is a complex topic, we are not going to get down into the details of packing and unpacking of network packages in this article. What we are going to discuss is the approach on how to enable your game to be multi-player through the networking framework available in Unity 3D.

Game design and programming by itself is a pretty complex topic, in addition to that, when you start thinking about multi-player game design and programming, things become even more complex. Your whole design and architecture will have to revolve around what kind of information is going to be broadcasted over the network. Who will be able to consume the information / data and how it will be represented visually if there is a visual component to the data that is being transmitted. In other words you will need to do a lot of accounting!!!

First and foremost, when you start your project, you should question the following:

  • Is your game going to be single player?

  • Is your game going to be multi-player?

  • Is your game going to have both single and multi-player components?

Knowing this ahead of time will significantly change your game design and architecture and reduce complexities later on in the project lifetime. It is much easier to design and develop a game for multi-player support from the beginning, then it is to convert an existing single player to a multi-player game later on down the road.

For instance, the game we have been creating during the ten article series has been purely oriented towards a single player. And we might be crazy enough to change this simple game to a multi-player game just for the heck of it! So let’s think about this for a second.

Some questions to ask:

  1. Why should we even convert our game into a multi-player game?

  2. How will the game mechanics and logic change in a multi-player environment?

  3. What will be some of the data / information that will need to be shared across the network?

The answer to the first questions is pretty simple: to challenge ourselves! And also to start discussion regarding networking and multi-player game programming and design.

Questions 2 and 3 are going to require a bit more planning.

But before we even answer the last two questions, let’s look at Unity 3D networking and a general example which will be independent of our game.

Eventually, our goal is to apply what we discover and learn in this article to our game in a different article.

Network Programming Using Unity 3D Network View

To understand and enable networking in Unity 3D, you will need to understand the NetworkView component. The best place to read about it is of course the documentation provided by Unity. Here is the link: NetowrkView documentation.

In brief, Network Views are the main component involved in sharing data across the network. They allow two kinds of network communication: State Synchronization and Remote Procedure Calls.

Network Views keep watch on particular objects to detect changes. These changes are then shared to the other clients on the network to ensure the change of state is noted by all of them. This concept is known as state synchronization and you can read about it further on the State Synchronization page.

There are some situations where you would not want the overhead of synchronizing state between clients, for example, when sending out the position of a new object or respawned player. Since events like this are infrequent, it does not make sense to synchronize the state of the involved objects. Instead, you can use a remote procedure call to tell the clients or server to perform operations like this. More information about Remote Procedure Calls can be found on the RPC manual page.

Creating the Server

We will start by first creating the server code for our network demonstration. Let’s create a new empty unity project. In your Project tab, under Assets, create two new folder. One should be called Game, and the other Server.

Image 9

Figure 1-Project Asset Structure

Now let’s create our first script. Click on the Server folder and create a C# script called ServerNetworkManager.cs.

This script is going to be responsible for starting and stopping the server for our game. It will also handle some of the server side events are you will see in the script.

Listing for ServerNetworkManager.cs:

using UnityEngine;
using UnityEngine.UI;

using System.Collections;

[RequireComponent(typeof(ServerPlayerManager))]
public class ServerNetworkManager : MonoBehaviour {

   private bool serverStarted = false;

   public Text lblServerCommandCaption;
   public Text lblServerIP;
   public Text lblServerPort;

   public Transform serverMessagePanel;
   public GameObject serverMessageInfo;

   private ServerPlayerManager spm;
   void Awake()
   {
      spm = gameObject.GetComponent<ServerPlayerManager>() as ServerPlayerManager; this.serverStarted = false;

      if (this.lblServerCommandCaption != null)
      {
         this.lblServerCommandCaption.text = "Start Server";
      }
   }

   /// <summary>
   /// This function was added to perform the new GUI commands
   /// </summary>
   public void ServerCommand()
   {
      if (!this.serverStarted)
      {
         this.startServer();
         this.serverStarted = !this.serverStarted;
         this.lblServerCommandCaption.text = "Stop Server";
      }
      else
      {
         this.stopServer();
         this.serverStarted = !this.serverStarted;
         this.lblServerCommandCaption.text = "Start Server";

         this.lblServerIP.text = "IP Address ...";
         this.lblServerPort.text = "Port ...";
      }
   }

   void Update()
   {
      if(this.serverStarted)
      {
         if (Network.peerType == NetworkPeerType.Connecting)
         {
            //GUILayout.Label("Network server is starting up...");
            Debug.Log("Network server is starting up...");
         }
         else
         {
            //GUILayout.Label("Network server is running.");
            Debug.Log("Network server is running.");
            showServerInformation();
            showClientInformation();
         }
      }
   }
 
 public int listenPort = 25000;
 public int maxClients = 5;

   private void CaptureServerEvent(string msg)
   {
      GameObject newButton = GameObject.Instantiate(this.serverMessageInfo) as GameObject;
      ServerInfoItem butItem = newButton.GetComponent<ServerInfoItem>();
      butItem.lblServerInfo.text = string.Format(msg);
      newButton.transform.SetParent(this.serverMessagePanel);
   }
 
 public void startServer() {
      this.CaptureServerEvent(">>>STARTING SERVER ....");
  Network.InitializeServer(maxClients, listenPort, false);
 }
 
 public void stopServer() {
  Network.Disconnect();
 }
 
 public void OnServerInitialized() {
  //Debug.Log("Network server initialized and ready");
      this.CaptureServerEvent("Network server initialized and ready");
 }
 
 public void OnDisconnectedFromServer(NetworkDisconnection info) {
  //Debug.Log("Network server disconnected");
      this.CaptureServerEvent("Network server disconnected");
 }
  
 public void OnPlayerConnected(NetworkPlayer player) { 
  //Debug.Log("Player " + player + "  connected from ip/port: " + player.ipAddress + "/" + player.port);
      this.CaptureServerEvent("Player " + player + "  connected from ip/port: " + player.ipAddress + "/" + player.port);
      spm.spawnPlayer(player);
 }
 
 public void OnPlayerDisconnected(NetworkPlayer player) {
  //Debug.Log("Player disconnected");
      this.CaptureServerEvent("Player disconnected");
      spm.deletePlayer(player);
 }

 // this function will show client information when called ...
 public void showClientInformation() {
  /*Debug.Log ("Clients: " + Network.connections.Length + "/" + maxClients);
  foreach(NetworkPlayer p in Network.connections) {
   Debug.Log(" Player " + p + " from ip/port: " + p.ipAddress + "/" + p.port); 
  }*/
 }
 
 public void showServerInformation() {
  //GUILayout.Label("IP: " + Network.player.ipAddress + " Port: " + Network.player.port);
      if(this.serverStarted)
      {
         this.lblServerIP.text = string.Format("IP: {0}", Network.player.ipAddress.ToString());
         this.lblServerPort.text = string.Format("Port: {0}", Network.player.port.ToString());
      }
 }

}

Let’s take a look at the script and tunderstand it better. Let’s take a look at two of the functions startServer() and stopServer(). As the names indicate, they are used to start and stop the server. Looking at the body of the function, there is only one important line that we are interested in, and that is the following Network.InitializeServer(maxClients, listenPort, false);

The Network class is the main networking class in Unity. The function InitializeServer() starts up the server, taking the maximum number of clients allows as well as the port to listen to for incoming clients, and lastly a parameter indicating whether or not the server should use NAT punchthought to enable clients to connect with it.

In the stopServer() function, the line Network.Disconnect() stops the server.

In the updated version of the code, the GUI (Graphical User Interface) was updated to use the new User Interface Framework. This provides a better representation of the GUI and also gives us more flexibility and also power on how to display the information we need.

The function OnGUI() has been replaced and the new code base is designed to work with the new GUI Architecture. The new function that handles the GUI commands is ServerCommand(). It is driven by a single GUI Button and checks the state of a Boolean variable serverStarted. If the value is false it will start the server by calling the startServer() function and set the appropriate display labels. If the value is true, it will stop the server by calling the stopserver() function and also update the appropriate display labels.

NOTE: We will discuss the UI Elements in a different section.

The Network.peerType can have the following values:

  • Disconnected – Server is not initialized, and no client connection running.

  • Server – We are running as server.

  • Client – We are running as client.

  • Connecting – We are attempting to connect to a server.

The other functions are used to handle specific events on the network.

  • OnServerInitialized()

  • OnDisconnectedFromServer(NetowkrDisconnection info)

  • OnPlayerConnected(NetworkPlayer player)

  • OnPlayerDisconnected(NetworkPlayer player)

The first two are self explanatory, when the server is initialized, the OnServerInitialized() function is called and when the server is disconnected, the OnDisconnectedFromServer(NetowkrDisconnection info) is called. The other two functions having to do with player connecting and disconnecting are of more interest to us at this point.

When a player / client connects to the server, the OnPlayerConnected(NetworkPlayer player) is called. This function will use the ServerPlayerManager class to spawn the newly connected player into the scene for us.

If you take a look at the Awake() function, we are getting a component called ServerPlayerManager that is attached to the GameObject holding our scripts and assigning it to the spm variable which is of type ServerPlayerManager. Now you must be wondering what the ServerPlayerManager class is and does.

In short, the ServerPlayerManager class is responsible for:

  1. spawning the player into the scene;

  2. deleting the player from the scene and cleaning up everything related to it;

  3. handling the player input;

Listing for ServerPlayerManager.cs:

using UnityEngine;
using System.Collections;

public class ServerPlayerManager : MonoBehaviour {

 public Hashtable players = new Hashtable();
 
 public void spawnPlayer(NetworkPlayer player) {
  Debug.Log("Spawning player game object for player " + player);
  PlayerInfo ply = GameObject.FindObjectOfType(typeof(PlayerInfo)) as PlayerInfo;
  GameObject go  = Network.Instantiate(ply.playerInfo, Vector3.up*3, Quaternion.identity, 0) as GameObject;
  players[player] = go;
 }
 
 public void deletePlayer(NetworkPlayer player) {
  Debug.Log("Deleting player game object for player " + player);
  GameObject go = players[player] as GameObject;
  Network.RemoveRPCs(go.networkView.viewID);   // remove buffered Instantiate calls
  Network.Destroy(go);                         // destroy the game object on all clienst
  players.Remove(player);                      // remove player from server list
 }
 
 [RPC]
 public void handlePlayerInput(NetworkPlayer player, float vertical, float horizontal) {
  Debug.Log("Received move from player " + player);
     
      GameObject go = players[player] as GameObject;

      if (horizontal > 0)
      {
         go.transform.Translate(Vector3.forward * Time.deltaTime);
      }
      else
      {
         go.transform.Translate(Vector3.back * Time.deltaTime);
      }

      if (vertical > 0)
      {
         go.transform.Rotate(Vector3.up, 10 * Time.deltaTime);
      }
      else
      {
         go.transform.Rotate(Vector3.up, -10 * Time.deltaTime);
      }
 }

   [RPC]
   public void handlePlayerInputV2(NetworkPlayer player, string key)
   {
      Debug.Log("Received move from player - V2" + player);

      GameObject go = players[player] as GameObject;

      if (key.Equals("W"))
      {
         go.transform.Translate(Vector3.forward * Time.deltaTime);
      }
      if(key.Equals("S"))
      {
         go.transform.Translate(Vector3.back * Time.deltaTime);
      }
      if (key.Equals("A"))
      {
         go.transform.Rotate(Vector3.up, -1);
      }
      if (key.Equals("D"))
      {
         go.transform.Rotate(Vector3.up, 1);
      }
   }
}

So the spawnPlayer(NetworkPlayer player) function spawn a player based on a prefab representing that player. The prefab is attached to yet another class called PlayerInfo. The whole purpose of the PlayerInfo class is to retain the prefab for our player at this point. Eventually you can expand this class to hold more information and etc…

First we get the PlayerInfo component from the GameObject that it is attached to, then we create a new GameObject that will hold the instance of the player over the network. If you want to instantiate a GameObject over the network, you will need to usethe Network.Instantiate(…) function! We instantiate our player object using the following line:

GameObject go  = Network.Instantiate(ply.playerInfo, Vector3.up*3, Quaternion.identity, 0) as GameObject;

Finally we store a reference to the player into our hashtable. We use a Hashtable to keep track of the players. This is so that we can properly delete them and also the related RPCs for that particular player.

When the player disconnects from the server, the deletePlayer(NetworkPlayer player) is called. We retrieve the player reference from our hashtable, and use the Network class to remove all RPC related stuff from the network and then finally destroy the object over the network and remove the entry from the hashtable.

The handlePlayerInput(NetworkPlayer player, float vertical, float horizontal) function will provide the means to communicate the player’s position to everyone on the network. This is a RPC call. More information about Remote Procedure Calls can be found on the RPC manual page. Basically what happens here is that each client sends its position information to the server, and then the server transmits the information to all clients.

The handlePlayerInputV2(NetworkView player, string key) function is created to handle the player’s movement based on the key input passed by the client to the server as a string value. This is also a RPC call. This function will give you the option to perform more commands, like firing cannon balls and etc…, in the future if needed.

NOTE: Every client on the connection has a copy of the Hashtable with all players. Notice, that we use the hashtable to grab the particular player GameObject and apply the changes to that particular player.

Now that we have our scripts, let’s go ahead and build our scene and a simple level for demo.

Creating the Game

The next step is for us to create our scene or level that will be used for the demonstration purposes. As mentioned previously, the level design and the functionality of what you can do is going to be very simple.

Go ahead and create the following items in your scene:

  • A plane GameObject. This can be done by selecting GameObject->3D Object->Plane from the main menu.

  • A Main Camera should already be present in your scene. If not create one using the same method: select GameObject->camera from the main menu.

  • A Directional Light, you can achieve this by selecting GameObject->Light->Directional Light from the main menu.

  • An Empty Game Object called GameController.

NOTE: If you are not familiar with GameObject creations and or the Unity IDE, I suggest you read the article series and or do some experimentation yourself to get an understanding of the basics.

Your Hierarchy Window should look something like this after you are done with the steps above:

Image 10

Figure 2-Scene Hierarchy Server Side

Save the scene under the Game folder you had created previously. Call the scene Game.

Next create Cube primitive, and attach the following components to the Cube GameObject:

  • Network View component

  • Rigid Body component

This can be achieved by selecting the Cube GameObject, and using the Inspector Window, select Add Component and find the listed components that are needed.

Leave the default values for both the Rigid Body and Network View components as they are.

Make a Prefab of the Cube by dragging it into the Game folder. A prefab has been created. You can delete the Cube from your scene now. We will not be needing it anymore.

If you recall from the previous section, we need another script to handle out PlayerInfo. This script will be also placed in the Game folder. Go ahead and create a new C# script and call it PlayerInfo.cs.

Listing for PlayerInfo.cs:

using UnityEngine;
using System.Collections;

public class PlayerInfo : MonoBehaviour {

 public GameObject playerInfo;

}

It is simply just a script that will reference our Prefab. Your final environment should look something like this visually:

Image 11

Figure 3-Scene View

At this point we have all of our Assets. Now we need to configure them properly so that we get the results we are looking for.

Select the GameController GameObject, and attach the PlayerInfo and ServerNetworkManager scripts to the GameController. Also add the Network View components to the GameController GameObject.

Notice, that when you attach the ServerNetworkManager script to the GameController, the ServerPlayerManager is also automatically attached, that is because in our ServerNetworkManager we have declared that the ServerNetworkManager requires the ServerPlayerManager by placing the following command before the class declaration: [RequireComponent(typeof(ServerPlayerManager))]

One last configuration on the GameController would be to change the attributes of the Network View to the following:

  • State Synchronization should be set to Off

  • Observed should be set to None

Almost forgot, there is one more last configuration that needs to be done. Notice that the PlayerInfo component has a place holder for our player prefab. This is where our cube prefab is going to be attached, so go ahead and drag and drop the Cube prefab into the Player Info slot.

If all is done correctly, you can now actually test out the server code. Go ahead and run the program. You will see the following screen:

Image 12

Figure 4-Running Server Code

As you can see our GUI states that the server is not running and it is displaying a button for us to start the server. Go ahead and click the Start Server button.

Image 13

Figure 5-Screen after Server was Initialized

Image 14

Figure 6-Console displaying our Debug Messages

As you can see we have started our server and it has been successfully initialized. Now we can turn our attention to our Client code.

Creating the Client

We are going to start a new project for the client. Yes, I realized after a very long time that it is best to keep the server and the client code separated. It just makes life easier, and helps explanation also easier for starters.

However, even though this is a new project, it will have to be the exact duplicate of the Server project. With a few differences. So the first thing we are going to do is create our directory structure. So go ahead and create the two folders:

  • Client

  • Game

The Game folder is going to be exactly like the Game folder in the server project, so do yourself a favor and copy and paste the Game folder content from the server project into the client project Game folder.

Now let’s create the Client scripts. In the Client folder go ahead and create a new C# script and name it ClientManger.cs. The ClientManager is responsible for the, you guessed it, the client. It is the code that handles the connection to the server and also the updates that the client sends to the server and so forth.

Listing for ClientManager.cs:

using UnityEngine;
using UnityEngine.UI;

using System.Collections;

public class ClientManager : MonoBehaviour {

   private bool clientStarted = false;

   public Text lblClientCommandCaption;
   public Text lblClientIP;
   public Text lblClientPort;

   public Transform clientMessagePanel;
   public GameObject clientMessageInfo;

   /// <summary>
   /// This function was added to perform the new GUI commands
   /// </summary>
   public void ClientCommand()
   {
      if (!this.clientStarted)
      {
         this.connectToServer();
         this.clientStarted = !this.clientStarted;
         this.lblClientCommandCaption.text = "Disconnect";
      }
      else
      {
         this.disconnectFromServer();
         this.clientStarted = !this.clientStarted;
         this.lblClientCommandCaption.text = "Connect to Server";

         this.lblClientIP.text = "IP Address ...";
         this.lblClientPort.text = "Port ...";
      }
   }

   private void CaptureClientEvent(string msg)
   {
      GameObject newButton = GameObject.Instantiate(this.clientMessageInfo) as GameObject;
      ClientInfoItem butItem = newButton.GetComponent<ClientInfoItem>();
      butItem.lblClientInfo.text = string.Format(msg);
      newButton.transform.SetParent(this.clientMessagePanel);
   }

 void Update() {
  if(Input.anyKey) {
      sendInputToServer();
  }
 }
 
 public void sendInputToServer() {

         if(Input.GetKey(KeyCode.W))
            networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "W");
         if(Input.GetKey(KeyCode.S))
            networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "S");
         if (Input.GetKey(KeyCode.A))
            networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "A");
         if (Input.GetKey(KeyCode.D))
            networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, "D");

 }
 
 [RPC]
 public void handlePlayerInput(NetworkPlayer player, float vertical, float horizontal) {
 }

   [RPC]
   public void handlePlayerInputV2(NetworkPlayer player, string key)
   {
   }

 
 public string remoteIP = "127.0.0.1"; // NOTE: You will replace with your server IP
 public int remotePort = 25000;
 
 public void connectToServer() {
  //Debug.Log("Tyring to connect to Server...");
      this.CaptureClientEvent("Tyring to connect to Server...");
  Network.Connect(remoteIP, remotePort); 
 }
 
 public void disconnectFromServer() {
  //Debug.Log("Tyring to disconnect from the Server...");
      this.CaptureClientEvent("Tyring to disconnect from the Server...");
      Network.Disconnect();
 }
 
 public void OnConnectedToServer() {
  //Debug.Log("Successfully connected to server as player " + Network.player);
      this.CaptureClientEvent("Successfully connected to server as player " + Network.player);

      this.lblClientIP.text = string.Format("IP: {0}", Network.player.ipAddress.ToString());
      this.lblClientPort.text = string.Format("Port: {0}", Network.player.port.ToString());
   }
 
 public void OnDisconnectedFromServer (NetworkDisconnection info) {
  if (info == NetworkDisconnection.LostConnection)
   //Debug.Log("Lost connection to the server");
         this.CaptureClientEvent("Lost connection to the server");

  else
   //Debug.Log("Disconnected from the server");
         this.CaptureClientEvent("Disconnected from the server");

      GameObject[] gos = GameObject.FindGameObjectsWithTag("Player");
  foreach(GameObject go in gos) {
   Destroy(go);
  }
 }
 
 public void OnFailedToConnect (NetworkConnectionError error)  {
  //Debug.Log("Failed to connect to Server: " + error);
      this.CaptureClientEvent("Failed to connect to Server: " + error);
   }
}

As you can see the structure of the code is very similar to the ServerNetworkManager code. But there are some differences. Let’s take a look at the differences.

Here are a list of the functions that have been defined:

  • connectToServer()

  • disconnectFromServer()

  • OnConnectedToServer()

  • OnDisconnectedFromServer(NetworkDisconnection info)

  • OnFailedToConnect(NetworkConnectionError error)

  • handlePlayerInput(NetworkPlayer player, float vertical, float horizontal)

  • sendInputToserver()

  • Update()

The functions connectToServer() uses the Network.Connect() function to connect to the specified server. The disconnectFromServer() function uses Network.Disconect() to disconnect from the server. The OnConnectedToServer() function just spills out who has been connected to the server. The OnFailedToConnect() function spills out the debug error of why we could not connect to the server.

The OnDisconnectedFromServer (NetworkDisconnection info) function is crucial because it does the clean-up on the client side. It finds all GameObjects with the tag called Player and it destroys them from the local scene.

The Update() functions check to see if any key was pressed, and it calls the sendInputToServer() function which gets the Vertical and the Horizontal axis and if they are not equal to 0, then it calls an RPC function over the network to the Server using the following line of code:

networkView.RPC("handlePlayerInputV2", RPCMode.Server, Network.player, string key);

An RPC function is a function that can be called over the network using the Network View component. It has three parameters:

  • RPC Function Name – handlePlayerInput

  • RPC Mode – In this case we are only sending this message to the server with RPCMode.Server

  • Function Parameters: Network.player, vertical and horizontal

Configuration for the Client

Now that we have our client scripts written as well. We need to make a modification on the GameController GameObject we copied over from the Server project. Select the GameController, and remove the Server scripts: ServerNetworkManager.cs and ServerPlayerManager.cs.

Now attach the ClientManager script to your GameController in the Client project. You should now only have the PlayerInfo.cs and ClientManager.cs scripts attached to the GameController.

Testing Our Client / Server Code

In order to test our client and server code working together you will need to start two separate instances of Unity 3D and have one open the server code and the other the client code.

Assuming that you have your server code up and running, now we can start up our client code. The following what will appear when you first run the client:

Image 15

Figure 7-Client Running - Not Connected to Server

Let’s go ahead and select the Connect to Server button and see what happens.

Image 16

Figure 8-Client Connected To Server - Server Spawns the Player Prefab

Client Side Console:

Image 17

Figure 9-Client Side Console

Server Side Console:

Image 18

Figure 10-Server Side Console

As you can see our code is performing as expected. Stop the Client project, and let’s go ahead and Build a stand-alone executable of our Client. Select File->Build…

Image 19

Figure 11-Unity 3D Build Interface

Select the PC, Mac & Linux Standalone and choose your platform. Make sure that in your Player Settings, Run In Background is checked, and Build your project.

Connecting Multiple Clients to the Server

Use the newly created executable for the server to execute and run the server. We will also start three clients using the client executable to demonstrate the server / client relationship in a multiplayer simulation.

Start the server, start a client by executing the executable you just build. Select the lowest resolution and check the Windowed checkbox field so that we do not take too much screen real estate. Click Play. The client start, and we get the Connect to Server button, go ahead and select it.

Your screen should now look something like the following:

Image 20

Figure 12-Server running in the background, with the client(s) attached.

Go ahead and using the Client, move the Cube (the Player) by using the keyboard arrow keys. Notice that the Cube moves on both screens. Also notice the Console on the server. We are capturing who is actually making the move!

Image 21

Figure 13-Server Console displaying movement input

Image 22

Figure 14-Server Running in the Background, and the three clients are in the Foreground

Also notice the Server console has updated itself with the new connection information. That’s it! We now have our basic Authoritative Server Networking Foundation implemented. Now that you know the basics, hopefully you can expand on it and create more complex and impressive multi-player games.

Image 23

Figure 14C-Server Running in the Background, and the three clients are in the Foreground

Notice that when we stop the server, all remaining clients that are connected to the server get disconnected automatically.

Points of Interest

Network programming as stated is complex and very tedious in general. Creating a Multi-Player game is a very challenging task and one must spend a lot of time learning about the topic in general. Then you will need to spend a lot of time learning about the Networking design and architecture provided by Unity 3D which could be another mile stone.

This article was based on several articles which I have researched to learn the Unity 3D networking mechanism and hopefully it will help someone out there to come up to speed faster than I did!

For those who have been following the Unity 3D Article Series, I will be trying to see if we can create a multi-player version of our game Gold Rush!

Unity 3D Article Series

  1. Unity 3D – Game Programming – Part 1
  2. Unity 3D – Game Programming – Part 2

  3. Unity 3D – Game Programming – Part 3

  4. Unity 3D – Game Programming – Part 4

  5. Unity 3D – Game Programming – Part 5

  6. Unity 3D – Game Programming – Part 6

  7. Unity 3D – Game Programming – Part 7

  8. Unity 3D – Game Programming – Part 8

  9. Unity 3D – Game Programming – Part 9

  10. Unity 3D – Game Programming – Part 10

Unity 3D Networking Article(s):

  1. Unity 3D - Network Game Programming

Unity 3D Leap Motion and Oculus Rift Article(s):

  1. Unity 3D - Leap Motion Integration

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)