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:
| Biblioteca | Vantagem | Ideal Para |
|---|---|---|
| raylib | Simples, tudo-em-um | Prototipagem e jogos 2D |
| SDL2 | Maduro, amplamente usado | Jogos 2D/3D serios |
| OpenGL/Vulkan | Controle total | Engines customizadas |
| Mach Engine | Nativo em Zig | Projetos 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
Janela interativa: Crie uma janela que mude de cor de fundo quando o usuario clica com o mouse.
Sprite simples: Carregue e exiba uma imagem PNG na tela usando raylib (
LoadTexture/DrawTexture).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
- Game Development com Zig — Visao geral
- Zig Build System — Configuracao de projetos
- Zig e Interoperabilidade com C — Usando bibliotecas C
- Zig Cross Compilation — Compilar para multiplas plataformas
Duvidas sobre game dev com Zig? Participe da comunidade Zig Brasil!