You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
CrowdControl/Assets/Scripts/AI/FSM_Template/StateMachine.cs

122 lines
3.1 KiB
C#

3 months ago
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.AI;
using Object = System.Object;
// Notes
// 1. What a finite state machine is
// 2. Examples where you'd use one
// AI, Animation, Game State
// 3. Parts of a State Machine
// States & Transitions
// 4. States - 3 Parts
// Tick - Why it's not Update()
// OnEnter / OnExit (setup & cleanup)
// 5. Transitions
// Separated from states so they can be re-used
// Easy transitions from any state
public class StateMachine
{
private class Transition
{
public Func<bool> Condition {get; }
public AIState To { get; }
public AIState From { get; }
public Transition(AIState from, AIState to, Func<bool> condition)
{
From = from;
To = to;
Condition = condition;
}
}
private static readonly List<Transition> EmptyTransitions = new List<Transition>(0);
private readonly Dictionary<Type, List<Transition>> _transitions = new Dictionary<Type,List<Transition>>();
private readonly List<Transition> _anyTransitions = new List<Transition>();
private List<Transition> _currentTransitions = new List<Transition>();
public AIState CurrentState { get; private set; }
// 0 = no decision delay
private float _decisionDelay = 0;
private float _timeOfNextPossibleDecision;
public StateMachine(float decisionDelay = 0)
{
_decisionDelay = decisionDelay;
}
public void Tick()
{
Transition transition = GetTransition();
if (transition != null)
SetState(transition.To);
CurrentState?.Tick();
}
public void SetState(AIState newState)
{
if (newState == CurrentState)
return;
if (_decisionDelay > 0)
{
// Wait for next possible decision time
if (Time.time < _timeOfNextPossibleDecision)
return;
_timeOfNextPossibleDecision = Time.time + _decisionDelay;
}
CurrentState?.OnExit();
CurrentState = newState;
_transitions.TryGetValue(CurrentState.GetType(), out _currentTransitions);
if (_currentTransitions == null)
_currentTransitions = EmptyTransitions;
CurrentState.OnEnter();
}
public void AddTransition(AIState from, AIState to, Func<bool> predicate)
{
if (_transitions.TryGetValue(from.GetType(), out var transitions) == false)
{
transitions = new List<Transition>();
_transitions[from.GetType()] = transitions;
}
transitions.Add(new Transition(from, to, predicate));
}
public void AddAnyTransition(AIState state, Func<bool> predicate)
{
_anyTransitions.Add(new Transition(null, state, predicate));
}
private Transition GetTransition()
{
foreach (Transition transition in _anyTransitions)
{
if (transition.Condition())
return transition;
}
foreach (Transition transition in _currentTransitions)
{
if (transition.Condition() && transition.From == CurrentState)
return transition;
}
return null;
}
}