Roguelike Development with C# – Part 2: A Basic Game Loop

To begin the game, we’ll start with a game loop. Every game works through the same basic three steps.

  1. Initialize
  2. Update
  3. Draw

In step one, the program sets up the inital state of the game. The game then repeats steps two and three until the game ends. To start with I’m going to build a simple Command Line project using the built in Console class in C#.

For the initial pass we’ll keep things simple. Our goal is to create a player character that can be moved around the screen with the keyboard. We’ll use the i, j, k, and m keys as in the classic Nethack to move. We’ll also use the @ character for our player, again as in Nethack (and most other text Roguelikes).

private static bool quit = false;
private const int screenWidth = 80;
private const int screenHeight = 24;
private static int playerX;
private static int playerY;

static void Main(string[] args)
{
   // Register Ctrl+C handler and hide the cursor
   Console.CancelKeyPress += ConsoleOnCancelKeyPress;
   Console.CursorVisible = false;

   // Initialize Game variables
   InitializeGame();
            
   // Enter Game Loop
   GameLoop();
}

We begin by defining five variables. The first stores a flag we can set to quit the game. We also store the size of the game screen along with the player’s current position.

A C# console app begins executing our Main method here. First I add a handler function that will be called when Ctrl+C is pressed. Next we hides the cursor and then call InitializeGame() to set up he initial state of our game by setting the variables above.

private static void InitializeGame()
{
    // Set window size
    Console.SetWindowSize(screenWidth, screenHeight);
    Console.SetBufferSize(screenWidth, screenHeight);

    // Set initial player position to center of screen
    playerX = screenWidth/2;
    playerY = screenHeight/2;
}

The first two lines set the window and buffer size of the Console to match our specified values. We then set the initial player position to the center of the screen.

That handler code by the way just sets the quit variable to true.

private static void ConsoleOnCancelKeyPress(object sender, ConsoleCancelEventArgs consoleCancelEventArgs)
{
    // Set flag that we'll use to cancel on our own terms
    quit = true;
    consoleCancelEventArgs.Cancel = true;
}

Next comes the heart of the game, our game loop code.

private static void GameLoop()
{
    while (!quit)
    {
        // Sleep a short period at the end of each iteration of the loop
        Thread.Sleep(100);
        
       // The Render section
        // Clear the console before we draw
        Console.Clear();

        // Draw the player at the current position in white
        Console.SetCursorPosition(playerX, playerY);
        Console.ForegroundColor = ConsoleColor.White;
        Console.Write('@');

       // The Update section
        // Wait for a key press and do not display key on the console
        ConsoleKeyInfo character = Console.ReadKey(true);

        // Handle the player input
        switch (character.KeyChar)
        {
            case 'i':
                playerY -= 1;
                break;
            case 'j':
                playerX -= 1;
                break;
            case 'k':
                playerX += 1;
                break;
            case 'm':
                playerY += 1;
                break;
            case 'q':
                quit = true;
                break;
        }

        // Ensure player is still on the screen
        if (playerX < 0)
            playerX = 0;
        if (playerX > screenWidth - 1)
            playerX = screenWidth - 1;
        if (playerY < 0)
            playerY = 0;
        if (playerY > screenHeight - 1)
            playerY = screenHeight - 1;
    }
}

The method begins with a while loop hat loops until the quit variable we defined earlier is true. Each pass through the loop begins with a short pause of 100ms. Given how little we’re doing at this stage of the loop, this pause keeps the pacing of our movement more controllable.

In this first pass I swapped the order of the render and update sections of the loop from above. They should occur be in the opposite order, but we’ll go with the code as written for now. These two sections repeat until we quit the game (or close the app).

The render section displays the current state of the game world and displays it to the player. Our loop begins by clearing the console giving a blank screen to draw on. Next the code prints the ‘@’ character, the player, in white at the player’s current position as specified by those variables we defined earlier.

The render section completed, the code continues to the update portion. Update handles user input and updates the game state to reflect that input.

We’re using a turn based approach and not real time. The Console.ReadKey method waits on a keypress before continuing. After the keypress, we interpret pressing the i,j,k, or m key as a request to move up, left, right, and down respectively and update the player’s position to match. We also respond to the q key by setting that quit flag to true which will end end the while loop the next time through, and thus end the game. Other keys are ignored.

After movement is processed, we check the player’s position and if the player has moved off the screen, then we shift the player back to the edge of the screen. If we didn’t do this then our game would crash with an exception when we attempted to print our character outside the defined screen. In a “real” game this check would better be done before responding to the move request.

You can download a full sample project for this simple game. Running the code as is gives us what we’d set out to accomplish. The player can move around the screen until pressing q or Ctrl-c to quit. It’s not much of a game, it’s barely even a demo, but it does give you a framework and start to build on. This basic process of update and render lies at the heart of any game.

Leave a Reply

Your email address will not be published. Required fields are marked *