Как реализовать конечный автомат, управляемый событиями, в Unity?

Я реализовал простой конечный автомат в Unity, который использовал в нескольких проектах, но постоянно сталкиваюсь с одной и той же проблемой: как мне общаться с состоянием объекта из внешнего скрипта?

Скажем, у меня есть класс Player с состояниями Idle и PoweredUp. Я хочу отправить событие из другого класса, которое имеет разные результаты в зависимости от того, в каком состоянии находится класс игрока (например, диспетчер повреждений отправляет событие ApplyDamage, которое убивает игрока, если он находится в состоянии Idle). и переводит их в состояние Idle, если они находятся в состоянии PoweredUp).

В компании, в которой я работал, был язык сценариев, который делал это через события, где вы могли транслировать события в сценарий состояния с любым количеством параметров, и они вели бы себя по-разному в зависимости от того, в каком состоянии находится сценарий. Возможно ли это в C#? ? Есть ли лучший подход? Что было бы лучшим способом спроектировать это?

Сценарий конечного автомата:

public class StateMachine {

private State previousState;
private State currentState;
public State CurrentState { get { return currentState; } set { currentState = value; } }

public void ChangeState(State newState)
{
    if (currentState != null)
    {
        currentState.Exit();
    }
    previousState = currentState;
    currentState = newState;
    currentState.Enter();
}

public void ExecuteStateUpdate()
{
    if (currentState != null)
    {
        currentState.Execute();
    }
}

public void ReturnToPreviousState()
{
    currentState.Exit();
    currentState = previousState;
    currentState.Enter();
}
}

Сценарий состояния:

[System.Serializable]
public abstract class State {
    public virtual void Enter() { }
    public virtual void Execute() { }
    public virtual void Exit() { }
}

Пример скрипта:

public class Player : MonoBehaviour
{
    StateMachine stateMachine = new StateMachine();
    Idle idleState => new Idle(this);
    PoweredUp poweredUpState => new PoweredUp(this);

    private void Start()
    {
        stateMachine.ChangeState(idleState);
    }
    private void Update()
    {
        stateMachine.ExecuteStateUpdate();   
    }

    // -----------------------------------------------------
    public abstract class Base : State
    {
        protected Player owner;
        public Base(Player owner) { this.owner = owner; }
    }
    // -----------------------------------------------------
    public class Idle : Base
    {
        public Idle(Player owner) : base(owner) { }

        public override void Execute ()
        {
            // do update stuff
        }
    }
    // -----------------------------------------------------
    public class PoweredUp : Base
    {
        public PoweredUp(Player owner) : base(owner) { }
        public override void Enter()
        {
            // play power up animation
            // play power up sound
        }
    }
}

До сих пор я пытался добавить Event(string eventName) в класс State, а затем запустить функцию переключения для eventName в моих состояниях, но это не позволяет мне передавать параметры с событием и опирается на строки, которые быстро становятся беспорядочными.

Я также только что добавил функции вне состояний, которые проверяют, какое значение установлено для currentState, а затем ведут себя соответствующим образом, но это полностью сводит на нет весь смысл сценария состояния.

Любая помощь высоко ценится!


person SupriseMechanics    schedule 11.08.2020    source источник
comment
Вы можете использовать Action и передать обратный вызов с любым количеством параметров.. или, если он должен вернуть определенное значение, используйте Func.. мне немного неясно, что именно вы хотите передать и выполнить.. может Вы добавляете несколько примеров для этого?   -  person derHugo    schedule 11.08.2020


Ответы (1)


Вы можете использовать события C# для этого или собственный UnityEvent Unity, если вы хотите, чтобы они были сериализованы в редактор. Они позволяют передавать аргументы.

В случае включения питания вы можете генерировать событие, которое слушает игрок, и, используя аргументы события, вы можете решить, как долго находиться в состоянии и т. д. В качестве альтернативы вы можете заставить игрока генерировать событие при изменении состояния, и powerup слушает это.

person TJHeuvel    schedule 11.08.2020