animations
There is no browser build of this example yet.
Is it the same as C?
- Desktop:verified The Zig version renders a final frame that is byte-identical to the C example.
- Browser:not checked yet The browser comparison does not cover this example yet.
//! 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.