Open APB9785 opened 1 year ago
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.
@APB9785 does that mean I should close https://github.com/ecsx-framework/ECSx/pull/60? 😄
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. 🙂
ActivePlayer
or ActiveTeam
Component, which contains the value of whose turn it isNextPlayer
Components storing the Entity of the next player after them, or a TurnRotationPosition
storing an integer, etc. So your turn System will know who gets to be the next active player/team.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.
@thiagomajesk
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:
Death
System which runs toward the end of every tick, checking for players with zero HP and running your kill_player
logic whenever it finds onekill_player
right there in the AttackDamage
or Collision
or whatever System is causing the damageThere 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
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.