IGX SDK for mobile is now available

Web to mobile

Today I pushed the latest version of the IGX SDK to Github which provides support for deploying IGX SDK compatible games, including Facebook Instant Games and general web games to Android and iOS.

Games are hosted in a web view within Unity 3D. A new library has been provided which plugs into the IGX SDK and communicates between the web view and Unity which provides access to native features such as in-app purchasing and adverts.

Supported features include:

  • Adverts via Unity Ads (Admob is work in progress)
  • In-app purchasing
  • Social login via Game Centre and Google Play Games
  • Leaerboards via Game Centre and Google Play Games
  • Native sharing
  • Analytics via Unity
  • Open external URL’s
  • File uploads
  • Localisation support

The IGX SDK wiki has been updated providing instructions on how to set up a Unity project to host your game and content.

Below is an example of a deployed Facebook Instant Game which has been deployed to Android and iOS stores:

Who is the IGX SDK for?

The IGX SDK is for web and and Facebook Instant Game developers that would like an easy route to take their products from web to mobile platforms such as Android and iOS. IGX provides an alternative solution to the likes of Cordova, PhoneGap and CocoonJS.

Thinking of Making an Indie Game?


So, I’ve been making Indie games for years, with very little success, usually because I write obscure little games that don’t usually see much action. So I decided to write something that many gamers do love, an Idle Upgrade Cookie Clicker. I decided to go with the same format as the original by Orteil because IMO its the best and I much prefer the straight forward layout compared to modern idle games. I also decided to theme it around climate change as it’s something that I care about (hoping that it rubs off on a few others). I decided to make it free to play (because obtrusive ads are horrible and paid is DOA) with the option of video ads to generate coins for those that don’t want to spend any money on coins. I opted to integrate Facebook for backup / restore and the usual social features such as login, inviting friends, sharing, gifting, leaderboards.

So how does a cookie clicker perform these days? Lets take a look at some numbers since release a couple of months ago:

Note that the above mobile downloads required me to spend around $200 across Apple search ads and Google Adwords, so I’ve made quite a loss.

So the game wasn’t performing well, but was performing slightly better than most of my other games. With this I decided to put out a version on Kongregate and Newgrounds, (both great game portals with some pretty decent games) to boost the games visibility, which is ok as long as you can put up with the extremely toxic communities. Seriously some of the users (calling them gamers would be a serious stretch) on there were absolutely dropped on their heads at birth, if you are fine dealing with petulant kids then you can turn it into a pretty fun pass time.

Today I have:

After placing the game onto those portals I did see a nice jump in mobile installs, but it was very short lived (a couple of days), its just not an effective marketing tool, especially considering the toxicity of the community and the abuse that you will have to deal with. Also, take note, when you do place a game on a web portal, expect it to be lifted (without permission) and placed all over the place so that some blood sucker can earn ad revenue from your hard work (the word cunts springs to mind).

So in summary, paid ads do drive installs works, but you will need a LOT and I cannot stress this enough a HUGE AMOUNT of money to get your game noticed. Web portals like Kongregate and Newgrounds help the game initially but once the toxic community has done with you, your game will have 0 visibility, so any visibility gains will be short lived. Take a look at the ratings for my game:

  • iOS – 5 stars
  • Android – 4.5 stars
  • Facebook – 4.8 stars
  • Kongregate – 2.65 stars
  • Newgrounds – 2.68 stars

Indie game development for making money is dead, will I still continue to make indie games? It’s fun creating games, so maybe, do I expect to ever earn money from it? No, very unlikely.

If you are thinking of becoming an Indie game developer, I recommend only doing so if you get enjoyment from it, if you expect to earn a living then try something else.

Big List of Popular Flash, HTML5 and Unity 3D Web Gaming Portals

Big list of web game portals / sites that accept games mainly from Flash, HTML5 and Unity 3D developers:

Facebook Alexa rank 3
Y8 Alexa rank 761
Kongregate Alexa rank 1375
Miniclip Alexa rank 1750
Kizi Alexa rank 1816
Newgrounds Alexa rank 2624
Poki Alexa rank 2985
itch.io Alexa rank 3449
Armor Games Alexa rank 3472
A10 Alexa rank 4164
Pogo Alexa rank 4955
Big Fish Games Alexa rank 5113
AGame Alexa rank 6009
Addicting Games Alexa rank 6657
Paco Games Alexa rank 8024
Girls Go Games Alexa rank 9280
Zepak Alexa rank 9516
Ninja Kiwi Alexa rank 10681
io Games Space Alexa rank 11295
NotDoppler Alexa rank 14516
Games Games Alexa rank 15194
Yepi Games Alexa rank 21526
Nitrome Alexa rank 24779
KHBG Games Alexa rank 24690
Game House Alexa rank 28541
Free Online Games Alexa rank 28628
Games Freak Alexa rank 35802
MAD Alexa rank 36964
Box10 Alexa rank 43227
One More Level Alexa rank 48128
Mouse Breaker Alexa rank 52226
Games 2 Girls Alexa rank 54837
io-games Alexa rank 56104
Fun Brain Alexa rank 57127
Frip Alexa rank 60455
Puff Games Alexa rank 65496
Crazy Monkey Games Alexa rank 68705
Flash Games 247 Alexa rank 72799
Game Gape Alexa rank 76718
Mind Jolt Alexa rank 78550
Bubble Box Alexa rank 80294
Cool Games Alexa rank 90502
Zapak Alexa rank 95136
GameZ Hero Alexa rank 109726
Spil Games Alexa rank 152091
Game Garage Alexa rank 1295785

* Alexa rank accurate as of 2nd October 2017

Cookie Clicker Save the World free game for iOS, Android and Facebook Gameroom

Just released my latest mobile and Facebook game Cookie Clicker Save the World targeted at raising awareness of climate change in a fun and challenging gaming environment. Gamers playing the game are gradually introduced to various climate issues and what can be done to help, such as recycling, using wind power etc..

The game is an idle builder / tapper based in the near future where current day currencies have been replaced with a new cryptocurrency called cookies. Players have to research and buy new technology to reduce carbon emissions reducing mankind’s impact on the environment.

Game features include:

  • Bake cookies by clicking on the cookie moon, use cookies to purchase items and technology that help the climate and produce more cookies.
  • Use cookies to buy over 200 upgrades and mods to speed up carbon reduction and cookie production.
  • Earn over 200 achievements.
  • Use powerups to speed cookie cooking production.
  • Evolve your cookie mastery to gain extra benefits.
  • Daily bonus awards.
  • Login with Facebook, invite friends, share progress and compete with friends in the cookie master leaderboards.
  • Offline cookie cooking.
  • Random blessings boost your play.
  • Deal with random curses such as striking workers, maintenance shut downs and more.
  • Endless play allows you to play an indefinite amount of time

Cookie Clicker Save the World is available for free on the App Store for iPhone and iPad

Cookie Clicker Save the World is available for free on the Google Play for Android

The game is also available for free on Facebook and Facebook Gameroom

You can find out more about the game at the official Cookie Clicker Save the World website.










From Unity 3D to Facebook Hosted!

So I decided to have a go at getting one of my mobile games up and running on Facebook’s site, chatting to other game developers they warned me to steer clear as its a pain and it will eventually break when they change to the next API release etc… Of course I didn’t heed their advice and went ahead and gave it a try. The main reason being that Facebook now host game content for you, so you no longer need to host it on your own web site using up your own meagre bandwidth and best of all you do not need to buy and manage SSL certificates, whats not to like.

Exporting to Unity WebGL

So the first thing I did was export a build to WebGL gave it a quick test. However I didn’t like the Unity logo, app name or full screen button at the bottom of the screen so I opened up the exported index.html and commented out the following lines of html to remove them and provide a clean clutter free view of my game:

[sourcecode language=”html”]
<div class="logo"></div>
<div class="fullscreen"><img src="TemplateData/fullscreen.png" width="38" height="38" alt="Fullscreen" title="Fullscreen" onclick="SetFullscreen(1);" /></div>
<div class="title">Tens Junior</div>
<p class="footer">&laquo; created with <a href="http://unity3d.com/" title="Go to unity3d.com">Unity</a> &raquo;</p>
[/sourcecode]

Set the screen resolution

Ok, I wasn’t happy with the default screen resolution that Unity exported my game at so I changed it, taking into account that my game will be shown in a frame on Facebook, so I changed the canvas width and height in the Unity WebGL export settings dialog to 480×800. Lovely, it now fits on a 1080 display.

Create a Facebook App for your game

So I now have an app that I can submit to Facebook and have it hosted by them, the next thing to do is to go and create a Facebook app. To do that head over to the Facebook Developer Site and create an app. When creating the app ensure that you add the Facebook Web Games platform and in settings ensure that you have WebGL and Simple Application Hosting enabled. In general settings also ensure that you at least provide App Name, Email Address, Privacy Policy URL, App Icon (1024×1024) and select the Games category.

Uploading your game to Facebook to be hosted

In the Facebook Web Games section, locate the Simple Application Hosting section, just beneath that there is a link named “uploaded assets”, click that link, a new browser tab will open which takes you to the Manage Hosted Asset section, click the add and setup button to the top right then under Hosting Type select Unity (WebGL).

Now go to your Unity exported WebGL folder and zip up all the files, this should include index.html, TemplateData and Release folders, ensure that index.html is in the root of the zip. Go back to Facebook Manage Hosted Assets section and click the Upload Version button, select the zip you just created then upload. The file will show up as processing, after a while this will change to Standby, at this point you can push your app to production by clicking on the small Move to Production button (If you want to later replace the build with a new version, simply move the build back to Standby by clicking the Move to Standby button, delete the build then re-upload a replacement.

Once the build is live you can visit your app url (see the Facebook Web Games Page URL in the Facebook Web Games section of your apps settings). Visit this URL and you will be able to test the game. Note that your game will not go live however until it is reviewed by Facebook and approved.

Getting reviewed

I am quite lucky with my game because I do not require any extra permissions. The game only logs into Facebook, uses the players profile picture and shares the players progress via the share dialog. To get the ball rolling you need to click the Add Products button to left hand side of the screen in app settings then select “App Centre”, fill out a bunch of info about the app including short / long description, app icons / screen shots etc. Once that is added you can submit for review. Note that if you need any additional permissions such as publish_action then you will need to provide additional info during submission.

Finally don’t forget to make your app public (go to the App Review section of the apps settings and click the button at the top of the screen, this will toggle your app between live and development mode).

The end result can be seen by taking a look at Tens Maths IQ Challenge on Facebook.

Colour Cycle Effect Unity Shader

Just added a new colour cycle effect transparent Unity shader to the free shaders collection. This shader can be used to overlay a y-axis colour cycle effect over a texture

The code for the shader is shown below:

[sourcecode language=”js”]
Shader "Unlit/ColourCycleTexture"
{
Properties
{
_MainTex("Color (RGB) Alpha (A)", 2D) = "white" {}
_WaveSpeed ("WaveSpeed", Range(-1000, 1000)) = 20
_Frequency ("Frequency", Range(0, 100)) = 10
_Amplitude ("Amplitude", Range(0, 3)) = 0.02
_RedScale("Red Scale", Range(0, 3)) = 1
_GreenScale("Green Scale", Range(0, 3)) = 1
_BlueScale("Blue Scale", Range(0, 3)) = 1
}
SubShader
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Frequency;
fixed _WaveSpeed;
fixed _Amplitude;
fixed _RedScale;
fixed _GreenScale;
fixed _BlueScale;

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed2 uvs = i.uv;
fixed4 col = tex2D(_MainTex, uvs);
fixed d = sin(uvs.y * _Frequency + _Time * _WaveSpeed) * _Amplitude;
col.r += d * _RedScale;
col.g += d * _GreenScale;
col.b += d * _BlueScale;
return col;
}
ENDCG
}
}
}
[/sourcecode]

The shader works by adjusting the colour of the texel pulled from the texture applying a wave effect which is then scaled by red, green and blue scaling factors which can be used to change the colour of the overlaid effect. Here are a few parameters to try:

Produce a nice upwards red scroll effect:
Wave Speed – -150
Frequency – -20
Amplitude – -1
Red Scale – 0
Green Scale – 0.4
Blue Scale – 0

Produce a slow flash:
Wave Speed – 150
Frequency – 1
Amplitude – 1
Red Scale – 0
Green Scale – 0.4
Blue Scale – 0

You may need to adjust the red, green and blue scaling factors to match the colour of your texture.

Wavy Text Unity Shader

Just added a new wavy transparent shader to the free shaders collection. This shade can be used to apply x and y axis waves to textures which looks nice on bitmapped based text, e.g.:

Wavy Transparent Unity Shader
Wavy Transparent Unity Shader

The code for the shader is shown below:

[sourcecode language=”js”]
Shader "Unlit/WaveTransTexture"
{
Properties
{
_MainTex("Color (RGB) Alpha (A)", 2D) = "white" {}
_WaveSpeedX ("X Axis WaveSpeed", Range(0, 100)) = 20
_FrequencyX ("X Axis Frequency", Range(0, 100)) = 10
_AmplitudeX ("X Axis Amplitude", Range(0, 100)) = 0.02
_WaveSpeedY ("Y Axis WaveSpeed", Range(0, 100)) = 20
_FrequencyY ("Y Axis Frequency", Range(0, 100)) = 10
_AmplitudeY ("Y Axis Amplitude", Range(0, 100)) = 0.02
}
SubShader
{
Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
Blend SrcAlpha OneMinusSrcAlpha
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed _FrequencyX;
fixed _AmplitudeX;
fixed _WaveSpeedX;
fixed _FrequencyY;
fixed _AmplitudeY;
fixed _WaveSpeedY;

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed2 uvs = i.uv;
uvs.x += sin(uvs.y * _FrequencyX + _Time * _WaveSpeedX) * _AmplitudeX;
uvs.x = (uvs.x * 0.9) + 0.05;
uvs.y += cos(uvs.x * _FrequencyY + _Time * _WaveSpeedY) * _AmplitudeY;
uvs.y = (uvs.y * 0.9) + 0.05;
fixed4 col = tex2D(_MainTex, uvs);
return col;
}
ENDCG
}
}
}
[/sourcecode]

The code works by applying a sine wave to the y-axis source texture coordinate and pumping that into the x-axis texture coordinate, a similar thing is done with the x-axis source texture coordinate, except that a cosine wave is applied to it. Note that when using this shader you should leave a little space around the bitmap text (around 10% of the image size) to prevent borders from showing.

Creating a Unity leaderboard using node.js and redis

I’ve recently added a new node.js based leaderboard system for Unity to Github. I’m going to use something like this in my next game which will feature global leaderboards as a major focus of the game.You can grab the code from Github.

Let start by taking a look at what the repo contains:

Node.js Server

The server side code is written in JavaScript using Node.js with Redis for super fast storage. To use this code you will need a web server that allows you to host your own node.js scripts. There are plenty of super cheap VPS providers around these days so if you go that route take a look at my article on installing node on your own VPS here. You will also need Redis, take a look at my article on installing Redis here.

Once you have everything installed and setup, run main.js located in the Server folder to set the leaderboards server off going:


node main.js

Once running, the server will be listening for connections from Unity on the default port which is set in server.js.

The entire leaderboards service consists of 3 files:

  • server.js – The web server which interacts with the Uniy app
  • leaderboard.js – The leaderboard code which communicates with Redis to store an retrieve persistent data
  • main.js – A main file to create the server and set it off running

The code in server,js is straight forward and much of it was already covered in this article here. The main changes include dealing with data hiding, we now pass the data base64 encoded via the d parameter:

[sourcecode language=”js”]
var vars = body.split("=");
if (vars[0] == "d")
{
var data = Buffer.from(vars[1], "base64");
console.log("Data: " + data);
this.processCommand(data.toString(), res);
}
[/sourcecode]

Here we decode the base 64 string data then pass it on to be processed. This enables me to encrypt the data at the other end then decrypt it when it arrives at this end (not covered in this article), this will allow me to hide what is sent and minimise the chance that someone spams my leaderboards with fake data.

The next major change is to handle commands that are passed to the server, to allow the client to perform different actions such as submit a score or get the users rank etc..

leaderboard.js was added to take care of all actions that are related to dealing with the actual leaderboard data, such as chatting to Redis about what to do with the data. The leaderboard class handles all of this including pushing multiple commands to Redis in one go instead of sending them separately to speed things up. For example sending scores to multiple leaderboards at the same time:

[sourcecode language=”js”]
setUserScores(userName, scores, cb)
{
var multi = this.client.multi();
for (var t = 1; t < this.MAX_BOARDS + 1; t++)
{
var name = this.boardName + ":" + t;
if (scores[t – 1] > 0 && scores[t – 1] < this.MAX_SCORE)
multi.zadd([name, scores[t – 1], userName]);
}
multi.exec((err, replies) =>
{
if (cb) cb(err);
});
}
[/sourcecode]

Here we issue many zadd commands to redis at the same time.

Unity Client

The Unity client code is implemented in Leaderboards.cs located in the Client folder. Lets take a quick look at the code:

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

public class Leaderboards : MonoBehaviour
{
public const int RANK_INVALID = -1; // The rank is invalid

private static string _Url = "http://localhost";
private static string _Port = "8080";

// Submits users score to the server.
//
// @param which Leaderboard index to submit score to
// @param score Score to submit
// @param userName Name of user to submit score for
// @param OnScoreSubmitted Callback to call when operation completes or fails, a bool is passed to the callback which
// is true if an error occurred or false if not
//
public void SubmitScore(int which, int score, string userName, Action<bool> OnScoreSubmitted)
{
StartCoroutine(SubmitScoreToServer(which, score, userName, OnScoreSubmitted));
}

private IEnumerator SubmitScoreToServer(int which, int score, string userName, Action<bool> OnScoreSubmitted)
{
Debug.Log("Submitting score");

// Create a form that will contain our data
WWWForm form = new WWWForm();
StringBuilder sb = new StringBuilder("m=score");
sb.Append("&w=");
sb.Append(which.ToString());
sb.Append("&s=");
sb.Append(score.ToString());
sb.Append("&n=");
sb.Append(userName);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
form.AddField("d", Convert.ToBase64String(bytes));

// 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);
if (OnScoreSubmitted != null)
OnScoreSubmitted(true);
}
else
{
if (www.responseCode == 200)
{
// Response code 200 signifies that the server had no issues with the data we sent
Debug.Log("Score send complete!");
Debug.Log("Response:" + www.downloadHandler.text);
if (OnScoreSubmitted != null)
OnScoreSubmitted(false);
}
else
{
// Any other response signifies that there was an issue with the data we sent
Debug.Log("Score send error response code:" + www.responseCode.ToString());
if (OnScoreSubmitted != null)
OnScoreSubmitted(true);
}
}
}

// Submits a collection of scores to the server.
//
// @param scores Scores to submit, once score per leaderboard
// @param userName Name of user to submit score for
// @param OnScoresSubmitted Callback to call when operation completes or fails, a bool is passed to the callback which
// is true if an error occurred or false if not
//
public void SubmitScores(int[] scores, string userName, Action<bool> OnScoresSubmitted)
{
StartCoroutine(SubmitScoresToServer(scores, userName, OnScoresSubmitted));
}

private IEnumerator SubmitScoresToServer(int[] scores, string userName, Action<bool> OnScoresSubmitted)
{
Debug.Log("Submitting scores");

WWWForm form = new WWWForm();
StringBuilder sb = new StringBuilder("m=scores");
sb.Append("&a=");
for (int t = 0; t < scores.Length; t++)
{
sb.Append(scores[t].ToString());
if (t < scores.Length – 1)
sb.Append(",");
}
sb.Append("&n=");
sb.Append(userName);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
form.AddField("d", Convert.ToBase64String(bytes));

UnityWebRequest www = UnityWebRequest.Post(_Url + ":" + _Port, form);
yield return www.Send();

if (www.isError)
{
Debug.Log(www.error);
if (OnScoresSubmitted != null)
OnScoresSubmitted(true);
}
else
{
if (www.responseCode == 200)
{
Debug.Log("Scores sent complete!");
Debug.Log("Response:" + www.downloadHandler.text);
if (OnScoresSubmitted != null)
OnScoresSubmitted(false);
}
else
{
Debug.Log("Scores sent error response code:" + www.responseCode.ToString());
if (OnScoresSubmitted != null)
OnScoresSubmitted(true);
}
}
}

// Gets the user rank from the server.
//
// @param which Leaderboard index to submit score to
// @param score Score to submit
// @param userName Name of user to submit score for
// @param OnScoreSubmitted Callback to call when operation completes or fails, an int is passed to the callback which
// represents the users rank
//
public void GetRank(int which, string userName, Action<int> OnRankRetrieved)
{
StartCoroutine(GetRankFromServer(which, userName, OnRankRetrieved));
}

private IEnumerator GetRankFromServer(int which, string userName, Action<int> OnRankRetrieved)
{
Debug.Log("Getting rank");

WWWForm form = new WWWForm();
StringBuilder sb = new StringBuilder("m=rank");
sb.Append("&w=");
sb.Append(which.ToString());
sb.Append("&n=");
sb.Append(userName);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
form.AddField("d", Convert.ToBase64String(bytes));

UnityWebRequest www = UnityWebRequest.Post(_Url + ":" + _Port, form);
yield return www.Send();

if (www.isError)
{
Debug.Log(www.error);
if (OnRankRetrieved != null)
OnRankRetrieved(RANK_INVALID);
}
else
{
if (www.responseCode == 200)
{
Debug.Log("Get rank complete!");
Debug.Log("Response:" + www.downloadHandler.text);
if (OnRankRetrieved != null)
{
int rank = RANK_INVALID;
int.TryParse(www.downloadHandler.text, out rank);
OnRankRetrieved(rank);
}
}
else
{
Debug.Log("Get rank error response code:" + www.responseCode.ToString());
if (OnRankRetrieved != null)
OnRankRetrieved(RANK_INVALID);
}
}
}

// Gets the users ranks from the server.
//
// @param userName Name of user to submit score for
// @param OnRanksRetrieved Callback to call when operation completes or fails, callback has an array of ints, with
// each element representing a leaerboard rank
//
public void GetRanks(string userName, Action<int[]> OnRanksRetrieved)
{
StartCoroutine(GetRanksFromServer(userName, OnRanksRetrieved));
}

private IEnumerator GetRanksFromServer(string userName, Action<int[]> OnRanksRetrieved)
{
Debug.Log("Getting ranks");

WWWForm form = new WWWForm();
StringBuilder sb = new StringBuilder("m=ranks");
sb.Append("&n=");
sb.Append(userName);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
form.AddField("d", Convert.ToBase64String(bytes));

UnityWebRequest www = UnityWebRequest.Post(_Url + ":" + _Port, form);
yield return www.Send();

if (www.isError)
{
Debug.Log(www.error);
if (OnRanksRetrieved != null)
OnRanksRetrieved(null);
}
else
{
if (www.responseCode == 200)
{
Debug.Log("Get ranks complete!");
Debug.Log("Response:" + www.downloadHandler.text);
if (OnRanksRetrieved != null)
{
string[] sranks = www.downloadHandler.text.Split(‘,’);
int[] ranks = new int[sranks.Length];
int i = 0;
foreach (string s in sranks)
{
ranks[i] = RANK_INVALID;
int.TryParse(sranks[i], out ranks[i]);
i++;
}
OnRanksRetrieved(ranks);
}
}
else
{
Debug.Log("Get ranks error response code:" + www.responseCode.ToString());
if (OnRanksRetrieved != null)
OnRanksRetrieved(null);
}
}
}
}
[/sourcecode]

This class contains four methods at present:

  • SubmitScore – Submits a score to the server
  • SubmitScores – Submits a collection of scores to the server
  • GetRank – Gets the users rank from the server
  • GetRanks – Gets all of the users ranks from the server

Internally each request to the server is called via a coroutine and the supplied callback that we pass is called when a response from the server comes in, e.g.:

[sourcecode language=”csharp”]
Leaderboards lbds = GetComponent<Leaderboards>();
lbds.GetRank(which, (int rank) =>
{
// Do something with the result
});
[/sourcecode]

Its also worth mentioning that when we send data to the server we make some effort to hide it which makes it a little harder for someone to spam the server with false scores, e.g.:

[sourcecode language=”csharp”]
// Create a form that will contain our data
WWWForm form = new WWWForm();
StringBuilder sb = new StringBuilder("m=score");
sb.Append("&w=");
sb.Append(which.ToString());
sb.Append("&s=");
sb.Append(score.ToString());
sb.Append("&n=");
sb.Append(userName);
byte[] bytes = Encoding.UTF8.GetBytes(sb.ToString());
form.AddField("d", Convert.ToBase64String(bytes));
[/sourcecode]

Note how we firstly encode the data to bytes then convert it to a base 64 string, this enables us to add a step between, where we can encrypt the byte data using whatever means we like.

Whats coming next….

I plan to extend the server to allow queries of the data, so I can for example view all of the leaderboards from a website and possibly on device.

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.

2D water shader in Unity 3D

I recently found the need to spruce up the water in a new 2D game that I am working on, so I decided to have a play around with Unity shaders again. Here’s the code to the shader:

[sourcecode language=”js”]
Shader "Unlit/WaterTexture"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_ScrollX ("X Scroll Speed", Range(-10, 10)) = 0
_WaveSpeed("WaveSpeed", Range(0, 100)) = 10
_FrequencyX("X Axis Frequency", Range(0, 100)) = 34
_AmplitudeX("X Axis Amplitude", Range(0, 100)) = 0.005
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;
fixed _ScrollX;
fixed _FrequencyX;
fixed _AmplitudeX;
fixed _WaveSpeed;

v2f vert (appdata v)
{
v2f o;
o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}

fixed4 frag(v2f i) : SV_Target
{
fixed time = _Time * _WaveSpeed;
fixed2 uvs = i.uv;
fixed offset = (_ScrollX * _Time);
uvs.x += offset;
uvs.x = fmod(uvs.x, 1);
uvs.x += sin(uvs.y * _FrequencyX + time) * _AmplitudeX;
fixed2 uvs2 = i.uv;
uvs2.x -= offset;
uvs2.x = fmod(uvs2.x, 1);
if (uvs2.x < 0)
uvs2.x += 1;
uvs2.y = 1 – i.uv.y;
uvs2.x += sin(uvs2.y * _FrequencyX + time) * _AmplitudeX;
fixed4 col = tex2D(_MainTex, uvs);
fixed4 col2 = tex2D(_MainTex, uvs2);
col = (col + col2) / 2;
return col;
}

ENDCG
}
}
}
[/sourcecode]

The main section of the code is the fragment shader, it plays with the textures U coordinates by applying a sine wave to them, it then does the same with the pixel on the opposite side of the V axis then merges them both together, it also scrolls the textures, this produces a nice running water effect.

You can grab all shaders that I make public from Github

Feel free to re-use the shader in your own creations.