2026-02-26 23:13:57 +01:00
|
|
|
|
#nullable enable
|
|
|
|
|
|
using System.Collections.Generic;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
using Godot;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Cirno.Scripts.Components.FSM;
|
|
|
|
|
|
|
2025-06-10 16:33:43 +02:00
|
|
|
|
public abstract partial class StateMachineBase<TKey, TType> : Node, IStateMachine<TKey, TType>
|
2025-03-04 17:50:16 +01:00
|
|
|
|
where TKey : notnull
|
|
|
|
|
|
where TType : Node
|
|
|
|
|
|
{
|
2026-02-26 23:13:57 +01:00
|
|
|
|
[Signal]
|
|
|
|
|
|
public delegate void StateChangedEventHandler(Variant from, Variant to);
|
|
|
|
|
|
|
2025-03-04 17:50:16 +01:00
|
|
|
|
public Dictionary<TKey, IState<TKey, TType>> States { get; set; } = new();
|
|
|
|
|
|
public TKey CurrentStateIndex { get; set; }
|
2026-02-26 23:13:57 +01:00
|
|
|
|
public TKey PreviousStateIndex { get; private set; }
|
2025-03-04 17:50:16 +01:00
|
|
|
|
public IState<TKey, TType> CurrentState => States[CurrentStateIndex];
|
|
|
|
|
|
public abstract TKey InitialState { get; protected set; }
|
|
|
|
|
|
|
2026-02-26 23:13:57 +01:00
|
|
|
|
private TType _mainObject = default!;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
public TType MainObject => _mainObject;
|
2026-02-26 23:13:57 +01:00
|
|
|
|
|
|
|
|
|
|
// Internal flag to indicate if a state has been set previously
|
|
|
|
|
|
private bool _hasState;
|
|
|
|
|
|
|
2025-03-04 17:50:16 +01:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-02-26 23:13:57 +01:00
|
|
|
|
|
2025-06-26 14:03:36 +02:00
|
|
|
|
public TKey GetState()
|
|
|
|
|
|
{
|
|
|
|
|
|
return CurrentState.StateId;
|
|
|
|
|
|
}
|
2026-02-26 23:13:57 +01:00
|
|
|
|
|
2025-03-04 17:50:16 +01:00
|
|
|
|
public void SetState(TKey stateId)
|
|
|
|
|
|
{
|
2026-02-26 23:13:57 +01:00
|
|
|
|
if (_hasState)
|
2025-03-04 17:50:16 +01:00
|
|
|
|
{
|
2026-02-26 23:13:57 +01:00
|
|
|
|
PreviousStateIndex = CurrentStateIndex;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
CurrentState.ExitState();
|
|
|
|
|
|
}
|
2026-02-26 23:13:57 +01:00
|
|
|
|
|
|
|
|
|
|
var from = _hasState ? CurrentStateIndex.ToString() : string.Empty;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
CurrentStateIndex = stateId;
|
2026-02-26 23:13:57 +01:00
|
|
|
|
var to = CurrentStateIndex.ToString();
|
2025-03-04 17:50:16 +01:00
|
|
|
|
CurrentState.EnterState();
|
2026-02-26 23:13:57 +01:00
|
|
|
|
_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);
|
2025-03-04 17:50:16 +01:00
|
|
|
|
}
|
2026-02-26 23:13:57 +01:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-03-04 17:50:16 +01:00
|
|
|
|
public override void _Process(double delta)
|
|
|
|
|
|
{
|
2026-02-26 23:13:57 +01:00
|
|
|
|
if (!_hasState) return;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
CurrentState.ProcessState(delta);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void _PhysicsProcess(double delta)
|
|
|
|
|
|
{
|
2026-02-26 23:13:57 +01:00
|
|
|
|
if (!_hasState) return;
|
2025-03-04 17:50:16 +01:00
|
|
|
|
CurrentState.PhysicsProcessState(delta);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|