Roguelike Development with C# – Part 5: Cleaning up Some Issues

First a note that I didn’t get the code for the last couple of articles posted before so that’s been done now. Today’s article will work on setting up some debugging and counter logic that we’ll use going forward. We’re also going to fix a small bug in our handling of turns left in last time.

Last time we created a pair of counters to track how long we’ve been running. The tick variable holds how many times our game has run through the update handler while the turn variable tells us how many times the player has performed an action. We’re going to add three more counters and move these values into a separate class. Create a new file in the program named GameCounters.cs and define it as follows

class GameCounters
{
    public int Tick { get; private set; }
    public int Turn { get; private set; }
    public DateTime StartTime { get; private set; }
    private DateTime LastFrameTime { get; set; }
    private double FrameDelta { get; set; }

    GameCounters()
    {
        Tick = Turn = 0;
        StartTime = DateTime.Now;
        LastFrameTime = StartTime;
        FrameDelta = 0.0;
    }
}

This creates a class that holds our current Tick and Turn counters. It also adds three new values that hold the date and time we start our game, the date and time of each frame, and the difference in time since the last frame for each frame.

Now we’ll add a few three more calculated properties to this class. Before the constructor add the following three properties.

public double SecondsSinceStart => (DateTime.Now - StartTime).TotalSeconds;
public double AverageFPS => Tick / SecondsSinceStart;
public double CurrentFPS => 1.0 / FrameDelta;

If this looks unfamiliar, I’m using a new syntax introduced in C# 6. This uses the lamba arrow (=>) to define the body for the expression. The effect is exactly the same as if the methods had had a block body with a single return statement. In other words

public double CurrentFPS => 1.0 / FrameDelta;

is the equivalent of:

public double CurrentFPS
{
    get
    {
       return 1.0 / FrameDelta;
    }
}

in a smaller space.

For short get methods I find the new syntax easier to read.

We’ll also add methods to the class to update the counters from our Update handler.

public void NewFrame()
{
    DateTime now = DateTime.Now;
    Tick++;
    FrameDelta = (now - LastFrameTime).TotalSeconds;
    LastFrameTime = now;
}

public void NewTurn()
{
    Turn++;
}

Now we need to update our game to use the new class. Replace the two existing counter variables with a single variable of the new GameCounter class.

private static GameCounters _counters;

We could initialize the counter class in our OnLoad handler, but I want to keep that to setting the initial game state. Instead we create the instance of the class immediately before starting the loop at the end of the Main method before the _rootConsole.Run(); line.

_counters = new GameCounters();
_rootConsole.Run();

We also now need to use the new methods whenever a turn or tick passes instead of directly changing the variables. Replace the turn++ call at the end of the loop handling key strokes with _counters.NewTurn();. Replace the increase of the tick counter at the end of the Update handler with a call to the new method so tick += 1; becomes _counters.NewFrame();.

In additon let’s add a new print statement to show the new counters. In the Render handler after the existing print statement add a new one.

_rootConsole.Print(1, 2, $"Time since start: {_counters.SecondsSinceStart:.000} Current FPS: {_counters.CurrentFPS:.0 fps} Average FPS: {_counters.AverageFPS:.0} fps", RLColor.White);

While these print statements are useful, I don’t think we’ll want them on the screen all the time as we add more functionality. Let’s add one more property to the CameCounters class.

public bool Visible { get; set; }

and update the constructor to set this property to false initially.

public GameCounters()
{
    Tick = Turn = 0;
    StartTime = DateTime.Now;
    LastFrameTime = StartTime;
    FrameDelta = 0.0;
    Visible = false;
}

We’ll update our Render handler to only show these statements when the Visible property is set to true in our Render handler.

private static void RootConsole_Render(object sender, UpdateEventArgs e)
{
    _rootConsole.Clear();

    _rootConsole.SetChar(playerX, playerY, '@');
    if (_counters.Visible)
    {
        _rootConsole.Print(1, 1, $"Current tick: {_counters.Tick} Current turn: {_counters.Turn}", RLColor.White);
        _rootConsole.Print(1, 2, $"Time since start: {_counters.SecondsSinceStart:.000} Current FPS: {_counters.CurrentFPS:.0 fps} Average FPS: {_counters.AverageFPS:.0} fps", RLColor.White);
    }

    _rootConsole.Draw();
}

To toggle the flag, we’ll use the d key (for Debug). After the case statement for the Q and Escape keys, add one more.

case RLKey.D:
    _counters.Visible = !_counters.Visible;
    break;

If you run the application as it is now, you’ll see that the statements are hidden at first, but will show after you press the D key. Pressing the D key again hides the information.

I also mentioned a bug in our handling of turns. Restart the game and press D. You’ll notice it shows one turn has passed. In fact if you press any key, including unrecognized ones such as F and the turn counter increases. That’s because we’re setting the userAction flag to true any time that we get a keypress.

We only want turns to pass when the player actually performs an action. Interface type activity (such as showing the menu here with the D key) shouldn’t count nor should keys we don’t recognize or act upon. Let’s fix this by only seting the userAction flag to true for keys resulting in the player peforming an action. Change the swtich statement handling keypresses within the Update handler to the following.

if (key != null)
{
    switch (key.Key)
    {
        case RLKey.Up:
            userAction = true;
            playerY -= 1;
            break;
    
        case RLKey.Left:
            userAction = true;
            playerX -= 1;
            break;
    
        case RLKey.Down:
            userAction = true;
            playerY += 1;
            break;
    
        case RLKey.Right:
            userAction = true;
            playerX += 1;
            break;
    
        case RLKey.Q:
        case RLKey.Escape:
            _rootConsole.Close();
            break;
    
        case RLKey.D:
            _counters.Visible = !_counters.Visible;
            break;
    }
}

Now we only set the flag when the user moves. Quitting or toggling the display of debugging information does not count as an action.

Today we’ll do one more change to move the code that should only execute after a turn and the code that executes every time through the Update handler into separate functions. This will leave the Update handler focused on code to process player input.

Create two new methods at the bottom of the Program class.

private static void ProcessFrame()
{
    _counters.NewFrame();
}

private static void ProcessTurn()
{
    // Ensure player stays 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;
    _counters.NewTurn();
}

And change the end of the Update handler to call these methods. Change this part of the Update handler:

// Turn based events only if userAction is true
if (userAction)
{
    // Ensure player stays 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;
    _counters.NewTurn();
}

// Real time actions
_counters.NewFrame();

into

// Turn based events only if userAction is true
if (userAction)
{
    ProcessTurn();
}

// Update frame timers and counters
ProcessFrame();

We’ve done a number of changes in this article. download a zip file with the code to this point. Next time we’ll actually start implementing a real game by adding someone else besides our player to the game.

Leave a Reply

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