using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace YKMoon.StateMachines
{
///
/// State machine's state.
///
public class AState
{
public virtual void OnEnter() { }
public virtual void OnUpdate(float deltaTime) { }
public virtual void OnExit() { }
}
///
/// State machine's condition.
///
public abstract class ACondition
{
public abstract bool IsMatch();
}
///
/// Example
///
/// //First you need Create A State Machine
///
/// //1.create states
/// IdleState idleState; //class IdleState:AState{...}
/// //2.create stateMachine
/// StateMachine machine = new StateMachine(idleState);
/// //3.add transition to this stateMachine
/// //class TimeCondition:ACondition{...}
/// machine.AddTransition(new TimeCondition(countDown:5f), StateMachine.anyState, idleState, 999);
/// //4.start this stateMachine
/// machine.Start();
///
/// //Then you can call StateMachine's Update in a monobehavior or anywhere you want.
///
/// Void Update()
/// {
/// machine.Update(Time.deltaTime)
/// }
///
///
public class StateMachine
{
public static readonly AnyState anyState = new AnyState();
public AState initState { get; private set; }
public AState currentState { get; private set; }
public StateMachineUpdateType updateType { get; private set; }
///
/// Key:from state, Value:transitions
///
private Dictionary> transitionDict = new Dictionary>();
private List states = new List();
///
/// Construct a state machine.
///
/// Not support null or AnyState type.
public StateMachine(AState initState, StateMachineUpdateType updateType = StateMachineUpdateType.Normal)
{
if(initState == null) {
throw new System.ArgumentNullException("initState");
}
if(initState is AnyState) {
throw new System.NotSupportedException("argument 'initState' can not be AnyState type");
}
this.initState = initState;
this.updateType = updateType;
AddState(initState);
}
///
/// Begin this machine.Will change state to initState.
///
public void Start()
{
ChangeState(initState);
StateMachineManager.Reg(this, updateType);
}
public void Update(float deltaTime)
{
if(currentState == null) {
return;
}
//1.Update current State.
currentState.OnUpdate(deltaTime);
//2.Find Any state transition that match condition
Transition nextTransition = GetHightPriotityTransitionMatchCondition(GetTransitions(anyState));
//3.Find normal state transition that match condition.
// If priority is higher than any state transition then use this transition.
if(transitionDict.ContainsKey(currentState)) {
var transition = GetHightPriotityTransitionMatchCondition(transitionDict[currentState]);
if(nextTransition == null || transition.priority > nextTransition.priority) {
nextTransition = transition;
}
}
//4.Change state
if(nextTransition != null) {
ChangeState(nextTransition.to);
}
}
///
/// Stop this machine.Will invoke last state's .
///
public void Stop()
{
currentState.OnExit();
currentState = null;
StateMachineManager.UnReg(this, updateType);
}
private void ChangeState(AState state)
{
if (state is AnyState) {
throw new System.NotSupportedException("argument 'state' can not be AnyState type");
}
if(currentState != null) {
currentState.OnExit();
}
currentState = state;
currentState.OnEnter();
}
///
/// Add a transition from a state to other state.
/// states and will add to this machine.
///
/// This transition's condition.Not support null.
/// From state. Not support null. Can use AnyState.
/// To state. Makesure be null. Can not use AnyState.
/// Transition's priority.The higher the value, the higher the priority.
public void AddTransition(ACondition condition, AState from, AState to, int priority = 0)
{
//1.Check arguments is supported
if (condition == null) {
throw new System.ArgumentNullException("condition");
}
if(from == null) {
throw new System.ArgumentNullException("from");
}
if(to == null) {
throw new System.ArgumentNullException("to");
}
if(to is AnyState) {
throw new System.NotSupportedException("argument 'to' can not be AnyState type");
}
//Add states to machine
AddState(from);
AddState(to);
//Add transition to machine. Make from state's transition list orderly.
var transition = new Transition(condition, from, to, priority);
if(!transitionDict.ContainsKey(from))
transitionDict.Add(from, new List());
transitionDict[from].Add(transition);
transitionDict[from].Sort((a, b) => b.priority - a.priority);
}
private void AddState(AState state)
{
if(!IsContainsState(state)) {
states.Add(state);
}
}
private bool IsContainsState(AState state)
{
if(state is AnyState) {
return true;
}
return states.Contains(state);
}
private List GetTransitions(AState state)
{
if(transitionDict.ContainsKey(currentState)) {
return transitionDict[currentState];
}
return null;
}
private Transition GetHightPriotityTransitionMatchCondition(List transitions)
{
if(transitions != null) {
foreach(var transition in transitions) {
if(transition.condition.IsMatch()) {
return transition;
}
}
}
return null;
}
public sealed class AnyState : AState {
internal AnyState() {}
}
internal class Transition
{
public int priority = 0;
public ACondition condition;
public AState from;
public AState to;
public Transition(ACondition condition, AState from, AState to, int priority)
{
this.condition = condition;
this.from = from;
this.to = to;
this.priority = priority;
}
}
}
}