Go言語で作ったゲームをWebで公開してみた

以前から個人的に気になっていたGo言語製のゲームエンジン[Ebitengine](https://ebitengine.org/ja/)をプライベートで触ってみました。
その際に、ebitengineで作ったゲームを静的ページとしてWebにデプロイできることを知ったので、今回試して見ました。

## Ebitengine
Go言語で作られたオープンソースなゲームエンジンです。
Go言語でゲーム開発が可能です。

https://ebitengine.org/ja/

2Dゲーム開発に用いられることが多いようで、商用ゲームを含む[色々なゲーム](https://ebitengine.org/ja/showcase.html)の開発に用いられているそうです。



## 簡単なゲームを作成
今回試しに、以下のような簡単なゲームを作ってデプロイしてみました。

<img width="800" alt="running_gopher.gif" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676273/257c046f-7a66-b6ff-9733-f60e898d102d.gif">

公開したゲームのURLは記事の最後の方に記載しています。

## 作ったゲームをWebで公開
以下の手順でデプロイしました。
1. WebAssembly (WASM) 形式にコンパイル・WASMファイルを生成
2. WASMファイルをブラウザで実行するために必要なJavaScriptファイルをプロジェクトディレクトリにコピー
3. 2.で作成したJavaScriptファイルを実行しWASMファイルを読み込むためのHTMLファイルを作成
4. 1., 2., 3.で作ったファイルをホスティングサービスで公開

1.〜3.に関しては[Ebitengineの公式ドキュメント](https://ebitengine.org/en/documents/webassembly.html)に記載されてある内容になります。
本記事でも全く同じことをしているので、公式ドキュメントを参考にされる方が良いかもしれないです。
また、Unix/Linux環境を前提として説明していくので、Windows環境の方は公式ドキュメントを参考にすることをよりお勧めします。

### 1. WebAssembly (WASM) 形式にコンパイル・WASMファイルを生成
下記コマンドをターミナルで実行すれば、WASMファイルが自動生成されます。
```shell
env GOOS=js GOARCH=wasm go build -o gopher.wasm .
```
`-o`オプションでアウトプットの形式を指定し、その後の`.`ではbuild対象のソースのパスを指定すれば大丈夫です。

### 2. WASMファイルをブラウザで実行するために必要なJavaScriptファイルをプロジェクトディレクトリにコピー
Ebitengineでは、WASMをブラウザで実行するために必要なJavaScriptのブートストラップコードが提供されています。
このコードが書かれたJavaScriptファイルがGoのインストールディレクトリにあるはずなので、以下のようなコマンドを実行して、JavaScriptファイルをWASMファイルを置いたディレクトリと同じ場所にコピーします。
```shell
cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
```

### 3. JavaScriptファイルとWASMファイルを読み込むためのHTMLファイルを作成
以下のようなHTMLファイルを作成します。
読み込むWASMファイルのパス (`fetch()`関数の引数) さえ適切であれば、あとはコピペで大丈夫だと思います。
```html
<!DOCTYPE html>
<script src="wasm_exec.js"></script>
<script>
// Polyfill
if (!WebAssembly.instantiateStreaming) {
    WebAssembly.instantiateStreaming = async (resp, importObject) => {
        const source = await (await resp).arrayBuffer();
        return await WebAssembly.instantiate(source, importObject);
    };
}

const go = new Go();
WebAssembly.instantiateStreaming(fetch("gopher.wasm"), go.importObject).then(result => {
    go.run(result.instance);
});
</script>
```

### 4. 1., 2., 3.で作ったファイルをホスティングサービスで公開

WASMファイル、JSファイル、HTMLファイルをホスティングサービスで公開していきます。
今回はホスティングサービスとして[Vercel](https://vercel.com/)を使用しました。

ちなみに、デバッグするためにHTMLファイルを直で表示しようとすると、インラインスクリプトで`fetch()`関数経由でリクエストを飛ばす際にWebサーバが必要そうでした。
#### 4-extra. Node.jsサーバを用意
なので、今回はNode.js (express) サーバを作って、そこでWASMを読み込んだHTMLファイルを返すことにしました (公開するだけなら多分ここまでしなくて良いです)。
下記のようにHTMLファイルを返すだけのものです。
```js
const express = require("express");
const path = require("path");

const app = express();

app.use(express.static("game"));

app.get("/", (_, res) => {
  res.sendFile(path.join(__dirname, "/game/main.html"));
});

app.listen(3000, () => {
  console.log("Listening on port 3000");
});
```
`res.sendFile(path.join(__dirname, "/game/main.html"))`のパス部分は、Node.jsプロジェクトを作った場所に応じて適宜変更してください。

##### 最終的なディレクトリ構成
```
.
├── game
│   ├── assets/
│   ├── go.mod
│   ├── go.sum
│   ├── gopher.wasm
│   ├── main.go
│   ├── main.html
│   └── wasm_exec.js
├── index.js
├── node_modules/
├── package-lock.json
└── package.json
```

#### 4-2. Node.jsサーバ or HTML・JS・WASMファイルをデプロイ
4-1.で作ったNode.jsサーバか、HTML, JS, WASMファイルをVercelでデプロイします。Node.jsアプリケーションをgithubにアップロードしていればリポジトリごとデプロイ可能です。
ここからはVercelでのデプロイ方法の説明になるので省きます。

## 公開したもの
ゲーム: https://go-game-ponsaoki.vercel.app/game/main.html
※スペースキー押さないとゲームが始まらないので、現状PCでしか動かせないです🙇‍♂️

リポジトリ: https://github.com/ponsAoki/go-game
※要リファクタ

## おわりに
Ebitengine個人的にちょっと興味あるので、また趣味で触ってみようかなと思います。

## 参考
https://ebitengine.org/en/documents/webassembly.html