Обычно игра запускается в игре-l oop. Очень простой пример может выглядеть так:
- Считывание ввода
- Обработка ввода (перемещение персонажа, продвижение врагов и т. Д. c)
- Нарисовать результат
Это 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;
}
}