Reworking the control flow for my tactical role-playing game

Liam McGillivray yoshi.pit.link.mario at gmail.com
Fri Mar 22 01:34:11 UTC 2024


On Thursday, 21 March 2024 at 16:48:39 UTC, Steven Schveighoffer 
wrote:
> 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.

Great. You may be the first person to download and run it. I 
would appreciate if you committed and pushed the changes to the 
`dub.sdl` file. So far I've only tested it on Linux.

Attacks don't fully work yet. When selecting "Attack" in the 
menu, you should see the tiles in range marked in red, but 
clicking one will as far as you can tell, do nothing but bring 
you back to the previous menu. Right now it's programmed to lower 
the enemy HP on attack, but there's no way for the player to see 
that it worked. Nothing currently happens when HP goes to zero or 
lower.

>> ## 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!

Good to hear. It makes some things difficult when I don't allow 
myself to put anything specific to any graphics or UI library in 
the library, but it also may make it easier if I rework the 
graphics and UI.

>> 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.

The "framerate-test" configuration only exists as a benchmark to 
give me an idea of how far I am from the CPU's limit. It's not 
for playing.

In the default configuration and the others, it currently uses 
the `getRefreshRate` function to set the target framerate to the 
monitor's refresh rate on Linux. It's supposed to do that too on 
Windows, but I haven't tested it. On Mac it just sets it to 60 
FPS.

> 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.

Interesting. This is a different approach from how I imagined it. 
I never thought I would add a whole new object just to move 
another object around. Instead I was thinking of having a 
function in `VisibleUnit` that moves itself to the next location 
on a path (represented as an array of either directions or 
locations). Either this would be called for every unit every 
frame (but it would do nothing if they don't have a path), or 
there would be an array of moving units in which this function 
would be called in the rendering loop.

Perhaps if I had just used the plain `Unit` class from the 
library instead of making the derived `VisibleUnit` class, I 
would consider this 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.

Well this sounds not so different from signals and slots. My 
current understanding is that every time the signal is called, 
that same thread goes through a list of functions connected to 
that signal.

Back when I wrote this post, I had a worse understanding of 
signals and slots, and thought that it might be a thing for 
inter-thread messaging.

> 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.

Yeah. I can see how it may make things unpredictable. My current 
thought is to have only one general-purpose thread. If I 
experiment with others, they will probably be created just for 
one particular task before being ended. It might still end-up 
being single-threaded until things start to slow down.

But even with single-threading, I will need to figure out a way 
to make animations happen at the right times without the library 
dictating when the animations happen.

>> 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.

I thought about that, but if I do this, then I would probably 
want to make it multi-threaded. The thread that updates things 
once per tick would be separate from the one that updates things 
once per frame. It would be easier than doing this 
single-threaded.

An update on Fluid; I realized that it doesn't have a feature to 
do scrolling. I also don't see anything for buttons with images. 
I have an idea of how I can implement these things with Raylib. 
I'm not sure if it would be better/easier to just do that with my 
custom GUI system, or to make derivatives of Fluid classes, and 
possibly make a contribution to Fluid. There's also the issue 
that the theme system in Fluid is going to be overhauled.

Right now I'm going through a trilemma of doing a fully custom 
GUI, using raygui, or Fluid. This becomes of quadrilemma if I add 
a fourth option of ditching Raylib, switching to SFML, and using 
one of their GUI libraries (TGUI or DIMGUI).

You might notice that in the current "raygui" configuration, the 
menu at the bottom of the screen in the preparation phase isn't 
using raygui. That's because I don't know how I would make the 
Unit cards with raygui. The rectangle around it doesn't use the 
raygui theme, because the `GuiDrawRectangle` function that raygui 
uses is private. While in some ways it's more appealing than the 
custom GUI system I have, it still isn't capable enough that I've 
decided to ditch my custom GUI system. Trying to load in a style 
from RayGuiStyler resulted in the buttons being invisible, so I 
used a different method to make and load a style.

Perhaps it would be nice to have a GUI system that uses SVG or 
HTML to make things customizable. I don't know if such a library 
exists. I've thought about trying to make something like that 
with SVG, but only after getting more experience with programming 
in D. Of course, it may be hard to not make it very heavyweight.

> 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

Thank you. Making the enemy AI is difficult, but overall things 
aren't going too badly. This is still a very amateur project by 
someone who has never made a GUI application from the main 
function up, so there may be some amateur design decisions that 
haven't been pointed out to me.


More information about the Digitalmars-d-learn mailing list