Я пытаюсь сделать FSM более гибким, имея базовый класс StateController, из которого могут извлекать другие контроллеры (PlayerController, AiController и др. c). До сих пор передаваемые аргументы имеют сильные зависимости, не допускающие такой гибкости, следовательно, обобщенные. Единственное, что я не могу сосредоточиться на этом подходе.
Вот код для FSM без гибкости, которой я надеялся достичь.
StateController.cs
using UnityEngine;
public class StateController : MonoBehaviour
{
public State currentState;
public State previousState;
public State remainInState;
private void Start()
{
if (!currentState)
return;
currentState.OnEnter(this);
}
private void FixedUpdate()
{
if (!currentState)
return;
currentState.OnFixedUpdate(this);
}
private void Update()
{
if (!currentState)
return;
currentState.OnUpdate(this);
}
private void LateUpdate()
{
if (!currentState)
return;
currentState.OnLateUpdate(this);
}
public void ChangeState(State nextState)
{
if (nextState != remainInState)
{
currentState.OnExit(this);
previousState = currentState;
currentState = nextState;
currentState.OnEnter(this);
}
}
}
State.cs
using UnityEngine;
[CreateAssetMenu(menuName = ("State Controller/State"))]
public class State : ScriptableObject
{
public StateAction[] onEnter;
public StateAction[] onFixed;
public StateAction[] onUpdate;
public StateAction[] onLate;
public StateAction[] onExit;
public StateTransition[] transitions;
public void OnEnter(StateController controller)
{
ExecuteActions(controller, onEnter);
}
public void OnFixedUpdate(StateController controller)
{
ExecuteActions(controller, onFixed);
}
public void OnUpdate(StateController controller)
{
ExecuteActions(controller, onUpdate);
CheckTransitions(controller);
}
public void OnLateUpdate(StateController controller)
{
ExecuteActions(controller, onLate);
}
public void OnExit(StateController controller)
{
ExecuteActions(controller, onExit);
}
private void ExecuteActions(StateController controller, StateAction[] actions)
{
for (int i = 0; i < actions.Length; i++)
{
actions[i].Execute(controller);
}
}
private void CheckTransitions(StateController controller)
{
for (int i = 0; i < transitions.Length; i++)
{
bool result = transitions[i].condition.CheckCondition(controller);
if (result == true)
{
controller.ChangeState(transitions[i].trueState);
}
else
controller.ChangeState(transitions[i].falseState);
}
}
}
StateAction.cs
using UnityEngine;
public abstract class StateAction : ScriptableObject
{
public abstract void Execute(StateController controller);
}
StateCondition.cs
using UnityEngine;
public abstract class StateCondition : ScriptableObject
{
public abstract bool CheckCondition(StateController controller);
}
StateTransition.cs
[System.Serializable]
public class StateTransition
{
public StateCondition condition;
public State trueState;
public State falseState;
}
Но я хочу, чтобы flexibilty использовал StateController.cs в качестве базового класса и мог выводить из него другой класс (очевидно, делая его абстрактным и используя защищенный виртуальный пустоту для функции), как сценарий PlayerController ...
using UnityEngine;
public class PlayerController : StateController
{
//Other Variables Specific to this class
protected override void Start()
{
base.Start();
}
//Other Functions Specific to this class
}
Я подумал, что использование Generics может помочь с этим, так как у меня есть другой сценарий, который может принимать только StateController в качестве аргумента ...
[CreateAssetMenu(menuName = ("State Controller/Action Test"))]
public class ActionTest : StateAction
{
public override void Execute(StateController controller /*place PlayerController Here instead */)
{
Debug.Log(controller.currentState);
}
}
Обобщения могут помочь, но их реализация пугающая для меня. Вот пример вышеприведенного скрипта с обобщениями, используемыми для передачи PlayerController, но это не приведет к исправлению системы, поскольку в качестве аргумента State.cs потребуется T.
public abstract class StateAction<T> : ScriptableObject where T : StateController
{
public abstract void Execute(T controller);
}
using UnityEngine;
[CreateAssetMenu(menuName = ("State Controller/Action Test"))]
public class ActionTest : StateAction<PlayerController>
{
public override void Execute(PlayerController controller)
{
Debug.Log(controller.currentState);
}
}
Я знаю, что FSM нужно быть реструктурированы, но есть ли кто-нибудь еще, кто сумел переработать аналогичный FSM с этим подходом?