DxPortLibを使ってHTML5で動くアプリがつくれるようになるまでの奮闘記

## はじめに

これは私がHTML5ゲームを作れるようになりたいと思うようになってから, DXライブラリで作ったゲームがブラウザ上で動くようになるまでのおよそ2ヶ月間の記録を, 忘れないうちに書き留めたものです.

その時に思っていたこと考えていたことも記事に含めているため, かなり駄文になってしまっています.
emscriptenを使った開発でつまづいたところを [ぶつかった壁一覧](#ぶつかった壁一覧) に並べているので, そこから該当部分に飛んでください. また, **太字に目を通せばある程度この記事の概要が掴める**ように書いたつもりです.

この記事で触れられている内容の成果物へのリンクを先に並べておきます

- DxPortLib(Emscripten対応板) GitHubリポジトリ (<https://github.com/nokotan/DxPortLib>)
- DxPortLibを使って移植してみた過去作のゲーム (<https://github.com/nokotan/DxLibToHTML5Test>)

## ぶつかった壁一覧

- [MAINMODULE=1, SIDEMODULE=1 を使いこなす](#mainmodule1-sidemodule1-を使いこなす) ~ emscripten でスタティックリンク
- [emscriptenでメインループを回す](#emscriptenでメインループを回す)
- [外部ファイルを使えるようにする](#外部ファイルを使えるようにする)
- [音が鳴るようにする](#音が鳴るようにする)

## emscriptenを使ってビルドするのを思い立つまで

EgretEngineとかcocos2d-jsとかを使ったHTML5ゲームの黎明期が終わっているのかな, 自分がそんなゲーム開発事情を知らないまま無謀な就活をやっていたのかなどと思うようになっていた2020年1月, DxPortLibとHTML5という単語がどこかで目に入ってきました. どこに書いていたのかなどはもう覚えていませんし, 気のせいだったのかもしれません. 

でも, **DxPortLibがSDL2ベースで書かれている**ことは知っていて, **emscriptenがSDL2に対応している**ことに気づくまではそんなにかかっていなかったと思います. ならば, **emscriptenとDxPortLibを組み合わせる**ことができるならば, ひょっとしたら自分が大学のサークル活動で関わった**DxLib製のゲームがブラウザで動かせる**かもしれないと思ってからは, 研究(交通工学でした)そっちのけでemscriptenと向き合っていました. 幸いにも, emscriptenのインストールは苦労しなかったと覚えています.

### 参考記事

- EmscriptenとCMakeでのビルド方法 (<https://gist.github.com/faithandbrave/9b3d439d135e63abdbe7>)
- emscriptenでC/C++プログラムをwebブラウザから使うまでの難所攻略 (<https://www.slideshare.net/llamerada-jp/cmu29>)

## ビルドが通って画面が表示できるようになるまで

基本構成として, DxPortLibのスタティックライブラリ(libDxPortLib.a)をソースコードからビルドして, そのスタティックライブラリをゲームのソースコードとリンクして組み込んでいたのですが, これを実現するまでにかなり苦戦を強いられました.

### MAINMODULE=1, SIDEMODULE=1 を使いこなす

- - -

※ 2020/ 5/ 9 追記
最新版の emscripten では、関数ポインタまわりの実装が改善されたのか、`MAINMODULE=1`, `SIDEMODULE=1` の**オプションを追加しなくてもリンクができる**ようになっています。むしろ、出力されるバインディング用 JavaScript ファイルと WebAssembly ファイルの肥大化につながるため、ダイナミックリンクを目的とするとき以外はつけないことを推奨します。

- - -

DxPortLibのスタティックライブラリのビルドにCMakeを使っていたのですが, 次のようにすればemscripten側がよしなにリンクしてくれると思っていました.

```cmake:CMakeLists.txt
# DxPortLib側
add_library(DxPortLib STATIC ${SOURCES})
```

```cmake:CMakeLists.txt
# ゲームプロジェクト側
link_libraries(Game DxPortLib)
```

ビルドは問題なく通るのですが, 実行時に次のようなエラーを吐いてしまいます.

![RuntimeError.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/158514/3232665b-fede-5249-b0a7-7c37e6d27427.png)

```bash:エラーの内容
Error: To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking
Error: exception thrown: Error: abort(To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking). Build with -s ASSERTIONS=1 for more info. (evaluating 'new WebAssembly.RuntimeError(what)'),RuntimeError@[native code]
```

dlopen? なにそれemscriptenでもできるの? など思いながら, <https://github.com/emscripten-core/emscripten/wiki/Linking> を参照していました. どうも SIDEMODULE, MAINMODULE というコンパイルオプションがあやしいまではわかったのですが, この組み合わせ方が分からなくて2, 3日費やした記憶があります. (特定の組み合わせで -fPIC オプションをつけてコンパイルしろみたいなエラーだったり, dyncall_v で関数が undefined だみたいな実行時エラーが出ていたのですが, 再現できなかったのでこれらのエラーの詳細については割愛)

結論としては, **ライブラリ側**(DxPortLib側)**のコンパイルオプションに** `SIDEMODULE=1`, **ゲームプロジェクト側に**`MAINMODULE=1`を指定することで少なくともエラーが出ずに黒い画面だけ出すというところまでたどり着きました.

```cmake:CMakeLists.txt
# DxPortLib側
set(CMAKE_C_FLAGS "-s SIDEMODULE=1")
set(CMAKE_CXX_FLAGS "-s SIDEMODULE=1")
```

```cmake:CMakeLists.txt
# ゲームプロジェクト側
set(CMAKE_C_FLAGS "-s MAINMODULE=1")
set(CMAKE_CXX_FLAGS "-s MAINMODULE=1")
```

それと, `CMAKE_C_FLAGS`**と**`CMAKE_CXX_FLAGS`**を間違っていることに気づかない**という小さなミスもこの解決を遅らせたといういい思い出なのです.

#### 参考記事

- emscripten: Linking <https://github.com/emscripten-core/emscripten/wiki/Linking>

### emscriptenでメインループを回す

あまり対処に時間がかからなかったものですが, 最初はかなり面食らったので, emscriptenでメインループの回し方にも触れておきましょう.

**メインスレッド上でゲームループを回してはいけません.** その代わり, `emscripten_set_main_loop`**を使いましょう**. さもなければブラウザーがフリーズしてしまいます. 

```cpp
# include <emscripten.h>

void mainloop() {
  // ここにゲームのループの中身を書く
}

int main() {
  emscripten_set_main_loop(&mainloop, 0, EM_TRUE)
}
```

ごくまれに`emscripten_set_main_loop`を使っていると, dyncall_v で関数が undefined だみたいな実行時エラーを吐くことがあります. その時は, `emscripten_set_main_loop_arg`を使うと回避できることが多いです.

```cpp
# include <emscripten.h>

void mainloop(void* unused) {
  // ここにゲームのループの中身を書く
}

int main() {
  emscripten_set_main_loop_arg(&mainloop, 0, EM_TRUE)
}
```

#### 参考記事

- emscriptenでC/C++プログラムをwebブラウザから使うまでの難所攻略 (<https://www.slideshare.net/llamerada-jp/cmu29>)

### 外部ファイルを使えるようにする

**外部ファイルを使えるよう**にするためには, **--preload-fileオプションを追加する**だけだったので楽だったのですが, いかんせんもともとのファイル配置とフォルダ階層が1つずれてしまうのがネックだった記憶があります. ただ, フォルダ名を指定できるので, わざわざファイルを1つずつオプションに追加する必要がないのはありがたいところと思っていました.

```cmake
set(CMAKE_EXE_LINKER_FLAGS "--preload-file ./assets")
```

#### 参考記事

<https://emscripten.org/docs/porting/files/packaging_files.html>

### frameBuffer を使うようにする

黒い画面が映るようになってから, 画面の描画に取り掛かったのですが, 次はなぜか画面の上下が逆になる怪現象に悩まされていました. これまた, openGLとDirectXの座標系の取り方の違いなのかななどと思っていましたが, 同じくOpenGLを使うmacOS版ではそのような問題は起こっていない...なぜだろうとまた数日悩みました.

結論は, OpenGLES2がframeBufferに対応しているのにもかかわらず, DxPortLib側がOpenGLES2使用時はframeBufferを使わないという謎設定になっていたので, そこを修正したのでした. 

![UpsideDown.png](https://pbs.twimg.com/media/EPG2DJaUUAEChUt?format=jpg&name=large)

## 音が鳴るようにする

Safari, Google Chromeでは**ユーザーの操作に合わせて AudioContextの設定**をする必要があったので, とりあえずゲームの最初にクリックしてもらってからゲームの初期化を開始するという設計にしました. この対応にもほとんど苦労しなかった記憶です.

```js:index.html
function gameStart() {
  canvas.removeEventListener("click", gameStart);
  callMain();
}

var Module = {
  canvas: (function() {
    var canvas = document.getElementById('canvas');

    canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
      return canvas;
  })(),
  onRuntimeInitialized: function() {
    Module.canvas.addEventListener('click', gameStart);
  }
}
```

## DxPortLibとemscriptenを使ってHTML5ゲームが作れるようになってから

DxPortLibを使ってmacOS版をビルドしていたDxLib製ゲームを優先して, 2日に1つずつ移植を進めていくペースで移植を続け, 最終的に6つのDxLib製ゲームのHTML5移植が完了しました.

DxPortLibを使って移植してみた過去作のゲーム: <https://github.com/nokotan/DxLibToHTML5Test>

ソースコードの変更も, 

- `WinMain`から`main`に変更
- メインループを`emscripten_set_main_loop`とコールバック関数の組みあわせに変更
- 外部ファイルの読み込みパスを変更

程度で済むのがほとんどで, 移植が出来上がるたびにDxLibを使っていた頃の懐かしさに浸っていました.

## 次に何が待っているのか

DxPortLibが本家DxLibより早くandroid, iOSに対応して, かつmacOSにも対応しているのはいいのですが, 残念ながら3Dの機能が全く実装されないまま長く放置されてしまっています. 

しかし, DxLibの長所は2Dゲームが簡単に開発できることの他にも, MMDが動かせることにもあるのではないかと思っています. 

![3DinDxLibForHTML5.png](https://pbs.twimg.com/media/EQ-kLLmU0AAYWlj?format=jpg&name=large)

すでにこの記事の執筆地点で実は本家DxLibのHTML5移植がある程度進んでいます. 次にqiitaに投稿することがありましたら, その開発経緯とつまづいた点をお話ししたいと考えています. ここまで長文にお付き合いありがとうございました. 

### 参考資料

- DxLibの3Dアクションサンプルプログラム <https://nokotan.github.io/DxLibToHTML5Test/DxLibSampleTest/DxLibHTML5Test.html>