Custom font and screen container

Font setup

For this project I will be using the “LCD_Tileset” font. I have made the background transparent, you can get a copy from the image underneath:

Transparent background version

You can create a “Fonts” folder in your project and you can store the “LCD_Tileset.png” font file in that folder.
Additionally we also need the font metadata in that folder.

{
    "$type": "SadConsole.SadFont, SadConsole",
    "Name": "LCD_Tileset",
    "FilePath": "LCD_Tileset.png",
    "GlyphHeight": 16,
    "GlyphPadding": 0,
    "GlyphWidth": 16,
    "SolidGlyphIndex": 219,
    "Columns": 16
}

Which you can store in the same folder as “LCD_Tileset.font”
Now you should end up with something like:

Before we continue we need to make sure that the files are copied to our output on building the project so the game can find the font file. To do this
we must select both the .font and .png file and in the Properties window
set for property “Copy to Output Directory” the value -> “Copy if newer”

All that is left is to tell the game to use this new font.
Lets add the font path to the Constants.cs.

public const string Font = "Fonts/LCD_Tileset.font";

Now we can define this new const in our ConfigureFonts method in the configuration builder inside Program.cs

.ConfigureFonts((f, gh) =>
{
    f.UseCustomFont(Constants.Font);
})

Running the game now should reflect the new font correctly.

Screen Container Setup

Now that we have our font setup, lets start with setting up our main screens.
Lets start with creating a new “Screens” folder in our project and add a .cs file in that folder named “ScreenContainer.cs”

This class will be our main container that will handle the initialization of our individual screens that will be rendered to the main window. It will be the hub to access all these screens in the future. These screens will consist of:

  • The world screen (playable area)
  • The player stats screen (overview of all player information)
  • The message log screen (screen that logs messages of actions happening in the world)

SadConsole has a generic base type “ScreenObject” that implements some very basic things like Components, Parent/Child and Position.
This is exactly what we need for our container so we will inherit our ScreenContainer from ScreenObject.

We will also create a static instance to our class so we can access it easily later from other scripts.

internal class ScreenContainer : ScreenObject
{
	private static ScreenContainer _instance;
	public static ScreenContainer Instance => _instance ?? throw new Exception("ScreenContainer is not yet initialized.");

	public ScreenContainer()
	{
		if (_instance != null)
			throw new Exception("Only one ScreenContainer instance can exist.");
		_instance = this;
	}
}

We will end up with something very simple and barebones like this.
Now we can integrate this container with the configuration of the game inside Program.cs

So in our Program.cs lets adjust the configuration builder like this, and remove the OnStart method:

private static void Main()
{
	Settings.WindowTitle = Constants.GameTitle;
	Settings.ResizeMode = Settings.WindowResizeOptions.Stretch;

	Builder configuration = new Builder()
	    .SetScreenSize(60, 40)
		.SetStartingScreen<ScreenContainer>()
		.IsStartingScreenFocused(true)
		.ConfigureFonts((f, gh) =>
		{
			f.UseCustomFont(Constants.Font);
		});

	Game.Create(configuration);
	Game.Instance.Run();
	Game.Instance.Dispose();
}

Now when you run the game, you will simply get a black screen. (This is expected!)
It basically means that the ScreenObject has no surface that can be rendered, and no child screens with a surface to render. Lets change that.

Lets add 3 new ScreenSurface properties in our ScreenContainer.cs:

public ScreenSurface World { get; }
public ScreenSurface PlayerStats { get; }
public ScreenSurface Messages { get; }

The ScreenSurface type is similar to ScreenObject only that it additionally also has a CellSurface that it renders.

Before we initialize these surfaces I would like to add a small helper class that gives us access to a useful extension method “PercentageOf”. This will help us setup our surfaces based on our screen size.

Lets add a new script “Extensions.cs” in our project (ideally this can be in the main root of our project, so next to our Fonts and Screens folder.

And we will simply add this small method to calculate the percentage of an integer value:

internal static class Extensions
{
	/// <summary>
	/// Calculates the specified percentage of the given integer value.
	/// </summary>
	/// <param name="value">The base value from which the percentage will be calculated.</param>
	/// <param name="percentage">The percentage to calculate from the base value.</param>
	/// <returns>The calculated percentage of the base value as an integer.</returns>
	internal static int PercentageOf(this int value, int percentage)
	{
		return (int)Math.Round(value * percentage / 100.0);
	}
}

Now back in our ScreenContainer.cs lets initialize our surfaces in the constructor:

public ScreenContainer()
{
    if (_instance != null)
        throw new Exception("Only one ScreenContainer instance can exist.");
    _instance = this;

    // World screen
    World = new ScreenSurface(Game.Instance.ScreenCellsX.PercentageOf(70), Game.Instance.ScreenCellsY);
    Children.Add(World);

    // Player stats screen
    PlayerStats = new ScreenSurface(Game.Instance.ScreenCellsX.PercentageOf(30), Game.Instance.ScreenCellsY.PercentageOf(60))
    {
        Position = new Point(World.Position.X + World.Width, World.Position.Y)
    };
    Children.Add(PlayerStats);

    // Messages screen
    Messages = new ScreenSurface(Game.Instance.ScreenCellsX.PercentageOf(30), Game.Instance.ScreenCellsY.PercentageOf(40))
    {
        Position = new Point(World.Position.X + World.Width, PlayerStats.Position.Y + PlayerStats.Height)
    };
    Children.Add(Messages);

    // Temporary for visualization of the surfaces
    World.Fill(background: Color.Blue);
    PlayerStats.Fill(background: Color.Green);
    Messages.Fill(background: Color.Yellow);
}

Lets explain what is happening here, so each surface is getting initialized by defining
the size of the surface in this case we are taking the ScreenCellsX and ScreenCellsY as a baseline (this is basically the size in cells of our Window).
We are taking from this a percentage.

  • World Surface : 70% width and full height
  • PlayerStats Surface: 30% width and 60% height
  • Messages Surface: 30% width, 40% height

This basically gives us 3 rectangles that should fill up the screen completely if positioned properly.

Which we are also doing here, the position of a surface is by default (0, 0) (topleft).
The World surface is situated at (0, 0).
The PlayerStats surface is situated at (0 + world.Width, 0)
The Messages surface is situated at (0 + world.Width, 0 + playerStats.Height)

If we were ever to change the screen size of the window in the configuration builder, because we are using percentages of the screen size it will scale automatically in similar fashion for our surfaces without having to change anything.

Now for SadConsole to know that these surfaces should be rendered, we must add these 3 surfaces as a “Child” element to the ScreenContainer, this can be easily done by simply adding each surface to the Children property inside the ScreenContainer as shown in the Constructor.

To visually show the surfaces I simply called .Fill on each surface with a set background color. Running the game it should look something like this:

  • Blue: World surface
  • Green: Player Stats surface
  • Yellow: Messages surface

Checkout the next article to continue with the series!

You can find all the code for this series down in the repository here:
https://github.com/Ven0maus/Code2DTutorials/tree/Tutorials/Roguelike

Leave a comment

Design a site like this with WordPress.com
Get started