Государственный образец в c# - PullRequest
0 голосов
/ 29 мая 2020

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

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

Я очень признателен за вашу помощь :)

1 Ответ

1 голос
/ 29 мая 2020

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

  1. Считывание ввода
  2. Обработка ввода (перемещение персонажа, продвижение врагов и т. Д. c)
  3. Нарисовать результат

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

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

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

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

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

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

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;
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...