Behavior

Behaviors helps NPCs and Enemies to work with a limited set of states, this gives flexibility on controlling how they act in the world. It is basically a state machine within a state machine.

How behaviors work:

Behaviors are managed by the class BehaviorManager. Each behavior contains a StateManager with a unique set of states for the current behavior. It can be used to control state flow for NPCs and Enemies.

BehaviorManager

The BehaviorManager is initialized by the owner, then it initializes the StateMachine which then initializes all the states passing the reference of the owner responsible for all the states and behaviors.

init

It is called by the owner, initializing all the behaviors and states within each behavior. It then passes the reference of the owner & change to the initial state for all the behaviors.

change_behavior

It changes the current behavior, runs the exit() function from the previous behavior and then enter() function from the current behavior, acting as some sort of _ready() and exit_tree() for the states.

physics_process & process

It is fueled by the _physics_process & _process from the owner. It won't run, unless the behavior & state is active.

StateManager

The StateManager works similarly to the BehaviorManager, however it controls only states, instead of states & behaviors.

BaseBehavior

BaseBehavior is the base class that any other base behavior can inherit.

Example: The BaseBehavior contains all the variables and functions necessary to work within the BehaviorManager. BaseBehavior > EnemyBaseBehavior > PouncerBehavior

BaseState

BaseState is the base class that any other base state can inherit.

Example: The BaseState contains all the variables and functions necessary to work within the StateManager. BaseState > EnemyBaseState > IdleState

Flow

Image

Adding a new base behavior:

To add a new base behavior you need to go through two steps.

1) Add a base behavior script extending BaseBehavior in res://src/AI/BehaviorManager/BaseBehaviors/ and name it NameBaseBehavior.gd, change the "Name" to whatever name your behavior has.

2) Each base behavior must have a new init function to pass the reference of the context. Behaviors must never extend the BaseBehavior directly.

class_name EnemyBaseBehavior
extends BaseBehavior

var enemy

func init(_context) -> void:
	enemy = _context
	state_manager.init(enemy)

func enter() -> void:
	.enter()

func exit() -> void:
	.exit()

func process(_delta: float) -> void:
	pass

func physics_process(_delta: float) -> void:
	pass

Adding a new behavior:

To add a new behavior you need to go through six steps.

1) Add a behavior script extending the base behavior you want in res://src/AI/BehaviorManager/Behaviors/ and name it NameBehavior.gd, change the "Name" to whatever name your behavior has.

2) Add a new Node scene with the behavior script just created in res://src/AI/BehaviorManager/Behaviors/.

3) To the recently created behavior scene, create a new StateManager Node as a child and attach the StateManager.gd to it.

4) Add all the necessary states as children of the StateManager node & attach the state script to each.

5) Set the Starting State NodePath parameter of the StateManager to the desired starting state you want for the behavior.

6) Don't forget to initialize the BehaviorManager from the entity you will be using in the _ready function.

class_name PouncerBehavior
extends EnemyBaseBehavior

export var proximity_distance:int = 80

func enter() -> void:
	.enter()

func exit() -> void:
	.exit()

func process(_delta: float) -> void:
	var state = state_manager.current_state.name
	if state != "Telegraph" and state != "Attack":
		if context.sight_target:
			var distance = context.global_position.distance_to(context.sight_target.global_position)
			if  distance <= proximity_distance:
				context.change_state("Telegraph")
	

func physics_process(_delta: float) -> void:
	pass

Adding a new base state:

To add a new state you need to go through two steps.

1) Add a base state script extending BaseState in res://src/AI/BehaviorManager/BaseStates/ and name it NameBaseState.gd, change the "Name" to whatever name your base state has.

2) Each base state must have a new init function to pass the reference of the context. States must never extend the BaseState directly.

class_name EnemyBaseState
extends BaseState

var enemy:Enemy
export var animation_name:String

func init(_context):
	enemy = _context

func enter() -> void:
	enemy.animation_player.play(animation_name)
	.enter()

func physics_process(_delta: float) -> void:
	pass

func process(_delta: float) -> void:
	pass

func exit() -> void:
	.exit()

Adding a new state:

To add a new state you need to go through six steps.

1) Add a state script extending the base state you want in res://src/AI/BehaviorManager/States/ and name it NameState.gd, change the "Name" to whatever name your state has.

2) Add a new Node scene with the state script just created in res://src/AI/BehaviorManager/States/.

#Idle State Example
extends EnemyBaseState

func enter() -> void:
	.enter()

func exit() -> void:
	.exit()

func process(_delta: float) -> void:
	.process(_delta)

func physics_process(_delta: float) -> void:
	.physics_process(_delta)