Шаблон состояний в C#

Я прочитал книгу под названием "Шаблоны программирования игр" и начал реализовывать некоторые из ее шаблонов на C#. Прямо сейчас я реализую шаблон состояния, который описывается приведенной ниже диаграммой классов UML.

Это моя основная программа. Я использую 2 разных потока, чтобы не останавливать игровой цикл пользовательским вводом с клавиатуры. В главном классе метод HandleInput вызывает метод HandleInput текущего состояния, хранящегося в поле state.

class Program
{

    private static string key;
    private static Thread t;
    private static Hero hero1;

    public delegate void PressDelegate();
    public static event PressDelegate OnPress;
    static void Main(string[] args)
    {
        // Creating Hero
        hero1 = new Hero("zelda");

        // Subscribing to KeyboardInput Event
        OnPress += KeyboardInputHandler;

        // Game Loop
        t = new Thread(new ThreadStart(KeyboardInput));
        t.Start();
    }
    private static void KeyboardInput()
    {
        while (true)
        {
            key = Console.ReadLine();
            OnPress();
        }

    }
    private static void KeyboardInputHandler()
    {
        hero1.HandleInput(key);
    }
}

}

Моя цель состоит в том, чтобы, когда пользователь нажимает пробел, герой меняет свое состояние на JumpingState, предполагая, что его состояние по умолчанию - это состояние ожидания, и через 1 секунду возвращается в состояние ожидания (имитируя эффект гравитации). Проблема в том, что я пробовал некоторые решения, такие как наличие таймера внутри IdleState, но тогда мне придется делать это для каждого состояния, которое переходит в состояние прыжка (например, состояние приглушения). Я также пытался сделать это в классе героя с помощью оператора if:(если состояние JumpingState, тогда запускайте таймер, а когда завершите вызов SetState(IdleState) что-то в этом роде), но я чувствую, что это нарушает тот факт, что метод SetState вызывается только внутри государства, а не на герое.

Я очень ценю вашу помощь:)

1 ответ

Решение

Обычно игра запускается в игровом цикле. Очень простой вариант может выглядеть так:

  1. Прочитать ввод
  2. Ввод процесса (перемещение персонажа, продвижение врагов и т. Д.)
  3. Нарисуйте результат

Этот цикл обычно имеет более или менее фиксированную скорость. это отличается от большинства программ Windows, которые запускаются только тогда, когда пользователь ввел какой-либо ввод или в программе есть сработавший таймер.

Таким образом, активное состояние будет получать вызов обновления каждый кадр, и тогда у него будет возможность решить, что делать. Состояние обычно проверяет состояние игры и пользовательский ввод, а затем может решить, что делать.

Вместо чтения ввода в отдельном потоке вы можете использовать Console.KeyAvailable, чтобы проверить, доступен ли ключ, и если да, ReadKey не должен блокироваться.

Я привык программировать конечные автоматы так, что текущее состояние вызывается с некоторыми входными параметрами и возвращает следующее состояние для использования. Есть ряд деталей, которые можно принять во внимание:

  • Следует ли обрабатывать весь ввод? только последний ввод каждого кадра? Только если клавиша нажимается при запуске кадра?
  • Можно ли переключать состояния более одного раза за кадр? Т.е. "прыжок" может перейти в "холостой ход", но если пользователь удерживает клавишу Shift, он может немедленно перейти в "приседание".
  • Обычно разделяют "состояние" и "переход". Это помогает сделать конечный автомат более абстрактным. Т.е. вместо жесткого кодирования того, что герой должен прыгать в пространстве в каждом состоянии, вы можете иметь OnKeyTransition, который принимает ключ ввода и целевое состояние в качестве ввода. Затем вы можете добавить этот переход ко всем состояниям, которые должны поддерживать прыжки.
  • В игре может быть много разных видов конечных автоматов. Один высокий уровень может быть, если игрок управляет автомобилем, пешком, пилотирует самолет и т. Д. Конечный автомат более низкого уровня может обрабатывать анимацию. Третий вид можно использовать для ИИ. Все они имеют разные требования, поэтому к ним нужно относиться по-разному.

Это был бы пример очень простого конечного автомата. Я не уверен, что это именно то, что вы ищете, но я надеюсь, что это хоть как-то вдохновит вас.

public class State
{
    public virtual State Update(ConsoleKey? input, TimeSpan deltaTime) => this;
}

public class IdleState : State
{
    public override State Update(ConsoleKey? input, TimeSpan deltaTime)
    {
        if (input == ConsoleKey.Spacebar)
        {
            return new JumpingState();
        }
        return base.Update(input, deltaTime);
    }
}

public class JumpingState : State
{
    private TimeSpan elapsed = TimeSpan.Zero;

    public override State Update(ConsoleKey? input, TimeSpan deltaTime)
    {
        elapsed += deltaTime;
        if (elapsed > TimeSpan.FromSeconds(1))
        {
            return new IdleState();
        }
        return base.Update(input, deltaTime);
    }
}

public class Game
{
    static void Main(string[] args)
    {
        var game = new Game();
        game.StartGame();

    }

    State currentState = new IdleState();
    private TimeSpan frameRate = TimeSpan.FromMilliseconds(30);

    public void StartGame()
    {
        Console.WriteLine("Game Started");
        while (true)
        {

            var input = GetLastKeypress()?.Key;
            if (input == ConsoleKey.Escape)
            {
                Console.WriteLine("Game Over");
                return;
            }

            // Update the state 
            var nextState = currentState.Update(input, frameRate);
            if (nextState != currentState)
            {
                currentState = nextState;
                Console.WriteLine(currentState.GetType().Name);
            }
            Thread.Sleep(frameRate);
        }
    }

    private ConsoleKeyInfo? GetLastKeypress()
    {
        ConsoleKeyInfo? info = null;
        while (Console.KeyAvailable)
        {
            info = Console.ReadKey();
        }
        return info;
    }
}
Другие вопросы по тегам