On Sunday, 17 March 2024 at 00:14:55 UTC, Liam McGillivray wrote:
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 got it to run on my mac, I had to do a few updates to the
dub.sdl file, but it did work, though I couldn't figure out how
to make attacks work.
## 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.
This is kind of cool, I like the idea!
### The Library:
All sounds good
### 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.
Note that disabling the cap on framerate just avoids the
sleep-per-frame that raylib does. I always recommend setting a
cap of something like 60 unless you are going to use vsync.
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.
So when doing video game development with a main loop that needs
to refresh the screen every frame, you need to componentize
long-running operations into frame-size bits.
So for instance, an animation that needs to move an object from A
to B, should be captured into a temporary item (class object,
struct, member of the sprite, etc), where you tell it every time
you are drawing a frame (or even better yet, each game tick), and
let it make the changes necessary for one tick of time.
For instance, build an object that takes start position, end
position, time to animate, and maybe a path function (like
linear, ease-in/ease-out, etc), and then each frame calculates
where the position should be based on the current time vs. the
start time. Encapsulating all this into an object makes things
easy to deal with. Then you just need to call it every frame/tick.
## 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.
I'm not sure if you want to do event driven here. It's a
possibility. But I find a game-tick system, where each tick, you
update each object according to its wishes to be pretty easy to
develop with. I will add the caveat that I am also a pretty
novice game developer, and have never actually built a complete
game.
If I were to recommend a system here, I'd create a linked list of
items to "tick" every frame, with something like:
```d
interface GameObj {
// returns false if this object is done
bool tick();
}
```
Then basically, you go through your list every frame, and call
the tick function, which will make needed changes, and if it
returns false, then you remove from the list while iterating.
This means you can do N things at once, and you don't need
multiple threads to do it.
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.
I think you are better off not using threads. Threads make things
very difficult to synchronize, and you have no guarantees that
your animations will run at any specific time. I don't think you
would like the results.
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.
Yep!
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.
One thing to consider is doing game ticks separate from frames.
That is, your game tick timer is not locked to the frame rate.
This way if you drop frames, the game doesn't change its timing.
The gui stuff is typically one call per frame loop, and I think
it does all drawing and processing of inputs there.
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?
No, I think you are in pretty good shape! I don't know much about
signals and slots, so I'll leave that unanswered.
-Steve