ansi|ware

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

Tutorial Part 8: User Interface Manager (SadConsole v8)

The process we’ve been following so far has been to first create a sketch of the intended behaviour in the GameLoop, which allows us to test it out. After the behaviour matches its intended purpose, we implement the sketch in a new class. In the last tutorial we started seeing more codebloat (and a bug!) in our GameLoop as we began to add UI features like map scrolling.

As we’ve done before, let’s create a new class that represents these features: the UIManager. This is going to be a large class by the time we’re done with it, but let’s imagine a few goals for the class:

We’re only going to implement a couple of those features today, by letting our UIManager take advantage of SadConsole’s ContainerConsole class. Why are we using ContainerConsole?

SadConsole’s ContainerConsole class doesn’t do much. It is a parent that exists only to process the activities of its child consoles. It does not even need a width or a height specified in the constructor.

UIManager

As usual, we start by laying out a new skeleton class for UIManager:

using Microsoft.Xna.Framework;
using SadConsole;
namespace SadConsoleRLTutorial
{
    // Creates/Holds/Destroys all consoles used in the game
    // and makes consoles easily addressable from a central place.
    public class UIManager : ContainerConsole
    {
        public ScrollingConsole MapConsole;

        public UIManager()
        {
            // must be set to true
            // or will not call each child's Draw method
            IsVisible = true;
IsFocused = true;

            // The UIManager becomes the only
            // screen that SadConsole processes
            Parent = SadConsole.Global.CurrentScreen;
        }
    }
}

In the above code we are storing a single console that will contain a map. MapConsole is public because the plan is to push data into it from another class that we’ll create later. The comment about IsVisible and IsFocused is important. IsVisible tells SadConsole to process the ScreenObject’s Draw method. IsFocused tells SadConsole to pay attention to any keyboard or mouse input.

Setting UIManager as the child of SadConsole.Global.CurrentScreen is required. This tells SadConsole that UIManager should be actively processed and displayed. Without this, you’ll just get a black screen.

Let’s add a new method to UIManager:

        // Creates all child consoles to be managed
        public void CreateConsoles()
        {
            MapConsole = new SadConsole.ScrollingConsole(GameLoop.World.CurrentMap.Width, GameLoop.World.CurrentMap.Height, Global.FontDefault, new Rectangle(0, 0, GameLoop.GameWidth, GameLoop.GameHeight), GameLoop.World.CurrentMap.Tiles);
        }

Recognize this code? It is cut/pasted/modified from GameLoop. We’re creating our MapConsole here instead of in the GameLoop. Instead of using Width and Height properties, we’re now addressing them as GameLoop.GameHeight and GameLoop.GameWidth because we’re working outside of the GameLoop class, peeking into its public properties.

Notice that the very last parameter was changed from GameMap.Tiles to GameLoop.World.CurrentMap.Tiles. With this, we’re anticipating a new class called World that has the current Map stored in it. That means we’ll be building a new class to store our world data soon

While we’re still in the UIManager, let’s move some more code over from GameLoop and modify it to suit the new circumstances:

        // centers the viewport camera on an Actor
        public void CenterOnActor(Actor actor)
        {
            MapConsole.CenterViewPortOnPoint(actor.Position);
        }

        public override void Update(TimeSpan timeElapsed)
        {
            CheckKeyboard();
            base.Update(timeElapsed);
        }

        // Scans the SadConsole's Global KeyboardState and triggers behaviour
        // based on the button pressed.
        private void CheckKeyboard()
        {
            // 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();
            }

            // 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.World.Player.MoveBy(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.World.Player.MoveBy(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.World.Player.MoveBy(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.World.Player.MoveBy(new Point(1, 0));
                CenterOnActor(GameLoop.World.Player);
            }
        }

Notice that I’ve removed the “static” keyword from these method definitions. As promised, we are slowly reducing the number of static methods as we create new homes for them in their own classes. 

We’ve also moved the Player object out of the GameLoop, into our World class. World will be where we store the player data – so any calls involving the player will require us to address it via GameLoop.World.Player.

Finally, take a look at the Update(TimeSpan time) method. This is a critical SadConsole-specific method that we are taking advantage of. The override keyword tells the compiler that we are extending the original method by adding some of our own code to it. In this particular case, the UIManager inherits methods from ConsoleContainer, which in turn inherits methods – eventually – from SadConsole.ScreenObject. This is where the original virtual Update(TimeSpan time) method lives.

Update is triggered before every single game frame update. Update is where you do all of your game logic processing before the Draw event is triggered. In this case, we’re using it to do two things:

  1. Checking the Keyboard for input.
  2. Calling the base method’s Update instructions afterwards.

The base.Update() call is critical, because without it our ConsoleContainer won’t update any of its children. A good rule of thumb for SadConsole is this: if your class inherits from SadConsole.Console or ScreenObject and you override an Update or Draw method, you must call base.Update or base.Draw at the end of your overriding method. Without it, SadConsole won’t know to update or draw its children and you’ll be wondering why your screen is blank and you’re living in a van down by the river.

World Class

World, in this context, takes on a wider meaning of the entire game state. So we won’t only be storing the map data – but also monster, NPC and player data too. We can also trigger map, monster and player generation from here too. If you remember the bad old FreeBSD UNIX days, I think of the World class as the place where we get to run make buildworld and sit back and drink some tea.

Per the usual way of doing things, we’ll begin with a skeleton class and move some code from the GameLoop into it.

using System;
using SadConsole.Components;
using Microsoft.Xna.Framework;

namespace SadConsoleRLTutorial
{
    // All game state data is stored in World
    // also creates and processes generators
    // for map creation
    public class World
    {
        // map creation and storage data
        private int _mapWidth = 100;
        private int _mapHeight = 100;
        private TileBase[] _mapTiles;
        private int _maxRooms = 100;
        private int _minRoomSize = 4;
        private int _maxRoomSize = 15;
        public Map CurrentMap { get; set; }

        // player data
        public Player Player { get; set; }

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

            // create an instance of player
            CreatePlayer();
        }

        // Create a new map using the Map class
        // and a map generator. Uses several 
        // parameters to determine geometry
        private void CreateMap()
        {
            _mapTiles = new TileBase[_mapWidth * _mapHeight];
            CurrentMap = new Map(_mapWidth, _mapHeight);
            MapGenerator mapGen = new MapGenerator();
            CurrentMap = mapGen.GenerateMap(_mapWidth, _mapHeight, _maxRooms, _minRoomSize, _maxRoomSize);
        }

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

            // Add the ViewPort sync Component to the player
            Player.Components.Add(new EntityViewSyncComponent());
        }
    }
}

Here is the completed class after a Cut-and-Paste from the GameLoop. The above class stores room generation geometry, the currently running Map, and the Player.

CreateMap consists of the code that was previously in the GameLoop’s Init method. CreatePlayer is a direct cut-and-paste, with some changes to the variable names. Again, these are no longer marked as static methods because we’ll be creating an actual World object/instance that they can be called from.

GameLoop Cleanup

class GameLoop
    {

        public const int GameWidth = 80;
        public const int GameHeight = 25;

        // Managers
        public static UIManager UIManager;
        public static World World;

        static void Main(string[] args)
        {
            // Setup the engine and create the main window.
            SadConsole.Game.Create(GameWidth, GameHeight);

            // 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)
        {

        }

        private static void Init()
        {
            //Instantiate the UIManager
            UIManager = new UIManager();

            // Build the world!
            World = new World();

            // Now let the UIManager create its consoles
            // so they can use the World data
            UIManager.CreateConsoles();
        }
    }

Notice how clean our GameLoop has become, now that we’ve moved almost all of the game state and game logic out of it, into custom classes?

Things worth nothing in the above code: we create a static UIManager and World. They each are created in the Init method, and then the UIManager is told to create its consoles as a final step. The order of these instructions is very important, because the UIManager must be created before the World can be created. Why? UIManager.CreateConsoles must be run as a last step, because it requires World to have a generated CurrentMap ready-at-hand.

Actor Edit

A quick change in the Actor class is now necessary, to reflect that the World class now holds the map data:

        // 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))
            {
                Position += positionChange;
                return true;
            }
            else
                return false;
        }

To be honest, I’m not 100% comfortable with this degree of fragility in our game. When a single instruction is put in the wrong order, the compiler will find null data or a missing object and asplode. There are better ways of engineering game initialization, but for now we have a working machine. All that refactoring now allows us to do something cool with it!

Draggable Windows

It’s about time we added some kind of windowing UI to this project, so the map only takes up a portion of the screen.

Add these definitions to the UIManager:

using SadConsole.Controls;
...
public Window MapWindow;

Add a new method called CreateMapWindow. Its purpose is to act as a window container for the MapConsole. Think about the possession hierarchy we’ll have now: UIManager (contains a) MapWindow (which contains a) MapConsole.

        // Creates a window that encloses a map console
        // of a specified height and width
        // and displays a centered window title
        // make sure it is added as a child of the UIManager
        // so it is updated and drawn
        public void CreateMapWindow(int width, int height, string title)
        {
            MapWindow = new Window(width, height);
            MapWindow.CanDrag = true;

            //make console short enough to show the window title
            //and borders, and position it away from borders
            int mapConsoleWidth = width - 2;
            int mapConsoleHeight = height - 2;

            // Resize the Map Console's ViewPort to fit inside of the window's borders snugly
            MapConsole.ViewPort = new Rectangle(0, 0, mapConsoleWidth, mapConsoleHeight);

            //reposition the MapConsole so it doesnt overlap with the left/top window edges
            MapConsole.Position = new Point(1, 1);

            //close window button
            Button closeButton = new Button(3, 1);
            closeButton.Position = new Point(0, 0);
            closeButton.Text = "[X]";

            //Add the close button to the Window's list of UI elements
            MapWindow.Add(closeButton);

            // Centre the title text at the top of the window
            MapWindow.Title = title.Align(HorizontalAlignment.Center, mapConsoleWidth);

            //add the map viewer to the window
            MapWindow.Children.Add(MapConsole);

            // The MapWindow becomes a child console of the UIManager
            Children.Add(MapWindow);

// Add the player to the MapConsole's render list
MapConsole.Children.Add(GameLoop.World.Player);

// Without this, the window will never be visible on screen
            MapWindow.Show();
        }

In the above code, we are setting the new window’s CanDrag property to true. This allows us to click’n’drag the window around by its titlebar. SadConsole does all of that work for us, thankfully!

We are resizing the MapConsole’s ViewPort to suit the size of the Window. If you fail to resize the ViewPort, you’re going to see the whole map spill over the boundaries of the window. (Go ahead, try it!) We then reposition the MapConsole inside of the window, to prevent the console from bleeding over the top-left edges of the window.

Adding UI elements like buttons is very straightforward. They require a width and height, and need to be positioned inside of the window’s coordinates. Setting a button’s text is done via the Button.Text property. Don’t forget to add the UI element to the Window’s list of children! This is done via the Window.Add method. Without this, your button (or label, or slider) will never appear.

Finally, we add MapConsole as a child of the MapWindow; the MapWindow is added as a child of the UIManager. And ta-da… to get the window to show up on the screen, we call MapWindow.Show!

A couple more minor edits before we can test out our windowing system. Let’s add an Init method to UIManager, which will eventually contain all of our console and window initialization:

        // Initializes all windows and consoles
        public void Init()
        {
            CreateConsoles();
            CreateMapWindow(GameLoop.GameWidth / 2, GameLoop.GameHeight / 2, "Game Map");
        }

And then we’ll call Init from the GameLoop. In GameLoop.Init, delete the UIManager.CreateConsoles(); line and replace it with:

UIManager.Init();

Run your project. You should have a draggable window containing your game map. When you walk your character around, it will stay in the right position relative to the map, even if you move the map window around.

Notice something awesome? Our ghosting @ bug mysteriously disappeared! All of that refactoring ensured that the UIManager and its children are in the right SadConsole Draw and Update processing order. Only you can prevent forest fires by keeping game logic and graphics out of the GameLoop!

SadConsole has some incredibly powerful UI creation abilities, and we’ll explore those in future tutorials.

Download the final source code for this tutorial here.

Published on February 27, 2019