Tutorial 06: Drawing Your First 2D Shape
SpriteBatch Introduction
SpriteBatch is CNA's primary 2D drawing interface. It batches multiple sprite draw calls into as few GPU draw calls as possible, making it efficient for drawing many sprites per frame.
The workflow for every 2D rendering frame is:
- Clear the back buffer with
device.Clear(color) - Call
spriteBatch_->Begin() - Issue as many
spriteBatch_->Draw(...)calls as needed - Call
spriteBatch_->End()— this flushes the batch to the GPU - Call
device.Present()to swap the back buffer to the screen
CNA's SpriteBatch matches the XNA 4.0 API. If you have used MonoGame or XNA before, this will feel familiar.
GraphicsDevice and GraphicsDeviceManager
GraphicsDeviceManager is a component you create in the constructor. It owns the window and the GraphicsDevice. You access the graphics device via getGraphicsDeviceProperty():
// In Draw():
auto& device = getGraphicsDeviceProperty(); // returns GraphicsDevice&
device.Clear(Color::CornflowerBlue); // clear to a color
device.Present(); // swap back buffer
The GraphicsDevice also gives you the current viewport dimensions, which you will use to center objects on screen:
auto& vp = getGraphicsDeviceProperty().getViewport();
int screenWidth = vp.getWidth(); // e.g. 800
int screenHeight = vp.getHeight(); // e.g. 600
The 1×1 White Texture Trick
XNA does not have a primitive rectangle drawing function. The standard approach is to create a 1×1 white Texture2D and stretch it to any size. Multiplying by a color tints the white pixels to that color.
// Create a 1x1 white texture
pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
Color white = Color::White; // RGBA (255,255,255,255)
pixel_->SetData(&white, 1); // upload one pixel to the GPU
This is the exact same technique used in XNA and MonoGame games. The texture costs essentially nothing: 4 bytes of VRAM.
Drawing Colored Rectangles
Once you have the 1×1 pixel texture, draw a filled rectangle by passing a Rectangle as the destination:
spriteBatch_->Begin();
// Draw a red rectangle at (50, 50), 200 pixels wide, 100 pixels tall
spriteBatch_->Draw(
*pixel_, // Texture2D&
Rectangle(50, 50, 200, 100), // destination rectangle
Color::Red // tint color
);
// Draw a semi-transparent blue rectangle
spriteBatch_->Draw(
*pixel_,
Rectangle(100, 200, 150, 80),
Color(0, 0, 255, 128) // R=0,G=0,B=255,A=128 (50% alpha)
);
spriteBatch_->End();
The Rectangle struct holds X, Y, Width, Height as integers. X and Y are the top-left corner in screen pixels, with (0,0) at the top-left of the window.
SpriteBatch Begin and End
Begin() and End() bracket a batch of draw calls. You must call them in pairs:
// Default Begin — deferred mode, sorted by texture
spriteBatch_->Begin();
// Immediate mode — each Draw flushes immediately (slower, but predictable)
spriteBatch_->Begin(SpriteSortMode::Immediate);
// Back-to-front depth sorting
spriteBatch_->Begin(SpriteSortMode::BackToFront);
// Additive blending — good for particles and lights
spriteBatch_->Begin(SpriteSortMode::Deferred, BlendState::Additive);
For now, the default parameterless Begin() is sufficient. We will cover blend states and sorting in a later tutorial.
Complete example
Here is a game that draws several colored rectangles demonstrating size, position, and color:
#include <memory>
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/Color.hpp"
#include "Microsoft/Xna/Framework/Graphics/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/SpriteBatch.hpp"
#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"
#include "Microsoft/Xna/Framework/Rectangle.hpp"
#include "Microsoft/Xna/Framework/Input/Keyboard.hpp"
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using namespace Microsoft::Xna::Framework::Input;
class ShapeDemo final : public Game {
public:
ShapeDemo() : graphics_(this) {
graphics_.setPreferredBackBufferWidth(800);
graphics_.setPreferredBackBufferHeight(600);
}
protected:
void LoadContent() override {
spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());
// The 1x1 white pixel — the workhorse of 2D shape drawing
pixel_ = std::make_unique<Texture2D>(getGraphicsDeviceProperty(), 1, 1);
Color white = Color::White;
pixel_->SetData(&white, 1);
}
void Update(GameTime& gameTime) override {
if (Keyboard::GetState().IsKeyDown(Keys::Escape)) Exit();
}
void Draw(const GameTime& gameTime) override {
auto& device = getGraphicsDeviceProperty();
device.Clear(Color::CornflowerBlue);
spriteBatch_->Begin();
// Large dark background panel
spriteBatch_->Draw(*pixel_, Rectangle(50, 50, 700, 500),
Color(0, 0, 0, 180));
// A red square
spriteBatch_->Draw(*pixel_, Rectangle(100, 100, 120, 120), Color::Red);
// A green rectangle
spriteBatch_->Draw(*pixel_, Rectangle(280, 100, 200, 80), Color::Green);
// A blue tall rectangle
spriteBatch_->Draw(*pixel_, Rectangle(540, 100, 80, 200), Color::Blue);
// A yellow wide rectangle
spriteBatch_->Draw(*pixel_, Rectangle(100, 300, 400, 50), Color::Yellow);
// A semi-transparent white rectangle
spriteBatch_->Draw(*pixel_, Rectangle(200, 380, 200, 100),
Color(255, 255, 255, 100));
// An orange square
spriteBatch_->Draw(*pixel_, Rectangle(550, 380, 100, 100),
Color(255, 140, 0, 255));
spriteBatch_->End();
device.Present();
}
private:
GraphicsDeviceManager graphics_;
std::unique_ptr<SpriteBatch> spriteBatch_;
std::unique_ptr<Texture2D> pixel_;
};
int main() {
ShapeDemo game;
game.Run();
return 0;
}
Run this and you will see a window with several colored rectangles layered on a cornflower blue background. The semi-transparent rectangle lets the shapes behind it show through.
In Tutorial 07 we go deeper into the Color struct — predefined colors, RGBA construction, lerping, and alpha blending.