Advertisement

A bit of help with a behaviortree tree.

Started by September 24, 2017 11:01 AM
1 comment, last by Kylotan 7 years, 2 months ago

I'm currently testing out behaviortree's with the LibGDX AI library. I have read a lot about behaviortree's past couple days but it's hard to get clear how I need to build the tree for my specific scenario. I'm looking to use a behaviortree for a Rimworld like game where the players units are not directly controllable. In the first place I'm wondering if I should have a single big tree for the complete AI or many smaller tree's, for example a separate tree for:

  • Moving an item
  • Building a building
  • Crafting an item
  • Resting when sleepy
  • Eating when hungry

In the examples I have seen they all talk about a "single job". Like entering a building, GoTo -> Open Door -> GoTo -> Close door. But what if I need to check if I have the keys on me? And I need to check a lot of these variables.

When A unit is Idle I'd like him to maintain his primary needs if he has access to them. If his needs are satisfied enough he can take on certain jobs like building walls or crafting items. I have a a lot of different jobs but jobs like building or crafting items are relatively the same with a different outcome so I could probably make a abstract job for that, it helps but I will still end up with a really huge tree though.

Another issue I'm facing is that when tasks are running, and something more important pops up (enemy spotted or some kind of emergence task) the unit should stop it's current task and act accordingly to the interruption. So since the task is running I need to do those checks on each runnable task then returned failed/cancelled and further down the sequence I need to do another check for these interruptions and handle them accordingly. I have briefly read into dynamic branches, not sure if GDX AI supports this but adding a behavior to the tree to handle an interruption seems a good idea. These dynamic branches also opens the opportunity to hold behaviors at the jobs and once a unit accepts a job it inserts that branch into it's own tree.

I hope I'm clear, it's hard to explain and get a global view of a complex behavior tree. I have read several times that behavior tree's are very easy to understand and implement. Well, that might be the case for those small tree's I find everywhere. On the other hand I might be over complicating things.

Generally, each character has everything in one behaviour tree, and that is responsible for choosing and executing whatever they are doing at that time. Some systems (e.g. Unreal) allow you to factor out subtrees into different files for convenience and modularity. It looks like LibGDX AI offers something like this too with the 'include' concept. But the top-level view is the same - one character is executing one tree at all times. My advice is to start with one single tree, and factor bits out later as necessary to keep it manageable.

Regarding checking conditions and decision making, it's sometimes easier to reframe the problem. A behaviour tree provides 2 key features:

  1. The ability to try a series of things in priority order until it finds one that it can carry out. (This is a Selector node.)
  2. The ability to perform several things in order until one fails. (This is a Sequence node.)

Additionally many systems offer some sort of Conditional node or Decorator system to add extra conditions on in a modular way.

Pretty much everything you want can be expressed by some arbitrary combination or nesting of the above (and often there are several alternative approaches). For example, for your "enter a building, but only if the character has keys", might start as a simple Sequence, GoTo -> Open Door -> GoTo -> Close door just you said. But you could add a Decorator on that sequence which is "Fail if doesn't have keys", which means the character would attempt the next action after that Sequence instead. (That could be a 'Find Keys' task of some sort.)

Regarding the concept of being 'idle', that basically refers to a low-priority state - and therefore that would be the last node of a Selector. It would only execute if more high priority nodes under that same Selector - e.g. a "Combat" node - fail (e.g. because you have a decorator that says 'Fail if there's no threat', or you simply make the Combat task fail if there's nothing to fight).

The need to be able to interrupt a task when circumstances change is obviously important. A naive behaviour tree system might evaluate the tree every frame/update in order to ensure that it's always up to date, and in such a system you would just use the conditionals or decorators as normal, and higher priority tasks would be picked in Selector nodes whenever they apply, as normal. However, many behaviour tree implementations don't want to re-evaluate the tree all the time as it's quite inefficient, and they ask you to explicitly specify the conditions under which the tree is re-evaluated when a task is ongoing. In the LibGDX AI system this appears to be the role of the 'dynamic guard selector'.

Note that each behaviour tree implementation is different, so it's often hard to translate a concept from one style of BT to another style until you're sufficiently familiar with both.

 

This topic is closed to new replies.

Advertisement