Table of Contents

Content

A game's art and fonts are loaded through ContentManager, which the game reaches as ContentManager. It reads files from a root folder, caches what it loads, and disposes of it when the game ends. Loading is by file name, relative to the root:

protected override void Initialize()
{
  ContentManager.SetRootPath(Path.Combine(AppContext.BaseDirectory, "Content"));

  _ship = ContentManager.GetTexture("sprites/ship.png");
  _font = ContentManager.GetBMFont("fonts/main.fnt");

  base.Initialize();
}

Copy the folder into the output in the project file:

<ItemGroup>
  <Content Include="Content\**\*" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>

The root folder

The root is the working directory of the process until the game sets one. A game started from another folder then finds nothing, so set the root to AppContext.BaseDirectory as above. ResetRootPath() puts the root back to the working directory.

The case of a name in a path does not have to match the file system, on any system: GetTexture("Sprites/SHIP.png") finds sprites/ship.png. Each folder and the file name are matched on their own, so a path can differ from the file on disk in any of them. If two entries differ only by case, the file system decides which one is used.

Textures

GetTexture(fileName) loads an image. PNG, JPEG and TGA files are read. The texture is cached under the file it resolves to, so asking again returns the same texture, and so do two spellings of one file, such as GUI/frame.png and GUI/../GUI/frame.png. A missing file throws a FileNotFoundException. Fonts behave differently: a missing font file returns null, with a warning that names the path (see Fonts), so check what a font load returns.

A texture can be loaded with a colour key. Every pixel exactly equal to that colour becomes transparent, which is how older sheets without an alpha channel mark their transparency:

var sheet = ContentManager.GetTexture("sprites/old-sheet.bmp", Color.FromRgba(255, 0, 255));

A keyed texture is cached apart from the same file loaded without a key.

Two other ways to make a texture:

  • GetTextureFromStream(name, stream) reads an image from a stream. It is cached under name.
  • CreateTextureFromColorArray(colors, width, height) makes a texture from pixels. It is not cached.

UnloadTexture(key) disposes a cached texture and removes it from the cache, so a later GetTexture loads it again. Do not draw with a texture after unloading it.

Sub-textures

A sub-texture is one rectangle of a larger texture, such as a frame of an animation. Describe the rectangle with a TextureRegion:

var frame = ContentManager.GetSubTexture("sprites/walk.png", new TextureRegion("walk-1", 0, 0, 32, 32));

GetSubTexture also takes a texture already loaded, in place of the file name. Asking again for the same region of the same file returns the same sub-texture.

Atlases

A region atlas is one texture with named rectangles on it, which is a good fit for a sheet packed by a tool:

var atlas = ContentManager.GetRegionTextureAtlas("sprites/atlas.png");
atlas.AddRegion("hero", 0, 0, 64, 64);
atlas.AddRegion("coin", 64, 0, 16, 16);

var hero = atlas["hero"];

atlas["hero"] gives a SubTexture, or null if no region has that name. AddRegions takes an array of TextureRegions instead. TotalItemCount is the number of regions.

GetRegionAtlas(fileName, colorKey) is the same kind of atlas, with an optional colour key as GetTexture takes one. It is cached like the texture under the file, so a keyed sheet is an atlas of its own, apart from the un-keyed one. The atlas starts empty, and the game adds the regions:

using BSGameLib.Game.Drawing;

var sheet = ContentManager.GetRegionAtlas("sprites/old-sheet.bmp", Color.FromRgba(255, 0, 255));
sheet.AddRegion("hero", 0, 0, 64, 64);

Without a colour key, GetRegionAtlas and GetRegionTextureAtlas return the same atlas. It returns null when the image cannot be loaded.

An indexed atlas cuts one or more sheets of the same size into cells of itemWidth by itemHeight, and numbers the cells from 0, row by row, through each sheet in turn:

var tiles = ContentManager.GetIndexedTextureAtlas(16, 16, "tiles/a.png", "tiles/b.png");
var tile = tiles[12];

tiles[12] is a SubTexture. TotalItemCount is the number of cells, so a game can check an index it was given from data before it uses it. An atlas over sheets of different sizes throws.

Fonts

GetBMFont(fileName) loads a BMFont .fnt file. The page images it names are read from the folder the .fnt file is in. GetTrueTypeFont(fileName, size) rasterises a TrueType font at one size. A font at a second size is a second font.

GetTrueTypeFont(fileName, size, bold: true, italic: true) asks for the font in bold or in italic. A file is one face, so the style is faked from it: bold emboldens the glyphs, and italic slants them to the right. Each combination of size and style is a font of its own, and unloading the file by name removes all of them. The same options are on GetTrueTypeFontFromStream.

Both return an IFont, which can measure and wrap text and draw through DrawText. See Drawing. Both are cached, so asking again with the same name, or with the full path, returns the same font. If the file is not there, both return null and log a warning such as BMFont fonts/main.fnt not found at <full path>, returning null.

GetBMFontFromStream(name, stream) and GetTrueTypeFontFromStream(name, size, stream) read the font from a stream instead. A TrueType font read this way needs no file on disk. The page images of a BMFont read this way come from the folder name resolves to, so GetBMFontFromStream("fonts/main.fnt", stream) reads fonts/ under the root. They are cached under name too.

AddFont(key, font) caches a font the game has made itself under key. The font is also found by the name it carries, which is how BSGameLib.UI's built-in fonts are found.

UnloadFont(key) disposes a font and removes it from the cache. key is the name the font was loaded with, relative or full, and a TrueType font is removed at every size it was loaded at. The next request loads it again.

Overlays for mods

SetOverlayPaths gives a list of folders to look in before the root, first to last. A texture or font loaded by file name is taken from the first overlay folder that has a file of that name, and from the root only when none does. Content the game ships stays in the root, and a mod replaces only the files it changes:

ContentManager.SetOverlayPaths(new[]
{
  Path.Combine(AppContext.BaseDirectory, "Mods", "Pack1"),
  Path.Combine(AppContext.BaseDirectory, "Mods", "Pack2"),
});

Set the overlays before loading. Anything already loaded from the root stays as it was until it is unloaded. A stream-based load takes its data from the stream, so the overlays are not searched for it. Its page images are read from the folder its name resolves to, which may be in an overlay. A .fnt file found in an overlay reads its page images from the overlay too.

EnumerateFiles(path, recurseSubDirectories) lists the files in a folder under the root. With overlays, the list merges the folder in each overlay and the root, and a file in an overlay stands in for the root's file of the same name. A game can use it to list the levels a mod adds.

Unloading and disposal

Textures and fonts stay in memory until they are unloaded. UnloadTexture and UnloadFont each take one name. Disposing the game disposes every cached texture and font, so the game does not need to unload them at exit.