Sviluppo di giochi in Rust: Bevy, Macroquad e altro
Tante persone imparano Rust perché vogliono fare un gioco. È un ottimo motivo! I giochi hanno bisogno di velocità, di un uso attento della memoria e di codice che non va in crash nel mezzo di uno scontro con il boss finale, e sono proprio le cose in cui Rust eccelle.
Questa pagina ti dà un quadro onesto: cosa si costruisce già con Rust, quale engine o libreria scegliere e le due idee che usa quasi ogni gioco, il game loop e l’ECS. Poi vedrai un primo programma in Bevy e in Macroquad.
Quali esempi girano dove? Il game loop, il minuscolo mondo ECS e l’esercizio pratico sono Rust puro, quindi hanno un pulsante «Provalo tu» per il Rust Playground. Bevy e Macroquad aprono una finestra, quindi quegli esempi sono da provare sul tuo computer con cargo run. L’esempio Macroquad è stato compilato (anche per il web) mentre scrivevamo questa pagina; l’esempio Bevy segue gli esempi ufficiali di Bevy.
Lo stato onesto dello sviluppo di giochi in Rust
Sezione intitolata “Lo stato onesto dello sviluppo di giochi in Rust”Il sito della community Are We Game Yet? (in inglese) tiene traccia dell’ecosistema dei giochi in Rust. La sua risposta alla domanda del titolo è: «Almost. We have the blocks, bring your own glue.», cioè «Quasi. I mattoni ci sono, la colla portala tu». È un riassunto giusto.
Cosa va bene oggi:
- Giochi veri escono davvero. Tiny Glade, un rilassante gioco di costruzione di castelli su Steam, e Veloren, un GDR voxel open source, sono entrambi elencati su Are We Game Yet come giochi in Rust.
- Un engine solido. Bevy ha una community grande e attiva e una nuova release ogni pochi mesi.
- Tanti mattoni. Are We Game Yet elenca crate per fisica, audio, input, rete, interfaccia e altro.
- Il tuo gioco può girare sul web. Rust compila in WebAssembly, quindi tanti giochi in Rust girano anche nel browser.
Cosa è ancora difficile:
- Non c’è ancora uno «Unity di Rust». La maggior parte degli engine Rust si usa scrivendo codice. Esistono editor visuali (Fyrox ne ha uno, e Godot può usare Rust), ma sono più giovani dei grandi strumenti commerciali.
- Le cose cambiano. Bevy è ancora prima della 1.0, e ogni release porta modifiche incompatibili. Un tutorial dell’anno scorso potrebbe non compilare più.
- I tempi di compilazione. I grandi engine richiedono minuti per la prima build. Più sotto impari i trucchi per ridurli.
Se il tuo obiettivo è pubblicare l’anno prossimo un gioco 3D commerciale con un team di artisti, Unity, Unreal o Godot sono scelte più sicure. Se vuoi imparare, fare giochi 2D, game jam, tool o simulazioni, o semplicemente ti piace Rust, è un posto molto divertente in cui stare. 🦀
Quale engine o libreria?
Sezione intitolata “Quale engine o libreria?”Ci sono due tipi di strumenti. Un engine ti dà una struttura completa: come funzionano gli oggetti di gioco, le scene e gli asset. Una libreria (o framework) ti dà una finestra, il disegno, il suono e l’input, e il resto lo organizzi tu.
| Crate | Cos’è | Adatto a | Da sapere |
|---|---|---|---|
bevy (0.20) |
Un engine completo 2D e 3D costruito attorno all’ECS | Progetti più grandi, 3D, capire come funzionano gli engine moderni | L’engine Rust più conosciuto. Modifiche incompatibili a ogni release; prima build lunga |
macroquad (0.4) |
Una semplice libreria 2D, ispirata a raylib | Il tuo primo gioco, game jam, giochi web | Pochissime dipendenze, compila in fretta. Chiami funzioni di disegno in un ciclo |
ggez (0.10) |
Un framework 2D leggero, ispirato a LÖVE | Giochi 2D con una struttura chiara | Implementi un trait EventHandler con update e draw |
fyrox (1.0) |
Un engine 2D e 3D con un editor di scene | Chi vuole un editor, come in Godot o Unity | Ha raggiunto la versione 1.0 nel 2026. Community più piccola di Bevy |
raylib (6.0) |
Binding Rust per la libreria C raylib | Chi conosce già raylib dal C | Richiede CMake e gli strumenti di build per il C, perché compila la libreria C (vedi FFI) |
godot (0.5, «gdext») |
Binding Rust per l’engine Godot 4 | Usare l’editor di Godot con la logica di gioco in Rust | Scrivi classi Rust che Godot carica come GDExtension |
Una regola semplice: inizia con Macroquad per il tuo primo gioco 2D. Scegli Bevy quando vuoi un engine completo e ti va di imparare l’ECS. Scegli Godot con gdext o Fyrox se vuoi un editor per posizionare le cose visivamente.
Il game loop
Sezione intitolata “Il game loop”Quasi ogni gioco è un unico grande ciclo che gira di continuo finché non esci:
- Leggi l’input (tastiera, mouse, controller).
- Aggiorna il gioco: muovi le cose, controlla le collisioni, conta i punti.
- Disegna la nuova immagine sullo schermo.
Un giro del ciclo si chiama frame. A 60 frame al secondo (FPS), ogni frame ha circa 16 millisecondi. Ma non tutti i frame durano uguale. Il computer potrebbe essere occupato, o un frame potrebbe dover caricare un file.
Se il tuo gioco sposta il giocatore di «2 unità a ogni frame», va più veloce su un computer veloce e più lento su uno lento. Una soluzione molto usata è il fixed timestep (passo fisso): la logica di gioco avanza sempre a passi uguali (qui 10 ms), e ogni frame esegue tutti i passi che stanno nel tempo trascorso. Il tempo avanzato aspetta in un accumulatore fino al frame successivo.
const STEP_MS: u32 = 10; // la logica di gioco avanza sempre di 10 ms alla voltaconst SPEED: i32 = 2; // il giocatore si sposta di 2 unità a ogni aggiornamento
fn main() { // Quanto è durato davvero ogni frame. Un gioco vero lo misura con un orologio. let frame_times = [16, 16, 45, 5, 16, 33];
let mut accumulator = 0; // tempo trascorso ma non ancora simulato let mut position = 0;
for (frame, elapsed) in frame_times.into_iter().enumerate() { accumulator += elapsed;
// Esegui tutti i passi fissi che stanno nel tempo che abbiamo let mut updates = 0; while accumulator >= STEP_MS { position += SPEED; // update(): logica di gioco accumulator -= STEP_MS; updates += 1; }
// render(): disegna una volta per frame, qualunque cosa sia successa println!("frame {frame}: {elapsed:>2} ms, aggiornamenti: {updates}, giocatore a {position:>2}, avanzano {accumulator} ms"); }}Output:
frame 0: 16 ms, aggiornamenti: 1, giocatore a 2, avanzano 6 msframe 1: 16 ms, aggiornamenti: 2, giocatore a 6, avanzano 2 msframe 2: 45 ms, aggiornamenti: 4, giocatore a 14, avanzano 7 msframe 3: 5 ms, aggiornamenti: 1, giocatore a 16, avanzano 2 msframe 4: 16 ms, aggiornamenti: 1, giocatore a 18, avanzano 8 msframe 5: 33 ms, aggiornamenti: 4, giocatore a 26, avanzano 1 msGuarda il frame 2: è stato lento (45 ms), quindi il gioco ha eseguito 4 aggiornamenti per recuperare. In totale sono passati 131 ms, e il gioco ha eseguito 13 aggiornamenti da 10 ms. Il giocatore finisce a 26 indipendentemente da come il tempo è stato diviso in frame. Così la fisica è stabile e uguale su ogni computer, il che conta anche per i replay e i giochi online. Un pericolo: se un frame è molto lento, il gioco esegue tanti aggiornamenti per recuperare, e anche il frame successivo diventa lento. Per questo i cicli veri limitano anche il numero di aggiornamenti per frame (l’orologio di gioco di Bevy, per esempio, avanza al massimo di 250 ms per frame di default).
L’altro metodo comune è il delta time: misuri i secondi passati dall’ultimo frame e ci moltiplichi le velocità (x += speed * dt). È più semplice, ed è quello che fanno gli esempi Bevy e Macroquad più sotto. Molti engine usano entrambi: delta time per le animazioni, passo fisso per la fisica.
Cos’è l’ECS: entità, componenti, sistemi
Sezione intitolata “Cos’è l’ECS: entità, componenti, sistemi”In molti linguaggi creeresti una classe Player, una classe Enemy che eredita da Character, e così via. Diventa presto un groviglio: e un nemico che si può anche raccogliere? La maggior parte degli engine di gioco in Rust usa invece l’ECS (Entity Component System):
- Un’entità (entity) è solo un ID, come un numero. Non ha dati né comportamento propri.
- Un componente (component) è un pezzo di dati semplici che attacchi a un’entità:
Position,Velocity,Health. In Rust sono normali struct o enum. - Un sistema (system) è una funzione che gira su ogni entità che ha certi componenti. Un sistema di movimento, per esempio, lavora su tutto ciò che ha sia una
Positionsia unaVelocity.
Un albero ha una Position ma non una Velocity, quindi il sistema di movimento lo salta. Vuoi che l’albero si muova? Dagli una Velocity. Non cambi nessuna classe. Dati e comportamento restano separati, e questo si sposa bene anche con le regole di borrowing di Rust.
Ecco un minuscolo mondo ECS in Rust puro. Ogni tipo di componente vive nel suo vettore, e l’ID dell’entità è l’indice. None significa «questa entità non ha quel componente» (vedi Option).
struct Position { x: i32, y: i32 }struct Velocity { dx: i32, dy: i32 }
// Un'entità è solo un ID: un indice in ogni lista di componentitype 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 }}
// Un sistema: lavora su ogni entità che ha SIA una Position SIA una 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; } }}
// Un altro sistema: stampa ogni entità che ha una 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} {:<9} in ({}, {})", world.names[id], pos.x, pos.y); } }}
fn main() { let mut world = World::default(); world.spawn("giocatore", Some(Position { x: 0, y: 0 }), Some(Velocity { dx: 1, dy: 0 })); world.spawn("albero", Some(Position { x: 5, y: 2 }), None); // niente Velocity: non si muove mai world.spawn("freccia", 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); }}Output:
tick 1: #0 giocatore in (1, 0) #1 albero in (5, 2) #2 freccia in (3, 2)tick 2: #0 giocatore in (2, 0) #1 albero in (5, 2) #2 freccia in (6, 1)L’albero resta in (5, 2) perché non ha una Velocity. Nessuno ha scritto «gli alberi non si muovono»: deriva semplicemente dai dati. Le vere librerie ECS sono molto più veloci e furbe di questa (salvano i componenti in modo che la CPU li legga in fretta, ed eseguono i sistemi in parallelo), ma l’idea è la stessa.
La tua prima app Bevy
Sezione intitolata “La tua prima app Bevy”Bevy è l’engine Rust più conosciuto, e al suo interno tutto è ECS. La versione 0.20 è uscita a ottobre 2026. Bevy ha bisogno dell’ultima versione stabile di Rust, quindi prima aggiorna, poi crea un progetto:
rustup updatecargo new my_bevy_gamecd my_bevy_gamecargo add bevySu Linux ti servono anche alcune librerie di sistema (suono, input, grafica). La guida di installazione (in inglese) di Bevy le elenca per ogni distribuzione. Su Windows ti servono i Visual Studio C++ Build Tools, e su macOS gli strumenti da riga di comando di Xcode.
Questo programma si basa sull’esempio ufficiale 2d/move_sprite di Bevy. Disegna un quadrato arancione che scivola a destra e a sinistra. (L’esempio ufficiale carica un’immagine; qui usiamo un semplice quadrato colorato, così non ti serve nessun file di immagine.)
use bevy::prelude::*;
fn main() { App::new() .add_plugins(DefaultPlugins) // finestra, rendering, input, audio... .add_systems(Startup, setup) // gira una volta all'avvio .add_systems(Update, sprite_movement) // gira a ogni frame .run();}
// Un componente: in che direzione si muove questa entità#[derive(Component)]enum Direction { Left, Right,}
fn setup(mut commands: Commands) { commands.spawn(Camera2d);
// Crea un'entità con tre componenti 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, ));}
// Un sistema: gira su ogni entità che ha una Direction E una 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; } }}Confrontalo con il minuscolo mondo ECS di prima. #[derive(Component)] trasforma un normale enum in un componente. commands.spawn((...)) crea un’entità con una tupla di componenti. E Query<(&mut Direction, &mut Transform)> chiede a Bevy «tutte le entità che hanno entrambi», proprio come faceva il nostro movement_system con zip. time.delta_secs() è il delta time della sezione sul game loop, quindi il quadrato si sposta di 150 pixel al secondo su qualsiasi computer.
Eseguilo con cargo run. La prima build compila centinaia di crate e può richiedere diversi minuti, quindi abbi pazienza. Dopo, le ricompilazioni sono molto più veloci.
Far compilare (e girare) Bevy più in fretta
Sezione intitolata “Far compilare (e girare) Bevy più in fretta”La guida di installazione di Bevy consiglia due cose. Primo, aggiungi questo a Cargo.toml. Ottimizza Bevy e le altre dipendenze, ma lascia veloce la compilazione del tuo codice:
[dependencies]bevy = "0.20"
# Un po' di ottimizzazione nel profilo dev.[profile.dev]opt-level = 1
# Molta ottimizzazione nel profilo dev per le dipendenze.[profile.dev.package."*"]opt-level = 3Senza questo, una build di debug di un gioco può essere molto lenta: pochi frame al secondo e caricamenti lunghi. Le dipendenze si compilano una volta sola, quindi paghi l’ottimizzazione solo alla prima build. (I profili sono spiegati in Prestazioni.)
Secondo, mentre sviluppi, attiva la feature dynamic_linking. Così Bevy viene compilato come libreria condivisa separata, e Cargo non deve ricollegare tutto Bevy nel tuo programma a ogni modifica:
cargo run --features bevy/dynamic_linkingDue avvertenze dalla guida di Bevy: su Windows, dynamic_linking funziona solo insieme alle impostazioni opt-level di sopra. E non pubblicare il gioco con questa feature: il gioco avrebbe bisogno di un file di libreria in più accanto. Compila la release senza la feature. La guida mostra anche come usare un linker più veloce (lld o mold), che aiuta molto su Linux.
Una finestra Macroquad
Sezione intitolata “Una finestra Macroquad”Macroquad dà una sensazione molto diversa. Niente ECS e niente app builder: il game loop lo scrivi tu, e disegni con funzioni semplici come draw_circle. Ha pochissime dipendenze, quindi una build da zero richiede secondi, non minuti.
cargo new move_the_ballcd move_the_ballcargo add macroquad[dependencies]macroquad = "0.4"Questo piccolo gioco mostra una pallina che muovi con le frecce:
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; // pixel al secondo
loop { // 1. Input: le frecce muovono il giocatore let dt = get_frame_time(); // secondi dall'ultimo frame 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. Tieni il giocatore dentro lo schermo player.x = player.x.clamp(0.0, screen_width()); player.y = player.y.clamp(0.0, screen_height());
// 3. Disegna tutto per questo frame 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 }}Si vedono i tre passi del game loop: input, aggiornamento, disegno. #[macroquad::main("Move the Ball")] apre una finestra con quel titolo. main è async, ma non ti servono Tokio né conoscere l’async: next_frame().await significa semplicemente «mostra questo frame e aspetta il prossimo». Su Linux, il README di Macroquad elenca alcuni pacchetti di sistema da installare prima (pkg-config libx11-dev libxi-dev libgl1-mesa-dev libasound2-dev su Ubuntu).
Giochi sul web (WebAssembly)
Sezione intitolata “Giochi sul web (WebAssembly)”Sia Macroquad sia Bevy possono compilare il tuo gioco per il browser come WebAssembly. Se Wasm è una novità per te, leggi prima Rust e WebAssembly. Si parte sempre aggiungendo il target Wasm:
rustup target add wasm32-unknown-unknownMacroquad rende tutto molto facile. Un solo comando crea un file .wasm:
cargo build --release --target wasm32-unknown-unknownIl file finisce in target/wasm32-unknown-unknown/release/move_the_ball.wasm (per l’esempio di sopra pesava circa 600 KB). Il README di Macroquad fornisce un breve index.html che lo carica con un piccolo file JavaScript, mq_js_bundle.js. Metti entrambi i file in una cartella e servila con un qualsiasi web server locale, per esempio basic-http-server . (lo installi con cargo install basic-http-server). Fare doppio clic sul file HTML di solito non funziona, perché i browser bloccano il caricamento del file .wasm direttamente dal disco.
Bevy usa wasm-bindgen per creare il codice JavaScript di collegamento. Questi passaggi sono adattati dal README degli esempi di Bevy (per il progetto my_bevy_game di sopra):
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.wasmQuesto scrive un file .js e un file _bg.wasm nella cartella web; una piccola pagina HTML importa il file .js e avvia il gioco. La guida di installazione di Bevy suggerisce anche un profilo wasm-release con opt-level = "s" per rendere il download più piccolo. Le build web sono un ottimo modo per condividere i giochi delle game jam: si gioca con un clic, senza installare niente.
Errori comuni
Sezione intitolata “Errori comuni”Spostare di una quantità fissa a ogni frame. x += 5.0 a ogni frame significa che il gioco va due volte più veloce a 120 FPS che a 60 FPS. Moltiplica per il delta time (x += speed * dt) oppure usa un passo fisso.
Seguire un vecchio tutorial di Bevy. Bevy cambia la sua API a ogni release, quindi un articolo scritto per la 0.12 spesso non compila con la 0.20. Controlla quale versione usa un tutorial, e preferisci gli esempi ufficiali per la tua versione. Ogni release ha anche una guida alla migrazione sul sito di Bevy.
Build di debug lente. Se il tuo gioco Bevy gira a pochi frame al secondo, probabilmente hai dimenticato le impostazioni opt-level in Cargo.toml.
Partire troppo in grande. Un GDR open world è un primo progetto difficile in qualsiasi linguaggio. Inizia con Pong, Snake, Breakout o Flappy Bird. Finiscilo, poi fai il prossimo un po’ più grande.
Pratica
Sezione intitolata “Pratica”1. Stati di gioco con un enum
Sezione intitolata “1. Stati di gioco con un enum”Ogni gioco ha delle schermate: un menu, il gioco vero e proprio, una schermata di pausa, il «game over». Crea un enum GameState con Menu, Playing, Paused e GameOver, e un enum Input con Enter, Escape e PlayerDied. Scrivi fn next_state(state: GameState, input: Input) -> GameState: Invio fa partire il gioco dal menu, Esc mette in pausa e toglie la pausa, morire fa finire la partita, e Invio sul «game over» torna al menu. Qualsiasi altro input non cambia niente. Passale una lista fissa di input e stampa ogni cambiamento. Suggerimento: fai match sulla tupla (state, input), e chiudi con (same, _) => same.
Mostra la soluzione
#[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, // Qualsiasi altra cosa: ignora l'input e resta dove sei (same, _) => same, }}
fn main() { use Input::*; // così possiamo scrivere Enter invece di 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; }}Output:
Menu + Escape -> MenuMenu + Enter -> PlayingPlaying + Escape -> PausedPaused + PlayerDied -> PausedPaused + Escape -> PlayingPlaying + PlayerDied -> GameOverGameOver + Enter -> MenuFare match su una tupla elenca tutti i cambiamenti ammessi in un solo posto, e l’ultimo ramo rende innocui tutti gli altri input. Un giocatore in pausa non può morire, perché (Paused, PlayerDied) finisce in (same, _). Bevy ha la stessa idea già integrata, e si chiama States.
Riepilogo
Sezione intitolata “Riepilogo”- Rust è una vera opzione per i giochi: «quasi pronto», con un engine solido (Bevy), librerie semplici (Macroquad, ggez), opzioni con editor (Fyrox, Godot con gdext) e tanti mattoni.
- Ogni gioco esegue un ciclo: input, aggiornamento, disegno. Usa il delta time o un passo fisso, così la velocità non dipende dal frame rate.
- L’ECS divide un gioco in entità (ID), componenti (dati semplici) e sistemi (funzioni che girano sulle entità con certi componenti).
- Bevy è ECS da cima a fondo. Aggiungi le impostazioni di profilo
opt-level, e usadynamic_linkingsolo mentre sviluppi. - Macroquad è la partenza più rapida per il 2D: scrivi tu il ciclo e disegni con funzioni semplici.
- Entrambi possono compilare per il browser con il target
wasm32-unknown-unknown.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Nell'ECS, cos'è un'entità?
2. Perché un passo fisso fa comportare un gioco allo stesso modo su ogni computer?
3. Con quale impostazione di Bevy NON dovresti pubblicare il gioco finito?
4. Vuoi fare il tuo primo piccolo gioco 2D con tempi di build molto brevi. Quale crate suggerisce questa pagina?
Punteggio: 0 / 4
Per approfondire
Sezione intitolata “Per approfondire”- Are We Game Yet? (in inglese): la panoramica della community su engine di gioco in Rust, librerie per fisica, audio e rete, e giochi fatti con Rust.
- Bevy Quick Start: Setup (in inglese): la guida ufficiale per installare Bevy, con le impostazioni per compilare in fretta e i profili per la release e per Wasm.
- Game development in Rust with Macroquad (in inglese): un libro online gratuito che costruisce passo dopo passo uno sparatutto spaziale completo con Macroquad.
- The godot-rust book (in inglese): come scrivere in Rust la logica di gioco per Godot 4 con gdext.
- Game Loop — Game Programming Patterns (in inglese): una spiegazione amichevole e dettagliata di game loop, delta time e passo fisso.