---
title: "Desenvolvimento de Jogos com Zig: Guia Completo para Game Devs"
url: "https://ziglang.com.br/tutoriais/zig-game-dev/"
markdown_url: "https://ziglang.com.br/tutoriais/zig-game-dev.MD"
description: "Aprenda a desenvolver jogos com Zig usando raylib, Mach Engine e SDL2. Inclui exemplos práticos de game loop, sprites e ECS."
date: "2026-02-20"
author: "Zig Brasil"
---

# Desenvolvimento de Jogos com Zig: Guia Completo para Game Devs

Aprenda a desenvolver jogos com Zig usando raylib, Mach Engine e SDL2. Inclui exemplos práticos de game loop, sprites e ECS.


O desenvolvimento de jogos exige o máximo de performance e controle sobre o hardware, e é exatamente isso que a **zig lang** oferece. A **linguagem Zig** combina velocidade comparável ao C, gerenciamento de memória sem garbage collector e excelente interoperabilidade com bibliotecas C, tornando-a uma escolha cada vez mais popular entre game devs. Neste guia completo, vamos explorar como construir jogos com Zig na prática.

## Por Que Zig para Game Development

Game devs enfrentam desafios únicos que fazem de Zig uma escolha atraente:

- **Sem pausas de GC**: Garbage collectors podem causar stuttering visível em jogos. Zig não tem GC, dando controle total sobre quando e como a memória é alocada e liberada.
- **Performance previsível**: Cada operação em Zig tem custo previsível. Não há alocações ocultas, funções virtuais escondidas ou boxing automático.
- **Cross-compilation embutida**: Compile para Windows, Linux, macOS e até consoles a partir de uma única máquina.
- **Interoperabilidade C zero-cost**: Use bibliotecas C de gamedev (raylib, SDL2, OpenGL, Vulkan) diretamente, sem wrapper ou overhead.
- **Comptime**: Gere lookup tables, bake dados de níveis e otimize estruturas de dados em tempo de compilação.
- **Alocadores customizáveis**: Use arena allocators para frames, pool allocators para entidades e allocators fixos para recursos que vivem por toda a partida.

## Engines e Bibliotecas do Ecossistema

O ecossistema de gamedev em Zig está crescendo rapidamente. Aqui estão as principais opções:

### Mach Engine

O Mach Engine é uma game engine escrita inteiramente em Zig, projetada para ser modular e de alta performance. Ela fornece:

- Rendering via WebGPU (compatível com Vulkan, Metal, DX12)
- Sistema de áudio integrado
- ECS embutido
- Build system integrado com Zig

### raylib-zig

Bindings Zig para raylib, uma das bibliotecas de gamedev mais acessíveis. Raylib é perfeita para protótipos rápidos e jogos 2D/3D simples.

### SDL2 Bindings

SDL2 é a biblioteca de mídia mais usada em gamedev. Os bindings Zig permitem usar SDL2 com a ergonomia da linguagem Zig.

### Outras Opções

- **Sokol bindings**: Abstração gráfica cross-platform minimalista.
- **GLFW bindings**: Para quem quer trabalhar diretamente com OpenGL/Vulkan.
- **Box2D bindings**: Física 2D.

## Configurando raylib com Zig

Vamos configurar um projeto de jogo usando raylib, que é a forma mais rápida de começar.

### Estrutura do Projeto

```
meu-jogo/
├── build.zig
├── build.zig.zon
└── src/
    └── main.zig
```

### build.zig.zon

```zig
.{
    .name = "meu-jogo",
    .version = "0.1.0",
    .dependencies = .{
        .raylib = .{
            .url = "https://github.com/raysan5/raylib/archive/refs/tags/5.0.tar.gz",
            .hash = "...", // Hash será fornecido pelo zig build na primeira execução
        },
    },
}
```

### build.zig

```zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const raylib_dep = b.dependency("raylib", .{
        .target = target,
        .optimize = optimize,
    });

    const exe = b.addExecutable(.{
        .name = "meu-jogo",
        .root_source_file = b.path("src/main.zig"),
        .target = target,
        .optimize = optimize,
    });

    exe.linkLibrary(raylib_dep.artifact("raylib"));
    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    const run_step = b.step("run", "Rodar o jogo");
    run_step.dependOn(&run_cmd.step);
}
```

## Criando uma Janela e Game Loop

O game loop é o coração de qualquer jogo. Vamos criar um com raylib:

```zig
const std = @import("std");
const rl = @cImport({
    @cInclude("raylib.h");
});

const LARGURA = 800;
const ALTURA = 600;

pub fn main() void {
    // Inicializar janela
    rl.InitWindow(LARGURA, ALTURA, "Meu Jogo em Zig");
    defer rl.CloseWindow();

    rl.SetTargetFPS(60);

    // Game Loop principal
    while (!rl.WindowShouldClose()) {
        // === ATUALIZAÇÃO ===
        atualizar();

        // === RENDERIZAÇÃO ===
        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.RAYWHITE);
        rl.DrawText("Olá, Game Dev em Zig!", 190, 200, 30, rl.DARKGRAY);
        rl.DrawFPS(10, 10);
    }
}

fn atualizar() void {
    // Lógica de atualização aqui
}
```

Compile e execute:
```bash
zig build run
```

## Movendo um Personagem com Input

Vamos criar um personagem que se move com as teclas de seta:

```zig
const std = @import("std");
const rl = @cImport({
    @cInclude("raylib.h");
});

const LARGURA = 800;
const ALTURA = 600;
const VELOCIDADE = 4.0;
const TAMANHO_JOGADOR = 40;

const Jogador = struct {
    x: f32,
    y: f32,
    cor: rl.Color,

    pub fn atualizar(self: *Jogador) void {
        if (rl.IsKeyDown(rl.KEY_RIGHT)) self.x += VELOCIDADE;
        if (rl.IsKeyDown(rl.KEY_LEFT)) self.x -= VELOCIDADE;
        if (rl.IsKeyDown(rl.KEY_DOWN)) self.y += VELOCIDADE;
        if (rl.IsKeyDown(rl.KEY_UP)) self.y -= VELOCIDADE;

        // Manter dentro da tela
        self.x = std.math.clamp(self.x, 0, LARGURA - TAMANHO_JOGADOR);
        self.y = std.math.clamp(self.y, 0, ALTURA - TAMANHO_JOGADOR);
    }

    pub fn desenhar(self: Jogador) void {
        rl.DrawRectangle(
            @intFromFloat(self.x),
            @intFromFloat(self.y),
            TAMANHO_JOGADOR,
            TAMANHO_JOGADOR,
            self.cor,
        );
    }
};

pub fn main() void {
    rl.InitWindow(LARGURA, ALTURA, "Jogador com Movimento");
    defer rl.CloseWindow();
    rl.SetTargetFPS(60);

    var jogador = Jogador{
        .x = @as(f32, LARGURA) / 2.0,
        .y = @as(f32, ALTURA) / 2.0,
        .cor = rl.BLUE,
    };

    while (!rl.WindowShouldClose()) {
        jogador.atualizar();

        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.RAYWHITE);
        jogador.desenhar();
        rl.DrawText("Use as setas para mover", 10, 10, 20, rl.GRAY);
    }
}
```

## Sprites e Texturas

Para jogos reais, precisamos carregar e exibir imagens:

```zig
const rl = @cImport({
    @cInclude("raylib.h");
});

const Sprite = struct {
    textura: rl.Texture2D,
    x: f32,
    y: f32,
    escala: f32 = 1.0,
    rotacao: f32 = 0.0,

    pub fn carregar(caminho: [*:0]const u8) Sprite {
        return Sprite{
            .textura = rl.LoadTexture(caminho),
            .x = 0,
            .y = 0,
        };
    }

    pub fn descarregar(self: *Sprite) void {
        rl.UnloadTexture(self.textura);
    }

    pub fn desenhar(self: Sprite) void {
        rl.DrawTextureEx(
            self.textura,
            rl.Vector2{ .x = self.x, .y = self.y },
            self.rotacao,
            self.escala,
            rl.WHITE,
        );
    }
};

pub fn main() void {
    rl.InitWindow(800, 600, "Sprites em Zig");
    defer rl.CloseWindow();
    rl.SetTargetFPS(60);

    var heroi = Sprite.carregar("assets/heroi.png");
    defer heroi.descarregar();

    heroi.x = 400;
    heroi.y = 300;
    heroi.escala = 2.0;

    while (!rl.WindowShouldClose()) {
        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.BLACK);
        heroi.desenhar();
    }
}
```

## Gerenciamento de Memória para Jogos: Arena Allocators

Em jogos, a alocação de memória precisa ser rápida e previsível. Zig oferece arena allocators que são perfeitos para dados temporários de cada frame.

```zig
const std = @import("std");

const Particula = struct {
    x: f32,
    y: f32,
    vx: f32,
    vy: f32,
    vida: f32,
};

pub fn main() !void {
    // Allocator de arena para dados por frame
    // Toda memória alocada é liberada de uma vez no final do frame
    var arena_buffer: [1024 * 1024]u8 = undefined; // 1MB para o frame
    var frame_allocator = std.heap.FixedBufferAllocator.init(&arena_buffer);

    // Allocator de longa duração para recursos persistentes
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const persistent_alloc = gpa.allocator();

    // Lista persistente de entidades (vive por toda a partida)
    var entidades = std.ArrayList(Particula).init(persistent_alloc);
    defer entidades.deinit();

    // Simulação de 60 frames
    var frame: u32 = 0;
    while (frame < 60) : (frame += 1) {
        // Resetar arena no início de cada frame
        frame_allocator.reset();
        const frame_alloc = frame_allocator.allocator();

        // Dados temporários deste frame (colisões, cálculos, etc.)
        var colisoes = std.ArrayList([2]usize).init(frame_alloc);
        // Não precisa de defer deinit! Arena vai resetar tudo

        // Detectar colisões (dado temporário do frame)
        for (entidades.items, 0..) |a, i| {
            for (entidades.items[i + 1 ..], 0..) |b, j_offset| {
                const j = i + 1 + j_offset;
                const dx = a.x - b.x;
                const dy = a.y - b.y;
                if (dx * dx + dy * dy < 100.0) {
                    try colisoes.append(.{ i, j });
                }
            }
        }

        // Processar colisões...
        _ = colisoes; // Uso simplificado para o exemplo
    }
}
```

A vantagem da arena é que a liberação de memória é instantânea: basta resetar o ponteiro. Isso evita a fragmentação de memória e elimina o overhead de liberações individuais.

## Padrão ECS (Entity Component System)

O ECS é o padrão de arquitetura mais popular em game development moderno. Zig é particularmente bom para implementar ECS graças ao comptime e aos generics.

```zig
const std = @import("std");

// === COMPONENTES ===
const Posicao = struct {
    x: f32 = 0,
    y: f32 = 0,
};

const Velocidade = struct {
    vx: f32 = 0,
    vy: f32 = 0,
};

const Sprite = struct {
    largura: u32,
    altura: u32,
    cor: u32 = 0xFFFFFFFF,
};

const Vida = struct {
    atual: i32,
    maxima: i32,
};

// === MUNDO ECS SIMPLIFICADO ===
const MAX_ENTIDADES = 1000;

const Mundo = struct {
    // Cada componente tem seu próprio array (SoA - Structure of Arrays)
    posicoes: [MAX_ENTIDADES]?Posicao = [_]?Posicao{null} ** MAX_ENTIDADES,
    velocidades: [MAX_ENTIDADES]?Velocidade = [_]?Velocidade{null} ** MAX_ENTIDADES,
    sprites: [MAX_ENTIDADES]?Sprite = [_]?Sprite{null} ** MAX_ENTIDADES,
    vidas: [MAX_ENTIDADES]?Vida = [_]?Vida{null} ** MAX_ENTIDADES,
    proxima_entidade: usize = 0,

    pub fn criarEntidade(self: *Mundo) usize {
        const id = self.proxima_entidade;
        self.proxima_entidade += 1;
        return id;
    }

    pub fn adicionarPosicao(self: *Mundo, id: usize, pos: Posicao) void {
        self.posicoes[id] = pos;
    }

    pub fn adicionarVelocidade(self: *Mundo, id: usize, vel: Velocidade) void {
        self.velocidades[id] = vel;
    }

    pub fn adicionarSprite(self: *Mundo, id: usize, spr: Sprite) void {
        self.sprites[id] = spr;
    }

    pub fn adicionarVida(self: *Mundo, id: usize, vida: Vida) void {
        self.vidas[id] = vida;
    }
};

// === SISTEMAS ===
fn sistemaMovimento(mundo: *Mundo, delta: f32) void {
    for (0..mundo.proxima_entidade) |id| {
        if (mundo.posicoes[id]) |*pos| {
            if (mundo.velocidades[id]) |vel| {
                pos.x += vel.vx * delta;
                pos.y += vel.vy * delta;
            }
        }
    }
}

fn sistemaRenderizacao(mundo: *Mundo) void {
    for (0..mundo.proxima_entidade) |id| {
        if (mundo.posicoes[id]) |pos| {
            if (mundo.sprites[id]) |spr| {
                // Aqui você chamaria rl.DrawRectangle ou similar
                _ = pos;
                _ = spr;
            }
        }
    }
}

pub fn main() void {
    var mundo = Mundo{};

    // Criar jogador
    const jogador = mundo.criarEntidade();
    mundo.adicionarPosicao(jogador, .{ .x = 400, .y = 300 });
    mundo.adicionarVelocidade(jogador, .{ .vx = 0, .vy = 0 });
    mundo.adicionarSprite(jogador, .{ .largura = 32, .altura = 32, .cor = 0xFF0000FF });
    mundo.adicionarVida(jogador, .{ .atual = 100, .maxima = 100 });

    // Criar inimigos
    for (0..10) |i| {
        const inimigo = mundo.criarEntidade();
        mundo.adicionarPosicao(inimigo, .{
            .x = @as(f32, @floatFromInt(i)) * 60.0,
            .y = 100,
        });
        mundo.adicionarVelocidade(inimigo, .{ .vx = 1, .vy = 0 });
        mundo.adicionarSprite(inimigo, .{ .largura = 24, .altura = 24, .cor = 0x00FF00FF });
        mundo.adicionarVida(inimigo, .{ .atual = 50, .maxima = 50 });
    }

    // Game loop
    const delta: f32 = 1.0 / 60.0;
    var frame: u32 = 0;
    while (frame < 3600) : (frame += 1) { // 60 segundos a 60fps
        sistemaMovimento(&mundo, delta);
        sistemaRenderizacao(&mundo);
    }
}
```

O layout SoA (Structure of Arrays) usado aqui é amigável ao cache da CPU, o que é crucial para performance em jogos com muitas entidades. Para aprender a medir e validar esses ganhos, veja o tutorial de [profiling e benchmarks em Zig](/tutoriais/zig-profiling-benchmarks/).

## Detecção de Colisão

Implementação básica de AABB (Axis-Aligned Bounding Box):

```zig
const Retangulo = struct {
    x: f32,
    y: f32,
    largura: f32,
    altura: f32,

    pub fn colide(self: Retangulo, outro: Retangulo) bool {
        return self.x < outro.x + outro.largura and
            self.x + self.largura > outro.x and
            self.y < outro.y + outro.altura and
            self.y + self.altura > outro.y;
    }

    pub fn contem(self: Retangulo, px: f32, py: f32) bool {
        return px >= self.x and
            px <= self.x + self.largura and
            py >= self.y and
            py <= self.y + self.altura;
    }
};

// Uso
const jogador_rect = Retangulo{ .x = 100, .y = 200, .largura = 32, .altura = 32 };
const inimigo_rect = Retangulo{ .x = 110, .y = 210, .largura = 24, .altura = 24 };

if (jogador_rect.colide(inimigo_rect)) {
    // Processar colisão!
}
```

## Timer e Animação

Controle de tempo para animações de sprites:

```zig
const AnimacaoSprite = struct {
    frames: []const rl.Rectangle,
    frame_atual: usize = 0,
    tempo_por_frame: f32,
    tempo_acumulado: f32 = 0,

    pub fn atualizar(self: *AnimacaoSprite, delta: f32) void {
        self.tempo_acumulado += delta;
        if (self.tempo_acumulado >= self.tempo_por_frame) {
            self.tempo_acumulado -= self.tempo_por_frame;
            self.frame_atual = (self.frame_atual + 1) % self.frames.len;
        }
    }

    pub fn frameAtual(self: AnimacaoSprite) rl.Rectangle {
        return self.frames[self.frame_atual];
    }
};
```

## Jogos e Engines Notáveis em Zig

O ecossistema de gamedev em Zig está crescendo. Alguns projetos notáveis:

- **Mach Engine**: Engine completa escrita em Zig, com rendering WebGPU e ECS integrado.
- **Zig Gamedev**: Coleção de bibliotecas de gamedev para Zig, incluindo math, physics e rendering.
- **Pacman.zig**: Implementação completa de Pac-Man em Zig como projeto educacional.
- **Cosmic**: Motor de busca semântico que demonstra a capacidade de Zig para projetos de alta performance.
- **Bun**: Embora não seja um jogo, demonstra como Zig pode ser usado para construir software de altíssima performance.

A comunidade de gamedev em Zig é ativa e acolhedora, com projetos open-source que servem como excelentes referências de aprendizado.

## Dicas de Performance para Game Devs

1. **Use arena allocators**: Aloque dados temporários de cada frame em uma arena e resete no final.
2. **Prefira SoA sobre AoS**: Structure of Arrays é melhor para performance de cache.
3. **Aproveite SIMD**: Zig tem suporte embutido a [operações vetoriais SIMD](/tutoriais/zig-simd-guide/) para cálculos de física e rendering.
4. **Comptime para lookup tables**: Gere tabelas de seno/cosseno em tempo de compilação.
5. **Evite alocações no hot path**: Pré-aloque buffers e reutilize memória.

```zig
// Tabela de seno gerada em tempo de compilação
const SIN_TABLE_SIZE = 360;
const sin_table = blk: {
    var table: [SIN_TABLE_SIZE]f32 = undefined;
    for (0..SIN_TABLE_SIZE) |i| {
        const angulo = @as(f32, @floatFromInt(i)) * std.math.pi / 180.0;
        table[i] = @sin(angulo);
    }
    break :blk table;
};

// Uso: sin_table[angulo_em_graus] é instantâneo, sem cálculo em runtime
```

## Conclusão

Zig está se posicionando como uma alternativa moderna ao C para desenvolvimento de jogos. Com performance nativa, gerenciamento de memória flexível, cross-compilation embutida e excelente interoperabilidade com bibliotecas C, Zig oferece tudo que um game dev precisa. O ecossistema ainda é jovem comparado ao C++ ou C#, mas está crescendo rapidamente com projetos como Mach Engine e a adoção por projetos de alta visibilidade como o Bun. Outra linguagem que tem ganhado espaço em game dev é <a href="https://rustlang.com.br/artigos/rust-game-dev/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'rustlang.com.br' })">Rust, com engines como Bevy e Fyrox</a>.

## Leia Também

- [Zig para Interfaces Gráficas (GUI)](/tutoriais/zig-gui-interfaces-graficas)
- [Guia de SIMD em Zig para Alta Performance](/tutoriais/zig-simd-guide)
- [Zig Build System: Guia Completo](/tutoriais/zig-build-system)
