Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add: Dynamic changes to Behaviour Patterns at runtime Nr.2 #73

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions addons/behaviour_toolkit/behaviour_tree/bt_behaviour.gd
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class_name BTBehaviour extends BehaviourToolkit
## which control the flow of the behaviours in Behaviour Tree system.


## Status enum returned by nodes executing behaviours.
enum BTStatus {
SUCCESS,
FAILURE,
Expand Down
17 changes: 16 additions & 1 deletion addons/behaviour_toolkit/behaviour_tree/bt_composite.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,27 @@ class_name BTComposite extends BTBehaviour
##
## Composites can hold multiple behaviour nodes and evalute/execute them
## based on custom logic based on their return values.
## [br][br]
## 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 @@ -21,7 +36,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
21 changes: 18 additions & 3 deletions addons/behaviour_toolkit/behaviour_tree/bt_decorator.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ class_name BTDecorator extends BTBehaviour
##
## Decorators are used to augment the behaviour of a leaf.[br]
## Think of it as another layer of logic that is executed before the leaf.
## [br][br]
## 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 @@ -18,6 +26,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 @@ -30,8 +45,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
3 changes: 0 additions & 3 deletions addons/behaviour_toolkit/behaviour_tree/bt_leaf.gd
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,4 @@ 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
84 changes: 73 additions & 11 deletions addons/behaviour_toolkit/behaviour_tree/bt_root.gd
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,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 @@ -30,9 +30,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
var entry_point: Node = null


# 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 @@ -46,14 +66,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 @@ -65,6 +102,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 @@ -78,21 +117,44 @@ 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:
if not get_child_count():
return null

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
Expand Up @@ -12,9 +12,36 @@ class_name BTIntegratedFSM extends BTComposite
## [enum BTBehaviour.BTStatus.RUNNING]. If FSM return
## [enum BTBehaviour.BTStatus.SUCCESS] or [enum BTBehaviour.BTStatus.FAILURE]
## the child FSM is stopped.
## After that, the final status of the FSM will be returned
## as the final status of the [BTIntegratedFSM] node.
## [br][br]
## 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]
## In case where [BTComposite] can't find [FiniteStateMachine] as it's first
## child [enum BTBehaviour.BTStatus.FAILURE] will be returned.


## 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

var state_machine: FiniteStateMachine = null

# 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():
Expand All @@ -24,6 +51,9 @@ func _ready():


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 @@ -33,25 +63,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() -> PackedStringArray:
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