cirnogodot/Scripts/Components/FSM/StateMachineBase.cs

99 lines
No EOL
2.8 KiB
C#

#nullable enable
using System.Collections.Generic;
using Godot;
namespace Cirno.Scripts.Components.FSM;
public abstract partial class StateMachineBase<TKey, TType> : Node, IStateMachine<TKey, TType>
where TKey : notnull
where TType : Node
{
[Signal]
public delegate void StateChangedEventHandler(Variant from, Variant to);
public Dictionary<TKey, IState<TKey, TType>> States { get; set; } = new();
public TKey CurrentStateIndex { get; set; }
public TKey PreviousStateIndex { get; private set; }
public IState<TKey, TType> CurrentState => States[CurrentStateIndex];
public abstract TKey InitialState { get; protected set; }
private TType _mainObject = default!;
public TType MainObject => _mainObject;
// Internal flag to indicate if a state has been set previously
private bool _hasState;
public override void _Ready()
{
_mainObject = this.GetParent<TType>();
var children = GetChildren();
foreach (var child in children)
{
if (child is IState<TKey, TType> state)
{
States.Add(state.StateId, state);
state.Init(this);
}
}
SetState(InitialState);
}
public TKey GetState()
{
return CurrentState.StateId;
}
public void SetState(TKey stateId)
{
if (_hasState)
{
PreviousStateIndex = CurrentStateIndex;
CurrentState.ExitState();
}
var from = _hasState ? CurrentStateIndex.ToString() : string.Empty;
CurrentStateIndex = stateId;
var to = CurrentStateIndex.ToString();
CurrentState.EnterState();
_hasState = true;
EmitSignal(nameof(StateChanged), from, to);
}
/// <summary>
/// Returns the first descendant under the main object (root node that owns this state machine)
/// that matches the requested type T, or null if none found. This lets states fetch
/// modules attached to the actor without per-state editor wiring.
/// </summary>
public T? GetModule<T>() where T : Node
{
if (MainObject is null) return default;
return FindInChildren<T>(MainObject);
}
private static T? FindInChildren<T>(Node parent) where T : Node
{
foreach (var obj in parent.GetChildren())
{
if (obj is T t) return t;
if (obj is Node node)
{
var found = FindInChildren<T>(node);
if (found is not null) return found;
}
}
return default;
}
public override void _Process(double delta)
{
if (!_hasState) return;
CurrentState.ProcessState(delta);
}
public override void _PhysicsProcess(double delta)
{
if (!_hasState) return;
CurrentState.PhysicsProcessState(delta);
}
}