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:
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:
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):
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:
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:
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):
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:
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:
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):
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:
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:
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:
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
{
}
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;
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;
}
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:
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:
Select the Movement script and the script should now appear in the Inspector area of the Cube object as seen here:
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):
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;
}
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:
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();
}
}
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:
- Build the websocket-sharp.dll from the source code provided on the WebSocket Sharp github page here
- 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
- Imports
- Variables
- Creating Our WebSocket Connection
- Receiving Messages
- Sending Data to Game Server
- Closing Game Server Connection
1. Imports
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using WebSocketSharp;
using Newtonsoft.Json.Linq;
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;
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();
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!");
};
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)...
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;
}
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);
}
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:
- 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.
- 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();
}
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:
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:
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):
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
}
...
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 (10)
Hi, very nice tutorial, clear and informative. Till this chapter, I had not problem to apply. Here, I'm stuck with the SocketManager script. Precisely, when I add the script ass a component, I don't see any field that is None. The tab display is exactly as appear in the Movement script (i.e. the above image). I tried to ignore it and started the socket, in Unity editor it raises the error: "A compiler error have to be fixed...".
Also, to ensure, I reproduce exactly the recommended steps, can you kindly advise on the following:
Cube/Player - I note that when you apply the script, you did it to the Player object. Was it renamed from the cube.
websocket-sharp.dll
should it be in the Assets root? ex:
Assets:
or should it be placed in a dedicated folder, ex:
Assets
Would highly appreciated your return so I can finish your tutorial.
Hi @friedmanyariv ! It's hard to be sure about diagnosing your problem but if the "None(Object)" aspect of the Socket Manager Compnent is not showing on the inspector, it likely means that you are likely missing this declaration at the top of your SocketManager file:
To answer your questions above..
Yes, I renamed the Player Cube to "Player" in the scene hierarchy to make it easy to find/reference.
You should be fine to have the websocket-sharp.dll file in the main Asset folder directory. You should be able to add it into a subdirectory within assets like you described above and it should still work fine as the namespace for Websocket should be accessible from any location in Assets.
If this doesn't help, please feel free to respond with screenshots of the the issue you are facing.
Hey, many thanks for your guide, it's awesome !
While using Unity 2022, I faced a bug with the SocketManager script, especially with the line
using Newtonsoft.Json.Linq;
To fix that, I added this following line to the manifest.json file located in the Packages folder (I opened my file explorer, located the manifest.json file and modified it in VS Code) :
"com.unity.nuget.newtonsoft-json": "3.0.2",
Also, I am facing another bug I do not know how to deal with.
When I first hit the button key M to send a message from the client to the server, nothing happens.
Then, if I hit M again, one message is sent to the server.
If I hit M again, two messages are sent.
If I hit M a fourth time, now three messages are sent!
And so on...
(Please see the picture if this is not clear.)
Any idea of what could cause this?
Is it a characteristic of the websocket connection?
Thank you for your time :)
I actually recall having a similar issue in the past but I cannot for the life of me recall what the issue was.
I think it may be where you are assigning/initializing the listener. There is a chance that every button press is actually creating a new reference to the messaging functionality instead of using the first one your made.
Does that make any sense?
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?
@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...
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.
Thank you for your efforts
I solved it
I hope you will continue to teach us
I wish you a happy day
Good article.
it's a great help.
I'm a game developer Unity and Unreal
I'm here to find developer that would like to hire me in the development of Unity and Unreal game.
Feel free to contact me.
PS: Free Test.