Getting Unity 3D and node.js talking

I’m working on a Unity 3D game at the moment that needs a global leaderboard system that works across platform and not tied into the likes of Google Play or Game Centre. After taking a look at various technologies including my old favourite .NET (specifically thew newish .NET Core) I decided to use node.js because a) I want to learn it and b) .NET is a bit of overkill I think.

So I installed node.js then wrote a small server:

[sourcecode language=”js”]
"use strict";

var http = require("http");

class Server
{
constructor()
{
this.port = 8080;
this.ip = "localhost";

this.start();
}

start()
{
this.server = http.createServer((req, res) =>
{
this.processRequest(req, res);
});

this.server.on("clientError", (err, socket) =>
{
socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
});
console.log("Server created");
}

listen()
{
this.server.listen(this.port, this.ip);
console.log("Server listening for connections");
}

processRequest(req, res)
{
// Process the request from the client
// We are only supporting POST
if (req.method === "POST")
{
// Post data may be sent in chunks so need to build it up
var body = "";
req.on("data", (data) =>
{
body += data;
// Prevent large files from benig posted
if (body.length > 1024)
{
// Tell Unity that the data sent was too large
res.writeHead(413, "Payload Too Large", {"Content-Type": "text/html"});
res.end("Error 413");
}
});
req.on("end", () =>
{
// Now that we have all data from the client, we process it
console.log("Received data: " + body);
// Split the key / pair values and print them out
var vars = body.split("&");
for (var t = 0; t < vars.length; t++)
{
var pair = vars[t].split("=");
var key = decodeURIComponent(pair[0]);
var val = decodeURIComponent(pair[1]);
console.log(key + ":" + val);
}
// Tell Unity that we received the data OK
res.writeHead(200, {"Content-Type": "text/plain"});
res.end("OK");
});
}
else
{
// Tell Unity that the HTTP method was not allowed
res.writeHead(405, "Method Not Allowed", {"Content-Type": "text/html"});
res.end("Error 405");
}
}

}

module.exports.Server = Server;
[/sourcecode]

Create a file called server.js and add the above code.

Note that the above code is designed as a module so you can import it into your own code, e.g.:

[sourcecode language=”js”]
var server = require(‘./server’);

var httpServer = new server.Server();
httpServer.listen();
[/sourcecode]

Create a file called main.js and add the above code. Run the server by running node main.js

In the server code we create an instance of a HTTP client then we begin listening for connections. When a connection comes in we read the POST data and print out the key / value pairs that are sent.

On the Unity side we write the following code to test out our mini server:

[sourcecode language=”csharp”]
using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class Leaderboards : MonoBehaviour
{
private static string _Url = "http://localhost";
private static string _Port = "8080";

public void SubmitScore(int which, int score)
{
StartCoroutine(SubmitScoreToServer(which, score));
}

private IEnumerator SubmitScoreToServer(int which, int score)
{
Debug.Log("Submitting score");

// Create a form that will contain our data
WWWForm form = new WWWForm();
form.AddField("which", which.ToString());
form.AddField("score", score.ToString());

// Create a POST web request with our form data
UnityWebRequest www = UnityWebRequest.Post(_Url + ":" + _Port, form);
// Send the request and yield until the send completes
yield return www.Send();

if (www.isError)
{
// There was an error
Debug.Log(www.error);
}
else
{
if (www.responseCode == 200)
{
// Response code 200 signifies that the server had no issues with the data we went
Debug.Log("Form sent complete!");
Debug.Log("Response:" + www.downloadHandler.text);
}
else
{
// Any other response signifies that there was an issue with the data we sent
Debug.Log("Error response code:" + www.responseCode.ToString());
}
}
}
}
[/sourcecode]

In the above code we create a form, add the data that we wish to send then create a web request (UnityWebRequest) to send the form to our test server. We send the request then yield the coroutine until the send completes. We finally test the response code to ensure that our data was sent ok,

To test, add the component to an object and then call SubmitScore(1, 100), you should see the following displayed from node:

Server created
Server listening for connections
Received data: which=1&score=100
which:1
score:100

And in the Unity console you should see:
Submitting score
Form sent complete!
Response:OK

At the moment this is still pretty much test code and doesn’t handle security other than to check for idiots sending really POST data. You should really encrypt the data sent to the server to prevent even bigger idiots from spamming your server with false information.

Next I will be looking at redis as a persistent store for my leaderboard data. I will be extending the node.js server code to include this functionality. I may do a blog on redis soon and then one another that includes details on how to use redis to create a leaderboard.

Installing and running Node.js on a VPS

Introduction

I recently had the requirement to implement a global none app store specific leaderboard system that can track scores and players for a mobile game that I am developing in Unity3D. After much investigation node.js seems to be the technology to use to create a server to handle it. I want it to be as quick as possible without having to resort to C++, plus I’ve been looking for an excuse to play with node.js in more detail. Ok, so I went to ovh.net and purchased one of their super cheap VPS (Virtual private server). I opted for Debian 8 LAMP (this is the Debian 8 Linux OS with, Apache web server, MySQL and PHP). After reading up the set up instructions, I managed to get logged into my VPS using PUTTY on Windows.

Installing node.js on a VPS

First thing to do is install node.js to the VPS. In the Putty terminal type the following:

Download and run the Nodesource PPA (personal package archive) installer script:

cd ~
curl -sL https://deb.nodesource.com/setup_6.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh

Install node.js:

sudo apt-get install nodejs

Install the build essentials for some packages that require compilation

sudo apt-get install build-essential

Testing out the node.js installation

Ok, so now we have node.js installed, its time to create a hello world and run it to ensure that it works. In terminal create hello.js:

cd ~
nano hello.js

If you do not have the nano text editor installed then you can install it as follows:

sudo apt-get install nano

Once the file is open add the following code:

[sourcecode language=”js”]
#!/usr/bin/env nodejs
var http = require(‘http’);
http.createServer(function (req, res)
{
res.writeHead(200, {‘Content-Type’: ‘text/plain’});
res.end(‘Hello World!\n’);
}).listen(8080, ‘localhost’);
console.log(‘Server is running at http://localhost:8080/’);
[/sourcecode]

Make the script executable:

chmod +x ./hello.js

Run the script:

./hello.js

Note that the script is blocking because it is sat in an infinite loop waiting for connections, so you can no longer type anything into terminal. To test, run another instance of terminal (Putty on Windows) and enter the following to test the script:

curl http://localhost:8080

You should see “Hello World” printed out which is the response from the hello.js script.

Installing and using Process Manager (PM2) for node.js

The next issue we need to look at is how to make our script run in the background so it does not block. To do that we need install a tool called PM2 (process manager for node.js):

sudo npm install -g pm2

Once installed we can set our script off running as a background process with:
pm2 start hello.js

And to make PM2 re-run when the server reboots:

pm2 startup systemd

Note that when PM2 restarts it will restart all of its running processes.

Getting node.js to work with Apache

The general idea is to run the node.js server on a different port and forward requests to a specific url to this port using a reverse proxy. To do this we need to update an Apache config file httpd.conf. Note that if you do not find this file in the /etc/apache2/ directory then you will need to create it and add the following text:

ProxyPass /node http://localhost:8000/
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

And add the following line to /etc/apache2/apache2.conf to ensure that the file gets included

Include /etc/apache2/httpd.conf

Note that it is likely that the proxy module is not enabled on Apache, in which case enable it using:

a2enmod proxy_http

Now requests to http://www.yourdomain.com/node will be forwarded to localhost:8000, adjust the original hello world node.js script to listen on port 8000 and give it a test.