2025年2月12日水曜日

「RustとWebAssemblyによるゲーム開発」に沿ってお試し中

「RustとWebAsseblyによるゲーム開発」という本を買ってみた

以下その本に沿った形で実装していくが、ところどころ、本のままでは動かなかったので細くしながら実装する


開発環境として、この間作ったDockerイメージ(Ubuntu22.04)で引き続きRustによるWebAssemblyのテストをする


Rustを入れる

curl --proto '=https' --tlsv1.3 https://sh.rustup.rs -sSf | sh

環境変数を反映

source $HOME/.cargo/env

バージョンの確認

rustc --version

build-essential は前回入れてるので今回は割愛



npmを入れる

apt install npm


適当なフォルダをつく

って移動

mkdir walk-the-dog

cd walk-the-dog


rust-webpackを入れる

npm init rust-webpack

npm install

npm run start

ブラウザが開いてまっさらの画面が出る・・・のだけどエラーが出てる

wasm-packが入ってくれないらしいので手動インストール

cargo install wasm-pack --force

気を取り直して
npm run start

あんれ・・・
まだコンパイル失敗している
しかたないので、Cargo.tomlに魔法の言葉を入れる

[build]
strip = true

[profile.dev]
strip = true

[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true
opt-level = 'z'
strip = true

しかしまだ失敗・・・
どうも今度はwebpack 4.47.0のあたりで出てるらしいので
package.jsonの以下を書き換えてバージョンを合わせる
"webpack": "^4.47.0",

npm run startで今度は動いてくれた

しかしバージョンだの設定だの面倒すぎる

ここは二度と触りたくないのだが・・・


続けます


lib.rsを以下のように修正

JsCastというのが何なのかはさっぱりだけど

JavaScriptをCastしてRustで使えるってことらしい


use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();
    context.move_to(300.0,0.0);
    context.begin_path();
    context.line_to(0.0,600.0);
    context.line_to(600.0,600.0);
    context.line_to(300.0,0.0);
    context.close_path();
    context.stroke();
    context.fill();
 
    Ok(())
}


cargo.tomlは以下のような状態

[dependencies.web-sys]のfeaturesのところに色々追加している
strip = true もセクションを増やしたりしてあちこちに追加している
(まだまだいじるところが残っているようだ)


# You must change these to your own details.
[package]
name = "rust-webpack-template"
description = "My super awesome Rust, WebAssembly, and Webpack project!"
version = "0.1.0"
authors = ["You <you@example.com>"]
categories = ["wasm"]
readme = "README.md"
edition = "2021"

[build]
strip = true

[profile.dev]
strip = true

[profile.release]
# This makes the compiled code faster and smaller, but it makes compiling slower,
# so it's only enabled in release mode.
lto = true
opt-level = 'z'
strip = true

[lib]
crate-type = ["cdylib"]

[features]
# If you uncomment this line, it will enable `wee_alloc`:
#default = ["wee_alloc"]

[dependencies]
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
wasm-bindgen = "0.2.78"
console_error_panic_hook = "0.1.7"

# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
# compared to the default allocator's ~10K. However, it is slower than the default
# allocator, so it's not enabled by default.
wee_alloc = { version = "0.4.2", optional = true }

# The `web-sys` crate allows you to interact with the various browser APIs,
# like the DOM.
[dependencies.web-sys]
version = "0.3.55"
features = ["console","Window","Document","HtmlCanvasElement","CanvasRenderingContext2d","Element"]

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so it's only enabled
# in debug mode.
#[target."cfg(debug_assertions)".dependencies]
#console_error_panic_hook = "0.1.5"

# These crates are used for running unit tests.
[dev-dependencies]
wasm-bindgen-test = "0.3.28"
futures = "0.3.18"
js-sys = "0.3.55"
wasm-bindgen-futures = "0.4.28"


package.jsonは以下

{
  "author": "You <you@example.com>",
  "name": "rust-webpack-template",
  "version": "0.1.0",
  "scripts": {
    "build": "rimraf dist pkg && webpack",
    "start": "rimraf dist pkg && webpack-dev-server --open -d",
    "test": "cargo test && wasm-pack test --headless"
  },
  "devDependencies": {
    "@wasm-tool/wasm-pack-plugin": "^1.1.0",
    "copy-webpack-plugin": "^5.0.3",
    "webpack": "^4.47.0",
    "webpack-cli": "^3.3.3",
    "webpack-dev-server": "^3.7.1",
    "rimraf": "^3.0.0"
  }
}


index.htmlを以下のように修正してcanvasを追加している

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My Rust + Webpack project!</title>
  </head>
  <body>
    <script src="index.js"></script>
    <canvas id="canvas" height="600" width="600"></canvas>
  </body>
</html>


npm run startすると、ブラウザが立ち上がり以下のような画面が出る


設定など面倒なことも多いが、一応思った通りのことはでき始めているようだ。


続いて、本によるとシェルピンスキーにしたいとのことで、

draw_triangleを作成する

lib.rsは以下のようになる

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    draw_triangle(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)]);

    Ok(())
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3]){
    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
}


ブラウザで三角形が確認できるはず



draw_triangleを足す

    draw_triangle(&context,[(300.0,0.0),(150.0,300.0),(450.0,300.0)]);

ブラウザではこうなる



左と右の三角も足してみる

draw_trigangleのところだけ抽出するとこんな感じ

    draw_triangle(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)]);
    draw_triangle(&context,[(300.0,0.0),(150.0,300.0),(450.0,300.0)]);
    draw_triangle(&context,[(150.0,300.0),(0.0,600.0),(300.0,600.0)]);
    draw_triangle(&context,[(450.0,300.0),(300.0,600.0),(600.0,600.0)]);

ブラウザではこうなっているはず



ここまでひな形ができたので、シェルピンスキの三角形になるように修正する

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    sierpinski(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)],5);

    Ok(())
}

fn sierpinski(context:&web_sys::CanvasRenderingContext2d,points:[(f64,f64);3],depth:u8){
    let depth = depth - 1;
    draw_triangle(&context,points);
    let [top, left, right] = points;
    if depth>0{
        let left_middle = ((top.0 + left.0)/2.0,(top.1 + left.1)/2.0);
        let right_middle = ((top.0 + right.0)/2.0,(top.1 + right.1)/2.0);
        let bottom_middle = (top.0, right.1);

        sierpinski(&context,[top,left_middle, right_middle],depth);
        sierpinski(&context,[left_middle, left, bottom_middle],depth);
        sierpinski(&context,[right_middle, bottom_middle,right],depth);
    }
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3]){
    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
}


画面ではこうなる



次に色を乱数で決定しようということで、Cargo.tomlにrandとgetrandomの行を追加する

[dependencies]
# The `wasm-bindgen` crate provides the bare minimum functionality needed
# to interact with JavaScript.
wasm-bindgen = "0.2.78"
console_error_panic_hook = "0.1.7"
rand = "0.8.5"
getrandom={version = "0.2.3", features = ["js"]}

バージョンは本だと0.8.4だったけど、エラーが出て0.8.5だとわかったので変えた

lib.rsにcolor関連の修正を加える

use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use web_sys::console;
use rand::prelude::*;



// When the `wee_alloc` feature is enabled, this uses `wee_alloc` as the global
// allocator.
//
// If you don't want to use `wee_alloc`, you can safely delete this.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;


// This is like the `main` function, except for JavaScript.
#[wasm_bindgen(start)]
pub fn main_js() -> Result<(), JsValue> {
    // This provides better error messages in debug mode.
    // It's disabled in release mode so it doesn't bloat up the file size.
    console_error_panic_hook::set_once();

    // Your code goes here!
    console::log_1(&JsValue::from_str("Hello world!"));

    let window = web_sys::window().unwrap();
    let document = window.document().unwrap();
    let canvas = document.get_element_by_id("canvas").unwrap().dyn_into::<web_sys::HtmlCanvasElement>().unwrap();
    let context = canvas.get_context("2d").unwrap().unwrap().dyn_into::<web_sys::CanvasRenderingContext2d>().unwrap();

    sierpinski(&context,[(300.0,0.0),(0.0,600.0),(600.0,600.0)],(0,255,0),5);

    Ok(())
}

fn sierpinski(context:&web_sys::CanvasRenderingContext2d,points:[(f64,f64);3],color:(u8,u8,u8),depth:u8){
    draw_triangle(&context,points,color);
    let depth = depth - 1;
    let [top, left, right] = points;
    if depth>0{
        let left_middle = ((top.0 + left.0)/2.0,(top.1 + left.1)/2.0);
        let right_middle = ((top.0 + right.0)/2.0,(top.1 + right.1)/2.0);
        let bottom_middle = (top.0, right.1);
       
        let mut rng = thread_rng();
        let next_color = (
            rng.gen_range(0..255),
            rng.gen_range(0..255),
            rng.gen_range(0..255),
        );
        sierpinski(&context,[top,left_middle, right_middle],next_color,depth);
        sierpinski(&context,[left_middle, left, bottom_middle],next_color,depth);
        sierpinski(&context,[right_middle, bottom_middle,right],next_color,depth);
    }
}

fn draw_triangle(context:&web_sys::CanvasRenderingContext2d, points:[(f64,f64);3],color:(u8,u8,u8)){
    let color_str = format!("rgb({},{},{})",color.0,color.1,color.2);
    context.set_fill_style(&wasm_bindgen::JsValue::from_str(&color_str));

    let [top, left, right] = points;
    context.move_to(top.0,top.1);
    context.begin_path();
    context.line_to(left.0,left.1);
    context.line_to(right.0,right.1);
    context.line_to(top.0,top.1);
    context.close_path();
    context.stroke();
    context.fill();
}


画面は次のようになる




0 件のコメント: