4.1 Membuat Proyek Rust Baru
Jalankan perintah berikut di terminal untuk membuat proyek Rust baru dengan tipe library:
cargo new --lib snake-game
Masuk ke folder proyek:
cd snake-game
Pelajari cara membuat game Snake yang menarik dengan Rust & WebAssembly! Temukan panduan praktis dan tips yang tidak boleh Anda lewatkan!
Game Snake adalah salah satu game klasik yang sederhana namun menyenangkan. Dalam panduan ini, kita akan belajar bagaimana membuat game Snake menggunakan bahasa pemrograman Rust dan mengompilasinya ke WebAssembly (Wasm) agar dapat dijalankan di browser dengan performa tinggi.
Rust adalah bahasa pemrograman yang cepat dan aman, sedangkan WebAssembly memungkinkan kode yang ditulis dalam bahasa lain selain JavaScript untuk berjalan di browser. Dengan menggabungkan keduanya, kita dapat membuat game yang ringan, cepat, dan modern.
Sebelum mulai membuat game, kita perlu menyiapkan beberapa alat dan lingkungan pengembangan:
cargo install wasm-pack
Setelah semua terpasang, kita siap membuat proyek Rust yang akan dikompilasi ke WebAssembly.
Struktur proyek yang akan kita buat kira-kira seperti ini:
snake-game/ ├── pkg/ # Folder hasil build wasm-pack ├── src/ │ └── lib.rs # Kode Rust utama ├── index.html # Halaman web untuk menjalankan game ├── package.json # Konfigurasi npm └── README.md # Dokumentasi proyek
Kita akan fokus pada
src/lib.rs
untuk logika game, dan
index.html
untuk menampilkan game di browser.
Jalankan perintah berikut di terminal untuk membuat proyek Rust baru dengan tipe library:
cargo new --lib snake-game
Masuk ke folder proyek:
cd snake-game
Buka file
Cargo.toml
dan tambahkan dependensi berikut:
[lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2" js-sys = "0.3" web-sys = { version = "0.3", features = ["Window", "Document", "HtmlCanvasElement", "CanvasRenderingContext2d", "KeyboardEvent", "console"] }
Ini memungkinkan kita mengakses API browser dan menghubungkan Rust dengan JavaScript.
src/lib.rs
Berikut contoh kode Rust untuk game Snake sederhana yang bisa Anda gunakan:
use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, KeyboardEvent}; use std::collections::VecDeque; #[wasm_bindgen] pub struct Game { width: u32, height: u32, snake: VecDeque<(u32, u32)>, dir: Direction, food: (u32, u32), game_over: bool, context: CanvasRenderingContext2d, } #[wasm_bindgen] impl Game { #[wasm_bindgen(constructor)] pub fn new(canvas_id: &str) -> Game { let window = web_sys::window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); let canvas = document.get_element_by_id(canvas_id) .unwrap() .dyn_into::() .unwrap(); let context = canvas .get_context("2d").unwrap() .unwrap() .dyn_into:: () .unwrap(); let width = canvas.width(); let height = canvas.height(); let mut snake = VecDeque::new(); snake.push_back((width / 2, height / 2)); let food = (width / 4, height / 4); Game { width, height, snake, dir: Direction::Right, food, game_over: false, context, } } pub fn change_direction(&mut self, key: &str) { self.dir = match key { "ArrowUp" if self.dir != Direction::Down => Direction::Up, "ArrowDown" if self.dir != Direction::Up => Direction::Down, "ArrowLeft" if self.dir != Direction::Right => Direction::Left, "ArrowRight" if self.dir != Direction::Left => Direction::Right, _ => self.dir, }; } pub fn update(&mut self) { if self.game_over { return; } let (head_x, head_y) = self.snake.front().unwrap(); let new_head = match self.dir { Direction::Up => (*head_x, head_y.saturating_sub(10)), Direction::Down => (*head_x, head_y.saturating_add(10)), Direction::Left => (head_x.saturating_sub(10), *head_y), Direction::Right => (head_x.saturating_add(10), *head_y), }; // Cek tabrakan dengan dinding if new_head.0 >= self.width || new_head.1 >= self.height { self.game_over = true; return; } // Cek tabrakan dengan tubuh sendiri if self.snake.contains(&new_head) { self.game_over = true; return; } self.snake.push_front(new_head); // Cek makan makanan if new_head == self.food { self.spawn_food(); } else { self.snake.pop_back(); } } pub fn draw(&self) { // Bersihkan canvas self.context.set_fill_style(&JsValue::from_str("#f0f0f0")); self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64); // Gambar makanan self.context.set_fill_style(&JsValue::from_str("#e53e3e")); self.context.fill_rect(self.food.0 as f64, self.food.1 as f64, 10.0, 10.0); // Gambar ular self.context.set_fill_style(&JsValue::from_str("#38a169")); for &(x, y) in &self.snake { self.context.fill_rect(x as f64, y as f64, 10.0, 10.0); } if self.game_over { self.context.set_fill_style(&JsValue::from_str("rgba(0,0,0,0.7)")); self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64); self.context.set_fill_style(&JsValue::from_str("#fff")); self.context.set_font("48px sans-serif"); self.context.fill_text("Game Over", (self.width / 4) as f64, (self.height / 2) as f64).unwrap(); } } fn spawn_food(&mut self) { use rand::Rng; let mut rng = rand::thread_rng(); loop { let x = (rng.gen_range(0..self.width / 10)) * 10; let y = (rng.gen_range(0..self.height / 10)) * 10; if !self.snake.contains(&(x, y)) { self.food = (x, y); break; } } } } #[derive(PartialEq, Copy, Clone)] enum Direction { Up, Down, Left, Right, }
Catatan:
Untuk menggunakan
rand
di WebAssembly, Anda perlu menambahkan dependensi
rand = "0.8"
di
Cargo.toml
dan mengaktifkan fitur yang kompatibel dengan Wasm. Namun, untuk kesederhanaan, Anda bisa mengganti logika spawn makanan dengan metode lain jika mengalami masalah.
Buat file
index.html
di root proyek dengan isi berikut:
<!DOCTYPE html> <html lang="id"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Game Snake Rust & Wasm</title> <script type="module"> import init, { Game } from './pkg/snake_game.js'; async function run() { await init(); const game = new Game('game-canvas'); window.addEventListener('keydown', e => { game.change_direction(e.key); }); function gameLoop() { game.update(); game.draw(); if (!game.game_over) { setTimeout(gameLoop, 100); } } gameLoop(); } run(); </script> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; } canvas { border: 2px solid #38a169; background-color: #ffffff; } </style> </head> <body> <canvas id="game-canvas" width="400" height="400"></canvas> </body> </html>
Halaman ini memuat modul WebAssembly yang sudah dibangun dan menjalankan game Snake di canvas HTML.
Jalankan perintah berikut untuk membangun proyek menjadi WebAssembly:
wasm-pack build --target web
Setelah selesai, jalankan server lokal untuk melihat hasilnya. Jika Anda memiliki
serve
dari npm, jalankan:
npx serve .
Buka browser dan akses
http://localhost:5000
(atau port yang ditampilkan) untuk memainkan game Snake yang Anda buat.
Berikut adalah source code lengkap
src/lib.rs
yang sudah dirapikan dan siap digunakan:
use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use std::collections::VecDeque; #[wasm_bindgen] pub struct Game { width: u32, height: u32, snake: VecDeque<(u32, u32)>, dir: Direction, food: (u32, u32), game_over: bool, context: CanvasRenderingContext2d, } #[wasm_bindgen] impl Game { #[wasm_bindgen(constructor)] pub fn new(canvas_id: &str) -> Game { let window = web_sys::window().expect("no global `window` exists"); let document = window.document().expect("should have a document on window"); let canvas = document.get_element_by_id(canvas_id) .unwrap() .dyn_into::() .unwrap(); let context = canvas .get_context("2d").unwrap() .unwrap() .dyn_into:: () .unwrap(); let width = canvas.width(); let height = canvas.height(); let mut snake = VecDeque::new(); snake.push_back((width / 2, height / 2)); let food = (width / 4, height / 4); Game { width, height, snake, dir: Direction::Right, food, game_over: false, context, } } pub fn change_direction(&mut self, key: &str) { self.dir = match key { "ArrowUp" if self.dir != Direction::Down => Direction::Up, "ArrowDown" if self.dir != Direction::Up => Direction::Down, "ArrowLeft" if self.dir != Direction::Right => Direction::Left, "ArrowRight" if self.dir != Direction::Left => Direction::Right, _ => self.dir, }; } pub fn update(&mut self) { if self.game_over { return; } let (head_x, head_y) = self.snake.front().unwrap(); let new_head = match self.dir { Direction::Up => (*head_x, head_y.saturating_sub(10)), Direction::Down => (*head_x, head_y.saturating_add(10)), Direction::Left => (head_x.saturating_sub(10), *head_y), Direction::Right => (head_x.saturating_add(10), *head_y), }; if new_head.0 >= self.width || new_head.1 >= self.height { self.game_over = true; return; } if self.snake.contains(&new_head) { self.game_over = true; return; } self.snake.push_front(new_head); if new_head == self.food { self.spawn_food(); } else { self.snake.pop_back(); } } pub fn draw(&self) { self.context.set_fill_style(&JsValue::from_str("#f0f0f0")); self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64); self.context.set_fill_style(&JsValue::from_str("#e53e3e")); self.context.fill_rect(self.food.0 as f64, self.food.1 as f64, 10.0, 10.0); self.context.set_fill_style(&JsValue::from_str("#38a169")); for &(x, y) in &self.snake { self.context.fill_rect(x as f64, y as f64, 10.0, 10.0); } if self.game_over { self.context.set_fill_style(&JsValue::from_str("rgba(0,0,0,0.7)")); self.context.fill_rect(0.0, 0.0, self.width as f64, self.height as f64); self.context.set_fill_style(&JsValue::from_str("#fff")); self.context.set_font("48px sans-serif"); self.context.fill_text("Game Over", (self.width / 4) as f64, (self.height / 2) as f64).unwrap(); } } fn spawn_food(&mut self) { // Cara sederhana spawn makanan tanpa rand crate let mut x = (self.food.0 + 30) % self.width; let mut y = (self.food.1 + 30) % self.height; // Pastikan makanan tidak muncul di tubuh ular while self.snake.contains(&(x, y)) { x = (x + 10) % self.width; y = (y + 10) % self.height; } self.food = (x, y); } } #[derive(PartialEq, Copy, Clone)] enum Direction { Up, Down, Left, Right, }
Pengembangan Game, JavaScript, Tutorial, Pemrograman, Teknologi
Temukan langkah mudah untuk membuat game Breakout dengan JavaScript. Mulai proyek coding Anda dan tampilkan kreativitas Anda! Klik dan pelajari sekarang!
Pengembangan Game, Rust, WebAssembly, Tutorial, Panduan Praktis
Pelajari cara membuat game Snake yang menarik dengan Rust & WebAssembly! Temukan panduan praktis dan tips yang tidak boleh Anda lewatkan!