I have an FSM as a scene with two states inside. It works for a single enemy, but I don’t understand how to implement it properly for all different enemies. Is it okay to link the FSM to BaseEnemy, or should we create another scene with all spawning enemies and include the FSM there? Which approach is correct and better?
FSM
extends Node
class_name FiniteStateMachine
var states:Dictionary = {}
@export var initial_state : EnemyState
var current_state : EnemyState
func _ready() -> void:
for child in get_children():
if child is EnemyState:
states[child.name.to_lower()] = child
child.state_transition.connect(change_state)
if initial_state:
initial_state.Enter()
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func change_state(old_state : EnemyState, new_state_name :String):
if old_state != current_state:
return
var new_state = states.get(new_state_name.to_lower())
if !new_state:
print("New state is empty")
return
if current_state:
current_state.Exit()
new_state.Enter()
current_state = new_state
Sounds like you can instantiate it to your enemy scene? Could you share your scene tree screen shots?
Yes, I wrote it in the BaseEnemy class. Is that a good decision?
The FSM retrieves the enemy from the Base class, and we pass the sprite’s animation to the state’s enter method. Below is all my code.
BaseEnemy
extends Node
class_name BaseEnemy
var damage:int
var speed:float
@export var sprite: AnimatedSprite2D
@export var fsm_scene_path: String = "res://enemies/FSM/FSM.tscn"
var fsm: FiniteStateMachine
func initialize(damage:int,speed:float) -> void:
self.speed = speed
self.damage = damage
func _ready() -> void:
var fsm_scene = load(fsm_scene_path)
if fsm_scene:
fsm = fsm_scene.instantiate() as FiniteStateMachine
add_child(fsm)
fsm.initialize(self)
print("FSM Added to the enemy ", name)
else:
print("Error FSM could not been added ", fsm_scene_path)
func get_character_body() -> CharacterBody2D:
return self.get_node("CharacterBody2D") as CharacterBody2D
func _process(delta: float) -> void:
pass
UpdatedFSM
extends Node
class_name FiniteStateMachine
var states:Dictionary = {}
@export var initial_state : EnemyState
var current_state : EnemyState
var owner_enemy:Enemy
func initialize(enemy: Node) -> void:
self.owner_enemy = enemy
for child in get_children():
if child is EnemyState:
states[child.name.to_lower()] = child
child.state_transition.connect(change_state)
if initial_state:
initial_state.Enter(self.owner_enemy)
current_state = initial_state
func _process(delta: float) -> void:
if current_state:
current_state.update(delta)
func change_state(old_state : EnemyState, new_state_name :String):
if old_state != current_state:
return
var new_state = states.get(new_state_name.to_lower())
if !new_state:
print("New state is empty")
return
if current_state:
current_state.Exit()
new_state.Enter()
current_state = new_state
ChaseState
extends EnemyState
class_name ChaseState
var player: CharacterBody2D = null
var enemy : Enemy
var enemy_character: CharacterBody2D
var sprite:AnimatedSprite2D
var move_direction: Vector2
func Enter(owner_enemy):
self.enemy = owner_enemy
self.enemy_character = self.enemy.get_character_body()
self.sprite = self.enemy_character.get_node("AnimatedSprite2D")
func Exit():
pass
func update(delta: float) -> void:
if !player:
player = get_tree().get_root().get_node("main").get_child(2).get_node("CharacterBody2D") as CharacterBody2D
if player and enemy:
move_direction = (player.global_position - self.enemy_character.global_position).normalized()
if self.enemy_character and self.enemy_character is CharacterBody2D:
move_direction = (player.global_position - self.enemy_character.global_position).normalized()
self.enemy_character.velocity = move_direction * self.enemy.speed
self.enemy_character.move_and_slide()
if self.sprite:
self.sprite.flip_h = player.global_position.x < self.enemy_character.global_position.x