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:
- Spawn of items randomly
- Mouse click detection
- Scoring
- Transition between scenes
- Sound playing
- Difficulty management
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:
- Player was losing because of not appearing enough items in higher levels - Since the spawn of items is random, when the player reached higher levels and needed to click more items, the game sometimes just didn't event spawn enough items for him to win. Making him lose not because he didn't had the skills but because the game didn't give him any opportunity. This kind of losing is highly unsatisfactory.
- The difficulty progression was not well ensured - Right now the player has to click more items over time but since the items are spawned randomly there is a huge variance in the needed player skills. There should be a more controlled learning curve according to the progress in the game.
- When the player fails he has to restart from level 1 - This one isn't a flaw by itself, but turns into a very big problem when combined with the random nature of the game. This behavior was making the player have to replay the whole game because the RNG didn't give him any chance of winning.
There was also some main improvements I wanted to explore:
- Feedback - As said by Lee Perry in this article, 'Simply pressing a button has to be an experience people want to repeat. It's what makes a game addictive, intoxicating... it's the recipe for a game that "feels" right.'.
- Game Feel - The "feel" that was talked in the last point. Needs feedback to be created, but there are much more possible steps to it's creation. Sonny has a really nice example.
- Player Uncertainty - In games the parts that have uncertainty are the ones the player needs skill to control. Like the variable jump in super mario, it gives us the need to control the height of the jump which adds uncertainty to the control or when playing a chess match against a friend, since we don't now what choices he will take it forces us to make strategies on how to handle them. This kind of uncertainty forces the player to take choices or to develop his skills to reduce it. This usually creates a more fun game when applied, but can break the game if there are too many points of uncertainty.
Engine Design Decisions
Since I was building my own engine, there were some features I wanted it to have:
- A data way to create the game entities. So I can more easily create tools to manipulate the game, like a graphical editor and such.
- After my initial tests using the ECS architecture now I wanted to try it in a bigger game, so I wanted the core of my engine to use ECS.
- I have been programming in Lua for most of my home development in the last years, but for the core of the system I needed more structure and control. This way I decided to build the main parts of the system in C and write the ecs systems and gameplay logic in lua.
The engine separation was like:
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:
- Systems to render images, rects and text
- System to update the game timers
- System to autodestroy elements after some time
- System to detect clicks in rects
- System to spawn game items when ingame
- System to destroy an item when it was clicked
- System to update the score when an item is pressed
- System to detect the end of a level
- System for the ingame UI
With these systems I could already play a level until the end like the old Arglan game.
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:
- At the clicking of a correct or incorrect item by spreading particles in every direction from bellow the pressed item, so the clicking would be more meaningful and fun.
- After the player wins the game I send fireworks to the air so the winning has a bigger celebration and impact.
- When the player loses, to make the effect that the wood on top of the screen is falling apart, like the player after losing.
Click Particles Effect
Win Particles Effect
Lose Particles 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.
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.
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.
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:
- The particle system was a really nice improvement, adding multiple effects when needed that improved the feedback. More systems like this must be used and tried.
- Most of the changes/improvements were thought when playing the game and testing stuff. While this kind of exploration for improvement is always needed, some extra initial planning could have helped preview some of the problems.
- The spreadsheet was a nice way to understand better the economy of the game, modeling the settings/game in a way like this should be done when possible.
- Implementing my own engine was really nice. It took some time from me, but gave me much more control over the system behavior. Another thing nice about this is that now I have multiple base systems ready for the next game.
- Having the entities with full data configuration and with the ECS was really nice. The next step to take advantage of this is maybe to creating an editor.
- I need to be careful with callbacks from lua to C, this made the web build not work.
- When trying another build with native modules for android, instead of using the NDK, I should try the dart ffi.
- The changes in the game items spawn rate, item duration, needed points, etc, were nice changes that made a total change in the game base needed skills.
- The highscores are working, but some time after the game launch I noticed that most people who played didn't submit their scores. I could have integrated the submit score in the win of level or when beating a difficulty to mitigate this.
- Another thing I think went well, is that I setup tools for looking for memory leaks and to measure the game performance early. This helped me verify that the performance was ok, and to fix some bugs.
Try the game
Other
If you want to receive updates when there are new games or development stories join the mailing list