Modularization without dependency hell
So I am currently splitting up some prototype code in order to create a good architecture. And I am standing before a classic dilemma situation.
Let's say I decided to create one module, which contains all the stuff for my game creatures. It contains health, damage calculations, keeps track of effects, etc. In addition, I have a module, which contains all the magic stuff. It contains how spells are built, executed, what effects they have and what kind of magical stuff is going on in the world.
Now, at some point, a spell hits a creature, and the spell should inflict damage. Before, I would have queried the target and just called some kind of doDamage()
method on it. However, this time, I am using a highly concurrent ECS, and I want all the different modules (actually Amethyst bundles) to be as independent as possible.
In order to solve the situation, I came up with different solutions, but they all don't really sit right, so maybe someone else (with more experience?) could give me a hint on how to go about this situation.
Idea 0: Of course, there is always the option that I just let the magic system depend on the creature module. I also have a weather system, which would have to depend on the creature, in order to simulate heat, wetness, coldness (stiffness?), damage by hail and thunderstruck, etc. And there is a quest system, which might inflict conditions as part of a quest. Lot's of dependencies and I don't know which future dependencies I might have to create. Sounds a little scary, tbh (though that is already widely practiced in package managers, like npm and cargo). If nothing else works, that's what I will roll with and hope that I can keep the dependencies to a minimum... however self-contained modules good-bye~
Idea 1: Create a messaging module, which everything else depends on. So instead of depending on various modules, the magic system just attaches an event-component to an entity and be done with it. The event may or may not be handled, which is nice for various reasons. However, such a messaging system would have several drawbacks. For one, components would have to be created and destroyed all the time and I don't think that would perform very well (might be wrong about that, though). I don't really think it would be practical, either, to just attach a messaging object to all entities and query them all the time, even when there are no messages (that might even be a lot worse than creating and destroying the components). Also, what would a messaging component contain in order to cater to not only the magic system, but also other systems, which I don't even know about, yet? How would it store the data for damage, translations, effects, etc.?
Idea 2: Putting the logic and data together into the individual bundles might be a flawed design by itself in this case. So maybe I should instead put the logic into a central place (or, alternatively, the data?), which has access to all modules with their data. This approach would instantly solve a lot (all?) of my problems, however I would be unable to have separated drop-in modules. Whenever I want to use a module, I would have to write some glue, which is ok for now, but might be annoying when I want to re-use the bundles or make them available to other people, who also need creatures and a magic system like mine. It just does not feel right. Well, this might be more of a problem with the idealist inside me than a practical problem, however I would love to have options and then decide for the least unsettling solution :D
Idea 3: Create "glue modules". If you want to use the magic system with creatures, you need to also have a magic-creature-bundle, which contains all the interaction logic between those two. Hell, this one sounds wrong just thinking about it, but at the same time also solves the problem. Well, the catch is: What if I want to also have a third bundle interact with the first two? I would have to create a new glue module. For every possible interaction, I would have to create a new glue module. Oof! That would increase the boilerplate by an order of scary, so I don't wanna do it.
And all that with only 2-3 bundles. Remember, that I also need a bundle for other stuff, like structures, terrain,... which might depend on one another, too. Any input on this?
For context, I am talking about Amethyst, a data-driven game engine implemented in Rust using an Entity Component System (ECS)