I got into this book called "game programming patterns" and I started implementing some of its patterns in C#. Right now, I'm implementing the State Pattern which is described by the UML Class Diagram below.
This is my main program. I use 2 different threads in order to not stop the game loop with the keyboard user input. In the hero, class the HandleInput method calls the HandleInput method of the current state stored in the field 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);
}
}
}
My goal is to when user press Space, the hero changes his states to JumpingState assuming that its default state is the idlestate and after 1-second changes back to idle (simulating the gravity effect). The problem is I've tried some solutions like having the timer inside the IdleState but then I'll have to do that for every state that transition to jumping state (for example a ducking state). I've also tried to do that on the hero class with an if statement :( if state is JumpingState then start the timer and when finish call SetState(IdleState) something like that) but I feel that this breaks the fact that the SetState method is only called inside the state and not on the hero.
I really appreciate your help :)
Usually a game is run in a game-loop. A very simple one could look like this:
This loop would typically at a more or less fixed rate. this is in contrast to most windows programs that only run when the user has provided some kind of input, or the program has some timer that has triggered.
So the active state would receive an update call every frame, and it has then a chance to decide what to do. The state would typically check the game-state and user input, from this it can then decide what to do.
Instead of reading the input in a separate thread you can use Console.KeyAvailable to check if a key is available, and if so, ReadKey should not block.
The way I'm used to program state machines is that the current state is called with some input parameters, and returns the next state to use. There are a bunch of details that can be taken into consideration:
This would be an example of a very simple state machine. I'm not sure that it is exactly what you are looking for, but I hope it will provide some inspiration at least.
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;
}
}