Reworking the control flow for my tactical role-playing game

Liam McGillivray yoshi.pit.link.mario at gmail.com
Sun Mar 17 00:14:55 UTC 2024


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](https://github.com/CyberShadow/ae) & 
[godot-D](https://github.com/godot-d/godot-d) but not really 
figuring it out, I was recommended 
[raylib-d](https://github.com/schveiguy/raylib-d), a binding for 
[raylib](https://www.raylib.com/) 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/`](https://github.com/LiamM32/Open_Emblem/tree/master/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`](https://git.samerion.com/Samerion/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?


More information about the Digitalmars-d-learn mailing list