// Animancer // Copyright 2020 Kybernetik // using System; using UnityEngine; namespace Animancer.FSM { /// <summary> /// A simple keyless Finite State Machine system. /// <para></para> /// This class doesn't keep track of any states other than the currently active one. /// See <see cref="StateMachine{TKey, TState}"/> for a system that allows states to be pre-registered and accessed /// using a separate key. /// </summary> [HelpURL(StateExtensions.APIDocumentationURL + "StateMachine_1")] public partial class StateMachine<TState> where TState : class, IState<TState> { /************************************************************************************************************************/ /// <summary>The current state.</summary> public TState CurrentState { get; private set; } /************************************************************************************************************************/ /// <summary> /// Constructs a new <see cref="StateMachine{TState}"/>. /// </summary> public StateMachine() { } /// <summary> /// Constructs a new <see cref="StateMachine{TState}"/> and immediately enters the `defaultState`. /// </summary> public StateMachine(TState defaultState) { CurrentState = defaultState; defaultState.OnEnterState(); } /************************************************************************************************************************/ /// <summary> /// Checks if it is currently possible to enter the specified `state`. This requires /// <see cref="IState{TState}.CanExitState"/> on the <see cref="CurrentState"/> and /// <see cref="IState{TState}.CanEnterState"/> on the specified `state` to both return true. /// </summary> public bool CanSetState(TState state) { if (CurrentState != null && !CurrentState.CanExitState(state)) return false; if (state != null && !state.CanEnterState(CurrentState)) return false; return true; } /************************************************************************************************************************/ /// <summary> /// Attempts to enter the specified `state` and returns true if successful. /// <para></para> /// This method returns true immediately if the specified `state` is already the <see cref="CurrentState"/>. /// To allow directly re-entering the same state, use <see cref="TryResetState"/> instead. /// </summary> public bool TrySetState(TState state) { if (CurrentState == state) return true; else return TryResetState(state); } /************************************************************************************************************************/ /// <summary> /// Attempts to enter the specified `state` and returns true if successful. /// <para></para> /// This method does not check if the `state` is already the <see cref="CurrentState"/>. To do so, use /// <see cref="TrySetState"/> instead. /// </summary> public bool TryResetState(TState state) { if (!CanSetState(state)) return false; ForceSetState(state); return true; } /************************************************************************************************************************/ /// <summary> /// Calls <see cref="IState{TState}.OnExitState"/> on the <see cref="CurrentState"/> then changes to the /// specified `state` and calls <see cref="IState{TState}.OnEnterState"/> on it. /// <para></para> /// This method does not check <see cref="IState{TState}.CanExitState"/> or /// <see cref="IState{TState}.CanEnterState"/>. To do that, you should use <see cref="TrySetState"/> instead. /// </summary> public void ForceSetState(TState state) { #if UNITY_EDITOR var owned = state as IOwnedState<TState>; if (owned != null && owned.OwnerStateMachine != this) { throw new InvalidOperationException( "You are attempting to use a state in a machine that is not its owner." + "\n State: " + state + "\n Machine: " + ToString()); } #endif if (CurrentState != null) CurrentState.OnExitState(); CurrentState = state; if (CurrentState != null) state.OnEnterState(); } /************************************************************************************************************************/ /// <summary> /// Returns a string describing the type of this state machine and its <see cref="CurrentState"/>. /// </summary> public override string ToString() { return GetType().Name + " -> " + CurrentState; } /************************************************************************************************************************/ } }