#include "Dog.h" #include <alive/TreeBuilder.h> #include <alive/engine/ConstantNode.h> #include <alive/tree/Parallel.h> #include <alive/tree/Repeat.h> #include <alive/tree/ErrorHandler.h>
This file sets up the dog entity by creating the Ogre 3D model, and setting up the AI logic. This particular implementation hardcodes the behavior tree in C++, using a type-safe templatized tree builder.
The behaviors are defined using modular trees that are parameterized, for example for turning or moving. On the high-level, the dog AI logic is broken down into bodily, active and passive behaviors.
#include "Dog.h" #include <alive/TreeBuilder.h> #include <alive/engine/ConstantNode.h> #include <alive/tree/Parallel.h> #include <alive/tree/Repeat.h> #include <alive/tree/ErrorHandler.h>
This is a helper function used to build a behavior tree procedurally based on the parameters provided. This provides locomotion & movement at the specified speed, gait type, and for a certain amount of time.
alive::Node* makeRandomMove(float time, char* type = "walk", float speed = 1.0f) { return alive::TreeBuilder() .composite<alive::ParallelNode>() .execute<ActionTranslate>() .Speed(speed) .end() .execute<ActionAnimate>() .Speed(1.0f) .Loop(true) .Name(type) .end() .execute<ActionWaitFor>() .Time(time) .Random(time) .end() .end(); }
This is another helper function to build a parametric behavior tree that plays a turning animation, and rotates the actor at the same time, for a specified duration.
alive::Node* makeRandomTurn(float time) { return alive::TreeBuilder() .composite<alive::SelectorProbability>() .Weight(1.0f) .composite<alive::ParallelNode>() .execute<ActionRotate>() .Speed(+1.0f) .end() .execute<ActionAnimate>() .Speed(1.0f) .Loop(true) .Name("turn left") .end() .execute<ActionWaitFor>() .Time(time) .Random(time) .end() .end() .Weight(1.0f) .composite<alive::ParallelNode>() .execute<ActionRotate>() .Speed(-1.0f) .end() .execute<ActionAnimate>() .Speed(1.0f) .Loop(true) .Name("turn right") .end() .execute<ActionWaitFor>() .Time(time) .Random(time) .end() .end() .end(); }
A helper function that creates a behavior tree to randomly select from a bunch of passive behaviors like sitting and lying down. Execute these animations once and bail out.
alive::Node* makePassive() { return alive::TreeBuilder() .composite<alive::SelectorProbability>() .Weight(1.0f) .execute<ActionAnimate>() .Speed(0.34f) .Loop(false) .Name("sitting") .end() .Weight(1.0f) .execute<ActionAnimate>() .Speed(0.67f) .Loop(false) .Name("laying idle") .end() .end(); }
Another helper function to create a behavior tree to randomly select active behaviors like walking around or chaising tail. Fail now and then also so the logic falls back to something else…
alive::Node* makeActive() { return alive::TreeBuilder() .decorator<alive::ErrorHandlerNode>() .composite<alive::SelectorProbability>() .Weight(1.0f) .node<alive::ConstantNode>() .TheTask(ERROR) .end() .Weight(0.25f) .execute<ActionAnimate>() .Speed(1.0f) .Loop(false) .Name("chasing tail") .end() .Weight(5.0f) .composite<alive::SequenceNode>() .add(makeRandomTurn(1.0f)) .end() .add(makeRandomMove(4.0f)) .end() .end() .end() .end(); }
A behavior tree that runs with high priority. Executes peeing behaviors, and sleeping for example. It also bails out with a high probability to force the top-level behavior to fall back to alternatives.
alive::Node* makeBodily() { return alive::TreeBuilder() .decorator<alive::ErrorHandlerNode>() .composite<alive::SelectorProbability>() .Weight(20.0f) .node<alive::ConstantNode>() .TheTask(ERROR) .end() .Weight(2.0f) .composite<alive::SequenceNode>() .execute<ActionAnimate>() .Speed(0.2f) .Loop(false) .Name("laying idle") .end() .execute<ActionAnimate>() .Speed(0.2f) .Loop(false) .Name("sleeping") .end() .end() .Weight(4.0f) .composite<alive::SequenceNode>() .add(makeRandomTurn(1.5f)) .end() .add(makeRandomMove(8.0f, "stalking prey", 0.5f)) .end() .execute<ActionAnimate>() .Speed(1.0f) .Loop(false) .Name("peeing") .end() .add(makeRandomTurn(1.5f)) .end() .add(makeRandomMove(6.0f, "jumpy run", 2.0f)) .end() .end() .Weight(4.0f) .composite<alive::SequenceNode>() .execute<ActionAnimate>() .Speed(1.0f) .Loop(false) .Name("scratching back") .end() .execute<ActionAnimate>() .Speed(1.0f) .Loop(false) .Name("scratching") .end() .end() .end() .end(); }
Set-up the different components of this game entity: model and behavior.
void Dog::init(Ogre::SceneManager& sceneManager) { Ogre::SceneNode *root = sceneManager.getRootSceneNode(); m_pDogNode = root->createChildSceneNode(Ogre::Vector3(0.f, 0.f, 0.f)); m_pDogEntity = sceneManager.createEntity("puppyEntity", "puppy_r1_ogre.mesh"); m_pDogNode->attachObject(m_pDogEntity); m_pDogEntity->setCastShadows(true); alive::Node* behave = alive::TreeBuilder() .decorator<alive::RepeatNode>() .composite<alive::SelectorNode>() .add(makeBodily()).end() .add(makeActive()).end() .add(makePassive()).end() .end() .end(); m_Brain.registry.setAddObserver(*new InitializeAction(*this)); m_Brain.add(*behave); }
There's no additional C++ logic to be executed at runtime. The behavior tree is wired up during initialization, and it deals with everything automatically now.
void Dog::update(const Ogre::FrameEvent& evt) { m_Brain.tick(); }
Each action is modular and only knows what it needs to know. Actions also have a single virtual extension point implemented using a visitor pattern, which can be used to implement the initialization.
This visitor essentially connects the data stored within the Dog actor and passes it to the actions on a need-to-know basis.
void InitializeAction::visit(ActionAnimate& action) { action.setup(*m_pEntity); } void InitializeAction::visit(SpatialAction& action) { action.setup(*m_pNode); } InitializeAction::InitializeAction(Dog& dog) : m_pEntity(dog.m_pDogEntity) , m_pNode(dog.m_pDogNode) { }