Как я могу использовать Generics, чтобы сделать этот FSM более гибким? - PullRequest
0 голосов
/ 19 марта 2020

Я пытаюсь сделать 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 с этим подходом?

1 Ответ

0 голосов
/ 20 марта 2020

Обновлено на основе комментариев

Попробуйте:

public abstract class StateAction : ScriptableObject
{ 
    public abstract void Execute(StateController controller);
}

public abstract class StateAction<T> : StateAction
    where T : StateController
{
    public abstract void Execute(T controller);

    public sealed override Execute(StateController controller)
    {
        if (controller is T)
        {
            Execute((T)controller);
        }
    }
}

Тогда вы можете иметь такие переменные, как:

StateAction[] onEnter;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...