Introduction
WebSockets are a great choice to implement networking in your Unity WebGL games. WebSockets provide two-way persistent connection to a server. Unfortunately, WebSockets in Unity WebGL is limited at this time. There is a sample source library package in the Unity Store from the Unity Team, however, websocket functionality is not fully implemented. This article proposes the use of Javascript Socket.io to implement persistent p2p communication through a web server.
A particular useful aspect of Socket.io is that the server code can be written in Javascript. This allows for the code to be analogous on both the server and client sides for better clarity and maintenance.
Explaining the Concept
There are three major parts to this approach in order to implement Socket.io with your WebGL game:
- Setting up a network communication script within Unity
- Creating the javascript client code
- Creating the javascript server code
Data is then passed back and forth to the Unity Game by converting the data to/from JSON objects and strings.
Implementation
Project Setup
In order to try this method out for yourself Node.js must be installed on your server. Once installed, open up a command line terminal and change directory to your Unity Project WebGL Build. Then create a JSON file titled package.json in that directory with the following contents:
{ "dependencies": { "express": "4.13.4", "socket.io": "1.4.5" } }
The actual latest version can be obtained from the command "npm info express version". After the file is created:
- Run "npm install" to download node modules for express and socket.io into your build directory.
- Create a folder "public" in the root of your unity build directory
- Create a blank "client.js" script inside the "public" folder
Unity Specific Code
The following is an example template that you would use to interact with the external client-side javascript that would in turn interact with the server script.
The JSONUtility class is leveraged in the example, since only string data can be passed via Application.externalCall and its corresponding receiver method on the client javascript side.
Data can be passed for execution in the browser using:
Application.ExternalCall (string functionName, string dataParameter);
Ideally, we should set some data classes first before coding the nnetwork manager to aid in conversion to/from Json Objects using JSONUtility:
// Sample Data Classes that could be stringified by JSONUtility public class User { public string uid; public string displayname; public User(string u,string d) { uid = u; displayname = d; } } public class MatchJoinResponse { public bool result; }
Next create a C# script named "NetworkManager" and attach to a GameObject in your scene:
using UnityEngine; public class NetworkManager : MonoBehaviour { public void ConnectToServer(User user) { Application.ExternalCall ("Logon", JsonUtility.ToJson (user)); } public void OnMatchJoined(string jsonresponse) { MatchJoinResponse mjr = JsonUtility.FromJson<MatchJoinResponse> (jsonresponse); if(mjr.result) { Debug.Log("Logon successful"); } else { Debug.Log("Logon failed"); } } public void BroadcastQuit() { Application.ExternalCall ("QuitMatch"); } }
When you rebuild your Unity project. Make sure you now add the following lines to the "index.html" file:
<script type="text/javascript" src="/socket.io/socket.io.js"></script> <script type="text/javascript" src="client.js"></script>
Client Javascript
The client javascript accepts calls from the Unity Game running in the previous section and connects with the server in the next section. Calls are forwarded back to the Unity Game with the following call:
SendMessage(string GameObjectName, string MethodName, string data);
The example uses a timeout to prevent locking up your game in the event the server is down. In the client.js file:
var connection; var logonTimeout; var logonCallback = function (res) { clearTimeout(logonTimeout); // Send Message back to Unity GameObject with name 'XXX' that has NetworkManager script attached SendMessage('XXX','OnMatchJoined',JSON.stringify(response)); }; function Logon(str) { var data = JSON.parse(str); connection = io.connect(); // Setup receiver client side function callback connection.on('JoinMatchResult', logonCallback); // Attempt to contact server with user data connection.emit('JoinMatchQueue', data); // Disconnect after 30 seconds if no response from server logonTimeout = setTimeout(function(){ connection.disconnect(); connection = null; var response ={result:false}; // Send Message back to Unity GameObject with name 'XXX' that has NetworkManager script attached SendMessage('XXX','OnMatchJoined',JSON.stringify(response)); },30000); } function QuitMatch() { if(connection) { connection.disconnect(); connection = null; } }
Server Javascript
The example server is setup using express for routing and file delivery simplicity. The server and client functions are analogous in this example.
// Variable Initialization var express = require('express'), app = express(), server = require('http').createServer(app), port = process.env.PORT || 3000, io = require('socket.io')(server); // Store Client list var clients = {}; // Allow express to serve static files in folder structure set by Unity Build app.use("/TemplateData",express.static(__dirname + "/TemplateData")); app.use("/Release",express.static(__dirname + "/Release")); app.use(express.static('public')); // Start server server.listen(port); // Redirect response to serve index.html app.get('/',function(req, res) { res.sendfile(__dirname + '/index.html'); }); // Implement socket functionality io.on('connection', function(socket){ socket.on('JoinMatchQueue', function(user){ socket.user = user; clients[user.uid] = socket; var response ={result:true}; socket.emit('JoinMatchResult', response); console.log(user.uid + " connected"); }); socket.on('disconnect', function() { delete clients[socket.user.uid]; console.log(socket.user.uid + " disconnected"); }); });
Once saved, the server can then be started on your home computer with Node.js with the command line "node server.js".
Other Notes
There is most likely a performance consideration in your game. Data is converted to and from JSON objects and strings from the browser and back in the game; each conversion is necessary overhead in this approach. I would be interested in hearing if anyone implementing this method has any information regarding any performance hit experienced.
Also, if implementing Socket.io on an Azure server, make sure the following line is also added in the website's web.config file:
<webSocket enabled="false"/>
This disables the IIS WebSockets module, which includes its own implementation of WebSockets and conflicts with Node.js specific WebSocket modules such as Socket.IO.
Conclusion
This article presented a way to network with Socket.io and WebGL builds of Unity.
Obviously a better way to execute Socket.io and Unity interoperability would be a plugin solely hosted in the Unity environment. The solution presented in this article is a extensible alternative until such time that Unity implements websocket functionality natively into its game engine. Finally, there is added benefit in that the javascript client and server codes will be very similar.
<h3>Article Update Log</h3>
10 Apr 2016: Initial release