Drawing
A game draws through an encoder. An encoder collects sprites, shapes and text, and draws them when
it is disposed, so a draw is always wrapped in a using. In Draw, the encoder comes from
Graphics2D:
protected override void Initialize()
{
_ship = ContentManager.GetTexture("ship.png");
_font = ContentManager.GetBMFont("font.fnt");
base.Initialize();
}
protected override void Draw(IGameTime gameTime)
{
using var encoder = Graphics2D.Begin();
encoder.Clear(Color.CornflowerBlue);
encoder.SubmitSprite(new SpriteDrawOptions(_ship, new Rectangle(100, 100, 64, 64)));
encoder.SubmitShape(ShapeDrawOptions.RectangleOutline(new Rectangle(20, 20, 200, 80), Color.White, thickness: 2));
encoder.DrawText(TextRenderer, _font, "Score: 42", new Vector2(20, 300), Color.White);
}
Begin belongs inside a render pass. In Draw, that is the default pass. It returns an
IRenderEncoderScope with these members:
SubmitSprite(in SpriteDrawOptions)draws a texture.SubmitShape(in ShapeDrawOptions)draws a line, a rectangle, a circle, a triangle or a point.SubmitDrawCall(Action)runs a callback in its place in the order of draws.Clear(Color)fills the target with a colour.Viewport,PassTagandPassNamedescribe the encoder.
Clear discards whatever was submitted to the encoder before it, so call it first.
Coordinates are logical units, from the top left. See Windows and display.
Sprites
A SpriteDrawOptions describes one sprite:
new SpriteDrawOptions(
texture,
destinationRectangle,
sourceRectangle: null,
color: Color.White,
flip: RenderFlip.None,
rotation: 0f,
origin: default,
scale: default,
material: default,
zIndex: 0);
textureis theITextureto draw. A sub-texture from an atlas uses its own region when no source rectangle is given.destinationRectangleis where the sprite goes, and its size.sourceRectangleis the part of the texture to draw. The default is the whole texture.colortints the texture. The default is white, which leaves it as it is.flipmirrors the sprite withRenderFlip.HorizontalorRenderFlip.Vertical.rotationturns the sprite, in degrees, about its origin.originis the point in the sprite, in pixels from its top left, that sits on the destination's top left corner. It is also the point the sprite turns about.scalemultiplies the destination's width and height.materialdecides whether the sprite is lit. The default,Material.Unlit, is not. See Materials, effects and lighting.
A sprite centred on a point turns about that point:
encoder.SubmitSprite(new SpriteDrawOptions(
_ship,
new Rectangle(400, 300, 64, 64),
rotation: _angle,
origin: new Vector2(32, 32)));
Shapes
ShapeDrawOptions has a static method for each kind of shape. Each takes a colour, and most take a
thickness, a zIndex and a material:
| Method | Draws |
|---|---|
Line(start, end, color) |
A line. |
RectangleOutline(rectangle, color) |
A rectangle's outline. |
RectangleFilled(rectangle, color) |
A filled rectangle. |
CircleOutline(center, radius, color) |
A circle's outline. The optional segments sets its smoothness. |
CircleFilled(center, radius, color) |
A filled circle. |
TriangleOutline(point1, point2, point3, color) |
A triangle's outline. |
Point(position, color) |
A point, size pixels across. |
TriangleList(points, colors) |
Triangles with a colour at each vertex. |
encoder.SubmitShape(ShapeDrawOptions.CircleOutline(new Vector2(320, 180), 40, Color.Gold, thickness: 3));
There is no filled triangle method. Use TriangleList, with three points per triangle and one
colour per point. The colours are blended across each triangle, which gives gradients:
var points = new Vector2[] { new(0, 0), new(200, 0), new(100, 150) };
var colors = new Color[] { Color.Red, Color.Green, Color.Blue };
encoder.SubmitShape(ShapeDrawOptions.TriangleList(points, colors));
A shape can use a custom material, such as the one in
Materials, effects and lighting. A sprite material is refused for a
shape, with an ArgumentException.
Text
Text is drawn from a bitmap font, and there are two ways to get one from ContentManager:
GetBMFont("name.fnt")loads a BMFont file. Its page images are read from the folder the.fntfile is in. It is cached, so asking again returns the same font.GetTrueTypeFont("name.ttf", 24)rasterises a TrueType font at that size, with every character the font has a glyph for in the Basic Multilingual Plane: every script in common use, but not emoji or other characters beyond U+FFFF.
DrawText is an extension on the encoder. It takes the game's TextRenderer, the font, the text,
and the usual position, colour, rotation, origin, scale and letter spacing:
encoder.DrawText(TextRenderer, _font, "Line one\nLine two", new Vector2(20, 40), Color.White);
Text starts a new line at each \n. A character the font does not have is skipped.
IFont also measures and wraps text:
MeasureText(text)is the size the text takes, at the font's own size. It does not include any scale you draw with.Wrap(text, maxWidth)returns the text with line breaks, so that each line fits inmaxWidth.LineHeightis the distance between lines.
Text is drawn with a zIndex of 0. See the section on order below.
Order
Draws are placed in the order they were submitted, unless zIndex is set on any draw in the
encoder. Then the encoder sorts all its draws, sprites and shapes together, lowest zIndex first,
so a draw with a higher zIndex is always drawn over one with a lower.
Draws with the same zIndex keep their submission order, with one exception: sprites that share a
material and texture are drawn together, which is far cheaper. So two overlapping sprites from
different textures need different zIndex values to come out in a set order.
One way to lay a game out is in bands: the ground from 0, things on it from 100000, the interface from 200000, adding to the band's base to order things within it.
Render targets
IOffscreenRenderer draws into a texture rather than the screen. RenderToTarget creates a target,
runs the drawing you give it, and returns the target:
var target = GetService<IOffscreenRenderer>().RenderToTarget(
256,
256,
encoder => encoder.SubmitShape(ShapeDrawOptions.CircleFilled(new Vector2(128, 128), 100, Color.Red)),
clearColor: Color.Transparent);
A render target is a texture, so it is drawn with SubmitSprite like any other. RenderToTarget
makes a new target on every call, and the caller owns it, so Dispose it when it is no longer
needed. To draw into one target over many frames, see Render passes.