Table of Contents

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, PassTag and PassName describe 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);
  • texture is the ITexture to draw. A sub-texture from an atlas uses its own region when no source rectangle is given.
  • destinationRectangle is where the sprite goes, and its size.
  • sourceRectangle is the part of the texture to draw. The default is the whole texture.
  • color tints the texture. The default is white, which leaves it as it is.
  • flip mirrors the sprite with RenderFlip.Horizontal or RenderFlip.Vertical.
  • rotation turns the sprite, in degrees, about its origin.
  • origin is 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.
  • scale multiplies the destination's width and height.
  • material decides 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 .fnt file 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 in maxWidth.
  • LineHeight is 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.