DEV Community 👩‍💻👨‍💻

Cover image for Creating Our Multiplayer Game using Unity | Creating a Multiplayer Game Server - Part 5
Bradston Henry for IBM Developer

Posted on • Updated on

Creating Our Multiplayer Game using Unity | Creating a Multiplayer Game Server - Part 5

In this entry of "Creating a Multiplayer Game Server" we will be creating our game application (using Unity 3d) that we will be connecting to our WebSocket server.

But as many of these blog go, let's make sure that we have some basic thing installed on your machine. Really the only thing that we will need is Unity3D/Unity Hub and some grit and determination.

Prerequisites

Here are the two thing I recommend having installed on your machine prior to beginning the game application creation process:

  1. Unity v2020 (Download latest LTS version here)
  2. Unity Hub (Download here)

I recommend using Unity Hub to manage you Unity download as it allows you to have multiple version of Unity, if you so desire. At the time of the writing, I am using on the Unity version 2020.3.23f1. For the most part, what we will be covering today should work in all of the 2018, 2019, 2020 versions of Unity. I recommend using one of the LTS (Long Term Support Releases) versions of Unity which you can download them from the LTS Releases Unity Page. Using one of these version ensures more stability and support as we build our gaming application.

So, now that we have the Unity3D Engine installed on our machines, let's create our Unity Project.

Creating Our Unity Project

First, let's open up Unity Hub and ensure that we are on the projects section of the application is displayed in the image below:

1-Unity-hub-projects

Once in Unity Hub, select the down caret next to the "New" button and then select the version of Unity you would like to create your application with as shown below (preferably a 2020 version):

2-Unity-hub-new

You should now be presented with a dialog titled "Create a new project..." and ensure that you have the "3D" selected in the "Templates" section. In the "Settings" section, choose a Project name (I will be using "The Best Game Ever - Multiplayer") and select where you would like the project to be created. Once ready select the "Create" button as seen in the image below:

3-select-create-project

Once you select "Create" you will see Unity beginning to work as it created the base project we will be using based on the "3D" project template.

Creating Our Player and Main Scene

After Unity is finished loading you should be presented with a default "empty" Unity scene. The only things that will exist in this scene will be a "Main Camera", which manages the view that the player sees when playing, and a Directional light, a simple light that will illuminate our scene. That scene should look something like this in this image below:

3b-new-unity-project

Now that we have a scene, let's create a VERY basic scene and create and add a VERY simple object that will use for our player object.

Creating Our Scene

So the first thing we will do is create our basic scene. All we will be doing is adding a simple plane that will allow the player that we create (in our next step) to move around freely.

To create this simple plane, navigate to the file menu bar for Unity and select the "Game Object" Menu. In this menu, select "3D Object" and then select "Plane" in the newly opened menu. (NOTE: I will be using the "arrow" notation for menu navigation in the future. For example the above set of commands would have been "Game Object > 3D Object > Plane" in "arrow" notation). Once you have selected "Plane", a plane should now appear in your "Scene" view (as shown below):

4-plane-in-unity-scene

Now that our plane is created, let's add our player.

Creating Our Player

Now, let's create our simple player. Using the Menu bar select "Game Object > 3D Object > Cube" which will add a cube into our scene as shown below:

6-cube-created-unity

As you may have noticed, our cube has appeared somewhat randomly in the world. If you are lucky is positioned itself reasonably but there is a chance it's in a random place in space or it may be intersecting, or "clipping" through our plane. So select our Cube and in the the "Inspector" view change the Position X value to zero, Y value to value 0.5, and Z value to zero. Check out the image below for what your scene should now look like:

7-cube-zeroed-unity

Before we move on to creating scripts that will allow our player to move and allow our game to connect to our server created in our previous part of our series, let's add a color to our player to make it a bit easier to see.

In my case I will be making our player Blue. In order to do this we must make a new Material and add it to our player. Because I like to keep my game projects organized, before we make this color Material, we will be creating a new folder in our project called "Material".

To do this, select "Assets" in the "Project" Window and in the area to the left of that under the section that says "Assets", right-click with your mouse in empty space and select "Create > Folder" (as seen below):

8-create-new-folder

Rename that folder "Material" and then double-click the folder and right-click in empty space within the folder. In the options menu, select "Create > Material". You should now see a new icon in your folder named "New Material as shown in the image below:

9-new-material-in-folder

I will be renaming the Material to "Blue" since I will be making a blue color but feel free to use ANY color you so desire. On renamed, select the icon for our new Material. In the inspector Window, under the "Main Maps" section select the small box (should be white) next to the world "Albedo". This should open a color picker where you can select the color you desire (which will then change the color of the material). See the images below for how that process should look:

10-select-material-color

Now that our color is selected, we need the apply the color to our player. This is very simple. There are quite a few ways we can do this but the easiest is to click and drag our material onto our Cube as shown in the gif below:

11-Drag-material-on-player

With our player now colored, it will be easier to differentiate between it and the plane it will be moving on.

NOTE: You may need to move your Main Camera game object after all of these adjustments to make sure your scene and player are in the game view.

Creating Our Player Movement Script

With a player character player created, let's add the ability for it to move around our scene by creating a movement script. To keep things organized, let's quickly create a "Scripts" folder for holding all our scripts, as well as a folder called "Player" within that folder to hold player specific folders.

So within our Assets folder in the "Project" window let's create a folder titled "Scripts". Once created, let's enter that folder and create a folder named "Player". With the folders created, let's create a new C# script that we will call "Movement".

NOTE: When we create our script, make sure to change the name of the file BEFORE deselecting the file. Otherwise you will have to do extra work to make sure your script is correctly named. I will not cover exactly what that means in detail as it'll take some time to explain but please be aware of that.

Within our Player folder, right-click in empty space and select "Create > C# Script". Once the script icon appears, we will name our script Movement as shown below:

Now that our "Movement" script is created let's open it and and our code. So go ahead double-click on the file and open our script in your preferred Editor (by default Unity uses Visual Studio) and remove all of the default code until your file looks like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Movement : MonoBehaviour
{

}
Enter fullscreen mode Exit fullscreen mode

Now that our file is clear, let's add this line of code within our file inside of the Movement public class:

float movementSpeed = 5.0f;
Enter fullscreen mode Exit fullscreen mode

This float variable will be used to determine the speed of our player. Theoretically you can set this value to whatever you would like but in this case we will be setting it to 5.0f.

With our first variable added, let's add the functionality that will actually move our player character. Below our new variable let's add this code:

void Update()
    {
        Vector3 moveDir = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
        Vector3 newMoveDir = moveDir * movementSpeed;
        transform.position += newMoveDir * Time.deltaTime;

    }
Enter fullscreen mode Exit fullscreen mode

This code leverages the Update function that is called every frame by Unity's Engine and changes the player position depending on the the movement inputs of the player (WASD and the arrow keys). The moveDir variable creates a Vector3 object that stores the the inputs of the player using the Input.GetAxis methods. GetAxis outputs a value from -1 to 1 depending on the direction pressed and the Horizontal and Vertical parameters passed into the Input.GetAxis methods correspond to the key inputs:

  • Horizontal: A/D/Left-Arrow/Right-Arrow
  • Vertical: W/S/Up-Arrow/Down-Arrow

Once this Vector3 Object is created, it is added to the player's object Transform Position which determines the location of the player in the world.

Now that we have create our script, we need to add it to our player object in order for our player character to move. Let's navigate back to our Unity editor, and select our Cube.
In the Inspector window, navigate to bottom and click the button that says "Add Component" as displayed here:

12-Add-Component-movement

A small dialog box should now open with a search bar. In the search bar, type Movement. The "Movement" script should now appear as shown below:

13-Movement-script-menu

Select the Movement script and the script should now appear in the Inspector area of the Cube object as seen here:

14-Movement-in-inspector

With our script now successfully attached to our player, let's test that our movement is working. In our unity editor, select the play button to see our code in action (See the image below):

15-Press-play-button

You should now be able to move our player character using WASD and the arrow keys.

Create Multiplayer Game Socket Connection

After taking some time to build our very simple game, it's time for us to connect our game to our multiplayer game server we made in previously. In order to do this, we need to add two new scripts to our project: PlayerData and SocketManager.

These two scripts are crucial to making our game communicate effectively with our WebSocket Server.

So let's start with the PlayerData script.

In our Scripts folder in our project, let's create a new folder titled "Multiplayer". This folder will hold all code specific to the multiplayer aspect of our game. Open our newly made "Multiplayer" folder and create a new script (Right-click > Create > C# Script) and name "PlayerData". Within our PlayerData script file replace it's entire contents with this code:

using System;

[Serializable]
public struct PlayerData
{
    public string id;
    public float xPos;
    public float yPos;
    public float zPos;
    public double timestamp;
}

Enter fullscreen mode Exit fullscreen mode

This script is essentially setting up the structure for the data that we will be passing to our server that will contain information about the current position of our player character. The reason we are creating this script is because it will allow us to easy convert our player data into a JSON object/string that we can easily send to our server.

If you look at the code you may notice the line that says [Serializable]. This essentially makes this data structure we creating as a serializable meaning we can use some base Unity utilities to convert this data into other forms easily.

You may also notice that this PlayerData object is a struct. This allows us to use this PlayerData object as a "structure" as the form that our data object will conform and keeps this data very lightweight compared to say a class. So this is probably the WORST way to describe this but if your interested to understand this further, I encourage you to look up the "difference between a struct and a class". I promise you'll learn a lot.

As we move on to our SocketManager Script, you will see that we use the PlayerData struct to just quickly store and convert our data to JSON before we send it to the server.

Now that we've made our PlayerData Script (or better described, PlayerData struct), let's create our SocketManager script that we will use to manage our connection, send data to and receive data from our game server.

In the Script folder in our Project area in our Unity editor, let's add a new script called "SocketManager" in our "Multiplayer" folder. We should now see two scripts, PlayerData and SocketManager in our Multiplayer folder as shown below:

16-Multiplayer-scripts-in-folder

With that file created, let's open the file. Once in the file, delete all the code pre-made for you and add this code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using Newtonsoft.Json.Linq;

public class SocketManager : MonoBehaviour
{
    WebSocket socket;
    public GameObject player;
    public PlayerData playerData;

    // Start is called before the first frame update
    void Start()
    {

        socket = new WebSocket("ws://localhost:8080");
        //socket = new WebSocket("ws://websocket-starter-code-multiplayer-websocket-app.bsh-serverconnect-b3c-4x1-162e406f043e20da9b0ef0731954a894-0000.us-south.containers.appdomain.cloud/");
        socket.Connect();

        //WebSocket onMessage function
        socket.OnMessage += (sender, e) =>
        {

            //If received data is type text...
            if (e.IsText)
            {
                //Debug.Log("IsText");
                //Debug.Log(e.Data);
                JObject jsonObj = JObject.Parse(e.Data);

                //Get Initial Data server ID data (From intial serverhandshake
                if (jsonObj["id"] != null)
                {
                    //Convert Intial player data Json (from server) to Player data object
                    PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
                    playerData = tempPlayerData;
                    Debug.Log("player ID is " + playerData.id);
                    return;
                }

            }

        };

        //If server connection closes (not client originated)
        socket.OnClose += (sender, e) =>
        {
            Debug.Log(e.Code);
            Debug.Log(e.Reason);
            Debug.Log("Connection Closed!");
        };
    }

    // Update is called once per frame
    void Update()
    {
        //Debug.Log(player.transform.position);
        if (socket == null)
        {
            return;
        }

        //If player is correctly configured, begin sending player data to server
        if (player != null && playerData.id != "")
        {
            //Grab player current position and rotation data
            playerData.xPos = player.transform.position.x;
            playerData.yPos = player.transform.position.y;
            playerData.zPos = player.transform.position.z;

            System.DateTime epochStart =  new System.DateTime(1970, 1, 1, 8, 0, 0, System.DateTimeKind.Utc);
            double timestamp = (System.DateTime.UtcNow - epochStart).TotalSeconds;
            //Debug.Log(timestamp);
            playerData.timestamp = timestamp;

            string playerDataJSON = JsonUtility.ToJson(playerData);
            socket.Send(playerDataJSON);
        }

        if (Input.GetKeyDown(KeyCode.M))
        {
            string messageJSON = "{\"message\": \"Some Message From Client\"}";
            socket.Send(messageJSON);
        }
    }

    private void OnDestroy()
    {
        //Close socket when exiting application
        socket.Close();
    }

}

Enter fullscreen mode Exit fullscreen mode

Now that's a hefty chunk of code. So what I will do is break it down into 6 sections and summarize what is happening in each section. Once we understand this code, we'll be able to setup our Unity scene to use it and connect to our server we create previously. But before we can proceed there is something very important you will need to do to make the SocketManager script work


IMPORTANT!!! DO NOT SKIP

Before I go any further, it is extremely important to note that none of the code in this script will work without a key framework that we need to add to our project; WebSocket. Or more specifically, WebSocket Sharp. In order to get WebSocket Sharp, you essentially have two options:

  1. Build the websocket-sharp.dll from the source code provided on the WebSocket Sharp github page here
  2. Or Download the prebuilt websocket-sharp.dll from my Github page located here

It 100% your choice on which option you prefer but irrelevant of the option, you will need to place the .dll file in your Asset folder of your project directory. You can either click-and-drag it into the asset folder inside of the Unity editor or manually navigate to the folder using your file directory and place it in the asset folder that way. Either way, it is crucial for the .dll to exist in your project or you will be unable to connect to our game server


Now that all necessary files are in place, let us continue. As I mentioned, I will now break down the socket manager into separate sections

  1. Imports
  2. Variables
  3. Creating Our WebSocket Connection
  4. Receiving Messages
  5. Sending Data to Game Server
  6. Closing Game Server Connection

1. Imports

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using Newtonsoft.Json.Linq;
Enter fullscreen mode Exit fullscreen mode

This section is pretty straight-forward as we are just importing all necessary frameworks we need to run our code. The most important things to note is that we are importing WebSocketSharp (from our .dll file) and Newtonsoft.Json.Linq. NOTE: Prior to Unity 2020, you need to manually add Newtonsoft to your project. So if you are using a version older than 2020, you can visit [this page] to learn more about adding Newtonsoft to your project.

2. Variables

WebSocket socket;
public GameObject player;
public PlayerData playerData;
Enter fullscreen mode Exit fullscreen mode

This section of code declares the variables we will be using throughout our script to in to make our SocketManager work. The socket variable is what we will be using to connect, send, and receive messages from our server. The player Game Object just represents our player in our game and our playerData object is the data we will be sending to our socket server. These will be the only variables we will need for our manager to work.

3. Creating Our WebSocket Connection

So within our Start function (the function that fires when we Start this script), you will first see these lines of code:

socket = new WebSocket("ws://localhost:8080");
socket.Connect();
Enter fullscreen mode Exit fullscreen mode

The first line of code specifies the location of our websocket game server and where to connect. This could on our local machine or on a remote server on Red Hat OpenShift. Only thing to note about this, is that since we are using the WebSocket protocol, our web address need to start with ws://

The second line of code actually will connect us to the game server specified on the line before. This should initiate the "handshake" between the client (the Unity game) and the server.

4. Receiving Messages

//WebSocket onMessage function
        socket.OnMessage += (sender, e) =>
        {

            //If received data is type text...
            if (e.IsText)
            {
                //Debug.Log("IsText");
                //Debug.Log(e.Data);
                JObject jsonObj = JObject.Parse(e.Data);

                //Get Initial Data server ID data (From intial serverhandshake
                if (jsonObj["id"] != null)
                {
                    //Convert Intial player data Json (from server) to Player data object
                    PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
                    playerData = tempPlayerData;
                    Debug.Log("player ID is " + playerData.id);
                    return;
                }

            }

        };

        //If server connection closes (not client originated)
        socket.OnClose += (sender, e) =>
        {
            Debug.Log(e.Code);
            Debug.Log(e.Reason);
            Debug.Log("Connection Closed!");
        };
Enter fullscreen mode Exit fullscreen mode

So this section of code should look very similar to code we created on our server. Essentially there are two listeners operating in this chunk of code; An OnMessage Listener and an OnClose Listener. Similarly to our server, this two listeners are listening from incoming messages and for when a close method is fired from the server (aka the server closes it's connection with our game for some reason).

In our OnMessage section of code you will see:

if (e.IsText)...
Enter fullscreen mode Exit fullscreen mode

This is just a sanity check as our server could theoretically send us binary or text data. In our case, we ONLY want to work with text data.

A little further down you will see this code:

JObject jsonObj = JObject.Parse(e.Data);

//Get Initial Data server ID data (From initial server handshake
if (jsonObj["id"] != null)
{
    //Convert Initial player data Json (from server) to Player data object
    PlayerData tempPlayerData = JsonUtility.FromJson<PlayerData>(e.Data);
    playerData = tempPlayerData;
    Debug.Log("player ID is " + playerData.id);
    return;
}
Enter fullscreen mode Exit fullscreen mode

This code parses our JSON string data sent from our server and then checks to see if the JSON string is in a format that we'd like for us to use. In out case, it's specifically looking to see if the JSON data has an id field. It does this because we have designed our server to send to the client a specific set of data that contains the players ID for storage for a later time. You can use logic like this to send datas with specific field and do different things with the data depending on what fields it may have.

Once we've confirmed the field, we are converting the JSON data we received into a PlayerData object that we can then use in our code. This is EXTREMELY convenient because it saves us ton of time by automatically linking JSON fields to data fields in our PlayerData object that we can then use in our Unity code. It won't have all the data fiends the PlayerData object is expecting but Unity is able to realize that and just sets non-existent fields to default values depending on the property type.

5. Sending Data to Game Server

//Debug.Log(player.transform.position);
if (socket == null)
{
    return;
}

//If player is correctly configured, begin sending player data to server
if (player != null && playerData.id != "")
{
    //Grab player current position and rotation data
    playerData.xPos = player.transform.position.x;
    playerData.yPos = player.transform.position.y;
    playerData.zPos = player.transform.position.z;

    System.DateTime epochStart = new System.DateTime(1970, 1, 1, 8, 0, 0, System.DateTimeKind.Utc);
    double timestamp = (System.DateTime.UtcNow - epochStart).TotalSeconds;
    //Debug.Log(timestamp);
    playerData.timestamp = timestamp;

    string playerDataJSON = JsonUtility.ToJson(playerData);
    socket.Send(playerDataJSON);
}
Enter fullscreen mode Exit fullscreen mode

In our Update function we have quite a bit going on. The first thing we do is just check to see if our socket variable is valid. If it's not we just short-circuit/stop our code as all the code below it need the socket object to be valid.

If the socket object is valid, we have an "IF statement" that checks to see if our player Game Object is valid AND if our playerData id field has a string value that is non-empty. This check does two important things:

  1. It ensures that we have a valid player associated to this script. This is important because all the code within our IF statement needs valid player data (such as position) to send to the server.
  2. It ensures the player has an ID. This is important because before we can send data to the server we need an identifier for the server to know which client has sent the data. This just ensures that our game isn't sending data before we have properly configured our connection with the server.

After we have ensured our player and player ID are valid, the rest of the code is grabbing information about the players current position and setting a timestamp at which we captured this information about the player. This is all be stored within our PlayerData object which is then converted to a JSON string by Unity's JSONUtility and sent to our server for it to retrieve.

And just like that our game is sending data to our server about our player and where the player is at any given moment.

5. Closing Game Server Connection

private void OnDestroy()
    {
        //Close socket when exiting application
        socket.Close();
    }
Enter fullscreen mode Exit fullscreen mode

Finally we have a small set of code that is fired when the script itself is destroyed (aka when the game application is closed for any reason). This code just sends a "Close" message to our server which will trigger the OnClose listener on the server. This is an important set of code because it is essential that server know when a player is leaving the game so it can remove that player from the server and inform all other players.

Hooking up our Socket Manager In-Game

Now that we have all of our code, it's time to get the code working in game. Lets navigate back to our Unity Editor and great an Empty Game object. Go to the Unity file menu and select "GameObject > Create Empty". You should now see an object in your "Hierarchy" titled "GameObject". Right-Click on that object and select "Rename" in the menu and rename the object "SocketManager" as shown below:

17-Change-name-socket-manager

Now Select that item, and in the "Inspector" window, select "Add Component" and add our newly made "SocketManager" script to the object. As you may notice, under the section for our script, that there are multiple items. Each of these items represent the variables we added to our script earlier. But we still have some configuring to do to make our script work. One of our items is empty and needs is to connect it with an object in our scene. You should see the words "None (Game Object)" as shown in this image:

18-None-Game-Object

It is a very simple process to fix this issue. In our Unity Editor, navigate to the Hierarchy window and click-and-drag our "Player" game object onto the area that says "None (Game Object)" on the same line as the word "Player" (as shown in this gif):

19-Drag-player-to-socket-manager-inspector

And with that, our game has been created and ready to connect to our server. But in order for us to test that our game is working correctly, we first need to have our game server running.

I am not going to cover how to do this as I covered this at the end in the previous blog, but go ahed and start your game server using your terminal. Once up, it should be running on your 8080 port of your localhost.

Once the server is up and running, go back to the Unity editor and press the play button to run our game. Once the game is playing, go back to your terminal window and you should now see your terminal window printing information about our player that looks something like this:

Client 50c7f039-1d1a-45d5-a695-a8656c6a4ba8 Connected!
Player Message
{
  id: '50c7f039-1d1a-45d5-a695-a8656c6a4ba8',
  xPos: -0.7148541808128357,
  yPos: 0.5,
  zPos: 0.6946321129798889,
  timestamp: 1638454171.455932
}
Player Message
{
  id: '50c7f039-1d1a-45d5-a695-a8656c6a4ba8',
  xPos: -0.7148541808128357,
  yPos: 0.5,
  zPos: 0.6946321129798889,
  timestamp: 1638454171.488816
}
...
Enter fullscreen mode Exit fullscreen mode

And just like that we have a working Multiplayer Game Server AND a working Multiplayer Game. 🔥🎉


In the next part of the series, we are going to learn how to publish our server into the cloud using Red Hat OpenShift. This will be important as in the coming parts of the series, we are going to be adding more functionality and capabilities but it's crucial that we have proved out that our server works in an actual remote cloud environment. After we prove that our game server works, we will continue to add the capability to track multiplayers simultaneously on our server and within our game application.

If you'd like to view the finished code and finished Unity assets, check out my github with all of the finished source code!

Thank you for reading and excited on what's to come!

See you soon!

Bradston Henry

==== FOLLOW ME ON SOCIAL MEDIA ====

Twitter: Bradston Dev
Dev.to: @bradstondev
Youtube: Bradston YT
LinkedIn: Bradston Henry

Top comments (6)

Collapse
 
ademch06 profile image
ademch06

Welcome
I have a problem
how to create the websocket-sharp.dll from the source code provided on the WebSocket Sharp github page ؟؟
I didn't know how to do it
Please answer me quickly (:

Or can you send me a copy?

Collapse
 
bradstondev profile image
Bradston Henry

@ademch06 I just updated the blog with the correct links under the "Important!! Do Not Skip!!" section.

But here is the direct link to the built our .dll file: github.com/bradstondevcode/Creatin...

Collapse
 
bradstondev profile image
Bradston Henry

Thank you for asking this question. I'm just realizing I linked to the wrong webpage. I will update the link and will let you know. Sorry about that.

Collapse
 
ademch06 profile image
ademch06

Thank you for your efforts
I solved it
I hope you will continue to teach us
I wish you a happy day

Collapse
 
metafox0114 profile image
MetaFox

Good article.
it's a great help.

Rust language vs others

Stop by this week's meme thread!