ansi|ware

Tutorial Part 15: Theme Colours and Maintenance

One amazing thing about taking two years to write a simple tutorial is that it gets tested a whole lot by different developers along the way, and you get extremely reliable code by the end. [Placating tone] I know, I know… I promised lockable doors… but part of roguelike ownership is cleaning up the coils of poop in the backyard when the snow melts off in the spring, right? So let’s squash some bugs and address some questions thanks to many folks in the SadConsole discord.

Changing Theme Colours

HodgePodge asked: Where do I go to change the theme of a window? Like from blue to black. According to the ansiware tutorial it was Theme.WindowTheme.FillStyle.Background. if I remove .windowtheme it doesn’t change the blue background

That little problem has been nagging me a lot too, since SadConsole switched to the “Theme”-based colour system! You probably noticed that we’d had a puke-blue background for our Game Map and Message Log since forever. Well, let’s do something about that. Thraka’s answer to HodgePodge kindly pointed out the proper procedure for re-colouring all objects, which I am gleefully stealing for this tutorial.

Important: before you continue, make sure that you have updated your SadConsole nuget to version 8.99.x which has a new Theme/Color architecture!

I hereby deem Themes to be a UI related topic, so let’s start by modifying the UIManager class:

    public class UIManager : ContainerConsole
    {
        ...
        public SadConsole.Themes.Colors CustomColors;

    // Initializes all windows and consoles
    public void Init()
    {
        SetupCustomColors();

In the above code, CustomColors will act as a reference to the custom coloured theme we are building. Note that it is marked public, so we can access our custom colours from anywhere within our program. Now let’s build the custom coloured theme method we reference above:

// Build a new coloured theme based on SC's default theme
// and then set it as the program's default theme.
private void SetupCustomColors()
{
    // Create a set of default colours that we will modify
    CustomColors = SadConsole.Themes.Colors.CreateDefault();

    // Pick a couple of background colours that we will apply to all consoles.
    Color backgroundColor = Color.Black;

    // Set background colour for controls consoles and their controls
    CustomColors.ControlHostBack = backgroundColor;
    CustomColors.ControlBack = backgroundColor;

    // Generate background colours for dark and light themes based on
    // the default background colour.
    CustomColors.ControlBackLight = (backgroundColor * 1.3f).FillAlpha();
    CustomColors.ControlBackDark = (backgroundColor * 0.7f).FillAlpha();

    // Set a color for currently selected controls. This should always
    // be different from the background colour.
    CustomColors.ControlBackSelected = CustomColors.GrayDark;

    // Rebuild all objects' themes with the custom colours we picked above.
    CustomColors.RebuildAppearances();

    // Now set all of these colours as default for SC's default theme.
    SadConsole.Themes.Library.Default.Colors = CustomColors;
}

Now, there’s a lot going on up there, so let’s break it down into bite-sized hunks. The first line sets CustomColors to SadConsole’s default colour set. This gives us a foundation of colours to build from, rather than having to set individual colours for every type of Window, Console and Control in the program. The second step is straightforward: pick a single colour to apply to every console, control and window. While I’ve selected black, I highly recommend you to choose Red or Yellow for that relaxing and subtle Hotdog Stand experience.

The next step is a bit trickier – we’re doing light procedurally generated colouring to produce colours that are darker than and lighter than our base background colour. When we multiply backgroundColor by 1.3, we’re effectively saying “crank up the brightness of all Red, Blue and Green colours by 130%”. The FillAlpha call simply makes the colour completely opaque (non-transparent), so we don’t end up with translucent backgrounds. (But hey, if you want translucent backgrounds, this is the place to do it!)

You certainly do not need to procedurally generate your colours here. You can just set ControlsBackLight and ControlsBackDark to whatever colours you’d like instead.

The RebuildAppearances method instructs SadConsole’s Theme system to dig through every single object in CustomColors and rebuild them to use the colours we’ve selected. Finally, the last call tells the Theme system to set all of its default colours for in-game objects to CustomColors. And that’s it! Run the program, and now you are one step closer to Hot Dog Stand nirvana.

Let’s not stop there though – it’s a bit boring to have the exact same colours throughout the UI right? Let’s set some new custom colours specifically for the Message Log so it stands out a bit. Modify the MessageLogWindow class thusly:

First, remove this line if you’ve got it. The old method for changing colours just doesn’t work anymore:

public MessageLogWindow(int width, int height, string title) : base(width, height)
{
    // Ensure that the window background is the correct colour
    Theme.WindowTheme.FillStyle.Background = Color.Black;

Now add the new code:

public MessageLogWindow(int width, int height, string title) : base(width, height)
{
    // Set some custom colours for the MessageLog
    ThemeColors = GameLoop.UIManager.CustomColors.Clone();
    ThemeColors.ControlBack = Color.DarkRed;
    ThemeColors.TitleText = Color.Red;
    ThemeColors.RebuildAppearances();
...

We begin by Cloning the CustomColors defaults we’ve already set in the UIManager, so we have a new working copy to modify. We then make a few minor modifications to the colours, and then RebuildAppearances when we’re satisfied with our settings. Notice that we’re using the Window’s built-in ThemeColors variable which controls the colours the Window uses.

It’s Not a Feature, It’s a Bug

Let’s get into the weeds a bit, shall we? Freiling noticed that Entities are being displayed on the MapConsole, but are not being properly synchronized to the MultiSpatialMap that we use to store our Entity positions. Because we aren’t using Entities for much in the tutorials yet, we’ve had this bug quietly lurking in the backgound. It only became noticeable when Freiling started to use Entities on the map for things like a tooltip system that shows you the name of the Entity the mouse is hovering over.

So what was the problem exactly? Some Entities were appearing on the MapWindow (that is – you could see them onscreen), but whenever Freiling used GetEntityAt(X,Y), the MultiSpatialMap would say that nothing is there! In other words: the MultiSpatialMap called Entities had different information in it than MapWindow. That’s a big problem, because it’s a form of data corruption. Someone, somewhere, was asleep at the switch.

(me.)

So, let’s open our Map class and try to figure out what exactly caused MapWindow to have different entities shown onscreen than our MultiSpatialMap<Entity>. Scroll down to this area:

// Adds an Entity to the MultiSpatialMap
public void Add(Entity entity)
{
    // add entity to the SpatialMap
    Entities.Add(entity, entity.Position);

    // Link up the entity's Moved event to a new handler
    entity.Moved += OnEntityMoved;
}

Remember that this method adds an Entity to the MultiSpatialMap called Entities, then subscribes the new entity’s Moved event to the OnEntityMoved EventHandler. That looks totally fine, right?

Not totally fine dude! Not totally fine!

Chris3606: The MultiSpatialMap.Add function can fail to add the entity, and when it does (in GoRogue 2.0, anyway) it simply returns false. So I’m thinking what’s happening is some of your adds are failing silently. your entities are still displayed because sadconsole doesn’t care about GoRogue’s entity list, but as far as GoRogue’s list is concerned (which is the one that GetEntitiesAt uses), it was never actually there.

Failing Silently… I should rename this tutorial series. What the good doctor is saying is that sometimes Entities.Add(entity, entity.Position) does not successfully add an Entity at that Position! Sometimes it’s failing to add – I’m guessing this is because there is already an entity at that position, or something else is blocking it from being positioned there. Chris3606 assures us that this is a bug in the GoRogue v.2 implementation, so we need to work around it. What’s the solution? We need to add some logic that traps any “silent” failures. Let’s start by modifying the Add method:

// Adds an Entity to the MultiSpatialMap
public void Add(Entity entity)
{
    if (!Entities.Add(entity, entity.Position))
        throw new Exception("Failed to add entity to map");

    entity.Moved += OnEntityMoved; // Link entity Moved event to new handler
}

Learning opportunity! We’re using a new .NET keyword in the above called throw. A throw tells the compiler that we’ve encountered an “exception” or undesirable circumstance where what we wanted to happen, didn’t. Let’s also mark out a second place where GoRogue might fail to access an Entity:

// When the Entity's .Moved value changes, it triggers this event handler
// which updates the Entity's current position in the SpatialMap
private void OnEntityMoved(object sender, Entity.EntityMovedEventArgs args)
{
    if (!Entities.Move(args.Entity as Entity, args.Entity.Position))
        throw new Exception("Failed to move entity on map.");
}

And finally, we need to modify the Remove method with a similar pattern:

// Removes an Entity from the MultiSpatialMap
public void Remove(Entity entity)
{
    // remove from SpatialMap
    if (!Entities.Remove(entity))
        throw new Exception("Failed to remove entity from map");

    // De-link the entity's Moved event from the handler
    entity.Moved -= OnEntityMoved;
}

Again, this does not fix the bug exactly, but it at least acknowledges that the Entity could not be moved. So we go from failing silently to failing loudly ūüôā Again, in a future release of GoRogue v.3 this bug will be fixed!

OK this last one has been bugging me for a long while. I can’t tell if this is a regression (that is, an old bug that was fixed, then re-introduced at a later date), or simple forgetfulness in my advanced age… but did you notice that Doors don’t have names? Milhouse opened a … kind of steps off a cliff, doesn’t it? If you haven’t already fixed this, it’s an easy one! Modify the TileDoor class constructor:

//A TileDoor can be set locked/unlocked/open/closed using the constructor.
public TileDoor(bool locked, bool open) : base(Color.Gray, Color.Transparent, '+')
{
    //+ is the closed glyph
    //closed by default
    Glyph = '+';

    // Name it
    Name = "Door";

Okay, so that’s it for the current round of fixes. For sure we’ll do locks and keys next time! ūüôā

Download the final source files for this tutorial here.

Published on March 12, 2021

Tutorial Part 14: Open Sesame

Door Generation Bugfix

@Freiling: Doors on North & West of rooms generate correctly, but appear to generate 1 tile recessed into hallways on South & East.

Good catch! So the question is, what are we going to do about it? 70% of writing good code is being a good detective when things look janky… and janky door generation is a sign of janky mathematics. So how do we track down the culprit?

Let’s take a look at our MapGenerator class. My first guesses would be either IsPotentialDoor or CreateDoor since both of them are involved in door placement. Skim through IsPotentialDoor: do you see any places where a tile could potentially be shifted one space to the right or one space down?

There are several candidate that fit the description, like this:

//store references to all neighbouring cells
Point right = new Point(location.X + 1, location.Y);
Point left = new Point(location.X - 1, location.Y);
Point top = new Point(location.X, location.Y - 1);
Point bottom = new Point(location.X, location.Y + 1);

Those sure do look suspicious don’t they? Lots of places for potential for tile mis-placement.

Unfortunately, that was a red herring Dr. Watson. The culprit lay elsewhere, hiding in the shadows from two tutorials earlier, so it could strike when it was least expected!

The tile-shifter was CreateRoom, in the MapGenerator, with an Integer!

    // Builds a room composed of walls and floors using the supplied Rectangle
    // which determines its size and position on the map
    // Walls are placed at the perimeter of the room
    // Floors are placed in the interior area of the room
    private void CreateRoom(Rectangle room)
    {
        // Place floors in interior area
        for (int x = room.Left + 1; x < room.Right - 1; x++)
        {
            for (int y = room.Top + 1; y < room.Bottom - 1; y++)
            {
                CreateFloor(new Point(x,y));
            }
        }

        // Place walls at perimeter
        List perimeter = GetBorderCellLocations(room);
        foreach (Point location in perimeter)
        {
            CreateWall(location);
        }
    }

The intention was to create Walls around the outer perimeter of the room rectangle, and Floors on the inner perimeter. Unfortunately, the above code produces a floor that’s offset Right + 1 and Bottom + 1! We want the floor to be inset, not outside of the room. Update these lines:

for (int x = room.Left + 1; x < room.Right; x++)
{
     for (int y = room.Top + 1; y < room.Bottom; y++)

And now you have floors inset correctly, and doors positioned at the border of the room.

Opening Doors

Now that we’ve managed to lock away our avatar in a seclusion for almost a year, it’s about time we allowed it brief excursions into the frightening unknown. What good is a door that can’t open or close?

We already have the door open and close code written, but we haven’t glued it to the CommandSystem yet. If you recall our CommandSystem, it works (so far) as a central controller that lets Actors do things on the Map, like begin combat with other Actors, or grab Items. In this case, we’re going to create a custom Command that controls how Actors can use TileDoor. We’ll start by letting Actors open doors, and then get fancier after that.

Open the CommandSystem class, and add a new Using directive so we can access the Tiles namespace:

using SadConsoleRLTutorial.Tiles;

Now add a new method called UseDoor. UseDoor can be called by any Actor when it steps into a TileDoor’s space.

    // Triggered when an Actor attempts to move into a doorway.
    // A closed door opens when used by an Actor.
    public void UseDoor(Actor actor, TileDoor door)
    {
        // Handle a locked door
        if (door.Locked)
        {
            // We have no way of opening a locked door for the time being.
        }
        // Handled an unlocked door that is closed
        else if (!door.Locked && !door.IsOpen)
        {
            door.Open();
            GameLoop.UIManager.MessageLog.Add($"{actor.Name} opened a {door.Name}");
        }
    }

Now it’s a case of allowing the Actor access to the UseDoor command we just added. Open your Actor class and modify it in the same way we previously handled monsters and items:

    // Moves the Actor BY positionChange tiles in any X/Y direction
    // returns true if actor was able to move, false if failed to move
    // Checks for Monsters, Items and Doors before moving
    // and allows the Actor to commit an action if one is present.
    public bool MoveBy(Point positionChange)
    {
        // Check the current map if we can move to this new position
        if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
        {
            // if there's a monster here,
            // do a bump attack
            Monster monster = GameLoop.World.CurrentMap.GetEntityAt(Position + positionChange);
            Item item = GameLoop.World.CurrentMap.GetEntityAt(Position + positionChange);
            TileDoor door = GameLoop.World.CurrentMap.GetTileAt(Position + positionChange);
            if (monster != null)
            {
                GameLoop.CommandManager.Attack(this, monster);
                return true;
            }
            // if there's an item here,
            // try to pick it up
            else if (item != null)
            {
                GameLoop.CommandManager.Pickup(this, item);
                return true;
            }
            // if there's a door here,
            // try to use it
            else if (door != null)
                GameLoop.CommandManager.UseDoor(this, door);

            Position += positionChange;
            return true;
        }
        else
            return false;
    }

Your IDE is likely barfing an error at you now: GetTileAt<TileDoor>() does not accept a Point as a parameter. Not to worry, with some glorious copypasta we can create a second GetTileAt method that accepts Point as a parameter (instead of having to send X and Y coordinates). (Thanks @Chris3606 for suggesting some cleaner code than I had originally put here!) Modify your Map class by adding a second GetTileAt method below the original one:

    // Checks if a specific type of tile at a specified location
    // is on the map. If it exists, returns that Tile
    // This form of the method accepts a Point coordinate.
    public T GetTileAt(Point location) where T : TileBase
    {
        return GetTileAt<T>(location.X, location.Y);
    }

Why does the compiler allow two nearly identical methods? It’s because most C-like compilers allow for methods with different signatures. A method’s signature tells the compiler how that method is different from other methods. In this case, GetTileAt<T>(Point location) gets its signature from its name (GetTileAt) and the type of parameter it accepts. So if we have two methods with the same name, but have different parameter types, the compiler sees these as two completely different methods. Overloading a method name so it can accept different types of parameters kicks butt because it simplifies (and reduces the amount of) code elsewhere in your project. This will keep our Actor class cleaner and easier to read.

Compile your project and run it. Your avatar should be able to walk up to doors and op–

— oh FFS. It didn’t work. Why?

Let’s go back to our Actor class and see if we can trace out why TileDoor is not opening:

        // Check the current map if we can move to this new position
        if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
        {
            ...
            // if there's a door here,
            // try to use it
            else if (door != null)
                GameLoop.CommandManager.UseDoor(this, door);

            Position += positionChange;
            return true;
        }

Ah…….. woops. Do you see the problem? If not, don’t worry – neither did I when I first wrote the code, or we wouldn’t be in this mess ūüôā It’s the first if statement that checks if the Tile is Walkable: since TileDoor is not walkable, it completely skips over our nice door opening code. We’ll have to refactor this a bit, and take into consideration how to handle tiles that are not walkable, yet are usable:

    // Moves the Actor BY positionChange tiles in any X/Y direction
    // returns true if actor was able to move, false if failed to move
    // Checks for Monsters, and Items before moving
    // and allows the Actor to commit an action if one is present.
    public bool MoveBy(Point positionChange)
    {
        // Check the current map if we can move to this new position
        if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
        {
            // if there's a monster here,
            // do a bump attack
            Monster monster = GameLoop.World.CurrentMap.GetEntityAt<Monster>(Position + positionChange);
            Item item = GameLoop.World.CurrentMap.GetEntityAt<Item>(Position + positionChange);
            if (monster != null)
            {
                GameLoop.CommandManager.Attack(this, monster);
                return true;
            }
            // if there's an item here,
            // try to pick it up
            else if (item != null)
            {
                GameLoop.CommandManager.Pickup(this, item);
                return true;
            }

            Position += positionChange;
            return true;
        }
        // Handle situations where there are non-walkable tiles that CAN be used
        else
        {
            // Check for the presence of a door
            TileDoor door = GameLoop.World.CurrentMap.GetTileAt<TileDoor>(Position + positionChange);
            // if there's a door here,
            // try to use it
            if (door != null)
            {
                GameLoop.CommandManager.UseDoor(this, door);
                return true;
            }
            return false;
        }
    }

It’s not the prettiest code, but it gets the job done for now. So the if statement is basically broken into two halves: one half handles walkable tiles that Items and Monsters are standing on, and the other half handles non-walkable tiles that we can use like TileDoor. Run your project and voila, we have opening doors!

One thing to consider in the above code is that it contains some gameplay logic: opening a door is treated as one move. Moving into a closed door only opens it, it does not open it and move the avatar at the same time. Some Roguelikes allow the player to open a door and move into it at the same time. If you want that style of gameplay, you can easily modify the above code to suit your preference. You can do this by adding Position += positionChange; between the UseDoor call and return true.

In the next tutorial, we’ll figure out how to create locks and keys, and turn our little series of rooms into a truly inhumane dungeon of misery.

Download the completed source files for the tutorial here.

Published on May 23, 2020

Tutorial Part 13: Door Generation

Since I’m doing this entire roguelike tutorial bass-ackwards, why don’t we build some doors? I’ve been avoiding them for weeks months, knowing that I don’t have a great way of doing them using our existing map generator. I was hoping that I’d come up with a brilliant door placement strategy, but failing that, I’m falling back to my default strategy of stealing from improving upon RogueSharp.

First, some bugfixing. @Chris3606 noticed a nasty habit of mine – instantiating Random number generators in constructors, using them, then disposing of them. There’s a good example of this in the Monster class:

    public class Monster : Actor
    {
        public Monster(Color foreground, Color background) : base(foreground, background, 'M')
        {
            Random rndNum = new Random();

Seems reasonable, doesn’t it? Well, every time a new Monster object is constructed, it also creates a new Random number generator. The problem is that the seed value passed to the Random generator isn’t guaranteed to be different than the last time the generator was created. In the end, you’ll end up with non-random Monster generation which is pretty annoying in a Roguelike. Thankfully, the fix is simple:

    public class Monster : Actor
    {
        private Random rndNum = new Random();

        public Monster(Color foreground, Color background) : base(foreground, background, 'M')
        {

The Random number generator must be declared in the class declaration itself. Now, each time rndNum.Next() is called, the generator will be passing a new pseudorandom number.

This simple mistake is peppered all over the project – take some time to move any Random number generator object declarations from the body of methods, into the class header. I found a couple of them in the World class’s CreateMonsters and CreateLoot methods. There is another in MapGenerator’s GenerateMap method. Faugh!

Okay, and now some very light duty refactoring because I did something very silly in a previous tutorial, when we were adding the EntityViewSyncComponent to each Entity in the World class. If you caught this yourself and fixed it earlier, good on you! Here’s a good example of what I was doing:

newMonster.Components.Add(new EntityViewSyncComponent());

And then again here:

Player.Components.Add(new EntityViewSyncComponent());

And AGAIN here:

newLoot.Components.Add(new EntityViewSyncComponent());

It sure would make a lot more sense to add the EntityViewSyncComponent to Entity’s constructor, instead of repeating code ad infinitum, wouldn’t it? Delete the aforementioned occurrences, and then modify the Entity class like so:

using SadConsole.Components;

namespace SadConsoleRLTutorial.Entities
{
    ...
    {
        ...
        protected Entity(Color foreground, Color background, int glyph, int width = 1, int height = 1) : base(width, height)
        {
           ...
            // Create a new unique identifier for this entity
            ID = Map.IDGenerator.UseID();
            // Ensure that the entity position/offset is tracked by scrollingconsoles
            Components.Add(new EntityViewSyncComponent());

        }

Okay, time for some housecleaning. Create a Tiles sub-folder in your project if you haven’t already – these will store all of our Tile-related classes, including TileBase, TileFloor, TileWall – and yes – our next class, TileDoor.

Move those classes into the folder. This also means updating their namespaces to:

namespace SadConsoleRLTutorial.Tiles

Try to compile your project. Now you’ll see it complain of missing references. Update these missing references by adding the following directive at the top of the files:

using SadConsoleRLTutorial.Tiles;

TileDoor Class

Now it’s time to get down and dirty. Our doors are going to have a few properties:

Now let’s construct the new TileDoor class:

using System;
using Microsoft.Xna.Framework;
namespace SadConsoleRLTutorial.Tiles
{
    public class TileDoor : TileBase
    {

        public bool Locked; // Locked door = 1, Unlocked = 0
        public bool IsOpen; // Open door = 1, closed = 0

        //Default constructor
        //A TileDoor can be set locked/unlocked/open/closed using the constructor.
        public TileDoor(bool locked, bool open) : base(Color.Gray, Color.Transparent, '+')
        {
            //+ is the closed glyph
            //closed by default
            Glyph = '+';

            //Update door fields
            Locked = locked;
            IsOpen = open;

            //change the symbol to open if the door is open
            if (!Locked && IsOpen)
                Open();
            else if (Locked || !IsOpen)
                Close();
        }

        //closes a door
        public void Close()
        {
            IsOpen = false;
            Glyph = '+';
            IsBlockingLOS = true;
            IsBlockingMove = true;
        }

        //opens a door
        public void Open()
        {
            IsOpen = true;
            IsBlockingLOS = false;
            IsBlockingMove = false;
            Glyph = '-';
        }
    }
}

I’ve intentionally kept this class extremely simple for the time being. Later we’re going to add doorlocks and keys, but for now we just want to set the locks and open/closed states using some simple booleans.

Map Door Generation

Now we’re going to generate some doors using the room generation method we created several tutorials ago. If you’ve created your own room generation algorithm, you’re on your own here. If you’re working from the vanilla tutorials, this is a breakdown of the strategy:

Load up the MapGenerator class. This is where we’re going to create doors by modifying the GenerateMap method. Let’s add a new method first:

        //Tries to create a TileDoor object in a specified Rectangle
        //perimeter. Reads through the entire list of tiles comprising
        //the perimeter, and determines if each position is a viable
        //candidate for a door.
        //When it finds a potential position, creates a closed and
        //unlocked door.
        private void CreateDoor(Rectangle room)
        {
            List<Point> borderCells = GetBorderCellLocations(room);

            //go through every border cell and look for potential door candidates
            foreach (Point location in borderCells)
            {
                int locationIndex = location.ToIndex(_map.Width);
                if (IsPotentialDoor(location))
                {
                    // Create a new door that is closed and unlocked.
                    TileDoor newDoor = new TileDoor(false, false);
                    _map.Tiles[locationIndex] = newDoor;

                }
            }
        }

You might remember the GetBorderCellLocations method from an earlier tutorial. If you feed it a Rectangle, it returns a List of Points that make up the rectangle’s perimeter. Handy for this kind of job!

The foreach loop goes through each location in the border of the room, testing if the position is a viable candidate for a door using IsPotentialDoor. If it finds a viable candidate, it creates a closed and unlocked door there.

Well, obviously we need that IsPotentialDoor method now, so let’s take a crack at it:

        // Determines if a Point on the map is a good
        // candidate for a door.
        // Returns true if it's a good spot for a door
        // Returns false if there is a Tile that IsBlockingMove=true
        // at that location
        private bool IsPotentialDoor(Point location)
        {
            //if the target location is not walkable
            //then it's a wall and not a good place for a door
            int locationIndex = location.ToIndex(_map.Width);
            if (_map.Tiles[locationIndex] != null && _map.Tiles[locationIndex] is TileWall)
            {
                return false;
            }

            //store references to all neighbouring cells
            Point right = new Point(location.X + 1, location.Y);
            Point left = new Point(location.X - 1, location.Y);
            Point top = new Point(location.X, location.Y - 1);
            Point bottom = new Point(location.X, location.Y + 1);

            // check to see if there is a door already in the target
            // location, or above/below/right/left of the target location
            // If it detects a door there, return false.
            if (_map.GetTileAt<TileDoor>(location.X, location.Y) != null ||
                _map.GetTileAt<TileDoor>(right.X, right.Y) != null ||
                _map.GetTileAt<TileDoor>(left.X, left.Y) != null ||
                _map.GetTileAt<TileDoor>(top.X, top.Y) != null ||
                _map.GetTileAt<TileDoor>(bottom.X, bottom.Y) != null
               )
            {
                return false;
            }

            //if all the prior checks are okay, make sure that the door is placed along a horizontal wall
            if (!_map.Tiles[right.ToIndex(_map.Width)].IsBlockingMove && !_map.Tiles[left.ToIndex(_map.Width)].IsBlockingMove && _map.Tiles[top.ToIndex(_map.Width)].IsBlockingMove && _map.Tiles[bottom.ToIndex(_map.Width)].IsBlockingMove)
            {
                return true;
            }
            //or make sure that the door is placed along a vertical wall
            if (_map.Tiles[right.ToIndex(_map.Width)].IsBlockingMove && _map.Tiles[left.ToIndex(_map.Width)].IsBlockingMove && !_map.Tiles[top.ToIndex(_map.Width)].IsBlockingMove && !_map.Tiles[bottom.ToIndex(_map.Width)].IsBlockingMove)
            {
                return true;
            }
            return false;
        }

See, this is why it’s better to buy beer from the liquor store instead of brewing your own at home. Yeah, you get that nice feeling of accomplishment and warmth with homebrewed stuff, but an hour later you’re bloated and gassy and your burps taste like cat food. Brewing my own Door placement logic tastes pretty much the same. This method is bloated and heavy, and is computationally expensive. But it works.

This is a pretty heavy method, so let’s step through it. It basically does a bunch of checks to determine whether the target Point is worthy of receiving a brand new door. To pass that test the location must:

See that GetTileAt method? It’s sexy, and we’ll be creating it soon. Its purpose is to check for a specific kind of tile at a specified position, and returns true if it finds one there. This is one of those general purpose methods that will become more useful the deeper we went into this little project of ours. For now though, it’s going to tell us if there is a Door located at a position.

Finally, the method does some boolean and/or logic that helps to decide whether the door is being placed along a wall that is horizontal or vertical. This is a sanity check to make sure that the door isn’t being placed, say, in the middle of two intersecting rooms or tunnels. There always have to be walls above/below, or right/left, of the Door to ensure that this is an appropriate place for a door.

Now it’s time to create that GetTileAt method. Open your Map class and add this method:

        //really snazzy way of checking whether a certain type of
        //tile is at a specified location in the map's Tiles
        //and if it exists, return that Tile
        //accepts an x/y coordinate
        public T GetTileAt<T>(int x, int y) where T : TileBase
        {
            int locationIndex = Helpers.GetIndexFromPoint(x, y, Width);
            // make sure the index is within the boundaries of the map!
            if (locationIndex <= Width * Height && locationIndex >= 0)
            {
                if (Tiles[locationIndex] is T)
                    return (T)Tiles[locationIndex];
                else return null;
            }
            else return null;
        }

Remember that little talk we had about generics in Tutorial 11? The idea is to create a GetTileAt method that accepts any type of Tile that inherits TileBase using the T directive. The where T: TileBase keywords guarantee to the compiler that it can only accept objects of type TileBase. The definition reads aloud something like this: Return a TileBase of Some Type given that we know the X and Y coordinates. If the location has no Tile of that type, then return null.

Oh – and don’t forget to include a using SadConsoleRLTutorial.Tiles directive in there too.

Okay, now back to the MapGenerator class. Now we can actually generate the doors. Modify the GenerateMap method like this:

        public Map GenerateMap(int mapWidth, int mapHeight, int maxRooms, int minRoomSize, int maxRoomSize)
        {
...
            // carve out tunnels between all rooms
            // based on the Positions of their centers
            for (int r = 1; r < Rooms.Count; r++)
            {
...

            // Create doors now that the tunnels have been carved out
            foreach (Rectangle room in Rooms)
            {
                CreateDoor(room);
            }

            // spit out the final map
            ...

Make sure that the CreateDoor code is after the Tunnels have been carved out, but before the final map has been returned.

Run your project. Congratulations! With any luck you have doors littered all over your map and no way of getting past them. Don’t worry – we’ll cover that in the next tutorial. For now, enjoy your imprisonment ūüėÄ

Download the completed source files for the tutorial here.

Published on April 3, 2019

Tutorial Part 12: Inventory Management & Loot Drops (SadConsole v8)

So a recap: we’ve got movement, monsters and combat. What’s next? We probably want those monsters to drop some loot, and have that loot appear on the map, then get picked up by the player when they walk over it. For that, we’re going to need a simple inventory system that lets Actors store all kinds of loot.

First off, let’s create an Item class. Items will be our most generic type of entity that allows itself to be picked up, damaged, and destroyed.

Overly complex inheritance hierarchies tend to rear their ugly heads when we start defining different types of Items. For that reason, I am going to keep the Item class as generic as possible. I’ll leave it up to you to make more specific types of items. But be warned: it’s tempting to abuse inheritance, and you’ll pay the price for your love of categorizing!

using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Entities
{
    // Item: Describes things that can be picked up or used
    // by actors, or destroyed on the map.
    public class Item : Entity
    {
        // backing field for Condition
        private int _condition;

        public int Weight { get; set; } // mass of the item
        // physical condition of item, in percent
        // 100 = item undamaged
        // 0 = item is destroyed

        public int Condition
        {
            get { return _condition; }
            set
            {
                _condition += value;
                if (_condition <= 0)
                    Destroy();
            }
        }

        // By default, a new Item is sized 1x1, with a weight of 1, and at 100% condition
        public Item(Color foreground, Color background, string name, char glyph, int weight = 1, int condition = 100, int width = 1, int height = 1) : base(foreground, background, glyph)
        {
            // assign the object's fields to the parameters set in the constructor
            Animation.CurrentFrame[0].Foreground = foreground;
            Animation.CurrentFrame[0].Background = background;
            Animation.CurrentFrame[0].Glyph = glyph;
            Weight = weight;
            Condition = condition;
            Name = name;
        }

        // Destroy this object by removing it from
        // the MultiSpatialMap's list of entities
        // and lets the garbage collector take it
        // out of memory automatically.
        public void Destroy()
        {
            GameLoop.World.CurrentMap.Remove(this);
        }
    }
}

Sweet and simple. For fun, I’ve included a cute Condition getter and setter that has a bit of game logic in it. When the Condition falls at or below zero, the item gets destroyed. Because Item is a managed type, the C# garbage collector will automatically destroy the object once the object falls out of scope. We force the object to leave scope by removing it from the Entities list.

The list of parameters in the constructor is a little on the chubby side. I’ve added several defaults to save time when instantiating new Items.

Now we can spawn a few items on the map. This is something we’ll want to do with a more formal generator at a later time, but for now let’s manually create a few items on the map. Modify the World class accordingly:

        // Create some sample treasure
        // that can be picked up on the map
        private void CreateLoot()
        {
            // number of treasure drops to create
            int numLoot = 20;

            Random rndNum = new Random();

            // Produce lot up to a max of numLoot
            for (int i = 0; i < numLoot; i++)
            {
                // Create an Item with some standard attributes
                int lootPosition = 0;
                Item newLoot = new Item(Color.Green, Color.Transparent, "fancy shirt", 'L', 2);

                // Let SadConsole know that this Item's position be tracked on the map
                newLoot.Components.Add(new EntityViewSyncComponent());

                // Try placing the Item at lootPosition; if this fails, try random positions on the map's tile array
                while (CurrentMap.Tiles[lootPosition].IsBlockingMove)
                {
                    // pick a random spot on the map
                    lootPosition = rndNum.Next(0, CurrentMap.Width * CurrentMap.Height);
                }

                // set the loot's new position
                newLoot.Position = new Point(lootPosition % CurrentMap.Width, lootPosition / CurrentMap.Width);

                // add the Item to the MultiSpatialMap
                CurrentMap.Add(newLoot);
            }

        }

There’s nothing new or surprising here. This is pretty much the same code we used when we created the CreateMonster method.

And don’t forget to call the method in the World constructor:

        public World()
        {
...
            // spawn some loot
            CreateLoot();
        }

Run the project and walk over the loot. Nothing much happens eh? We need to create some game logic to deal with picking up items. Some Roguelikes have a special key reserved for Picking up items, while others let your walk over the item and auto-pickup. You’ll have to decide what kind of gameplay you prefer. For now, let’s do the easy one: picking up involves walking over an item.

Let’s modify the Actor class to add auto-pickup in the MoveBy method:

// Moves the Actor BY positionChange tiles in any X/Y direction
        // returns true if actor was able to move, false if failed to move
        public bool MoveBy(Point positionChange)
        {
            // Check the current map if we can move to this new position
            if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
            {
                // if there's a monster here,
                // do a bump attack
                Monster monster = GameLoop.World.CurrentMap.GetEntityAt<Monster>(Position + positionChange);
                Item item = GameLoop.World.CurrentMap.GetEntityAt<Item>(Position + positionChange);
                if (monster != null)
                {
                    GameLoop.CommandManager.Attack(this, monster);
                    return true;
                }
                // if there's an item here,
                // try to pick it up
                else if (item != null)
                {
                    GameLoop.CommandManager.Pickup(this, item);
                    return true;
                }

                Position += positionChange;
                return true;
            }
            else
                return false;
        }

The modification is straightforward: we’re testing for the existence of an Item at the target location. If an item exists there, try to pick it up by calling the Pickup method in the CommandManager class.

Now, I don’t know about you, but I’m getting a little itchy in the britches seeing that much game logic packed into every Actor’s MoveBy method. If you walk away from your code for 6 months, would you remember that the Pickup and Attack commands are called from within the Actor class? It seems like a recipe for uninhabitable code. Worse, every time we add a different type of object to test for, that MoveBy method is going to become fatter and fatter. So let’s flag this class in the back of our minds for future refactoring. I don’t know how we’ll solve that problem yet – but there has to be a better location for it!

Actors need an inventory to store their possessions. This one’s simple. Add the following to the definition in the Actor class:

    public abstract class Actor : Entity
    {
        ...
        public List<Item> Inventory = new List<Item>(); // the player's collection of items
        ...
    }

(You may need to add a using System.Collections.Generic to the top of your class, to let you use Lists). Simple, right? We’ll add plenty of functionality to the Inventory List later.

Okay, let’s finally add the Pickup command to our CommandManager:

        // Tries to pick up an Item and add it to the Actor's
        // inventory list
        public void Pickup(Actor actor, Item item)
        {
            // Add the item to the Actor's inventory list
            // and then destroy it
            actor.Inventory.Add(item);
            GameLoop.UIManager.MessageLog.Add($"{actor.Name} picked up {item.Name}");
            item.Destroy();
        }

Hey, neat! We found an alternate use for our Item.Destroy method. Keep in mind that it does not actually destroy the item – it tells SadConsole to stop tracking it as an onscreen Entity, and GoRogue to remove it from its MultiSpatialMap. Urgh. Now I’m beginning to think we should have called the method Item.RemoveFromMap. Oh well. It’s just that Destroy sounds so hot.

And for the piece de resistance – let’s give monsters actual loot, instead of that fake gold they’ve been lying about for days! Modify the Monster class:

        public Monster(Color foreground, Color background) : base(foreground, background, 'M')
        {
            Random rndNum = new Random();

            //number of loot to spawn for monster
            int lootNum = rndNum.Next(1, 4);

            for (int i = 0; i < lootNum; i++)
            {
                // monsters are made out of spork, obvs.
                Item newLoot = new Item(Color.HotPink, Color.Transparent, "spork", 'L', 2);
                newLoot.Components.Add(new SadConsole.Components.EntityViewSyncComponent());
                Inventory.Add(newLoot);
            }
        }

Okay, all Monsters possess spork now. Pounds and pounds of spork. Now we need them to drop their spork when they croak. That’s a job handled by our CommandManager. I rewrote the ResolveDeath method to take advantage of more verbose logging:

        // Removes an Actor that has died
        // and displays a message showing
        // the actor that has died, and they loot they dropped
        private static void ResolveDeath(Actor defender)
        {
            // Set up a customized death message
            StringBuilder deathMessage = new StringBuilder($"{defender.Name} died");

            // dump the dead actor's inventory (if any)
            // at the map position where it died
            if (defender.Inventory.Count > 0)
            {
                deathMessage.Append(" and dropped");

                foreach (Item item in defender.Inventory)
                {
                    // move the Item to the place where the actor died
                    item.Position = defender.Position;

                    // Now let the MultiSpatialMap know that the Item is visible
                    GameLoop.World.CurrentMap.Add(item);

                    // Append the item to the deathMessage
                    deathMessage.Append(", " + item.Name);
                }

                // Clear the actor's inventory. Not strictly
                // necessary, but makes for good coding habits!
                defender.Inventory.Clear();
            }
            else
            {
                // The monster carries no loot, so don't show any loot dropped
                deathMessage.Append(".");
            }

            // actor goes bye-bye
            GameLoop.World.CurrentMap.Remove(defender);

            // Now show the deathMessage in the messagelog
            GameLoop.UIManager.MessageLog.Add(deathMessage.ToString());
        }

In the above snippet, we’re digging through the dead actor’s Inventory. For every item found in that inventory, set its position to the actor’s (previous) position, and add that Item to the Map.

Give that a go. If you’re not smiling when you pick up pounds upon pounds of gleaming pink monster spork giblets, you’re slowly turning into John Carmack. If you giggled maniacally, you’ve turned into John Romero.

In our next tutorial, let’s build some UI to show off our prized collection of spork and fancy shirts! Oh – and some door generation code. >_>

Download the final source code for Tutorial Part 12 here.

Published on March 10, 2019

Tutorial Part 11: Enemy Entities, Combat & GoRogue Integration (SadConsole v8)

<Hoonius> And you’re at part 10 and no glyph has been yet killed. You going for pacifistlike? ūüėČ

Well er… good point. I guess it’s time that we define what an enemy is, and come up with some clever ways to make them disappear from the screen. I’ll be borrowing extensively from the RogueSharp tutorials again, because the architecture is very understandable for a first-time RL creator.

The overall plan is to:

Let’s sort out our project a bit, if you haven’t already. We’re going to be creating more Entities aside from the Player. Create an Entities sub-folder in your project, and move the Actor and Player classes into it. Don’t forget to  update their namespaces accordingly:

namespace SadConsoleRLTutorial.Entities

This also means adding the following using to the CommandManager, World and UIManager classes:

using SadConsoleRLTutorial.Entities;

Entities

SadConsole handles all of the graphics display stuff, so we don’t need to worry about their positions or on/offscreen clipping – but we will need to be able to check for collisions between Entities very soon.

An earlier version of this tutorial used an EntityManager. A quickly bloating number of middle managers is a rank code smell, detected by @Chris3606 who suggested getting rid of the EntityManager entirely. This turned out to be great advice, and the tutorial is much, much simpler now!

Ah, now we finally have a need for the GoRogue library. I’ve been waiting to integrate this sucker for weeks now. GoRogue (documentation here) is a collection of engine-agnostic tools that take care of a ton of jobs that I personally don’t want to write code for. One of those jobs is searching through lists of objects for a specific type of object. Another one of those little jobs is Dicing. We could write our own search algorithms, but why re-invent the wheel?

So, add the “GoRogue” NuGet to your packages. We’ll be using version 2.

We’ll be using GoRogue a lot more in the future. You don’t have to use it, but it’s going to help us do much faster searches for Entity objects on the map and cut down on type conversions.

A Custom Entity Class

So far we’ve used the SadConsole.Entities.Entity class without needing to extend it in any way. Unfortunately, now all of our Entities (Actors, Monsters, and eventually Items) will need some additional data structures in order to work for us. So let’s extend SadConsole’s default Entity class by creating a new class: Entity. Put it in the Entities folder and add the following code:

using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Entities
{
    // Extends the SadConsole.Entities.Entity class
    // by adding an ID to it using GoRogue's ID system
    public abstract class Entity : SadConsole.Entities.Entity, GoRogue.IHasID
    {
        public uint ID { get; private set; } // stores the entity's unique identification number

        protected Entity(Color foreground, Color background, int glyph, int width = 1, int height = 1) : base(width, height)
        {
            Animation.CurrentFrame[0].Foreground = foreground;
            Animation.CurrentFrame[0].Background = background;
            Animation.CurrentFrame[0].Glyph = glyph;

            // Create a new unique identifier for this entity
            ID = Map.IDGenerator.UseID();
        }
    }
}

A look at the class definition tells us that our custom Entity inherits SadConsole’s Entity, and also inherits GoRogue’s IHasID interface.

What’s an Interface? Interfaces require a class to implement its data structures and methods. It’s like someone hands you a worksheet that says, “Solve this math problem. I don’t care how you do it, but your answer has to be an unsigned integer with the variable name answer.” Interfaces only contain the signatures or definitions of methods and data structures – they have no implementation code.

So in this case, we’re telling the compiler that our Entity class must use the data structures and methods contained in GoRogue’s IHasID.

Naming pro-tip: Interface classes are usually named with an I preceding the variable name to indicate that they are Interfaces and not some other type of class. e.g. ICanHasCheezburger.

IHasID is a pretty simple interface. It wants us to implement a single data structure: an unsigned integer property with the name ID.

@Chris3606 warned me that it’s critical to give ID a private setter, as changing the ID mid-game can horribly break things. He writes, “changing the ID of an entity while it’s a part of a spatial map can break things pretty hard, as spatial map implementations assume those remain constant from when an entity is added to when it is removed. ” Roger Wilco!

The Entity class constructor accepts the kinds of parameters we are used to using when we create Entities. You’ve seen this all before.

Ooh. What’s that last line though? Every time we construct a new Entity, we’re telling it that it needs to have a new ID assigned. GoRogue will do the heavy lifting here, and create random unique identification numbers for us. As you can see, we’re making a call to a static method in the Map class. So let’s rework the Map class right now.

Rewritten Map Class

We need our Map class to tell us if an Entity is at a specific position and generate unique IDs for us. Later we’ll add some functionality to the class in order to manage our burgeoning cast of actors.

using System;
using System.Linq;
using Microsoft.Xna.Framework;
using SadConsoleRLTutorial.Entities;

namespace SadConsoleRLTutorial
{
    // Stores, manipulates and queries Tile data
    public class Map
    {
        TileBase[] _tiles; // contain all tile objects
        private int _width;
        private int _height;

        public TileBase[] Tiles { get { return _tiles; } set { _tiles = value; } }
        public int Width { get { return _width; } set { _width = value; } }
        public int Height { get { return _height; } set { _height = value; } }

        public GoRogue.MultiSpatialMap<Entity> Entities; // Keeps track of all the Entities on the map
        public static GoRogue.IDGenerator IDGenerator = new GoRogue.IDGenerator(); // A static IDGenerator that all Entities can access

        //Build a new map with a specified width and height
        public Map(int width, int height)
        {
            _width = width;
            _height = height;
            Tiles = new TileBase[width * height];
            Entities = new GoRogue.MultiSpatialMap<Entity>();
        }

        // IsTileWalkable checks
        // to see if the actor has tried
        // to walk off the map or into a non-walkable tile
        // Returns true if the tile location is walkable
        // false if tile location is not walkable or is off-map
        public bool IsTileWalkable(Point location)
        {
            // first make sure that actor isn't trying to move
            // off the limits of the map
            if (location.X < 0 || location.Y < 0 || location.X >= Width || location.Y >= Height)
                return false;
            // then return whether the tile is walkable
            return !_tiles[location.Y * Width + location.X].IsBlockingMove;
        }

        // Checking whether a certain type of
        // entity is at a specified location the manager's list of entities
        // and if it exists, return that Entity
        public T GetEntityAt<T>(Point location) where T : Entity
        {
            return Entities.GetItems(location).OfType<T>().FirstOrDefault();
        }

        // Removes an Entity from the MultiSpatialMap
        public void Remove(Entity entity)
        {
            // remove from SpatialMap
            Entities.Remove(entity);

            // Link up the entity's Moved event to a new handler
            entity.Moved -= OnEntityMoved;
        }

        // Adds an Entity to the MultiSpatialMap
        public void Add(Entity entity)
        {
            // add entity to the SpatialMap
            Entities.Add(entity, entity.Position);

            // Link up the entity's Moved event to a new handler
            entity.Moved += OnEntityMoved;
        }

        // When the Entity's .Moved value changes, it triggers this event handler
        // which updates the Entity's current position in the SpatialMap
        private void OnEntityMoved(object sender, Entity.EntityMovedEventArgs args)
        {
            Entities.Move(args.Entity as Entity, args.Entity.Position);
        }
    }
}

Whoa! What the heck are MultiSpatialMaps? Generally, GoRogue’s SpatialMaps are an alternative to creating a huge List or Collection of Entities that we search through for entities with expensive loops. I won’t go into their design and implementation, so here’s a quote directly from the source docs:

Typical roguelikes will use a 2D array (or 1D array accessed as a 2D array), for terrain, and lists of objects for things like entities, items, etc. This is simple but ultimately not efficient; for example, in that implementation, determining if there is an object at a location takes an amount of time proportional to the number of objects in this list. However, the other simple option is to use an array with size equal to the size of the map (as many do for terrain) for all object lists. This is even less ideal, as in that case, the time to iterate over all objects becomes proportional to the size of the map (since one has to do that for rendering, ouch!), which is typically much larger than the number of objects in a list. This is the problem SpatialMap is designed to solve. It provides fast, near-constant-time operations for getting the object at a location, adding entities, removing entities, and will allow you to iterate through all objects in the SpatialMap in time proportional to the number of objects in it (the best possible). Effectively, it is a more efficient list for objects that have a position associated with them. This implementation can only allow one item at a given location at a time — for an implementation that allows multiple items, see MultiSpatialMap. The objects stored in a SpatialMap must implement the IHasID (see that interface’s documentation for an easy implementation example). This is used internally to keep track of the objects, since uints are easily (efficiently) hashable.

In other words: a SpatialMap is way faster at searching for objects than looping through Lists or Collections. We’re going to use that feature bigtime.

OK fine. So what’s a MultiSpatialMap? It’s a SpatialMap that allows multiple objects (items, entities, etc) to exist in the same position.

Alright, so next in our class, we’re creating a static GoRogue IDGenerator. Its job will be to generate the unique IDs that our Entities require. These IDs allow SpatialMaps to single out objects from the map without doing a massive (expensive) search for them. I created this as a static instance to allow our Entity instances to assign themselves new IDs without needing an Map instance to exist in the first place.

And finally, the class implements a GetEntityAt method. The GetEntityAt method checks our MultiSpatialMap for a specific type of Entity at a specific location. If it finds an Entity of the given type at that location, it returns the object to the caller.

Now, let’s get down to the nitty-gritty of that nasty-looking Linq expression. Let’s break down the method signature first:

public T GetEntityAt<T>(Point location) where T : Entity

The T guarantees that the method will return an object of type T. What’s T? It is a variable that stands in for the type of object we’re interested in finding. Generics allow us to deal with a wide range of object types, while still guaranteeing type safety to the compiler. Think of it this way: say we want to search through our Entities list for a monster at some X and Y coordinate. We’d write a method definition that looks like this:

public Monster GetMonsterAt(Point location)
{
...
}

Okay, but what if we want to search for a Player at some X/Y location? Then we need to write a new method definition for that type:

public Player GetPlayerAt(Point location)
{
...
}

And so on. Every Actor type we’re interested in would require its own method, and soon our Map would be full of copypasta-repeated code. Generics allows us to write a single method that takes care of all types of Entities.

Back to our method signature. The <T> tells the compiler to generate code of the requested type specifically of type T. In other words, the compiler is actually generating new instances of the GetEntityAt method for each type of object we’re searching for — doing our typing for us, and saving us a ton of space in the class.

The where T : Entity keyword is tricky. This is a type-safety constraint that I’ve added to the definition in order to 100% ensure that we’re only passing objects of the right kinds to the method. This will prevent a coked-up programmer from trying to search for a Cell in the Entities list. Is that a likely thing to happen? You tell me. It’s not my job to judge your addiction. I just refuse to play the enabler.

Generics are confusing at first, because it seems impossible that we can pass something called T to the compiler and that it will understand this is a meaningful thing. We’ll use generics when necessary, but not too frequently – they have a way of making code impenetrable.

Finally, the method implements the meat of our code: it searches through the MultiSpatialMap for an object of type T and determines if it exists at a specific location.

return Entities.GetItems(location).OfType<T>().FirstOrDefault();

The method returns the first object that it finds at that location matching such a description. If it finds an object of type T at that location, it returns it to the caller.

The Add method adds the Entity to the MultiSpatialMap. Then it does something we haven’t dealt with much before: it subscribes the OnEntityMoved method to the Entity’s Moved event. An event is a message fired off by an object when something has happened. In this case, the object – an Entity – fires off a message every time it Moves. When we subscribe the Entities MultiSpatialMap to this event, we say something like, “Tell me when the Entity has moved, so I can do my own thing when it happens…”

In this case, the OnEntityMoved method is called whenever the event fires off a message. When the event fires off a message, it sends both the object who sent it (the sender) and the arguments it sent with it (args). We’ll use that object and its arguments to update our Entities SpatialMap. This method takes the sending entity, and updates its position in the Entities SpatialMap. This is necessary to keep the SpatialMap synched with the Entities we have on our Map.

The Remove method does two similar things for us: it removes the Entity from the MultiSpatialMap, and it unsubscribes the OnEntityMoved handler from the Entity’s Moved event. The first prevents the Entity from being checked for collisions, the second tells the compiler that we no longer care if the Entity has been Moved, because it’s gone!

UIManager Modifications

Now that we have more than one entity appearing on the MapConsole, we need to modify the UIManager so it can handle multiple entities.

First, let’s add a SyncMapEntities method that allows us to add the bulk of the MultiSpatialMap’s Entities to our MapConsole:

        // Adds the entire list of entities found in the
        // World.CurrentMap's Entities SpatialMap to the
        // MapConsole, so they can be seen onscreen
        private void SyncMapEntities(Map map)
        {
            // remove all Entities from the console first
            MapConsole.Children.Clear();

            // Now pull all of the entities into the MapConsole in bulk
            foreach (Entity entity in map.Entities.Items)
            {
                MapConsole.Children.Add(entity);
            }

            // Subscribe to the Entities ItemAdded listener, so we can keep our MapConsole entities in sync
            map.Entities.ItemAdded += OnMapEntityAdded;

            // Subscribe to the Entities ItemRemoved listener, so we can keep our MapConsole entities in sync
            map.Entities.ItemRemoved += OnMapEntityRemoved;
        }

Notice in the foreach loop that each Entity in the Map.Entities SpatialMap is an Item. Any time you need to access a SpatialMap’s objects, you’ll need to do it via Items. ISpatialMaps don’t care what you store in there – they’re all just generally called Items. You can store Entities or Cells or your grandmother’s collection of tiny teaspoons – as long as they all have IDs.

Now add a couple of new methods to handle event calls:

        // Remove an Entity from the MapConsole every time the Map's Entity collection changes
        public void OnMapEntityRemoved(object sender, GoRogue.ItemEventArgs<Entity> args)
        {
            MapConsole.Children.Remove(args.Item);
        }

        // Add an Entity to the MapConsole every time the Map's Entity collection changes
        public void OnMapEntityAdded(object sender, GoRogue.ItemEventArgs<Entity> args)
        {
            MapConsole.Children.Add(args.Item);
        }

Again, we’ll be using an Event and Listener pair to synchronize all of the Map’s Entities to the MapConsole. Without this, our MapConsole won’t notice that our game Map has changed. For example, if we kill a monster, we want the monster to disappear from both the Map and the MapConsole. Subscribing the OnMapEntityAdded method to the map.Entities SpatialMap event tells us to update the MapConsole every time there is a new Entity added to the SpatialMap. The same logic applies to OnMapEntityRemoved: if an Entity disappears from the Map, remove it from the MapConsole too!

I’m not happy at all with the way our UIManager instantiates itself and creates consoles. Let’s rewrite the Init method:

     // Initializes all windows and consoles
        public void Init()
        {
            CreateConsoles();

            //Message Log initialization
            MessageLog = new MessageLogWindow(GameLoop.GameWidth, GameLoop.GameHeight / 2, "Message Log");
            Children.Add(MessageLog);
            MessageLog.Show();
            MessageLog.Position = new Point(0, GameLoop.GameHeight / 2);

            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 12");
            MessageLog.Add("Testing 1");
            MessageLog.Add("Testing");
            MessageLog.Add("Testing 12");
            MessageLog.Add("Testing 1");
            MessageLog.Add("Testing");
            MessageLog.Add("Testing 12");
            MessageLog.Add("Testing 1");
            MessageLog.Add("Testing Last");

            // Load the map into the MapConsole
            LoadMap(GameLoop.World.CurrentMap);

            // Now that the MapConsole is ready, build the Window
            CreateMapWindow(GameLoop.GameWidth / 2, GameLoop.GameHeight / 2, "Game Map");
            UseMouse = true;

            // Start the game with the camera focused on the player
            CenterOnActor(GameLoop.World.Player);
        }

And while we’re at it, rewrite the CreateConsoles method like this:

        // Creates all child consoles to be managed
        public void CreateConsoles()
        {
            // Temporarily create a console with *no* tile data that will later be replaced with map data
            MapConsole = new ScrollingConsole(GameLoop.GameWidth, GameLoop.GameHeight);
        }

Now we can write a LoadMap method that loads any map that we pass to it as a parameter:

        // Loads a Map into the MapConsole
        private void LoadMap(Map map)
        {
            // First load the map's tiles into the console
            MapConsole = new SadConsole.ScrollingConsole(GameLoop.World.CurrentMap.Width, GameLoop.World.CurrentMap.Height, Global.FontDefault, new Rectangle(0, 0, GameLoop.GameWidth, GameLoop.GameHeight), map.Tiles);

            // Now Sync all of the map's entities
            SyncMapEntities(map);
        }

Whew. Take a breather! We’ve just refactored a ton of code. Now our program is a little less fragile and brittle.

Actors are Entities too, y’know.

Now that we are using a custom Entity class, we need to tell Actor that it is an Entity, not a SadConsole.Entities.Entity. So modify the Actor class by replacing its definition with this code:

public abstract class Actor : Entity

Take a breather. We’re at the halfway point of this very long tutorial. So far we’ve managed to build an Entity system that takes advantage of GoRogue’s MultiSpatialMaps. Now we’re going to put it to work using Monster generation and combat!

Monster Class

Create a new class called Monster in the Entities sub-folder. Monster will inherit the Actor class. Let’s keep this class extremely simple for now; we’ll come back to it later:

using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Entities
{
    //A generic monster capable of
    //combat and interaction
    //yields treasure upon death
    public class Monster : Actor
    {
        public Monster(Color foreground, Color background) : base(foreground, background, 'M')
        {

        }
    }
}

Let’s make some modifications to our Actor class, since combat and stats will apply to both Monsters and the Player:

    public abstract class Actor : Entity
    {
        public int Health { get; set; } // current health
        public int MaxHealth { get; set; } // maximum health
        public int Attack { get; set; } // attack strength
        public int AttackChance { get; set; } // percent chance of successful hit
        public int Defense { get; set; } // defensive strength
        public int DefenseChance { get; set; } // percent chance of successfully blocking a hit
        public int Gold { get; set; } // amount of gold carried
        ...
    }

Notice that I’ve removed the hand-written getters and setters for Health and MaxHealth, and replaced them with auto-properties. It saves space, and makes the class easier on the eyes. Besides, we don’t need the getters/setters to do anything special beyond communicating with the class’s private data.

Command: Attack

Modify the CommandManager to include:

using System.Text;

We’ll be using the Stringbuilder to add nice text descriptions of our attack sequence to the Message Log.

        // Executes an attack from an attacking actor
        // on a defending actor, and then describes
        // the outcome of the attack in the Message Log
        public void Attack(Actor attacker, Actor defender)
        {
            // Create two messages that describe the outcome
            // of the attack and defense
            StringBuilder attackMessage = new StringBuilder();
            StringBuilder defenseMessage = new StringBuilder();

            // Count up the amount of attacking damage done
            // and the number of successful blocks
            int hits = ResolveAttack(attacker, defender, attackMessage);
            int blocks = ResolveDefense(defender, hits, attackMessage, defenseMessage);

            // Display the outcome of the attack & defense
            GameLoop.UIManager.MessageLog.Add(attackMessage.ToString());
            if (!string.IsNullOrWhiteSpace(defenseMessage.ToString()))
            {
                GameLoop.UIManager.MessageLog.Add(defenseMessage.ToString());
            }

            int damage = hits - blocks;

            // The defender now takes damage
            ResolveDamage(defender, damage);
        }

The StringBuilder is a fantastic class — it allows us to create and modify strings that are mutable. Strings in C# are normally immutable or “non-modifiable”. Each time you modify a plain string, you are actually creating a new string. This feels counterintuitive at the best of times, or just plain annoying most of the time. The StringBuilder allows us to modify a string right on the spot, without creating a new output string.

In this case, we’re creating an empty StringBuilder that we’ll populate with characters later. We call the ResolveAttack and ResolveDefense methods to do the actual damage calculations, as well as create our fancy MessageLog messages. We’ll get to implementing those methods next. The ResolveDamage method will just subtract the total damage from the defender’s Health.

Add a new using to the CommandManager. We’ll be relying upon GoRogue.DiceNotation for all dice rolls:

using GoRogue.DiceNotation;

Let’s build our ResolveAttack method. Ours a simplified version of the RogueSharp method.

        // Calculates the outcome of an attacker's attempt
        // at scoring a hit on a defender, using the attacker's
        // AttackChance and a random d100 roll as the basis.
        // Modifies a StringBuilder message that will be displayed
        // in the MessageLog
        private static int ResolveAttack(Actor attacker, Actor defender, StringBuilder attackMessage)
        {
            // Create a string that expresses the attacker and defender's names
            int hits = 0;
            attackMessage.AppendFormat("{0} attacks {1}, ", attacker.Name, defender.Name);

            // The attacker's Attack value determines the number of D100 dice rolled
            for (int dice = 0; dice < attacker.Attack; dice++)
            {
                //Roll a single D100 and add its results to the attack Message
                int diceOutcome = Dice.Roll("1d100");

                //Resolve the dicing outcome and register a hit, governed by the
                //attacker's AttackChance value.
                if (diceOutcome >= 100 - attacker.AttackChance)
                    hits++;
            }

            return hits;
        }

It’s worth noting that I’ve greatly simplified RogueSharp’s style of dicing outcomes with a for loop. GoRogue does offer a much more efficient means for resolving multiple dice without re-rolls, but I find that style less habitable. After all, we’re here to build readable code, not an Enzo Ferrari.

We resolve the dicing outcome by comparing the roll of a single D100 dice (“1d100”) vs. 100 – attacker.AttackChance. In other words: if the attacker has an AttackChance of 20, it will need to roll at least a 20 in order to register a hit. Let’s move on to the ResolveDefense method:

        // Calculates the outcome of a defender's attempt
        // at blocking incoming hits.
        // Modifies a StringBuilder messages that will be displayed
        // in the MessageLog, expressing the number of hits blocked.
        private static int ResolveDefense(Actor defender, int hits, StringBuilder attackMessage, StringBuilder defenseMessage)
        {
            int blocks = 0;
            if (hits > 0)
            {
                // Create a string that displays the defender's name and outcomes
                attackMessage.AppendFormat("scoring {0} hits.", hits);
                defenseMessage.AppendFormat(" {0} defends and rolls: ", defender.Name);

                //The defender's Defense value determines the number of D100 dice rolled
                for (int dice = 0; dice < defender.Defense; dice++)
                {
                    //Roll a single D100 and add its results to the defense Message
                    int diceOutcome = Dice.Roll("1d100");

                    //Resolve the dicing outcome and register a block, governed by the
                    //attacker's DefenceChance value.
                    if (diceOutcome >= 100 - defender.DefenseChance)
                        blocks++;
                }
                defenseMessage.AppendFormat("resulting in {0} blocks.", blocks);
            }
            else
            {
                attackMessage.Append("and misses completely!");
            }
            return blocks;
        }

This method is almost the same as our ResolveAttack method, except that we’re counting the number of successful blocks in the for loop. Notice that we’re using DefenseChance as our modifier for successful blocks. Now we can move on to resolving the damage taken per hit:

        // Calculates the damage a defender takes after a successful hit
        // and subtracts it from its Health
        // Then displays the outcome in the MessageLog.
private static void ResolveDamage(Actor defender, int damage)
        {
            if (damage > 0)
            {
                defender.Health = defender.Health - damage;
                GameLoop.UIManager.MessageLog.Add($" {defender.Name} was hit for {damage} damage");
                if (defender.Health <= 0)
                {
                    ResolveDeath(defender);
                }
            }
            else
            {
                GameLoop.UIManager.MessageLog.Add($"{defender.Name} blocked all damage!");
            }
        }

And now we can add the ResolveDeath command:

        // Removes an Actor that has died
        // and displays a message showing
        // the number of Gold dropped.
        private static void ResolveDeath(Actor defender)
        {
            GameLoop.World.Map.Remove(defender);

            if (defender is Player)
            {
                GameLoop.UIManager.MessageLog.Add($" {defender.Name} was killed.");
            }
            else if (defender is Monster)
            {
                GameLoop.UIManager.MessageLog.Add($"{defender.Name} died and dropped {defender.Gold} gold coins.");
            }
        }

That’s a fairly simple way of dealing with Actor death. We can gussy it up with something prettier later. Note that we’re making a call to the Map to Remove the actor from the play field when it dies.

Now to actually generate some Monsters! Modify the World class, and we’ll spawn our monsters in there for now. Eventually, we’ll want our own monster generation class.

        // Create some random monsters with random attack and defense values
        // and drop them all over the map in
        // random places.
        private void CreateMonsters()
        {
            // number of monsters to create
            int numMonsters = 10;

            // random position generator
            Random rndNum = new Random();

            // Create several monsters and 
            // pick a random position on the map to place them.
            // check if the placement spot is blocking (e.g. a wall)
            // and if it is, try a new position
            for (int i=0; i < numMonsters; i++)
            {
                int monsterPosition=0;
                Monster newMonster = new Monster(Color.Blue, Color.Transparent);
                newMonster.Components.Add(new EntityViewSyncComponent());
                while (CurrentMap.Tiles[monsterPosition].IsBlockingMove)
                {
                    // pick a random spot on the map
                    monsterPosition = rndNum.Next(0, CurrentMap.Width * CurrentMap. Height);
                }

                // plug in some magic numbers for attack and defense values
                newMonster.Defense = rndNum.Next(0, 10);
                newMonster.DefenseChance = rndNum.Next(0, 50);
                newMonster.Attack = rndNum.Next(0, 10);
                newMonster.AttackChance = rndNum.Next(0, 50);
                newMonster.Name = "a common troll";

                // Set the monster's new position
                // Note: this fancy math will be replaced by a new helper method
                // in the next revision of SadConsole
                newMonster.Position = new Point(monsterPosition % CurrentMap.Width, monsterPosition / CurrentMap.Width);
                CurrentMap.Add(newMonster);
            }
        }

The while loop in this method makes sure that the monster will be placed in a walkable tile position. This is not the preferred nor recommended method for monster placement, because it’s possible that your map has no available unblocked spots and would cause an endless loop. We’re just using this loop for the time being, until we create our own monster generation class.

Don’t forget to add the EntityViewSyncComponent to the monster, as this allows the monster to appear on our ScrollingConsole.

We’re also giving the new monster some random defence and attack attributes. I picked magical numbers out of my head as “maximums” for the random number generator, so be sure to modify these to your liking.

Finally, we set the monster’s position using a snazzy algorithm for determining X and Y coordinates from an array index. The % or remainder operator isn’t used very often, yet it is one of the most powerful and useful operators there is. It just computes the reminder of division between the first and second terms. In this case, it’s computing the remainder from the division of monsterPosition by the map Width. The reminder is the X coordinate of the monster. The Y coordinate of the monster is the dividend given by division of the monsterPosition by the map Width.

Then, we need to add the new monster to the Map’s MultiSpatialMap. Without that, our monster won’t trigger collisions with the player.

Now modify the CreatePlayer method in the World class:

        // Create a player using the Player class
        // and set its starting position
        private void CreatePlayer()
        {
            Player = new Player(Color.Yellow, Color.Transparent);
            Player.Components.Add(new EntityViewSyncComponent());

            // Place the player on the first non-movement-blocking tile on the map
            for (int i = 0; i < CurrentMap.Tiles.Length; i++)
            {
                if (!CurrentMap.Tiles[i].IsBlockingMove)
                {
                    // Set the player's position to the index of the current map position
                    Player.Position = SadConsole.Helpers.GetPointFromIndex(i, CurrentMap.Width);
                    break;
                }
            }

            // add the player to the Map's collection of Entities
            CurrentMap.Add(Player);
}

Run your project. You should have several M’s spawned all over the map in random places. Try bumping into them – you just step right through them. Let’s add a bump attack next.

That’s it for our Map and Entity modifications.

Alright, we’re into the final stretch. Let’s modify our Actor class to create the ubiquitous bump attack. Modify the MoveBy method like this:

        // Moves the Actor BY positionChange tiles in any X/Y direction
        // returns true if actor was able to move, false if failed to move
        public bool MoveBy(Point positionChange)
        {
            // Check the current map if we can move to this new position
            if (GameLoop.World.CurrentMap.IsTileWalkable(Position + positionChange))
            {
                // if there's a monster here,
                // do a bump attack
                Monster monster = GameLoop.World.CurrentMap.GetEntityAt<Monster>(Position + positionChange);
                if (monster != null)
                {
                    GameLoop.CommandManager.Attack(this, monster);
                    return true;
                }

                Position += positionChange;
                return true;
            }
            else
                return false;
        }

This isn’t the prettiest logic, but it gets the job done. We’re using our GetEntityAt method, of pok√©mon-type Monster at the Actor’s intended position. If there is a Monster there, tell the CommandManager to execute a battle round between¬†this¬†actor (the player!) and the monster we found.

You’ll need to modify your World class to include the new CreateMonsters method we created earlier:

    // Creates a new game world and stores it in
    // publicly accessible
    public World()
    {
        // Build a map
        CreateMap();

        // create an instance of player
        CreatePlayer();

        // spawn a bunch of monsters
        CreateMonsters();
    }

Give it a run. You can walk around, attack monsters, and watch them asplode. Try modifying the Player’s Attack and AttackChance attributes to see how they change combat outcomes. If your Player is constantly missing, it’s likely that your Player class is missing values for the Attack and AttackChance fields:

        public Player(Color foreground, Color background) : base(foreground, background, '@')
        {
            Attack = 10;
            AttackChance = 40;
            Defense = 5;
            DefenseChance = 20;
            Name = "Milhouse";
        }

Our monsters don’t move, and they don’t even drop their treasure on the ground. That’s for a future tutorial.

Phew.

Download the final source code for this tutorial here.

Published on March 9, 2019

Tutorial Part 3: Movement and Keyboard Input (SadConsole v8)

In this tutorial, we’ll look at how we can move our simple Player character around a single non-scrolling screen using SadConsole’s keyboard handling methods.

Let’s do some housekeeping first. I’m getting tired of staring at “program-example.cs”. Let’s rename the file and the class to something a little more meaningful. Rename the file to GameLoop.cs.

Namespaces

Now let’s rename the namespace and the class. At the top of the GameLoop.cs file, look for:

namespace MyProject

You’ve probably already noticed the many references to SadConsole.Game or SadConsole.Settings or SadConsole.Global. All of those use dot notation to tell the compiler we’re looking for a specific class of objects within a “namespace”. A namespace is a tree diagram of all the different kinds of classes and objects covered under one umbrella term. This tree usually has many different branches within it – for instance, the SadConsole.Entities namespace has all kinds of classes and objects and properties related to Entities. The SadConsole.Console namespace covers anything to do with Consoles. The SadConsole.Game namespace has methods and classes related to the main game loop. Hopefully this makes sense.

So let’s rename our game’s namespace. MyProject isn’t a good description of all the types of classes and methods we plan to build. Rename it to: namespace RogueTutorial. (Or, rename it to whatever you’d like!)

Classes

Now for renaming the class. If you remember from our last tutorial, a class describes an object in a general manner… Cat is the class, but MyCat is one of its instances.

The class we’ve been working, so far, handles the main game loop. Sure there is a bit of game logic in there that creates a player character, but for the most part its job has been to load the game, run it, and handle cleanup when the game closes.

class Program

Rename this to: class GameLoop

There’s another reason we’ve named it GameLoop: that’s because we renamed the file GameLoop.cs earlier. For your sanity and the sanity of others on your project, it’s very important to give your filenames the same name as the classes within them. No, the compiler won’t complain if your class has a different name than its filename… but 100 hours from now when you’re trying to find one file in a folder of hundreds, you’ll thank me!

Movement

Let’s make our little @ character walk around the console at our bidding. Remember the Keyboard handling code we wrote in the Update method in the first tutorial? We’re going to add some more keyboard handling code in the same place, this time to use the arrow keys. Below the first if(…) statement in the Update method, add the following code:

// Keyboard movement for Player character: Up arrow
if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Up))
{
     player.Position += new Point(0, -1);
}

Notice that we’re doing a couple of different things here:

1. We’re checking the state of the Keyboard – and we’re specifically interested in whether the Up key has been pressed. IsKeyPressed is different from IsKeyReleased. IsKeyPressed only checks if the key is currently pressed down. IsKeyReleased checks if the key was pressed, then released. IsKeyPressed lets us hold down the arrow key and motor our little @ sign around quickly.

2. We’re updating the player’s Position by adding a new Point to it. The += is the addition-assigment operator. It first adds two things together, then makes the first variable equal to that final sum. We could have wrote the same operation as:

player.Position = player.Position + new Point(0, -1);

The += operator is one of the tools in your toolbox that will delay your inevitable early onset carpal tunnel syndrome.

Back to the Position code: the Point(0, -1) we’re adding tells the compiler that the player’s X position should stay the same (by adding zero) and the player’s Y position should decrease by one. Why decrease? Because making the player @ symbol move upwards is a negative Y value in computer coordinate space. The top-left corner of the console is defined as 0,0 and the bottom-left corner of the console is width, height.

Run the program. You should be able to hold down the Up key and watch the little @ zoom upwards. You probably noticed that’s the only direction @ can walk though ūüôā Let’s address that with some more keyboard handling code:

// Keyboard movement for Player character: Down arrow
// Increment player's Y coordinate by 1
if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Down))
{
     player.Position += new Point(0, 1);
}

// Keyboard movement for Player character: Left arrow
// Decrement player's X coordinate by 1
if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Left))
{
     player.Position += new Point(-1, 0);
}

// Keyboard movement for Player character: Right arrow
// Increment player's X coordinate by 1
if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Right))
{
     player.Position += new Point(1, 0);
}

So now you’ve got an @ character that can zoom around the screen! Congrats ūüôā

Download the final version of GameLoop.cs for this tutorial here.

In our next tutorial, we’ll work on treating the console like a moving camera using ViewPorts.

Published on February 28, 2019

Tutorial Part 2: Player Creation and Display (SadConsole v8)

Creating a Player Entity

Let’s get a player on the screen. This means introducing a new SadConsole class called Entities. Entities are objects that have an X and Y position on a console, a name, and – this is for much later – an animation. An Entity can be something as simple as a single dot on the screen, or a complex animated creature. Let’s try the single dot first.

In this tutorial we’ll create an Entity (our Player’s character), and display it on the screen.

Start by declaring a new Entity belonging to your Program class. Variable declarations belong between the Class definition and class methods.

class Program
    {
        public const int Width = 80;
        public const int Height = 25;
        private static SadConsole.Entities.Entity player;

This declaration says to the compiler: ‘Make some space for a new variable called player whose type is Entity. We don’t know what exact data is going to go inside of it, but we know the type of data that will.’

The “private” declares this as a private variable, meaning that only methods in the Program class can see it. No other class is allowed to peek into Program’s private data. We’ll talk about public variables later. The “static” keyword tells the compiler that we plan on making a single copy of player, not a hundred instances of it. We’ll talk about instancing later. For now, just accept that it needs to be a static variable.

Now let’s create a new method that creates or “instantiates” the player variable by filling it with some basic data that describes its position and name.

Create a new method below the Init() method that looks like this:

        // Create a player using SadConsole's Entity class
        private static void CreatePlayer()
        {
            player = new SadConsole.Entities.Entity(1, 1);
            player.Animation.CurrentFrame[0].Glyph = '@';
        }

A private method is one that only this class (Program) has access to. No other class is able to call CreatePlayer() except Program. Void is the “return type” of this class. A return is a final value that a method spits out when it finishes running. A method that returns a void basically says to the compiler: I don’t care about what the final output of this method is, I just want the method to run and do its thing.

And again, this is a static method because there can only be one instance of it. There’s a Highlander joke there somewhere. We’ll talk about that much later in the tutorial.

Instances and Classes

The meat of this method is to “instantiate” player… in other words, turning an empty variable into an “instance”. In Object-Oriented Programming, our goal is to create a general framework of something, and then create specific instances of it. So if your goal is to create a cat, you’ll write a class called Cat that has a few general properties: a colour, a first and last name, four legs, a head, a meow, whiskers, a tail…. That framework or class only tells us what Cats generally are like. It’s up to us to create a specific instance of a cat:

MyCat : Cat

MyCat is an instance of Cat. There are a kazillion instances of Cat all with different colours and features, but only one general class of Cat.

That’s what we’re doing here with an Entity. We’ve got a general framework of Entity that we’re populating with some specific data… and in the process we’re making a new Entity called player.

player = new SadConsole.Entities.Entity(1, 1);

The “new” keyword tells the compiler that we want to create a new instance of an existing class called Entity. The (1,1) are parameters passed to the Entity class’s constructor.. these tell the constructor to create an Entity of a certain kind. In this case, 1 and 1 refer to the Width and Height of the Entity… meaning that it will be 1 character wide and 1 character tall. The Entity constructor can accept many kinds of parameters, like Animation data, font type, and so on. For now, let’s stick with a Player character that’s sized 1×1.

Entity Properties

player.Animation.CurrentFrame[0].Glyph = '@';

The above code updates a single animation frame of the player Entity. Remember how I said all Entities can have Animations earlier? The animation’s “Glyph” property tells SadConsole what ASCII character or graphics tile we should display onscreen for the Entity. Let’s stick with ASCII characters to make our lives a little easier for now, but rest assured – it’s just as easy to use graphics tiles to represent each Entity on the screen. The CurrentFrame[0] property just tells SadConsole that we’re trying to modify the 0th or “first” frame of the animation. Remember: computers start counting at zero, not at one! ūüôā

Now that we’ve created the method to create the player, we have to actually call the method from somewhere in our code. A method alone doesn’t do anything unless it has been called by another method. So let’s insert some code at the end of the Init() method:

        private static void Init()
        {
            // Any custom loading and prep. We will use a sample console for now

            Console startingConsole = new Console(Width, Height);
            //startingConsole.FillWithRandomGarbage();
            startingConsole.Fill(new Rectangle(3, 3, 27, 5), null, Color.Black, 0, SpriteEffects.None);
            startingConsole.Print(6, 5, "Hello from SadConsole", ColorAnsi.CyanBright);

            // Set our new console as the thing to render and process
            SadConsole.Global.CurrentScreen = startingConsole;
            
            //create an instance of the player
            CreatePlayer();

            // add the player Entity to our only console
            // so it will display on screen
            startingConsole.Children.Add(player);
        }

While you’re there, why not comment out FillWithRandomGarbage()? Now that we’re getting down to business, we don’t need our console covered with garbage anymore ūüôā

CreatePlayer() runs the method that we wrote earlier.

Console.Children.Add

startingConsole.Children.Add() is one of the most important methods in SadConsole. Think of a Console as a blank canvas that can have many things stuck on to it. To stick something on to a console – and have the ability to pull it off the console later, you need to Add it as a Child of the console. That’s so the console can keep track of what has been added or removed. Every console can act like a parent to other entities, and even other consoles! Think of your e-mail client. It’s a window, and it has other windows nested inside of it like individual e-mails and the composer. That’s how consoles work too.

So if you forget to add an Entity to your console, SadConsole won’t draw it! Whenever you run into a situation where you’re seeing a black screen and expected to see graphics on it – it’s likely because somewhere along the way you forgot to add a graphics element to a parent Console. This probably seems confusing for now, but Parent-Child relationships between consoles, other consoles, and entities, will become an extremely useful concept later in this tutorial when we start building our own graphical user interfaces.

So go ahead and run your project. You should have a little white @ symbol doing a whole lotta nothin in the top left corner of the window.

Entity Customization

Now let’s customize our little player character for fun by adding some code to CreatePlayer():

        // Create a player using SadConsole's Entity class
        private static void CreatePlayer()
        {
            player = new SadConsole.Entities.Entity(1, 1);
            player.Animation.CurrentFrame[0].Glyph = '@';
            player.Animation.CurrentFrame[0].Foreground = Color.HotPink;
            player.Position = new Point(20, 10);
        }

The .Foreground property allows you to change an ASCII Glyph’s colour on the fly. Color is a Microsoft XNA class that gives you access to a predefined table of colours, which I’ve found really useful. Try changing HotPink to any of the colours you see in the list.

the .Position property sets the X and Y coordinates of the Entity on the console. You’ll notice that we set both the X and Y coordinates at the same time using a new variable type called “Point”. A Point consists of two integer coordinates (e.g. if this were a piratey treasure map, a Point on it would be at: Latitude 60, Longitude 20). We use the “new” keyword to tell the compiler to create a new variable of type Point with 20 and 10 as its coordinates.

Download the final program.cs file for this tutorial here.

Run your program. You can move the player’s character anywhere you’d like on the screen by changing the values in the Point() statement. In our next tutorial, we’ll work on moving the character around using the keyboard in real-time instead.

Published on February 28, 2019

Tutorial Part 1: Preface and Hello World (SadConsole v8)

Preface

This series of tutorials is aimed at beginners with a little experience in coding – say, with scripting Game Maker Studio or Adventure Game Studio – but not deep experience with object-oriented programming concepts and C#. I modelled this tutorial on the fantastic Complete Roguelike Tutorial, using python+libtcod, which does a great job of explaining terminology. I try to explain relevant OOP concepts along the way, as well as keywords and concepts specific to .NET and C# programming.

If you’re a hardcore C++ coder looking to dive straight in to a SadConsole-based project, you’re probably better off just going to the SadConsole GitHub repo and get gitting.

Goals

Process

Most tutorials are written using an established codebase that has been thoroughly nitpicked before writing about it. I’m not doing that here, because (a) it prevents you and I from making mistakes that we can learn from, and (b) that’s not how projects are done in the real world. That means we’ll be refactoring and rewriting code as we go along, taking advantage of new concepts and fixing errors along the way. If you’re looking for perfect code, you’re looking in several wrong places.

I use Visual Studio Community 2017 for Mac. There are minor UI differences between the Mac and Windows version of VS, so if you’re running Windows you might notice a few menu/UI naming differences along the road. This is not intended to be a Visual Studio tutorial – so if you get stuck, you can do that research on your own. That being said, here are a few steps to get your started…

Installation

  1. Download and install MonoGame 3.7.1 for your particular operating system.
  2. Create a new Solution in Visual Studio 2017. Set the Solution type to be a MonoGame Cross Platform Desktop Project using OpenGL.
  3. You’ll need to change your project properties so that the .NET target framework is v4.6.1 or higher. I use 4.7.1 currently. The Target Framework option is buried in Project Options/Build/General.
  4. Using the NuGet package browser, search for SadConsole and add SadConsole. This is the engine we’ll be using.

Hello World

Now let’s get to work understanding the basics before we turn this Hello World application into a game.

Edit program.cs and replace its contents with the following code:

using System;
using SadConsole;
using Console = SadConsole.Console;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace MyProject
{
    class Program
    {

        public const int Width = 80;
        public const int Height = 25;

        static void Main(string[] args)
        {
            // Setup the engine and creat the main window.
            SadConsole.Game.Create(Width, Height);

            // Hook the start event so we can add consoles to the system.
            SadConsole.Game.OnInitialize = Init;

            // Hook the update event that happens each frame so we can trap keys and respond.
            SadConsole.Game.OnUpdate = Update;
                        
            // Start the game.
            SadConsole.Game.Instance.Run();

            //
            // Code here will not run until the game window closes.
            //
            
            SadConsole.Game.Instance.Dispose();
        }
        
        private static void Update(GameTime time)
        {
            // Called each logic update.
            // As an example, we'll use the F5 key to make the game full screen
            if (SadConsole.Global.KeyboardState.IsKeyReleased(Microsoft.Xna.Framework.Input.Keys.F5))
            {
                SadConsole.Settings.ToggleFullScreen();
            }
        }

        private static void Init()
        {
            // Any custom loading and prep. We will use a sample console for now

            Console startingConsole = new Console(Width, Height);
            startingConsole.FillWithRandomGarbage();
            startingConsole.Fill(new Rectangle(3, 3, 27, 5), null, Color.Black, 0, SpriteEffects.None);
            startingConsole.Print(6, 5, "Hello from SadConsole", ColorAnsi.CyanBright);

            // Set our new console as the thing to render and process
            SadConsole.Global.CurrentScreen = startingConsole;
        }
    }
}

Main is the first method that the game loads. This is where you’ll do some of your heavy duty stuff to set up SadConsole for making a game. This method first uses SadConsole.Game.Create to set a font, and set the width and height of the game. It then calls the Init() method to initialize the game, and prep it for putting something on the screen — we’ll get to that in a bit. Then the SadConsole.Game.OnUpdate property tells the library which method it should call every time an update event is triggered — we’ll deal with that later. Finally, it runs the game using SadConsole.Game.Instance.Run. When the window is closed, SadConsole.Game.Instance disposes of itself.

Run the program. Hit F5 and watch the game switch to fullscreen. Hit F5 again to exit fullscreen, and close the window.

Look up at the top of program-example.cs:

public const int Width = 80; 
public const int Height = 25;

Those two constants set the width and height of the console and program. The width and height are in characters, not in pixels. The font you use (e.g. IBM.Font or Cheepicus12.Font) determines how wide/tall your console will be. Some fonts are taller or fatter than others. I prefer to use an 8×8 px font to make things square and snug.

Now take a look at the Init() method. Notice that the first step is to create a new Console called startingConsole of a specific Width and Height. Consoles are foundational to SadConsole – think of them as giant canvases that you can paint stuff on. Consoles have a specified Width and Height, and there are a few different types of Consoles. But for now, we’re creating the simplest kind of Console.

private static void Init()
{
    // Any custom loading and prep. We will use a sample console for now

    Console startingConsole = new Console(Width, Height);
    startingConsole.FillWithRandomGarbage();
    startingConsole.Fill(new Rectangle(3, 3, 27, 5), null, Color.Black, 0, SpriteEffects.None);
    startingConsole.Print(6, 5, "Hello from SadConsole", ColorAnsi.CyanBright);

    // Set our new console as the thing to render and process
    SadConsole.Global.CurrentScreen = startingConsole;
}

Next, the program uses startingConsole (which you just created) and runs a method called FillWithRandomGarbage(). That’s a built-in method that all Consoles have, and it’s a useful way of testing whether your console is working and can display stuff. startingConsole then uses a Fill() method that creates a Rectangle that is positioned at X=3, Y=3, and has a width of 27 and height of 5. The foreground colour of the rectangle is set to Null, and the background colour is set to Black. The “glyph” or picture that is displayed is set to zero, meaning that this rectangle will be just an empty box with no characters or pictures in it. Finally, there are no SpriteEffects — a topic for much later.

Now we can write some text to the screen using the Console.Print() method. In this case, we’re printing “Hello from SadConsole” at console coordinates x=6 and y=5 (sixth column, fifth row) in a bright Cyan. Every Console has its own set of coordinates that are separate from the coordinates of other consoles.

And finally – this is a critical step – SadConsole sets the Global.CurrentScreen to startingConsole. Global.CurrentScreen keeps track of which console is currently on screen and is being processed. If you forget this step, you’ll end up with an empty black screen. Go ahead – try it. Comment out SadConsole.Global.CurrentScreen = startingConsole; and run the program. See? Uncomment that line, and let’s move on.

Take a look at Update(). This is another critical SadConsole method that you’ll get used to seeing and using everywhere:

private static void Update(GameTime time)
{
     // Called each logic update.

     // As an example, we'll use the F5 key to make the game full screen
     if (SadConsole.Global.KeyboardState.IsKeyReleased(Microsoft.Xna.Framework.Input.Keys.F5))
     {
          SadConsole.Settings.ToggleFullScreen();
     }
}

SadConsole checks an Update(GameTime time) method every time it runs through one cycle of game processing. This is a useful place to add special features like checking if the user has pressed a keyboard key or moved the mouse. It’s also a place where we can do things like update the player’s health, or process enemy attacks. In this case, we’re checking SadConsole.Global.KeyboardState.IsKeyReleased for the F5 key. If that has been released, toggle SadConsole’s FullScreen setting.

In the next tutorial we’ll put a character on the screen and have it move around.

Published on February 28, 2019

Tutorial Part 10: Command Manager (SadConsole v8)

It’s time to do some work on our basic tunnelling generation algorithm. It’s not much fun just running around the maze without things we can interact with. Let’s start by adding a basic command system manager (again – modelled on the RogueSharp tutorials and a Command programming pattern), so in our next tutorial we can integrate some lockable doors that the player can bump into for fun and profit.

A command system/manager allows us to do at least a couple of things:

Command Manager

Let’s build out a basic CommandManager. Create a new subfolder in your project called Commands and create a new CommandManager class within it:

using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.Commands
{
    // Contains all generic actions performed on entities and tiles
    // including combat, movement, and so on.
    public class CommandManager
    {
        public CommandManager()
        {

        }

        // Move the actor BY +/- X&Y coordinates
        // returns true if the move was successful
        // and false if unable to move there
        public bool MoveActorBy(Actor actor, Point position)
        {
            return actor.MoveBy(position);
        }
    }
}

Now we can decouple the keyboard keys from the player’s move method by using the CommandSystem’s more generic call. Modify the UIManager’s CheckKeyboard method:

            // Keyboard movement for Player character: Up arrow
            // Decrement player's Y coordinate by 1
            if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Up))
            {
                GameLoop.CommandManager.MoveActorBy(GameLoop.World.Player, new Point(0, -1));
                CenterOnActor(GameLoop.World.Player);
            }

            // Keyboard movement for Player character: Down arrow
            // Increment player's Y coordinate by 1
            if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Down))
            {
                GameLoop.CommandManager.MoveActorBy(GameLoop.World.Player, new Point(0, 1));
                CenterOnActor(GameLoop.World.Player);
            }

            // Keyboard movement for Player character: Left arrow
            // Decrement player's X coordinate by 1
            if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Left))
            {
                GameLoop.CommandManager.MoveActorBy(GameLoop.World.Player, new Point(-1, 0));
                CenterOnActor(GameLoop.World.Player);
            }

            // Keyboard movement for Player character: Right arrow
            // Increment player's X coordinate by 1
            if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.Right))
            {
                GameLoop.CommandManager.MoveActorBy(GameLoop.World.Player, new Point(1, 0));
                CenterOnActor(GameLoop.World.Player);
            }

Now we’re going to instance the CommandManager in the GameLoop:

// Managers
...
public static CommandManager CommandManager;
...

private static void Init()
{
...
            //Instantiate a new CommandManager
            CommandManager = new CommandManager();
...
}

Give your project a quick run. The player should move around just like before, but under the hood we have a much more generic – and flexible! – method for moving them. Now we can populate the Command System with more actions that allow for more diverse gameplay.

Undo and Redo

I like the idea of storing Undo and Redo information that allows a place to take back (or repeat) an action with a single keypress. Fortunately the Command Manager makes this fairly straightforward to do.

We’re going to store some action information in the Command Manager. It will keep track of what our last action was, so we can repeat it or undo it. Modify CommandManager:

    public class CommandManager
    {
        //stores the actor's last move action
        private Point _lastMoveActorPoint;
        private Actor _lastMoveActor;
...
}
        public bool MoveActorBy(Actor actor, Point position)
        {
            // store the actor's last move state
            _lastMoveActor = actor;
            _lastMoveActorPoint = position;

            return actor.MoveBy(position);
        }

Now let’s use that stored action information by creating Undo and Redo methods:

        // Redo last actor move
        public bool RedoMoveActorBy()
        {
// Make sure there is an actor available to redo first!
            if (_lastMoveActor != null)
            {
                return _lastMoveActor.MoveBy(_lastMoveActorPoint);
            }
            else
                return false;
        }

        // Undo last actor move
        // then clear the undo so it cannot be repeated
        public bool UndoMoveActorBy()
        {
            // Make sure there is an actor available to undo first!
            if (_lastMoveActor != null)
            {
                // reverse the directions of the last move
                _lastMoveActorPoint = new Point(-_lastMoveActorPoint.X, -_lastMoveActorPoint.Y);

                if (_lastMoveActor.MoveBy(_lastMoveActorPoint))
                {
                    _lastMoveActorPoint = new Point(0, 0);
                    return true;
                }
                else
                {
                    _lastMoveActorPoint = new Point(0, 0);
                    return false;
                }
            }
            return false;
        }

The RedoMoveActorBy method simply repeats the last actor’s move. It only checks if there is an actor stored to move to prevent the inevitable situation where there is (null) stored actor data.

The UndoMoveActorBy method checks for an available actor, then reverses the direction of the move using the – operator on the X and Y coordinates of the last move. The – operator should be read as -1 * _lastMoveActorPoint.X.

The if statement at the end of the method allows us to return the success (or failure) of the actor’s undo. This allows for the situation where the actor cannot return to their last position because a tile is no longer there, or creature has moved into that spot. We then clear the actor data from the undo storage, preventing the player from repeating the same undo multiple times.

Now implement these as keyboard commands in the UIManager:

       private void CheckKeyboard()
       {
       ...
            // Undo last command: Z
            if (SadConsole.Global.KeyboardState.IsKeyReleased(Microsoft.Xna.Framework.Input.Keys.Z))
            {
                GameLoop.CommandManager.UndoMoveActorBy();
                CenterOnActor(GameLoop.World.Player);
            }

            // Repeat last command: X
            if (SadConsole.Global.KeyboardState.IsKeyPressed(Microsoft.Xna.Framework.Input.Keys.X))
            {
                GameLoop.CommandManager.RedoMoveActorBy();
                CenterOnActor(GameLoop.World.Player);
            }
       ...
       }

Okay I should have done this ages ago. I’m incredibly sick of my @ player spawning smack dab in the middle of a dungeon wall. (If you’ve already fixed this problem, kudos to you – you’re now promoted to the rank of honorary tutorial writer. I cede my throne to you, your majesty.) Modify the World class’ CreatePlayer method:

        // Create a player using the Player class
        // and set its starting position
        private void CreatePlayer()
        {
            Player = new Player(Color.Yellow, Color.Transparent);

            // Place the player on the first non-movement-blocking tile on the map
            for (int i = 0; i < CurrentMap.Tiles.Length; i++)
            {
                if (!CurrentMap.Tiles[i].IsBlockingMove)
                {
                    // Set the player's position to the index of the current map position
                    Player.Position = SadConsole.Helpers.GetPointFromIndex(i, CurrentMap.Width);
                }
            }
            // Add the ViewPort sync component to the player entity
            Player.Components.Add(new EntityViewSyncComponent());
        }

And finally a quick edit to the UIManager’s Init method to refocus the camera:

            // Start the game with the camera focused on the player
            CenterOnActor(GameLoop.World.Player);

Done and done. Let’s put this tutorial in the can and get thinking on doors, locks and keys for the next one.

Download the final source for tutorial 10 here.

Published on February 27, 2019

Tutorial Part 9: Message Log (SadConsole v8)

Let’s make some more use out of our UI system by creating a traditional RL Message Log window that will keep track of our player’s actions. We’ll make use of SadConsole’s interface enhancements like scrolling and windowing along the way.

If you’re starting to feel anal about the structure of your project, now is a good time to re-organize your classes a bit. I’ve created a new folder called UI which represents the SadConsoleRLTutorial.UI namespace, and it will house the UIManager and all of its subsidiary windows.

If you’re grouping things my way, that means modifying the UIManager class definition to suit the new namespace it lives in:

namespace SadConsoleRLTutorial.UI

Afterwards, modify the GameLoop class namespace definitions so it knows the namespace that the UIManager is now located:

using SadConsoleRLTutorial.UI;

MessageLogWindow Class

Create a new class called MessageLogWindow in the UI folder. If you’re using Visual Studio, it will auto-populate the namespace in the class definition. Now let’s build out the class:

using System.Collections.Generic;
using SadConsole;
using System;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial.UI
{
    //A scrollable window that displays messages
    //using a FIFO (first-in-first-out) Queue data structure
    public class MessageLogWindow : Window
    {
        //max number of lines to store in message log
        private static readonly int _maxLines = 100;

        // a Queue works using a FIFO structure, where the first line added
        // is the first line removed when we exceed the max number of lines
        private readonly Queue<string> _lines;

        // the messageConsole displays the active messages
        private SadConsole.ScrollingConsole _messageConsole;

        // Create a new window with the title centered
        // the window is draggable by default
        public MessageLogWindow(int width, int height, string title) : base(width, height)
        {
            // Ensure that the window background is the correct colour
            Theme.WindowTheme.FillStyle.Background = DefaultBackground;
            _lines = new Queue<string>();
            CanDrag = true;
            Title = title.Align(HorizontalAlignment.Center, Width);

            // add the message console, reposition, and add it to the window
            _messageConsole = new SadConsole.ScrollingConsole(width - 1, height - 1);
            _messageConsole.Position = new Point(1, 1);
            Children.Add(_messageConsole);
        }
}
}

First, we’re defining MessageLogWindow as a type of SadConsole.Window. This allows it to automagically inherit wonderful properties like .CanDrag and .Title, as well as take care of the underlying UI processing.

The Console _messageConsole will display our actual message data. The window itself acts more like a container. Why are we creating this extra layer of confusion by nesting a console inside of the window? That will become apparent later, when we want to add a scrollbar!

The Theme.FillStyle.Background property allows us to change the background colour of the window. You’ll probably find that the default blue background SadConsole uses is a little harsh on the eyeballs, so you can change it to any colour using a (Microsoft.Xna.Framework) Color. For now, it is set to the default background colour.

Then we’re creating a Queue, which is a wonderful C# data structure that maintains a collection of objects in a “first-in, first-out” (FIFO) relationship. FIFO allows the MessageLog queue to destroy the earliest messages when its size exceeds _maxLines.

The last few steps instantiate the _messageConsole and reposition it on the screen. Note that we’re making the console smaller than the window so it will not overlap with the window borders.

And now let’s implement a new (public) Add method that allows us to add messages to the queue:

        //add a line to the queue of messages
        public void Add(string message)
        {
            _lines.Enqueue(message);
            // when exceeding the max number of lines remove the oldest one
            if (_lines.Count > _maxLines)
            {
                _lines.Dequeue();
            }
// Move the cursor to the last line and print the message.
_messageConsole.Cursor.Position = new Point(1, _lines.Count);
            _messageConsole.Cursor.Print(message + "\n");
        }

Cursor.Position and Cursor.Print are specific to Console classes. The Cursor acts like a traditional command line interface cursor, which can be positioned anywhere in the console via its Position property. The Cursor.Print method translates raw string data into readable lines, split line-by-line to prevent words from breaking apart at the ends of lines. It also respects the “\n\r” escape sequences which allow for new lines and carriage returns. In this case, we’re appending a new line after each message is printed.

It’s time to test out our basic MessageWindow. Add a MessageLog to the UIManager’s class definition:

public MessageLogWindow MessageLog;

Modify the UIManager’s Init method with a few lines of test code. Add these lines to the end of the method:

            MessageLog = new MessageLogWindow(GameLoop.GameWidth / 2, GameLoop.GameHeight / 2, "Message Log");
            Children.Add(MessageLog);
            MessageLog.Show();
            MessageLog.Position = new Point(0, GameLoop.GameHeight / 2);

            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 1224");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 12543");
            MessageLog.Add("Testing 123");
            MessageLog.Add("Testing 1253");
            MessageLog.Add("Testing 1212");
            MessageLog.Add("Testing 1");
            MessageLog.Add("Testing");
            MessageLog.Add("Testing 122");
            MessageLog.Add("Testing 51");
            MessageLog.Add("Testing");
            MessageLog.Add("Testing 162");
            MessageLog.Add("Testing 16");
            MessageLog.Add("Testing Last");

Now you have a working MessageLog. It would be a lot more useful though, if you could scroll back through its buffer to read older messages. Let’s do that now.

Adding a Scrollbar to MessageLogWindow

For a scrolling console, the idea is to expand _messageConsole’s height property to _maxLines, and then using its ViewPort to selectively view portions of it. That’s how we’ll see the scrollback buffer.

The overall architecture of the Window will become like this, where each of these bullet points is a ‘has a’ relationship:

So: one console for message data, one scrollbar for moving it. First modify MessageLogWindow’s class definition by adding a few lines to it:

        //scrollbar for message console
        private SadConsole.Controls.ScrollBar _messageScrollBar;

        //Track the current position of the scrollbar
        private int _scrollBarCurrentPosition;

        // account for the thickness of the window border to prevent UI element spillover
        private int _windowBorderThickness = 2;

Now modify the MessageLogWindow’s constructor and replace _messageConsole’s instantiation code:

            // add the message console, reposition, enable the viewport, and add it to the window
            _messageConsole = new SadConsole.ScrollingConsole(width - _windowBorderThickness, _maxLines);
            _messageConsole.Position = new Point(1, 1);
            _messageConsole.ViewPort = new Rectangle(0, 0, width - 1, height - _windowBorderThickness);

            // create a scrollbar and attach it to an event handler, then add it to the Window
            _messageScrollBar = new SadConsole.Controls.ScrollBar(SadConsole.Orientation.Vertical, height - _windowBorderThickness);
            _messageScrollBar.Position = new Point(_messageConsole.Width + 1, _messageConsole.Position.X);
            _messageScrollBar.IsEnabled = false;
            _messageScrollBar.ValueChanged += MessageScrollBar_ValueChanged;
            Add(_messageScrollBar);

            // enable mouse input
            UseMouse = true;

// Add the child consoles to the window
Children.Add(_messageConsole);

Let’s walk through the code dump. We’re resizing the _messageConsole to make room for the scrollbar. Subtracting the _windowBorderThickness from the width makes sure that the _messageConsole is contained within the Window. That’s something you’ll see throughout these projects.

Then we’re creating the _messageScrollBar. SadConsole has a handy Orientation enum that lets us position the scrollbar on the window, which we pass as the first parameter.

We reposition _messageScrollBar to the right side of the window, letting it overlap the window edge. You can skooch it inside of the window, but you risk letting it overlap with the _messageConsole. If that happens, the scrollbar won’t accept mouse control (because the _messageConsole will steal the mouse from it).

Until now, we haven’t had to deal with Events. An Event allows a class to notify an object (within the class or in another class) that something interesting has happened. In this case, we’re interested in the ValueChanged EventHandler built into the ScrollBar. The += operator is used to subscribe to events. The right side of the operator describes the method – in this case MessageScrolBar_ValueChanged – that will be called when the event is triggered.

Afterwards, we Add the _messageScrollBar to the Window. Note: we use Add, not Children.Add! 

We set UseMouse to true, to tell the Window to begin processing Mouse events. That is a critical step, because without it, it won’t trigger any mouse events. And finally, we add _messageConsole as a child of the Window, otherwise it won’t display.

Now let’s add some code to process the scrollbar behaviour when it’s triggered. Create a new method called MessageScrollBar_ValueChanged:

        // Controls the position of the messagelog viewport
        // based on the scrollbar position using an event handler
        void MessageScrollBar_ValueChanged(object sender, EventArgs e)
        {
            _messageConsole.ViewPort = new Rectangle(0, _messageScrollBar.Value + _windowBorderThickness, _messageConsole.Width, _messageConsole.ViewPort.Height );
        }

En anglais: every time the scrollbar’s value changes, we update the viewport position.

Now for the biggest code hunk – we’re going to override the Update method so we can do some math on the scrollbar:

        // Custom Update method which allows for a vertical scrollbar
        public override void Update(TimeSpan time)
        {
            base.Update(time);

            // Ensure that the scrollbar tracks the current position of the _messageConsole.
            if (_messageConsole.TimesShiftedUp != 0 | _messageConsole.Cursor.Position.Y >= _messageConsole.ViewPort.Height + _scrollBarCurrentPosition)
            {
                //enable the scrollbar once the messagelog has filled up with enough text to warrant scrolling
                _messageScrollBar.IsEnabled = true;

                // Make sure we've never scrolled the entire size of the buffer
                if (_scrollBarCurrentPosition < _messageConsole.Height - _messageConsole.ViewPort.Height)
                    // Record how much we've scrolled to enable how far back the bar can see
                    _scrollBarCurrentPosition += _messageConsole.TimesShiftedUp != 0 ? _messageConsole.TimesShiftedUp : 1;

// Determines the scrollbar's max vertical position
// Thanks @Kaev for simplifying this math!
                _messageScrollBar.Maximum = _scrollBarCurrentPosition - _windowBorderThickness;

                // This will follow the cursor since we move the render area in the event.
                _messageScrollBar.Value = _scrollBarCurrentPosition;

                // Reset the shift amount.
                _messageConsole.TimesShiftedUp = 0;
            }
        }

Lots of stuff going on in this hunk. We perform a base.Update first, to ensure SadConsole updates this window. The first if statement determines whether the console has filled with enough data to warrant scrolling. Afterwards, we do some math to determine the proper position of the scrollbar in relation to the _messageConsole. The TimesShiftedUp is a built-in SadConsole.Console field.

And that’s it. Run the project, and you should have a scrollable window with a bunch of test data in it. We’ll put this window to good use in the future when we track player behaviour.

Download the final version of the source code for tutorial #9 here.

Published on February 27, 2019