IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 从零开始创建WebAssembly 游戏 -> 正文阅读

[JavaScript知识库]从零开始创建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-server
   npm install --save webpack-cli
   npm install --save copy-webpack-plugin
  • 加载wasm 文件必须得异步加载
    wat2wasm
    对于js 的使用wasm , 使用异步的方式对其进行加载
async function run() {
    const response = await fetch("yz.wasm");
    const buffer = await response.arrayBuffer();
    // 获取web wasm
    const 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: https://rustwasm.github.io/wasm-pack/install
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 https://doc.rust-lang.org/cargo/reference/manifest.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 element

struct 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_cell
    pub 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>

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-21 20:40:43  更:2022-03-21 20:42:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:48:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码