In short, to answer your question about where to connect the data between Nodes and Creeps…With Decoupling set aside your suggestions are completely fine and its mostly up to preference.
I’m actually building my own TD game where the path-finding is dynamically updated as the player shapes the paths on their map. Due to the nature of my game I use a multidimentional linked-list to construct a graph and then employ an altered version of the biconnected component algorithm along with Djikstra’s Algorithm for path-finding. and due to the complexity of some of the creeps, defenses, spells and debuffs, decoupling was very important in my game.
If you’re worried about properly decoupling, then a couple of Design principals to start with:
Each class should have one and only one responsibility (or as few as possible)
This principal is a big game changer and it can explode the number of classes your project can have. The improved flexibility it will grant you will likely cost you in terms of complexity and debugging, just be aware of the trade-offs.
In this specific case you’ll likely want 3 classes:
- Creeps: manages actual movment and other behavior.
- Nodes: Tracks who it’s current neighbors are.
- Agent: connects Creeps and Nodes. controlling how Creeps path through the map and informing nodes then they are occupied.
In this design example, Node will have a reference to an agent (or null) to determine if its occupied, though you could have easily had the Agent store a Dictionary of nodes to creeps instead. Having the Creeps connect to an Agent class allows to find the preferred path to a destination via the neighbors its node is linked to.
program to an interface, not an implementation
Interfaces are a great way for decoupling your classes while at the same time enabling extensibility. For example, you have a map with roads and rivers. You might have one type of enemy that only walks on the road, while another that can only swim, and finally a third that can fly. All three movement modes have a common behavior, they find the next waypoint (even if how they get their next waypoint is found differently). you can simply create an interface “IAgent” that has a method Next() which your adapter classes (WalkableAgent,SwimableAgent,FlyableAgent) all implement. Your creeps can polymorphically call their agent.next() to find their next way-point without even knowing what type of class they are referencing. this has a benefit of allowing you to near effortlessly swap out agent types (maybe you have an air tower that forces a flyer to walk) and the creep is none-the-wiser.
With interfaces the implementing classes are abstracted away from the calling class so that the caller only knows what it needs to know. the more you have a class reference an interface over a concrete class the more decoupled you can make your project.
but like I said before, decoupling will make your project’s complexity skyrocket. if you have multiple programmers (even if you don’t you might in the future) then documentation is key because following decoupled code when it breaks is a headache. So its best to find compromises where you want flexibility + extensibility over simplicity + development time. Don’t forget your scope!