extends Node class_name StateMachine #machine a etats generique : elle ne connait ni le joueur, ni le HUD, ni le lancer. Reutilisable pour n'importe quel acteur #(joueur, PNJ, pokemon sauvage...) : poser la StateMachine sous l'acteur, y mettre des nodes qui heritent de State. #ce qui est propre au joueur vit ailleurs : le lancer dans GroundedState, les hints dans StateHints. signal state_changed(from : State, to : State) #from est null pour l'etat initial @export var initial_state : State @export var actor : Node #le node pilote par la machine, passe a chaque etat. Par defaut : le parent var curr_state : State var curr_state_name : String: #nom du node de l'etat courant (ex : "IdleState") get: return String(curr_state.name) if curr_state else "" var states : Dictionary = {} #nom du node (casse exacte) -> State var blocked_states : Dictionary = {} #noms des etats dont l'entree est refusee var _warned : Dictionary = {} #noms introuvables deja signales : un seul avertissement par nom, pas un par frame func _ready() -> void: if actor == null: actor = get_parent() #get all the state childrens for child in get_children(): if child is State: states[String(child.name)] = child child.machine = self #if initial state, transition to it if initial_state: await get_tree().create_timer(0.1).timeout initial_state.enter(actor) curr_state = initial_state state_changed.emit(null, initial_state) func _process(delta : float) -> void: if curr_state: curr_state.update(delta) func _physics_process(delta : float) -> void: if curr_state: curr_state.physics_update(delta) #---------- requetes ---------- func has_state(state_name : String) -> bool: return states.has(state_name) func is_in(state_name : String) -> bool: return curr_state_name == state_name func is_blocked(state_name : String) -> bool: return blocked_states.has(state_name) func set_state_blocked(state_name : String, blocked : bool) -> void: if blocked: blocked_states[state_name] = true else: blocked_states.erase(state_name) #---------- transitions ---------- func request(from : State, new_state_name : String, data : Dictionary = {}) -> bool: #appelee par State.request_transition() : renvoie true si la transition a eu lieu if from != curr_state: return false #l'etat demandeur n'est plus l'etat courant (transition deja faite pendant cette frame) var new_state : State = states.get(new_state_name) if new_state == null: _warn_unknown_state(from, new_state_name) return false if is_blocked(new_state_name): return false #exit the current state : il connait sa destination, et les donnees qu'il envoie (outgoing_data) sont lisibles from.next_state_name = new_state_name from.outgoing_data = data from.exit() from.outgoing_data = {} #enter the new state : il recoit les donnees de l'etat qu'on quitte new_state.enter_data = data new_state.enter(actor) curr_state = new_state state_changed.emit(from, new_state) return true func _warn_unknown_state(from : State, new_state_name : String) -> void: if _warned.has(new_state_name): return _warned[new_state_name] = true push_warning("StateMachine : %s demande une transition vers '%s', mais aucun node d'etat ne porte ce nom (etats disponibles : %s). Verifier le nom du node dans la scene." % [from.name, new_state_name, ", ".join(PackedStringArray(states.keys()))])