Game Dev com Zig: Setup e Janela Grafica com SDL2 e Raylib

O primeiro passo para desenvolver jogos com Zig e configurar seu ambiente e abrir uma janela grafica. Zig se destaca aqui gracas a sua interoperabilidade nativa com C, que permite usar bibliotecas graficas consagradas como SDL2 e raylib sem wrappers ou bindings complicados. Neste artigo, vamos configurar tudo do zero e exibir nossa primeira janela.

Escolhendo uma Biblioteca Grafica

Para game dev com Zig, as opcoes mais populares sao:

BibliotecaVantagemIdeal Para
raylibSimples, tudo-em-umPrototipagem e jogos 2D
SDL2Maduro, amplamente usadoJogos 2D/3D serios
OpenGL/VulkanControle totalEngines customizadas
Mach EngineNativo em ZigProjetos Zig-first

Para esta serie, usaremos raylib como biblioteca principal por sua simplicidade, e mostraremos exemplos com SDL2 quando relevante.

Setup com Raylib

Criando o Projeto

mkdir meu-jogo
cd meu-jogo
zig init

Configurando build.zig para Raylib

Raylib e particularmente facil de integrar com Zig porque o build system do Zig pode compilar codigo C diretamente:

const std = @import("std");

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

    // Buscar raylib como dependencia
    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"));
    exe.addIncludePath(raylib_dep.path("src"));

    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    run_cmd.step.dependOn(b.getInstallStep());

    const run_step = b.step("run", "Executar o jogo");
    run_step.dependOn(&run_cmd.step);
}

Configurando build.zig.zon

.{
    .name = "meu-jogo",
    .version = "0.1.0",
    .dependencies = .{
        .raylib = .{
            .url = "https://github.com/raysan5/raylib/archive/refs/tags/5.5.tar.gz",
            .hash = "...", // Sera preenchido automaticamente na primeira build
        },
    },
}

Primeira Janela com Raylib

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

pub fn main() void {
    // Configurar janela
    const largura: c_int = 800;
    const altura: c_int = 600;

    rl.InitWindow(largura, altura, "Meu Primeiro Jogo em Zig!");
    defer rl.CloseWindow();

    rl.SetTargetFPS(60);

    // Loop principal
    while (!rl.WindowShouldClose()) {
        // Update (logica do jogo)
        // ...

        // Render
        rl.BeginDrawing();
        rl.ClearBackground(rl.RAYWHITE);

        rl.DrawText("Ola, Game Dev com Zig!", 190, 280, 20, rl.DARKGRAY);
        rl.DrawFPS(10, 10);

        rl.EndDrawing();
    }
}

Execute com zig build run e voce vera sua primeira janela grafica.

Setup com SDL2

SDL2 e mais verboso mas oferece maior controle. Vejamos como integra-lo.

Configurando SDL2 no build.zig

const std = @import("std");

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

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

    // Linkar SDL2 do sistema
    exe.linkSystemLibrary("SDL2");
    exe.linkLibC();

    b.installArtifact(exe);
}

Primeira Janela com SDL2

const std = @import("std");
const c = @cImport({
    @cInclude("SDL2/SDL.h");
});

pub fn main() !void {
    // Inicializar SDL2
    if (c.SDL_Init(c.SDL_INIT_VIDEO) != 0) {
        std.debug.print("Erro ao inicializar SDL: {s}\n", .{c.SDL_GetError()});
        return error.SDLInitFailed;
    }
    defer c.SDL_Quit();

    // Criar janela
    const janela = c.SDL_CreateWindow(
        "Meu Jogo SDL2 + Zig",
        c.SDL_WINDOWPOS_CENTERED,
        c.SDL_WINDOWPOS_CENTERED,
        800,
        600,
        c.SDL_WINDOW_SHOWN,
    ) orelse {
        std.debug.print("Erro ao criar janela: {s}\n", .{c.SDL_GetError()});
        return error.WindowCreationFailed;
    };
    defer c.SDL_DestroyWindow(janela);

    // Criar renderer
    const renderer = c.SDL_CreateRenderer(
        janela,
        -1,
        c.SDL_RENDERER_ACCELERATED | c.SDL_RENDERER_PRESENTVSYNC,
    ) orelse {
        std.debug.print("Erro ao criar renderer: {s}\n", .{c.SDL_GetError()});
        return error.RendererCreationFailed;
    };
    defer c.SDL_DestroyRenderer(renderer);

    // Loop principal
    var rodando = true;
    while (rodando) {
        // Processar eventos
        var evento: c.SDL_Event = undefined;
        while (c.SDL_PollEvent(&evento) != 0) {
            if (evento.type == c.SDL_QUIT) {
                rodando = false;
            }
        }

        // Limpar tela (azul escuro)
        _ = c.SDL_SetRenderDrawColor(renderer, 25, 25, 112, 255);
        _ = c.SDL_RenderClear(renderer);

        // Desenhar retangulo vermelho
        const rect = c.SDL_Rect{ .x = 350, .y = 250, .w = 100, .h = 100 };
        _ = c.SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
        _ = c.SDL_RenderFillRect(renderer, &rect);

        // Apresentar frame
        c.SDL_RenderPresent(renderer);
    }
}

Estrutura de Projeto Recomendada

Para um jogo organizado, recomendamos esta estrutura:

meu-jogo/
├── build.zig
├── build.zig.zon
├── src/
│   ├── main.zig          # Ponto de entrada
│   ├── game.zig          # Logica principal do jogo
│   ├── renderer.zig      # Abstracoes de renderizacao
│   ├── input.zig         # Sistema de input
│   ├── audio.zig         # Sistema de audio
│   ├── physics.zig       # Fisica
│   └── ecs/
│       ├── world.zig     # Mundo ECS
│       ├── entity.zig    # Entidades
│       └── components.zig # Componentes
├── assets/
│   ├── sprites/
│   ├── sounds/
│   └── fonts/
└── tests/
    └── game_test.zig

Modulo de Jogo Base

// src/game.zig
const std = @import("std");
const rl = @cImport(@cInclude("raylib.h"));

pub const Config = struct {
    titulo: [*:0]const u8 = "Meu Jogo",
    largura: c_int = 800,
    altura: c_int = 600,
    fps: c_int = 60,
    fullscreen: bool = false,
};

pub const Game = struct {
    config: Config,
    rodando: bool = true,
    delta_time: f32 = 0,
    frame_count: u64 = 0,

    pub fn init(config: Config) Game {
        rl.InitWindow(config.largura, config.altura, config.titulo);
        rl.SetTargetFPS(config.fps);

        if (config.fullscreen) {
            rl.ToggleFullscreen();
        }

        return .{
            .config = config,
        };
    }

    pub fn deinit(self: *Game) void {
        _ = self;
        rl.CloseWindow();
    }

    pub fn rodar(self: *Game) void {
        while (self.rodando and !rl.WindowShouldClose()) {
            self.delta_time = rl.GetFrameTime();
            self.frame_count += 1;

            self.update();
            self.render();
        }
    }

    fn update(self: *Game) void {
        // Logica do jogo aqui
        if (rl.IsKeyPressed(rl.KEY_ESCAPE)) {
            self.rodando = false;
        }
    }

    fn render(self: *Game) void {
        rl.BeginDrawing();
        defer rl.EndDrawing();

        rl.ClearBackground(rl.BLACK);
        rl.DrawFPS(10, 10);

        var buf: [64]u8 = undefined;
        const texto = std.fmt.bufPrint(&buf, "Frame: {d}", .{self.frame_count}) catch "???";
        rl.DrawText(@ptrCast(texto.ptr), 10, 30, 16, rl.GREEN);
    }
};
// src/main.zig
const Game = @import("game.zig").Game;

pub fn main() void {
    var jogo = Game.init(.{
        .titulo = "Aventura Zig",
        .largura = 1024,
        .altura = 768,
    });
    defer jogo.deinit();

    jogo.rodar();
}

Tipos Matematicos Essenciais

Para game dev, voce precisa de vetores e operacoes basicas:

pub const Vec2 = struct {
    x: f32 = 0,
    y: f32 = 0,

    pub fn add(self: Vec2, other: Vec2) Vec2 {
        return .{ .x = self.x + other.x, .y = self.y + other.y };
    }

    pub fn scale(self: Vec2, s: f32) Vec2 {
        return .{ .x = self.x * s, .y = self.y * s };
    }

    pub fn magnitude(self: Vec2) f32 {
        return @sqrt(self.x * self.x + self.y * self.y);
    }

    pub fn normalize(self: Vec2) Vec2 {
        const mag = self.magnitude();
        if (mag == 0) return .{};
        return .{ .x = self.x / mag, .y = self.y / mag };
    }

    pub fn distance(self: Vec2, other: Vec2) f32 {
        const dx = self.x - other.x;
        const dy = self.y - other.y;
        return @sqrt(dx * dx + dy * dy);
    }
};

Exercicios

  1. Janela interativa: Crie uma janela que mude de cor de fundo quando o usuario clica com o mouse.

  2. Sprite simples: Carregue e exiba uma imagem PNG na tela usando raylib (LoadTexture/DrawTexture).

  3. Resolucao adaptavel: Implemente um sistema que ajuste a renderizacao automaticamente para diferentes resolucoes de tela.


Proximo Artigo

No proximo artigo, implementamos o game loop e rendering, explorando fixed timestep, renderizacao de sprites e gerenciamento de assets.

Conteudo Relacionado


Duvidas sobre game dev com Zig? Participe da comunidade Zig Brasil!

Continue aprendendo Zig

Explore mais tutoriais e artigos em português para dominar a linguagem Zig.