Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion addons/behaviour_toolkit/behaviour_toolkit.gd
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
@icon("res://addons/behaviour_toolkit/icons/Gear.svg")
class_name BehaviourToolkit extends Node
## The main node of the BehaviourToolkit plugin.
## The main node of the Behaviour Toolkit plugin.
##
## All nodes of Behaviour Toolkit pluign extend it.
4 changes: 4 additions & 0 deletions addons/behaviour_toolkit/behaviour_tree/bt_behaviour.gd
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
@icon("res://addons/behaviour_toolkit/icons/BTBehaviour.svg")
class_name BTBehaviour extends BehaviourToolkit
## Base class for Behaviours used in the behaviour tree.
##
## TODO


## Status enum returned by nodes executing behaviours.
enum BTStatus {
SUCCESS,
FAILURE,
Expand Down
18 changes: 17 additions & 1 deletion addons/behaviour_toolkit/behaviour_tree/bt_composite.gd
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
@tool
@icon("res://addons/behaviour_toolkit/icons/BTComposite.svg")
class_name BTComposite extends BTBehaviour
## Basic Composite node for Behaviour Tree.
##
## By itself is not doing much but is aware of it's children. You can use it
## to implement custom composite behaviours.


# Connecting signal using @onready to omit the need to use super() call
# in _ready() of extended nodes if they override _ready().
@onready var __connect_child_order_changed: int = child_order_changed.connect(_on_child_order_changed)


## The leaves under the composite node.
@onready var leaves: Array = get_children()


func _on_child_order_changed() -> void:
if Engine.is_editor_hint():
return

leaves = get_children()


func _get_configuration_warnings() -> PackedStringArray:
var warnings: Array = []

Expand All @@ -17,7 +33,7 @@ func _get_configuration_warnings() -> PackedStringArray:
warnings.append("BTComposite node must be a child of BTComposite or BTRoot node.")

if children.size() == 0:
warnings.append("BTComposite node must have at least one child.")
warnings.append("BTComposite node should have at least one child to work.")

if children.size() == 1:
warnings.append("BTComposite node should have more than one child.")
Expand Down
22 changes: 19 additions & 3 deletions addons/behaviour_toolkit/behaviour_tree/bt_decorator.gd
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
@tool
@icon("res://addons/behaviour_toolkit/icons/BTDecorator.svg")
class_name BTDecorator extends BTBehaviour
## Basic Decorator node for Behaviour Tree.
##
## By itself is not doing much but is aware of it's children and holds reference
## to its first child (index 0 child). You can use it to implement custom
## decorators.


# Connecting signal using @onready to omit the need to use super() call
# in _ready() of extended nodes if they override _ready().
@onready var __connect_child_order_changed: int = child_order_changed.connect(_on_child_order_changed)

## The leaf the decorator is decorating.
@onready var leaf: BTBehaviour = _get_leaf()
@onready var leaf: BTBehaviour = _get_leaf()


func _get_leaf() -> BTBehaviour:
Expand All @@ -14,6 +23,13 @@ func _get_leaf() -> BTBehaviour:
return get_child(0)


func _on_child_order_changed() -> void:
if Engine.is_editor_hint():
return

leaf = _get_leaf()


func _get_configuration_warnings() -> PackedStringArray:
var warnings: Array = []

Expand All @@ -26,8 +42,8 @@ func _get_configuration_warnings() -> PackedStringArray:
if children.size() == 0:
warnings.append("Decorator node should have a child.")
elif children.size() > 1:
warnings.append("Decorator node should have only one child.")
warnings.append("Decorator node has more than one child. Only the first child will be used, other sibilings will be ingored.")
elif not children[0] is BTBehaviour:
warnings.append("Decorator node should have a BTBehaviour node as a child.")
warnings.append("Decorator nodes first child must be a BTBehaviour node.")

return warnings
5 changes: 2 additions & 3 deletions addons/behaviour_toolkit/behaviour_tree/bt_leaf.gd
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
@tool
@icon("res://addons/behaviour_toolkit/icons/BTLeaf.svg")
class_name BTLeaf extends BTBehaviour
## Basic Leaf node used as a base to model and write custom behaviours for
## [BTRoot]


func tick(_delta: float, _actor: Node, _blackboard: Blackboard) -> BTStatus:
Expand All @@ -15,8 +17,5 @@ func _get_configuration_warnings() -> PackedStringArray:

if not parent is BTBehaviour and not parent is BTRoot:
warnings.append("BTLeaf node must be a child of BTBehaviour or BTRoot node.")

if children.size() > 0:
warnings.append("BTLeaf node must not have any children.")

return warnings
81 changes: 70 additions & 11 deletions addons/behaviour_toolkit/behaviour_tree/bt_root.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
@icon("res://addons/behaviour_toolkit/icons/BTRoot.svg")
class_name BTRoot extends BehaviourToolkit
## Node used as a base parent (root) of a Behaviour Tree
##
## TODO


enum ProcessType {
Expand All @@ -13,7 +15,7 @@ enum ProcessType {
@export var autostart: bool = false

## Can be used to select if Behaviour Tree tick() is calculated on
## rendering (IDLE) frame or physics (PHYSICS) frame.
## rendering (IDLE) frame or physics (PHYSICS) frame.
## [br]
## More info: [method Node._process] and [method Node._physics_process]
@export var process_type: ProcessType = ProcessType.PHYSICS:
Expand All @@ -25,11 +27,29 @@ enum ProcessType {
@export var blackboard: Blackboard


var active: bool = false
var active: bool = false:
set(value):
active = value
if value and entry_point != null:
_setup_processing()
else:
set_physics_process(false)
set_process(false)

var current_status: BTBehaviour.BTStatus


@onready var entry_point = get_child(0)
# Connecting signal using @onready to omit the need to use super() call
# in _ready() of extended nodes if they override _ready().
@onready var __connect_child_order_changed: int = child_order_changed.connect(_on_child_order_changed)

@onready var entry_point: BTBehaviour = get_entry_point()


func _validate_property(property: Dictionary) -> void:
if property.name == "autostart" and get_parent() is FSMStateIntegratedBT:
autostart = false
property.usage = PROPERTY_USAGE_NO_EDITOR


func _ready() -> void:
Expand All @@ -41,14 +61,31 @@ func _ready() -> void:

if blackboard == null:
blackboard = _create_local_blackboard()

if autostart:

if entry_point == null:
return
elif autostart:
active = true

if not process_type:
process_type = ProcessType.PHYSICS

_setup_processing()
## Swap this [BTRoot] nodes current entry point with the provided one.
## If root has no [BTBehaviour] as a child the provided one will be added.
## [br][br]
## Old behaviour nodes are freed and the new behaviour will be started on the
## next [code]tick()[/code] callback call.
func swap_entry_point(behaviour: BTBehaviour,
force_readable_name: bool = false, keep_groups: bool = false) -> void:

if keep_groups == true and entry_point != null:
for g in entry_point.get_groups():
if not behaviour.is_in_group(g):
behaviour.add_to_group(g, true)

if entry_point == null:
add_child(behaviour, force_readable_name)
else:
entry_point.queue_free()
add_child(behaviour, force_readable_name)


func _physics_process(delta: float) -> void:
Expand All @@ -60,6 +97,8 @@ func _process(delta: float) -> void:


func _process_code(delta: float) -> void:
# TODO Would be nice to remove it in future and make use of set_process()
# and set_physics_process()
if not active:
return

Expand All @@ -73,21 +112,41 @@ func _create_local_blackboard() -> Blackboard:

# Configures process type to use, if BTree is not active both are disabled.
func _setup_processing() -> void:
if Engine.is_editor_hint():
set_physics_process(false)
set_process(false)
return

set_physics_process(process_type == ProcessType.PHYSICS)
set_process(process_type == ProcessType.IDLE)


func get_entry_point() -> BTBehaviour:
var first_child := get_child(0)
if first_child is BTBehaviour:
return first_child
else:
return null


func _on_child_order_changed() -> void:
if Engine.is_editor_hint():
return

entry_point = get_entry_point()


func _get_configuration_warnings() -> PackedStringArray:
var warnings: Array = []

var children = get_children()

if children.size() == 0:
warnings.append("Behaviour Tree needs to have one Behaviour child.")
warnings.append("Behaviour Tree needs to have a Behaviour child to work.")
elif children.size() == 1:
if not children[0] is BTBehaviour:
warnings.append("The child of Behaviour Tree needs to be a Behaviour.")
warnings.append("The child of Behaviour Tree needs to be a BTBehaviour.")
elif children.size() > 1:
warnings.append("Behaviour Tree can have only one Behaviour child.")
warnings.append("Behaviour Tree has more than one child. Only the first child will be used, other sibilings will be ingored.")

return warnings
Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
@tool
@icon("res://addons/behaviour_toolkit/icons/BTCompositeIntegration.svg")
class_name BTIntegratedFSM extends BTComposite
## [BTComposite] node of Behaviour Tree that can handle [FiniteStateMachine]
##
## When [BTIntegratedFSM] finds node of type [FiniteStateMachine] as it's first
## child it starts the state machine and runs it on every
## [code]tick()[/code] until the FSM node itself will stop returning
## [enum BTBehaviour.BTStatus.RUNNING].
## [br][br]
## After FSM returns either of [enum BTBehaviour.BTStatus.SUCCESS] or
## [enum BTBehaviour.BTStatus.FAILURE] then the final status of the FSM will be returned
## as the final status of the [BTIntegratedFSM] node.
## [br][br]
## In case where [BTComposite] can't find [FiniteStateMachine] as it's first
## child [enum BTBehaviour.BTStatus.FAILURE] will be returned.


var state_machine: FiniteStateMachine = null
## Default status in case [BTIntegratedFSM] will not find [FiniteStateMachine]
## child as a first node.
@export_enum("SUCCESS", "FAILURE") var default_status: String = "FAILURE":
set(value):
if value == "SUCCESS":
_default_status = BTStatus.SUCCESS
else:
_default_status = BTStatus.FAILURE


var _default_status: BTStatus = BTStatus.FAILURE


# Connecting signal using @onready to omit the need to use super() call
# in _ready() of extended nodes if they override _ready().
@onready var __connect_finite_state_machine_changed: int = child_order_changed.connect(_finite_state_machine_changed)

@onready var state_machine: FiniteStateMachine = _get_machine()

func _ready():
if not Engine.is_editor_hint():
state_machine = _get_machine()


func tick(_delta: float, _actor: Node, _blackboard: Blackboard) -> BTStatus:
if state_machine == null:
return _default_status

if state_machine.active == false:
state_machine.start()

Expand All @@ -20,25 +53,56 @@ func tick(_delta: float, _actor: Node, _blackboard: Blackboard) -> BTStatus:
return state_machine.current_bt_status


## Swap this composite nodes current state machine with the provided one.
## If state has no [FiniteStateMachine] as a child the provided one will be added.
## [br][br]
## Old state machine is freed and the new machine will be started on the next
## [code]tick()[/code] callback call.
func swap_finite_state_machine(finite_state_machine: FiniteStateMachine,
force_readable_name: bool = false, keep_groups: bool = false) -> void:

if keep_groups == true and state_machine != null:
for g in state_machine.get_groups():
if not finite_state_machine.is_in_group(g):
finite_state_machine.add_to_group(g, true)

if state_machine == null:
add_child(finite_state_machine, force_readable_name)
else:
state_machine.queue_free()
add_child(finite_state_machine, force_readable_name)


func _get_machine() -> FiniteStateMachine:
if get_child_count() == 0:
return null
else:
return get_child(0)
if get_child(0) is FiniteStateMachine:
return get_child(0)

return null


func _finite_state_machine_changed() -> void:
if Engine.is_editor_hint():
return

state_machine = _get_machine()
state_machine.autostart = false


func _get_configuration_warnings():
var warnings: Array = []
var children = get_children()

if children.size() == 0:
warnings.append("BTIntegratedFSM must have a child node. The first child will be used as the state machine.")
warnings.append("BTIntegratedFSM should have a child node to work. The first child will be used as the state machine.")

if children.size() > 1:
warnings.append("BTIntegratedFSM can only have one child node. The first child will be used as the state machine.")
warnings.append("BTIntegratedFSM has more than one child node. Only the first child will be used as the state machine.")

if children.size() == 1:
if not children[0] is FiniteStateMachine:
warnings.append("BTIntegratedFSM's child node must be a FiniteStateMachine. The first child will be used as the state machine.")
warnings.append("BTIntegratedFSM's first child node must be a FiniteStateMachine. The first child will be used as the state machine.")

return warnings
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ var rng = RandomNumberGenerator.new()
var active_leave: BTBehaviour


func _ready():
# Connecting signal using @onready to omit the need to use super() call
# in _ready() of extended nodes if they override _ready().
@onready var __connect_hash_seed: int = ready.connect(_hash_seed)


func _hash_seed():
if use_seed:
rng.seed = hash(seed)

Expand Down
Loading