├── assets ├── .keep └── behavior_trees.json ├── config.json ├── tools ├── assets │ ├── .keep │ └── icons.png ├── config.json ├── src │ ├── Evts.hx │ ├── Core.hx │ ├── nodes │ │ ├── BaseNode.hx │ │ ├── RectangleNode.hx │ │ ├── CircleNode.hx │ │ └── SequenceNode.hx │ ├── Main.hx │ └── Logger.hx ├── project.flow └── hxbtGraph.hxproj ├── .gitignore ├── behaviour.hxml ├── behaviour.bat ├── src ├── behaviour │ ├── BehaviourMain.hx │ └── hxbt │ │ ├── BehaviorTreeBehaviour.hx │ │ ├── MockBehaviour.hx │ │ ├── decorators │ │ ├── IncludeBehaviour.hx │ │ ├── UntilFailBehaviour.hx │ │ ├── UntilSucceedBehaviour.hx │ │ ├── InvertBehaviour.hx │ │ ├── AlwaysFailBehaviour.hx │ │ └── AlwaysSucceedBehaviour.hx │ │ ├── BehaviorBehaviour.hx │ │ ├── loaders │ │ └── BehaviorTreeJSONLoaderBehaviour.hx │ │ └── composites │ │ ├── ParallelBehaviour.hx │ │ ├── SequenceBehaviour.hx │ │ └── SelectorBehaviour.hx ├── sample │ ├── BeLazyBehaviour.hx │ ├── OpenDoorBehaviour.hx │ ├── CloseDoorBehaviour.hx │ ├── Walk.hx │ ├── WalkToDoorBehaviour.hx │ ├── WalkThroughDoorBehaviour.hx │ └── Door.hx ├── hxbt │ ├── Decorator.hx │ ├── decorators │ │ ├── AlwaysFail.hx │ │ ├── AlwaysSucceed.hx │ │ ├── Include.hx │ │ ├── UntilFail.hx │ │ ├── UntilSuccess.hx │ │ └── Invert.hx │ ├── Composite.hx │ ├── Behavior.hx │ ├── composites │ │ ├── Parallel.hx │ │ ├── Selector.hx │ │ └── Sequence.hx │ ├── BehaviorTree.hx │ └── loaders │ │ └── BehaviorTreeJSONLoader.hx └── Main.hx ├── README.md ├── project.flow ├── .travis.yml ├── LICENSE └── hxbt.hxproj /assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /tools/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | behaviour.n -------------------------------------------------------------------------------- /tools/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/hxbt/master/tools/assets/icons.png -------------------------------------------------------------------------------- /behaviour.hxml: -------------------------------------------------------------------------------- 1 | -D behaviour 2 | -lib buddy 3 | -main behaviour.BehaviourMain 4 | -cp src 5 | -neko behaviour.n -------------------------------------------------------------------------------- /behaviour.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | haxe behaviour.hxml 4 | if %errorlevel% neq 0 exit /b %errorlevel% 5 | neko behaviour.n -------------------------------------------------------------------------------- /src/behaviour/BehaviourMain.hx: -------------------------------------------------------------------------------- 1 | package behaviour; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | class BehaviourMain extends BuddySuite implements Buddy { 7 | public function new() { 8 | // nothing here for now 9 | } 10 | } -------------------------------------------------------------------------------- /tools/src/Evts.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | /** 4 | * ... 5 | * @author Kristian Brodal 6 | */ 7 | class Evts 8 | { 9 | public static var EVT_LOG = 'Logger.Log'; 10 | 11 | 12 | private function new() 13 | { 14 | 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /tools/src/Core.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | /** 4 | * ... 5 | * @author Kristian Brodal 6 | */ 7 | class Core 8 | { 9 | 10 | public static function Initialize() 11 | { 12 | Logger.Instance(); 13 | } 14 | 15 | private function new() : Void { } 16 | } -------------------------------------------------------------------------------- /tools/src/nodes/BaseNode.hx: -------------------------------------------------------------------------------- 1 | package nodes; 2 | import luxe.Entity; 3 | import luxe.Vector; 4 | 5 | class BaseNode extends Entity 6 | { 7 | public function new(name : String) 8 | { 9 | super( { name : name } ); 10 | } 11 | 12 | public function pointInside(p : Vector) : Bool 13 | { 14 | return false; 15 | } 16 | } -------------------------------------------------------------------------------- /src/sample/BeLazyBehaviour.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | 6 | class BeLazyBehaviour extends Behavior { 7 | public function new() 8 | { 9 | super(); 10 | } 11 | 12 | override function update(context : Dynamic, dt : Float) : Status 13 | { 14 | return Status.RUNNING; 15 | } 16 | } 17 | #end -------------------------------------------------------------------------------- /src/hxbt/Decorator.hx: -------------------------------------------------------------------------------- 1 | package hxbt; 2 | 3 | /** 4 | * ... 5 | * @author Kenton Hamaluik 6 | */ 7 | class Decorator extends Behavior 8 | { 9 | private var m_child : Behavior; 10 | 11 | 12 | public function new(?child : Behavior) 13 | { 14 | super(); 15 | m_child = child; 16 | } 17 | 18 | public function setChild(child : Behavior) { 19 | m_child = child; 20 | } 21 | } -------------------------------------------------------------------------------- /assets/behavior_trees.json: -------------------------------------------------------------------------------- 1 | { 2 | "WalkThroughDoorAndStop": [{ 3 | "hxbt.sequence": [ 4 | { "sample.WalkToDoorBehaviour": [] }, 5 | { "sample.OpenDoorBehaviour": [] }, 6 | { "sample.WalkThroughDoorBehaviour": [] }, 7 | { "sample.CloseDoorBehaviour": [] }, 8 | { "sample.BeLazyBehaviour": [] } 9 | ] 10 | }] 11 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hxbt 2 | 3 | Behavior Tree implementation for Haxe. 4 | 5 | [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](https://raw.githubusercontent.com/whuop/hxbt/master/LICENSE) [![Version](http://img.shields.io/github/tag/whuop/hxbt.svg?style=flag&label=version)](https://github.com/whuop/hxbt) [![Build Status](https://travis-ci.org/whuop/hxbt.svg?branch=master)](https://travis-ci.org/whuop/hxbt) -------------------------------------------------------------------------------- /project.flow: -------------------------------------------------------------------------------- 1 | { 2 | project : { 3 | name : 'hxbt', 4 | version : '1.0.0', 5 | author : 'Kristian Brodal', 6 | 7 | app : { 8 | name : 'hxbt', 9 | package : 'com.luxeengine.empty', 10 | main : 'Main' 11 | }, 12 | 13 | build : { 14 | dependencies : { 15 | luxe : '*', 16 | buddy : '*' 17 | } 18 | }, 19 | 20 | files : { 21 | config : 'config.json', 22 | assets : 'assets/' 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tools/project.flow: -------------------------------------------------------------------------------- 1 | { 2 | project : { 3 | name : 'hxbtGraph', 4 | version : '1.0.0', 5 | author : 'Kristian Brodal ( whuop )', 6 | 7 | app : { 8 | name : 'hxbtGraph', 9 | package : 'com.whuop.hxbtGraph', 10 | main : 'Main' 11 | }, 12 | 13 | build : { 14 | dependencies : { 15 | luxe : '*', 16 | } 17 | }, 18 | 19 | files : { 20 | config : 'config.json', 21 | assets : 'assets/' 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/hxbt/decorators/AlwaysFail.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.Decorator; 5 | 6 | /** 7 | * Will always fail no matter whether the child succeeds or fails 8 | * @author Kenton Hamaluik 9 | */ 10 | class AlwaysFail extends Decorator 11 | { 12 | public function new(?child : Behavior) 13 | { 14 | super(child); 15 | } 16 | 17 | override function update(context : Dynamic, dt : Float) : Status 18 | { 19 | return Status.FAILURE; 20 | } 21 | } -------------------------------------------------------------------------------- /src/hxbt/decorators/AlwaysSucceed.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.Decorator; 5 | 6 | /** 7 | * Will always succeed no matter whether the child succeeds or fails 8 | * @author Kenton Hamaluik 9 | */ 10 | class AlwaysSucceed extends Decorator 11 | { 12 | public function new(?child : Behavior) 13 | { 14 | super(child); 15 | } 16 | 17 | override function update(context : Dynamic, dt : Float) : Status 18 | { 19 | return Status.SUCCESS; 20 | } 21 | } -------------------------------------------------------------------------------- /src/sample/OpenDoorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | import Main.ActorContext; 6 | import sample.Door; 7 | import sample.Door.DoorState; 8 | 9 | class OpenDoorBehaviour extends Behavior { 10 | public function new() 11 | { 12 | super(); 13 | } 14 | 15 | override function update(context : ActorContext, dt : Float) : Status 16 | { 17 | var door:Door = cast(context.door.get('Door'), Door); 18 | door.open(); 19 | if(door.state == DoorState.OPEN) { 20 | return Status.SUCCESS; 21 | } 22 | return Status.RUNNING; 23 | } 24 | } 25 | #end -------------------------------------------------------------------------------- /src/sample/CloseDoorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | import Main.ActorContext; 6 | import sample.Door; 7 | import sample.Door.DoorState; 8 | 9 | class CloseDoorBehaviour extends Behavior { 10 | public function new() 11 | { 12 | super(); 13 | } 14 | 15 | override function update(context : ActorContext, dt : Float) : Status 16 | { 17 | var door:Door = cast(context.door.get('Door'), Door); 18 | door.close(); 19 | if(door.state == DoorState.CLOSED) { 20 | return Status.SUCCESS; 21 | } 22 | return Status.RUNNING; 23 | } 24 | } 25 | #end -------------------------------------------------------------------------------- /src/hxbt/decorators/Include.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.BehaviorTree; 5 | import hxbt.Decorator; 6 | 7 | /** 8 | * Includes an entire behaviour tree as a child and runs it 9 | * @author Kenton Hamaluik 10 | */ 11 | class Include extends Decorator 12 | { 13 | var m_childTree : BehaviorTree; 14 | 15 | public function new(childTree : BehaviorTree) 16 | { 17 | super(); 18 | m_childTree = childTree; 19 | } 20 | 21 | override function update(context : Dynamic, dt : Float) : Status 22 | { 23 | m_childTree.update(dt); 24 | return Status.SUCCESS; 25 | } 26 | } -------------------------------------------------------------------------------- /src/hxbt/decorators/UntilFail.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.Decorator; 5 | 6 | /** 7 | * will repeat the child until it fails 8 | * @author Kenton Hamaluik 9 | */ 10 | class UntilFail extends Decorator 11 | { 12 | public function new(?child : Behavior) 13 | { 14 | super(child); 15 | } 16 | 17 | override function update(context : Dynamic, dt : Float) : Status 18 | { 19 | var childResult : Status = m_child.tick(context, dt); 20 | if(childResult != Status.FAILURE) 21 | { 22 | return Status.RUNNING; 23 | } 24 | 25 | return Status.SUCCESS; 26 | } 27 | } -------------------------------------------------------------------------------- /src/hxbt/Composite.hx: -------------------------------------------------------------------------------- 1 | package hxbt; 2 | 3 | /** 4 | * ... 5 | * @author Kristian Brodal 6 | */ 7 | class Composite extends Behavior 8 | { 9 | private var m_children : Array; 10 | 11 | 12 | public function new() 13 | { 14 | super(); 15 | m_children = new Array(); 16 | } 17 | 18 | /* 19 | * Add child to the end of the child array. 20 | */ 21 | public function add(b : Behavior) 22 | { 23 | m_children.push(b); 24 | } 25 | 26 | /* 27 | * Remove given child from the array of children. 28 | */ 29 | public function remove(b : Behavior) 30 | { 31 | m_children.remove(b); 32 | } 33 | } -------------------------------------------------------------------------------- /src/hxbt/decorators/UntilSuccess.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.Decorator; 5 | 6 | /** 7 | * will repeat the child until it succeeds 8 | * @author Kenton Hamaluik 9 | */ 10 | class UntilSuccess extends Decorator 11 | { 12 | public function new(?child : Behavior) 13 | { 14 | super(child); 15 | } 16 | 17 | override function update(context : Dynamic, dt : Float) : Status 18 | { 19 | var childResult : Status = m_child.tick(context, dt); 20 | if(childResult != Status.SUCCESS) 21 | { 22 | return Status.RUNNING; 23 | } 24 | 25 | return Status.SUCCESS; 26 | } 27 | } -------------------------------------------------------------------------------- /tools/src/nodes/RectangleNode.hx: -------------------------------------------------------------------------------- 1 | package nodes; 2 | import luxe.Color; 3 | import phoenix.geometry.QuadGeometry; 4 | 5 | class RectangleNode extends BaseNode 6 | { 7 | 8 | public var box(default, default) : QuadGeometry; 9 | 10 | public function new(_name : String, _width : Int, _height : Int, _color : Color) 11 | { 12 | super( name ); 13 | box = Luxe.draw.box( { 14 | x : 0, 15 | y : 0, 16 | w : _width, 17 | h : _height, 18 | color : _color 19 | }); 20 | box.transform.parent = this.transform; 21 | } 22 | 23 | override function update(dt : Float) : Void 24 | { 25 | super.update(dt); 26 | } 27 | } -------------------------------------------------------------------------------- /src/sample/Walk.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import luxe.Sprite; 5 | import luxe.Component; 6 | import luxe.Vector; 7 | 8 | /* 9 | * Will move towards a location with a given velocity 10 | * @author Kenton Hamaluik 11 | */ 12 | class Walk extends Component { 13 | public var speed:Float = 32; 14 | public var target:Vector; 15 | public function new(?speed:Float) { 16 | super({ name: 'Walk' }); 17 | if(speed != null) this.speed = speed; 18 | } 19 | 20 | override function update(dt:Float) { 21 | if(target != null) { 22 | entity.pos.add(target.clone().subtract(entity.pos).normalized.multiplyScalar(speed * dt)); 23 | } 24 | } 25 | } 26 | #end -------------------------------------------------------------------------------- /tools/src/nodes/CircleNode.hx: -------------------------------------------------------------------------------- 1 | package nodes; 2 | import luxe.Color; 3 | 4 | class CircleNode extends BaseNode 5 | { 6 | public var x(default, default) : Int; 7 | public var y(default, default) : Int; 8 | public var r(default, default) : Int; 9 | public var color(default, default) : Color; 10 | 11 | public function new(_name : String, _x : Int, _y : Int, _r : Int, _color : Color) 12 | { 13 | super( name ); 14 | this.x = _x; 15 | this.y = _y; 16 | this.r = _r; 17 | this.color = _color; 18 | } 19 | 20 | override function update(dt : Float) : Void 21 | { 22 | Luxe.draw.ring({ 23 | x : this.x, 24 | y : this.y, 25 | r : this.r, 26 | color : this.color, 27 | immediate : true 28 | }); 29 | 30 | super.update(dt); 31 | } 32 | } -------------------------------------------------------------------------------- /tools/src/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | import luxe.Color; 4 | import luxe.Input; 5 | import nodes.CircleNode; 6 | import nodes.RectangleNode; 7 | import nodes.SequenceNode; 8 | 9 | class Main extends luxe.Game 10 | { 11 | 12 | var node : RectangleNode; 13 | var cnode : CircleNode; 14 | var snode : SequenceNode; 15 | 16 | override function ready() 17 | { 18 | Core.Initialize(); 19 | //node = new RectangleNode('rectNode', 100, 100, 100, 50, new Color(63/255, 91/255, 127/255, 1)); 20 | cnode = new CircleNode('cnode', 220, 100, 50, new Color(63, 0, 0, 1)); 21 | snode = new SequenceNode('sequence', 100, 100); 22 | } 23 | 24 | override function onkeyup(e:KeyEvent) 25 | { 26 | if(e.keycode == Key.escape) 27 | Luxe.shutdown(); 28 | } 29 | 30 | override function update(dt:Float) 31 | { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/behaviour/hxbt/BehaviorTreeBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.BehaviorTree; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class BehaviorTreeBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var tree = new BehaviorTree(); 19 | var behavior:MockBehaviour = new MockBehaviour(); 20 | tree.setRoot(behavior); 21 | 22 | describe("Using a behavior tree", { 23 | it("should wait until the correct time to tick its root", { 24 | tree.update(tree.period / 2); 25 | behavior.m_initializedCalled.should.be(0); 26 | tree.update(tree.period / 2); 27 | behavior.m_initializedCalled.should.be(1); 28 | }); 29 | }); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/sample/WalkToDoorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | import luxe.Vector; 6 | import Main.ActorContext; 7 | 8 | class WalkToDoorBehaviour extends Behavior { 9 | var targetPosition:Vector; 10 | 11 | public function new() 12 | { 13 | super(); 14 | } 15 | 16 | override function onInitialize(context : ActorContext) : Void 17 | { 18 | targetPosition = context.door.pos.clone(); 19 | } 20 | 21 | override function update(context : ActorContext, dt : Float) : Status 22 | { 23 | if(targetPosition.clone().subtract(context.actor.pos).length <= 16) { 24 | cast(context.actor.get('Walk'), Walk).target = null; 25 | return Status.SUCCESS; 26 | } 27 | else { 28 | cast(context.actor.get('Walk'), Walk).target = targetPosition; 29 | } 30 | return Status.RUNNING; 31 | } 32 | } 33 | #end -------------------------------------------------------------------------------- /src/sample/WalkThroughDoorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | import luxe.Vector; 6 | import Main.ActorContext; 7 | 8 | class WalkThroughDoorBehaviour extends Behavior { 9 | var targetPosition:Vector; 10 | 11 | public function new() 12 | { 13 | super(); 14 | } 15 | 16 | override function onInitialize(context : ActorContext) : Void 17 | { 18 | targetPosition = context.door.pos.clone().add_xyz(32); 19 | } 20 | 21 | override function update(context : ActorContext, dt : Float) : Status 22 | { 23 | if(targetPosition.clone().subtract(context.actor.pos).length <= 16) { 24 | cast(context.actor.get('Walk'), Walk).target = null; 25 | return Status.SUCCESS; 26 | } 27 | else { 28 | cast(context.actor.get('Walk'), Walk).target = targetPosition; 29 | } 30 | return Status.RUNNING; 31 | } 32 | } 33 | #end -------------------------------------------------------------------------------- /src/hxbt/decorators/Invert.hx: -------------------------------------------------------------------------------- 1 | package hxbt.decorators; 2 | 3 | import hxbt.Behavior.Status; 4 | import hxbt.Decorator; 5 | 6 | /** 7 | * Returns success if the child returns failure, 8 | * returns failure if the child returns success, 9 | * otherwise returns what the child returns 10 | * @author Kenton Hamaluik 11 | */ 12 | class Invert extends Decorator 13 | { 14 | public function new(?child : Behavior) 15 | { 16 | super(child); 17 | } 18 | 19 | override function update(context : Dynamic, dt : Float) : Status 20 | { 21 | if(m_child == null) 22 | { 23 | throw "Inverter requires child to not be null!"; 24 | } 25 | 26 | var childResult : Status = m_child.tick(context, dt); 27 | if(childResult == Status.SUCCESS) 28 | { 29 | return Status.FAILURE; 30 | } 31 | else if(childResult == Status.FAILURE) 32 | { 33 | return Status.SUCCESS; 34 | } 35 | return childResult; 36 | } 37 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/MockBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt; 2 | 3 | import hxbt.Behavior; 4 | 5 | class MockBehaviour extends Behavior 6 | { 7 | public var m_initializedCalled = 0; 8 | public var m_terminateCalled = 0; 9 | public var m_updateCalled = 0; 10 | 11 | public var m_terminateStatus : Status; 12 | 13 | public var m_operationResult:Status = null; 14 | 15 | public function new() 16 | { 17 | super(); 18 | } 19 | 20 | override function onInitialize(context : Dynamic) : Void 21 | { 22 | m_initializedCalled++; 23 | } 24 | 25 | override function onTerminate(context : Dynamic, status : Status) : Void 26 | { 27 | m_terminateCalled++; 28 | m_terminateStatus = status; 29 | } 30 | 31 | override function update(context : Dynamic, dt : Float) : Status 32 | { 33 | m_updateCalled++; 34 | if(m_operationResult != null) 35 | { 36 | return m_operationResult; 37 | } 38 | return Status.RUNNING; 39 | } 40 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/IncludeBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import hxbt.BehaviorTree; 8 | import behaviour.hxbt.MockBehaviour; 9 | import hxbt.decorators.Include; 10 | 11 | /** 12 | * ... 13 | * @author Kenton Hamaluik 14 | */ 15 | class IncludeBehaviour extends BuddySuite 16 | { 17 | public function new() 18 | { 19 | var tree:BehaviorTree = new BehaviorTree(); 20 | var behaviour = new MockBehaviour(); 21 | tree.setRoot(behaviour); 22 | var decorator:Include = new Include(tree); 23 | 24 | describe("Using an include decorator", { 25 | it("should update the tree on ticks", { 26 | decorator.tick(null, 5); 27 | behaviour.m_initializedCalled.should.be(1); 28 | }); 29 | it("should always respond with success", { 30 | decorator.status.should.be(Status.SUCCESS); 31 | }); 32 | }); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Travis now has haxe support, but something is going wrong. Try the old-school way 2 | language: python 3 | 4 | # Install Haxe before running the test. 5 | before_script: 6 | - sudo apt-get update > /dev/null # run update before installing anything 7 | - sudo apt-get install python-software-properties -y > /dev/null # for the next command 8 | - sudo add-apt-repository ppa:eyecreate/haxe -y > /dev/null # add the ubuntu ppa that contains haxe 9 | - sudo apt-get update > /dev/null # pull info from ppa 10 | - sudo apt-get install haxe -y > /dev/null # install haxe (and neko) 11 | - mkdir ~/haxelib > /dev/null # create a folder for installing haxelib 12 | - haxelib setup ~/haxelib > /dev/null 13 | - haxelib install buddy > /dev/null 14 | 15 | # Run the test! 16 | script: 17 | - haxe behaviour.hxml 18 | - neko behaviour.n -------------------------------------------------------------------------------- /tools/src/nodes/SequenceNode.hx: -------------------------------------------------------------------------------- 1 | package nodes; 2 | import luxe.Color; 3 | import luxe.Rectangle; 4 | import luxe.Sprite; 5 | import luxe.Vector; 6 | import phoenix.Texture; 7 | 8 | /** 9 | * ... 10 | * @author Kristian Brodal 11 | */ 12 | class SequenceNode extends RectangleNode 13 | { 14 | private static var NORMAL_COLOR = new Color(63 / 255, 91 / 255, 127 / 255, 0.7); 15 | 16 | private var m_icon : Sprite; 17 | 18 | public function new(name : String, x : Int, y : Int) 19 | { 20 | super(name, 84, 84, NORMAL_COLOR); 21 | 22 | Luxe.loadTexture('assets/icons.png', function(texture : Texture) 23 | { 24 | m_icon = new Sprite( { 25 | name : 'icon', 26 | name_unique : true, 27 | texture : texture, 28 | uv : new Rectangle(0, 0, 64, 64), 29 | size : new Vector(64, 64), 30 | origin : new Vector(32, 32), 31 | pos : new Vector( 84 / 2, 84 / 2) 32 | } ); 33 | m_icon.transform.parent = this.transform; 34 | }); 35 | 36 | this.pos = new Vector(x, y); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /tools/src/Logger.hx: -------------------------------------------------------------------------------- 1 | package ; 2 | import luxe.Events; 3 | import luxe.Text; 4 | import luxe.Vector; 5 | 6 | /** 7 | * ... 8 | * @author Kristian Brodal 9 | */ 10 | class Logger 11 | { 12 | 13 | private static var m_instance : Logger; 14 | 15 | private var m_text : Text; 16 | public static var events(default, null) : Events; 17 | 18 | private function new() 19 | { 20 | events = new Events(); 21 | m_text = new Text( { 22 | name : 'log_text', 23 | name_unique : true, 24 | text : '/ Logger / Initialized', 25 | pos : new Vector(10, Luxe.screen.h - 26), 26 | point_size : 16 27 | } ); 28 | } 29 | 30 | public static function Instance() : Logger 31 | { 32 | if (m_instance == null) 33 | { 34 | m_instance = new Logger(); 35 | } 36 | 37 | return m_instance; 38 | } 39 | 40 | public static function Log(msg : String) 41 | { 42 | Instance().log(msg); 43 | } 44 | 45 | private function log(msg : String) 46 | { 47 | m_text.text = msg; 48 | events.fire(Evts.EVT_LOG, msg); 49 | } 50 | } -------------------------------------------------------------------------------- /src/hxbt/Behavior.hx: -------------------------------------------------------------------------------- 1 | package hxbt; 2 | import hxbt.Behavior.Status; 3 | 4 | /* 5 | * Behavior states. 6 | */ 7 | enum Status 8 | { 9 | INVALID; 10 | SUCCESS; 11 | FAILURE; 12 | RUNNING; 13 | } 14 | 15 | /** 16 | * Base class for all actions, conditons and composites. 17 | * A composite is a branch in the behavior tree. 18 | * @author Kristian Brodal 19 | */ 20 | @:keepSub 21 | class Behavior 22 | { 23 | 24 | public var status(default, default) : Status = Status.INVALID; 25 | 26 | public function new() 27 | { 28 | 29 | } 30 | 31 | public function update( context : Dynamic, dt : Float) : Status { return Status.INVALID; } 32 | public function onInitialize( context : Dynamic) : Void { } 33 | public function onTerminate(context : Dynamic, status : Status) : Void { } 34 | 35 | public function tick(context : Dynamic, dt : Float) : Status 36 | { 37 | if (status == Status.INVALID) 38 | { 39 | onInitialize(context); 40 | } 41 | 42 | status = update(context, dt); 43 | 44 | if (status != Status.RUNNING) 45 | { 46 | onTerminate(context, status); 47 | } 48 | 49 | return status; 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kristian Brodal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/UntilFailBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.decorators.UntilFail; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class UntilFailBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var decorator:UntilFail = new UntilFail(); 19 | var behaviour = new MockBehaviour(); 20 | decorator.setChild(behaviour); 21 | 22 | describe("Using an until fail decorator", { 23 | it("should report as running if the child is running", { 24 | behaviour.m_operationResult = null; 25 | decorator.tick(null, 0.1); 26 | decorator.status.should.be(Status.RUNNING); 27 | }); 28 | it("should report as running if the child succeeds", { 29 | behaviour.m_operationResult = Status.SUCCESS; 30 | decorator.tick(null, 0.1); 31 | decorator.status.should.be(Status.RUNNING); 32 | }); 33 | it("should report as success if the child fails", { 34 | behaviour.m_operationResult = Status.FAILURE; 35 | decorator.tick(null, 0.1); 36 | decorator.status.should.be(Status.SUCCESS); 37 | }); 38 | }); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/UntilSucceedBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.decorators.UntilSuccess; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class UntilSucceedBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var decorator:UntilSuccess = new UntilSuccess(); 19 | var behaviour = new MockBehaviour(); 20 | decorator.setChild(behaviour); 21 | 22 | describe("Using an until success decorator", { 23 | it("should report as running if the child is running", { 24 | behaviour.m_operationResult = null; 25 | decorator.tick(null, 0.1); 26 | decorator.status.should.be(Status.RUNNING); 27 | }); 28 | it("should report as running if the child fails", { 29 | behaviour.m_operationResult = Status.FAILURE; 30 | decorator.tick(null, 0.1); 31 | decorator.status.should.be(Status.RUNNING); 32 | }); 33 | it("should report as success if the child succeeds", { 34 | behaviour.m_operationResult = Status.SUCCESS; 35 | decorator.tick(null, 0.1); 36 | decorator.status.should.be(Status.SUCCESS); 37 | }); 38 | }); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/hxbt/composites/Parallel.hx: -------------------------------------------------------------------------------- 1 | package hxbt.composites; 2 | import hxbt.Behavior.Status; 3 | 4 | /** 5 | * A special branch that starts or resume all children every time it runs. 6 | * Will success if _all_ the children succeed. 7 | * Will fail if any one of the children fail. 8 | * @author Kenton Hamaluik 9 | */ 10 | class Parallel extends Composite 11 | { 12 | public function new() 13 | { 14 | super(); 15 | } 16 | 17 | override function onInitialize(context : Dynamic) 18 | { 19 | } 20 | 21 | override function onTerminate(context : Dynamic, status : Status) 22 | { 23 | for(_child in m_children) 24 | { 25 | _child.status = Status.INVALID; 26 | } 27 | } 28 | 29 | override function update(context : Dynamic, dt : Float) : Status 30 | { 31 | var allSucceeded:Bool = true; 32 | for(child in m_children) 33 | { 34 | child.tick(context, dt); 35 | 36 | // if any of the children fail, 37 | // fail the whole branch 38 | if(child.status == Status.FAILURE) 39 | { 40 | return Status.FAILURE; 41 | } 42 | // if all children didn't succeed, we're not done yet 43 | else if(child.status != Status.SUCCESS) 44 | { 45 | allSucceeded = false; 46 | } 47 | } 48 | 49 | if(allSucceeded) 50 | { 51 | return Status.SUCCESS; 52 | } 53 | 54 | return Status.RUNNING; 55 | } 56 | } -------------------------------------------------------------------------------- /src/hxbt/composites/Selector.hx: -------------------------------------------------------------------------------- 1 | package hxbt.composites; 2 | 3 | import hxbt.Behavior.Status; 4 | 5 | /** 6 | * Similar to sequence, looks for one behavior that returns success and returns. 7 | * @author Kristian Brodal 8 | */ 9 | class Selector extends Composite 10 | { 11 | private var m_currentChild : Behavior; 12 | private var m_currentIndex : Int; 13 | 14 | 15 | public function new() 16 | { 17 | super(); 18 | } 19 | 20 | override function onInitialize(context : Dynamic) 21 | { 22 | m_currentIndex = 0; 23 | } 24 | 25 | override function onTerminate(context : Dynamic, status : Status) 26 | { 27 | for(_child in m_children) 28 | { 29 | _child.status = Status.INVALID; 30 | } 31 | } 32 | 33 | override function update(context : Dynamic, dt : Float) : Status 34 | { 35 | m_currentChild = m_children[m_currentIndex]; 36 | var s = m_currentChild.tick(context, dt); 37 | 38 | // If the child succeeds or is still running, early return. 39 | if (s != Status.FAILURE) 40 | { 41 | return s; 42 | } 43 | m_currentIndex++; 44 | // If the end of the children is hit, that means the whole thing fails. 45 | if (m_currentIndex == m_children.length) 46 | { 47 | // Reset index otherwise it will crash on next run through 48 | m_currentIndex = 0; 49 | return Status.FAILURE; 50 | } 51 | 52 | return Status.RUNNING; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/InvertBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.decorators.Invert; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class InvertBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var decorator:Invert = new Invert(); 19 | var behaviour = new MockBehaviour(); 20 | decorator.setChild(behaviour); 21 | 22 | describe("Using an inverter decorator", { 23 | it("should respond with failure if the child succeeds", { 24 | behaviour.m_operationResult = Status.SUCCESS; 25 | decorator.tick(null, 0.1); 26 | decorator.status.should.be(Status.FAILURE); 27 | }); 28 | it("should respond with success if the child fails", { 29 | behaviour.m_operationResult = Status.FAILURE; 30 | decorator.tick(null, 0.1); 31 | decorator.status.should.be(Status.SUCCESS); 32 | }); 33 | it("should respond with running if the child is running", { 34 | behaviour.m_operationResult = null; 35 | decorator.tick(null, 0.1); 36 | decorator.status.should.be(Status.RUNNING); 37 | }); 38 | it("should respond with invalid if the child is invalid", { 39 | behaviour.m_operationResult = Status.INVALID; 40 | decorator.tick(null, 0.1); 41 | decorator.status.should.be(Status.INVALID); 42 | }); 43 | }); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/hxbt/BehaviorTree.hx: -------------------------------------------------------------------------------- 1 | package hxbt; 2 | 3 | /** 4 | * Base of a behavior tree, this is used to hold the rest of the tree for the AI 5 | * as this has extra functionality. 6 | * @author Kristian Brodal 7 | */ 8 | class BehaviorTree 9 | { 10 | // The period decides how often the behavior tree should update. 11 | // Period of 0.2 will make the tree update 5 times a second. 12 | public var period(default, default) : Float; 13 | 14 | // Root of the behavior tree. 15 | private var m_tree : Behavior; 16 | 17 | // The context contains all the data needed to run 18 | // the different behaviors. Also called blackboard in some cases. 19 | private var m_context : Dynamic; 20 | 21 | // When counter reaches 0 tree is updated. 22 | private var m_counter : Float; 23 | 24 | public function new(?period:Float) 25 | { 26 | // 0.2 will make the tree run 5 times a second. 27 | // Used as default for now 28 | this.period = period == null ? 0.2 : period; 29 | m_counter = this.period; 30 | } 31 | 32 | public function setRoot(root : Behavior) : Void 33 | { 34 | m_tree = root; 35 | } 36 | 37 | public function setContext(context : Dynamic) : Void 38 | { 39 | m_context = context; 40 | } 41 | 42 | public function update(dt : Float) : Void 43 | { 44 | m_counter -= dt; 45 | while (m_counter <= 0) 46 | { 47 | m_counter += this.period; 48 | if (m_tree != null) 49 | { 50 | m_tree.tick(m_context, this.period); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/AlwaysFailBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.decorators.AlwaysFail; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class AlwaysFailBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var decorator:AlwaysFail = new AlwaysFail(); 19 | var behaviour = new MockBehaviour(); 20 | decorator.setChild(behaviour); 21 | 22 | describe("Using an always fail decorator", { 23 | it("should respond with failure if the child succeeds", { 24 | behaviour.m_operationResult = Status.SUCCESS; 25 | decorator.tick(null, 0.1); 26 | decorator.status.should.be(Status.FAILURE); 27 | }); 28 | it("should respond with failure if the child fails", { 29 | behaviour.m_operationResult = Status.FAILURE; 30 | decorator.tick(null, 0.1); 31 | decorator.status.should.be(Status.FAILURE); 32 | }); 33 | it("should respond with failure if the child is running", { 34 | behaviour.m_operationResult = null; 35 | decorator.tick(null, 0.1); 36 | decorator.status.should.be(Status.FAILURE); 37 | }); 38 | it("should respond with failure if the child is invalid", { 39 | behaviour.m_operationResult = Status.INVALID; 40 | decorator.tick(null, 0.1); 41 | decorator.status.should.be(Status.FAILURE); 42 | }); 43 | }); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/behaviour/hxbt/decorators/AlwaysSucceedBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.decorators; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.decorators.AlwaysSucceed; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class AlwaysSucceedBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var decorator:AlwaysSucceed = new AlwaysSucceed(); 19 | var behaviour = new MockBehaviour(); 20 | decorator.setChild(behaviour); 21 | 22 | describe("Using an always succeed decorator", { 23 | it("should respond with success if the child succeeds", { 24 | behaviour.m_operationResult = Status.SUCCESS; 25 | decorator.tick(null, 0.1); 26 | decorator.status.should.be(Status.SUCCESS); 27 | }); 28 | it("should respond with success if the child fails", { 29 | behaviour.m_operationResult = Status.FAILURE; 30 | decorator.tick(null, 0.1); 31 | decorator.status.should.be(Status.SUCCESS); 32 | }); 33 | it("should respond with success if the child is running", { 34 | behaviour.m_operationResult = null; 35 | decorator.tick(null, 0.1); 36 | decorator.status.should.be(Status.SUCCESS); 37 | }); 38 | it("should respond with success if the child is invalid", { 39 | behaviour.m_operationResult = Status.INVALID; 40 | decorator.tick(null, 0.1); 41 | decorator.status.should.be(Status.SUCCESS); 42 | }); 43 | }); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/hxbt/composites/Sequence.hx: -------------------------------------------------------------------------------- 1 | package hxbt.composites; 2 | import hxbt.Behavior.Status; 3 | 4 | /** 5 | * A sequence goes through all behaviors attached to it from left to right. 6 | * Keeps going through behavior until all behaviors have returned SUCCESS. 7 | * If a single behavior returns FAILURE, the whole sequence fails. 8 | * @author Kristian Brodal 9 | * @author Kenton Hamaluik 10 | */ 11 | class Sequence extends Composite 12 | { 13 | private var m_currentChild : Behavior; 14 | private var m_currentIndex : Int; 15 | 16 | 17 | public function new() 18 | { 19 | super(); 20 | } 21 | 22 | override function onInitialize(context : Dynamic) 23 | { 24 | m_currentIndex = 0; 25 | } 26 | 27 | override function onTerminate(context : Dynamic, status : Status) 28 | { 29 | for(_child in m_children) 30 | { 31 | _child.status = Status.INVALID; 32 | } 33 | } 34 | 35 | override function update(context : Dynamic, dt : Float) : Status 36 | { 37 | // get the current child which is being evaluated 38 | m_currentChild = m_children[m_currentIndex]; 39 | var s = m_currentChild.tick(context, dt); 40 | 41 | // If the child failed or is still running, early return. 42 | if (s != Status.SUCCESS) 43 | { 44 | return s; 45 | } 46 | m_currentIndex++; 47 | // If end of array hit the whole sequence succeeded. 48 | if (m_currentIndex == m_children.length) 49 | { 50 | // Reset index otherwise it will crash on next run through 51 | m_currentIndex = 0; 52 | return Status.SUCCESS; 53 | } 54 | 55 | return Status.RUNNING; 56 | } 57 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/BehaviorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | 9 | /** 10 | * ... 11 | * @author Kenton Hamaluik 12 | */ 13 | class BehaviorBehaviour extends BuddySuite 14 | { 15 | public function new() 16 | { 17 | var behaviour = new MockBehaviour(); 18 | 19 | describe("Using a subclassed behaviour", { 20 | it("should not have been initialized without ticking", { 21 | behaviour.m_initializedCalled.should.be(0); 22 | }); 23 | it("should should auto-initialize on it's first tick", { 24 | behaviour.tick(null, 0.1); 25 | behaviour.m_initializedCalled.should.be(1); 26 | }); 27 | it("should have updated on it's first tick", { 28 | behaviour.m_updateCalled.should.be(1); 29 | }); 30 | it("should not have terminated without the leaf saying so", { 31 | behaviour.m_terminateCalled.should.be(0); 32 | behaviour.m_terminateStatus.should.be(null); 33 | }); 34 | it("should not re-initialize after the first time without terminating first", { 35 | behaviour.tick(null, 0.1); 36 | behaviour.m_initializedCalled.should.be(1); 37 | behaviour.m_updateCalled.should.be(2); 38 | }); 39 | it("should terminate whe the leaf says so", { 40 | behaviour.m_operationResult = Status.SUCCESS; 41 | behaviour.tick(null, 0.1); 42 | behaviour.m_updateCalled.should.be(3); 43 | behaviour.m_terminateCalled.should.be(1); 44 | behaviour.m_terminateStatus.should.be(Status.SUCCESS); 45 | }); 46 | }); 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /src/behaviour/hxbt/loaders/BehaviorTreeJSONLoaderBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.loaders; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import hxbt.BehaviorTree; 8 | import hxbt.loaders.BehaviorTreeJSONLoader; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class BehaviorTreeJSONLoaderBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | describe("Using a JSON loader", { 19 | it("should be able to instantiate a tree from a JSON object", {}); 20 | it("should be able to instantiate a tree containing HXBT nodes", {}); 21 | it("should be able to instantiate a tree containing custom leaf behaviours", {}); 22 | it("should be able to instantiate a tree from a JSON string", { 23 | // for some reason this fails in the tests but works normally ? 24 | /*var jsonString = ' 25 | { 26 | "WalkThroughDoorAndStop": [{ 27 | "hxbt.sequence": [ 28 | { "sample.WalkToDoorBehaviour": [] }, 29 | { "sample.OpenDoorBehaviour": [] }, 30 | { "sample.WalkThroughDoorBehaviour": [] }, 31 | { "sample.CloseDoorBehaviour": [] }, 32 | { "sample.BeLazyBehaviour": [] } 33 | ] 34 | }] 35 | } 36 | '; 37 | hxbt.loaders.BehaviorTreeJSONLoader.FromJSONString.bind(jsonString, "TestTree").should.not.throwType(String); 38 | var tree:BehaviorTree = hxbt.loaders.BehaviorTreeJSONLoader.FromJSONString(jsonString, "WalkThroughDoorAndStop"); 39 | tree.should.not.be(null);*/ 40 | }); 41 | }); 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/sample/Door.hx: -------------------------------------------------------------------------------- 1 | package sample; 2 | 3 | #if !behaviour 4 | import luxe.Sprite; 5 | import luxe.Component; 6 | import luxe.Vector; 7 | 8 | enum DoorState { 9 | CLOSED; 10 | OPENING; 11 | OPEN; 12 | CLOSING; 13 | } 14 | 15 | /* 16 | * Animate the width of a sprite using "open()" and "close()" functions 17 | * @author Kenton Hamaluik 18 | */ 19 | class Door extends Component { 20 | public var state:DoorState; 21 | var closedWidth:Float; 22 | var openWidth:Float; 23 | var spr:Sprite; 24 | 25 | var speed:Float = 32; 26 | var targetWidth:Float = null; 27 | 28 | public function new(?closedWidth:Float, ?openWidth:Float) { 29 | super({ name: 'Door' }); 30 | state = DoorState.CLOSED; 31 | this.closedWidth = closedWidth; 32 | this.openWidth = openWidth; 33 | } 34 | 35 | override function init() { 36 | spr = cast(entity, Sprite); 37 | } 38 | 39 | public function open() { 40 | if(state == DoorState.CLOSED) { 41 | state = DoorState.OPENING; 42 | targetWidth = openWidth; 43 | } 44 | } 45 | 46 | public function close() { 47 | if(state == DoorState.OPEN) { 48 | state = DoorState.CLOSING; 49 | targetWidth = closedWidth; 50 | } 51 | } 52 | 53 | override function update(dt:Float) { 54 | if(state == DoorState.OPENING) { 55 | spr.size.x -= speed * dt; 56 | if(spr.size.x <= openWidth) { 57 | spr.size.x = openWidth; 58 | state = DoorState.OPEN; 59 | } 60 | } 61 | else if(state == DoorState.CLOSING) { 62 | spr.size.x += speed * dt; 63 | if(spr.size.x >= closedWidth) { 64 | spr.size.x = closedWidth; 65 | state = DoorState.CLOSED; 66 | } 67 | } 68 | } 69 | } 70 | #end -------------------------------------------------------------------------------- /src/behaviour/hxbt/composites/ParallelBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.composites; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.composites.Parallel; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class ParallelBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var parallel:Parallel = new Parallel(); 19 | 20 | var behaviourA:MockBehaviour = new MockBehaviour(); 21 | var behaviourB:MockBehaviour = new MockBehaviour(); 22 | 23 | parallel.add(behaviourA); 24 | parallel.add(behaviourB); 25 | 26 | describe("Using a parallel", { 27 | it("should initialize every child on the first tick", { 28 | parallel.tick(null, 0.1); 29 | behaviourA.m_initializedCalled.should.be(1); 30 | behaviourB.m_initializedCalled.should.be(1); 31 | }); 32 | it("should update every child only once on each tick", { 33 | behaviourA.m_updateCalled.should.be(1); 34 | behaviourB.m_updateCalled.should.be(1); 35 | }); 36 | it("should keep running if not all of its children succeed", { 37 | behaviourA.m_operationResult = Status.SUCCESS; 38 | parallel.tick(null, 0.1); 39 | parallel.status.should.be(Status.RUNNING); 40 | }); 41 | it("should succeed if all of its children succeed", { 42 | behaviourB.m_operationResult = Status.SUCCESS; 43 | parallel.tick(null, 0.1); 44 | parallel.status.should.be(Status.SUCCESS); 45 | }); 46 | it("should fail if any of it's children fail", { 47 | behaviourA.m_operationResult = null; 48 | behaviourB.m_operationResult = null; 49 | parallel.tick(null, 0.1); 50 | 51 | behaviourB.m_operationResult = Status.FAILURE; 52 | parallel.tick(null, 0.1); 53 | parallel.status.should.be(Status.FAILURE); 54 | }); 55 | it("should invalidate all its children when it terminates", { 56 | behaviourA.status.should.be(Status.INVALID); 57 | behaviourB.status.should.be(Status.INVALID); 58 | }); 59 | }); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Main.hx: -------------------------------------------------------------------------------- 1 | package; 2 | 3 | #if !behaviour 4 | import hxbt.Behavior; 5 | import hxbt.Behavior.Status; 6 | import hxbt.BehaviorTree; 7 | import hxbt.composites.Sequence; 8 | import luxe.Component; 9 | import luxe.Input; 10 | import luxe.Sprite; 11 | import luxe.Vector; 12 | import luxe.Color; 13 | 14 | import sample.Walk; 15 | import sample.Door; 16 | 17 | import sample.WalkToDoorBehaviour; 18 | import sample.OpenDoorBehaviour; 19 | import sample.WalkThroughDoorBehaviour; 20 | import sample.CloseDoorBehaviour; 21 | import sample.BeLazyBehaviour; 22 | 23 | typedef ActorContext = { 24 | var actor:Sprite; 25 | var door:Sprite; 26 | } 27 | 28 | class Main extends luxe.Game 29 | { 30 | var actor:Sprite; 31 | var door:Sprite; 32 | var behaviourTree:BehaviorTree; 33 | 34 | override function ready() 35 | { 36 | // create our dude who's going to walk around 37 | var actor:Sprite = new Sprite({ 38 | pos: new Vector((Luxe.screen.w / 2) - 128, Luxe.screen.h / 2), 39 | size: new Vector(16, 16), 40 | color: new Color(0.9, 0.9, 0.9, 1), 41 | origin: new Vector(8, 16) 42 | }); 43 | actor.add(new Walk(128)); 44 | 45 | // create a door for him to walk through 46 | door = new Sprite({ 47 | pos: new Vector((Luxe.screen.w / 2) + 128, Luxe.screen.h / 2), 48 | size: new Vector(16, 32), 49 | color: new Color(0.5, 0.25, 0, 1), 50 | origin: new Vector(8, 32) 51 | }); 52 | door.add(new Door(16, 2)); 53 | 54 | Luxe.loadJSON("assets/behavior_trees.json", function(res) { 55 | try { 56 | behaviourTree = hxbt.loaders.BehaviorTreeJSONLoader.FromJSONObject(res.json, "WalkThroughDoorAndStop"); 57 | behaviourTree.setContext({ 58 | actor: actor, 59 | door: door 60 | }); 61 | } 62 | catch(err:String) { 63 | trace("ERROR: " + err); 64 | } 65 | }); 66 | } 67 | 68 | override function onkeyup(e:KeyEvent) 69 | { 70 | if(e.keycode == Key.escape) 71 | Luxe.shutdown(); 72 | } 73 | 74 | override function update(dt:Float) 75 | { 76 | if(behaviourTree != null) { 77 | behaviourTree.period = dt; // make it update every frame 78 | behaviourTree.update(dt); 79 | } 80 | } 81 | } 82 | 83 | #end -------------------------------------------------------------------------------- /src/behaviour/hxbt/composites/SequenceBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.composites; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.composites.Sequence; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class SequenceBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var sequence:Sequence = new Sequence(); 19 | 20 | var behaviourA:MockBehaviour = new MockBehaviour(); 21 | var behaviourB:MockBehaviour = new MockBehaviour(); 22 | 23 | sequence.add(behaviourA); 24 | sequence.add(behaviourB); 25 | 26 | describe("Using a sequence", { 27 | it("should initialize only the first child on the first tick", { 28 | sequence.tick(null, 0.1); 29 | behaviourA.m_initializedCalled.should.be(1); 30 | behaviourB.m_initializedCalled.should.be(0); 31 | }); 32 | it("should not move on to the next child if the first one hasn't finished", { 33 | sequence.tick(null, 0.1); 34 | behaviourA.m_updateCalled.should.be(2); 35 | behaviourB.m_initializedCalled.should.be(0); 36 | }); 37 | it("should move on to the next child if the first finishes", { 38 | behaviourA.m_operationResult = Status.SUCCESS; 39 | sequence.tick(null, 0.1); 40 | sequence.tick(null, 0.1); 41 | behaviourA.m_updateCalled.should.be(3); 42 | behaviourB.m_initializedCalled.should.be(1); 43 | }); 44 | it("should succeed if all children succeed", { 45 | behaviourB.m_operationResult = Status.SUCCESS; 46 | sequence.tick(null, 0.1); 47 | sequence.status.should.be(Status.SUCCESS); 48 | }); 49 | it("should report as running if its children are running", { 50 | behaviourA.m_operationResult = null; 51 | behaviourB.m_operationResult = null; 52 | sequence.tick(null, 0.1); 53 | sequence.status.should.be(Status.RUNNING); 54 | }); 55 | it("should fail if a child returns failure", { 56 | behaviourA.m_operationResult = Status.FAILURE; 57 | sequence.tick(null, 0.1); 58 | sequence.status.should.be(Status.FAILURE); 59 | }); 60 | it("should invalidate all its children when it terminates", { 61 | behaviourA.status.should.be(Status.INVALID); 62 | behaviourB.status.should.be(Status.INVALID); 63 | }); 64 | }); 65 | } 66 | } -------------------------------------------------------------------------------- /src/behaviour/hxbt/composites/SelectorBehaviour.hx: -------------------------------------------------------------------------------- 1 | package behaviour.hxbt.composites; 2 | 3 | import buddy.*; 4 | using buddy.Should; 5 | 6 | import hxbt.Behavior.Status; 7 | import behaviour.hxbt.MockBehaviour; 8 | import hxbt.composites.Selector; 9 | 10 | /** 11 | * ... 12 | * @author Kenton Hamaluik 13 | */ 14 | class SelectorBehaviour extends BuddySuite 15 | { 16 | public function new() 17 | { 18 | var selector:Selector = new Selector(); 19 | 20 | var behaviourA:MockBehaviour = new MockBehaviour(); 21 | var behaviourB:MockBehaviour = new MockBehaviour(); 22 | 23 | selector.add(behaviourA); 24 | selector.add(behaviourB); 25 | 26 | describe("Using a selector", { 27 | it("should initialize only the first child on the first tick", { 28 | selector.tick(null, 0.1); 29 | behaviourA.m_initializedCalled.should.be(1); 30 | behaviourB.m_initializedCalled.should.be(0); 31 | }); 32 | it("should not move on to the next child if the first one hasn't finished", { 33 | selector.tick(null, 0.1); 34 | behaviourA.m_updateCalled.should.be(2); 35 | behaviourB.m_initializedCalled.should.be(0); 36 | }); 37 | it("should move on to the next child if the first fails", { 38 | behaviourA.m_operationResult = Status.FAILURE; 39 | selector.tick(null, 0.1); 40 | selector.tick(null, 0.1); 41 | behaviourB.m_initializedCalled.should.be(1); 42 | }); 43 | it("should succeed if a child succeeds", { 44 | behaviourB.m_operationResult = Status.SUCCESS; 45 | selector.tick(null, 0.1); 46 | selector.status.should.be(Status.SUCCESS); 47 | }); 48 | it("should report as running if its children are running", { 49 | behaviourA.m_operationResult = null; 50 | behaviourB.m_operationResult = null; 51 | selector.tick(null, 0.1); 52 | selector.status.should.be(Status.RUNNING); 53 | }); 54 | it("should fail if all children report failure", { 55 | selector = new Selector(); 56 | selector.add(behaviourA); 57 | selector.add(behaviourB); 58 | behaviourA.m_operationResult = Status.FAILURE; 59 | behaviourB.m_operationResult = Status.FAILURE; 60 | 61 | selector.tick(null, 0.1); 62 | selector.tick(null, 0.1); 63 | 64 | selector.status.should.be(Status.FAILURE); 65 | }); 66 | it("should invalidate all its children when it terminates", { 67 | behaviourA.status.should.be(Status.INVALID); 68 | behaviourB.status.should.be(Status.INVALID); 69 | }); 70 | }); 71 | } 72 | } -------------------------------------------------------------------------------- /hxbt.hxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | "$(CompilerPath)/haxelib" run flow build $(TargetBuild) --$(BuildConfig) 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /tools/hxbtGraph.hxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | "$(CompilerPath)/haxelib" run flow build $(TargetBuild) --$(BuildConfig) 47 | 48 | 49 | 50 | 51 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/hxbt/loaders/BehaviorTreeJSONLoader.hx: -------------------------------------------------------------------------------- 1 | package hxbt.loaders; 2 | import hxbt.Behavior; 3 | import hxbt.BehaviorTree; 4 | import hxbt.composites.Parallel; 5 | import hxbt.composites.Sequence; 6 | import hxbt.composites.Selector; 7 | import hxbt.decorators.AlwaysFail; 8 | import hxbt.decorators.AlwaysSucceed; 9 | import hxbt.decorators.Include; 10 | import hxbt.decorators.Invert; 11 | import hxbt.decorators.UntilFail; 12 | import hxbt.decorators.UntilSuccess; 13 | 14 | /** 15 | * The BehaviorTreeJSONLoader loads a behavior from a JSON file 16 | * with a very simple structure. 17 | * The structure of the behavior tree JSON is the following: 18 | 19 | { 20 | "test_tree": { 21 | "hxbt.sequence": [ 22 | { "package.behavior0": [] }, 23 | { "package.behavior1": [] }, 24 | { "hxbt.selector": [ 25 | { "package.behavior2": [] }, 26 | { "package.behavior3": [] } 27 | ]} 28 | ] 29 | } 30 | } 31 | 32 | * @author Kristian Brodal 33 | * @author Kenton Hamaluik 34 | */ 35 | class BehaviorTreeJSONLoader 36 | { 37 | 38 | private function new() 39 | { 40 | 41 | } 42 | 43 | // Constructs the behavior tree 'treeName' defined in the JSON file 'JSON'. 44 | // Returns constructed tree if successful, returns null if unsuccessful. 45 | public static function FromJSONString(JSON : String, treeName : String) : BehaviorTree 46 | { 47 | return FromJSONObject(haxe.Json.parse(JSON), treeName); 48 | } 49 | 50 | // Constructs the behavior tree 'treeName' defined in the JSON file 'JSON'. 51 | // Returns constructed tree if successful, returns null if unsuccessful. 52 | public static function FromJSONObject(JSON : Dynamic, treeName : String) : BehaviorTree 53 | { 54 | if(!Reflect.hasField(JSON, treeName)) { 55 | throw "Behaviour tree '" + treeName + "' doesn't exist in this JSON!"; 56 | } 57 | 58 | var tree = Reflect.field(JSON, treeName); 59 | var roots:Array = Reflect.fields(tree); 60 | #if !behaviour 61 | if(roots.length != 1) { 62 | #else 63 | if(roots.length != 2) { 64 | #end 65 | throw "Behaviour tree '" + treeName + "' must have a singular root! It has " + roots.length + "!"; 66 | } 67 | var root = Reflect.field(tree, roots[0]); 68 | 69 | var tree:BehaviorTree = new BehaviorTree(); 70 | var rootBehaviour:Behavior = GetBehavior(root, treeName); 71 | tree.setRoot(rootBehaviour); 72 | return tree; 73 | } 74 | 75 | private static function GetBehavior(object : Dynamic, errorTrail : String):Behavior 76 | { 77 | // we should be recieving an array of fields 78 | // we actually only want there to be one field (JSON is kinda dumb that way) 79 | var rootFields:Array = Reflect.fields(object); 80 | if(rootFields.length != 1) { 81 | throw "Object '" + errorTrail + "' must have exactly 1 child! It has " + rootFields.length + "!"; 82 | } 83 | var name:String = rootFields[0]; 84 | var objects:Array = Reflect.field(object, name); 85 | 86 | switch(name) { 87 | case 'hxbt.sequence': { 88 | var sequence:Sequence = new Sequence(); 89 | var arrI:Int = 0; 90 | for(object in objects) { 91 | sequence.add(GetBehavior(object, errorTrail + "." + name + ".[" + arrI + "]")); 92 | arrI++; 93 | } 94 | return sequence; 95 | } 96 | 97 | case 'hxbt.selector': { 98 | var selector:Selector = new Selector(); 99 | var arrI:Int = 0; 100 | for(object in objects) { 101 | selector.add(GetBehavior(object, errorTrail + "." + name + ".[" + arrI + "]")); 102 | arrI++; 103 | } 104 | return selector; 105 | } 106 | 107 | case 'hxbt.parallel': { 108 | var parallel:Parallel = new Parallel(); 109 | var arrI:Int = 0; 110 | for(object in objects) { 111 | parallel.add(GetBehavior(object, errorTrail + "." + name + ".[" + arrI + "]")); 112 | arrI++; 113 | } 114 | return parallel; 115 | } 116 | 117 | case 'hxbt.alwaysfail': { 118 | if(objects.length > 1) { 119 | throw "hxbt.alwaysfail can't have more than one child!"; 120 | } 121 | var alwaysFail:AlwaysFail = new AlwaysFail(); 122 | if(objects.length > 0) { 123 | alwaysFail.setChild(GetBehavior(objects[0], errorTrail + "." + name)); 124 | } 125 | return alwaysFail; 126 | } 127 | 128 | case 'hxbt.alwayssucceed': { 129 | if(objects.length > 1) { 130 | throw "hxbt.alwayssucceed can't have more than one child!"; 131 | } 132 | var alwaysSucceed:AlwaysSucceed = new AlwaysSucceed(); 133 | if(objects.length > 0) { 134 | alwaysSucceed.setChild(GetBehavior(objects[0], errorTrail + "." + name)); 135 | } 136 | return alwaysSucceed; 137 | } 138 | 139 | case 'hxbt.include': { 140 | throw "hxbt.include is not supported in JSON yet!"; 141 | } 142 | 143 | case 'hxbt.invert': { 144 | if(objects.length != 1) { 145 | throw "hxbt.invert MUST have exactly one child!"; 146 | } 147 | var invert:Invert = new Invert(); 148 | invert.setChild(GetBehavior(objects[0], errorTrail + "." + name)); 149 | return invert; 150 | } 151 | 152 | case 'hxbt.untilfail': { 153 | if(objects.length != 1) { 154 | throw "hxbt.untilfail MUST have exactly one child!"; 155 | } 156 | var untilFail:UntilFail = new UntilFail(); 157 | untilFail.setChild(GetBehavior(objects[0], errorTrail + "." + name)); 158 | return untilFail; 159 | } 160 | 161 | case 'hxbt.untilsuccess': { 162 | if(objects.length != 1) { 163 | throw "hxbt.untilsuccess MUST have exactly one child!"; 164 | } 165 | var untilSuccess:UntilSuccess = new UntilSuccess(); 166 | untilSuccess.setChild(GetBehavior(objects[0], errorTrail + "." + name)); 167 | return untilSuccess; 168 | } 169 | 170 | default: { 171 | var t = Type.resolveClass(name); 172 | if(t == null) { 173 | throw "Can't resolve behavior class '" + name + "' @ '" + errorTrail + "'!"; 174 | } 175 | 176 | var inst = Type.createInstance(Type.resolveClass(name), []); 177 | if(!Std.is(inst, Behavior)) { 178 | throw "Error: class '" + name + "' isn't a behavior @ '" + errorTrail + "'!"; 179 | } 180 | return inst; 181 | } 182 | } 183 | } 184 | } --------------------------------------------------------------------------------