234 lines
8.9 KiB
C#
234 lines
8.9 KiB
C#
|
using System.Linq;
|
||
|
using System.Collections.Generic;
|
||
|
using NodeCanvas.Framework;
|
||
|
using ParadoxNotion.Design;
|
||
|
using UnityEngine;
|
||
|
using Logger = ParadoxNotion.Services.Logger;
|
||
|
using ParadoxNotion;
|
||
|
|
||
|
namespace NodeCanvas.StateMachines
|
||
|
{
|
||
|
|
||
|
///<summary> Use FSMs to create state like behaviours</summary>
|
||
|
[GraphInfo(
|
||
|
packageName = "NodeCanvas",
|
||
|
docsURL = "https://nodecanvas.paradoxnotion.com/documentation/",
|
||
|
resourcesURL = "https://nodecanvas.paradoxnotion.com/downloads/",
|
||
|
forumsURL = "https://nodecanvas.paradoxnotion.com/forums-page/"
|
||
|
)]
|
||
|
[CreateAssetMenu(menuName = "ParadoxNotion/NodeCanvas/FSM Asset")]
|
||
|
public class FSM : Graph
|
||
|
{
|
||
|
///<summary>Transition Calling Mode (see "EnterState")</summary>
|
||
|
public enum TransitionCallMode
|
||
|
{
|
||
|
Normal = 0,
|
||
|
Stacked = 1,
|
||
|
Clean = 2,
|
||
|
}
|
||
|
|
||
|
private List<IUpdatable> updatableNodes;
|
||
|
private IStateCallbackReceiver[] callbackReceivers;
|
||
|
private Stack<FSMState> stateStack;
|
||
|
private bool enterStartStateFlag;
|
||
|
|
||
|
public event System.Action<IState> onStateEnter;
|
||
|
public event System.Action<IState> onStateUpdate;
|
||
|
public event System.Action<IState> onStateExit;
|
||
|
public event System.Action<IState> onStateTransition;
|
||
|
|
||
|
///<summary>The current FSM state</summary>
|
||
|
public FSMState currentState { get; private set; }
|
||
|
///<summary>The previous FSM state</summary>
|
||
|
public FSMState previousState { get; private set; }
|
||
|
|
||
|
///<summary>The current state name. Null if none</summary>
|
||
|
public string currentStateName => currentState != null ? currentState.name : null;
|
||
|
|
||
|
///<summary>The previous state name. Null if none</summary>
|
||
|
public string previousStateName => previousState != null ? previousState.name : null;
|
||
|
|
||
|
public override System.Type baseNodeType => typeof(FSMNode);
|
||
|
public override bool requiresAgent => true;
|
||
|
public override bool requiresPrimeNode => true;
|
||
|
public override bool isTree => false;
|
||
|
public override bool allowBlackboardOverrides => true;
|
||
|
sealed public override bool canAcceptVariableDrops => false;
|
||
|
public sealed override PlanarDirection flowDirection => PlanarDirection.Auto;
|
||
|
|
||
|
///----------------------------------------------------------------------------------------------
|
||
|
|
||
|
protected override void OnGraphInitialize() {
|
||
|
//we may be loading in async
|
||
|
ThreadSafeInitCall(GatherCallbackReceivers);
|
||
|
updatableNodes = new List<IUpdatable>();
|
||
|
for ( var i = 0; i < allNodes.Count; i++ ) {
|
||
|
if ( allNodes[i] is IUpdatable ) {
|
||
|
updatableNodes.Add((IUpdatable)allNodes[i]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnGraphStarted() {
|
||
|
stateStack = new Stack<FSMState>();
|
||
|
enterStartStateFlag = true;
|
||
|
}
|
||
|
|
||
|
protected override void OnGraphUpdate() {
|
||
|
|
||
|
if ( enterStartStateFlag ) {
|
||
|
//use a flag so that other nodes can do stuff on graph started
|
||
|
enterStartStateFlag = false;
|
||
|
EnterState((FSMState)primeNode, TransitionCallMode.Normal);
|
||
|
}
|
||
|
|
||
|
if ( currentState != null ) {
|
||
|
|
||
|
//Update defer IUpdatables
|
||
|
for ( var i = 0; i < updatableNodes.Count; i++ ) {
|
||
|
updatableNodes[i].Update();
|
||
|
}
|
||
|
|
||
|
//this can only happen if FSM stoped just now (from the above update)
|
||
|
if ( currentState == null ) { Stop(false); return; }
|
||
|
|
||
|
//Update current state
|
||
|
currentState.Execute(agent, blackboard);
|
||
|
|
||
|
//this can only happen if FSM stoped just now (from the above update)
|
||
|
if ( currentState == null ) { Stop(false); return; }
|
||
|
|
||
|
if ( onStateUpdate != null && currentState.status == Status.Running ) {
|
||
|
onStateUpdate(currentState);
|
||
|
}
|
||
|
|
||
|
//this can only happen if FSM stoped just now (from the above update)
|
||
|
if ( currentState == null ) { Stop(false); return; }
|
||
|
|
||
|
//state has nowhere to go..
|
||
|
if ( currentState.status != Status.Running && currentState.outConnections.Count == 0 ) {
|
||
|
//...but we have a stacked state -> pop return to it
|
||
|
if ( stateStack.Count > 0 ) {
|
||
|
var popState = stateStack.Pop();
|
||
|
EnterState(popState, TransitionCallMode.Normal);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//...and no updatables -> stop
|
||
|
if ( !updatableNodes.Any(n => n.status == Status.Running) ) {
|
||
|
Stop(true);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//if null state, stop.
|
||
|
if ( currentState == null ) {
|
||
|
Stop(false);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void OnGraphStoped() {
|
||
|
if ( currentState != null ) {
|
||
|
if ( onStateExit != null ) {
|
||
|
onStateExit(currentState);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
previousState = null;
|
||
|
currentState = null;
|
||
|
stateStack = null;
|
||
|
}
|
||
|
|
||
|
///<summary>Enter a state providing the state itself</summary>
|
||
|
public bool EnterState(FSMState newState, TransitionCallMode callMode) {
|
||
|
|
||
|
if ( !isRunning ) {
|
||
|
Logger.LogWarning("Tried to EnterState on an FSM that was not running", LogTag.EXECUTION, this);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( newState == null ) {
|
||
|
Logger.LogWarning("Tried to Enter Null State", LogTag.EXECUTION, this);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ( currentState != null ) {
|
||
|
if ( onStateExit != null ) { onStateExit(currentState); }
|
||
|
currentState.Reset(false);
|
||
|
if ( callMode == TransitionCallMode.Stacked ) {
|
||
|
stateStack.Push(currentState);
|
||
|
if ( stateStack.Count > 5 ) {
|
||
|
Logger.LogWarning("State stack exceeds 5. Ensure that you are not cycling stack calls", LogTag.EXECUTION, this);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ( callMode == TransitionCallMode.Clean ) {
|
||
|
stateStack.Clear();
|
||
|
}
|
||
|
|
||
|
previousState = currentState;
|
||
|
currentState = newState;
|
||
|
|
||
|
if ( onStateTransition != null ) { onStateTransition(currentState); }
|
||
|
if ( onStateEnter != null ) { onStateEnter(currentState); }
|
||
|
currentState.Execute(agent, blackboard);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
///<summary>Trigger a state to enter by it's name. Returns the state found and entered if any</summary>
|
||
|
public FSMState TriggerState(string stateName, TransitionCallMode callMode) {
|
||
|
|
||
|
var state = GetStateWithName(stateName);
|
||
|
if ( state != null ) {
|
||
|
EnterState(state, callMode);
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
Logger.LogWarning("No State with name '" + stateName + "' found on FSM '" + name + "'", LogTag.EXECUTION, this);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
///<summary>Get all State Names</summary>
|
||
|
public string[] GetStateNames() {
|
||
|
return allNodes.Where(n => n is FSMState).Select(n => n.name).ToArray();
|
||
|
}
|
||
|
|
||
|
///<summary>Get a state by it's name</summary>
|
||
|
public FSMState GetStateWithName(string name) {
|
||
|
return (FSMState)allNodes.Find(n => n is FSMState && n.name == name);
|
||
|
}
|
||
|
|
||
|
//Gather IStateCallbackReceivers and subscribe them to state events
|
||
|
void GatherCallbackReceivers() {
|
||
|
|
||
|
if ( agent == null ) { return; }
|
||
|
|
||
|
callbackReceivers = agent.gameObject.GetComponents<IStateCallbackReceiver>();
|
||
|
if ( callbackReceivers.Length > 0 ) {
|
||
|
onStateEnter += (x) => { foreach ( var m in callbackReceivers ) m.OnStateEnter(x); };
|
||
|
onStateUpdate += (x) => { foreach ( var m in callbackReceivers ) m.OnStateUpdate(x); };
|
||
|
onStateExit += (x) => { foreach ( var m in callbackReceivers ) m.OnStateExit(x); };
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public FSMState PeekStack() {
|
||
|
return stateStack != null && stateStack.Count > 0 ? stateStack.Peek() : null;
|
||
|
}
|
||
|
|
||
|
|
||
|
///----------------------------------------------------------------------------------------------
|
||
|
///---------------------------------------UNITY EDITOR-------------------------------------------
|
||
|
#if UNITY_EDITOR
|
||
|
|
||
|
[UnityEditor.MenuItem("Tools/ParadoxNotion/NodeCanvas/Create/State Machine Asset", false, 1)]
|
||
|
static void Editor_CreateGraph() {
|
||
|
var newGraph = EditorUtils.CreateAsset<FSM>();
|
||
|
UnityEditor.Selection.activeObject = newGraph;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
}
|
||
|
}
|