This document written: 2014-05-13 .. 2022-01-21
本(BAG)では、ゲーム開発で必要になると思われる一通りの機能(API)を、インターフェースとして定義するところから解説している。
インターフェースとして定義し、実装クラスと 2 段構えの手順を踏んでいるのは、マリオ先生の目指すところは、Android に限定せず、デスクトップ PC 等でも利用できるようなマルチプラットフォーム化を視野に入れているからだ。
マルチプラットフォーム化の必要がなく、僕のように、ただ単に Android 専用のゲーム開発用フレームワークを作りたいだけであれば、インターフェースではなく、抽象クラスの設計で十分に間に合う。
そこで BAG のこの第 3 章ではインターフェースの解説にはなっているが、インターフェースの考察というよりは、一通りの API のリストアップ作業として参照していきたいと思う。
BAG の Input クラスでは 3 種の入力デバイスに対応する設計となっている。libGDX ではデスクトップ PC 用など汎用的な用途を視野に入れて設計されているため(そのためにインターフェイスで設計し、Android API で実装するという 2 重構造をとっているわけだが)、タッチだけではなくキーボードにも対応しているが、Android 用途に絞れば、キーボード関係は割愛できるし、加速度センサーもゲーム用途ではかなり特殊で現時点において僕は使う予定がないので、やはり割愛することにする。つまりタッチ入力だけでよい。するとより簡略化されて、次のようなわかりやすいインターフェース表現となる:
import java.util.List;
public interface Input {
public static class TouchEvent {
public static final int TOUCH_DOWN = 0;
public static final int TOUCH_UP = 1;
public static final int TOUCH_DRAGGED = 2;
public int type;
public int x, y;
public int pointer;
}
public boolean isTouchDown(int pointer);
public int getTouchX(int pointer);
public int getTouchY(int pointer);
public List<TouchEvent> getTouchEvents();
}
type
、x
、y
、pointer
が getter / setter なしの public メンバーとなっているのは Java の定石からは外れるが、パフォーマンスのために敢えてやっているとのこと。Android では getter / setter の反応が遅いらしい。タッチイベントのように頻繁にアクセスが行われる処理では無視できないのだろう。
もちろん、Hata 版フレームワークとしてはインターフェースとして用意するわけではないのだが、要するにここで定義されているように、タッチイベントオブジェクトを定義するクラスと、タッチイベントをリストから取り出すメソッド、そして、タッチ状態の判定(on/off と (x, y) 座標)用のメソッドの、これらを一通り用意すればいいということになる。
FileIO クラスは 3 種類のメソッドから構成されている。Asset というのは、ゲームプログラム自体に埋め込みでコンパイルされている読み出し専用のデータなので、read のみ用意する。一方、File については、ゲームの設定やセーブデータのように外部保存領域(SDカードなど)に読み書きされるデータなので、read/write 双方を用意することになる。
さらに上記 3 種類に加えて、原著 2 版“Beginning Android Games 2”では、実装段階で、Android API 特有の SharedPreferences
も加えられている。ゲームの設定データのように、《キー名:値》のペアのデータを保存したいようなケースでは、わざわざテキストファイルの読み書きよりも、SharedPreferences
の方が正にうってつけの機能だ。なので僕のフレームワークでもメソッドとして採用することにする。
音といっても単一のクラスではなく、効果音(SE)と音楽(BGM)ではプログラム的に扱いが基本的に異なる。そのため、音に関しては、効果音用の Sound クラスと音楽用の Music クラスの 2 種類の補助クラスを用意する。
効果音というのは、単発(ワンショット)再生されるものなので、一時停止や停止機能は考慮する必要がない。一方、音楽の方はそれらが必要であり、さらにループ再生の on/off といったようなこともある。また内部的にも効果音はデータ全体がメモリーに読み込まれて素早く再生されるのに対し、音楽は少しずつ読み込みながら再生するというストリーミング再生である。内部的にかなり違うことがわかる。
このような両者の違いがあることを押えておけばいいだろう。上記理由のため、BAG においては、Sound クラスは非常に簡単な設計となっているが、実際の libGDX の Sound クラスはもっと多機能で、メソッドの数の上では libGDX の Music クラスよりも上回ってすらいる。あくまでも Music クラスとの比較において、互いの相対的特徴としてこのようになっていると理解するべきであって、Sound クラス単体で絶対的に考えるならば、必要になってくる機能があってもおかしくはない。
そして、効果音や音楽をファイルから読み込んでこれらの Sound や Music オブジェクトとしてインスタンス化するための統括クラスが Audio クラスである。
基本的に Graphics クラスのみのシンプルな設計であり、Graphics クラスで利用するピクセルマップデータ用の補助クラスとして Pixmap クラスが用意されている。ちなみに、OpenGL ES 2.0/3.0 が主体となってグラフィックス関連は非常に高度化している libGDX においては、これら 2 つのクラスの機能は Graphics.Pixmap クラスにまとめられているが、基本的には同じコンセプトとなっている。
BAG のものは draw 関連のメソッドは Pixel(点)、Line(線)、Rect(矩形)、Pixmap(ビットマップデータ)に絞っているが、libGDX の方では Circle や fill 系の命令など、細かい機能まで用意されている。これも必要に応じてどこまで用意するかだが、Circle 程度は Android API の Canvas のメソッドに存在するので、追加しておくことにする。
Pixmap 以外の draw メソッドは、ベクター的に直接図形を描くものであり、一方 Pixmap はラスター的なデータを貼り付けるような機能である。一般的な 2D キャラクターゲームは、Pixmap 主体となることが普通なので、この機能さえちゃんと用意してあれば、画像系のインターフェースとしてはほぼ事足りるものだと思う。Pixmap については、元の Pixmap 全体をそのまま貼り付けるメソッドと、元の Pixmap の一部を切り出して貼り付けるメソッドの 2 種類を用意する。
Game クラスは上述の各種ドライバーインターフェースによって定義されたオブジェクトを一括したエンジンとなるオブジェクトであり、フレームワークを利用するゲーム開発者からそれらのドライバーを直接操作、意識することなく、このゲームエンジンオブジェクトとのみ対話すれば事足りるようにするためのものである。
また、BAG においてはゲームを論理的なスクリーンの集合として扱う。例えば、スタート用のメニュー画面やゲームのメイン画面、設定用画面といったものである。そのため、補助クラスとして Screen クラスを定義する。
Game クラス側では、各種ドライバークラスのオブジェクトを get するためのメソッドが用意され、Screen オブジェクトに関しては getter は getStartScreen()
と getCurrentScreen()
の 2 種があり、さらに、画面は交代するので、setter の setScreen()
も用意されている。
また Screen クラスの詳細は、update() と present() はゲームのメインループそのものであり、pause()
は一時停止、resume()
は再開なので、要するにゲームのセーブ・ロードに関係する。dispose()
は当前、要らなくなった Screen オブジェクトを破棄してメモリーを空けるためのものである。
ここで、メインループが update() と present() の 2 種に分けてあるのは、前者は主にゲームロジックの更新、後者は主に画面描画の更新に関わるループということで分けてあるようだ。しかし両者には呼び出される場所など何の違いもない(常にペアで順番に並んでいる)。実際、libGDX の Screen クラスでは render()
一つにまとめられている。なので、僕のフレームワークでも、 render()
一つに統一することにする。
Game オブジェクトが中心となる。この Game オブジェクトが水面下で、タッチ入力・ファイル入出力・音出力・グラフィックス出力用の各オブジェクトをドライバーとして保持するので、フレームワークを利用してゲーム開発を行うプログラマー側はそれらのドライバーの仕事を意識する必要はない。
ゲーム開発を行うプログラマー側は、主に、各 Screen オブジェクトの内容をプログラムする。Screen オブジェクトは Game オフジェクトの setScreen()
によって Game オブジェクトにセットされ(ファミコン本体にセットされるカートリッジのようだ)、Game オブジェクトはセットされた Screen オブジェクトを使ってメインループで回す。メインループで回された内容が逐次、画面に反映(レンダリング)されるような仕組みになっている(ファミコン本体とつながったテレビのようだ)。
もう少し Java プログラムとして具体的な説明をすると、中心となる Game オブジェクトが各種ドライバーオブジェクトを保持していて、ゲーム開発者が Screen オブジェクトをプログラムする際、Game オブジェクト経由でタッチ入力・ファイル入出力・音出力・グラフィックス出力用のすべての API にアクセスできるようになっている。またぶんぶん回っているメインループは、Game オブジェクト経由で、Screen オブジェクトの render()
を呼び出すようにしてある。そのため、ゲーム開発者側は、Screen オブジェクトのプログラムさえ用意しておけば、あとはよろしくループで回してもらえるのである。自らメインループ構造をもったプログラムの全体構造を構築する仕事をする必要はない。ファミコンのカセットのみ用意すればよく、ファミコン本体は自分で設計する必要はないのである。