godot-karrot-starter-theme/Common/StateMachines/StateMachine.cs
2024-11-12 18:09:47 +01:00

192 lines
4.9 KiB
C#

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<string, Variant> _inputs = new Dictionary<string, Variant>();
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<string, State> states { get; private set; } = new Dictionary<string, State>();
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<bool> TransitionToState(string stateId)
{
// if (currentState != null && currentState.id == stateId)
// {
// return Task.FromResult(false);
// }
return TransitionToState(states[stateId]);
}
private async Task<bool> 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;
}
}