Arglan2

This project started because I wanted to make a new game and wanted to build my own game engine. I wasn't going to implement everything from scratch but wanted to get deeper in the technology stack and structure the system in my own way. To do this but not overwhelm myself I decided to pick a game with simple mechanics and build from there.

I selected my old title Arglan which had simple mechanics like:

Since I was picking an old game of mine I didn't want just to reimplement it with my own engine. I also wanted to try to apply some game design techniques I have researched in the meantime and try to improve the game as much as I could.

Game Design Decisions

For the game design there was some main flaws I perceived that had a big impact on the game:

There was also some main improvements I wanted to explore:

Engine Design Decisions

Since I was building my own engine, there were some features I wanted it to have:

The engine separation was like:

Engine Architecture

Implementation

Engine Implementation

The graphics and input of the game were implemented using raylib, a really nice C lib with an excellent API and with cross-platform support.

The audio was done by using sound buffers with miniaudio which is an awesome lib with support for multiple sound formats and platform audio backends while being just one header file. Miniaudio actually is the raylib audio backend also, but when I tried it with raylib for some reason it wasn't working, so I just used miniaudio directly.

Initially I made the network calls in lua with Luasec + Openssl + Luasocket but I couldn't get it to work across GNU/Linux, Windows and Mac. There was always some problem in some of the dependencies. In the end I stripped these out and implemented the network in the C core using HTTPS with libcurl. Libcurl is used for file transfer but with its easy interface I could use it for https in a cross-platform way without many troubles.

The ECS implementation had its specificities for its main logic specially because I decided to have the systems specific implementation in lua but let the entity, components and systems storage in C. This gave me a nice and simple way to implement the systems with much flexibility while having control over the data lifecycle. Here we can see the system which updates all the game timers:


return {
    components = {types.TIMER},
    update = function(dt, entity)
        local timer = ecs.get_component(entity, types.TIMER)

        if timer.increment then
            timer.current_time = timer.current_time + dt
        else
            timer.current_time = timer.current_time - dt
        end
    end
}

Each of the engine core modules exposed an api where the lua scripting could call. And with that I achieved a quite pleasant entity creation format. Here we can see the code to create an item from the lua code:


ecs.new_entity({
    {types.POINT, x, y},
    {types.RECT, width, height},
    {types.COLOR, {255, 255, 255, 255}},
    {types.ITEM, type},
    {types.TIMER, DECREMENT, duration},
    {types.IMAGE, path, width, height, CACHE}
})

Using the ECS architecture and having the systems implemented, this entity will have directly the draw code being applied, the mouse detection that triggers the destruction of item and the autodestroy after the time limit passes without the item being clicked.

Game Implementation

So the first part was recreating the game using my new engine. While doing that I implemented most of the base systems:

With these systems I could already play a level until the end like the old Arglan game.

Old Arglan Recriated

Having the playable part of the game running I started thinking what to implement to try to achieve the improvements I wanted. The first point I started chasing was to improve the game feedback. I went back to my last projects to look at the particles editor so I could generate nice effects for the player actions or in game changes. Particle effects were applied:

Click Particles Effect

Item Click Effect

Win Particles Effect

Win Effect

Lose Particles Effect

Lose Effect

After seeing that adding particles to the clicking of items added some nice feeling to it, I started trying to improve it more.

I started looking for sounds that could be used as feedback for clicking the items. After some search I found some really cool celebration voices for an FPS that called the player "Crazy" and "Maniac" when he was on a killing spree. At this moment I thought, well whats the difference between a killing spree and a clicking streak? :) .

After thinking of clicking streaks, I also thought I could promote a preferred playing style that is more challenging and fun.

With this I added 4 special sounds at 5, 10, 15 and 20 correctly clicked items and started displaying the number of items clicked without failing. While playing this sounded really nice, adding very much to the experience.

But after some time playing I noticed something, since I knew why the sounds where there I understood why the sounds appeared, but any other player didn't get it. So now I needed to make the streak count text more noticeable when the player reaches some rewardable streak value.

First I made the text bigger when reaching those values, but since the player is looking at the items while playing it turned to not be very noticeable. After thinking for a bit I thought I could use particles. After adding particles it was more visible but at the same time it was still not enough.

I looked at the particle generator and thought that I could use text as a particle instead of rects or circles. With this I made an effect that sends multiple instances of the streak score text to multiple directions. It turned out nice and already noticeable.

Streak Text Effect

This new approach was nice, the game had a much better feeling while playing it. But one problem was created when the streaks where added.

If we incentive the player to play in a certain style, if we make him try hard to have bigger streaks, we should reward the player for that, not just play cool sounds during play.

To make the streaks more meaningful a special impact in the level score was added. With this being added another problem was noted. Since the player was playing alone the scores really didn't matter. So to mitigate this the next step was to allow the player to publish his score to a central server so all the players could compete with their scores.

Highscores Screen

Since the network layer was already implemented the communication to the server was relatively easy. On the other side since I only have a small VPS I wanted to reduce to the max possible the resources usage, instead of using the tools I use at my job like python + django, I wanted something smaller and lighter. I used lapis for some other projects and it is really nice, based on openresty, but if possible I wanted something event smaller.

After some research I found kore, a full C web framework. It has an integrated web server with good security defaults. It can release the web application as a single binary without the need to install dependencies on the server and it uses like 10MB of RAM for the main process with the keys and less than that for each worker. Another cool point was that it allowed me to make asynchronous queries to the postgresql database.

Difficulty Progression

After all this implementation the wanted improvements were addressed in some ways but the main identified flaws weren't resolved yet.

I started creating a menu for the levels, so the player instead of playing all levels without being able to stop, now could play each level one at a time and if he lost in an higher level because the game didn't spawn enough items he could just restart in the same level.

With this the impact in the player satisfaction were much lighter when losing in higher levels. At the same time this made a skilled player not need to play again the easy levels, in a way that the initial levels can be used to teach the skills to the player, before making the game harder

Also to have a better understanding of the game difficulty curve I built a calc spreadsheet with the main values of the levels configuration, an estimate of the minimum time possible to beat a level and some previews of the time taken by players in different levels of skill to beat it.

Using this spreadsheet I tweaked the levels in order to make the game have a smooth progress. While doing this I also added new levels reaching a total of 40 levels. This spreadsheet was then exported as a CSV and transformed to a lua table with the levels configuration with a lua script.

Levels Configuration

With all of this the game progression was much smoother, but at the same time the main problem is still not solved. The player will lose in higher levels because the game RNG didn't spawn enough items to win. To solve this I shifted the needed scores up a big part in every level, added much more spawned items in each level and made the items disappear faster according to the level.

Using the spreadsheet I made sure that the player, if fast enough, had sufficient items in every level and controlled better the difficulty progression over the whole game. With these changes the gameplay changed alot, going from a very much luck based game to a much more proficiency based game. Another thing I noticed is that now it's much more clear the evolution of the playing capabilities of the player.

Platforms Support

Since the beginning I made sure that my dependencies had support for multiple platforms. My main development machine has Arch Linux, So my first supported platform was GNU/Linux. Lua, raylib, libcurl and openssl are simple to compile using the makefile provided in each project. Miniaudio was even simpler because it is distributed as a single header file so there is no need to compile shared libraries, was just an '#include "miniaudio.h"' in the code.

GNU/Linux, making the first version was not complicated, just creating the shared objects for each dependency, making a bash script which added the shared objects path to the LD_LIBRARY_PATH environment variable and then ran the executable. This worked well in some machines, but when I tested it in older machines it rapidly crashed. My dependencies had their own dependencies which older versions of ubuntu didn't had.

So the next step was to look the dependencies of each of my dependencies and bundle shared objects for each one. After this I thought everything would work, but some other error happened, now one of the dependencies used methods only available in new versions of GLIBC and this machine had an older version.

To solve this I memcpy and another libc methods that when compiled where used instead of the glibc ones. After this last change it worked in all the machines I tried (Arch Linux, Debian and Ubuntu).

Windows, I used mingw as my compiler toolchain. Actually for windows I didn't need much special handling, was just creating the needed dlls instead of shared objects, creating a rc file which were used to add the icon to the final executable and added the steps during compilation to convert the rc file into a resources file.

MacOS, more changes where needed. Instead of using the -l flags I used in gcc for openGL and other dependencies I needed to use the -frameworks and clang.

Raylib had a bug that assumed that all the macs had an highDPI screen and the mouse only mapped to half the coordinates so I had to address that. The relative paths in a mac when launching from command line work as usual, but if launching the executable from the file manager we had absolute paths and missing environment variables. So I had to change the game to use absolute paths everywhere when in mac.

After all of this, in order to have the mac users not run a bash file, I created the needed structure for an app file which looks like an application executable, but is actually a directory with all the app content inside. Some information were needed for this but it was just question of creating the needed folder structure, adding the icon in the icns format and creating a XML with the application information.

Web, I used the emscripten compiler to compile the project to WebAssembly. I compiled the dependencies to .bc objects, changed the main game loop to a separate function, because the browser has to control the main loop or the application doesn't work. I also needed to set flags to allow memory expansion.

After this the game was already starting in the browser, but it crashed after a bit complaining it couldn't understand the types of some callback functions I created in lua and stored in C. I could get around this by activating the EMULATE_FUNCTION_POINTER_CASTS flag, but with this the fps went really low and after some time the game did crash again. So in the end I dropped the web build.

Android, using the android SDK, the JDK and the NDK I generated apk for arglan that called my native code stored in a shared object. But it had multiple problems, like the multiple android versions, the multiple architectures armeabi-v7a, arm64-v8a, etc. In the end I had troubles when launching the apk after multiple build types. Because of this and because I knew for android I would need to refactor multiple parts of the game to make it work well I dropped the android build.

Final Result

Project Takeaways

I'm very glad with how this project turned, I learned tons of stuff, some that I think I did well and want to repeat and other stuff I now know I need to be more careful or check new approaches in next projects. Of these lessons I can mainly pin the points:

Try the game