Bỏ qua để đến nội dung

Lập trình game với Rust: Bevy, Macroquad và hơn nữa

Rất nhiều người học Rust vì muốn làm game. Đó là một lý do tuyệt vời! Game cần tốc độ, dùng bộ nhớ cẩn thận và code không crash giữa trận đánh trùm, và đó chính xác là những thứ Rust làm tốt.

Trang này cho bạn một bức tranh thẳng thắn: người ta đã làm được gì với Rust, nên chọn engine hay thư viện nào, và hai ý tưởng mà gần như game nào cũng dùng, vòng lặp game (game loop) và ECS. Sau đó bạn sẽ thấy chương trình đầu tiên viết bằng Bevy và bằng Macroquad.

Ví dụ nào chạy ở đâu? Vòng lặp game, thế giới ECS tí hon và bài luyện tập là Rust thuần, nên chúng có nút “Try it Yourself” để chạy trên Rust Playground. Bevy và Macroquad mở một cửa sổ, nên các ví dụ đó dành cho máy của bạn với cargo run. Ví dụ Macroquad đã được biên dịch (cả cho web) khi viết trang này; ví dụ Bevy dựa theo các ví dụ chính thức của Bevy.

Tình trạng thật của lập trình game với Rust

Phần tiêu đề “Tình trạng thật của lập trình game với Rust”

Trang cộng đồng Are We Game Yet? (tiếng Anh) theo dõi hệ sinh thái game của Rust. Câu trả lời của nó cho chính câu hỏi trong tên là: “Almost. We have the blocks, bring your own glue.” (Gần rồi. Chúng tôi có sẵn các khối, bạn tự mang keo đến.) Đó là một bản tóm tắt công bằng.

Những gì đã tốt hiện nay:

  • Game thật đã được phát hành. Tiny Glade, một game xây lâu đài nhẹ nhàng trên Steam, và Veloren, một game RPG voxel mã nguồn mở, đều có tên trên Are We Game Yet là game viết bằng Rust.
  • Một engine mạnh. Bevy có cộng đồng lớn, năng động và cứ vài tháng lại có bản phát hành mới.
  • Rất nhiều khối xây dựng. Are We Game Yet liệt kê các crate cho vật lý, âm thanh, input, mạng, giao diện và hơn nữa.
  • Game của bạn chạy được trên web. Rust biên dịch ra WebAssembly, nên nhiều game Rust cũng chạy được trong trình duyệt.

Những gì vẫn còn khó:

  • Chưa có “Unity của Rust”. Phần lớn engine Rust thiên về code. Có trình soạn thảo trực quan (Fyrox có một cái, và Godot dùng được Rust), nhưng chúng còn non hơn các công cụ thương mại lớn.
  • Mọi thứ thay đổi. Bevy vẫn chưa đến 1.0, và mỗi bản phát hành đều có thay đổi không tương thích. Một hướng dẫn từ năm ngoái có thể không còn biên dịch được.
  • Thời gian biên dịch. Các engine lớn mất vài phút cho lần build đầu. Bạn sẽ học các mẹo xử lý việc này ở bên dưới.

Nếu mục tiêu của bạn là phát hành một game 3D thương mại vào năm sau cùng một đội họa sĩ, Unity, Unreal hay Godot an toàn hơn. Nếu bạn muốn học, làm game 2D, tham gia game jam, làm công cụ hay mô phỏng, hoặc đơn giản là thích Rust, thì đây là một nơi rất vui. 🦀

Có hai loại công cụ. Một engine cho bạn cả một cấu trúc: đối tượng game, cảnh (scene) và tài nguyên (asset) hoạt động thế nào. Một thư viện (hay framework) cho bạn cửa sổ, vẽ hình, âm thanh và input, còn phần còn lại bạn tự sắp xếp.

Crate Nó là gì Hợp với Cần biết
bevy (0.20) Engine 2D và 3D đầy đủ, xây quanh ECS Dự án lớn hơn, 3D, tìm hiểu cách engine hiện đại hoạt động Engine Rust nổi tiếng nhất. Mỗi bản phát hành đều có thay đổi không tương thích; lần build đầu lâu
macroquad (0.4) Thư viện 2D đơn giản, lấy cảm hứng từ raylib Game đầu tiên, game jam, game trên web Rất ít phụ thuộc, build nhanh. Bạn gọi các hàm vẽ trong một vòng lặp
ggez (0.10) Framework 2D gọn nhẹ, lấy cảm hứng từ LÖVE Game 2D có cấu trúc rõ ràng Bạn implement trait EventHandler với update và draw
fyrox (1.0) Engine 2D và 3D có trình soạn thảo cảnh Người muốn có trình soạn thảo, như trong Godot hay Unity Lên phiên bản 1.0 vào năm 2026. Cộng đồng nhỏ hơn Bevy
raylib (6.0) Binding Rust cho thư viện C raylib Người đã biết raylib từ C Cần CMake và bộ công cụ build C, vì nó build thư viện C (xem FFI)
godot (0.5, “gdext”) Binding Rust cho engine Godot 4 Dùng trình soạn thảo của Godot với logic game viết bằng Rust Bạn viết các class Rust mà Godot nạp dưới dạng GDExtension

Một quy tắc đơn giản: bắt đầu với Macroquad cho game 2D đầu tiên. Chọn Bevy khi bạn muốn một engine đầy đủ và sẵn lòng học ECS. Chọn Godot với gdext hoặc Fyrox nếu bạn muốn một trình soạn thảo để đặt mọi thứ bằng mắt.

Gần như game nào cũng là một vòng lặp lớn chạy đi chạy lại cho đến khi bạn thoát:

  1. Đọc input (bàn phím, chuột, tay cầm).
  2. Cập nhật (update) game: di chuyển mọi thứ, kiểm tra va chạm, tính điểm.
  3. Vẽ (draw) hình mới lên màn hình.

Một vòng lặp gọi là một khung hình (frame). Ở 60 khung hình mỗi giây (FPS), mỗi khung hình có khoảng 16 mili giây. Nhưng không phải khung hình nào cũng tốn cùng một thời gian. Máy tính có thể đang bận, hoặc một khung hình cần tải một file.

Nếu game di chuyển người chơi “2 đơn vị mỗi khung hình”, nó sẽ chạy nhanh hơn trên máy nhanh và chậm hơn trên máy chậm. Một cách sửa phổ biến là bước thời gian cố định (fixed timestep): logic game luôn tiến lên theo những bước bằng nhau (ở đây là 10 ms), và mỗi khung hình chạy bao nhiêu bước vừa với khoảng thời gian đã trôi qua. Thời gian còn dư chờ trong một bộ tích lũy (accumulator) cho đến khung hình sau.

const STEP_MS: u32 = 10; // logic game luôn tiến lên mỗi lần 10 ms
const SPEED: i32 = 2; // người chơi di chuyển 2 đơn vị mỗi lần update
fn main() {
// Mỗi khung hình thật sự mất bao lâu. Game thật đo bằng đồng hồ.
let frame_times = [16, 16, 45, 5, 16, 33];
let mut accumulator = 0; // thời gian đã trôi qua nhưng chưa được mô phỏng
let mut position = 0;
for (frame, elapsed) in frame_times.into_iter().enumerate() {
accumulator += elapsed;
// Chạy bao nhiêu bước cố định vừa với thời gian ta có
let mut updates = 0;
while accumulator >= STEP_MS {
position += SPEED; // update(): logic game
accumulator -= STEP_MS;
updates += 1;
}
// render(): vẽ một lần mỗi khung hình, dù chuyện gì đã xảy ra
println!("khung {frame}: {elapsed:>2} ms, {updates} lần update, người chơi ở {position:>2}, còn dư {accumulator} ms");
}
}

Kết quả:

khung 0: 16 ms, 1 lần update, người chơi ở 2, còn dư 6 ms
khung 1: 16 ms, 2 lần update, người chơi ở 6, còn dư 2 ms
khung 2: 45 ms, 4 lần update, người chơi ở 14, còn dư 7 ms
khung 3: 5 ms, 1 lần update, người chơi ở 16, còn dư 2 ms
khung 4: 16 ms, 1 lần update, người chơi ở 18, còn dư 8 ms
khung 5: 33 ms, 4 lần update, người chơi ở 26, còn dư 1 ms
▶ Tự chạy thử

Nhìn vào khung 2: nó chậm (45 ms), nên game chạy 4 lần update để đuổi kịp. Tổng cộng 131 ms đã trôi qua, và game chạy 13 lần update, mỗi lần 10 ms. Người chơi dừng ở 26, bất kể thời gian được chia thành các khung hình ra sao. Nhờ vậy vật lý ổn định và giống nhau trên mọi máy, điều này cũng quan trọng cho tính năng xem lại (replay) và game online. Một mối nguy: nếu một khung hình rất chậm, game phải chạy nhiều lần update để đuổi kịp, khiến khung hình sau cũng chậm theo. Vì vậy vòng lặp thật còn giới hạn số lần update mỗi khung hình (ví dụ, đồng hồ game của Bevy mặc định tiến tối đa 250 ms mỗi khung hình).

Cách phổ biến còn lại là delta time: bạn đo số giây kể từ khung hình trước và nhân tốc độ với nó (x += speed * dt). Cách này đơn giản hơn, và đó là cách các ví dụ Bevy và Macroquad bên dưới dùng. Nhiều engine dùng cả hai: delta time cho hoạt ảnh, bước cố định cho vật lý.

Trong nhiều ngôn ngữ, bạn sẽ tạo một class Player, một class Enemy kế thừa từ Character, và cứ thế. Cách đó nhanh chóng trở nên rối: còn một kẻ địch mà người chơi nhặt lên được thì sao? Thay vào đó, phần lớn engine game Rust dùng ECS (Entity Component System):

  • Một entity chỉ là một ID, giống như một con số. Nó không có dữ liệu hay hành vi riêng.
  • Một component là một mẩu dữ liệu thuần bạn gắn vào entity: Position, Velocity, Health. Trong Rust, chúng là các struct hoặc enum bình thường.
  • Một system là một hàm chạy trên mọi entity có những component nhất định. Ví dụ, system di chuyển làm việc với mọi thứ có cả Position lẫn Velocity.

Cái cây có Position nhưng không có Velocity, nên system di chuyển bỏ qua nó. Muốn cây di chuyển? Hãy cho nó một Velocity. Bạn không phải sửa class nào cả. Dữ liệu và hành vi tách biệt, và điều này cũng rất hợp với quy tắc mượn (borrowing) của Rust.

Đây là một thế giới ECS tí hon bằng Rust thuần. Mỗi loại component nằm trong vector riêng của nó, và ID của entity chính là chỉ số. None nghĩa là “entity này không có component đó” (xem Option). Tên các entity được giữ tiếng Anh vì chúng được căn lề theo độ rộng 6 ký tự bằng {:<6}.

struct Position { x: i32, y: i32 }
struct Velocity { dx: i32, dy: i32 }
// Một entity chỉ là một ID: chỉ số trong mọi danh sách component
type 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
}
}
// Một system: làm việc với mọi entity có CẢ Position lẫn Velocity
fn 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;
}
}
}
// Một system khác: in mọi entity có Position
fn print_system(world: &World, tick: u32) {
println!("lượt {tick}:");
for (id, pos) in world.positions.iter().enumerate() {
if let Some(pos) = pos {
println!(" #{id} {:<6} ở ({}, {})", 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); // không có Velocity: nó không bao giờ di chuyển
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);
}
}

Kết quả:

lượt 1:
#0 player ở (1, 0)
#1 tree ở (5, 2)
#2 arrow ở (3, 2)
lượt 2:
#0 player ở (2, 0)
#1 tree ở (5, 2)
#2 arrow ở (6, 1)
▶ Tự chạy thử

Cái cây (tree) đứng yên ở (5, 2) vì nó không có Velocity. Không ai viết “cây không di chuyển”; điều đó tự suy ra từ dữ liệu. Các thư viện ECS thật nhanh và thông minh hơn thế này rất nhiều (chúng lưu component sao cho CPU đọc nhanh, và chạy các system song song), nhưng ý tưởng thì giống hệt.

Bevy (tiếng Anh) là engine Rust nổi tiếng nhất, và mọi thứ trong nó đều là ECS. Phiên bản 0.20 ra mắt vào tháng 10 năm 2026. Bevy cần Rust stable mới nhất, nên hãy cập nhật trước, rồi tạo project:

Terminal window
rustup update
cargo new my_bevy_game
cd my_bevy_game
cargo add bevy

Trên Linux, bạn còn cần vài thư viện hệ thống (âm thanh, input, đồ họa). Hướng dẫn cài đặt (tiếng Anh) của Bevy liệt kê chúng cho từng bản phân phối. Trên Windows bạn cần Visual Studio C++ Build Tools, còn trên macOS là Xcode command line tools.

Chương trình này dựa trên ví dụ chính thức 2d/move_sprite của Bevy. Nó vẽ một hình vuông màu cam trượt qua trượt lại. (Ví dụ chính thức nạp một bức ảnh; ở đây dùng một hình vuông tô màu đơn giản, nên bạn không cần file ảnh nào.)

use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins) // cửa sổ, vẽ hình, input, âm thanh...
.add_systems(Startup, setup) // chạy một lần lúc bắt đầu
.add_systems(Update, sprite_movement) // chạy mỗi khung hình
.run();
}
// Một component: entity này đang di chuyển theo hướng nào
#[derive(Component)]
enum Direction {
Left,
Right,
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
// Tạo một entity với ba component
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,
));
}
// Một system: chạy trên mọi entity có Direction VÀ Transform
fn 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;
}
}
}

Hãy so sánh với thế giới ECS tí hon ở trên. #[derive(Component)] biến một enum bình thường thành component. commands.spawn((...)) tạo một entity với một tuple các component. Còn Query<(&mut Direction, &mut Transform)> xin Bevy “mọi entity có cả hai”, đúng như movement_system của ta đã làm với zip. time.delta_secs() chính là delta time trong phần vòng lặp game, nên hình vuông di chuyển 150 pixel mỗi giây trên bất kỳ máy nào.

Chạy bằng cargo run. Lần build đầu biên dịch hàng trăm crate và có thể mất vài phút, nên hãy kiên nhẫn. Sau đó, những lần build lại nhanh hơn nhiều.

Hướng dẫn cài đặt của Bevy khuyên hai điều. Thứ nhất, thêm đoạn này vào Cargo.toml. Nó tối ưu Bevy và các phụ thuộc khác, nhưng giữ cho code của chính bạn biên dịch nhanh:

[dependencies]
bevy = "0.20"
# Bật một chút tối ưu trong profile dev.
[profile.dev]
opt-level = 1
# Bật tối ưu mạnh trong profile dev cho các phụ thuộc.
[profile.dev.package."*"]
opt-level = 3

Không có đoạn này, bản build debug của một game có thể rất chậm: tốc độ khung hình thấp và thời gian tải lâu. Các phụ thuộc chỉ biên dịch một lần, nên bạn chỉ trả giá cho việc tối ưu ở lần build đầu. (Profile được giải thích ở trang Hiệu năng.)

Thứ hai, trong lúc phát triển, hãy bật feature dynamic_linking. Khi đó Bevy được build thành một thư viện dùng chung riêng, nên Cargo không phải link toàn bộ Bevy vào chương trình của bạn sau mỗi lần sửa:

Terminal window
cargo run --features bevy/dynamic_linking

Hai cảnh báo từ hướng dẫn của Bevy: trên Windows, dynamic_linking chỉ hoạt động cùng với các thiết lập opt-level ở trên. Và đừng phát hành game kèm nó: game sẽ cần thêm một file thư viện nằm bên cạnh. Hãy build bản release không có feature này. Hướng dẫn cũng chỉ cách dùng một linker nhanh hơn (lld hoặc mold), giúp ích rất nhiều trên Linux.

Macroquad (tiếng Anh) mang cảm giác rất khác. Không có ECS và không có app builder: bạn tự viết vòng lặp game, và vẽ bằng những hàm đơn giản như draw_circle. Nó có rất ít phụ thuộc, nên một lần build mới chỉ mất vài giây chứ không phải vài phút.

Terminal window
cargo new move_the_ball
cd move_the_ball
cargo add macroquad
[dependencies]
macroquad = "0.4"

Game nhỏ này hiển thị một quả bóng bạn di chuyển bằng các phím mũi tên:

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 mỗi giây
loop {
// 1. Input: phím mũi tên di chuyển người chơi
let dt = get_frame_time(); // số giây kể từ khung hình trước
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. Giữ người chơi trong màn hình
player.x = player.x.clamp(0.0, screen_width());
player.y = player.y.clamp(0.0, screen_height());
// 3. Vẽ mọi thứ cho khung hình này
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
}
}

Bạn thấy được ba bước của vòng lặp game: input, update, vẽ. #[macroquad::main("Move the Ball")] mở một cửa sổ với tiêu đề đó. main là async, nhưng bạn không cần Tokio hay phải biết về async: next_frame().await đơn giản nghĩa là “hiển thị khung hình này và chờ khung hình sau”. Trên Linux, README của Macroquad liệt kê vài gói hệ thống cần cài trước (pkg-config libx11-dev libxi-dev libgl1-mesa-dev libasound2-dev trên Ubuntu).

Cả Macroquad lẫn Bevy đều build được game của bạn cho trình duyệt dưới dạng WebAssembly. Nếu Wasm còn mới với bạn, hãy đọc Rust và WebAssembly trước. Bạn luôn bắt đầu bằng việc thêm target Wasm:

Terminal window
rustup target add wasm32-unknown-unknown

Macroquad làm việc này rất dễ. Một lệnh là build ra file .wasm:

Terminal window
cargo build --release --target wasm32-unknown-unknown

File nằm ở target/wasm32-unknown-unknown/release/move_the_ball.wasm (với ví dụ ở trên, nó khoảng 600 KB). README của Macroquad đưa sẵn một file index.html ngắn nạp nó bằng một file JavaScript nhỏ, mq_js_bundle.js. Đặt cả hai file vào một thư mục và phục vụ nó bằng bất kỳ web server cục bộ nào, ví dụ basic-http-server . (cài bằng cargo install basic-http-server). Chỉ nhấp đúp vào file HTML thường không chạy, vì trình duyệt chặn việc nạp file .wasm trực tiếp từ đĩa.

Bevy dùng wasm-bindgen để tạo phần “keo” JavaScript. Các bước này được điều chỉnh từ README các ví dụ của Bevy (cho project my_bevy_game ở trên):

Terminal window
cargo install wasm-bindgen-cli
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --out-dir web --target web target/wasm32-unknown-unknown/release/my_bevy_game.wasm

Lệnh này ghi một file .js và một file _bg.wasm vào thư mục web; một trang HTML nhỏ import file .js và khởi động game. Hướng dẫn cài đặt của Bevy cũng gợi ý một profile wasm-release với opt-level = "s" để file tải về nhỏ hơn. Bản build web là cách tuyệt vời để chia sẻ bài dự thi game jam: mọi người chơi được chỉ với một cú nhấp, không cần cài gì.

Di chuyển một lượng cố định mỗi khung hình. x += 5.0 mỗi khung hình nghĩa là game chạy nhanh gấp đôi ở 120 FPS so với ở 60 FPS. Hãy nhân với delta time (x += speed * dt) hoặc dùng bước thời gian cố định.

Làm theo một hướng dẫn Bevy cũ. Bevy thay đổi API ở mỗi bản phát hành, nên một bài blog cho 0.12 thường sẽ không biên dịch được với 0.20. Hãy kiểm tra hướng dẫn dùng phiên bản nào, và ưu tiên các ví dụ chính thức cho phiên bản của bạn. Mỗi bản phát hành cũng có một hướng dẫn chuyển đổi (migration guide) trên website của Bevy.

Bản build debug chậm. Nếu game Bevy của bạn chỉ chạy vài khung hình mỗi giây, có lẽ bạn đã quên thiết lập opt-level trong Cargo.toml.

Bắt đầu quá lớn. Một game RPG thế giới mở là dự án đầu tiên khó với bất kỳ ngôn ngữ nào. Hãy bắt đầu với Pong, Snake, Breakout hay Flappy Bird. Làm cho xong, rồi làm cái tiếp theo lớn hơn một chút.

Game nào cũng có các màn hình: menu, phần chơi chính, màn hình tạm dừng, “game over”. Tạo một enum GameState với Menu, Playing, Paused và GameOver, và một enum Input với Enter, Escape và PlayerDied. Viết fn next_state(state: GameState, input: Input) -> GameState: Enter bắt đầu game từ menu, Escape tạm dừng và tiếp tục, chết thì kết thúc game, và Enter ở màn hình “game over” quay về menu. Mọi input khác không thay đổi gì. Cho nó một danh sách input cố định và in ra từng thay đổi. Gợi ý: dùng match trên tuple (state, input), và kết thúc bằng (same, _) => same.

Xem lời giải
#[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,
// Mọi trường hợp khác: bỏ qua input và giữ nguyên trạng thái
(same, _) => same,
}
}
fn main() {
use Input::*; // cho phép viết Enter thay vì 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;
}
}

Kết quả:

Menu + Escape -> Menu
Menu + Enter -> Playing
Playing + Escape -> Paused
Paused + PlayerDied -> Paused
Paused + Escape -> Playing
Playing + PlayerDied -> GameOver
GameOver + Enter -> Menu
▶ Tự chạy thử

So khớp trên một tuple liệt kê mọi thay đổi được phép ở cùng một chỗ, và nhánh cuối cùng khiến mọi input khác trở nên vô hại. Người chơi đang tạm dừng không thể chết, vì (Paused, PlayerDied) rơi vào (same, _). Bevy có sẵn đúng ý tưởng này, gọi là States.

  • Rust là một lựa chọn thật sự cho game: “gần tới nơi rồi”, với một engine mạnh (Bevy), các thư viện đơn giản (Macroquad, ggez), các lựa chọn có trình soạn thảo (Fyrox, Godot với gdext) và rất nhiều khối xây dựng.
  • Game nào cũng chạy một vòng lặp: input, update, vẽ. Dùng delta time hoặc bước thời gian cố định để tốc độ không phụ thuộc vào tốc độ khung hình.
  • ECS chia game thành entity (ID), component (dữ liệu thuần) và system (hàm chạy trên các entity có những component nhất định).
  • Bevy là ECS từ trên xuống dưới. Thêm các thiết lập profile opt-level, và chỉ dùng dynamic_linking trong lúc phát triển.
  • Macroquad là cách bắt đầu nhanh nhất cho 2D: bạn tự viết vòng lặp và vẽ bằng những hàm đơn giản.
  • Cả hai đều build được cho trình duyệt với target wasm32-unknown-unknown.

Kiểm tra nhanh

1. Trong ECS, entity là gì?

2. Vì sao bước thời gian cố định giúp game chạy giống nhau trên mọi máy?

3. Bạn KHÔNG nên phát hành game hoàn chỉnh kèm thiết lập Bevy nào?

4. Bạn muốn làm game 2D nhỏ đầu tiên với thời gian build rất ngắn. Trang này gợi ý crate nào?

Điểm: 0 / 4

  • Are We Game Yet? (tiếng Anh): tổng quan của cộng đồng về engine game Rust, các thư viện vật lý, âm thanh và mạng, cùng những game làm bằng Rust.
  • Bevy Quick Start: Setup (tiếng Anh): hướng dẫn chính thức để cài Bevy, các thiết lập biên dịch nhanh và các profile cho release và Wasm.
  • Game development in Rust with Macroquad (tiếng Anh): một cuốn sách trực tuyến miễn phí, xây dựng từng bước một game bắn tàu vũ trụ hoàn chỉnh với Macroquad.
  • The godot-rust book (tiếng Anh): cách viết logic game Godot 4 bằng Rust với gdext.
  • Game Loop — Game Programming Patterns (tiếng Anh): giải thích thân thiện và chi tiết về vòng lặp game, delta time và bước thời gian cố định.