Build The Pyramid
This is a browsergame client/server tech concept. When playing Forge of Empires (FoE), I wondered how this type of game works and how they “did things”. So, decided to roll a small similar application of my own. You can try it out here: https://www.mikoweb.eu/unity/BuildThePyramidClient/
Gameplay
It should be a simplified “building & production” type of game, with authorative server. Like, use buildings to produce goods, then use goods to advance techlevel and buy even more. A very small “production ladder”, with greatly reduced production time (compared to a real game application), also updating when the user is not logged in. Graphical representation would be “faked 3D” on an iso view grid – also mimicking some of the FoE features.
Toolchain selection: Client
For development of the client, I had two options in mind.
- Phaser3, jQuery, TypeScript on VisualStudio or
- Unity(2D).
Up to a certain stage, I did actually work on both solutions. Finally ended up with Unity – just out of personal preference though (C#, dev environment). From a technical point of view, the Phaser pipeline would probably have been more convenient and lightweight. Especially, as you could “outsource” most of the gui representation from the WebGL context to the browser (Phaser’s “adding html elements”).
Toolchain selection: Server
At first thought, a dedicated server script with continuous message loop was planned. It would listen on a special port, using, maybe, node.js or python. This seemed a bit like overkill, though. The server has to store state data only (like, building position, production end time, etc), no realtime connection nor loop is needed (as long as the client can handle async data updates without blocking).
So, now there are several php “mini-scripts” on the (LAMP; Debian) server. Each is dedicated to one special task (e.g., “check client id” or “store building location”) and called by the client. The php scripts would then read/write to a server-side mySql database. Imho, this is enough to do the job.
Scaling this solution would be easy also – just need to increase the number of idle Apache tasks on the server to handle more incoming requests.
Toolchain selection: Other
Pre-rendering would – as very often in my case – be done with C4D. For pixel work, I have my antique PaintShopPro5. For special treatments (like, sprite slicing and custom sprite atlas creation), python comes in handy.
Game States
The internal game states would be supervised by a state manager, checking for allowed states like so:
switch (GameState) { case GameState.None: if (!Array.Exists(new GameState[] { GameState.Initialize, GameState.ErrorScreen }, e => e == newState)) return false; break; } return true;
Also, there is an “Init” and “Exit” routine for each state, being called on entry/exit of the respective state. This helps cleaning things up and make states more independent. It sometimes results in redundant calls, though (e.g. a button is disabled at end of one state, but then again enabled by next state during the same frame).
Client Internal Communication
I used both, a custom eventmanager implementation (see https://stackoverflow.com/questions/42034245/unity-eventmanager-with-delegate-instead-of-unityevent), and also “direct script access via static instance pointer”:
private static StateManager thisInstance; public static StateManager Instance { get { if (!thisInstance) thisInstance = FindObjectOfType(typeof(StateManager)) as StateManager; return thisInstance; } }
Sprite Positioning
In Unity2D, one would use an ISO cam, but can still place sprites in 3D (where z position would act as depth value). I decided to position each tile of my 16×16 sized grid at a different depth value, to ensure correct drawing order.
Sprites Slicing and Animation
For each building, there are 10 different renders packed into a custom atlas: 4 for coloring, 6 for animation. The animation pictures are just cycled with a constant timestep. For a single building of 1 tile size, I went with 100×200 pixels.
To avoid wrong clipping for buildings that cover more than one tile, the pictures were sliced and assigned to single tiles separately. “Back tiles” that would be covered by the building’s “front tiles” are omitted, though. So, for a building of base plane size 2×2, tiles (0,0 = center bottom), (1,0), (0,1) would be drawn, (1,1 = center top) has no sprite assigned (but is still marked “occupied” on the map, obviously).
Network Messages
Only the client is initiating communication. This is done by sending POST form data via an UnityWebRequest, followed by an async wait (Coroutine) for server response (which is just the output of the remotely called php script). Data is sent back and forth in JSON format.
Message Example:
Sent to ProductionBegin.php:
{"ClientId":"5e57a3481e047","Strings":[],"Ints":[4,2,4,15828,15828]}
Received as script output:
{"Status":0,"Strings":[],"Ints":[4,2,4],"BData":[]}
As communication is asynchronous, the client must not be blocked, but has to handle time delays between sending and receiving data. E.g., when a building is clicked to retrieve a finished production, the message is sent, then the bulding goes to internal “waiting state” (not accepting clicks). It will switch to “idle state” (ready for next click/production), as soon as the server response has been processed.
This would then look like so:
Recognition of recurrent Clients
Each client will receive an unique id from server. It is then stored in the browser persistant data area (using Unity’s PlayerPrefs class) and can be used for later identification. Naturally, this way, recognition of recurrent clients will only work on the same machine, using the same browser. A more convenient way would be a login system…
Server Scripts
This is an example of a “mini script” on the server, handling one special task. Some general routines have been moved to a include file:
<?php // TechlevelGet.php // Task: Get current techlevel of a client. Returns ERR, if client not known include "./inc/includes_common.php"; //---- General Block ---------------------------------------------------------- $InputData = GetInputData($_POST); // Will OutputReturnERROR + die() on error $ClientId = $InputData["ClientId"]; // String value $Strings = $InputData["Strings"]; // VarLen array of strings from client $Ints = $InputData["Ints"]; // VarLen array of strings from client $DbHandle = OpenDB(); // Will OutputReturnERROR + die() on error // Check if the ClientId is in the database //----------------------------------------------------------------------------- if (!ClientIdExists($DbHandle, $ClientId)) { OutputReturnERROR(); die(); } // Get and return techlevel from database //----------------------------------------------------------------------------- $ClientId = mysqli_real_escape_string($DbHandle, $ClientId); $query = "SELECT Techlevel FROM $SQL_ClientTable_GLOBAL WHERE Id='$ClientId'"; if ($result = mysqli_query($DbHandle, $query)) { $row = mysqli_fetch_array($result); $result->close(); $Ret = GetReturnArray(); $Ret["Status"] = Status::OK; $Ret["Ints"] = [intval($row[0])]; OutputReturn($Ret); } else { OutputReturnERROR(); } ?>
Server Time Sync
The client stores production end times of buildings to the server, to be reloaded on a client’s next run. This time is based on the global UnixTimeStamp value. To avoid possible issues with different time zones or machines, a time sync between server and client is done at first connection. The offset between server’s and client’s reported UnixTimeStamp value is stored for correction.
Hopefully, these remarks can help you if you’re going for a similar application…