HTML5 ゲームを支える技術勉強会に参加しました
6/17のHTML5 ゲームを支える技術勉強会に参加してきました。 connpass.com 各発表の内容と感想等をまとめます。
HTML5 ゲームフレームワーク開発について
発表の目的
アジェンダ
ゲーム開発用に必要な要素
HTML5でどう実現する?
グラフィック描画
- WebGLを使う
- requestAnimationFrameで定期的に更新
- ブラウザの描画タイミングを拾えるAPI
- 最高60FPSで呼ばれる
- 負荷が高いとフレーム間隔が伸びる
- 60FPS前提でフレームごとに固定値のアニメーションは❌
- じゃあどうするか
- performance.now()でフレーム間隔を算出する
- 起動からの経過時間をマイクロ秒単位で取得できる
- 経過時間を算出し時間ベースでアニメーションさせる
サウンド再生
- Web Audio APIを使用する
- エフェクトかけたり色々できる
- 自動再生ポリシーへの対応
- ユーザの操作を契機に再生開始する必要がある
- メモリ使用量に注意
- 解放バグや解放のためにテクニックが必要です
- バックグラウンドでも音が鳴り続ける
- ライフサイクル制御により連携が必要
- その他の問題や対策などqiitaにあります
ムービー再生
プレイヤーとの対話
- MouseEventsとTouchEventsを併用
- イベントリスナーでキャッチできる
- 離すイベントが発火しないパターンに注意
- PointerEventsがやってくる
- MouseとTouchの統合イベント
リソースの管理
- ブラウザキャッシュで通信量節約
- IndexeddbでJS主導の通信量節約
- idb-cacheを使っている
ライフサイクル管理
- resizeイベントを検知してからcanvasリサイズ
- リサイズは少し待ってからする
デバッグ機能
- ブラウザ備え付けの開発者ツール使う
- drecom/stats.js
PWAゲーム開発の課題と対策
クライアント・サーバのアーキテクチャ紹介
クライアント(図で階層分けてあった・・)
- Application(SPA)
サーバ(こっちも図がありましたが、構成要素だけ抜き出し)
通信量をできるだけ減らすためのアセット配信
- ユーザの期待
- 出先でも遊べるようにとにかく通信量を減らして欲しい
- どんなに面白いゲームでも通信量をがかさむゲームは遊びたくない
- 期待に応えたい、でもゲームはwebアプリに比べアセットが肥大化しやすい
- 最近はそうアセットサイズが1GMを超える事も珍しくない
- ディスクキャッシュ容量はモバイルSafariだと50MB程度しかない
- CacheStorageは救世主になるか
通信量を減らすためにできる事
- Cache-Controlで1年キャッシュ
- キャッシュの削除戦略
- キャッシュの取得方法
- デフォルトでgzip圧縮対象でないファイル形式のgzip対応
- 圧縮フォーマットの利用
- アトラス化
- 画像のアトラス化はやって当たり前
- 音声データのアトラス化もある
- asm.jsを利用したJSのwebassembly対応
- 両方用意しておいてwebassembly非対応のブラウザの場合はasm.js側のファイルをリクエストする
ソースコードや通信内容が容易に見れる中でのチート対策
API通信内容の秘匿化
- 大前提としてリクエスト・レスポンスの内容は簡単に見ることができる
- リクエスト内容がわかると同じリクエストを複製したり一部を書き換えて偽装したリクエストを送ることが容易になる
- botを作られるリスクが上がる
- レスポンス内容がわかるとそこからゲームの攻略情報が漏れてしまう危険性がある
対策
- リクエスト内容を共通鍵暗号方式で暗号化、復号する
- 暗号化処理はC++で書かれたものをEmscriptenを使ってasm.js/WebAssembly化して行う
- 通常リクエスト本文をヘッダを含めて暗号化し、POSTリクエストのbodyに入れて送る
- 復号は専用のnginx moduleを通して行い。bodyの中身を復号したのち、本来のリクエスト内容を入手してアプリケーションサーバにリバースプロキシする
- レスポンスの暗号化・復号
- リクエストと同じく共通鍵方式で暗号化、復号する
- 暗号化はc++で書かれた処理をcgo経由でGoのアプリケーションサーバから直接呼ぶ
- 復号は同じリクエストと同じ
アセットパスの類推対策
- アセットへのアクセス内容は全て簡単に見ることができる
- アセットパスが類推可能な場合公開前のリソースを抜かれるリスクがある
- アセット内容がjsonなどのテキストデータの場合は直接見ることができるのでゲームの攻略情報に繋がる情報を取得されるリスクがある
アセットパスの類推対策案
- パスの途中にサーバが払い出した文字列を含める
- パス全体をハッシュ化して難読化する
- ファイル名を含めたパスを完全に難読化する
- 拡張子も、存在しないと動かない場合を覗きできる限りとる
- 問題: 難読された後のパスを使ってデバッグするのは困難
実際に行った対策
- サーバが払い出したハッシュ値を使って組み立てたアセットパス全体を難読化する
- 難読化ロジックはc++で記述し、Emscriptenで変換する(JSのライブラリを利用していない理由は、変換後のソースが読みにくい事をあえて利用しているから)
- アセットパス全体を難読化するのはステージング環境に限定し、開発環境ではハッシュ値付きの状態では扱う
- デバッガビリティの確保
アセット自体の秘匿化
画像の秘匿化
- 画像はそのままだとdevtoolを使って見る事やダウンロードすることができてしまう
- リリース前に実験したが、実装の問題でメモリに対する負荷を許容できなかったため見送りしたがやり方を紹介
- HTMLImageElementを作るとどうしてもdevtoolに表示されてしまう
- DataURLSchemaを使ってもダメ
- WebGLに対応していることが前提になるが、WebGLRenderingContext.tex(Sub)Image2DにはImageDataを渡すことができる
アプリケーションコードの秘匿化
- 共通鍵暗号・復号を行なっている箇所を特定されたり鍵を取られると今までの対策が破られてしまう
- uglifyをかけた程度のソースコードでは該当の処理を特定される
- 元のソースコードをA、暗号化した後のソース事をxhr経由で取得して復号した後にevalするプラグラムをC++で作成し、これをemscriptenで変換して利用する
インゲームのチート対策
- ゲームの構成をインゲームとアウトゲームに分けた場合、インゲームはそのゲームの遊びのキモでありやりがいがある部分
- それだけに攻撃を受けやすい
ロジックをどこで動かすか
- クライアントのみで動かす場合
- チーターから攻撃を受けやすく対策もしづらい
- サーバのみで動かす場合
- チートの攻撃は受けにくく対策もしやすい
- サーバアプリケーションを書く必要がある
- 通信していない事を意識されるゲーム体験を提供したい場合は応答速度に気を配る必要がある
- クライアントとサーバの両方で動かす場合
- 比較的ターン制のゲームに向いている
- 同じロジックがサーバでも動くでチートはほぼ不可能でクライアントで計算した結果をすぐに表示できるのでユーザ体験もよくなる
- アーキテクチャ的にも複雑になる
PWAのネイティブアプリ化
- 期待されている事
- 通信量の削減
- パフィーマンスの向上
- 各種ブラウザ起因のバグの解消
- 実際の実装はwebviewを使うことがほとんどなのでブラウザ特有の問題は残る
- 通信量の削減だけは対応できた(xhrとimage編)
- WKWebViewのWKUserScriptを.atDocumentStartとして作れば任意のJSをアプリケーションロード前に読み込まれることができる
- プリソードするJS内でXMKHttpRequestやHTMLImageElementの読み込みりょりをフックしてURLをネイティブ側にプロキシする
- 動画編
- あらかじめネイティブ側でダウンロードしたバイナリデータをblobに変換し、与えたblobデータを使って動画を再生できるようになる
まとめ
- 触れられなかった具体的な話はbuilderscon tokyo 2019でする予定
HTML5でリアルタイム対戦の3Dシューティングゲームを開発してしまった。反省会
- 技術的な条件
- 3Dでサクサク
- 縦画面
- 片手で遊べる
- 20人/サーバ
- iPhone6(+)対応
- 技術選定
- Unity❌
- モバイルで3D動かない
- three.js❌
- エディターがない
- PlayCanvas⭕️(?)
- 一見なかなかいい
- 短期間でそれっぽいプロトタイプ
- エディタがいい
- 3Dサクサク
ドローコール管理
ドローコールの予算を立てる(100~150)
- キャラクター、環境、発射体、シャドウ
- スキンしたメッシュのバッチングはできない
- マテリアルを減らす
- カスタムシェーダを必要最低限
- ライティング戦略
- ライティングは贅沢
- Scene全体に対し、ライト1つだけ
- ポイントライトは必要最低限に
- ベイクしたライティングを使用したいが、
- バグがでとる
- ダウンロードサイズが大きくなる
- 静的オブジェクトは一度生成したらそのあとも使うのでドローコール1回で済むようにしている
FBXのインポートに要注意
- PlayCanvasのFBXインポート機能は素晴らしい
- マテリアルが組み込まれているFBXがあるとドローコールが倍に
- パフォーマンスまとめ
- 3Dアセット=重い
- エンジン信用するな
ネットワーク
- 要件
- latency: 20~40ms(LTE/4G)
- websocket: Head-of-Line blocking(1つのパケットを失うとその後のパケットも再度通信が必要に)
- MTUを見極める:〜1400bytes超えちゃうと2個になる=>latencyが上がる
- 通信不良によるパケット損失
- 解決策
- メッセージのサイズを1265bytesに制限
- 移動:メッセージをバッチし、50msごとに通信
- バッチにタイムスタンプをつけて、100ms以上古いメッセージを捨てる
- 補完と外挿
- ライブイベントはリアルタイムで通信
ビルドサイズ
- ビルドサイズ7〜10MB
- モデルはtextureなしで250k(45k圧縮)
- CDNコスト3~5万円/月 for 5000~7000 DAU
- キャッシュが残らない
- PlayCanvasのアセットバンドリングを対応していない
ネットワークまとめ
- メッセージングサーバはややこしい
- ネットワークの深いところまで知っておかないと痛い目にある
- ビルドサイズは気をつけないとバカにならない
まとめ
- 3Dの課題は技術だけでは解決できない
- 完璧なフレームワークはまだまだ
- Unityがなんとか頑張ってくれる事を期待