Elixirで作る最高のゲームエンジン


Elixirでゲームエンジンを作ろうとしていることについて書きます。

概要

ZpidsというElixirを使ってブラウザゲームを作るためのゲームエンジンです。
以下の機能が搭載されたものになる予定です。

  • WebGLレンダリング(PixiJS)
  • 物理エンジン(Box2D)
  • ボイスチャット
  • オーディオミキサー

誰でも、ゲームやゲームエンジンを作る側にまわることができるゲームエンジンを目指しています。
「作る側にまわる」とは、ゲームにオリジナルのキャラクターやカード、能力、ステージなどを追加したり、ゲームをフォークして新しいゲームを作ったりすることを意味しています。
その目的のために、Zpidsでは拡張性やコードの簡単さを重視しています。そしてオープンソースです(AGPL-3.0)。

Elixirで作る理由

以下ではElixirがゲームづくりに向いている理由を説明します。
それに関連して、Elixirで作るMMOの基礎 #なぜElixirがMMOに向いているか?が面白いです。

高い並列性

Erlang VMは軽量プロセスによる高い並列性が特徴です。
ゲーム内の様々なオブジェクトや処理を軽量プロセスとして動かす事によって、高速に動くゲームを作ることができるはずです。

例えばZpidsでは以下に挙げるような、ほとんどの物を軽量プロセスとして起動します。

  • キャラクター
  • ゲーム内の構造物や落ちているアイテム
  • 物理エンジン
  • オーディオミキサー

パターンマッチング

Elixirは関数定義や条件分岐などでパターンマッチングを使うことができます。
パターンマッチングを行うことで、ゲームを作る上でよくある、複雑な条件分岐などを簡単に書くことができます。

以下は実際に使っている例です。

[https://github.com/ryo33/Zpids/blob/90ec65a5a4d1710de54d733bdce6d73b8c320fea/apps/zpids_game/lib/zpids_game/player.ex#L76-L80]

x-axis
move_x = case {left, right} do
  {true, false} -> -1
  {false, true} -> 1
  _ -> 0
end

マクロ

Elixirではマクロが使えるので複雑なコードを簡単に生成したり、構文を拡張してDSLを作ったりできます。

例えばZpidsで画面上に表示するオブジェクトを定義するときに使うdefobjectはマクロで、コンパイル時にJSON Schemaを使ったバリデーションと関数の定義を行います。

defobject
defmacro defobject(definition) do
  quote do
    json = unquote(definition) |> Poison.encode! |> Poison.decode!
    case ExJsonSchema.Validator.validate(Zpids.Display.Object.schema, json) do
      {:error, x} ->
        raise inspect x
      _ -> :ok
    end
    def definition, do: unquote(definition)
  end
end

Phoenix Channel

PhoenixはElixirで作られたWebアプリケーションフレームワークです。
その機能のひとつであるPhoenix ChannelはWebSocket等を使ってErlangのプロセスと外部のアプリケーション間でのメッセージのやりとりを実現します。
Phoenix ChannelのWebSocket実装は他と比べたときにある程度性能が高いです。
Websocket Shootout: Clojure, C++, Elixir, Go, NodeJS, and Ruby

ZpidsではブラウザとサーバーがWebSocketを使ってメッセージをやり取りするのに使っています。

詳細仕様

イベント

Zpidsの動作の根幹となるのがイベント駆動です。
Zpidsでは、ゲームエンジンやゲームによって起動された大量のプロセスはイベント駆動で協調動作するようになっています。
これによってプロセス同士の結びつきを弱くし、拡張性が低くならないようにします。

例えばプレイヤーに付いてくる小さな妖精を追加したくなったときには、妖精プロセスは、おおまかにはプレイヤーの移動イベントを受け取ったときに妖精自身の移動イベントを配信する処理を書くだけで、プレイヤーの実装を書き換える必要はありません。

イベントのやりとりを司るのがEventDispatcherです。
ElixirのRegistryを使っただけなのでZpidsの根幹にしてはかなり短いコードになっています。

例えば、ゲームを60FPSで動かすために必要なTickイベントもClockモジュールによって配信されます。

描画

Zpidsにおける描画ではディスプレイの状態はElixirの側で管理されブラウザは一定時間ごとにその差分を受け取って更新します。
その処理は主に以下の2つのモジュールによって行われます。

Displayプロセス

プレイヤーの画面と対応して起動されるプロセスです。
後述のDisplay.Objectを作成するためのイベントを受け取ったときに、Display.Objectプロセスを自身にリンクして起動し、ブラウザに対してオブジェクトの追加を通知するためのイベントを配信します。

Display.Objectプロセス

表示したいオブジェクトの状態を通知するイベントを受け取って状態を更新します。
一定時間ごとにオブジェクトの状態の差分(座標や角度など)をブラウザに対して通知するためのイベントを配信します。

現状

イベントのキャンセリングとかデバッグ用のロギングとかが今の最優先事項ですが、どのようにするかまだ決まってないので今は物理エンジンのところを作っています。
あと、プロトタイピングした時のコードをそのまま使っていて、テストが一切ないので、書くべきところには追加していく予定です。

リンク