← ziglang.inraylib-zigGitHub

animations

★☆☆☆by Gabriel Piangers (@gabriel-piangers)

There is no browser build of this example yet.

Is it the same as C?

//! raylib [text] example - animations
//!
//! Zig-API translation of examples/text/text_animations.c (raylib 6.2).
//! Example contributed by Gabriel Piangers (@gabriel-piangers) and reviewed by Ramon Santamaria (@raysan5)
//! Example licensed under an unmodified zlib/libpng license.
//! Copyright (c) 2026 Gabriel Piangers (@gabriel-piangers)

const rl = @import("raylib");
const reasings = @import("reasings"); // Required for: Easing functions

const max_text_length = 64;
const text_spacing = 4;

/// Animated text type
const AnimatedText = struct {
    str: [:0]const u8, // Text to animate
    position: rl.Vector2, // Text position on screen
    delta: f32, // Intensity of the effect (does not apply for flashing)
    offset: f32, // Time offset (in frames) between each char
    duration: f32, // Time duration (in frames) of the animation
};

const animations = [_][:0]const u8{ "Waving", "Flashing", "Shaking" };

pub fn main() void {
    // Initialization
    const screen_width = 800;
    const screen_height = 450;

    rl.initWindow(screen_width, screen_height, "raylib [text] example - animations");
    defer rl.closeWindow(); // Close window and OpenGL context

    var current_animation: usize = 0;

    var text: AnimatedText = .{
        .str = "This is an animated text!",
        .position = .{ .x = 200, .y = 250 },
        .delta = 24.0,
        .offset = 6,
        .duration = 60,
    };
    var frame_count: i32 = 0;

    var glyph_position: [max_text_length]rl.Vector2 = undefined;
    var glyph_alpha: [max_text_length]u8 = undefined;

    rl.setTargetFPS(60);

    // Main game loop
    while (!rl.windowShouldClose()) { // Detect window close button or ESC key
        // Update
        frame_count += 1;

        if (rl.isKeyPressed(.s)) {
            text.delta = rl.math.clamp(text.delta - 2.0, 10.0, 60.0);
        } else if (rl.isKeyPressed(.w)) {
            text.delta = rl.math.clamp(text.delta + 2.0, 10.0, 60.0);
        }
        if (rl.isKeyPressed(.a)) {
            text.duration = @trunc(rl.math.clamp(text.duration - 5.0, 20.0, 120.0));
        } else if (rl.isKeyPressed(.d)) {
            text.duration = @trunc(rl.math.clamp(text.duration + 5.0, 20.0, 120.0));
        }
        if (rl.isKeyPressed(.z)) {
            text.offset = rl.math.clamp(text.offset - 1.0, 0.0, 15.0);
        } else if (rl.isKeyPressed(.x)) {
            text.offset = rl.math.clamp(text.offset + 1.0, 0.0, 15.0);
        }
        if (rl.isKeyPressed(.q)) {
            current_animation = if (current_animation == 0) animations.len - 1 else current_animation - 1;
        } else if (rl.isKeyPressed(.e)) {
            current_animation = (current_animation + 1) % animations.len;
        }

        const glyph_count = text.str.len;

        // NOTE: Calculating every glyph position by its width
        var offset_x: u32 = 0;
        for (text.str, glyph_position[0..glyph_count], glyph_alpha[0..glyph_count]) |char, *position, *alpha| {
            position.* = .{ .x = text.position.x + @as(f32, @floatFromInt(offset_x)), .y = text.position.y };
            alpha.* = 255;

            const glyph = [1:0]u8{char};
            offset_x += @intCast(rl.measureText(&glyph, 30) + text_spacing);
        }

        const duration: i32 = @intFromFloat(text.duration);
        for (glyph_position[0..glyph_count], glyph_alpha[0..glyph_count], 0..) |*position, *alpha, i| {
            const char_frame = @as(f32, @floatFromInt(frame_count)) + @as(f32, @floatFromInt(i)) * text.offset;
            const time: f32 = @floatFromInt(@rem(@as(i32, @intFromFloat(char_frame)), duration));

            switch (current_animation) {
                0 => { // Waving animation
                    if (time < text.duration / 2) {
                        position.y = reasings.easeSineIn(time, text.position.y, text.delta, text.duration);
                    } else {
                        position.y = reasings.easeSineOut(time, text.position.y + text.delta, -text.delta, text.duration);
                    }
                },
                1 => { // Flashing animation
                    alpha.* = @intFromFloat(reasings.easeSineInOut(time, 255.0, -255.0, text.duration));
                },
                2 => { // Shaking animation
                    const shake: i32 = @intFromFloat(text.delta / 10);
                    const shake_min: i32 = @intFromFloat(-text.delta / 10);
                    const delta_x: f32 = @floatFromInt(rl.getRandomValue(shake_min, shake));
                    const delta_y: f32 = @floatFromInt(rl.getRandomValue(shake_min, shake));
                    const half = text.duration / 2;

                    if (time < half) {
                        position.x = reasings.easeLinearNone(time, position.x, delta_x, half);
                        position.y = reasings.easeLinearNone(time, text.position.y, delta_y, half);
                    } else {
                        position.x = reasings.easeLinearNone(time - half, position.x + delta_x, -delta_x, half);
                        position.y = reasings.easeLinearNone(time - half, text.position.y + delta_y, -delta_y, half);
                    }
                },
                else => unreachable,
            }
        }

        // Draw
        rl.beginDrawing();
        defer rl.endDrawing();

        rl.clearBackground(.ray_white);

        rl.drawText(rl.textFormat("Duration (A | D): {d}", .{duration}), 20, 20, 20, .gray);
        rl.drawText(rl.textFormat("Delta (W | S): {d:.1}", .{text.delta}), 20, 50, 20, .gray);
        rl.drawText(rl.textFormat("Offset (Z | X): {d:.1}", .{text.offset}), 20, 80, 20, .gray);
        rl.drawText(rl.textFormat("Curent Animation (Q | E): {s}", .{animations[current_animation]}), 20, 110, 20, .gray);

        const font = rl.getFontDefault();
        for (text.str, glyph_position[0..glyph_count], glyph_alpha[0..glyph_count]) |char, position, alpha| {
            // NOTE: Drawing text glyph by glyph, so, spacing is actually not used
            const glyph = [1:0]u8{char};
            const font_size = @as(f32, @floatFromInt(font.baseSize)) * 3.0;
            rl.drawTextEx(font, &glyph, position, font_size, 0, .{ .r = 200, .g = 122, .b = 255, .a = alpha });
        }
    }
}
/*******************************************************************************************
*
*   raylib [text] example - animations
*
*   Example complexity rating: [★☆☆☆] 1/4
*
*   Example originally created with raylib 6.0, last time updated with raylib 6.0
*
*   Example contributed by Gabriel Piangers (@gabriel-piangers) and reviewed by Ramon Santamaria (@raysan5)
*
*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
*   BSD-like license that allows static linking with closed source software
*
*   Copyright (c) 2026 Gabriel Piangers (@gabriel-piangers)
*
********************************************************************************************/

#include "raylib.h"

#include "reasings.h"       // Required for: Easing functions
#include "raymath.h"        // Required for: Clamp()

#define MAX_TEXT_LENGTH     64
#define TEXT_SPACING         4

// Animated text type
typedef struct {
    char str[MAX_TEXT_LENGTH]; // Text to animate
    Vector2 position;       // Text position on screen
    float delta;            // Intensity of the effect (does not apply for flashing)
    float offset;           // Time offset (in frames) between each char
    float duration;         // Time duration (in frames) of the animation
} AnimatedText;

//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
    // Initialization
    //--------------------------------------------------------------------------------------
    const int screenWidth = 800;
    const int screenHeight = 450;

    InitWindow(screenWidth, screenHeight, "raylib [text] example - animations");

    char animations[3][MAX_TEXT_LENGTH] = { "Waving", "Flashing", "Shaking" };
    int animationsSize = sizeof(animations)/sizeof(animations[0]);
    int currentAnimation = 0;

    AnimatedText text = { "This is an animated text!", { 200, 250 }, 24.0f, 6, 60 };
    int frameCount = 0;

    Vector2 glyphPosition[MAX_TEXT_LENGTH] = { 0 };
    unsigned char glyphAlpha[MAX_TEXT_LENGTH] = { 0 };

    SetTargetFPS(60);
    //--------------------------------------------------------------------------------------

    // Main game loop
    while (!WindowShouldClose())    // Detect window close button or ESC key
    {
        // Update
        //----------------------------------------------------------------------------------
        frameCount++;

        if (IsKeyPressed(KEY_S)) text.delta = Clamp(text.delta - 2.0f, 10.0f, 60.0f);
        else if (IsKeyPressed(KEY_W)) text.delta = Clamp(text.delta + 2.0f, 10.0f, 60.0f);
        if (IsKeyPressed(KEY_A)) text.duration = (int) Clamp((float) text.duration - 5.0f, 20.0f, 120.0f);
        else if (IsKeyPressed(KEY_D)) text.duration = (int) Clamp((float) text.duration + 5.0f, 20.0f, 120.0f);
        if (IsKeyPressed(KEY_Z)) text.offset = Clamp(text.offset - 1.0f, 0.0f, 15.0f);
        else if (IsKeyPressed(KEY_X)) text.offset = Clamp(text.offset + 1.0f, 0.0f, 15.0f);
        if (IsKeyPressed(KEY_Q)) currentAnimation = currentAnimation? currentAnimation - 1 : animationsSize - 1;
        else if (IsKeyPressed(KEY_E)) currentAnimation = (currentAnimation + 1)%animationsSize;

        for (unsigned int i = 0, offsetX = 0; i < TextLength(text.str); i++)
        {
            // NOTE: Calculating every glyph position by its width
            glyphPosition[i] = (Vector2) { text.position.x + offsetX, text.position.y };
            glyphAlpha[i] = 255U;

            offsetX += (MeasureText(TextSubtext(text.str, i, 1), 30) + TEXT_SPACING);
        }

        switch (currentAnimation)
        {
            case 0: // Waving animation
            {
                for (unsigned int i = 0; i < TextLength(text.str); i++)
                {
                    float time = (int) (frameCount + i*text.offset)%(int)text.duration;

                    if (time < text.duration/2) glyphPosition[i].y = EaseSineIn(time, text.position.y, text.delta, text.duration);
                    else glyphPosition[i].y = EaseSineOut(time, text.position.y + text.delta, -text.delta, text.duration);
                }
            } break;
            case 1: // Flashing animation
            {
                for (unsigned int i = 0; i < TextLength(text.str); i++)
                {
                    float time = (int) (frameCount + i*text.offset)%(int)text.duration;
                    glyphAlpha[i] = (unsigned char) EaseSineInOut(time, 255.0f, -255.0f, text.duration);
                }
            } break;
            case 2: // Shaking animation
            {
                for (unsigned int i = 0; i < TextLength(text.str); i++)
                {
                    float time = (int) (frameCount + i*text.offset)%(int)text.duration;
                    float deltaX = (float) GetRandomValue(-text.delta/10, text.delta/10);
                    float deltaY = (float) GetRandomValue(-text.delta/10, text.delta/10);

                    if (time < (text.duration/2))
                    {
                        glyphPosition[i].x = EaseLinearNone(time, glyphPosition[i].x, deltaX, text.duration/2);
                        glyphPosition[i].y = EaseLinearNone(time, text.position.y, deltaY, text.duration/2);
                    }
                    else
                    {
                        glyphPosition[i].x = EaseLinearNone(time - text.duration/2, glyphPosition[i].x + deltaX, -deltaX, text.duration/2);
                        glyphPosition[i].y = EaseLinearNone(time - text.duration/2, text.position.y + deltaY, -deltaY, text.duration/2);
                    }
                }
            } break;
            default: break;
        }

        //----------------------------------------------------------------------------------

        // Draw
        //----------------------------------------------------------------------------------
        BeginDrawing();

        ClearBackground(RAYWHITE);

        DrawText(TextFormat("Duration (A | D): %d", (int)text.duration), 20, 20, 20, GRAY);
        DrawText(TextFormat("Delta (W | S): %.1f", text.delta), 20, 50, 20, GRAY);
        DrawText(TextFormat("Offset (Z | X): %.1f", text.offset), 20, 80, 20, GRAY);
        DrawText(TextFormat("Curent Animation (Q | E): %s", animations[currentAnimation]), 20, 110, 20, GRAY);

        for (unsigned int i = 0; i < TextLength(text.str); i++)
        {
            // NOTE: Drawing text glyph by glyph, so, spacing is actually not used
            DrawTextEx(GetFontDefault(), TextSubtext(text.str, i, 1), glyphPosition[i],
                GetFontDefault().baseSize*3.0f, 0, (Color) { 200U, 122U, 255U, glyphAlpha[i] });
        }

        EndDrawing();
        //----------------------------------------------------------------------------------
    }

    // De-Initialization
    //--------------------------------------------------------------------------------------
    CloseWindow();        // Close window and OpenGL context
    //--------------------------------------------------------------------------------------

    return 0;
}

From raylib 6.2. The original on GitHub.