using System.Collections.ObjectModel; using System.Threading.Tasks; using Godot; using Godot.Collections; namespace KarrotStarterTemplate.Common.StateMachines; public class StateInputsPackage { /* * Potrzebujemy: * 1. Informacji jakie klawisze trzyma teraz gracz (np czy trzyma Shift, żeby przejść z Walk na Run) - done * 2. Globalnych flag, które sie nie resetują (np. czy gracz wcisnął C i jest w trybie kucania, wciśnięcie C ponownie wyłącza tę flagę) - done * 3. Informacji o tym czy w trakcie eventu gracz wcisnął dany klawisz (jeśli gracz kliknie atak w trakcie uniku to musimy zakolejkować to działanie * 4. Wartości w wektorach (np inputDirection z WASD) - done * 5. Informacji o tym, że skończyła się animacja, albo event z animnotify * * Gdy OnCheckRelevance coś zwraca, to nie powinniśmy przechodzić w ten state, tylko kolejkować to i sprawdzać czy aktualny stejt ma flagę "canExit" na true, * wtedy odpalać ostatni dostępny state? */ private readonly Dictionary _inputs = new Dictionary(); public void Add(string key, Variant value) { _inputs[key] = value; } public void Clear(string key) { _inputs.Remove(key); } public Variant Get(string key) { if (_inputs.TryGetValue(key, out Variant value)) { return value; } return new Variant(); } public Vector3 GetVector3(string key) { if (_inputs.TryGetValue(key, out var value)) { return value.AsVector3(); } return Vector3.Zero; } public string GetString(string key) { if (_inputs.TryGetValue(key, out var value)) { return value.AsString(); } return ""; } public bool GetBool(string key) { if (_inputs.TryGetValue(key, out var value)) { return value.AsBool(); } return false; } } [GlobalClass] public partial class StateMachine : Node { [Export] public Node3D owner { get; set; } [Export] public State defaultState { get; set; } [Export] public bool activateOnReady { get; set; } public StateInputsPackage package { get; private set; } = new StateInputsPackage(); public State currentState { get; private set; } public bool isTransitioning { get; private set; } = false; public bool active => currentState != null; public Dictionary states { get; private set; } = new Dictionary(); public CharacterBody3D ownerCharacterBody => (CharacterBody3D)owner; // Called when the node enters the scene tree for the first time. public override async void _Ready() { foreach (Node node in GetChildren()) { if (node is State state) { states.Add(state.id, state); } } if (activateOnReady) { await Activate(); } } public async Task Activate() { await TransitionToState(defaultState); } public async void Deactivate() { if (currentState != null) { await TransitionToState(null as State); } } public override void _PhysicsProcess(double delta) { currentState?.StatePhysicsProcess(delta); } // Called every frame. 'delta' is the elapsed time since the previous frame. public override void _Process(double delta) { if (currentState != null) { currentState.StateProcess(delta); if (!isTransitioning) { string nextState = currentState.CheckRelevance(); if (nextState != null) { TransitionToState(nextState); } } } } private Task TransitionToState(string stateId) { // if (currentState != null && currentState.id == stateId) // { // return Task.FromResult(false); // } return TransitionToState(states[stateId]); } private async Task TransitionToState(State state) { if (currentState == state && !state.canTransitionToSelf) { return false; } GD.Print("--"); GD.Print($"------------------------------------------"); GD.Print($"------Transitioning from {currentState?.id} to {state?.id}"); isTransitioning = true; if (currentState != null) { await currentState.Exit(); } if (state != null) { await state.Enter(); } currentState = state; GD.Print($"------Transition finished to {state?.id}"); GD.Print($"------------------------------------------"); GD.Print("--"); isTransitioning = false; return true; } }