I have come back from a few days (5-8 h/day) of chasing the root #include of the infamous circular dependency loop. I am shaken but my morale is high.
I had a wonderful idea; I was going to make a “BlackBoard” class. It was supposed to have the ability to collect data and enable the path finding alghoritm…
Before I dig into the idea I need to give you a little backstory; I have a class made up in a hurry only to have a window and basic graphics to visualize how the A* behaves. I called it StartPrototype. I handle a lot of things there, the window, rendering, event handling, map drawing and now it also starts the A* and calls the agent’s update function. I will change this later but for now I want to refer to it as GameWorld.
I figured that a BlackBoard class will need access to the GameWorld to let the BT know whether its leaf nodes and branches succeeds, fails or need to return running. What could I do? the simplest way that I could think of was to declare BlackBoard a friend class of GameWorld. Then I could just create the functions needed for the BT in BlackBoard which would in its turn use the GameWorld just as if they were the same, but GameWorld would not reach the members of BlackBoard. Then all that was needed was to give the nodes in my tree an instance or pointer to BlackBoard and I could tailor fit BlackBoard to get the information the nodes needed.
Up until this point, the idea is still simple and straight forward and I still want to implement this concept. The following is what I am going to change and what made my #includes go crazy.
About 5 days ago I wrote the text above. I sometimes do not complete posts. Now I have implemented the BT! This wasn’t as easy as I wanted it to be but I have implemented it the way I said I would.
I had not been thinking about the includes. The circular dependency was rough. I figured since StartPrototype was handling so many things (described above) and I needed parts of it for the BT such as access to the tile grid and nodes. This meant I included the file StartPrototype.h into the Behavior tree and the Agent (the entity “Tank” that will be manipulated by the BT) and here and there. StartPrototype.h also had included Agent and Behavior tree and so on and so on.. If I would have written a diagram of it it would make a ball of dry pasta tagliatelle look like parallel lines.
I was inspired by what I had written in the top of this post, break down StartPrototype into several classes. StartPrototype should only start the prototype. Then I migrated the window and event handling to my custom class Window. I am keeping it short, I will focus explaination to the BT. Then I made a new class GameWorld. Game world creates the grid and the graph. I also made a new state, PrototypeState, for my state machine.
After a few days of migrating code I had the game running again like before and I started making the needed Tree Nodes. One RootNode that inherits behavior. It only starts the tree. One sequence node that holds a container with the 4 leaf nodes. The sequence runs the children one by one so long as they succeed. If one child returns running or failed, the behavior tree starts over from the root node again.
I had my BlackBoard, BehaviorTree (the custom nodes), Agent and GameWorld in a mexican stand off again. I wrote a sloppy diagram:
As you can see Agent had behavior tree, that included GameWorld, which included Agent which included Behavior Tree and so on! There could probably be other lines drawn here as well. I did not want to have GameWorld.h included inside BehaviorTree.h because it brought other includes with it. but BlackBoard needed a pointer to GameWorld to access its private members (lol I can’t help thinking that sounds dirty). But it just could not be included at all. The Behavior Tree needed BlackBoard, and blackboard brought with it GameWorld and there was yet another dependency loop.
The solution I came up with was to make both BlackBoard and my custom BT nodes templates. Now these templates are not generic, they use functions from blackboard which is accessing GameWorld members. Both the behavior tree and the black board are instantiated in the new prototype state using class GameWorld. The game world is also instantiated in the same function.
The link above is the PrototypeState::Enter() function. If the ServiceProviders are confusing it is explained in this chapter of a very useful, free online book : http://gameprogrammingpatterns.com/service-locator.html
If you don’t enjoy looking at code, I use the service provider to get pointers to the window for example, but most importantly I initialize the game world, the blackboard and my custom BT. You can see I use the GameWorld class to initiate the black board and the BT.
Now let me draw another sloppy diagram:
This looks much cleaner than before, there are no circularity and things only have what they require. There is only one untidy thing, ServiceLocator.h and Graph.h appears twice each. It is OK since I use #pragma once so that they all get compiled only once.
I’ll get around to tidying that up later.
Now, I do not know really the pros and cons with making the BT and black board templates that are designed for a particular class. One con is the long BehaviorTree.h file, but when it comes to computer performance and compile time and such I do not know. I get the feeling that some people might object that templates are meant to make more general classes that could handle any data type. If the classes are not generic then why use templates? My low level compile knowledge is not very extensive so I answer that question; why not? It did solve the dependency loop quite efficiently. Anybody who know feel free to tell me pros and cons and perhaps even another suggestion to compare with.
Ok, I need to think about what I do next. I think I should try to get more entities getting steered across the tiles by the BT. I will need more characters sooner or later and I should try to get them going while the BT is still very simple.
I will update as soon as I know any more