从零开始创建WebAssembly 游戏

编程入门 行业动态 更新时间:2024-10-25 16:28:16

<a href=https://www.elefans.com/category/jswz/34/1769682.html style=从零开始创建WebAssembly 游戏"/>

从零开始创建WebAssembly 游戏

概述

学习一段时间rust,开始使用rust 创建一些小项目,rust的一大用处,结合JS,算法部分使用rust进行编写将编译成webassembly 后通过js调用,提高运行速度
,学习从0开始创建一个WebAssembly 游戏,学习rust如何与web 前端进行整合,该项目重点在于如何将rust编译成WebAssembly结合JS使用,至于游戏界面JS在此不进行详细介绍,直接使用模板编写。

wasm 与 wat

  • 二进制格式(执行): .wasm文件后缀
  • 文本格式:.wat文件后缀 wat 是基于文本助记表示形式WAT, wat 通过WABT工具编译成二进制文件wasm
  • chrome会将wasm 二进制编译成wat
  • wat2wasm 网址:

项目创建

1. cargo new --lib wasm-game
2. 创建www 目录
3. 在www目录下面:npm install --save-dev webpack-dev-servernpm install --save webpack-clinpm install --save copy-webpack-plugin
  • 加载wasm 文件必须得异步加载
    wat2wasm
    对于js 的使用wasm , 使用异步的方式对其进行加载
async function run() {const response = await fetch("yz.wasm");const buffer = await response.arrayBuffer();// 获取web wasmconst wasm = await WebAssembly.instantiate(buffer);// 从 wasm 获取其中方法const addTwoFunction = wasm.instance.exports.addTwo;const result = addTwoFunction(10, 20);console.log(result);
}run();

webAssembly中rust与js交互

  • 前后端分离后,前端负责一部分,后端负责一部分内如,做一个互相的review
  • 前往不要rust 写完部分功能后,把Trello卡片一移,交给JS这样主要树沟通成本太高。
1. 下载wasm-pack: 
2. 配置crate-type
3. 配置wasm-bindgen
4. 配置wasm-opt:[package.metadata.wasm-pack.profile.release]wasm-opt= false
JS:1. wasm-pack build --target web 生成包含wasm文件2. 配置npm 的package.json 将PKG目录导入3. 调用hello
  • 第一步在Cargo.toml 配置
[package]
name = "wasm_game"
version = "0.1.0"
edition = "2021"# See more keys and their definitions at .html[dependencies]
wasm-bindgen = "0.2.78"[lib]
crate-type = ["cdylib"]
// 此配置是必须的 不然会报错
[package.metadata.wasm-pack.profile.release]
wasm-opt = false
  • lib.rs 中写调用jS中alert
use wasm_bindgen::prelude::*;// rust 掉头JS
#[wasm_bindgen]
extern "C" {// 需要在 rust中表明这个是一个外部的方法pub fn alert(s: &str);
}#[wasm_bindgen]
pub fn hello(name: &str){alert(name);
}
  • 第二步:在当前rust目录下:
cargo build // 生成target文件
wasm-pack build --target web // 生成wasm pkg目录
  • 在 js的package.json 引入刚才生成的文件
"dependencies": {"copy-webpack-plugin": "^10.2.0","ts-loader": "^9.2.6","typescript": "^4.5.4","wasm_game": "file:../pkg", // 名称和lib.rs 保持一致"webpack": "^5.66.0","webpack-cli": "^4.9.1"},

npm install 重新导入, npm run dev 重新运行
效果如下在wasm中调用 js中函数

weealloc 内存分配,bootstrap.js 捕获错误

wee_alloc webassembly属于一个轻量级的内存分配器

use wasm_bindgen::prelude::*;
use wee_alloc::WeeAlloc;#[global_allocator]
static ALLOC: WeeAlloc = WeeAlloc::INIT;
// rust 掉头JS
#[wasm_bindgen]
extern "C" {// 需要在 rust中表明这个是一个外部的方法pub fn alert(s: &str);
}#[wasm_bindgen]
pub fn hello(name: &str){alert(name);
}
// 用来捕获错误
import ("./index.js").catch((e) => {console.error("Error", e);
})

js 切换到TS

npm install typescript
npm install --save typescript ts-loader

创建画布

1.使用rust 存储蛇的长度,每个蛇的像素带你位置,向右移动 像素+1,注意边界问题
2. js 中使用canve 创建画布
3. 使用setTime 进行刷新

use wasm_bindgen::prelude::*;
use wee_alloc::WeeAlloc;#[global_allocator]
static ALLOC: WeeAlloc = WeeAlloc::INIT;// rust use js module
#[wasm_bindgen(module = "/www/utils/random.js")]
extern "C" {fn random(max: usize) -> usize;
}#[wasm_bindgen]
#[derive(Copy, Clone)]
pub enum GameStatus {Won,Lost,Played,
}// Directions to move
#[wasm_bindgen]
#[derive(PartialEq)]
pub enum Direction {Up,Down,Left,Right,
}#[derive(PartialEq, Clone, Copy)]
pub struct SnakeCell(usize); // snake elementstruct Snake {body: Vec<SnakeCell>,direction: Direction,
}impl Snake {fn new(spawn_index: usize, size: usize) -> Self {let mut body = Vec::new();for i in 0..size {body.push(SnakeCell(spawn_index - i))}Self {body,direction: Direction::Down,}}
}#[wasm_bindgen]
pub struct World {width: usize,size: usize,reward_cell: Option<usize>, // 蛋的方向snake: Snake,next_cell: Option<SnakeCell>,status: Option<GameStatus>,
}#[wasm_bindgen]
impl World {pub fn new(width: usize, snake_index: usize) -> Self {let size = width * width;let snake = Snake::new(snake_index, 3);Self {width,size: width * width,reward_cell: Some(World::gen_reward_cell(size, &snake.body)),snake,next_cell: None,status: None,}}// 蛋不能在蛇身fn gen_reward_cell(max: usize, snake_body: &Vec<SnakeCell>) -> usize {let mut reward_cell;loop {reward_cell = random(max);if !snake_body.contains(&SnakeCell(reward_cell)) {break;}}reward_cell}pub fn start_game(&mut self) {self.status = Some(GameStatus::Played);}pub fn game_status(&self) -> Option<GameStatus> {self.status}pub fn game_status_info(&self) -> String {match self.status {Some(GameStatus::Won) => "You Won!".to_string(),Some(GameStatus::Lost) => "You Lost!".to_string(),Some(GameStatus::Played) => "You Playing...".to_string(),None => "None!".to_string(),}}pub fn reward_cell(&self) -> Option<usize> {self.reward_cell}pub fn width(&self) -> usize {self.width}pub fn snake_head_index(&self) -> usize {self.snake.body[0].0}// 调用gen_next_snake_cellpub fn change_snake_direction(&mut self, direction: Direction) {// 正在向左 不能向右let next_cell = self.gen_next_snake_cell(&direction);if self.snake.body[1].0 == next_cell.0 {return;}self.snake.direction = direction;}pub fn snake_cells(&self) -> *const SnakeCell {self.snake.body.as_ptr()}pub fn snake_length(&self) -> usize {self.snake.body.len()}pub fn update(&mut self) {let temp = self.snake.body.clone();// 调用gen_next_snake_cell 使用Option来提高性能match self.next_cell {Some(cell) => {self.snake.body[0] = cell;self.next_cell = None;}None => {self.snake.body[0] = self.gen_next_snake_cell(&self.snake.direction);}}let len = self.snake.body.len();for i in 1..len {self.snake.body[i] = SnakeCell(temp[i - 1].0);}if self.snake.body[1..len].contains(&self.snake.body[0]) {self.status = Some(GameStatus::Lost);}if self.reward_cell == Some(self.snake_head_index()) {if self.snake_length() < self.size {self.reward_cell = Some(World::gen_reward_cell(self.size, &self.snake.body));} else {self.reward_cell = None;self.status = Some(GameStatus::Won);}self.snake.body.push(SnakeCell(self.snake.body[1].0));}}fn gen_next_snake_cell(&self, direction: &Direction) -> SnakeCell {let snake_index = self.snake_head_index();let row = snake_index / self.width;return match direction {Direction::Up => {let border_hold = snake_index - row * self.width;if snake_index == border_hold {SnakeCell((self.size - self.width) + border_hold)} else {SnakeCell(snake_index - self.width)}}Direction::Down => {let border_hold = snake_index + ((self.width - row) * self.width);if snake_index + self.width == border_hold {SnakeCell(border_hold - (row + 1) * self.width)} else {SnakeCell(snake_index + self.width)}}Direction::Left => {let border_hold = row * self.width;if snake_index == border_hold {SnakeCell(border_hold + self.width - 1)} else {SnakeCell(snake_index - 1)}}Direction::Right => {let border_hold = (row + 1) * self.width;if snake_index + 1 == border_hold {SnakeCell(border_hold - self.width)} else {SnakeCell(snake_index + 1)}}};}}
import init, { World, Direction, GameStatus } from "wasm_game";
import {random} from "./utils/random";init().then(wasm => {const CELL_SIZE = 20;const WORLD_WIDTH = 4;const snakeIndex = random(WORLD_WIDTH * WORLD_WIDTH);const world = World.new(WORLD_WIDTH, snakeIndex);const worldWidth = world.width();const fps = 2;const gameStatus = document.getElementById("game-status");const gameControlBtn = document.getElementById("game-control-btn");const canvas = <HTMLCanvasElement>document.getElementById("snake-world");const context = canvas.getContext("2d");canvas.width = worldWidth * CELL_SIZE;canvas.height = worldWidth * CELL_SIZE;gameControlBtn.addEventListener("click", ()=>{const status = world.game_status();if(status == undefined) {gameControlBtn.textContent = "游戏中...";world.start_game();run();} else {location.reload();}})document.addEventListener("keydown", e => {switch (e.code) {case "ArrowUp":world.change_snake_direction(Direction.Up);break;case "ArrowDown":world.change_snake_direction(Direction.Down);break;case "ArrowLeft":world.change_snake_direction(Direction.Left);break;case "ArrowRight":world.change_snake_direction(Direction.Right);break;}})function drawWorld() {context.beginPath();for (let x = 0; x < worldWidth + 1; x++) {context.moveTo(CELL_SIZE * x, 0);context.lineTo(CELL_SIZE * x, CELL_SIZE * worldWidth);}for (let y = 0; y < worldWidth + 1; y++) {context.moveTo(0, CELL_SIZE * y);context.lineTo(CELL_SIZE * worldWidth, CELL_SIZE * y);}context.stroke();}function drawSnake() {const snakeCells = new Uint32Array(wasm.memory.buffer,world.snake_cells(),world.snake_length());snakeCells.filter((cellIdx, i) => !(i>0 && cellIdx == snakeCells[0])).forEach((cellIndex, i)=> {const col = cellIndex % worldWidth;const row = Math.floor(cellIndex/worldWidth);context.beginPath();context.fillStyle = i === 0 ? '#787878':'#000000';context.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);})context.stroke();}function drawReward() {const index = world.reward_cell();const row = Math.floor(index / worldWidth);const col = index % worldWidth;context.beginPath();context.fillStyle = '#FF0000';context.fillRect(col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE);context.stroke();}function drawGameStatus() {gameStatus.textContent = world.game_status_info();}function draw() {drawWorld();drawSnake();drawReward();drawGameStatus();}function run() {const status = world.game_status();if (status === GameStatus.Won || status == GameStatus.Lost) {gameControlBtn.textContent = "再玩一次?";return;}setTimeout(() => {context.clearRect(0, 0, canvas.width, canvas.height);world.update();draw();requestAnimationFrame(run);}, 1000 / fps);}draw();
});
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>.flex {display: flex}.label {font-weight: bold;margin-right: 13px;}.game-content {margin-bottom: 20px;}.content-wrapper {top: 0;left: 0;width: 100%;height: 100%;position: absolute;display: flex;align-items: center;justify-content: center;flex-direction: column;}</style>
</head><body><div class="content-wrapper"><div class="game-content"><div class="flex"><div class="label">Status:</div><div id="game-status">None</div></div><div class="flex"><button id="game-control-btn">开始游戏</button></div></div><canvas id="snake-world"></canvas></div><script src="./bootstrap.js"></script>
</body></html>

更多推荐

从零开始创建WebAssembly 游戏

本文发布于:2024-03-11 17:12:30,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1729469.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:从零开始   游戏   WebAssembly

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!