Javaエンジニアに贈る、JavaでAndroid/iOS両対応のゲームアプリを作れるlibGDXを使って実際にゲームを作ってみよう


[libGDX](http://libgdx.badlogicgames.com/index.html)はマルチプラットフォーム対応のゲーム開発フレームワークです。

似たようなコンセプトのゲーム開発フレームワークとしてUnityやCocos2d-xなどがありますが、libGDXは **Javaで開発ができる** というのが大きな特徴になっています。そのため、「スマホゲームを開発したいけれど、AndroidはともかくiOSはObjective-Cなのでハードルが高くて…」とお嘆きのJavaエンジニアにもってこいのフレームワークなのです。

このドキュメントではlibGDXを使ってJavaでAndroidとiOSの両方のプラットフォーム向けのゲームを作るための基本をお伝えしたいと思います。

## お話の前提

このドキュメントは以下の環境で実際に試した結果に基づいて記載されています。

- プラットフォーム：Android、iOS
- 開発用PC：Mac (OS X Mountain Lion)
- IDE：Eclipse (Kepler), Xcode (5.1.1)

## 環境の準備

[Setting up your Development Environment (Eclipse, Intellij IDEA, NetBeans)](https://github.com/libgdx/libgdx/wiki/Setting-up-your-Development-Environment-%28Eclipse%2C-Intellij-IDEA%2C-NetBeans%29)に記載されている手順に基づいて環境を準備します。

### JDKのインストール

JDKは7以上である必要があります。[Oracleのサイト](http://www.oracle.com/technetwork/java/javase/downloads/index.html)からダウンロードしてインストールして下さい。

### Android SDKのインストール

[Android SDKのサイト](http://developer.android.com/sdk/index.html)からダウンロードしてインストールして下さい。

### Eclipseのインストール

[Eclipseのサイト](https://www.eclipse.org/downloads/)からダウンロードしてインストールして下さい。

### ADT Pluginのインストール

EclipseでAndroidアプリを開発するためのプラグインであるADT Pluginを[アップデートサイト](https://dl-ssl.google.com/android/eclipse/)からインストールして下さい。

### Gradle Pluginのインストール

EclipseにGradleを統合するためのプラグインであるGradle Pluginを[アップデートサイト](http://dist.springsource.com/release/TOOLS/gradle)からインストールして下さい。

### RoboVM Pluginのインストール

（iOSアプリの開発をしない場合は不要です）

libGDXではJavaコードからiOSアプリをビルドするのに[RoboVM](http://www.robovm.com/)を使っています。Eclipse上でRoboVMのコマンドを簡単に呼び出せるよう、[アップデートサイト](http://download.robovm.org/eclipse/)からRoboVM Pluginをインストールして下さい。

### Xcodeのインストール（iOSアプリの開発をしない場合は不要です）

[Appleの開発者サイト](https://developer.apple.com/xcode/downloads/)からXcodeをダウンロードしてインストールして下さい。

### libGDXのセットアップアプリケーションのインストール

libGDXを使ったゲームの開発プロジェクトのひな形を生成するためのアプリケーションを[ダウンロードサイト](http://libgdx.badlogicgames.com/download.html)からダウンロードして下さい。

アプリケーションは実行可能なJAR形式ですので、JARファイル（gdx-setup.jar）をダブルクリックするかターミナルから`open gdx-setup.jar`とすると起動することができます。

## チュートリアル：簡単なゲームを作成してみる

libGDXのサイトで公開されている[チュートリアル](https://github.com/libgdx/libgdx/wiki/A-simple-game)に沿って簡単なゲームを作成してみましょう。

このチュートリアルで使う機能は、

- 基本的なファイルアクセス
- スクリーンをクリアする
- イメージの描画
- カメラの利用
- 基本的な入力処理
- 効果音を鳴らす

です。

### どんなゲームなの？

バケツで雨粒をキャッチするゲームです。

- バケツはスクリーンの下部にある
- 雨粒は加速しながらスクリーン上方から毎秒ランダムに降ってくる
- プレイヤーはバケツを水平に動かすことができる
- ゲームに終わりはない。禅の修行だと思いねぇ :)

### プロジェクトを作成する

まずはプロジェクトを準備しましょう。libGDXのセットアップアプリケーションを起動して空のプロジェクトを作成します。

```sh
$ open gdx-setup.jar
```

各項目を以下のように入力して「Generate」ボタンを押します。Destinationとしてはプロジェクトを保存したい場所を指定して下さい。

- Name: `drop`
- Package: `com.badlogic.drop`
- Game class: `Drop`
- Destination: `/Users/your.name/spike/libGDX/drop`
- Sub Projects: AndroidとIosにチェック
- Extension: デフォルトのまま

![libGDX-001.png](https://qiita-image-store.s3.amazonaws.com/0/39704/83a08e75-85b0-e1aa-7978-5e27607ccdee.png "libGDX-001.png")

プロジェクトを作成したらEclipseにインポートします。

1. メニューから「File -> Import...」を選択してImportダイアログから「Gradle -> Gradle Project」を選択します。
2. 「Import Gradle Project」ダイアログの「Root folder」にプロジェクトの場所（上の例では`/Users/your.name/spike/libGDX/drop`）を指定して「Build Model」をクリックします。
3. しばらくするとプロジェクトが表示されるので、全てのプロジェクトにチェックを入れて「Finish」をクリックします。

![libGDX-002.png](https://qiita-image-store.s3.amazonaws.com/0/39704/ef738a20-30fb-716c-9400-d0a703fd153a.png "libGDX-002.png")

次のように、4つのプロジェクトがEclipse上に追加されます。

![libGDX-003.png](https://qiita-image-store.s3.amazonaws.com/0/39704/93e28eda-4ec6-c7d8-be30-4c8eabbc4bf9.png "libGDX-003.png")

`drop`プロジェクトは全てのサブプロジェクトの親プロジェクトです。`drop-core`プロジェクトは共通モジュールを開発するためのプロジェクト、`drop-android`と`drop-ios`プロジェクトはそれぞれAndroidとiOSに固有の処理を開発するためのプロジェクトです。

さあ開発を進めましょう！

### アセットを準備する

このゲームではアセットとして画像とサウンドを使います。

スクリーンの解像度については、今回は800x480ピクセルとします。（これを拡大縮小してデバイスのディスプレイサイズに合わせて表示するようにします。）これを踏まえて、雨粒とバケツのサイズは64x64ピクセルとします。

雨粒とバケツの画像とサウンドには、今回は次のフリー素材を使うことにします。

- 雨粒のサウンド（drop.wav）：http://www.freesound.org/people/junggle/sounds/30341/
- 雨のサウンド（rain.mp3）：http://www.freesound.org/people/acclivity/sounds/28283/
- 雨粒のスプライト画像（droplet.png）：https://www.box.com/s/peqrdkwjl6guhpm48nit
- バケツのスプライト画像（bucket.png）：https://www.box.com/s/605bvdlwuqubtutbyf4x

これらのアセットをプロジェクトに追加するには、`drop-android`プロジェクトの`assets`フォルダにだけ置けばOKです。`drop-ios`などの他のターゲットプロジェクトには自動的に適用されます。

![libGDX-004.png](https://qiita-image-store.s3.amazonaws.com/0/39704/6851aefd-d572-0266-b97d-f3369253a62c.png "libGDX-004.png")

### アプリケーション全体の設定をする

アプリケーション全体の設定で、アプリの消費電力を抑えるために加速度センサーとコンパスを使わないようにしてみます。

#### Androidプロジェクト

`AndroidLauncher.java`にて設定します。

```AndroidLauncher.java
        ...
        AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
        config.useAccelerometer = false;
        config.useCompass = false;
        ...
```

#### iOSプロジェクト

`IOSLauncher.java`にて設定します。

```IOSLauncher.java
        ...
        IOSApplicationConfiguration config = new IOSApplicationConfiguration();
        config.useAccelerometer = false;
        config.useCompass = false;
        ...
```

### ゲームのコードを書く

以下、ゲームのコードを書いていきます。コードは基本的に`drop-core`プロジェクトの`Drop.java`に書きます。

### アセットをロードする

先ほど用意したアセットを読み込むためのコードを書きます。

```Drop.java
public class Drop extends ApplicationAdapter {
    private Texture dropImage;

    private Texture bucketImage;

    private Sound dropSound;

    private Music rainMusic;

    @Override
    public void create() {
        // 雨粒とバケツのスプライト画像をロードする
        dropImage = new Texture(Gdx.files.internal("droplet.png"));
        bucketImage = new Texture(Gdx.files.internal("bucket.png"));

        // 雨音の効果音と雨のBGMのデータをロードする
        dropSound = Gdx.audio.newSound(Gdx.files.internal("drop.wav"));
        rainMusic = Gdx.audio.newMusic(Gdx.files.internal("rain.mp3"));

        // BGMを鳴らし始める
        rainMusic.setLooping(true);
        rainMusic.play();
    }

    ...
}
```

### カメラとSpriteBatch

カメラは800x480のスクリーンを使ってレンダリングした画面をデバイスのディスプレイサイズに合わせるために使われます。

SpriteBatchは2D画像を描画するために使われる特別なクラスです。

まずソースコードにこれらのクラスのためのフィールドを追加します。

```Drop.java
   OrthographicCamera camera;
   SpriteBatch batch;
```

次に`create()`メソッドの中でインスタンスを生成します。

```Drop.java
   camera = new OrthographicCamera();
   camera.setToOrtho(false, 800, 480);

   batch = new SpriteBatch();
```

OrthographicCameraは平行投影カメラです。平行投影カメラとは視点が無限遠にあり遠近感がつかないカメラで、2Dゲームを作るのに適しています。

第一引数は「Y軸を下向きにするか」で、ここでは`false`を指定して上向きにしています。`800`、`480`はそれぞれビューポートの幅と高さです。これで800x480のエリアに描画されたものが画面に表示されるようになります。

### バケツを追加する

バケツと雨粒の仕様は次のとおりです。

- バケツ／雨粒はX/Yの位置情報を持つ。
- バケツ／雨粒は幅と高さを持つ。
- バケツ／雨粒はグラフィカルな外見を持つ。

位置と大きさを保持するためにlibGDXから提供されている`Rectangle`クラスを使ってバケツを表現します。まずはフィールドを追加します。

```Drop.java
    Rectangle bucket;
```

次に`create()`メソッドの中でインスタンスを生成して初期化します。初期位置は下から20ピクセル上方、左右の位置はスクリーンの真ん中とします。

```Drop.java
   bucket = new Rectangle();
   bucket.x = 800 / 2 - 64 / 2;
   bucket.y = 20;
   bucket.width = 64;
   bucket.height = 64;
```

### メインループの処理を書く

ゲームのメインループでは通常以下のような処理を行ないます。

1. ユーザの入力によって制御されるタイプのオブジェクトについて、ユーザの入力に従って状態を変更する
2. コンピュータによって制御されるタイプのオブジェクトについて、ルールに従って状態を変更する
3. 移動の結果生じるイベントに従って、オブジェクトの状態を変更する
4. 現在のオブジェクトの状態を描画する

具体的にはそれぞれ以下のような処理に対応します。

1. ユーザの入力に従ってバケツを動かす
2. 雨粒を移動させる／新たに生成する
3. バケツで雨粒をすくった場合は音を鳴らして雨粒を消す
4. スクリーンをクリアし、雨粒とバケツを描画する

これらの処理は`render()`メソッドに記述します。

### バケツを動かす

まずはバケツをユーザに操作できるようにしましょう。ユーザがスクリーンにタッチしたらバケツを移動させるために以下のコードを`render()`に追加して下さい。

```Drop.java
       if(Gdx.input.isTouched()) {
          Vector3 touchPos = new Vector3();
          touchPos.set(Gdx.input.getX(), Gdx.input.getY(), 0);
          camera.unproject(touchPos);
          bucket.x = touchPos.x - 64 / 2;
       }
```

最初の行でタッチされたかを確認し、2行目から3行目でタッチ位置を`Vector3`オブジェクトに積めます。今のままだと位置はカメラの座標系になっていないため、4行目の`camera.unproject(touchPos)`でタッチ位置をカメラの座標系に変換します。最後にバケツのX座標をタッチ位置に合わせます。この時画像の大きさの半分を差し引くことでタッチ位置が画像の真ん中に合うようにします。

*注意*：オブジェクトを頻繁に生成するとGCによって処理が停止してしまうため、`touchPos`は毎回インスタンスを生成するのではなく`Drop`クラスのインスタンスフィールドにする方が良いでしょう。

*注意2*：`touchPos`が2Dベクトルではなく3Dベクトルなのは、カメラが3D用だからです。今回のゲームは2Dですが、3Dの平行投影カメラを2D用に流用しているのです。

### 雨粒を動かす

雨粒は複数存在するので、`Rectangle`のリストとして状態を保持します。まずはフィールドを追加します。

```Drop.java
   Array<Rectangle> raindrops;
```

`Array`クラスはJavaの標準コレクションである`ArrayList`の代わりにlibGDXが提供しているユーティリティクラスです。`Array`クラスではガーベッジコレクションを最低限にするための工夫がなされています。この他にもlibGDXではいくつかのコレクションクラスが提供されています。

また、新しい雨粒を落とすかどうかを決定するために雨粒を落とした最終時刻を記録しておく必要があるため、次のフィールドを追加します。

```Drop.java
    long lastDropTime;
```

時刻をナノ秒で記録するためにlongを使っています。

雨粒の生成をコントロールするための`spawnRaindrop()`メソッドを追加します。このメソッドは新しく`Rectangle`インスタンスを生成し、スクリーン上端のランダムな位置に配置されるよう初期化し、`raindrops`配列に追加します。

```Drop.java
   private void spawnRaindrop() {
      Rectangle raindrop = new Rectangle();
      raindrop.x = MathUtils.random(0, 800 - 64);
      raindrop.y = 480;
      raindrop.width = 64;
      raindrop.height = 64;
      raindrops.add(raindrop);
      lastDropTime = TimeUtils.nanoTime();
   }
```

雨粒の状態を保持する配列を初期化して、`spawnRaindrop()`を使って最初の雨粒を落とす処理を`create()`に追加します。

```Drop.java
        raindrops = new Array<Rectangle>();
        spawnRaindrop();
```

最後に雨粒を落としてから十分時間が経過したら新しい雨粒を落とす処理を`render()`メソッドに追加します。

```Drop.java
        if (TimeUtils.nanoTime() - lastDropTime > 1000000000) {
            spawnRaindrop();
        }
```

また、雨粒を動かすための処理を追加します。雨粒は秒速200ピクセルの一定速度で落下させます。またスクリーンの下端に到達したら消します。

```Drop.java
        Iterator<Rectangle> iter = raindrops.iterator();
        while (iter.hasNext()) {
            Rectangle raindrop = iter.next();
            raindrop.y -= 200 * Gdx.graphics.getDeltaTime();
            if (raindrop.y + 64 < 0) {
                iter.remove();
            }
            ...
        }
```

最後に今回のゲームの肝である、バケツで雨粒をすくった時の処理を雨粒の移動処理のループ部分に追加します。

```Drop.java
            if (raindrop.overlaps(bucket)) {
                dropSound.play();
                iter.remove();
            }

```

### 描画する

最初にスクリーンを暗い青でクリアします。`render()`メソッドの末尾に以下のコードを追加します。

```Drop.java
      Gdx.gl.glClearColor(0, 0, 0.2f, 1);
      Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
```

この2行はOpenGLの命令です。最初の行で色を指定し、次の行で実際にクリアを実行します。色の指定はそれぞれ赤、青、緑、アルファで、それぞれ0から1までの値を指定します。

次にカメラを更新します。カメラでは描画のための座標系をセットアップする役割であるマトリックスと呼ばれる数学的なエンティティが使われていますが、カメラの状態や設定を変更した場合はこのマトリックスを再計算する必要があります。このチュートリアルではカメラの状態は変更しないためカメラの更新は不要ではありますが、フレーム毎にカメラを更新するようにしておくと良いでしょう。

```Drop.java
       camera.update();
```

バケツを描画します。

```Drop.java
       batch.setProjectionMatrix(camera.combined);
       batch.begin();
       batch.draw(bucketImage, bucket.x, bucket.y);
       ...
       batch.end();
```

最初の行は`SpriteBatch`がカメラの座標系を使うようにします。次の行で新しいバッチが開始されます。`SpriteBatch`はバッチの開始から終了までの描画命令をまとめてOpenGLに渡します。OpenGLには一度に多くの描画命令を渡す方が効率的なので、こうすることでパフォーマンスが向上します。

バッチ処理に雨粒を描画するコードも追加します。

```Drop.java
        for (Rectangle raindrop : raindrops) {
            batch.draw(dropImage, raindrop.x, raindrop.y);
        }
```

### クリーンアップ

ユーザはいつでもアプリを終了できます。アプリ終了時にゲーム内で生成されたリソースをOSがクリーンアップするのを手助けするコードを書くのはいいアイデアです。

`Disposable`インタフェースを実装しているlibGDXのクラスについては、インスタンスを使わなくなったら`dispose()`メソッドを呼び出してクリーンアップする必要があります。

このチュートリアルで使ったリソースではテクスチャ、サウンド、音楽、`SpriteBatch`がクリーンアップ対象です。`ApplicationListener#dispose()`を次のように実装してアプリ終了時にこれらがクリーンアップされるようにします。

```Drop.java
    @Override
    public void dispose() {
        dropImage.dispose();
        bucketImage.dispose();
        dropSound.dispose();
        rainMusic.dispose();
        batch.dispose();
    }
```

### ポーズとレジュームに対応させる

libGDXはポーズの際にストップしたBGMをレジュームの際に再開したり、ポーズの際に失われた画像リソースをレジュームの際に再読み込みするなど、ポーズとレジュームの際の処理を自動的にやってくれます。

そのためこのチュートリアルでは特にポーズとレジュームに関して処理を書く必要はありませんが、なんらかの処理を自分で行ないたい場合は`AppilcationListener#pause()`と`ApplicationListener#resume()`に書いて下さい。

### 動かしてみる

さあ、これで完成です！実際に動かしてみましょう。

まずは簡単なAndroidから。Androidの実機でゲームを動かすには以下のようにします。

1. 実機をUSB接続する
2. `drop-android`プロジェクトを右クリックして`Run As -> Android Application`を選択する

iPhoneシミュレータで動かすには次のようにします。

1. `drop-ios`プロジェクトを右クリックして`Run As -> iOS Simulator App (iPhone)`を選択する
2. 待つ
3. 待つ
4. ...
5. OutOfMemoryErrorが出たら再度実行する
6. ...
7. 待つ
8. 待つ

（iPhoneの実機で動かすのは割と面倒なのでここでは割愛します…。）

次のような画面が表示されれば成功です！

![game.png](https://qiita-image-store.s3.amazonaws.com/0/39704/5a33ddae-5ba3-039e-71ec-cc6f231c9909.png "game.png")

### 続く...

これでチュートリアルは終わりですが、先に進みたい場合はlibGDXのサイトにある[続き](https://github.com/libgdx/libgdx/wiki/Extending-the-simple-game)を読んでみて下さい。

## おわりに

いかがでしたか？Javaを使ってAndroidゲームだけでなくiOSゲームが作れるって素晴らしいですね！それでは、Enjoy game programming with Java!

## おまけ

### libGDXの情報

- libGDXのドキュメントは[libGDXのGitHub wiki](https://github.com/libgdx/libgdx/wiki)にあります。
    - libGDXのWebサイトのDocumentationページにはムービーしかありません…。

### 横向き対応

横向きのゲームを作る場合、Androidの場合は以下の設定を`drop-android/AndroidManifest.xml`に追加して下さい。

```drop-android/AndroidManifest.xml
<activity
            ...
            android:screenOrientation="landscape"
            ...
```

iOSの場合は以下の設定を`drop-ios/info.plist.xml`に追加して下さい。

```drop-ios/info.plist.xml
    ...
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    ...
```