Tutorial 08: Loading and Drawing Textures

CNA Tutorial Series  ·  Beginner

What you’ll learn

  • Loading a Texture2D through ContentManager.
  • Why CNA needs no separate texture descriptor file.
  • The SpriteBatch::Draw overloads, and flipping with SpriteEffects.

Before you startTutorial 06: Drawing Your First 2D Shape for the SpriteBatch workflow, and Tutorial 07: Working with Colors for the tint parameter.

Texture2D Overview

Texture2D represents a 2D image stored on the GPU. In CNA you load image files (PNG, JPEG, BMP) and use SpriteBatch::Draw() to render them. Key properties:

#include "Microsoft/Xna/Framework/Graphics/Texture2D.hpp"

// After loading:
int w = texture->getWidthProperty();   // width in pixels
int h = texture->getHeightProperty();  // height in pixels

Texture2D is owned by your game class via std::unique_ptr<Texture2D>. The GPU memory is freed when the unique_ptr is destroyed.

Loading Textures

CNA supports two loading paths:

Option A: Direct file load (simplest)

Pass a file path directly to the Texture2D constructor:

void MyGame::LoadContent() {
    spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());

    // Load a PNG from the assets/ directory
    // Path is relative to the executable
    player_ = std::make_unique<Texture2D>("assets/player.png",
                                           getGraphicsDeviceProperty());

    background_ = std::make_unique<Texture2D>("assets/background.png",
                                               getGraphicsDeviceProperty());
}

Option B: ContentManager (XNA-style)

The Content member (of type ContentManager) provides the XNA-style asset pipeline. It caches assets and resolves paths relative to a root directory:

void MyGame::Initialize() {
    Game::Initialize();
    // Set the root directory for content
    Content.setRootDirectoryProperty("assets");
}

void MyGame::LoadContent() {
    spriteBatch_ = std::make_unique<SpriteBatch>(getGraphicsDeviceProperty());

    // Loads assets/player.png — the extension is resolved automatically
    playerTex_ = getContentProperty().Load<Texture2D>("player");

    // Loads assets/ui/button.png
    buttonTex_ = getContentProperty().Load<Texture2D>("ui/button");
}

The ContentManager path strips the extension and resolves the asset name against the image formats CNA can decode.

There Is No Texture Descriptor

Textures have no sidecar or descriptor file. Load<Texture2D> resolves your asset name against .png, .jpg, .jpeg, .bmp, .gif, .tga, .tif, .tiff and .qoi, and decodes the first one it finds. There is nothing else to configure at load time.

Filtering and wrapping are not properties of the asset. As in XNA, they are sampler state on the graphics device, applied when you draw — which means the same texture can be sampled differently in different batches:

#include "Microsoft/Xna/Framework/Graphics/SamplerState.hpp"

auto& gd = getGraphicsDeviceProperty();

// Point sampling for a crisp pixel-art look:
gd.getSamplerStatesProperty()[0] = SamplerState::PointClamp;

// Or tile a texture across a larger quad:
gd.getSamplerStatesProperty()[0] = SamplerState::LinearWrap;

SpriteBatch::Begin() also takes a SamplerState* directly, which is the usual way to set it for 2D drawing.

Draw Overloads

SpriteBatch::Draw() has several overloads with increasing control. Here they are from simplest to most complete:

1. Position only

// Draw at (x, y), no tint (White), full texture
spriteBatch_->Draw(*tex_, Vector2(100.0f, 200.0f), Color::White);

2. Position with tint

// Draw tinted red at position
spriteBatch_->Draw(*tex_, Vector2(100.0f, 200.0f), Color::Red);

3. Destination rectangle

// Stretch texture to fill a 200x150 rectangle at (50,50)
spriteBatch_->Draw(*tex_, Rectangle(50, 50, 200, 150), Color::White);

4. Source rectangle (draw sub-region)

// Draw only the top-left 32x32 pixels of the texture
Rectangle src(0, 0, 32, 32);
spriteBatch_->Draw(*tex_, Vector2(100.0f, 100.0f), src, Color::White);

5. Full control overload

// Full signature:
// Draw(texture, position, sourceRect, color, rotation, origin, scale, effects, depth)
Vector2   position(400.0f, 300.0f);   // screen position
Rectangle source(0, 0, 64, 64);       // source region (nullptr for whole texture)
Color     color   = Color::White;
float     rotation = 0.5f;            // radians
Vector2   origin(32.0f, 32.0f);       // rotation pivot (centre of 64x64 sprite)
float     scale   = 2.0f;             // 2x size
SpriteEffects fx  = SpriteEffects::None;
float     depth   = 0.0f;             // z-depth for sorting (0=front, 1=back)

spriteBatch_->Draw(*tex_, position, source, color,
                   rotation, origin, scale, fx, depth);

Parameters explained:

ParameterTypeDescription
positionVector2Top-left (or origin point) on screen
sourceRectangleRectangleRegion of the texture to draw; omit for whole texture
colorColorTint color; White = no tint
rotationfloatRotation in radians, clockwise
originVector2Pivot point in texture space (pixels); (0,0) = top-left
scalefloat or Vector2Scale multiplier (1.0 = original size)
effectsSpriteEffectsFlip flags
layerDepthfloat0..1, used with BackToFront / FrontToBack sorting

Practical examples

// Draw a sprite centred at screen centre, rotated, 2x scale
auto& vp = getGraphicsDeviceProperty().getViewportProperty();
Vector2 screenCentre(vp.getWidthProperty() / 2.0f, vp.getHeightProperty() / 2.0f);
Vector2 texCentre(tex_->getWidthProperty() / 2.0f, tex_->getHeightProperty() / 2.0f);

spriteBatch_->Draw(*tex_, screenCentre, std::nullopt, Color::White,
                   rotation_, texCentre, 2.0f, SpriteEffects::None, 0.0f);

// Update rotation in Update():
rotation_ += MathHelper::Pi * dt;  // rotate 180 degrees per second
// Non-uniform scale: stretch width only
Vector2 scaleXY(3.0f, 1.0f);  // triple width, normal height
spriteBatch_->Draw(*tex_, Vector2(50.0f, 50.0f), std::nullopt,
                   Color::White, 0.0f, Vector2::Zero, scaleXY,
                   SpriteEffects::None, 0.0f);

SpriteEffects: Flipping

SpriteEffects is an enum used to flip sprites horizontally or vertically — essential for characters that face both left and right:

#include "Microsoft/Xna/Framework/Graphics/SpriteEffects.hpp"

// No flip (default)
SpriteEffects::None

// Mirror horizontally (flip left-right) — for a character facing left
SpriteEffects::FlipHorizontally

// Mirror vertically (flip up-down) — less common
SpriteEffects::FlipVertically

// Flip both axes
SpriteEffects::FlipBoth  // = FlipHorizontally | FlipVertically

Typical usage — a character that faces the direction of movement:

// In Update():
if (velocity_.X < 0.0f) {
    facingLeft_ = true;
} else if (velocity_.X > 0.0f) {
    facingLeft_ = false;
}

// In Draw():
SpriteEffects flip = facingLeft_
    ? SpriteEffects::FlipHorizontally
    : SpriteEffects::None;

spriteBatch_->Draw(*playerTex_, position_, std::nullopt, Color::White,
                   0.0f, Vector2::Zero, 1.0f, flip, 0.0f);

You now know how to load image files and draw them with full control. In Tutorial 09 you will add text to your game using SpriteFont.