Reworking the control flow for my tactical role-playing game

harakim harakim at gmail.com
Tue Mar 26 03:55:25 UTC 2024


On Saturday, 23 March 2024 at 22:37:06 UTC, Liam McGillivray 
wrote:
> ...a tick system would go hand-in-hand with making animations 
> happen in a separate thread, but it sounds like you're talking 
> about the same thread. Are you suggesting a fixed framerate?
I have done both ways. If you're new to programming, I think the 
single thread would be better since your game logic is fairly 
simple. Simply update your world, then draw everything then 
sleep. Steven Schveighoffer does a lot of entry-level games so 
hopefully he will respond if he disagrees. You can have a bool 
flag you set when an action happens that requires the win 
condition to be checked and then if that flag is set, check the 
win condition at the end of the loop, assuming only one player 
can achieve a win condition at a time.

> This is during the Unit's constructor, where it gives the 
> `Tile` object a reference to itself. What exactly is wrong with 
> this? Can memory addresses change when a constructor completes? 
> I assumed that objects come into existence at the beginning of 
> the constructor function.

I think the idea is the object is not ready to use until the 
constructor is complete and if you leak a reference, someone else 
who doesn't know could use it and they might have expectations 
about it's state that are not upheld midway through the 
constructor. I used to do this a lot and never had a problem with 
it. In an application where you're the only developer and it's 
single threaded, it won't be a problem. It's just bad practice 
because on a team you want to be able to trust that a reference 
to an object is of a completely constructed object and you don't 
want to have to check it's constructor every time.
> Anyway, I can change this by calling `Unit.setLocation` after 
> creating a new `Unit` object.
That's a good plan

>
> I suppose the current system for setting up objects from a 
> `JSONValue` is messy because much of the data applies to the 
> base `Tile` & `Unit` objects, but then some of it needs to be 
> in `VisibleTile` & `VisibleUnit` because it has sprite 
> information.
That's fine. I would move the code out because I only care about 
it when I'm working on loading from JSON, so it wouldn't be 
helpful to see it all the time when I'm working on game logic. 
The value comes in having less to see and think about at a time 
but it will work just the same (assuming you don't break it when 
you move it :). If you like it there, then leave it.
> Do you really think it would be better if I removed 
> `Unit.currentTile`, and just used `Unit.xlocation` & 
> `Unit.ylocation` as parameters for `Map.getTile` instead?
Go with your gut. If you start having problems with them getting 
out of sync, then I would separate them. Most of my suggestions 
boil down to: if you have problems and the fix breaks something 
else and that fix breaks something else or it's just hard to 
understand, then my suggestion will probably help. If it ain't 
broke, don't fix it.

> I have considered doing a rewrite of the rendering system, in 
> which all the rendering, and possibly input during game are 
> handled by a new `Renderer` object, which may have it's own 
> array with all the units in it.
> This sounds a little like my idea of the `Renderer` object, in 
> which the state of what's on screen would be updated by calling 
> it's methods, but having a log of the UI wasn't what I had in 
> mind.
I like the renderer idea. I would keep it in the same thread for 
now unless you really want to make a separate thread.

> Before going with the current approach, I intended to have a 
> module called `loadData` which would read JSON files and make 
> objects out of them
I think you made the right call. A generic json file loader is 
overkill at this point. If you make a second game, make a generic 
and reusable json loader. I try to never make things generic 
unless I have multiple actually use cases that I can design 
against or if it's pretty obvious.

> //Always set destroy to false when calling from the Unit 
> destructor, to avoid an infinite > loop.
> I was just doing some work on the AI system, and I had a 
> segfault every time the enemy was in reach to attack a unit. It 
> turns out this was because the destructor was called. I 
> replaced destroy(unit) with map.deleteUnit(unit), and it solved 
> the problem.
> Nevermind. It turns out this was because the call to the Unit 
> destructor was missing in Map.deleteUnit. The segfault happens 
> whenever a unit is destroyed.
This is exactly the kind of problem circular references cause. 
You're not quite sure what makes the bug or when to consider it 
done, especially when there's multiple entry points into the 
cycle.

> In this case, how should I handle safe destruction of units, 
> given that there are multiple arrays that contain references to 
> it?
Here are my options, in inverse preference order (neglecting the 
time it would take):
1. Keep it like you have it
2. Switch to the tick system. At the end of your updating the 
world phase, clean up and for each unit that has alive = false, 
remove it from all the things.
3. Move the combat logic out of the unit and either have 
receiveDamage return whether they are still alive or check their 
hitpoints after each attack. If they are dead, then clean them up 
there instead of in the unit. Make the change I suggested to set 
the occupant of the tile outside of the constructor. Do not save 
the faction on the unit.

> It just occurred to me that this must be what you were 
> suggesting here.
That's very similar to what I was suggesting. All you would have 
to do is track which tick you processed the event. When you add 
random numbers, you'd want to use a deterministic random number 
generator and save your seed. It's a great idea.


More information about the Digitalmars-d-learn mailing list