You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

134 lines
4.5 KiB

2 years ago
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ZelleGames.Log;
namespace ZelleGames.StateMachine
{
/// <summary>
/// Generic abstract state machine with OnEnter and OnExit features
/// </summary>
public abstract class StateMachine : MonoBehaviour
{
/// <summary>
/// Readonly State currently executing
/// </summary>
public State CurrentState { get; protected set; }
/// <summary>
/// Drive the current state if present
/// </summary>
private void Update()
{
if (CurrentState != null)
{
CurrentState.Update(Time.deltaTime);
}
}
/// <summary>
/// Drive the current state's FixedUpdate method if state is present
/// </summary>
private void FixedUpdate()
{
if (CurrentState != null)
{
CurrentState.FixedUpdate(Time.fixedDeltaTime);
}
}
/// <summary>
/// Public facing function to change state based on a transition.
/// Relies on an implementation of <see cref="GetNewState(Transition)"/>
/// </summary>
/// <param name="t">The transition to pass in. Validitiy depends on the implementation of GetNewState</param>
public void TransitionState(Transition t)
{
var newState = GetNewState(t);
if (newState != null)
{
CurrentState?.OnExit();
CurrentState = newState;
CurrentState.StateMachine = this;
CurrentState.OnEnter();
} else
{
Logging.Log(LogLevel.WARN, $"Could not transition from {CurrentState} with {t}");
}
}
/// <summary>
/// Load a new state without consideration of the GetNewState function.
/// This is sessentially the "goto" of state machine management, use only if absolutely needed.
/// </summary>
/// <param name="s"></param>
public void ForceState(State s)
{
CurrentState.OnExit();
CurrentState = s;
CurrentState.OnEnter();
}
/// <summary>
/// Derived classes should provide an implementation that returns a new <see cref="State"/> depending
/// on the Transition passed in.
/// </summary>
/// <param name="t"></param>
/// <returns>The new state for a valid transition for the current state,
/// Null otherwise to maintain current state</returns>
protected abstract State GetNewState(Transition t);
}
/// <summary>
/// Transitions are passed into the state machine to move from one state to the next.
/// </summary>
public abstract class Transition
{
/// <summary>
/// Generic object that can contain any information this transition needs to pass into the next state
/// </summary>
public object Context { get; }
public Transition() : this(null){}
public Transition(object ctx)
{
Context = ctx;
}
}
/// <summary>
/// Describes a finite state for a <see cref="StateMachine"/>
/// in Unity.
/// </summary>
public abstract class State
{
/// <summary>
/// Reference to the parent statemachine. Derived classes can cast this reference into their specialized
/// class for specific statemachine functionality, or use this reference as is to operate on the base class
/// </summary>
public StateMachine StateMachine;
/// <summary>
/// Called every frame by the <see cref="StateMachine"/>
/// </summary>
/// <param name="deltaTime"></param>
public abstract void Update(float deltaTime);
/// <summary>
/// Called according to the Physics timing configuration by the <see cref="StateMachine"/>
/// </summary>
/// <param name="fixedDeltaTime"></param>
public virtual void FixedUpdate(float fixedDeltaTime) { }
/// <summary>
/// Called on the frame the machine switches to this state
/// </summary>
public virtual void OnEnter() { }
/// <summary>
/// Called on the last frame before this state is replaced.
/// </summary>
public virtual void OnExit() { }
}
}