As many of you know, I have been trying to write a tactical role-playing game (a mix of turn-based stategy & RPG) in D. This is the furthest I have ever gotten in making an interactive program from the main function up. Right now, it is not yet playable as a game, but you can interact with it and get a rough idea of what I'm going for. Feel free to download and run it to see what I have so far.
https://github.com/LiamM32/Open_Emblem
I'm now at a point where I have trouble figuring out the next step to making the game playable. The complexity may have just reached a point where I find it harder to keep track of everything that I have written. There is probably a fair amount of unused code that I abandoned after deciding on a different solution, but forgot to delete. There are probably also some amateur decisions I've made in structuring the program, given that I largely figured it out myself.
For some time now I've thought that I may later want to overhaul how the whole rendering and UI system work. Perhaps now is a good time since my productivity under the current system is slowing down.
The Current Structure:
The code for Open Emblem (name subject to change) is split between a source library, which handles the internal game logic, and a graphical front-end program which uses that library, but makes it usable.
When starting, I decided to structure it this way so that I can experiment with different graphics and UI libraries. This may have been a good move, even if it complicates some aspects, as the first library I tried wasn't the one I've stuck with. I also thought that this library may also be usable as a platform for others to make their own tactical RPG games, though that's unlikely with the current direction of the project.
The Library:
The most important modules here are map
, tile
, & unit
, which contain the classes Map
, Tile
, & Unit
. There is nothing here specific to any particular graphics or game library.
Well, Map
is now longer actually a class, as it's been replaced by the Map
interface and MapTemp
template which implements it, but for simplicity, I'll refer to Map
as a class. This class is meant to serve as the master that controls the flow of a single game mission. Only one instance is meant to exist at a time. It holds a 2-dimensional array of Tile
objects which represents the grid that the game is on (like a chessboard) and an array of all Unit
objects.
Unit
represents a character in the game that can be moved on the map (like a chess piece). It has some stats stored as variables, and some functions to do various things a player (or AI) may ask the unit to do during their turn. Each unit occupies a tile object.
Tile
is a square on the map, which has it's own x & y coordinate.
The Faction
class currently only serves to store a set of units belonging to a certain player or AI, but is planned to play a bigger role later.
The Raylib Front-end:
After looking at many libraries and taking a shot at ae & godot-D but not really figuring it out, I was recommended raylib-d, a binding for raylib by @Steven Schveighoffer. Raylib is a rather simple graphical library written in C. I ended up sticking with it because the website has so many easy-to-follow examples that make it easy as my first graphical library. They're written in, but I adapted them to D rather easily. Of course, being written in C has limitations as it isn't object-oriented.
This is front-end is in the oe-raylib/
directory.
For this front-end, I've made the classes VisibleTile
and VisibleUnit
, which inherit Tile
& Unit
, but add sprite data and other graphics-related functionality.
I then have the Mission
class which inherits the MapTemp
class. This class dominates the program flow in it's current state. It handles loading data from JSON files, switching between different game phases and does most of the function calls related to rendering and input.
The way I have it currently, there's a startPreparation
function and playerTurn
function, each of which run a once-per-frame loop that renders all the necessary objects and takes user input. They each have a rather messy set of if-statements for the UI system. Any UI elements that may pop-up are declared before the loop begins, with if-statements to determine whether they should be visible this frame.
For UI elements, I currently have version
flags for either customgui
(which I started writing before discovering raygui) and customgui
, which you can select between using dub --config=
. Having both makes the code messier, but I haven't yet decided on which I prefer. They are both currently achieve equivalent functionality.
Everything here is single-threaded. Despite that, I still get thousands of frames-per-second when disabling the cap on framerate.
To get a glimpse of a flaw with the current approach (which may be simpler to fix with an overhaul), try asking one of the units to move during your turn, but then try moving the other unit while the first one hasn't reached their destination. The first unit will stop.
Should I rework things?
So now I am thinking of reworking the rendering system, but also changing some of my approach to how the Open Emblem library works.
I've been thinking of adopting an event-driven approach, using signals and slots, for both the library and the front-end (and between the two). I'm curious if more experienced programmers think this is the right approach.
Play Fire Emblem. When you command one of your units to move and attack an enemy unit, you don't just see them teleported to their destination and the enemy dead (or lower in HP) as soon as next frame. Instead, it will start an animation of your unit attempting to attack the other, and after ~3 seconds you find out whether they hit or missed (which is based on probability). In contrast, under my current approach where a game event happens by calling a function, everything will happen instantly. One way to solve this would be to have the rendering object not look directly at the underlying variables, but some cached variables that get updated less quickly. In Fire Emblem, it's likely that the game has already determined whether an attack succeeds or fails immediately after it's selected, even if the player has to wait 3 seconds before being shown. This is a little bit like how my Unit
objects have a variable for their grid location which gets changed by the move
function, but then there's another variable to represent screen location, which gets updated more slowly as they walk across the screen. The other option is to have these functions happen in a separate thread, with parts where they must wait for a signal to continue further.
Another use of signals and slots is that I can use multi-threading for things that happen once-per-frame. When I added the feature to make units slowly move to the destination selected by the player, I thought I would use a separate thread, but then I realized it would need to be synchronized with the frames, which happens in the main thread.
If I redo the rendering and UI system, I will probably start using Fluid
, which is a Raylib-based UI system written in D.
As for the rendering loop, how should that work? I don't know how it works in other 2D games. Should it be much like the current approach, with a loop for every game phase containing everything it might need to render during that phase, and using logical statements for things that only sometimes appear? As an alternative, I was thinking of making a Renderer
object that runs the rendering loop in it's own thread, and it has variables to keep track of what's currently visible. Another thread would access functions of this object to change what must be rendered. I don't know what's the best approach.
To anyone who made it this far, thank you very much for reading all of this. Is my current approach to rendering bad, or actually not that far off? Would signals and slots be a good thing to adopt?