How to integrate behavior trees [using GdxAI] with ES?

Hi Friends

I want to use btrees from GdxAI for my game AI. I do not know where is the best place to ask this question, on libGdx forum or here ?
Let me just ask it here and hope for the help from nice guys :slight_smile:
To get the basic idea of how GdxAI is working please take a look at Behavior Trees Ā· libgdx/gdx-ai Wiki Ā· GitHub (Itā€™s afew pages)

So as an example, this is test for a Dog ai. We have afew dogs and each has an owner and they are loyal to their owners.

(image : GitHub - jsjolund/GdxDemo3D: 3D demo game built with libGDX)

dog.btree

import calculatePathToHuman:"com.mygdx.game.objects.dog.CalculatePathToHumanTask"
import calculatePathToStick:"com.mygdx.game.objects.dog.CalculatePathToStickTask"
import followPath:"com.mygdx.game.objects.dog.FollowPathTask"
import giveStick:"com.mygdx.game.objects.dog.GiveStickTask"
import lieDown:"com.mygdx.game.objects.dog.LieDownTask"
import pickUpStick:"com.mygdx.game.objects.dog.PickUpStickTask"
import piss:"com.mygdx.game.objects.dog.PissTask"
import setThrowButton:"com.mygdx.game.objects.dog.SetThrowButtonTask"
import sit:"com.mygdx.game.objects.dog.SitTask"
import spinAround:"com.mygdx.game.objects.dog.SpinAroundTask"
import spinAroundToFaceHuman:"com.mygdx.game.objects.dog.SpinAroundToFaceHumanTask"
import stand:"com.mygdx.game.objects.dog.StandTask"
import stickThrown?:"com.mygdx.game.objects.dog.StickThrownCondition"
import wander:"com.mygdx.game.objects.dog.WanderTask"
import whine:"com.mygdx.game.objects.dog.WhineTask"

import alreadyCriedForHumanDeath?:"com.mygdx.game.objects.dog.AlreadyCriedForHumanDeathCondition"
import isHumanDead?:"com.mygdx.game.objects.dog.IsHumanDeadCondition"
import isHumanInRange?:"com.mygdx.game.objects.dog.IsHumanInRangeCondition"
import humanWantToPlay?:"com.mygdx.game.objects.dog.HumanWantToPlayCondition"
import setAlreadyCriedForHumanDeath:"com.mygdx.game.objects.dog.SetAlreadyCriedForHumanDeathTask"


subtree name:"shouldFeelSadForHumanDeath?"
  sequence
    invert
      alreadyCriedForHumanDeath?
    isHumanDead?
    isHumanInRange? meters:20

subtree name:"feelSadForHumanDeath"
  sequence
    calculatePathToHuman
    followPath gait:"run"
    whine
    parallel policy:"selector"
      wait seconds:"uniform,10,25"
      lieDown
    whine
    parallel policy:"selector"
      wait seconds:"uniform,5,9"
      sit
    setAlreadyCriedForHumanDeath   # this makes the subtree's guard fail; the dog will start acting on his own

subtree name:"playWithMan"
  sequence
    calculatePathToHuman
    followPath gait:"run"
    giveStick
    spinAroundToFaceHuman
    setThrowButton enabled:true
    parallel policy:"selector" # wait for the man to throw the stick
      stickThrown?
      repeat
        sequence
          parallel policy:"selector"
            wait seconds:"uniform,3,5"
            randomSelector
              stand
              sit
          spinAround
    setThrowButton enabled:false
    calculatePathToStick
    followPath gait:"run"
    pickUpStick

subtree name:"actOnYourOwn"
  selector
    (random success:0.1) piss
    parallel policy:"selector"
      wait seconds:"uniform,3,6"
      randomSelector
        wander gait:"run"
        wander gait:"walk"
        lieDown
        sit

root
  dynamicGuardSelector
    ($shouldFeelSadForHumanDeath?) $feelSadForHumanDeath
    (humanWantToPlay?)             $playWithMan
    ()                             $actOnYourOwn  # fallback behavior (no guard)

This creates behavior tree from text file. First we import LeafTasks. You can take a look at them here

Then we gather them in a concrete behavior as a subtree.

Then at the very bottom

root
  dynamicGuardSelector
    ($shouldFeelSadForHumanDeath?) $feelSadForHumanDeath
    (humanWantToPlay?)             $playWithMan
    ()                             $actOnYourOwn  # fallback behavior (no guard)

we create root behavior which is running in a loop.
so as an example
($shouldFeelSadForHumanDeath?) $feelSadForHumanDeath

the firs part shouldFeelSadForHumanDeath? is a Guard for the feelSadForHumanDeath task which means it will be executed only if itā€™s guard task be successful.

and the last one
() $actOnYourOwn # fallback behavior (no guard)
which is a task with no guard and indicate a fallback behavior.

Here is the Class hierarchy for tasks

And should mention that tasks can accept a blackboard object (here DogCharacter) which is the entity the task is running on it.
Note that instead of using text file i also can create subtrees (branch tasks) programatically.

Sorry it is too long ā€¦

And i want to know what/how is the correct way to incorporate it with ES ? Any idea is very much appreciated.

I am considering to run AI on server.

So should i just create a DogState and load the the whole tree at once and update it from there ?

Or should i break the tree and create separate states (systems) for each behavior (subtrees)
for example create one state which create and run/update this behavior ($shouldFeelSadForHumanDeath?) $feelSadForHumanDeath
and another one for (humanWantToPlay?) $playWithMan ?

As far I know @methusalah did use this library wit Zay ES.
I personally would go with one AI system. At least I would start with one system and see.

1 Like

I also want to first start with this.
But it feels a bit like an OOP design than an ES design? :thinking::rolling_eyes:

Ah, I think i found the example https://github.com/methusalah/alchemist/tree/e6779e0cd9beaf53104afc77f51c710fb53ed988/sampleProject/src/main/java/logic/AI

I will take a look to see how he is doing there.

Thank you.

But this GdxAI hidde this behin this description language. So I would have an AI component with all this sit, spinAround, piss, getStick commands as string. So for example new Ai(ā€œpissā€) and you Ai system knows what to do.

At least I would start that way and see if it is enough or if you need more flexiblity. So no this those not feel like OO.

Note: I donā€™t know GdxAI. I planed to have a closer look but so far I could life without that :grinning:

1 Like

Here is the general approach I would take for pretty much any new systemā€¦

Start with one system.

  1. Decide what components will indicate that an entity belongs to that system. It could be a single marker component.

  2. Think about what information the other systems might need from this system (usually none, really, if you are adding this to existing working code).

  3. Think about what information the other systems might need to provide to this system. (Also usually none if you are adding this to an existing working game).

  4. Go back to the first decision on how you select items to be in the system in case the last thing makes that simpler.

  5. If some part of this system feels like it would be useful separated then think about separating it. (not often)

In this case, for the AI:

  1. make a self-contained AI system that selects on some sort of AiActive() component or AiBehavior(ā€œname of root treeā€) or some such.
  2. outwardly you will probably need to move/control a ā€˜mobā€™ā€¦ but you can do this through the same sort of control drivers that the player does, really. You may only have to add and update a MovementStates component to your entity. Nothing else needs to change.
  3. input is just the world state. The AI systems job will be to provide this world state in some way whether giving the behavior trees access to where other mobs are, what the walk mesh looks like, whatever. No additional components should be needed for this, probably.
  4. Nopeā€¦ root behavior selection is probably the only component needed at this point.
  5. Nopeā€¦ all of it is self-contained.

Itā€™s not OOPā€¦ itā€™s proper system encapsulation and the benefit of an ES. A real OOP approach, youā€™d have touched almost every class in your whole game just to add AI. Here weā€™ve created one new system and its own supporting classes. Nothing else needs to care we did that.

Edit: Note: it may end up being desirable to publish additional components just to expose the state of the behavior but this is really just for debugging your app. If you have another system selecting on these components then you should be very suspicious.

2 Likes

This approach have been very basic but efficient. I added an AI component that only store the behavior tree path, plus a dedicated system that reacts to the presence of this component :

  • it launches the behavior tree when component is added
  • it updates the tree each tick

ECS is not involved in the process from this point.

Iā€™ve met only one issue : tasks and decorators continuously presume the presence of needed components on the AI entity, which seems to be bad design and require tons of assertions (or null pointer exceptions !). I assume there is a better design hidding in there.

1 Like

I think if the system contains OO it is totally ok. The system just shall hidde the complexity. I learned that the component only knows the pure data like radius of collision shape, the position x,y,z, the model name but never holds a real object like the collision shape object, the model object. If I respect this rule plus some more then I never end in a corner, at least I could not manage that so far with the ES approach. With OO it was that I reached this hack myself in the corner very very early and spent endless hours on refactoring. Refactoring is pain in the ass when you go with TDD really.

Iā€™m loving ā€œPissTaskā€ btw

2 Likes

Are they otherwise optional sometimes? If not then you can select for them specifically.

Alternately, you can keep several entity sets around that select for them and then use those sets to update some per-character stateā€¦ youā€™d still have to check for null on the optional components. Really no way around that, I guessā€¦ but it would give you the option of having default values, too.

1 Like

Seems like you may have accidentally removed your other good answer to respond to mineā€¦ at least according to the edit.

Thanks from ALL of you guys.
Paul thanks for those steps. I printed them on a paper and stuck it on my desk and will take a look every time defining a system.

I cloned alchemist from github and going to also take a look at alchemist/sampleProject.

Will ask my question if i have any.

What theā€¦? I need to sleep, obviously. Here it is again :

I would say no. The ā€œaimā€ task will always need a ā€œsightā€ component, for exemple. Sometimes itā€™s relevant to create a default component if the needed one is missing. But in general, I end up throwing an exception/alert saying that the component is missing.
And I confirm that Entity prefabs are usefull for that (and many other things). In Alchemist, I integrated it on the framework level as the blueprints.

I just reverted your other message to the previous version where you were responding to Ali_RS. I figured you didnā€™t really want to delete that.

2 Likes

The sample project helped a lot. The SightProc made logic clear. Which is of category sense systems and regularly checks for all watchers and watchables and set the result (all watchables seen by watcher) as a component on watcher entity, so the corresponding AI task IsEnemyInSight will check that component to see what entities can be seen by this entity. Previously i was wrongly thinking to do this sensing process inside the task itself ā€¦ but i can now see that actually i was needing a new system which I am calling it SightingState, the other one is HearingState, ā€¦

This made the path clear for me for What system do i need create and the next step is How should i create it which Paul already answered this by clarifying the steps in his post.
Thatā€™s it, Zachman framework :stuck_out_tongue: :slight_smile: (Just kiddingā€¦)
And I do appreciate from all you friends for helping me.