Desenvolvimento de jogos em Rust: Bevy, Macroquad e mais
Muita gente aprende Rust porque quer fazer um jogo. Que ótimo motivo! Jogos precisam de velocidade, de cuidado com a memória e de um código que não trave no meio da luta contra o chefão, e é exatamente nisso que o Rust é bom.
Esta página mostra um retrato honesto: o que as pessoas já constroem com Rust, qual engine ou biblioteca escolher e as duas ideias que quase todo jogo usa, o game loop (laço do jogo) e o ECS. Depois você vai ver um primeiro programa em Bevy e em Macroquad.
Qual exemplo roda onde? O game loop, o pequeno mundo ECS e o exercício de prática são Rust puro, então têm um botão “Experimente você mesmo” para o Rust Playground. Bevy e Macroquad abrem uma janela, então esses exemplos são para o seu próprio computador, com cargo run. O exemplo com Macroquad foi compilado (inclusive para a web) enquanto esta página era escrita; o exemplo com Bevy segue os exemplos oficiais do Bevy.
A situação real do desenvolvimento de jogos em Rust
Seção intitulada “A situação real do desenvolvimento de jogos em Rust”O site da comunidade Are We Game Yet? (em inglês) acompanha o ecossistema de jogos em Rust. A resposta dele para a própria pergunta é: “Almost. We have the blocks, bring your own glue.” (“Quase. Nós temos as peças, traga a sua própria cola.”) É um bom resumo.
O que já é bom hoje:
- Jogos de verdade são lançados. Tiny Glade, um jogo aconchegante de construir castelos no Steam, e Veloren, um RPG de voxels de código aberto, aparecem no Are We Game Yet como jogos feitos em Rust.
- Uma engine forte. O Bevy tem uma comunidade grande e ativa, e uma versão nova a cada poucos meses.
- Muitas peças prontas. O Are We Game Yet lista crates (pacotes) para física, áudio, entrada, rede, interface e muito mais.
- O seu jogo pode rodar na web. O Rust compila para WebAssembly, então muitos jogos em Rust também rodam no navegador.
O que ainda é difícil:
- Ainda não existe um “Unity do Rust”. A maioria das engines em Rust é voltada para código. Existem editores visuais (o Fyrox tem um, e o Godot pode usar Rust), mas eles são mais novos do que as grandes ferramentas comerciais.
- As coisas mudam. O Bevy ainda não chegou à versão 1.0, e cada versão traz mudanças incompatíveis. Um tutorial do ano passado pode não compilar mais.
- Tempo de compilação. Engines grandes levam minutos no primeiro build. Você vai aprender os truques para isso mais abaixo.
Se o seu objetivo é lançar um jogo 3D comercial no ano que vem, com uma equipe de artistas, Unity, Unreal ou Godot são escolhas mais seguras. Se você quer aprender, fazer jogos 2D, game jams, ferramentas ou simulações, ou simplesmente gosta de Rust, é um lugar muito divertido para estar. 🦀
Qual engine ou biblioteca?
Seção intitulada “Qual engine ou biblioteca?”Existem dois tipos de ferramenta. Uma engine (motor de jogo) entrega uma estrutura completa: como funcionam os objetos do jogo, as cenas e os assets. Uma biblioteca (ou framework) entrega uma janela, desenho, som e entrada, e o resto você organiza do seu jeito.
| Crate | O que é | Bom para | O que saber |
|---|---|---|---|
bevy (0.20) |
Uma engine 2D e 3D completa, construída em torno do ECS | Projetos maiores, 3D, aprender como as engines modernas funcionam | A engine em Rust mais conhecida. Mudanças incompatíveis a cada versão; primeiro build demorado |
macroquad (0.4) |
Uma biblioteca 2D simples, inspirada na raylib | O seu primeiro jogo, game jams, jogos para a web | Pouquíssimas dependências, compila rápido. Você chama funções de desenho dentro de um laço |
ggez (0.10) |
Um framework 2D leve, inspirado no LÖVE | Jogos 2D com uma estrutura clara | Você implementa uma trait EventHandler com update e draw |
fyrox (1.0) |
Uma engine 2D e 3D com editor de cenas | Quem quer um editor, como no Godot ou no Unity | Chegou à versão 1.0 em 2026. Comunidade menor que a do Bevy |
raylib (6.0) |
Bindings em Rust para a biblioteca C raylib | Quem já conhece a raylib do C | Precisa de CMake e de ferramentas de build de C, porque compila a biblioteca C (veja FFI: chamando C) |
godot (0.5, “gdext”) |
Bindings em Rust para a engine Godot 4 | Usar o editor do Godot com a lógica do jogo em Rust | Você escreve classes em Rust que o Godot carrega como uma GDExtension |
Uma regra simples: comece com o Macroquad no seu primeiro jogo 2D. Escolha o Bevy quando quiser uma engine completa e estiver disposto a aprender ECS. Escolha o Godot com gdext ou o Fyrox se quiser um editor para posicionar as coisas visualmente.
O game loop
Seção intitulada “O game loop”Quase todo jogo é um grande laço que roda de novo e de novo até você sair:
- Ler a entrada (teclas, mouse, controle).
- Atualizar o jogo: mover as coisas, verificar colisões, contar pontos.
- Desenhar a nova imagem na tela.
Uma volta do laço se chama quadro (frame). A 60 quadros por segundo (FPS), cada quadro tem cerca de 16 milissegundos. Mas nem todos os quadros levam o mesmo tempo. O seu computador pode estar ocupado, ou um quadro pode precisar carregar um arquivo.
Se o seu jogo move o jogador “2 unidades a cada quadro”, ele roda mais rápido em um computador rápido e mais devagar em um lento. Uma solução popular é o fixed timestep (passo de tempo fixo): a lógica do jogo sempre avança em passos iguais (aqui, de 10 ms), e cada quadro roda quantos passos couberem no tempo que passou. O tempo que sobra espera em um acumulador até o próximo quadro.
const STEP_MS: u32 = 10; // a lógica do jogo sempre avança 10 ms de cada vezconst SPEED: i32 = 2; // o jogador anda 2 unidades a cada atualização
fn main() { // Quanto tempo cada quadro levou de verdade. Um jogo real mede isso com um relógio. let frame_times = [16, 16, 45, 5, 16, 33];
let mut accumulator = 0; // tempo que já passou, mas ainda não foi simulado let mut position = 0;
for (frame, elapsed) in frame_times.into_iter().enumerate() { accumulator += elapsed;
// Roda quantos passos fixos couberem no tempo que temos let mut updates = 0; while accumulator >= STEP_MS { position += SPEED; // update(): a lógica do jogo accumulator -= STEP_MS; updates += 1; }
// render(): desenha uma vez por quadro, aconteça o que acontecer println!("quadro {frame}: {elapsed:>2} ms, {updates} atualização(ões), jogador em {position:>2}, sobram {accumulator} ms"); }}Saída:
quadro 0: 16 ms, 1 atualização(ões), jogador em 2, sobram 6 msquadro 1: 16 ms, 2 atualização(ões), jogador em 6, sobram 2 msquadro 2: 45 ms, 4 atualização(ões), jogador em 14, sobram 7 msquadro 3: 5 ms, 1 atualização(ões), jogador em 16, sobram 2 msquadro 4: 16 ms, 1 atualização(ões), jogador em 18, sobram 8 msquadro 5: 33 ms, 4 atualização(ões), jogador em 26, sobram 1 msOlhe o quadro 2: ele foi lento (45 ms), então o jogo rodou 4 atualizações para recuperar o atraso. No total passaram 131 ms, e o jogo rodou 13 atualizações de 10 ms. O jogador termina em 26, não importa como o tempo foi dividido em quadros. Isso deixa a física estável e igual em qualquer computador, o que também importa para replays e jogos online. Um perigo: se um quadro for muito lento, o jogo roda muitas atualizações para recuperar o atraso, e isso deixa o próximo quadro lento também. Por isso os laços de verdade também limitam o número de atualizações por quadro (o relógio de jogo do Bevy, por exemplo, avança no máximo 250 ms por quadro, por padrão).
A outra forma comum é o delta time: você mede os segundos desde o último quadro e multiplica as velocidades por esse valor (x += speed * dt). É mais simples, e é o que os exemplos com Bevy e Macroquad mais abaixo fazem. Muitas engines usam os dois: delta time para animação e passo fixo para a física.
O que é ECS: entidades, componentes, sistemas
Seção intitulada “O que é ECS: entidades, componentes, sistemas”Em muitas linguagens, você criaria uma classe Player, uma classe Enemy que herda de Character, e assim por diante. Isso fica bagunçado rápido: e um inimigo que também pode ser pego do chão? A maioria das engines de jogos em Rust usa ECS (Entity Component System, ou sistema de entidades e componentes):
- Uma entidade é só um ID, como um número. Ela não tem dados nem comportamento próprios.
- Um componente é um pedaço de dados simples que você prende a uma entidade:
Position,Velocity,Health. Em Rust, eles são structs ou enums normais. - Um sistema é uma função que roda em todas as entidades que têm certos componentes. Um sistema de movimento, por exemplo, trabalha com tudo o que tem ao mesmo tempo uma
Positione umaVelocity.
Uma árvore tem Position, mas não tem Velocity, então o sistema de movimento a ignora. Quer que a árvore se mova? Dê a ela uma Velocity. Você não muda classe nenhuma. Dados e comportamento ficam separados, e isso também combina bem com as regras de borrowing (empréstimo) do Rust.
Aqui está um mundo ECS minúsculo em Rust puro. Cada tipo de componente mora no seu próprio vetor, e o ID da entidade é o índice. None significa “esta entidade não tem esse componente” (veja Option em Rust). Os textos impressos ficaram em inglês porque {:<6} alinha os nomes em colunas, e nomes traduzidos de tamanhos diferentes quebrariam esse alinhamento.
struct Position { x: i32, y: i32 }struct Velocity { dx: i32, dy: i32 }
// Uma entidade é só um ID: um índice em cada lista de componentestype Entity = usize;
#[derive(Default)]struct World { names: Vec<&'static str>, positions: Vec<Option<Position>>, velocities: Vec<Option<Velocity>>,}
impl World { fn spawn(&mut self, name: &'static str, pos: Option<Position>, vel: Option<Velocity>) -> Entity { self.names.push(name); self.positions.push(pos); self.velocities.push(vel); self.names.len() - 1 }}
// Um sistema: trabalha com toda entidade que tem uma Position E uma Velocityfn movement_system(world: &mut World) { for (pos, vel) in world.positions.iter_mut().zip(&world.velocities) { if let (Some(pos), Some(vel)) = (pos, vel) { pos.x += vel.dx; pos.y += vel.dy; } }}
// Outro sistema: imprime toda entidade que tem uma Positionfn print_system(world: &World, tick: u32) { println!("tick {tick}:"); for (id, pos) in world.positions.iter().enumerate() { if let Some(pos) = pos { println!(" #{id} {:<6} at ({}, {})", world.names[id], pos.x, pos.y); } }}
fn main() { let mut world = World::default(); world.spawn("player", Some(Position { x: 0, y: 0 }), Some(Velocity { dx: 1, dy: 0 })); world.spawn("tree", Some(Position { x: 5, y: 2 }), None); // sem Velocity: nunca se move world.spawn("arrow", Some(Position { x: 0, y: 3 }), Some(Velocity { dx: 3, dy: -1 }));
for tick in 1..=2 { movement_system(&mut world); print_system(&world, tick); }}Saída:
tick 1: #0 player at (1, 0) #1 tree at (5, 2) #2 arrow at (3, 2)tick 2: #0 player at (2, 0) #1 tree at (5, 2) #2 arrow at (6, 1)A árvore (tree) fica em (5, 2) porque não tem Velocity. Ninguém escreveu “árvores não se movem”; isso simplesmente resulta dos dados. As bibliotecas de ECS de verdade são muito mais rápidas e espertas do que isto (elas guardam os componentes de um jeito que a CPU lê rapidinho e rodam os sistemas em paralelo), mas a ideia é a mesma.
Seu primeiro app com Bevy
Seção intitulada “Seu primeiro app com Bevy”O Bevy é a engine em Rust mais conhecida, e tudo nele é ECS. A versão 0.20 saiu em outubro de 2026. O Bevy precisa do Rust stable mais recente, então atualize primeiro e depois crie um projeto:
rustup updatecargo new my_bevy_gamecd my_bevy_gamecargo add bevyNo Linux, você também precisa de algumas bibliotecas do sistema (som, entrada, gráficos). O guia de instalação do Bevy (em inglês) lista quais são para cada distribuição. No Windows, você precisa das Visual Studio C++ Build Tools, e no macOS, das ferramentas de linha de comando do Xcode.
Este programa é baseado no exemplo oficial 2d/move_sprite do Bevy. Ele desenha um quadrado laranja que desliza para a esquerda e para a direita. (O exemplo oficial carrega uma imagem; aqui usamos um quadrado de cor sólida, para você não precisar de nenhum arquivo de imagem.)
use bevy::prelude::*;
fn main() { App::new() .add_plugins(DefaultPlugins) // janela, renderização, entrada, áudio... .add_systems(Startup, setup) // roda uma vez, no início .add_systems(Update, sprite_movement) // roda a cada quadro .run();}
// Um componente: para que lado esta entidade está se movendo#[derive(Component)]enum Direction { Left, Right,}
fn setup(mut commands: Commands) { commands.spawn(Camera2d);
// Cria uma entidade com três componentes commands.spawn(( Sprite::from_color(Color::srgb(1.0, 0.5, 0.2), Vec2::new(50.0, 50.0)), Transform::from_xyz(0.0, 0.0, 0.0), Direction::Right, ));}
// Um sistema: roda em toda entidade que tem uma Direction E um Transformfn sprite_movement(time: Res<Time>, mut sprite_position: Query<(&mut Direction, &mut Transform)>) { for (mut logo, mut transform) in &mut sprite_position { match *logo { Direction::Right => transform.translation.x += 150. * time.delta_secs(), Direction::Left => transform.translation.x -= 150. * time.delta_secs(), }
if transform.translation.x > 200. { *logo = Direction::Left; } else if transform.translation.x < -200. { *logo = Direction::Right; } }}Compare com o mundo ECS minúsculo lá de cima. #[derive(Component)] transforma um enum normal em um componente. commands.spawn((...)) cria uma entidade com uma tupla de componentes. E Query<(&mut Direction, &mut Transform)> pede ao Bevy “toda entidade que tenha os dois”, exatamente como o nosso movement_system fazia com zip. time.delta_secs() é o delta time da seção do game loop, então o quadrado anda 150 pixels por segundo em qualquer computador.
Rode com cargo run. O primeiro build compila centenas de crates e pode levar vários minutos, então tenha paciência. Depois disso, os novos builds são bem mais rápidos.
Deixe o Bevy compilar (e rodar) mais rápido
Seção intitulada “Deixe o Bevy compilar (e rodar) mais rápido”O guia de instalação do Bevy recomenda duas coisas. Primeiro, acrescente isto ao Cargo.toml. Isso otimiza o Bevy e as suas outras dependências, mas deixa o seu próprio código rápido de compilar:
[dependencies]bevy = "0.20"
# Ativa um pouco de otimização no perfil dev.[profile.dev]opt-level = 1
# Ativa bastante otimização no perfil dev para as dependências.[profile.dev.package."*"]opt-level = 3Sem isso, um build de debug de um jogo pode ficar muito lento: poucos quadros por segundo e telas de carregamento longas. As dependências são compiladas uma vez só, então você paga pela otimização apenas no primeiro build. (Os perfis são explicados em Desempenho em Rust.)
Segundo, enquanto estiver desenvolvendo, ative a feature dynamic_linking. Assim o Bevy é compilado como uma biblioteca compartilhada separada, e o Cargo não precisa ligar o Bevy inteiro ao seu programa de novo a cada mudança:
cargo run --features bevy/dynamic_linkingDois avisos do guia do Bevy: no Windows, dynamic_linking só funciona junto com as configurações de opt-level acima. E não lance o seu jogo com ela: o jogo precisaria de um arquivo de biblioteca extra ao lado dele. Compile a versão final sem essa feature. O guia também mostra como usar um linker mais rápido (lld ou mold), o que ajuda muito no Linux.
Uma janela com Macroquad
Seção intitulada “Uma janela com Macroquad”O Macroquad é bem diferente. Não tem ECS nem construtor de app: você mesmo escreve o game loop e desenha com funções simples, como draw_circle. Ele tem pouquíssimas dependências, então um build do zero leva segundos, e não minutos.
cargo new move_the_ballcd move_the_ballcargo add macroquad[dependencies]macroquad = "0.4"Este joguinho mostra uma bola que você move com as setas do teclado:
use macroquad::prelude::*;
#[macroquad::main("Move the Ball")]async fn main() { let mut player = vec2(screen_width() / 2.0, screen_height() / 2.0); let speed = 250.0; // pixels por segundo
loop { // 1. Entrada: as setas movem o jogador let dt = get_frame_time(); // segundos desde o último quadro if is_key_down(KeyCode::Right) { player.x += speed * dt; } if is_key_down(KeyCode::Left) { player.x -= speed * dt; } if is_key_down(KeyCode::Down) { player.y += speed * dt; } if is_key_down(KeyCode::Up) { player.y -= speed * dt; } if is_key_pressed(KeyCode::Escape) { break; }
// 2. Mantém o jogador dentro da tela player.x = player.x.clamp(0.0, screen_width()); player.y = player.y.clamp(0.0, screen_height());
// 3. Desenha tudo neste quadro clear_background(DARKBLUE); draw_circle(player.x, player.y, 20.0, ORANGE); draw_text("Arrow keys to move, Esc to quit", 20.0, 30.0, 28.0, WHITE);
next_frame().await }}Dá para ver os três passos do game loop: entrada, atualização, desenho. #[macroquad::main("Move the Ball")] abre uma janela com esse título. O main é async, mas você não precisa do Tokio nem de saber async: next_frame().await significa simplesmente “mostre este quadro e espere o próximo”. No Linux, o README do Macroquad lista alguns pacotes do sistema para instalar antes (pkg-config libx11-dev libxi-dev libgl1-mesa-dev libasound2-dev no Ubuntu).
Jogos na web (WebAssembly)
Seção intitulada “Jogos na web (WebAssembly)”Tanto o Macroquad quanto o Bevy conseguem compilar o seu jogo para o navegador, como WebAssembly. Se Wasm é novidade para você, leia Rust e WebAssembly primeiro. Você sempre começa adicionando o target do Wasm:
rustup target add wasm32-unknown-unknownO Macroquad deixa isso muito fácil. Um único comando gera um arquivo .wasm:
cargo build --release --target wasm32-unknown-unknownO arquivo vai parar em target/wasm32-unknown-unknown/release/move_the_ball.wasm (no exemplo acima, ele ficou com uns 600 KB). O README do Macroquad traz um index.html curtinho que o carrega com um pequeno arquivo JavaScript, o mq_js_bundle.js. Coloque os dois arquivos em uma pasta e sirva essa pasta com qualquer servidor web local, por exemplo basic-http-server . (instale com cargo install basic-http-server). Só dar um clique duplo no arquivo HTML normalmente não funciona, porque os navegadores bloqueiam o carregamento do .wasm direto do disco.
O Bevy usa o wasm-bindgen para criar o código de ligação em JavaScript. Estes passos foram adaptados do README de exemplos do Bevy (para o projeto my_bevy_game acima):
cargo install wasm-bindgen-clicargo build --release --target wasm32-unknown-unknownwasm-bindgen --out-dir web --target web target/wasm32-unknown-unknown/release/my_bevy_game.wasmIsso grava um arquivo .js e um _bg.wasm na pasta web; uma pequena página HTML importa o .js e inicia o jogo. O guia de instalação do Bevy também sugere um perfil wasm-release com opt-level = "s" para deixar o download menor. Builds para a web são um ótimo jeito de compartilhar jogos de game jam: as pessoas jogam com um clique, sem instalar nada.
Erros comuns
Seção intitulada “Erros comuns”Mover uma quantidade fixa a cada quadro. Com x += 5.0 a cada quadro, o jogo roda duas vezes mais rápido a 120 FPS do que a 60 FPS. Multiplique pelo delta time (x += speed * dt) ou use um passo de tempo fixo.
Seguir um tutorial antigo de Bevy. O Bevy muda a sua API a cada versão, então um post de blog sobre a 0.12 muitas vezes não compila com a 0.20. Confira qual versão o tutorial usa e prefira os exemplos oficiais da sua versão. Cada versão também tem um guia de migração no site do Bevy.
Builds de debug lentos. Se o seu jogo em Bevy roda a poucos quadros por segundo, você provavelmente esqueceu as configurações de opt-level no Cargo.toml.
Começar grande demais. Um RPG de mundo aberto é um primeiro projeto difícil em qualquer linguagem. Comece com Pong, Snake, Breakout ou Flappy Bird. Termine o jogo e depois faça o próximo um pouco maior.
Prática
Seção intitulada “Prática”1. Estados do jogo com um enum
Seção intitulada “1. Estados do jogo com um enum”Todo jogo tem telas: um menu, o jogo em si, uma tela de pausa, “game over”. Crie um enum GameState com Menu, Playing, Paused e GameOver, e um enum Input com Enter, Escape e PlayerDied. Escreva fn next_state(state: GameState, input: Input) -> GameState: Enter começa o jogo a partir do menu, Escape pausa e tira da pausa, morrer termina o jogo, e Enter no “game over” volta para o menu. Qualquer outra entrada não muda nada. Passe uma lista fixa de entradas e imprima cada mudança. Dica: use match na tupla (state, input) e termine com (same, _) => same.
Ver solução
#[derive(Debug, Clone, Copy)]enum GameState { Menu, Playing, Paused, GameOver,}
#[derive(Debug, Clone, Copy)]enum Input { Enter, Escape, PlayerDied,}
fn next_state(state: GameState, input: Input) -> GameState { match (state, input) { (GameState::Menu, Input::Enter) => GameState::Playing, (GameState::Playing, Input::Escape) => GameState::Paused, (GameState::Paused, Input::Escape) => GameState::Playing, (GameState::Playing, Input::PlayerDied) => GameState::GameOver, (GameState::GameOver, Input::Enter) => GameState::Menu, // Qualquer outra coisa: ignora a entrada e fica onde está (same, _) => same, }}
fn main() { use Input::*; // permite escrever Enter em vez de Input::Enter let inputs = [Escape, Enter, Escape, PlayerDied, Escape, PlayerDied, Enter];
let mut state = GameState::Menu; for input in inputs { let new_state = next_state(state, input); println!("{state:?} + {input:?} -> {new_state:?}"); state = new_state; }}Saída:
Menu + Escape -> MenuMenu + Enter -> PlayingPlaying + Escape -> PausedPaused + PlayerDied -> PausedPaused + Escape -> PlayingPlaying + PlayerDied -> GameOverGameOver + Enter -> MenuFazer match em uma tupla lista todas as mudanças permitidas em um só lugar, e o último braço deixa todas as outras entradas inofensivas. Um jogador pausado não pode morrer, porque (Paused, PlayerDied) cai em (same, _). O Bevy tem essa mesma ideia embutida, chamada States.
- O Rust é uma opção real para jogos: “quase lá”, com uma engine forte (Bevy), bibliotecas simples (Macroquad, ggez), opções com editor (Fyrox, Godot com gdext) e muitas peças prontas.
- Todo jogo roda um laço: entrada, atualização, desenho. Use delta time ou um passo de tempo fixo para que a velocidade não dependa da taxa de quadros.
- O ECS divide um jogo em entidades (IDs), componentes (dados simples) e sistemas (funções que rodam nas entidades que têm certos componentes).
- O Bevy é ECS de ponta a ponta. Acrescente as configurações de
opt-levelnos perfis e usedynamic_linkingsó durante o desenvolvimento. - O Macroquad é o começo mais rápido em 2D: você mesmo escreve o laço e desenha com funções simples.
- Os dois conseguem compilar para o navegador com o target
wasm32-unknown-unknown.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. No ECS, o que é uma entidade?
2. Por que um passo de tempo fixo faz o jogo se comportar igual em qualquer computador?
3. Com qual configuração do Bevy você NÃO deve lançar o seu jogo pronto?
4. Você quer fazer o seu primeiro joguinho 2D, com builds bem rápidos. Qual crate esta página sugere?
Pontuação: 0 / 4
Para saber mais
Seção intitulada “Para saber mais”- Are We Game Yet? (em inglês): a visão geral da comunidade sobre engines de jogos em Rust, bibliotecas de física, áudio e rede, e jogos feitos com Rust.
- Bevy Quick Start: Setup (em inglês): o guia oficial para instalar o Bevy, com as configurações para compilar rápido e os perfis de release e de Wasm.
- Game development in Rust with Macroquad (em inglês): um livro on-line gratuito que constrói passo a passo um jogo de nave espacial completo com Macroquad.
- The godot-rust book (em inglês): como escrever a lógica de jogos para o Godot 4 em Rust com o gdext.
- Game Loop — Game Programming Patterns (em inglês): uma explicação amigável e detalhada sobre game loops, delta time e passos de tempo fixos.