ℹ️ Select 'Choose Exercise', or randomize 'Next Random Exercise' in selected language.

Choose Exercise:
Timer 00:00
WPM --
Score --
Acc --
Correct chars --

GDScript Complex State Machine for Enemy AI

GDScript

Goal -- WPM

Ready
Exercise Algorithm Area
1extends Node
2
3# --- State Machine Definition ---
4
5# Define states as constants for clarity and to avoid typos
6const STATE_IDLE = "idle"
7const STATE_PATROLLING = "patrolling"
8const STATE_CHASING = "chasing"
9const STATE_ATTACKING = "attacking"
10const STATE_FLEEING = "fleeing"
11const STATE_SEARCHING = "searching"
12
13var current_state = STATE_IDLE
14var previous_state = null
15
16# State-specific data
17var patrol_points: Array = []
18var current_patrol_index = 0
19var target_node: Node = null
20var player_last_known_position: Vector3 = Vector3.ZERO
21var detection_radius: float = 100.0
22var attack_range: float = 20.0
23var flee_threshold: float = 50.0
24var flee_speed_multiplier: float = 1.5
25var patrol_speed: float = 50.0
26var chase_speed: float = 100.0
27var attack_cooldown: float = 1.0
28var time_since_last_attack: float = 0.0
29var search_timer: float = 5.0
30var time_in_search: float = 0.0
31
32# References to other nodes (e.g., player, navigation agent)
33@onready var navigation_agent = $NavigationAgent3D
34@onready var animation_player = $AnimationPlayer
35@onready var health_component = $HealthComponent # Assuming a HealthComponent exists
36
37# --- Signals ---
38
39signal state_changed(new_state: String, old_state: String)
40
41# --- Initialization ---
42
43func _ready():
44# Initialize patrol points if they are set in the inspector
45if patrol_points.is_empty():
46printerr("Patrol points not set for enemy AI.")
47# Potentially set default patrol points or disable patrolling
48
49# Set initial state
50set_state(STATE_IDLE)
51
52# --- State Management ---
53
54func set_state(new_state: String):
55if current_state == new_state:
56return # Already in this state
57
58previous_state = current_state
59current_state = new_state
60print("Enemy AI state changed from %s to %s" % [previous_state, current_state])
61emit_signal("state_changed", new_state, previous_state)
62
63# Reset state-specific variables on transition
64match new_state:
65STATE_IDLE:
66target_node = null
67player_last_known_position = Vector3.ZERO
68time_in_search = 0.0
69STATE_PATROLLING:
70current_patrol_index = 0
71navigation_agent.set_velocity_computed_callback(null)
72animation_player.play("walk") # Assuming 'walk' animation
73STATE_CHASING:
74# Target node should already be set by the transition logic
75pass
76STATE_ATTACKING:
77animation_player.play("attack") # Assuming 'attack' animation
78# Attack logic will be handled in _process
79STATE_FLEEING:
80animation_player.play("run") # Assuming 'run' animation
81# Fleeing logic will be handled in _process
82STATE_SEARCHING:
83time_in_search = search_timer
84animation_player.play("search") # Assuming 'search' animation
85
86# --- Core Logic (Process) ---
87
88func _process(delta):
89# Handle global cooldowns or timers
90if time_since_last_attack < attack_cooldown:
91time_since_last_attack += delta
92
93# State-specific behavior
94match current_state:
95STATE_IDLE:
96handle_idle(delta)
97STATE_PATROLLING:
98handle_patrolling(delta)
99STATE_CHASING:
100handle_chasing(delta)
101STATE_ATTACKING:
102handle_attacking(delta)
103STATE_FLEEING:
104handle_fleeing(delta)
105STATE_SEARCHING:
106handle_searching(delta)
107
108# Check for global state transitions (e.g., player detection)
109check_global_transitions(delta)
110
111# --- State Handler Functions ---
112
113func handle_idle(delta):
114# In idle, wait for something to happen or transition to patrolling
115# For simplicity, let's say it transitions to patrolling after a short delay
116# Or if a player is detected, it will transition via check_global_transitions
117pass # No specific action in idle, relies on global checks
118
119func handle_patrolling(delta):
120if patrol_points.is_empty():
121set_state(STATE_IDLE)
122return
123
124var current_point = patrol_points[current_patrol_index]
125var distance_to_point = global_position.distance_to(current_point)
126
127# Move towards the current patrol point
128navigation_agent.set_target_position(current_point)
129var next_path_position = navigation_agent.get_next_path_position()
130var direction = (next_path_position - global_position).normalized()
131velocity = direction * patrol_speed
132move_and_slide()
133
134# If close enough to the point, move to the next one
135if distance_to_point < 1.0: # Tolerance for reaching the point
136current_patrol_index = (current_patrol_index + 1) % patrol_points.size()
137# If we've completed a full patrol cycle, consider transitioning
138# For now, just continue patrolling
139
140func handle_chasing(delta):
141if target_node == null or not is_instance_valid(target_node):
142set_state(STATE_SEARCHING)
143return
144
145var distance_to_target = global_position.distance_to(target_node.global_position)
146
147# If within attack range, transition to attacking
148if distance_to_target <= attack_range and time_since_last_attack >= attack_cooldown:
149set_state(STATE_ATTACKING)
150return
151
152# If player is too far or health is low, consider fleeing
153if health_component != null and health_component.current_health < health_component.max_health * 0.3:
154set_state(STATE_FLEEING)
155return
156
157# Move towards the target
158navigation_agent.set_target_position(target_node.global_position)
159var next_path_position = navigation_agent.get_next_path_position()
160var direction = (next_path_position - global_position).normalized()
161velocity = direction * chase_speed
162move_and_slide()
163
164func handle_attacking(delta):
165# Attack logic is primarily triggered by the animation or a timer
166# For this example, assume attack animation plays and we transition back after it finishes
167# Or if the target moves out of range
168if animation_player.is_playing() and animation_player.current_animation == "attack":
169# Keep facing the target
170look_at(target_node.global_position)
171# Attack logic would typically happen here, e.g., dealing damage
172# For simplicity, we'll transition back after a short duration or if target is lost
173pass # Attack animation handles duration
174else:
175# If animation finished or was interrupted
176time_since_last_attack = 0.0 # Reset cooldown
177if target_node != null and is_instance_valid(target_node):
178var distance_to_target = global_position.distance_to(target_node.global_position)
179if distance_to_target <= attack_range:
180set_state(STATE_CHASING) # Re-engage if still in range
181else:
182set_state(STATE_CHASING) # Go back to chasing if out of range
183else:
184set_state(STATE_SEARCHING)
185
186func handle_fleeing(delta):
187if target_node == null or not is_instance_valid(target_node):
188set_state(STATE_SEARCHING)
189return
190
191# Flee away from the player
192var flee_direction = (global_position - target_node.global_position).normalized()
193var flee_target_position = global_position + flee_direction * 50.0 # Flee a set distance
194
195navigation_agent.set_target_position(flee_target_position)
196var next_path_position = navigation_agent.get_next_path_position()
197var direction = (next_path_position - global_position).normalized()
198velocity = direction * chase_speed * flee_speed_multiplier
199move_and_slide()
200
201# If player is no longer a threat (e.g., out of range, or health is high)
202var distance_to_target = global_position.distance_to(target_node.global_position)
203if distance_to_target > flee_threshold * 2.0 or (health_component != null and health_component.current_health > health_component.max_health * 0.7):
204set_state(STATE_SEARCHING)
205
206func handle_searching(delta):
207if time_in_search > 0:
208time_in_search -= delta
209# Look around randomly or in a pattern
210# For simplicity, just stay put and look
211pass
212else:
213# Finished searching, decide next action
214if target_node != null and is_instance_valid(target_node):
215# If player was last seen, maybe go to last known position
216set_state(STATE_CHASING) # Or go back to patrolling
217else:
218set_state(STATE_PATROLLING)
219
220# --- Global Transition Checks ---
221
222func check_global_transitions(delta):
223# Check for player detection
224var player = get_tree().get_first_node_in_group("player") # Assuming player is in 'player' group
225if player != null:
226var distance_to_player = global_position.distance_to(player.global_position)
227
228# Player detection logic
229if distance_to_player <= detection_radius:
230if current_state != STATE_CHASING and current_state != STATE_ATTACKING and current_state != STATE_FLEEING:
231target_node = player
232player_last_known_position = player.global_position
233set_state(STATE_CHASING)
234return # Prioritize chasing if detected
235else:
236# Player out of detection range
237if current_state == STATE_CHASING or current_state == STATE_ATTACKING:
238# If player was just in range and is now out, start searching
239player_last_known_position = player.global_position # Update last known position
240set_state(STATE_SEARCHING)
241return
242
243# Check for low health to flee
244if health_component != null and health_component.current_health < health_component.max_health * 0.3:
245if current_state != STATE_FLEEING:
246set_state(STATE_FLEEING)
247return
248
249# If player is lost and we are not actively chasing/attacking/fleeing
250if target_node == null and current_state != STATE_PATROLLING and current_state != STATE_IDLE:
251set_state(STATE_SEARCHING)
252
253# --- Helper Functions ---
254
255func move_and_slide():
256# Placeholder for actual movement logic, e.g., using CharacterBody3D
257# This would typically involve setting velocity and calling move_and_slide()
258# For this example, we'll assume it's handled elsewhere or is a simplified representation.
259pass
260
261func look_at(target_position: Vector3):
262# Placeholder for looking at a target position
263# This would typically involve rotating the node's basis
264pass
265
266func get_velocity() -> Vector3:
267# Placeholder for getting current velocity
268return velocity
269
270func set_velocity(new_velocity: Vector3):
271# Placeholder for setting current velocity
272velocity = new_velocity
Algorithm description viewbox

GDScript Complex State Machine for Enemy AI

Algorithm description:

This GDScript code defines a sophisticated state machine for an enemy AI in a 3D game. It manages various AI behaviors like idling, patrolling, chasing the player, attacking, fleeing, and searching. The state machine uses signals to notify other systems about state changes and includes logic for transitioning between states based on player proximity, AI health, and timers. This is crucial for creating believable and responsive enemy behaviors in games.

Algorithm explanation:

This state machine implements a finite state automaton (FSA) pattern. The `current_state` variable dictates the AI's current behavior, and transitions occur based on conditions checked in `check_global_transitions` and within individual state handlers. Each state (`STATE_IDLE`, `STATE_PATROLLING`, etc.) has a corresponding `handle_` function that executes its logic. Transitions are managed by `set_state`, which updates `current_state`, `previous_state`, and emits a `state_changed` signal. The `_process` function continuously updates the AI's behavior and checks for global transitions. Time complexity for processing is O(S) where S is the number of states, as each state's handler is called once per frame, and global checks are constant time. Space complexity is O(1) for the state variables, but can increase if states manage complex data structures. Edge cases include null target nodes, empty patrol points, and low health. The correctness relies on ensuring all possible transitions are covered and that state-specific logic correctly updates AI parameters like velocity and target.

Pseudocode:

class EnemyAI:
  states = [IDLE, PATROLLING, CHASING, ATTACKING, FLEEING, SEARCHING]
  currentState = IDLE
  previousState = null
  
  function onReady():
    set_state(IDLE)

  function setState(newState):
    if currentState == newState:
      return
    previousState = currentState
    currentState = newState
    emit state_changed signal
    reset state-specific variables

  function onProcess(delta):
    handleGlobalCooldowns()
    switch currentState:
      case IDLE:
        handle_idle(delta)
      case PATROLLING:
        handle_patrolling(delta)
      # ... other states ...
    check_global_transitions(delta)

  function check_global_transitions(delta):
    detect_player()
    check_low_health()
    handle_lost_player()

  # ... handler functions for each state ...
  # ... helper functions for movement, looking, etc. ...