Reworking the control flow for my tactical role-playing game

Steven Schveighoffer schveiguy at gmail.com
Thu Mar 21 16:48:39 UTC 2024


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


More information about the Digitalmars-d-learn mailing list