digitalmars.D.learn - Reworking the control flow for my tactical role-playing game
- Liam McGillivray (148/148) Mar 16 As many of you know, I have been trying to write a tactical
- Steven Schveighoffer (55/165) Mar 21 I got it to run on my mac, I had to do a few updates to the
- Liam McGillivray (87/170) Mar 21 Great. You may be the first person to download and run it. I
- harakim (95/95) Mar 22 I am not an expert but I would second the tick system. That is
- Liam McGillivray (97/158) Mar 23 Alright. I suppose this is largely what I'm doing.
- harakim (61/105) Mar 25 I have done both ways. If you're new to programming, I think the
- Liam McGillivray (7/9) Mar 23 I was just doing some work on the AI system, and I had a segfault
- Liam McGillivray (17/19) Mar 23 Nevermind. It turns out this was because the call to the Unit
- Liam McGillivray (9/17) Mar 24 I was thinking about how I can have one thread doing the
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 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 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. 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. 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?
Mar 16
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_EmblemI 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 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!All sounds goodAfter 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.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
Mar 21
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: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.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_EmblemI 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.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.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 "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.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.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.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.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.No, I think you are in pretty good shape! I don't know much about signals and slots, so I'll leave that unanswered. -SteveThank 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.
Mar 21
I am not an expert but I would second the tick system. That is pretty solid advice. Just iterate through your events or, in your case, units and such objects and update them all. Then draw. Then go to the next tick. You should engineer the system in a way that makes sense to you. The currency of finishing programs is motivation and that comes from success and believing you will succeed. If you're implementing XYZ pattern from someone else, if you don't get it then you will get unmotivated. I have seen some pretty horrible programs make it to all the way done and have almost never seen someone's first run at a problem be both pretty and get finished. Don't beat yourself up about keeping it clean. If you feel like it or it's getting out of control where you can't understand it, then you should go look for a solution. The motivation is in moving forward, though. This is probably the piece of advice I wish I had in the beginning! You can always refactor later when it's done. Solicited advice: * Move to the tick system. You make a loop and update each object based on the time that has passed. If your tick is 1/60th of a second then you make a loop. In the loop, you update the map state knowing 1/60th of a second has passed. Then you update all the items and move them or deplete them or whatever however much would happen in 1/60th of a second. Then you update all of the units. Then you can update the factions or check win conditions. At the end of the loop, you draw everything. (maybe raylib just draws whenever it wants, which is fine) In order to know what to update, you will have to save what action they are doing. ex. an arrow is flying toward a target. you will need to keep track of what it is doing so when you iterate, you can continue that train of thought. This will require a bit of a rewrite but I have worked on a game server that was used by thousands of people for years and it was based on this simple system. It scales really well and is pretty easy to understand. Unsolicited advice: * Your verifyEverything method is awesome. I call that strategy fail-fast. It means you fail right away when you have a chance to identify what went wrong. * Construct the unit and then call map.setOccupant(unit) after the unit is constructed. I would not do anything complicated in a constructor. It's also generally frowned upon to pass a reference to an object to anything before the constructor completes. Most of the changes I mention are things to think about, but this specifically is something you ought to change. I would also remove the unit from the map and then delete the unit rather than removing the unit from within the map class. unit.d:44 map.getTile(xlocation, ylocation).setOccupant(this); * Another reason I would switch that line is that it's best to avoid circular dependencies where you can. It will make it hard to reason about either in isolation. It relates to that line because your map has units in it and your unit takes Map in the constructor. That is a red flag that you are too coupled. That concept is not a rule but just something to think about when you get stuck. This comment points to a symptom of the circular dependency: //Always set `destroy` to false when calling from the Unit destructor, to avoid an infinite loop. * In your game loop, I would keep track of the units separately from the map, if you can. Go through the map and do updates and go through the units and update each one. If the logic is too tied together, don't worry about it for now. * I would break the json loading into separate classes (eg FactionLoader, Unit loader) instead of being included in the map and unit class. I like to have code to intialize my programs separate so I don't have to look at it or think about it or worry about breaking it when working on my main code. * You said //Change this later so that the faction with the first turn is determined by the map file. Comments like that are perfect. Jot down all your ideas while you're working on the main functionality. Once you have something working, tweaking it will be so much fun. Take side quests when you want to stay motivated, but I would stray away from the bigger ones until you have the basic functionality working. It's often the fastest way to get the side quest done anyway since you can test it. * You should probably not do this, but it might give you some ideas for later. What I would do is make a separate thread for managing the UI state and push events to that thread through the mailbox. I have done this once (on my third version of a program) and it was by far the cleanest and something I was proud of. The benefit is you can publish those same events to a log and if something in your UI goes wrong, you can look at the log. Better than that is the ability to replay your log. Instead of sending the events from a game engine, you have a module that just reads from the file and sends the events. Then you can debug where it went awry. And you can have a feature for players to do that to rewatch their game. You can also replace your game engine with a module that reads from the network for a multi-player game and it uses the exact same UI logic. In that case, you can save the network traffic in a log the same way to replay to diagnose network packet processing errors. That method does not require any synchronization or any thread complexity because the communications are one way. However, you will not need it so I would put thinking about this and doing it on the list after everything that is motivating for you.
Mar 22
On Saturday, 23 March 2024 at 04:32:29 UTC, harakim wrote:You should engineer the system in a way that makes sense to you. The currency of finishing programs is motivation and that comes from success and believing you will succeed. If you're implementing XYZ pattern from someone else, if you don't get it then you will get unmotivated. I have seen some pretty horrible programs make it to all the way done and have almost never seen someone's first run at a problem be both pretty and get finished. Don't beat yourself up about keeping it clean. If you feel like it or it's getting out of control where you can't understand it, then you should go look for a solution. The motivation is in moving forward, though. This is probably the piece of advice I wish I had in the beginning! You can always refactor later when it's done.Alright. I suppose this is largely what I'm doing. Thank you very much for looking through my code and making the effort to understand it. So far you are the first person to comment on my code, beyond the little excerpts I've pasted directly onto this forum.Move to the tick system. You make a loop and update each object based on the time that has passed. If your tick is 1/60th of a second then you make a loop. In the loop, you update the map state knowing 1/60th of a second has passed. Then you update all the items and move them or deplete them or whatever however much would happen in 1/60th of a second. Then you update all of the units. Then you can update the factions or check win conditions. At the end of the loop, you draw everything.When you said "tick system", I thought you meant that it would be asynchronous to the framerate, but then you say "At the end of the loop, you draw everything". Right now the function that moves units across the screen moves them each frame a distance proportional to the duration of the previous frame. I was thinking that 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? Because this is a turn-based game, I don't need to update the factions and win conditions every frame, but at most every time a unit makes a move or when a new turn starts.Your verifyEverything method is awesome. I call that strategy fail-fast. It means you fail right away when you have a chance to identify what went wrong.That's nice. I thought it might be an amateurish solution to something that better programmers would do differently, similar to how I use `writeln` to print variables for debugging. It looks like I don't yet have any calls to this function, so perhaps I should add one under the debug configuration.Construct the unit and then call map.setOccupant(unit) after the unit is constructed. I would not do anything complicated in a constructor. It's also generally frowned upon to pass a reference to an object to anything before the constructor completes. Most of the changes I mention are things to think about, but this specifically is something you ought to change.I didn't immediately understand what you were saying, but then I looked at what line `unit.d:44` was at the time you wrote this. ``` map.getTile(xlocation, ylocation).setOccupant(this); ``` 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. Anyway, I can change this by calling `Unit.setLocation` after creating a new `Unit` object. That's unless if there's a particular reason why you think I should make this a function of the `Map` object. 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.I would also remove the unit from the map and then delete the unit rather than removing the unit from within the map class.Sure. I'll change that. Even when I wrote the Unit destructor I was thinking that perhaps this was a bad way to implement this. I suppose I should just call the `Map.deleteUnit` whenever a unit should be deleted, right? I was also thinking of replacing `Map.deleteUnit` with a function that removes all null references from `this.allUnits` which can be called after destroying a unit, but unless if I need to hyper-optimize, that probably won't be any better than the function I have.Another reason I would switch that line is that it's best to avoid circular dependencies where you can. It will make it hard to reason about either in isolation. It relates to that line because your map has units in it and your unit takes Map in the constructor. That is a red flag that you are too coupled. That concept is not a rule but just something to think about when you get stuck.I don't quite understand. Are you saying that I shouldn't have objects reference each-other, like how a `Unit` object has a reference to a `Tile` object called `currentTile`, and that tile object has a reference back to the unit called `occupant`? I see how bugs may happen if there's ever a case where the references aren't mutual, which is the reason for the `verifyEverything` function that you praised. But I imagine things getting more complicated if I can't determine a unit's current tile just by looking at a reference in 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?In your game loop, I would keep track of the units separately from the map, if you can. Go through the map and do updates and go through the units and update each one. If the logic is too tied together, don't worry about it for now.I'm not sure what you mean by "keep track of the units separately from the map". Do you mean to not rely on the `Map.allUnits` array? 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.I would break the json loading into separate classes (eg FactionLoader, Unit loader) instead of being included in the map and unit class. I like to have code to intialize my programs separate so I don't have to look at it or think about it or worry about breaking it when working on my main code.Is this a matter of not having all that JSON-related code cluttering the top of the file? Does putting too much of it in the objects themselves make them take up more memory? Or does it really make bugs less likely? 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, but then I figured it was easier to have the JSON data read directly in object constructors so that private and protected variables can be set. I've thought about going back to this approach, but I haven't because I couldn't think of a reason to. If I do, perhaps they should be placed in the same modules as the objects being constructed, instead of being placed together in the `loadData` module.Take side quests when you want to stay motivated, but I would stray away from the bigger ones until you have the basic functionality working. It's often the fastest way to get the side quest done anyway since you can test it.Yep. At this very moment I have two feature branches on my local copy. I'll have to figure out how to merge them when complete, but I did this for motivation-management, so that I don't need to finish one feature when I feel like working on another.You should probably not do this, but it might give you some ideas for later. What I would do is make a separate thread for managing the UI state and push events to that thread through the mailbox. I have done this once (on my third version of a program) and it was by far the cleanest and something I was proud of. The benefit is you can publish those same events to a log and if something in your UI goes wrong, you can look at the log.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. That being said, I've thought about the possibility of having a log of events that can be used to rewind, replay, or possibly save the game. I agree that this should be further down the line. Even the ability to save a game is not very high on the priority list now. I wasn't thinking of logging *every* UI event though.your game engineThank you for implying that I wrote a "game engine". That sounds like an accomplishment on my part. I'm not really sure which parts of my code would be referred to as the "engine" though.
Mar 23
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 planI 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 themI 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.
Mar 25
On Saturday, 23 March 2024 at 04:32:29 UTC, harakim wrote: This comment points to a symptom of the circulardependency: //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.
Mar 23
On Saturday, 23 March 2024 at 23:59:18 UTC, Liam McGillivray wrote: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. In this case, how should I handle safe destruction of units, given that there are multiple arrays that contain references to it? - Just call the unit destructor directly. The unit destructor will call functions in `Map` & `Faction` to delist the unit from it's arrays. - Call a function in `Map` which will delist the unit from `allUnits`, call a function in it's faction to delist it, and then call the unit's destructor. - Just call the units destructor, which will be rather plain. `Map` & `Faction` will have functions that frequently go over it's list of units, and delete any null references found.
Mar 23
On Saturday, 23 March 2024 at 04:32:29 UTC, harakim wrote:* You should probably not do this, but it might give you some ideas for later. What I would do is make a separate thread for managing the UI state and push events to that thread through the mailbox. I have done this once (on my third version of a program) and it was by far the cleanest and something I was proud of. The benefit is you can publish those same events to a log and if something in your UI goes wrong, you can look at the log.I was thinking about how I can have one thread doing the rendering and another doing everything else, given that the "everything else" thread would be idle most of the time. I thought about giving the "everything else" thread a queue; an array of functions that it's tasked with going over. Every UI interaction would add one to the queue. It just occurred to me that this must be what you were suggesting here.
Mar 24