Skip to content

Commit 6ddb170

Browse files
author
kagenashi
committed
New complex example, refactored BTNodeState with enum and several optimisations.
1 parent 596e808 commit 6ddb170

File tree

12 files changed

+112
-78
lines changed

12 files changed

+112
-78
lines changed

addons/behavior_tree/src/behavior_tree.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ onready var bt_root = get_child(0) as BTNode
1919

2020

2121
func _ready() -> void:
22-
assert(get_child_count() == 1)
22+
assert(get_child_count() == 1, "A Behavior Tree can only have one entry point.")
2323
bt_root.propagate_call("connect", ["abort_tree", self, "abort"])
2424
start()
2525

addons/behavior_tree/src/blackboard.gd

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,13 @@ export(Dictionary) var data: Dictionary
1515

1616

1717

18-
1918
func _enter_tree() -> void:
2019
data = data.duplicate()
2120

2221

23-
2422
func _ready() -> void :
2523
for key in data.keys():
26-
assert(key is String)
24+
assert(key is String, "Blackboard keys must be stored as strings.")
2725

2826

2927
func set_data(key: String, value) -> void:

addons/behavior_tree/src/bt_node.gd

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,11 @@ extends Node
55
# You don't usually need to instance this node directly.
66
# To define your behaviors, use and extend BTLeaf instead.
77

8-
class BTNodeState:
9-
var success: bool = true setget set_success
10-
var failure: bool = false setget set_failure
11-
var running: bool = false setget set_running
12-
13-
14-
func set_success(value: bool = true):
15-
if value == false:
16-
print_debug("Ignoring manual change of BTNodeState. Use setters.")
17-
return
18-
success = true
19-
failure = false
20-
running = false
21-
22-
23-
func set_failure(value: bool = true):
24-
if value == false:
25-
print_debug("Ignoring manual change of BTNodeState. Use setters.")
26-
return
27-
success = false
28-
failure = true
29-
running = false
30-
31-
32-
func set_running(value: bool = true):
33-
if value == false:
34-
print_debug("Ignoring manual change of BTNodeState. Use setters.")
35-
return
36-
success = false
37-
failure = false
38-
running = true
39-
40-
41-
8+
enum BTNodeState {
9+
FAILURE,
10+
SUCCESS,
11+
RUNNING,
12+
}
4213

4314
# (Optional) Emitted after a tick() call. True is success, false is failure.
4415
signal tick(result)
@@ -55,7 +26,7 @@ export(bool) var debug: bool = false
5526
# Turn this on to abort the tree after completion.
5627
export(bool) var abort_tree: bool
5728

58-
var state: BTNodeState = BTNodeState.new()
29+
var state: int setget set_state
5930

6031

6132

@@ -89,49 +60,51 @@ func _post_tick(agent: Node, blackboard: Blackboard, result: bool) -> void:
8960

9061
### BEGIN: RETURN VALUES ###
9162

92-
# Your _tick() must return on of the two following funcs.
63+
# Your _tick() must return one of the following functions.
9364

9465
# Return this to set the state to success.
9566
func succeed() -> bool:
96-
state.set_success()
67+
state = BTNodeState.SUCCESS
9768
return true
9869

9970

10071
# Return this to set the state to failure.
10172
func fail() -> bool:
102-
state.set_failure()
73+
state = BTNodeState.FAILURE
10374
return false
10475

10576

10677
# Return this to match the state to another state.
107-
func set_state(rhs: BTNodeState) -> bool:
108-
if rhs.success:
109-
return succeed()
110-
else:
111-
return fail()
78+
func set_state(rhs: int) -> bool:
79+
match rhs:
80+
BTNodeState.SUCCESS:
81+
return succeed()
82+
BTNodeState.FAILURE:
83+
return fail()
84+
85+
assert(false, "Invalid BTNodeState assignment. Can only set to success or failure.")
86+
return false
11287

11388
### END: RETURN VALUES ###
11489

11590

11691

117-
# You are not supposed to use this.
118-
# It's called automatically.
119-
# It won't do what you think.
92+
# Don't call this.
12093
func run():
121-
state.set_running()
94+
state = BTNodeState.RUNNING
12295

12396

12497
# You can use the following to recover the state of the node
12598
func succeeded() -> bool:
126-
return state.success
99+
return state == BTNodeState.SUCCESS
127100

128101

129102
func failed() -> bool:
130-
return state.failure
103+
return state == BTNodeState.FAILURE
131104

132105

133106
func running() -> bool:
134-
return state.running
107+
return state == BTNodeState.RUNNING
135108

136109

137110
# Or this, as a string.
@@ -163,21 +136,18 @@ func tick(agent: Node, blackboard: Blackboard) -> bool:
163136
var result = _tick(agent, blackboard)
164137

165138
if result is GDScriptFunctionState:
166-
# If you yield, the node must be running.
167-
# If you crash here it means you changed the state before yield.
168-
assert(running())
139+
assert(running(), "BTNode execution was suspended but it's not running. Did you succeed() or fail() before yield?")
169140
result = yield(result, "completed")
170141

171-
# If you complete execution (or don't yield anymore), the node can't be running.
172-
# If you crash here it means you forgot to return succeed() or fail().
173-
assert(not running())
142+
assert(not running(), "BTNode execution was completed but it's still running. Did you forget to return succeed() or fail()?")
174143

175144
# Do stuff after core behavior depending on the result
176145
_post_tick(agent, blackboard, result)
177146

178147
# Notify completion and new state (i.e. the result of the execution)
179148
emit_signal("tick", result)
180149

150+
# Queue tree abortion at the end of current tick
181151
if abort_tree:
182152
emit_signal("abort_tree")
183153

addons/behavior_tree/src/btnodes/bt_composite.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var bt_child: BTNode # Used to iterate over children
1515

1616

1717
func _ready():
18-
assert(get_child_count() > 1)
18+
assert(get_child_count() > 1, "A BTComposite must have more than one child.")
1919

2020

2121

addons/behavior_tree/src/btnodes/bt_decorator.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ onready var bt_child: BTNode = get_child(0) as BTNode
99

1010

1111
func _ready():
12-
assert(get_child_count() == 1)
12+
assert(get_child_count() == 1, "A BTDecorator can only have one child.")
1313

1414

1515
func _tick(agent: Node, blackboard: Blackboard) -> bool:

addons/behavior_tree/src/btnodes/bt_leaf.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ extends BTNode
99

1010

1111
func _ready():
12-
assert(get_child_count() == 0)
12+
assert(get_child_count() == 0, "A BTLeaf cannot have children.")
1313

addons/behavior_tree/src/btnodes/decorators/bt_guard.gd

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extends BTDecorator
88
# If you don't specify an unlocker, the unlock_if variable is useless and only the lock_time will
99
# be considered, and viceversa.
1010
# You can also choose to lock permanently or to lock on startup.
11+
#
1112
# A locked BTGuard will always return fail().
1213

1314
export(bool) var start_locked = false
@@ -41,6 +42,9 @@ func _on_locker_tick(_result):
4142
func lock():
4243
locked = true
4344

45+
if debug:
46+
print(name + " locked for " + str(lock_time) + " seconds")
47+
4448
if permanent:
4549
return
4650
elif unlocker:
@@ -51,10 +55,13 @@ func lock():
5155
else:
5256
yield(get_tree().create_timer(lock_time, false), "timeout")
5357
locked = false
58+
59+
if debug:
60+
print(name + " unlocked")
5461

5562

5663
func check_lock(current_locker: BTNode):
57-
if ((lock_if == 2 and not current_locker.running())
64+
if ((lock_if == 2 and not current_locker.running())
5865
or ( lock_if == 1 and current_locker.succeeded())
5966
or ( lock_if == 0 and current_locker.failed())):
6067
lock()

bt_example/agent.gd

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
extends Node
22

3+
const max_ammo = 10
4+
35
var is_alive = true
4-
var ammo = 5
6+
var ammo = max_ammo
7+
8+
59

610
func _ready():
11+
$Blackboard.set_data("target_aquired", false)
712
yield(get_tree().create_timer(3, false), "timeout")
813
print("\n - Agent: 'Target aquired!'\n")
914
$Blackboard.set_data("target_aquired", true)
15+
16+
17+
func _process(delta: float) -> void:
18+
set_process(false)
19+
yield(get_tree().create_timer(6.66, false), "timeout")
20+
$Blackboard.set_data("target_aquired", false)
21+
yield(get_tree().create_timer(2, false), "timeout")
22+
$Blackboard.set_data("target_aquired", true)
23+
set_process(true)

bt_example/ex_behavior_tree.tscn

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[gd_scene load_steps=11 format=2]
1+
[gd_scene load_steps=15 format=2]
22

33
[ext_resource path="res://addons/behavior_tree/src/btnodes/decorators/bt_always.gd" type="Script" id=1]
44
[ext_resource path="res://addons/behavior_tree/src/behavior_tree.gd" type="Script" id=2]
@@ -10,6 +10,10 @@
1010
[ext_resource path="res://addons/behavior_tree/src/btnodes/composites/bt_random_selector.gd" type="Script" id=8]
1111
[ext_resource path="res://addons/behavior_tree/src/btnodes/leaves/bt_wait.gd" type="Script" id=9]
1212
[ext_resource path="res://addons/behavior_tree/src/btnodes/decorators/bt_repeat.gd" type="Script" id=10]
13+
[ext_resource path="res://addons/behavior_tree/src/btnodes/decorators/bt_guard.gd" type="Script" id=11]
14+
[ext_resource path="res://bt_example/leaves/take_cover.gd" type="Script" id=12]
15+
[ext_resource path="res://addons/behavior_tree/src/btnodes/composites/bt_selector.gd" type="Script" id=13]
16+
[ext_resource path="res://addons/behavior_tree/src/btnodes/decorators/bt_repeat_until.gd" type="Script" id=14]
1317

1418
[node name="BehaviorTree" type="Node"]
1519
script = ExtResource( 2 )
@@ -19,39 +23,61 @@ debug = true
1923
[node name="BTSequence" type="Node" parent="."]
2024
script = ExtResource( 6 )
2125

22-
[node name="BTRandomSelector" type="Node" parent="BTSequence"]
26+
[node name="Always succeed" type="Node" parent="BTSequence"]
27+
script = ExtResource( 1 )
28+
always_what = 1
29+
30+
[node name="Lock while taking cover" type="Node" parent="BTSequence/Always succeed"]
31+
script = ExtResource( 11 )
32+
_locker = NodePath("../../BTSelector/Out of ammo\?/BTSequence/Repeat Until Success/Take Cover")
33+
_unlocker = NodePath("../../BTSelector/Out of ammo\?/BTSequence/Reload")
34+
unlock_if = 1
35+
36+
[node name="BTRandomSelector" type="Node" parent="BTSequence/Always succeed/Lock while taking cover"]
2337
script = ExtResource( 8 )
2438

25-
[node name="Wait 1 sec" type="Node" parent="BTSequence/BTRandomSelector"]
39+
[node name="Wait 1 sec" type="Node" parent="BTSequence/Always succeed/Lock while taking cover/BTRandomSelector"]
2640
script = ExtResource( 9 )
2741
debug = true
2842
wait_time = 1.0
2943

30-
[node name="Wait 2 sec" type="Node" parent="BTSequence/BTRandomSelector"]
44+
[node name="Wait 2 sec" type="Node" parent="BTSequence/Always succeed/Lock while taking cover/BTRandomSelector"]
3145
script = ExtResource( 9 )
3246
debug = true
3347
wait_time = 2.0
3448

35-
[node name="Always succeed" type="Node" parent="BTSequence"]
36-
script = ExtResource( 1 )
37-
always_what = 1
49+
[node name="BTSelector" type="Node" parent="BTSequence"]
50+
script = ExtResource( 13 )
3851

39-
[node name="Out of ammo\?" type="Node" parent="BTSequence/Always succeed"]
52+
[node name="Out of ammo\?" type="Node" parent="BTSequence/BTSelector"]
4053
script = ExtResource( 5 )
4154
debug = true
4255

43-
[node name="Reload" type="Node" parent="BTSequence/Always succeed/Out of ammo\?"]
56+
[node name="BTSequence" type="Node" parent="BTSequence/BTSelector/Out of ammo\?"]
57+
script = ExtResource( 6 )
58+
59+
[node name="Repeat Until Success" type="Node" parent="BTSequence/BTSelector/Out of ammo\?/BTSequence"]
60+
script = ExtResource( 14 )
61+
until_what = 1
62+
63+
[node name="Take Cover" type="Node" parent="BTSequence/BTSelector/Out of ammo\?/BTSequence/Repeat Until Success"]
64+
script = ExtResource( 12 )
65+
66+
[node name="Reload" type="Node" parent="BTSequence/BTSelector/Out of ammo\?/BTSequence"]
4467
script = ExtResource( 4 )
4568
debug = true
4669

47-
[node name="TargetAquired\?" type="Node" parent="BTSequence"]
70+
[node name="Repeat until fail" type="Node" parent="BTSequence/BTSelector"]
71+
script = ExtResource( 14 )
72+
73+
[node name="Target Aquired\?" type="Node" parent="BTSequence/BTSelector/Repeat until fail"]
4874
script = ExtResource( 3 )
4975
debug = true
5076

51-
[node name="BTRepeat" type="Node" parent="BTSequence/TargetAquired\?"]
77+
[node name="Repeat twice" type="Node" parent="BTSequence/BTSelector/Repeat until fail/Target Aquired\?"]
5278
script = ExtResource( 10 )
5379
times_to_repeat = 2
5480

55-
[node name="Shoot" type="Node" parent="BTSequence/TargetAquired\?/BTRepeat"]
81+
[node name="Shoot" type="Node" parent="BTSequence/BTSelector/Repeat until fail/Target Aquired\?/Repeat twice"]
5682
script = ExtResource( 7 )
5783
debug = true

bt_example/leaves/reload.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ func _tick(agent: Node, blackboard: Blackboard) -> bool:
44
assert("ammo" in agent)
55

66
yield(get_tree().create_timer(2, false), "timeout")
7-
agent.ammo = 5
7+
agent.ammo = agent.max_ammo
88
return succeed()

bt_example/leaves/shoot.gd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ func _tick(agent: Node, blackboard: Blackboard) -> bool:
44
assert("ammo" in agent)
55

66
if agent.ammo <= 0:
7+
print("No ammo!")
78
return fail()
89

910
yield(get_tree().create_timer(.4, false), "timeout")

bt_example/leaves/take_cover.gd

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
extends BTLeaf
2+
3+
4+
var distance_to_cover = 4
5+
6+
7+
func _tick(agent: Node, blackboard: Blackboard) -> bool:
8+
distance_to_cover -= 1
9+
print("Walking towards cover..")
10+
yield(get_tree().create_timer(1, false), "timeout")
11+
12+
if distance_to_cover == 0:
13+
print("Cover reached")
14+
distance_to_cover = 4
15+
return succeed()
16+
17+
return fail()
18+

0 commit comments

Comments
 (0)