Melee Weapon State Machine Refactor
Description
We refactored the code of the states within the melee state machine. This was done to simplify and optimise the code as some flaws were found in the code. For this we made some timer objects coupled to the required states in instead of remaking new timers each time we enter the function.
Code implementation
Melee weapon handler This script is used to get a reference to the node owning this scene.
extends Node3D# Used to get reference to player/enemy in their respective scenes.@export var actor : CharacterBody3D
Base attack state: Sets the base functions for the other states as well as some important variables used in other classes.
class_name BaseCombatStateextends BaseState
@export var ANIMATION_NAME: Stringvar melee_combat_transition_state : Signal
var root_actor: CharacterBody3Dvar weapon: MeleeWeapon
func setup(_weapon_node: MeleeWeapon, _melee_combat_transition_state: Signal, _root_actor: CharacterBody3D) -> void: if not _weapon_node: print("Weapon does not exist! Please provide a valid weapon node.") return weapon = _weapon_node root_actor = _root_actor melee_combat_transition_state = _melee_combat_transition_state
func enter(_previous_state, _information: Dictionary = {}) -> void: pass
Idle state: This state is used to reset the weapon and wait on the trigger to attack.
## IdleState: The weapon is idle and waiting for input.class_name IdleStateextends BaseCombatState
# Constants# Defines this state as IDLEconst STATE_TYPE: int = WeaponEnums.WeaponState.IDLE
var hit_area: Area3Dvar valid_attack : bool = false
# Set up the weapon and cache important nodesfunc setup(weapon_node: MeleeWeapon, melee_combat_transition_state: Signal, root_actor: CharacterBody3D) -> void: super(weapon_node, melee_combat_transition_state, root_actor) hit_area = weapon_node.hit_area
# This is only excecuted if the root actor is an enemy.# This looks for the player within the weapon's attack area and then transitions to windup like player.func process(delta: float) -> void: var targets: Array[Node3D] = hit_area.get_overlapping_bodies() for target in targets: if(target == GameManager.chicken_player): valid_attack = true else: valid_attack = false if(valid_attack): melee_combat_transition_state.emit(WeaponEnums.WeaponState.WINDUP, {})
# Checks for player input (attack button press)func input(event: InputEvent) -> void: if event.is_action_pressed("attack"): # Switch to the WINDUP state when attacking melee_combat_transition_state.emit(WeaponEnums.WeaponState.WINDUP, {})
Windup state: Uses a timer to give a windup time before the attack takes place. Can be used with heavier weapons to give a feeling of more impact. Could add extra animation later. After the timer runs out transition to the attack state.
## WindupState: The weapon is preparing to attack.class_name WindupStateextends BaseCombatState
# Constantsconst STATE_TYPE: int = WeaponEnums.WeaponState.WINDUP# Variables@onready var windup_timer: Timer = %WindupTimer
# When entering this state, start the windup timerfunc enter(_previous_state, _information: Dictionary = {}) -> void: if weapon.current_weapon.windup_time <= 0: melee_combat_transition_state.emit(WeaponEnums.WeaponState.ATTACKING, {}) return elif weapon.current_weapon.windup_time > 0: windup_timer.wait_time = weapon.current_weapon.windup_time windup_timer.start()
# When exiting this state, stop the windup timerfunc exit() -> void: if windup_timer: windup_timer.stop()
# When the windup timer runs out, switch to the ATTACKING statefunc _on_windup_timer_timeout() -> void: melee_combat_transition_state.emit(WeaponEnums.WeaponState.ATTACKING, {})
Attack state: Uses a timer to give time for the attack animation to take place. Also checks for collision with objects to deal damage. When the timer runs out transitions to the cooldown state.
## AttackingState: The weapon is actively attacking.class_name AttackingStateextends BaseCombatState
# Constantsconst STATE_TYPE: int = WeaponEnums.WeaponState.ATTACKING
var hit_area: Area3D
@onready var attack_timer: Timer = %AttackTimer
# Set up the weapon and cache important nodesfunc setup(weapon_node: MeleeWeapon, melee_combat_transition_state: Signal, root_actor: CharacterBody3D) -> void: super(weapon_node, melee_combat_transition_state, root_actor) hit_area = weapon_node.hit_area
# When entering this state, start the attack timer and attackfunc enter(_previous_state, _information: Dictionary = {}) -> void: attack_timer.wait_time = weapon.current_weapon.attack_duration attack_timer.start() _attack()
# When exiting this state, stop and remove the attack timerfunc exit() -> void: if attack_timer: attack_timer.stop()
# When the attack timer runs out, switch to the cooldown statefunc _on_attack_timer_timeout() -> void: melee_combat_transition_state.emit(WeaponEnums.WeaponState.COOLDOWN)
func _attack() -> void: if not hit_area: print("HitArea not found!") return
# Get targets for the given area in the attack area. Check which actor is making the attack # and corresponding to what actor makes the attack deal damage to certain types of targets var targets: Array[Node3D] = hit_area.get_overlapping_bodies() if(root_actor == GameManager.chicken_player): for target in targets: if target is Enemy: SignalManager.weapon_hit_target.emit(target, weapon.current_weapon.damage) else: for target in targets: if target == GameManager.chicken_player: SignalManager.weapon_hit_target.emit(target, weapon.current_weapon.damage)
Cooldown state: Uses timer for end delay after the attack. This gives some recovery time to attacks. After the timer finishes transition to the idle state.
## CooldownState: The weapon is cooling down after an attack.class_name CooldownStateextends BaseCombatState
# Constantsconst STATE_TYPE: int = WeaponEnums.WeaponState.COOLDOWN # Defines this state as COOLDOWN# Variables@onready var cooldown_timer: Timer = %CooldownTimer
# When entering this state, start the cooldown timerfunc enter(_previous_state, _information: Dictionary = {}) -> void: # Create a timer that lasts as long as the weapon's cooldown time cooldown_timer.wait_time = weapon.current_weapon.attack_duration cooldown_timer.start()
# When exiting this state, stop and remove the cooldown timerfunc exit() -> void: if cooldown_timer: cooldown_timer.stop()
# When the cooldown timer runs out, switch back to the IDLE statefunc _on_cooldown_timer_timeout() -> void: melee_combat_transition_state.emit(WeaponEnums.WeaponState.IDLE, {})