ゲームエンジンとは?

最近のゲームは、ゲームエンジンを使って作られることが一般的である。その理由であるが、多くのプラットフォームに対応するためである。冒頭で紹介したDead Trigger 2はiOSとAndroidに対応する。当然、最終的な実行ファイルは異なる。それらをまったく別々に開発するのでは効率が悪い。そこで、各プラットフォームで共通する部分としない部分に分離するのである。こうすることで、複数のプラットフォーム用に同時作成が可能であったり、他のプラットフォームへの移植が容易になる。ゲームエンジンには、そのような役目もある。

ゲームでは、キャラクタや背景などは必須の要素である。これらも、ゲームエンジンを使うことで、再利用することができる。同様に

  • 物理演算
  • スクリプト処理
  • モーション処理
  • サウンド処理
  • インタフェース

などもゲームエンジンが受け持つことが多い。たとえば、サッカーゲームでボールの動きを物理演算で実装したとしよう。今度は別のボールゲームを作る場合、サッカーゲームで作成したルーチンを応用でき、開発コストを削減できる。図3は、デモでも使われているゲームエンジンのUnityである(個人では無償で利用可能)。

図3 Unityゲームエンジン、デモプロジェクトでAngry Botsが起動

C/C++からJavaScriptへ

どうやって一般のゲームをFirefox上で動かすのか? 答えを先にいってしまうと、以下の手順である。

 C/C++ → LLVMの中間コード → asm.jsで最適化されたJavaScript

まずは、C/C++から中間コードへの変換である。ここでは、コンパイラ基盤のLLVM使われる。

図4 LLVM処理の流れ

フロントエンドと呼ばれる処理系で、ソースから仮想機械向けの中間コード(LLVM IR)を生成する。バックエンドでは、中間コードを実行コードにしたり、他の言語に変換する。細部まで説明すると長くなるので、要点のみをあげよう。

  • LLVMは、言語やアーキテクチャから独立
  • 中間コードを含め、コンパイル時に最適化が行われる

ゲームエンジンなどは、C/C++で記述されることが多い。そこで、まずゲームエンジンを中間コードにコンパイルする。コンパイルには、Clang(クランと発音)という、C、C++、Objective-C、Objective-C++向けのコンパイラフロントエンドが使われる。

作成された中間コードを、JavaScriptにコンパイルするのが、バックエンドのEmscripten(エムスクリプテンと発音)である。

デモなども紹介されているので、試してみてほしい。図6は、エディタのVimをFirefoxで起動したものだ。

図6 Vim.js

筆者の環境にもインストールしてみた(簡単ではないが、やる価値はあるかも)。Hello World表示程度のソースをHTMLに変換したのが、図7である。

図7 Emscriptenでコンパイルしたファイルの実行結果

JavaScriptは、変数の型が事前に決まっていない。実行段階で判断したり、型変換をするので時間がかかる(つまり、遅くなる)。そこで、Emscriptenは型付き配列(Typed Arrays)を使ったJavaScriptコードに変換する。結果、人間が書いたコードよりも実行が高速といわれる。

JavaScriptをasm.jsでさらに最適化

LLVMやEmscriptenの最適化でも、ゲームエンジンのような大きなプログラムでは十分なスピードが得られない(小さなプログラムでは、ネイティブの3倍以内に収まることが多い)。そこで、さらなる最適化のために、JavaScriptのサブセットのasm.jsが使われる(Mozillaが推進している)。簡単な例を1つ紹介しよう。

var a = 1;

という記述にアノテーションを追加し、以下のように記述する。

var a = 1|0;

「|0」が付いたことで、内部的にint32型として扱う。型がわかったので事前にコンパイル(AOTコンパイル)すれば、より高速に実行される。当然、ブラウザ側もasm.jsに対応する必要がある(Firefoxは、22のOdinMonkeyエンジンで対応)。デメリットは、高速化のための記述を、人が直接書くことが難しいことだ(デバッグもかなりめんどうになる)。そこで、Emscriptenのようなコンパイラに出力させる方法を使う。ams.jsが有効かどうかは、コンソールで確認できる。

図8 コンソール、Successfully compiled asm.js codoと表示される