It seems like behavior trees are the way to go to control my animals. I see some interesting articles outlining their logic and structure.
Has anyone implemented behavior trees? I see there are java, c++ and python libraries.
It seems like behavior trees are the way to go to control my animals. I see some interesting articles outlining their logic and structure.
Has anyone implemented behavior trees? I see there are java, c++ and python libraries.
I have the basics of a behavior tree working to make the pig behave as I had it doing in pascal based logic. Swim up if under water. Swim forward until you reach land if in water. Otherwise walk forward. This uses the sequence and selector nodes as described in the first link. I find it hard to think in this âtree logicâ way, but it seems like it will work well, especially for spreading looped functionality between ticks. I will clean up the code and post the basics.
If anybody is interested in such things, here are functioning baseclasses for a rudimentary Behavior Tree based primarily on the information in the first link of my initial post.
unit BehaviorTree;
{$mode ObjFPC}{$H+}
{ Base Behavior Tree Classes
rudimentary level implementation }
{ based on ideas from primarily the first of these links }
{ https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work#close-modal }
{ https://robohub.org/introduction-to-behavior-trees/ }
{ Erik Donovan Johnson [email protected]
Free to use and modify in any way you want.
Example code. No warranty. Use at your own risk. }
interface
uses Classes;
{ behavior status values }
const behavior_notrun = 0;
behavior_running = 1;
behavior_success = 2;
behavior_fail = 3;
type TBehaviorStatus = integer;
TBehaviorNode = class; { forward }
{ low level stack }
TBehaviorDataStack = class { for storing stack }
procedure Push(item : tbehaviornode);
function Pop : tbehaviornode;
private
stack : array of tbehaviornode;
end;
{ manages the running, data and active state of a behavior tree }
TBehaviorRunner = class; { forward }
{ root behavior tree class that everything derives from }
TBehaviorNode = class
laststatus : TBehaviorStatus;
constructor create;
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; virtual;
procedure reset; dynamic;
end;
{ single child behavior node that can modify the results of that child in subclasses }
TBehaviorDecorator = class( TBehaviorNode )
child : TBehaviorNode;
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
procedure reset; override;
end;
{ multichild behavior node. how those children are processed depends on subclasses }
TBehaviorComposite = class( TBehaviorNode )
childindex : integer;
children : array of TBehaviorNode;
constructor create;
destructor destroy; override;
procedure reset; override;
procedure add( item : TBehaviorNode );
end;
{ sublass this to make checks and actions in overloaded Run methods }
TBehaviorLeaf = class( TBehaviorNode )
end;
{ composite nodes }
{ Sequence : visit each child in order, starting with the first, and when that
succeeds will call the second, and so on down the list of children.
If any child fails it will immediately return failure to the parent. If the
last child in the sequence succeeds, then the sequence will return success to
its parent. }
TBehaviorSequence = class( TBehaviorComposite )
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
end;
{ Selector : Unlike sequence which is AND, requiring all children to succeed to
return a success, a selector will return a success if any of its children
succeed and not process any further children. It will process the first child,
and if it fails will process the second, and if that fails will process the third,
until a success is reached, at which point it will instantly return success.
It will fail if all children fail. This means a selector is analagous with an
OR gate, and as a conditional statement can be used to check multiple conditions
to see if any one of them is true. }
TBehaviorSelector = class( TBehaviorComposite )
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
end;
{ keep track of active behavior node and other info (in subclass) }
TBehaviorRunner = class
RootNode : TBehaviorNode;
ActiveNode : TBehaviorNode;
constructor create( iRootNode : TBehaviorNode );
destructor destroy; override;
function RunNext( secondspassed : single ) : TBehaviorStatus;
{ all Run calls must call this when finished with a success or failure to update the active node from the stack }
procedure ActiveNodeFinishedRun;
private
DataStack : TBehaviorDataStack;
end;
implementation //==============================================================
procedure TBehaviorDataStack.push( item : tbehaviornode );
var l : integer;
begin
l := length( stack );
setlength(stack, l + 1 );
stack[l] := item;
end;
function TBehaviorDataStack.Pop : tbehaviornode;
var l : integer;
begin
result := nil;
l := length( stack );
if l > 0 then
begin
dec( l );
result := stack[l];
setlength( stack, l );
end;
end;
//--------------------------------
constructor TBehaviorNode.create;
begin
laststatus := behavior_notrun;
end;
procedure TBehaviorNode.reset;
begin
laststatus := behavior_notrun;
end;
function TBehaviorNode.Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
begin
result := behavior_running;
end;
//--------------------------------
function TBehaviorDecorator.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
begin
result := inherited;
if assigned( child ) then
begin
runner.datastack.push( self ); { push this node as parent for the child, it will pop when done }
runner.activenode := child;
laststatus := child.run( runner, secondspassed );
end;
if laststatus <> behavior_running then { leaves active at deepest child that is running }
runner.ActiveNodeFinishedRun;
end;
procedure TBehaviorDecorator.reset;
begin
inherited;
if assigned( child ) then
child.reset;
end;
//--------------------------------
constructor TBehaviorComposite.create;
begin
inherited;
childindex := 0;
end;
destructor TBehaviorComposite.destroy;
var i : integer;
begin
for i := 0 to length( children ) - 1 do
children[i].free;
setlength( children, 0 );
end;
procedure TBehaviorComposite.reset;
var i : integer;
begin
inherited;
childindex := 0;
for i := 0 to length( children ) - 1 do
children[i].reset;
end;
procedure TBehaviorComposite.add( item : TBehaviorNode );
var l : integer;
begin
l := length( children );
setlength( children, l + 1 );
children[l] := item;
end;
//--------------------------------
function TBehaviorSequence.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
{ will run one child per tick, finished with first child that is a fail }
var childcount, i : integer;
child : TBehaviorNode;
childstatus : integer;
childix : integer;
begin
if laststatus <= behavior_running then
begin
result := inherited;
runner.activenode := self;
childcount := length( children );
if childindex < childcount then
begin
runner.datastack.push( self ); { push this node as parent for the child, it will pop when done }
child := children[childindex];
runner.activenode := child;
ChildStatus := child.Run( runner, secondspassed );
laststatus := childstatus; { running will continue waiting for child, fail will halt iteration with fail }
if childstatus = behavior_success then
begin
inc( childindex );
if childindex < childcount then
laststatus := behavior_running; { keep looping until fail or finished }
end;
end
else
laststatus := behavior_fail;
if laststatus <> behavior_running then { leaves active at deepest child that is running }
runner.ActiveNodeFinishedRun;
end;
result := laststatus;
end;
//--------------------------------
function TBehaviorSelector.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
{ will run one child per tick, finished with first child that is a success }
var childcount, i : integer;
child : TBehaviorNode;
childstatus : integer;
begin
if laststatus <= behavior_running then
begin
result := inherited;
runner.activenode := self;
childcount := length( children );
if childindex < childcount then
begin
runner.datastack.push( self ); { push this node as parent for the child, it will pop when done }
child := children[childindex];
runner.activenode := child;
ChildStatus := child.Run( runner, secondspassed );
laststatus := childstatus; { running will continue waiting for child, success will halt iteration with success }
if childstatus = behavior_fail then
begin
inc( childindex );
if childindex < childcount then
laststatus := behavior_running; { keep looping until success or finished }
end;
end
else
laststatus := behavior_fail;
end;
if laststatus <> behavior_running then { leaves active at deepest child that is running }
runner.ActiveNodeFinishedRun;
result := laststatus;
end;
//------------------------------------
constructor TBehaviorRunner.create( iRootNode : TBehaviorNode );
begin
{ unowned references to externally owned nodes }
RootNode := iRootNode;
ActiveNode := RootNode;
{ owned datastack of unowned references to externally owned nodes }
DataStack := TBehaviorDataStack.create;
end;
destructor TBehaviorRunner.destroy;
begin
DataStack.Free;
end;
function TBehaviorRunner.RunNext( secondspassed : single ) : TBehaviorStatus;
begin
result := rootnode.laststatus;
if result <= behavior_running then
begin
if not assigned( activenode ) then
activenode := rootnode;
result := activenode.run( self, secondspassed ); { active node will change the the deepest running child }
if result <> behavior_running then
activenode := nil;
end;
end;
procedure TBehaviorRunner.ActiveNodeFinishedRun;
begin
activenode := DataStack.pop; { set active node to prior node in stack after success or fail }
end;
end.
Then my simple pig behavior is set as follows using Behavior leaf subclasses. When it finishes, success or fail, it loops.
function initbehaviors : TBehaviorNode;
var underwater: TBehaviorSequence;
inwater : TBehaviorSequence;
_or : TBehaviorSelector;
begin
underwater := TBehaviorSequence.create;
underwater.add( TBehavior_AmIUnderWater.create );
underwater.add( TBehavior_SwimUp.create );
inwater := TBehaviorSequence.create;
inwater.add( TBehavior_AmIInWater.create );
inwater.add( TBehavior_Swim.create );
_or := TBehaviorSelector.create;
_or.add( underwater );
_or.add( inwater );
_or.add( TBehavior_moveonland_obstaclecheck.create );
result := _or;
end;
and then tie it to my castle game engine object with the TBehaviorRunner class which holds state for the behaviors. Then every frame I can call a RunNext(secondspassed)
which will run the next active thing in the behavior tree.
Thank you for sharing!
Personally I did actually implement behavior trees long time ago in C#, for a Unity project in a company I worked for ~10 years ago. I found them a very useful concept, I felt behavior trees nicely allow to express various creature logic using typical OOP concepts. It was a good decision to use them, behavior trees allowed to easily adapt code to various experiments on the game design side â e.g. maybe some creature can do X before doing Y, maybe something could do Z from time to time⊠behavior trees allowed to express it nicely, without turning the code into a hard-to-maintain mess with giant âcase / switchâ statement
It see the same benefits in your above code, cool. I.e. reading initbehaviors
implementation is very easy, it is visible at a glance how pigs behave (and how to tweak it).
I plan to implement behavior trees in Pascal for creature logic in planned TCastleMoveAttack component (code in castle-engine/src/transform/castlebehaviors_moveattack.inc at master · castle-engine/castle-engine · GitHub , but it is commented out and doesnât compile yet; and it doesnât use behavior trees yet). The idea is to have a ready-to-use component to drive typical creatures in games, useful e.g. for examples/fps_game
. Exposing behavior trees will allow to customize the behavior nicely,
I cleaned it up more and got rid of the unneeeded laststatus
and childindex
, the latter I now store on the stack. This way a behavior tree can be shared between creatures as it doesnât store any data about the individual instance, that is all in the behavior runner class. the data stack is now its own unit allowing stacking the parent node, the child index and now also the distance to move on land.
unit basedata;
{$mode ObjFPC}{$H+}
interface
uses
Classes, SysUtils;
type
TBehaviorData = class
private
procedure setinteger( ivalue : integer ); virtual;
function getinteger : integer; virtual;
procedure setsingle( ivalue : single ); virtual;
function getsingle : single; virtual;
public
property asinteger : integer read getinteger write setinteger;
property assingle : single read getsingle write setsingle;
end;
TBehaviorInt = class( TBehaviorData )
constructor create( ivalue : integer );
private
value : integer;
procedure setinteger( ivalue : integer ); override;
function getinteger : integer; override;
end;
TBehaviorSingle = class( TBehaviorData )
constructor create( ivalue : single );
private
value : single;
procedure setsingle( ivalue : single ); override;
function getsingle : single; override;
end;
TBehaviorDataStack = class { for storing data }
destructor destroy; override;
procedure Push(item : tobject);
function Pop : tobject;
function IsEmpty : boolean;
procedure pushint( i : integer );
function popint : integer;
function peekint : integer; { look at what would have popped without popping }
procedure pushsingle( i : single );
function popsingle : single;
function peeksingle : single; { look at what would have popped without popping }
private
stack : array of tobject;
end;
implementation
procedure TBehaviorData.setinteger( ivalue : integer );
begin
end;
function TBehaviorData.getinteger : integer;
begin
result := 0;
end;
procedure TBehaviorData.setsingle( ivalue : single );
begin
end;
function TBehaviorData.getsingle : single;
begin
result := 0;
end;
constructor TBehaviorInt.create( ivalue : integer );
begin
value := ivalue;
end;
procedure TBehaviorInt.setinteger( ivalue : integer );
begin
value := ivalue;
end;
function TBehaviorInt.getinteger : integer;
begin
result := value;
end;
constructor TBehaviorSingle.create( ivalue : single );
begin
value := iValue;
end;
procedure TBehaviorSingle.setsingle( ivalue : single );
begin
value := ivalue;
end;
function TBehaviorSingle.getsingle : single;
begin
result := value;
end;
//-------------------------------
destructor TBehaviorDataStack.destroy;
var i : integer;
item : TObject;
begin
for i := 0 to length( stack ) - 1 do
begin
item := stack[i];
if Item is TBehaviorData then
Item.Free;
end;
end;
procedure TBehaviorDataStack.Push(item : tobject);
var l : integer;
begin
l := length( stack );
setlength(stack, l + 1 );
stack[l] := item;
end;
function TBehaviorDataStack.Pop : tobject;
var l : integer;
begin
result := nil;
l := length( stack );
if l > 0 then
begin
dec( l );
result := stack[l];
setlength( stack, l );
end;
end;
function TBehaviorDataStack.IsEmpty : boolean;
begin
result := length( stack ) > 0;
end;
procedure TBehaviorDataStack.pushint( i : integer );
begin
push( TBehaviorInt.create( i ));
end;
function TBehaviorDataStack.popint : integer;
var item : tobject;
begin
result := 0;
item := tobject( pop );
if assigned( item ) then
begin
assert( item is TBehaviorInt ); { enforced types to be safer }
result := TBehaviorData( item ).asinteger;
item.free;
end;
end;
function TBehaviorDataStack.peekint : integer; { look at what would have popped without popping }
var l : integer;
item : tobject;
begin
result := 0;
l := length( stack );
if l > 0 then
begin
dec( l );
item := stack[l];
if item is TBehaviorInt then
result := TBehaviorData( item ).asinteger;
end;
end;
procedure TBehaviorDataStack.pushsingle( i : single );
begin
push( TBehaviorSingle.create( i ));
end;
function TBehaviorDataStack.popsingle : single;
var item : tobject;
begin
result := 0;
item := tobject( pop );
if assigned( item ) then
begin
assert( item is TBehaviorSingle ); { enforced types to be safer }
result := TBehaviorData( item ).assingle;
item.free;
end;
end;
function TBehaviorDataStack.peeksingle : single; { look at what would have popped without popping }
var l : integer;
item : tobject;
begin
result := 0;
l := length( stack );
if l > 0 then
begin
dec( l );
item := stack[l];
if item is TBehaviorSingle then
result := TBehaviorData( item ).assingle;
end;
end;
end.
and then the main unit
unit BehaviorTree;
{$mode ObjFPC}{$H+}
{ Base Behavior Tree Classes
rudimentary level implementation }
{ based on ideas from primarily the first of these links }
{ https://www.gamedeveloper.com/programming/behavior-trees-for-ai-how-they-work#close-modal }
{ https://robohub.org/introduction-to-behavior-trees/ }
{ Erik Donovan Johnson [email protected]
Free to use and modify in any way you want.
Example code. No warranty. Use at your own risk. }
{ TBehaviorRunner keeps track of active node so doesn't have to traverse to find it.
Designed so there a behavior tree can be shared by many
objects without taking more data than the TBehaviorRunner and its data stack per object. }
interface
uses Classes,
basedata;
{ behavior status values }
const behavior_notrun = 0; { not used }
behavior_running = 1;
behavior_success = 2;
behavior_fail = 3;
type TBehaviorStatus = integer;
TBehaviorNode = class; { forward }
{ manages the running, data and active state of a behavior tree }
TBehaviorRunner = class; { forward }
{ root behavior tree class that everything derives from }
TBehaviorNode = class
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; virtual; abstract;
end;
{ single child behavior node that can modify the results of that child in subclasses }
TBehaviorDecorator = class( TBehaviorNode )
child : TBehaviorNode;
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
end;
{ multichild behavior node. how those children are processed depends on subclasses }
TBehaviorComposite = class( TBehaviorNode )
children : array of TBehaviorNode;
constructor create;
destructor destroy; override;
procedure add( item : TBehaviorNode );
function RunNextChild( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
end;
{ sublass this to make checks and actions in overloaded Run methods }
TBehaviorLeaf = class( TBehaviorNode )
end;
{ composite nodes }
{ Sequence : visit each child in order, starting with the first, and when that
succeeds will call the second, and so on down the list of children.
If any child fails it will immediately return failure to the parent. If the
last child in the sequence succeeds, then the sequence will return success to
its parent. }
TBehaviorSequence = class( TBehaviorComposite )
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
end;
{ Selector : Unlike sequence which is AND, requiring all children to succeed to
return a success, a selector will return a success if any of its children
succeed and not process any further children. It will process the first child,
and if it fails will process the second, and if that fails will process the third,
until a success is reached, at which point it will instantly return success.
It will fail if all children fail. This means a selector is analagous with an
OR gate, and as a conditional statement can be used to check multiple conditions
to see if any one of them is true. Some people call this a Fallback. }
TBehaviorSelector = class( TBehaviorComposite )
function Run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus; override;
end;
{ keep track of active behavior node and other info (in subclass) }
TBehaviorRunner = class
RootNode : TBehaviorNode;
ActiveNode : TBehaviorNode;
DataStack : TBehaviorDataStack;
constructor create( iRootNode : TBehaviorNode );
destructor destroy; override;
function RunTick( secondspassed : single ) : TBehaviorStatus;
{ all Run calls must call this when finished with a current status to update the active node from the stack as needed }
procedure UpdateActiveRunStatus( istatus : TBehaviorStatus );
end;
implementation //==============================================================
function TBehaviorDecorator.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
begin
if assigned( child ) then
begin
runner.datastack.push( self ); { push this node as parent for the child, it will pop when done }
runner.activenode := child;
result := child.run( runner, secondspassed );
end
else
result := behavior_fail;
runner.UpdateActiveRunStatus( result );
end;
//--------------------------------
constructor TBehaviorComposite.create;
begin
inherited;
end;
destructor TBehaviorComposite.destroy;
var i : integer;
begin
for i := 0 to length( children ) - 1 do
children[i].free;
setlength( children, 0 );
end;
procedure TBehaviorComposite.add( item : TBehaviorNode );
var l : integer;
begin
l := length( children );
setlength( children, l + 1 );
children[l] := item;
end;
function TBehaviorComposite.RunNextChild( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
var childcount, childix : integer;
child : TBehaviorNode;
begin
childcount := length( children );
childix := runner.datastack.peekint; { peek for childix on stack }
if childix < childcount then
begin
if childix = 0 then
runner.datastack.pushint( 0 ); { push 0 since it wasn't actually stored }
runner.datastack.push( self ); { push this node as parent for the child, it will pop when done }
child := children[childix];
runner.activenode := child;
result := child.Run( runner, secondspassed );
end
else
begin
assert( false ); { this should never happen }
result := behavior_fail;
end;
end;
//--------------------------------
function TBehaviorSequence.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
{ will run one child per tick, finished with first child that is a fail }
var childcount : integer;
childix : integer;
begin
runner.activenode := self;
childcount := length( children );
childix := runner.datastack.peekint; { peek for childix on stack }
if childix < childcount then
begin
result := RunNextChild( runner, secondspassed );
{ running will continue waiting for child, fail will halt iteration with fail }
case result of
behavior_success :
begin
childix := runner.datastack.popint;
inc( childix );
if childix < childcount then
begin
runner.datastack.pushint(childix); { add child ix back to stack }
result := behavior_running; { keep looping until fail or finished }
end;
end;
behavior_fail : runner.datastack.popint;
behavior_running :;
end;
end
else
result := behavior_fail;
runner.UpdateActiveRunStatus( result );
end;
//--------------------------------
function TBehaviorSelector.run( runner : TBehaviorRunner;
secondspassed : single ) : TBehaviorStatus;
{ will run one child per tick, finished with first child that is a success }
var childcount : integer;
childix : integer;
begin
runner.activenode := self;
childcount := length( children );
childix := runner.datastack.peekint; { peek for childix on stack }
if childix < childcount then
begin
result := RunNextChild( runner, secondspassed );
{ running will continue waiting for child, success will halt iteration with success }
case result of
behavior_fail :
begin
childix := runner.datastack.popint;
inc( childix );
if childix < childcount then
begin
runner.datastack.pushint(childix); { add child ix back to stack }
result := behavior_running; { keep looping until success or finished }
end;
end;
behavior_success : runner.datastack.popint;
behavior_running :;
end;
end
else
result := behavior_fail;
runner.UpdateActiveRunStatus( result );
end;
//------------------------------------
constructor TBehaviorRunner.create( iRootNode : TBehaviorNode );
begin
{ unowned references to externally owned nodes }
RootNode := iRootNode;
ActiveNode := RootNode;
{ owned datastack of unowned references to externally owned nodes }
DataStack := TBehaviorDataStack.create;
end;
destructor TBehaviorRunner.destroy;
begin
DataStack.Free;
end;
function TBehaviorRunner.RunTick( secondspassed : single ) : TBehaviorStatus;
begin
if not assigned( activenode ) then
activenode := rootnode;
result := activenode.run( self, secondspassed ); { active node will change tthe the deepest running child }
if result <> behavior_running then
begin
repeat until DataStack.Pop = nil; { clear stack even though it should already be clear }
activenode := nil;
end;
end;
procedure TBehaviorRunner.UpdateActiveRunStatus( istatus : TBehaviorStatus );
begin
if istatus <> behavior_running then
begin
assert( DataStack.peekint = 0 );
assert( DataStack.peeksingle = 0 );
activenode := TBehaviorNode( DataStack.pop ); { set active node to prior node in stack after success or fail }
end;
end;
end.
I shared this, and icosphere (and my x3dtools that it now uses) in a public repo at GitHub - xistext/edjlib: Code I have shared on Castle Game Engine Forum.
I also added GPC.pas General Polygon Clipper. I ported to Delphi long long ago and used for decades in aviation software so it is solid. Uses all doubles, so not perfect for CGE but I still find it useful. It could be converted with some effort to use TVertex2. Lets you combine 2d polygons with add and subtract to create complex polygons with holes and islands, etc. It was the basis for the water layer in the gps software I wrote. I use it now to find out if my curved road shape 2d outline polygons intersects the 2d outline polygons of other roads or buildings in my current game.
Thank you! I updated Additional Components | Castle Game Engine to point to your repo:)
This behavior tree code is flawed. It fails to unwind the stack for nested composite nodes, so then fails to hit subsequent children as it starts returning statuses back up the tree. A fundamental flaw in my logic that doesnât have a clear solution yet⊠So nobody should waste any time on this yet.
I have that problem solved and have updated the code on the repo. Part of my problem is that the flow of the tree is counterintuitive to me so my little behavior tree in initbehaviors was defined wrong for what I wanted to occur. I found this free online behavior tree visualizer helpful ⊠Behavior Tree Visual Editor.
Now my pig UnderWater? Swimup. InWater? Swim to shore, walk a little and then wiggle (presumably to dry) works. I added a name field to the node to make debugging easier. It is defined like this
underwater := TBehaviorSequence.create2( TBehavior_AmIUnderWater.create, { underwater? }
TBehavior_SwimUp.create ); { swim up }
underwater.name := 'underwater';
inwater := TBehaviorSequence.create2( TBehavior_AmIInWater.create, { in water? }
TBehavior_Swim.create ); { swim to land }
inwater.Add( TBehavior_moveonland_obstaclecheck.create ); { move 5 units on land }
inwater.Add( TBehavior_Wiggle.create ); { wiggle to dry }
inwater.name := 'inwater';
_and := TBehaviorSequence.create2( underwater, inwater );
result := _and;
It is working great. Pig ran in circles all night long, swimming and wiggling to dry off as needed. 901699 ticks ran. My tree is getting a little bigger. I reworked some of the leaf functions to play nicer with the logic the tree sorta forces you to do.
abovewater := TBehaviorSelector.create2( TBehavior_AmIAboveWater.create, { above water | swim up }
TBehavior_SwimUp.create,
'abovewater' );
righttoland := TBehaviorSequence.create2( TBehavior_RightIsBetterForLand.create, { land is closer to right? }
TBehavior_SwimTurn.create( true ), { swim turn right }
'righttoland' );
righttoland := TBehavior_Forcefail.Create( righttoland ); { whether or not we turned, force fail so we continue seletor}
dry := TBehaviorSequence.create2( TBehavior_moveonland_obstaclecheck.create, { move 1 unit on land }
TBehavior_Wiggle.create, 'dry off' ); { wiggle to dry }
outofwater := TBehaviorSelector.create2( TBehavior_AmIOutOfWater.create, { in water? }
righttoland, { if land is closer to right, turn right }
'outofwater' );
outofwater.add(TBehavior_Inverter.Create( TBehavior_Swim.create(1) )); { swim 1 unit success if land or complete so will continue behavior cycle }
outofwater.add( dry );
safefromwater := TBehaviorSequence.create2( abovewater, outofwater, 'safefromwater' );
{ walk in circle }
defaultbehavior := TBehaviorSequence.create2( TBehavior_Turn.create(true),
TBehavior_moveonland_obstaclecheck.create(0.1) );
mainloop := TBehaviorSequence.create2( safefromwater, { only proceed if safe from water }
defaultbehavior, 'mainloop' );
result := mainloop;
Here is some debug output to get an idea of how it flows.
>-Tick18:MoveOnLand1[.89]: Running
>-Tick19:MoveOnLand1[.88]: Running
>-Tick20:MoveOnLand1[.87]: Running
>-Tick21:MoveOnLand1[.86]: Running
>-Tick22:MoveOnLand1[.85]: Running
>-Tick23:MoveOnLand1[]: Fail ]: Fail ]: Fail ]: Fail ]: Fail
>-Tick24:mainloopâș[ child0>safefromwaterâș[ child0>abovewater?[ child0>AmIAboveWater[]: Fail ]: Running ]: Running ]: Running
>-Tick25:abovewater?[ child1>SwimUp[]: Running ]: Running
>-Tick26:SwimUp[]: Success ]: Success
>-Tick27:safefromwaterâș[ child1>outofwater?[ child0>AmIOutOfWater[]: Fail ]: Running ]: Running
>-Tick28:outofwater?[ child1>ForceFail[righttolandâș[ child0>RightIsBetterForLand[]: Fail ]: Fail ]: Fail ]: Running
>-Tick29:outofwater?[ child2>Invert[Swim[1]: Running ]: Running ]: Running
>-Tick30:Swim[.99]: Running
>-Tick31:Swim[.99]: Running
>-Tick32:Swim[.99]: Running
>-Tick33:Swim[.98]: Running
>-Tick34:Swim[.98]: Running
>-Tick35:Swim[.98]: Running
>-Tick36:Swim[.98]: Running
was walking along then found itself underwater so failed out of its current node, back to the root ⊠so swam up and started swimming ahead. The number in the brackets is the distance remaining before swim fails for not get to shore and the loop starts again (where it has a chance to turn right toward shore if closer). Reminds me of programming in Prolog back in the 80s⊠where one unexpected fail can really alter the expected results. Note that the arrow and the question mark indicate sequencer and selector. There are two decorators being used âForceFailâ and âInvertâ.