Textures and Animation

A texture lets Raylib draw pixel artwork efficiently. This page covers loading, positioning, resizing, tinting, rotation, sprite sheets, and the important difference between an image and a texture.

Table of Contents

  1. Assets and Relative Paths
  2. Images and Textures
  3. Loading Safely
  4. Drawing, Resizing, and Tinting
  5. Source and Destination Rectangles
  6. Sprite-Sheet Animation

Assets and Relative Paths

An asset is a file your program uses at run time, such as an image, font, sound, or level file. The examples on this page use assets/scarfy.png, a six-frame sprite sheet from the official Raylib examples.

Six-frame Scarfy running sprite sheet

Copy the module’s assets folder into the location expected by your course project. A path such as "assets/scarfy.png" is normally interpreted relative to the program’s working directory, not relative to the .cpp source file. In Visual Studio, the working directory depends on how the project is configured and launched.

If an asset does not load, first check the exact path, spelling, capitalization, and working directory. GetWorkingDirectory() and FileExists() can help:

TraceLog(LOG_INFO, "Working directory: %s", GetWorkingDirectory());
TraceLog(LOG_INFO, "Sprite exists: %s",
         FileExists("assets/scarfy.png") ? "yes" : "no");

Images and Textures

Raylib distinguishes between two related types:

  • An Image stores pixels in normal CPU memory. Use it when you need to generate, resize, crop, or inspect pixel data.
  • A Texture2D stores image data in graphics memory so the GPU can draw it efficiently.

Most 2D programs should load a file directly as a texture:

Texture2D sprite{LoadTexture("assets/scarfy.png")};

// Main loop goes here.

UnloadTexture(sprite);

When CPU-side image processing is needed, transfer the result to a texture and release the image once it is no longer needed:

Image image{LoadImage("assets/scarfy.png")};
ImageResize(&image, image.width * 2, image.height * 2);

Texture2D texture{LoadTextureFromImage(image)};
UnloadImage(image); // The texture now has its own GPU copy.

// Draw texture in the main loop.

UnloadTexture(texture);

An Image does not need to remain loaded after LoadTextureFromImage(). A Texture2D must remain loaded for as long as you draw it.

Loading Safely

Load textures after InitWindow(), because loading a texture requires a graphics context. Check that the result is valid before using it:

Texture2D sprite{LoadTexture("assets/scarfy.png")};

if (!IsTextureValid(sprite)) {
    TraceLog(LOG_ERROR, "Could not load assets/scarfy.png");
    CloseWindow();
    return 1;
}

Unload every successfully loaded texture before CloseWindow():

UnloadTexture(sprite);
CloseWindow();

Drawing, Resizing, and Tinting

The simplest texture call places the texture’s top-left corner at an x/y position:

DrawTexture(sprite, 40, 70, WHITE);
DrawTextureV(sprite, Vector2{180.0F, 70.0F}, SKYBLUE);

The final argument is a tint. WHITE leaves the original colours unchanged; another colour multiplies the texture’s colour channels. Fade(WHITE, 0.5F) draws it at half opacity.

DrawTextureEx() adds rotation and scale:

DrawTextureEx(sprite, Vector2{300.0F, 120.0F}, 15.0F, 2.0F, WHITE);

Scaling while drawing changes its displayed size without altering the texture data.

Source and Destination Rectangles

DrawTexturePro() gives the most control:

Rectangle source{0.0F, 0.0F,
                 static_cast<float>(sprite.width),
                 static_cast<float>(sprite.height)};
Rectangle destination{400.0F, 220.0F, 240.0F, 144.0F};
Vector2 origin{destination.width / 2.0F, destination.height / 2.0F};

DrawTexturePro(sprite, source, destination, origin, 20.0F, WHITE);
  • The source rectangle selects pixels from the texture.
  • The destination rectangle controls the on-screen position and size.
  • The origin is measured from the destination rectangle’s top-left corner and becomes its drawing and rotation pivot.
  • The rotation is clockwise in degrees.

The destination’s x/y position is where Raylib places the origin. In this example, (400, 220) is the centre of the drawn texture.

Sprite-Sheet Animation

A sprite sheet stores animation frames side by side in one texture. The included scarfy.png is 768 pixels wide by 128 pixels high and contains six 128 by 128 pixel frames. We change the source rectangle over time while drawing the same texture.

This complete program animates Scarfy and lets you move left and right:

#include "raylib.h"
#include <algorithm> // std::clamp

int main() {
    constexpr int frameCount{ 6 };
    constexpr float frameDuration{ 0.10F };
    constexpr float movementSpeed{ 220.0F };

    InitWindow(900, 300, "Raylib - Sprite Animation");
    SetTargetFPS(60);

    Texture2D sprite{ LoadTexture("assets/scarfy.png") };
    if (!IsTextureValid(sprite)) {
        TraceLog(LOG_ERROR, "Could not load assets/scarfy.png");
        CloseWindow();
        return 1;
    }

    const float frameWidth{ static_cast<float>(sprite.width) / frameCount };
    const float halfDrawnWidth{ frameWidth / 2.0F };
    float positionX{ GetScreenWidth() / 2.0F };
    int currentFrame{ 0 };
    float animationTimer{ 0.0F };
    bool facingRight{ true };

    while (!WindowShouldClose()) {
        const float deltaTime{ GetFrameTime() };
        const int direction{ IsKeyDown(KEY_D) - IsKeyDown(KEY_A) };

        if (direction) {
            positionX += direction * movementSpeed * deltaTime;
            facingRight = direction > 0;
            animationTimer += deltaTime;
            while (animationTimer >= frameDuration) {
                animationTimer -= frameDuration;
                currentFrame = (currentFrame + 1) % frameCount;
            }
        }
        else {
            currentFrame = 0;
            animationTimer = 0.0F;
        }
        positionX = std::clamp(positionX, halfDrawnWidth, GetScreenWidth() - halfDrawnWidth);

        Rectangle source{ currentFrame * frameWidth, 0.0F,
            facingRight ? frameWidth : -frameWidth, static_cast<float>(sprite.height) };
        Rectangle destination{ positionX, GetScreenHeight() - 85.0F, frameWidth, sprite.height };

        BeginDrawing();
        ClearBackground(Color{ 28, 32, 52, 255 });
        DrawRectangle(0, GetScreenHeight() - 85, GetScreenWidth(), 85, Color{ 45, 52, 70, 255 });
        DrawTexturePro(sprite, source, destination,
            { destination.width / 2.0F, destination.height }, 0.0F, WHITE);
        DrawText("Move with A and D", 24, 22, 24, RAYWHITE);
        DrawText(TextFormat("Frame %i of %i", currentFrame + 1, frameCount), 24, 54, 18, LIGHTGRAY);
        EndDrawing();
    }

    UnloadTexture(sprite);
    CloseWindow();
}

A negative source width flips the selected frame horizontally. The source rectangle still selects one frame; only its drawing direction changes.

Resources

scarfy.png was created by Eiden Marsal and is included in the official Raylib examples under the CC BY-NC 4.0 licence.