That is not how you do do decisions in a behavior tree.
If I understand correctly, your tree has this logic:
while(true)
{
if (HP > 50%)
{
if (Visible enemy closer than 10m)
{
Aim Weapon
Fire Weapon
}
else
{
Move
}
}
else
{
Use Medkit
}
}
This is how you should express it:
selector
{
sequence
{
HP > 50%
selector
{
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
Move
}
}
Use Medkit
}
Let go over it, starting from the most inner part:
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
This is a sequence
, it will succeed if all its tasks succeed. Fail as soon as one fails. That means that if Visible enemy closer than 10m
fails, you don't run Aim Weapon
and Fire Weapon
. Similarly, if Aim Weapon
fails, you don't run Fire Weapon
.
Next is this:
selector
{
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
Move
}
This is a selector
, it will fail if all its tasks fail. Succeed as soon as one succeeds. That means that if the sequences we reviewed before succeeds you don't run Move
. That means that Move
only runs when Visible enemy closer than 10m
(or any other task of the sequence) fails.
Next up:
sequence
{
HP > 50%
selector
{
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
Move
}
}
Hopefully, you can see how this works… If HP > 50%
fails, you don't go into the selector.
Finally:
selector
{
sequence
{
HP > 50%
selector
{
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
Move
}
}
Use Medkit
}
As you can see, if the prior sequence succeeds (which implies that HP > 50%
succeeds), then you don't run Use Medkit
. In fact, Use Medkit
would only run in scenarios where HP > 50%
failed.
For example, suppose I have the following tree and the AI encounters an enemy that's closer than 10m, so he starts to aim his weapon. But then while he's aiming, his HP drops below 50%. I would want the agent to stop the aiming/firing sequence and move to the "use medkit" action.
You would have to add a task that fails the sequence, for example:
selector
{
sequence
{
HP > 50%
selector
{
sequence
{
Visible enemy closer than 10m
HP > 50%
Aim Weapon
Fire Weapon
}
Move
}
}
Use Medkit
}
Now, the agent checks its health before aiming, and if did drop below 50%, it will move… Wait, that is not what you want.
We can of course do this:
selector
{
sequence
{
HP > 50%
selector
{
sequence
{
Visible enemy closer than 10m
HP > 50%
Aim Weapon
Fire Weapon
}
sequence
{
HP > 50%
Move
}
}
}
Use Medkit
}
And we don't need to check health above, do we?
selector
{
selector
{
sequence
{
Visible enemy closer than 10m
HP > 50%
Aim Weapon
Fire Weapon
}
sequence
{
HP > 50%
Move
}
}
sequence
{
HP < 50%
Use Medkit
}
}
No need for nested selectors, either:
selector
{
sequence
{
Visible enemy closer than 10m
HP > 50%
Aim Weapon
Fire Weapon
}
sequence
{
HP > 50%
Move
}
sequence
{
HP < 50%
Use Medkit
}
}
This is an useful pattern: make a selector of sequences, where each sequence lists its preconditions.
Right, but, we don't want to repeat ourselves…
Take advantage of the order in the selector:
selector
{
sequence
{
HP < 50%
Use Medkit
}
sequence
{
Visible enemy closer than 10m
Aim Weapon
Fire Weapon
}
Move
}
As you can see, this agent will only Use Medkit
when HP < 50%
succeeds. And if that succeeds, it will not aim or move. In other words, it only aims or move given that HP < 50%
failed.
Right, but what if the health fell between aiming and firing?
There is no concept of aborting a tasks in behavior trees. Instead a task could translate to any code you want to run, and that code may fail. Thus, you could package the preconditions in the task.
It is a design principle of behavior trees to make tasks fast. If aiming takes time don't have the tree handle it.
We already have an idea of state: HP. Let us go further. We will have a target enemy.
selector
{
sequence
{
HP < 50%
Use Medkit
}
sequence
{
Has selected enemy
Aim at enemy
}
sequence
{
Has aimed enemy
Fire weapon
}
sequence
{
Find visible enemy closer than 10m
Set selected enemy
}
Move
}
Perhaps aiming does not have to exist in the tree. Instead, if there is an enemy selected, an animation subsystem would handle aiming at it (and might even do it while moving).
However, imagine aiming happens asynchronously… Or perhaps imagine it happens beyond the control of the tree, just like losing HP happens beyond the control of the tree. However, once the agent has aimed an enemy, the tree should decide to fire the weapon.