Behavior Tree and Blackboard Questions

Started by
12 comments, last by haegarr 8 years ago

Hello,

I implemented behavior tree and I have few questions:

1. How do I link between the behavior tree and the blackboard? Should I add CBlackboard class instance inside CBehaviourTree class? Should I have one single blackboard for each behavior tree?

2. Should I use "new" to create behavior tree nodes? For example: actor->addAction(new sequence(new RotateX(10.0f), new RotateZ(10.0f)));

3. If I have a character, should I use two behavior trees? One for the character AI and one for actor->addAction() function?

4. Inside node::run() what is the correct way to access the actor and the blackboard?

5. Should I have variables in the blackboard that can be based on a node? (I mean if the node die then the variable die too)

Advertisement

1.

It all depends on what you write on the black board. It also depends on how many characters use the same behavior tree and whether they share information.

If you store information about the behavior tree on the blackboard, I'd say the behavior tree should be able to access it.

If you use the same tree for different characters, and these characters do not share information, it's feasible that the character carries its information rather than the tree.

2.

Depends on programming language (eg Java cannot make class instances without "new"), how you make the tree available (load from file?, get from static const array?)

Is the tree dynamic? Is it static?

Also here, there is no single right way to do this. Do what works for you, and then switch to more urgent problems that need solving.

3.

Consider both options, what are the costs and what are the benefits? Pick the one you like most.

4.

Any way that works is correct, since, it works! Until you have a more specific issue with whatever you do now, why would you change it?

5.

Is it useful? If yes, then yes, else nope.

You generally seem to believe there are single right ways to do each thing, and all other ways are wrong, or at least, inferior.

That is not the case. Any solution that provides a working system is by definition correct. Thus if you're implementation works, you already have a correct/right way.

Within a "correct" solution, you still have loads of different choices, which eventually change some properties of the system as a whole, like speed, memory use, or scalability. Choices are however not independent, each choice is related in some complicated way to every other choice.

At best, you can steer your correctly working system a bit towards one or perhaps two desired properties. That only works for a specific use case though. "The right way" does not exist at a general level (for all use cases that you might have).

Therefore, I think you should not try to optimize anything further until you have a specific case with it.

I generally take that a step further, any sub-system that does its job sufficiently is not worth my time. I rather spend my time on other parts of the program.

@Alberth:

You generally seem to believe there are single right ways to do each thing, and all other ways are wrong, or at least, inferior.

I could be doing something and after sometime I discover that I was doing it wrong and I have to do it all over again, also sometimes it will work but there might be a better way.

1.

It all depends on what you write on the black board. It also depends on how many characters use the same behavior tree and whether they share information.

I'm not sure how the characters can actually share the same behavior tree, What if one character remove nodes from the behavior tree? I mean it will affect the other characters while it shouldn't, how do I handle aborting the composites so it will not affect other characters?

2.

Depends on programming language (eg Java cannot make class instances without "new"), how you make the tree available (load from file?, get from static const array?)

Is the tree dynamic? Is it static?

Also here, there is no single right way to do this. Do what works for you, and then switch to more urgent problems that need solving.

I'm using C++, would you suggest that I add nodes as pointers or as variable (actor->addAction(Node* node) or actor->addAction(Node node)

4. Inside node::run() what is the correct way to access the actor and the blackboard?

Maybe I could have a parameter inside run(), for example: NodeParams which is a structure that will contain the information such as elapsed time, a pointer to the entity, etc...? if you have any other suggestion, please let me know

Thanks,

Hi again,

I could be doing something and after sometime I discover that I was doing it wrong and I have to do it all over again, also sometimes it will work but there might be a better way.
You could indeed, but that's not a bad thing. Discovering you made a mistake is not fun, but it is a learning experience that helps you avoid similar problems in the future. You get better at fore-seeing holes in the road to avoid, or to examine closer before going in :)

In time, you will see most of them coming at you from a mile or more, and avoiding becomes easier.

I'm not sure how the characters can actually share the same behavior tree, What if one character remove nodes from the behavior tree?
Wait a moment, that's a new requirement :)

In the articles I have read so far, the tree never changed. If your tree does change, then sure, all characters will be affected. You either have to agree that is wanted behavior (eg trimming a part of the behavior due to some area becoming unreachable or so). You can do that either by adding flags to the characters (you cannot do action X of the tree), or change the tree itself. In the latter case, it's probably wanted that you make a new copy of the tree for each character.

I mean it will affect the other characters while it shouldn't, how do I handle aborting the composites so it will not affect other characters?
not sure what this means, maybe it can be solved by adding information with each character?

I'm using C++, would you suggest that I add nodes as pointers or as variable (actor->addAction(Node* node) or actor->addAction(Node node)
I don't understand how this is relevant, either can be done, just make sure memory management is properly taken care of.

Maybe I could have a parameter inside run(), for example: NodeParams which is a structure that will contain the information such as elapsed time, a pointer to the entity, etc...?
Why does the tree need a pointer to the entity? The tree is just a decision tree structure, a sort-of paper phonebook. You don't put the phone into the phonebook do you? Why do you need the character that makes the decision inside the decision tree?

(You can have a good reason, but without it, I'd say do not connect things that do not need to be connected. It's much easier to pass some data into a call if you need some combination of data, than it is to separate two data items that you connected afterwards.)

Why does the tree need a pointer to the entity? The tree is just a decision tree structure, a sort-of paper phonebook. You don't put the phone into the phonebook do you? Why do you need the character that makes the decision inside the decision tree?

Lets say that I want to rotate an actor, actor->addAction(new rotateX(2.0f)), if the behavior tree don't know about the actor, how the actor will be rotated in that case?

I mean it will affect the other characters while it shouldn't, how do I handle aborting the composites so it will not affect other characters?

not sure what this means, maybe it can be solved by adding information with each character?

Lets say that I have a sequence that should be executed once, after it's executed, usually I set the sequence status to StatusAborted, so I remove it from the tree later since it was executed once and that's all I want, another possibility is that I could want to add rotation that will be done for 2 seconds and that's it.

An example would be adding action using actor->addAction(RotationX(20.0f, 2000.0f)) // Rotate 20.0f degree in 2000 milliseconds

This rotation should be done only once for 2000 milliseconds

After finishing RotationX node, I don't need it to be executed anymore so I usually remove it from the behavior tree, is that wrong? How it should be handled?

Lets say that I want to rotate an actor, actor->addAction(new rotateX(2.0f)), if the behavior tree don't know about the actor, how the actor will be rotated in that case?

Ok, one step back, how does the tree know to do any action to the actor at that specific moment in time?

I would think that happens when the actor finds itself out of actions, and asks the tree for a new action. That is, when "tree.addNewAction()" is executed by the actor, right?

In that case, can't you do "tree.addNewAction(this)" ? (where "this" is the actor object)

In other words, give the actor to the tree while the next action is being computed as a parameter of the call. That gives the tree access to the actor for adding an action, but there does not need to be a link to the actor within the tree itself.

After finishing RotationX node, I don't need it to be executed anymore so I usually remove it from the behavior tree, is that wrong? How it should be handled?

It's working isn't it? In that case, it's correct by definition.

Removing an obsolete action from the tree is one solution. Another solution is to add a query node before the turn, and ask the actor "are you turned?" first. If it says yes, skip the turn action, else perform it (where the actor then records being turned, of course).

I don't know which solution is better, both solutions are the best in some situations, and not the best in some other situations. Compare both solutions, and pick the one you think is better. You cannot do much more than that.

I have two characters and they both share the same behavior tree, in the tree I have a sequence as the following:

- WalkToDoor
- OpenDoor
- WaitUntilDoorIsOpen
- WalkInside
Each node above will store its current status inside a variable (m_status), so in the next frame the sequence will know which child to execute next
The problem now is that if both characters share the same behavior tree, one character could be currently in WaitUntilDoorIsOpen node (which means WalkToDoor and OpenDoor are done and m_status is now set to Success for both of them), this will cause a problem for the other character who expect that WalkToDoor m_status should be Running and NOT Success
In other words, If they both share the same behavior tree, there will be a conflict in the sequence nodes m_status, how do I fix this problem?

Ok, one step back, how does the tree know to do any action to the actor at that specific moment in time?

I would think that happens when the actor finds itself out of actions, and asks the tree for a new action. That is, when "tree.addNewAction()" is executed by the actor, right?

In that case, can't you do "tree.addNewAction(this)" ? (where "this" is the actor object)

In other words, give the actor to the tree while the next action is being computed as a parameter of the call. That gives the tree access to the actor for adding an action, but there does not need to be a link to the actor within the tree itself.

I'm not sure if I understand you correctly, but why not just pass the current actor using behaviorTree->update(DeltaTime, Actor* actor)? Then I could do something like this:


NodeStatus RotateZ::run(float DeltaTime, Actor* actor)
{
    actor->rotateZ(...);
    return StatusAbort; // Since I want it to run once, this function will always return abort status so the behavior tree remove the node
}

Of course, there are many possible solutions. However, a well-known vocabulary is associated with a well-known solution. It seems me that the meaningful distinction of different levels of tasks is ignored here. So I want to throw in some architectural thoughts. In summary: Do not mix up the layers, or you run into danger to end sooner or later in a maintenance chaos. I.e. the (more or less) simple modeling of a BT is lost, because e.g. low level motion tasks litter it.


Lets say that I want to rotate entity, actor->addAction(new rotateX(2.0f)), if the behavior tree don't know about the entity, how it will rotate the entity?

The BT should not do that at all. The behavior tree should drive behaviors. For example, the MeleeAttack behavior is selected. Let it be a sequential selector as follows. Its first sub-behavior is DrawMeleeWeapon which sets the DrawMeleeWeaponAction in the blackboard and returns as "running". The AnimationSubsystem picks up the action because it knows how to handle it, and hence drives the belonging animation. When done, it removes the action from the blackboard. The DrawMeleeWeapon behavior reacts by continuing with success. The next behavior in the sequence is ApporachSelectedEnemy. The selected enemy is given by TargetEntity, just another variable on the blackboard, set by a formerly executed part of the BT. The behavior sets the ApproachAction. That action is picked up by the SteeringSubsystem, which uses the MotionSubsystem to move the entity. The SteeringSubsystem considers the physics parameters of the entity to do so. And so on ...

The different tasks are separated into layers. Lower layers are controlled by higher layers, as usual. The blackboard allows the parts to work together without the need to know of each other.


I would think that happens when the actor finds itself out of actions, and asks the tree for a new action. That is, when "tree.addNewAction()" is executed by the actor, right?
In that case, can't you do "tree.addNewAction(this)" ? (where "this" is the actor object)
In other words, give the actor to the tree while the next action is being computed as a parameter of the call. That gives the tree access to the actor for adding an action, but there does not need to be a link to the actor within the tree itself.
The actor should not ask the tree for actions. The BT should control the actor, not the other way around. And it need to do so continuously. The BT must be able to react to changes in situations / circumstances, rethink and cancel current actions.
Also, the OP adds actions to the actor, while here actions are added to the BT. This isn't the same and needs clarification, because the former one are motion actions while the latter one are behavior actions.


In other words, If they both share the same behavior tree, there will be a conflict in the sequence nodes m_status, how do I fix this problem?
The solution is as follows: There is a shareable, constant part that represents the BT as itself, e.g. a BehaviorTree object. This is a resource like a texture or script. Attaching the BT to an actor means to instantiate a runtime object, e.g. an ActorBehavior object, that refers to the BehaviorTree resource and store the runtime dependent, variable part for exactly that actor.

@haegarr:

The solution is as follows: There is a shareable, constant part that represents the BT as itself, e.g. a BehaviorTree object. This is a resource like a texture or script. Attaching the BT to an actor means to instantiate a runtime object, e.g. an ActorBehavior object, that refers to the BehaviorTree resource and store the runtime dependent, variable part for exactly that actor.

The behavior tree sequence class structure that I have is similar to the following:


class Sequence
{
public:
      NodeStatus run()
      {
            // Code here to run children...
            m_status = childStatus;
            return m_status
      }
private:
       Status m_status;
};

I'm not exactly sure, how do I change the above code so I store the current node status inside ActorBehavior object?

The BT should not do that at all. The behavior tree should drive behaviors. For example, the MeleeAttack behavior is selected. Let it be a sequential selector as follows. Its first sub-behavior is DrawMeleeWeapon which sets the DrawMeleeWeaponAction in the blackboard and returns as "running"

So action class like the following is not done correctly and I should use the blackboard instead to tell the actor to move forward? If yes, why not just move the actor forward using actor->moveForward() inside MoveForwardAction::run()?


NodeStatus MoveForwardAction::run(float DeltaTime, Actor* actor)
{
     actor->moveForward();
     return StatusRunning;
}

.

The BT should not do that at all. The behavior tree should drive behaviors.

How do I execute a function like actor->addAction(new Sequence(new RotateX(2.0f), RotateZ(4.0f)))?


I'm not exactly sure, how do I change the above code so I store the current node status inside ActorBehavior object?

One way is to store the current path through the currently selected nodes in the BT, e.g. storing a pointer to as well as the state of each active node.


So action class like the following is not done correctly and I should use the blackboard instead to tell the actor to move forward? If yes, why not just move the actor forward using actor->moveForward() inside MoveForwardAction::run()?

Look at the overall process of running the different sub-systems during a cycle through the game loop. For example: First process input gathering, then process player control, then process NPC control (by some AI), then process physics simulation and initial animation step, then process collision detection, then collision resolution, then second step of animation, then rendering. Then start over. Letting optimizations like load distribution aside, all of the sub-systems process all belonging entities in each step before switching over to the next sub-system. This causes all entities to be in an expected state when a sub-system is run. E.g. all entities are moved into their current position before collision detection is done. Also, it is problematic that an NPC makes a decision when already a third of the entities has moved, but the other 2 third have not. And so on.

This separation makes writing sub-systems, well, possible at all in a long term run, because it allows them to be written without direct coupling. As a consequence, sub-systems at that level should not communicate directly with each other. Instead, the stored output of previously run sub-systems is used as input to the next ones. A blackboard is a software pattern that allows exactly that.


How do I execute a function like actor->addAction(new Sequence(new RotateX(2.0f), RotateZ(4.0f)))?

It depends. Why are those 2 rotations meaningful? From a behavior point of view, an action is like "turn to actor X", "move to location X", "draw melee weapon", and so on. The "turn to actor X" action, for example, is picked up by the motion sub-system when the game loop executes that sub-system. The motion system computes the turn angles from the current orientation and the desired one, perhaps considers some physics (e.g. something like "how fast can the actor turn"), and hence knows of a turn rate and duration. It runs the turn then for as many frames as prescribed by the duration. During these frames the "turn to actor X" action is alive, and hence the path through the BT down to the node that has set those action is active (the nodes down the path are all in the state "running"). Of course, the BT will inspect the situation in the meanwhile and may cancel the action gracefully, but usually it simply wait for the action to end due to the motion sub-system has turned the entity to the target direction.

This topic is closed to new replies.

Advertisement