第2回html5jゲーム部勉強会 oh! javascript...
TRANSCRIPT
夢の続きを語ろうよ
Emscriptenの逆襲HTML5編
sun6925 とよしま
ウェブ大好きっ子
低レイヤー大好きっ子
レトロPC大好きっ子
X68000大好きっ子
わーい!すごーい!たーのしー!
知ってますか?● なぜか 編集部が作ったゲーム
○ 創刊10周年記念 PRO-68K に収録(雑誌付録ディスク)
○ 編集部制作の強力インフラ
■ MAGIC2 - 3D描画ライブラリ
■ Z-MUSIC - サウンドドライバ
○ ゲーム自体、普通に面白い
○ 25年前!
実演原作@エミュレータ
あれ?ショボい!
256 x 256 ドット 16 色 ワイヤフレーム
・・・
んじゃ、高解像度化しますか
原作をウェブで
Powered by Emscripten
run68 (in C)
sion2.x(Binary for X68k)
実行
JavaScript VM
実行
互換レイヤー(in JavaScript)
DOS/IOCSMAGIC
Z-MUSICGVRAM I/O
z-music.js
zmusic.x(Binary for X68k)
非同期RPC化
ここで気合ハイレゾ化するぞ!
X68Sound.dll (in C++)
run68 (in C)
基本方針:エミュレータ+黒魔術
高解像度+16:9化
256 pixel
256 lines
ピクセルアスペクト比
4:3 1080 lines
1440 pixel
4:3
1920 pixel
16:9
アスペクト比補正+ベクトル拡大
+本来見えていなかった部分の表示
ワイヤーフレームは楽勝
スプライトやBGパターンの描画
座標とパタン番号等を拾ってCanvasに高解像度で描画
X : 140Y : 80Pattern: 1Xflip : NoYflip : No
I/OアクセスからGVRAMの中身を追跡
❏ (140, 8)を表示座標に変換
❏ Pattern 1なら星を描画
ビットマップ文字のベクターフォント置き換え
一部の文字(4×6サイズ)が画像だった
● 仕方ないのでGVRAMのアクセスパタンから文字推定
{ “111010101110101010100000”: 64, ... }
文字コード64 A
内部で使ってる主な技術
● Web App Manifest : ホーム追加・全画面起動
● HTML5 Canvas(2D)○ requestAnimationFrame : 60fps
○ ダブルバッファ
○ WebFonts : Canvasで正しく使おう
● Gamepad API / TouchEvent
● DeviceOrientationEvent
● Web Audio API : FM音源と音響処理
● Web MIDI API : 外部音源による演奏対応
モバイルでホームに追加・全画面起動
Web App Manifest (manifest.json)
❏ ファイルを置くだけ { "name": "SION2 HD", "icons": [ … ], "start_url": "index.html", "display": "standalone", "orientation": "landscape"}
画面描画
描画ループにrequestAnimationFrameを使う❏ Emscripten内からはemscripten_set_main_loop(fps=0)を使いましょう
void emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop)
Canvas 2面を重ねて交互に表示❏ display: [none|block]を交互に切り替える
❏ オフスクリーン→Canvas転送は(読み出しが)遅いので避けるべき
WebFontsをCanvasで正しく使う❏ 遅延読み込みを正しく扱う必要があるので注意しましょう
WebFontsについて補足(1)
遅延読み込みについて❏ 実際にレンダリングに必要になったときに読み込みが始まる
❏ 読み込み完了までは利用できない❏ HTMLで使っている分には自動的に読み込み待ちや再描画が行われる
❏ Canvas API経由で使うためには正しく読み込みを待って描画する必要がある
正しく読み込み待ちするための手順(Edge非対応)1. document.fonts (FontFaceSet)にフォントを登録
2. フォントの読み込みを開始させる
3. フォントの読み込み完了を待つ
WebFontsについて補足(2)
1. FontFaceSetに登録❏ FontFaceで作成したフォントの場合: document.fonts.add(fontFace)
❏ CSSで指定したフォントの場合、自動的に登録されるので考慮不要
2. フォントの読み込みを開始させる❏ FontFaceで作成したフォントの場合: FontFace.load()
❏ 登録済みのフォントの場合: document.fonts.load(“50px myfont”)
3. 読み込み完了を待つ❏ フォント個別: FontFace.load / document.fonts.loadの返すPromiseを待つ
❏ フォント全部: document.fonts.ready : Promise を待つ
Gamepad API
❏ Androidでも利用可能❏ この手(→)のBluetoothの物が使える
(Amazonで今日現在260円!!!)
❏ Cardboard対応を考えると一家に一台
❏ APIの仕様は極めて単純明快
❏ ただし、WebKit系では利用できない
const gamepads = navigator.getGamepads();// gamepads[].axes[], gamepads[].buttons[]をチェック
TouchEvent
❏ タッチ対応デバイスならmobile/desktopで利用可能❏ マルチタッチも対応可能
❏ 基本的にネイティブアプリと同等の処理ができるはず
❏ 使いやすい「方向キー」実現のためにはノウハウも必要
今回利用したアルゴリズム例:
❏ タッチ地点を起点として、移動した方向に入力を取る
❏ 逆方向の移動を検知したら入力キャンセル、起点の取り直し
ノウハウを持った人によるライブラリ化が望まれる
DeviceOrientationEvent
❏ Cardboardを使ってヘッドトラッキング!❏ この手(→)のスマホ用VRケースが使える
(Amazonで今日現在998円!!!)
❏ EventListenerを登録するだけ
デバイスの傾きがEular角で通知される
→3D計算の視点に取り込めば向いた方向で視点を動かせる
window.addEventListener('deviceorientation', e => { // e.alpha, e.beta, e.gamma});
Web Audio API
❏ JavaScriptでリアルタイム波形合成❏ 現仕様はノイズ耐性が低い(描画負荷やGCに気をつける)
❏ 今年登場すると思われるAudio Workletを使えば改善される
❏ FM音源エミュレーション❏ とは言いつつEmscriptenでコンパイルしたC++コードでも動いてる
❏ バッファサイズ2048の場合:~42msecごとに1-2msecの合成処理
❏ リバーブ❏ ConvolverNodeにインパルス応答を渡すだけで実現可能
Web MIDI API
❏ Web MIDI経由で外部音源による演奏をサポート❏ 中で動いているZ-MUSICに対し、MIDIボードに対するI/O操作を
フックしてWeb MIDIに繋ぎこんでいる
❏ Web Audio / MIDIについて詳しく知りたい人❏ Web Music Developers JP
❏ WebAudio.tokyo
まとめ
● エミュレータ+黒魔術で昔のゲームを移植
● HTML5でネイティブ同様の作り込みが可能○ モバイルホーム画面追加・全画面起動
○ 60フレーム+ダブルバッファでヌルヌル描画
○ リッチなフォント
○ ゲームパッド・タッチ操作
○ ヘッドトラッキングによる視点移動
○ リアルタイム合成による音源エミュレーション
○ 外部楽器を用いたBGM演奏
質問など
Emscriptenまめ知識〜その1
描画ループにrequestAnimationFrameを使う
❏ emscripten_set_main_loop()を使いましょうvoid emscripten_set_main_loop(em_callback_func func, int fps, int simulate_infinite_loop)
❏ fpsに0を指定するとrequestAnimationFrameが使われる
❏ それ以外の値だとsetTimeoutを使うので注意❏ 失敗談:知らずにsetTimeoutを書き換えて中からrAF呼んでた
❏ simulate_infinite_loop=1でこの関数から戻らない
Emscriptenまめ知識〜その2
常駐するサービス・ライブラリを作る
❏ emscripten_exit_with_live_runtime()で常駐void emscripten_exit_with_live_runtime(void)
❏ ひとまず実行終了
❏ ただしatexit()系やリソース解放処理は走らない
❏ JavaScriptからC/C++の関数を継続して呼び出し可能
❏ 空のmain()から呼べば共有ライブラリっぽい物が作れる
Emscriptenまめ知識〜その3
JavaScriptから呼び出せる関数を(確実に)作る
❏ リンク時に-s EXPORTED_FUNCTIONS="..."❏ "..."にはmainを含めた関数のリストを渡す
例:"['_main', '_myAPI1', '_myAPI2']"
❏ 関数名は"_"のプレフィクスが付くので注意
❏ 数値以外は直接渡せない点に注意❏ C/C++側のメモリ空間はModule.HEAPU8等でアクセス
❏ Module.cwrap()やWebIDL-Binderなどの道具はある
Emscriptenまめ知識〜その4
JavaScriptのライブラリを呼び出す
❏ リンク時に--js-library myLibrary.jsで組み込む❏ 渡すJavaScriptは特定の方法で記述されている必要がある
mergeInto(LibraryManager.library, { myFunc: function(a, b) { // ここから外の世界は直接触れる(this==Window) console.info(‘hello my func’); return a + b; }, // 以下、同様に関数を定義});
❏ 登録なしでCから呼ぶとundefined symbolで実行時abort
Emscriptenまめ知識〜その5
実行開始を遅延させる
❏ preInit内でaddRunDependency()を呼ぶ❏ 対応するremoveRunDependency()が呼ばれるまで待つ
❏ WebFonts読み込みを待つ例preInit: function() { Module.addRunDependency("fonts"); document.fonts.ready.then(function() { Module.removeRunDependency("fonts"); });},