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 undername.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.