Support for turn-based apps
Opened this issue ยท 6 comments
An idea which came up was the option to remove automatic tick events, while exposing a function to manually trigger a tick at the end of the turn.
After further consideration, a full-manual mode might not be the way to go. Even in turn-based scenarios, we probably don't want to totally shut off the Manager. The best way forwards here might be to write a clean, efficient implementation of a turns mechanic using standard ECS design principles, and include it in the documentation as a guide.
Hi @APB9785! Do you have any updates on this? I'm ultra curious about how that looks like in ECSx.
I'm testing some approaches for a turn-based MMO that uses LiveView and right now I'm mainly relying on a single GenServer to control battle states - it's mostly event-based and it's working pretty well tbh. So, I'd very much like to understand how ECSx could help in these specific cases (considering a more data-drive approach).
It goes without saying, but props to the excellent work on ECSx. Cheers!
@thiagomajesk Sorry for the late reply! I appreciate the kind words, and your interest in ECSx. ๐
- However you organize your players, whether it's into rooms, games, lobbies, instances, realms, etc... Assuming each of those is an Entity, you can add an
ActivePlayer
orActiveTeam
Component, which contains the value of whose turn it is - If you have more than two players/teams per turn rotation, you could give them
NextPlayer
Components storing the Entity of the next player after them, or aTurnRotationPosition
storing an integer, etc. So your turn System will know who gets to be the next active player/team. - If you just have one big game happening - i.e. it isn't divided into rooms or instances - a Tag can be used such as
IsActive
which marks the active player/team.
Hope this gives you some ideas while we consider an "official approach" for the documentation
Thanks for your reply, @APB9785!
So, if I got it right because the game world will always be modeled using components, the turns have to be contained within the main game loop as well, which means the game never really stops running. Ok, that makes sense because it's how the architecture is supposed to work, but now I'm wondering if you have done any experiments with systems that have variable tick rates (or maybe systems that only run when some events happen).
Let me give you an example: Say you have a forest with some trees that can be taken down by players, and once those trees have been taken down, they'll respawn after 30 minutes. In this case, I could have a TreeRespawn
system that only has to run 30 minutes after I have attached the tag IsTakenDown
to a tree.
I know you can mix events with ECS, but I'm essentially thinking more in terms of resource usage and I think this could be very helpful for building turn-based games. Have you seen something like this while studying to build ECSx? I'm not sure if this is a completely different pattern or something that you can build on top of an ECS engine.
systems that only run when some events happen
This could just be an Elixir function which gets called at the time and place where the event is happening
For example, let's say you want players to have Hit Points and the player dies when HP reduces to zero. You could take one of two options:
- Have a
Death
System which runs toward the end of every tick, checking for players with zero HP and running yourkill_player
logic whenever it finds one - Every time a player's HP is reduced, check if it went down to zero, and if so, call
kill_player
right there in theAttackDamage
orCollision
or whatever System is causing the damage
There are some tradeoffs to either approach - I would lean towards the former, since ECSx can now search for specific values in constant time with index: true (i.e. it is extremely fast to check for all HitPoints components where the value is exactly 0). A nice benefit of the former approach is that your logic is organized neatly into a single System, whereas with the latter approach, you have multiple systems sharing the same code.
There is also a "third way" so to speak, in cases where search
ing for a specific value is not possible, but you want to keep the aforementioned code organization benefit. When the event happens (like the player dropping to zero HP) you can create a Tag such as ReadyToDie
for the player and then your Death system simply reads ReadyToDie.get_all()
each tick, which is almost always an empty table.
In this case, I could have a TreeRespawn system that only has to run 30 minutes after I have attached the tag IsTakenDown to a tree
Early in development, we tried including an option for only running systems periodically, but decided against this because it makes it more difficult to accurately monitor your application's performance with ECSx.LiveDashboard. We highly encourage everyone to use the LiveDashboard; it can be a huge benefit to see the "big picture" as you add more and more Systems into your app.
There is a workaround, however, which I would recommend to use sparingly for the reason mentioned above. Since you have an Elixir app, with a regular application.ex
supervision tree, nothing is stopping you from adding your own processes outside ECSx, such as a GenServer which tracks a 30 min cooldown. The limitation is that your GenServer will not have any write access to your Components. Therefore you must consider the GenServer (and any other processes you spin up outside of ECSx) to be a Client (similar to a LiveView) and use ClientEvents to delegate the respawn task to an ECSx System.
For this specific case, I personally would add a RespawnAt
component with a timestamp 30 minutes in the future, whenever the tree is taken down. Then the TreeRespawn
system checks just these components (every tick) to see if the time is up. This should not become a performance concern until the number of trees grows to a very large amount, and at that point there are still some techniques to squeeze out more, such as using integer timestamps with search
+ index: true