Rust + Bevyで作ったものをGithubPagesに公開してみた

## なぜこの記事を書こうと思ったのか
先日、Rust + Bevyで作ったゲームを[WebAssembly](https://ja.wikipedia.org/wiki/WebAssembly) (以後Wasm) 化してGithubPagesに[公開](https://8bittd.github.io/jump_cube/)してみたのですが、
その時に意外と簡単に出来るものだなと思った反面、Wasm化のときに音が鳴らない問題に悩まされて困っておりました。
自分の他にも困っている人がいるかなと思い、今回記事にしてみました。
以下の人には参考になるかもしれません。
- Rust + Bevy でゲームを作ってサクッとウェブに公開したい人
- Wasm化したときに音が鳴らなくて困っている人
## 出来上がったもの
https://8bittd.github.io/test_wasm/ 
:warning:クリックすると音楽が流れます:warning:
## 実行環境
- Window10
## 事前準備
- [Rust](https://www.rust-lang.org/ja/tools/install) をインストール
- [Visual Studio Code](https://www602.math.ryukoku.ac.jp/Prog1/vscode-win.html)をインストール
- [Github Desktop](https://docs.github.com/ja/desktop/installing-and-authenticating-to-github-desktop/installing-github-desktop)をインストール
- [Github アカウント](https://docs.github.com/ja/get-started/start-your-journey/creating-an-account-on-github)を作成
### Rustのプロジェクトを作成
```bat
cargo new test_wasm
cd test_wasm
code .
```
```toml
#Cargo.toml
[package]
name = "test_wasm"
version = "0.1.0"
edition = "2024"

[dependencies]
bevy = "0.15.3"
```
```Rust
//main.rs

use bevy::{color::palettes::basic, prelude::*};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(Startup, setup)
        .run();
}

fn setup(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
    asset_server: Res<AssetServer>,
) {
    commands.spawn(Camera2d);
    commands.spawn(AudioPlayer::new(
        asset_server.load("sounds/Windless Slopes.ogg"),
    ));
    commands.spawn((
        Mesh2d(meshes.add(Rectangle::default())),
        MeshMaterial2d(materials.add(Color::from(basic::GRAY))),
        Transform::default().with_scale(Vec3::splat(128.)),
    ));
}
```
:warning: プロジェクトフォルダ/assets/soundsフォルダに以下のファイルを配置しておいてください
`https://github.com/bevyengine/bevy/blob/main/assets/sounds/Windless Slopes.ogg`
`cargo run —release` でビルドするとグレーの図形が表示されて、音楽が流れていると思います

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/285fa08a-ea02-43ca-a8ec-f103914e915a.png)
### Wasm化の手順
プロジェクトフォルダに以下のファイルを配置してください
```index.html
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        background-color: #181818;
      }
    </style>
    <title>jump_cube</title>
  </head>
  <script type="module">
    import init from './wasm/target/test_wasm.js'
    init().catch((error) => {
      if (!error.message.startsWith("Using exceptions for control flow, don't mind me. This isn't actually an error!")) {
        throw error;
      }
    });
  </script>
</html>
```
```build.bat
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli
cargo build --release --target wasm32-unknown-unknown
wasm-bindgen --out-name test_wasm --out-dir wasm/target --target web ./target/wasm32-unknown-unknown/release/test_wasm.wasm
cargo install basic-http-server
basic-http-server -a "127.0.0.1:4000" ./
```
build.batを実行してサーバーを起動する
ブラウザでhttp://localhost:4000 にアクセスする
![image(1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/fddc3396-8bf6-4567-b0b7-2bad9721ca73.png)
図形は表示されてますが、音が鳴っていないことに気づくと思います。
コンソールで調べてみると…
![image(2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/04c44914-15c1-4aae-95f6-990f94243bed.png)
と書いてあって、Audioの再生が止めらているのがわかると思います。
(筆者は、ここで数週間ハマりました。。。orz)
[公式のGithub](https://github.com/bevyengine/bevy/tree/main/examples#audio-in-the-browsers)にも記載がありまして、
>In browsers, audio is not authorized to start without being triggered by an user interaction. 

と書いてありました。。

解決方法なのですが、リンクにある[GoogleChromeのブログ](https://developer.chrome.com/blog/web-audio-autoplay?hl=ja)にヒントが乗っておりました。
> 新しい AudioContext オブジェクトの作成をインターセプトし、ユーザーがさまざまなユーザー操作を実行したときに、これらのオブジェクトの resume 関数を自動的にトリガーします。

記載されているjsコードをWasmが呼ばれる前に実行すると音が鳴るみたいです。
早速jsを配置してindex.htmlを修正してみます。
```プロジェクトフォルダ/wasm/target/restart-audio-context.js
(function () {
  // An array of all contexts to resume on the page
  const audioContextList = [];

  // An array of various user interaction events we should listen for
  const userInputEventNames = [
    'click',
    'contextmenu',
    'auxclick',
    'dblclick',
    'mousedown',
    'mouseup',
    'pointerup',
    'touchend',
    'keydown',
    'keyup',
  ];

  // A proxy object to intercept AudioContexts and
  // add them to the array for tracking and resuming later
  self.AudioContext = new Proxy(self.AudioContext, {
    construct(target, args) {
      const result = new target(...args);
      audioContextList.push(result);
      return result;
    },
  });

  // To resume all AudioContexts being tracked
  function resumeAllContexts(event) {
    let count = 0;

    audioContextList.forEach(context => {
      if (context.state !== 'running') {
        context.resume();
      } else {
        count++;
      }
    });

    // If all the AudioContexts have now resumed then we
    // unbind all the event listeners from the page to prevent
    // unnecessary resume attempts
    if (count == audioContextList.length) {
      userInputEventNames.forEach(eventName => {
        document.removeEventListener(eventName, resumeAllContexts);
      });
    }
  }

  // We bind the resume function for each user interaction
  // event on the page
  userInputEventNames.forEach(eventName => {
    document.addEventListener(eventName, resumeAllContexts);
  });
})();
```
```プロジェクトフォルダ/index.html
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <style>
      body {
        background-color: #181818;
      }
    </style>
    <title>test_wasm</title>
  </head>
  <script type="module">
    import './wasm/target/restart-audio-context.js' <!--←この1行を追加-->
    import init from './wasm/target/test_wasm.js'
    init().catch((error) => {
      if (!error.message.startsWith("Using exceptions for control flow, don't mind me. This isn't actually an error!")) {
        throw error;
      }
    });
  </script>
</html>
```

build.batを実行後に、ブラウザで http://localhost:4000 にアクセスすると音が鳴っていることを確認できると思います。(※クリックするか、キーボードのボタンを押すなど１アクションを起こさないと音は鳴らないみたいです)

### Githubにリポジトリを作成
- GithubDesktopを起動する
File → Add local repositoryで先程作った test_wasm プロジェクトのフォルダを指定する
- ブラウザで https://github.com/ユーザー名 にアクセスして、リポジトリを作成する
![NewRepository.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/7122588e-653d-4e3a-8583-448653121b1b.png)
Ripository Name を test_wasm、Publicにチェックを入れて Create repositoryボタンを押す
- GithubDesktopを再起動して、右上のFetch originをクリック
![FetchOrigin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/e9713cbc-8da2-47f0-99f1-239936bf01af.png)
Publish branchをクリックしてプッシュする
![image(3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/7d76a2a2-c497-43fc-8bf5-415a1f5d0dd0.png)
- https://github.com/ユーザー名/test_wasm にアクセスすると↓この形になっていると思います
![Github.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/f4ac6957-611e-4109-ad30-97a6645abab3.png)
## GithubPagesに公開する手順
Settingsタブをクリックする
![image(4).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/75d82cea-ecf3-47f0-a333-0ac062d873fe.png)
左側のPagesをクリック
![Pages.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/8b946cb8-03b2-46b5-a669-f8b1f842051c.png)
Build and deploymentのSourceをGithub Actionsに設定
![Github Actions.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/dd54beb4-4294-43c7-8e82-2d267974ad6a.png)
GitHub Pages JekyllのConfigureボタンを押す
![Configure.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/7e07febc-8420-4086-a15a-2c08c1cfaa7e.png)
右上の Commit changes… ボタンを押す
![CommitChanges.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/fe4619f2-3f28-4bbb-875d-595e0615e7f3.png)
以上で、公開する手順が終了です。https://ユーザー名.github.io/リポジトリ名 にアクセスすると表示されます。
![githubio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139073/beeeb7f8-0e49-46b5-9c0f-87aee346854d.png)
## 参考にさせていただいたURL
https://github.com/bevyengine/bevy/tree/main/examples#wasm