Oh, Behave (III) – Decorators

As we’ve seen before, composites let us build complex behavior trees by combining smaller ones and executing them in several different ways. Still, we’re in need of a way to add new features to existing behaviors, like inverting its result or execute it multiple times or until a given condition is met.

Modularity and reusability are key when working with behavior trees (actually, that applies to pretty much all in software engineer). Therefore, we should avoid reimplementing a behavior whenever possible. So the question is: how can we add these new features and maximize reusability at the same time?

That’s when decorators are added to the mix.

behave

Decorators

In contrast to composites, decorator behaviors have one and only one child behavior. Although technically doable, there is no gain in adding a decorator as leaf node to our tree, since their strength lies in modifying existing behaviors.

Decorator behaviors add new functionalities to their child behavior without necessarily knowing its internal execution. A decorator behavior may transform the result provided by their child behavior and/or change the way it’s executed in some other way.

For example, a common use for decorators is to repeat the execution of its child behavior until a given condition is satisfied, independently of what the child behavior actually does.

Available Decorators

At the time of this writing, the following decorators are already available in Crimild:

Repeat

A Repeat behavior will execute its child behavior multiple times. There are several variations of the Repeat behavior, implementing different looping conditions. For example, we might need to loop only a limited number of times or do so until the child behavior fails or succeeds.

If, for example, you need a character that should run from one location to another, you’ll probably be using a Repeat behavior along with a Sequence or Selector that moves the character and check if the destination has been reached in every loop. Such a behavior tree should like this:

RepeatUntilFail
  |-> Sequence
        |-> AwayFromTarget
        |-> Move

The behavior tree above will move a character (or any Node) until AwayFromTarget fails. When that happens, the Sequence behavior will fail too and RepeatUntilFail itself will terminate (you might be surprised to know that RepeatUntilFail will return a SUCCESS result even when the inner behaviors failed. That’s because the loop itself did succeeded).

Another use of a Repeat behavior is to just execute the same behavior over and over again, like and idle behavior.

Inverter

As the name suggests, an Inverter behavior will change the result of its child behavior once it has been executed. Then, if the child returns SUCCESS, the Inverter will return FAILURE, and vice versa.

Inverter behaviors are commonly used to check conditions inside a loop. The behavior tree shown earlier could be rewritten as follows:

RepeatUntilFail
  |-> Sequence
      |-> Inverter
            |-> ArrivedAtTarget
      |-> Move

Basically, move the character as long as we’re not at the target location. In this case, the Inverter behavior allows to reuse other behaviors. Otherwise, we might need to implement behaviors for all cases.

Succeeder

A Succeeder behavior always returns SUCCESS, disregarding of whether its child behavior returned SUCCESS or FAILURE. This behavior is useful when we need to branch the executing of a tree.

Please not that there is not “Failer” behavior since that can be accomplished by using an Inverter behavior on a Succeeder.

Other uses

Decorators are quite powerful and could radically change the execution of a branch in order behavior tree. Here are some other uses for Decorators:

  • Avoid executing the child node based on a predefined condition
  • Avoid executing a branch in our tree based on a timer
  • Deal with error status
  • Pause execution of a tree (like a breakpoint)

By combining Sequence and Decorator behaviors in our tree we can create really complex logics for virtually any logic we would like to achieve.

It’s show time!

Enough theory. The time has come for us to see behavior trees in action.

So, in the next episode, we’re going to create at a live demo to better understand how behavior trees work in practice, their strengths and limitations.

Stay tuned.

 

 

Advertisements

Crimild v4.8.0 is out!

Crimild v4.8.0 is already available for download here!!

This new version, which is the first one of 2018, includes many new features and improvements, paving the road for the next major release for Crimild (hopefully later this year).

Key Changes

Coding

Introduced in this new version is the new Coding system. By implementing the Codable interface, objects can be encoded to and decoded from many different formats, including binary (particularly useful for production builds), Lua scenes (that can be used for data-driven scene creation) and any other custom format you need.

Here’s how the Node class implements the Coding interface:

void Node::encode( coding::Encoder &encoder )
{
   Codable::encode( encoder );
 
   encoder.encode( "name", getName() );
   encoder.encode( "transformation", getLocal() );
   encoder.encode( "worldTransformation", getWorld() );
   encoder.encode( "worldIsCurrent", worldIsCurrent() );
 
   containers::Array< SharedPointer< NodeComponent >> cmps;
   for ( auto &it : _components ) {
     if ( it.second != nullptr ) {
       cmps.add( it.second );
     }
   }
   encoder.encode( "components", cmps );
}

void Node::decode( coding::Decoder &decoder )
{
   Codable::decode( decoder );
 
   std::string name;
   decoder.decode( "name", name );
   setName( name );
 
   decoder.decode( "transformation", local() );
   decoder.decode( "worldTransformation", world() );
 
   crimild::Bool worldIsCurrent = false;
   decoder.decode( "worldIsCurrent", worldIsCurrent );
   setWorldIsCurrent( worldIsCurrent );
 
   containers::Array< SharedPointer< NodeComponent >> cmps;
   decoder.decode( "components", cmps );
   cmps.each( [ this ]( SharedPointer< NodeComponent > &c, crimild::Size ) {
     attachComponent( c );
   });
}

The Coding system is an evolution of both the Stream system and the SceneBuilder pattern. Up until now, supporting loading a scene from a binary file or a Lua script required a lot of redundant code. The above code handles both formats (an any other Encoder/Decoder implementation) in a common interface.

Please note that the Stream, SceneBuilder, LuaSceneBuilder and all their related classes have been marked as “deprecated” and will be removed in later versions. 

Containers

The implementation of custom containers using the latest C++ features was long overdue. I’m happy to announce that included in this new version are working implementations for:

  • Array
  • List
  • Stack
  • Priority Queue
  • Map

The new containers support move semantics and traversing items by using lambda functions. In addition, they make use of the new ThreadModel policy to define how to handle concurrency.

Many new and existing systems already make use of these new containers. It is expected that the rest of the classes will be modified to work with these containers in later releases.

Please note that existing collections have been deprecated and will be removed in future versions.

Behaviors

Recently added to Crimild, behaviors are already a hot feature and are continuously being improved in each iteration. In this new version, all of the existing behaviors have been enhanced by supporting the new Coding system. No more individual builders and redundant code.

Scripting

With the introduction of the Coding system, most of the classes available in the crimild::scripting module have been rendered obsolete. The new version includes implementations for supporting both encoding and decoding scenes from Lua scripts and there is no need to maintain separated builders for the Core classes.

An much more…

Check out the complete Release Notes for this new version in Github!

Feel free to contact me if you have any issues or comments. Feedback is always welcomed!