前回は、画像ファイルを読み込んでOpenGLのテクスチャとし、背景を描くところまで説明した。今回は、キャラクタを描いてみることにしよう。また、アニメーションのための準備も行なっておく。

キャラクタのための画像

まず、キャラクタのための画像を用意しよう。今回作ろうとしているゲームはタワーディフェンスものなので、攻めて来る敵、味方である砲台、砲台から発射される弾、などが必要となる。敵は歩きながら攻めて来るので、数パターンの画像が必要になるだろう。

そのような画像が用意できたとして、それをどのようにアプリ側に渡すべきだろうか。一つのやり方は、キャラクタの画像を1つずつ別ファイルとして保存することである。確かにこのやり方もある。だがこの方法だと、あるキャラクタのあるパターンごとにOpenGLテクスチャを作成することになる。テクスチャ番号の管理が必要になるし、大量のテクスチャ生成によるパフォーマンスの低下も懸念される。

そこで、複数のキャラクタを一つの画像ファイルにまとめてしまうことにしよう。こうすれば、1つの画像ファイルを読み込んで、1つのテクスチャを生成するだけですむ。表示するときは、テクスチャ座標を適切に設定すれば、目的のキャラクタだけを表示することができるだろう。

今回は、次のような画像を用意してみた。4つのキャラクタが含まれている。上に並んでいるのが敵キャラクタだ。2パターンを切り替えて使う。下に並んでいるのが砲台と弾ということになる。

アニメーションの仕組み

このようなキャラクタを描画して動かしたいのだが、ここでアニメーションのやり方を説明しておこう。

今回は、Xcodeが用意しているテンプレートを利用してプロジェクトを作成している。実は、このテンプレートではあらかじめアニメーションのための仕組みが用意されている。その管理を行なっているのは、EAGLViewクラスだ。EAGLViewクラスには、アニメーションに関連した次のようなメソッドやプロパティが用意されている。

List 1.

@interface EAGLView : UIView
{
    ...
}

// アニメーション中かどうかを表す
@property (readonly, nonatomic, getter=isAnimating) BOOL animating;
// フレーム間のインターバルを表す
@property (nonatomic) NSInteger animationFrameInterval;

// アニメーションを開始する
- (void) startAnimation;
// アニメーションを停止する
- (void) stopAnimation;
// 描画を行なう
- (void) drawView:(id)sender;

@end

startAnimationメソッドを呼び出す事で、アニメーションが開始する。実際のところは、デフォルトではアプリが起動するとすぐにこのメソッドを呼び出すので、常にアニメーションが行なわれていることになる。

では、アニメーションの開始とは具体的に何を行なっているのかというと、描画を行なうためのdrawViewメソッドを定期的に呼び出す事、だけである。デフォルトの設定では、1秒間に60回drawViewメソッドを呼び出している。drawViewメソッドで行なっている事は、ES1Rendererクラスのrender呼び出すだけである。つまり、前回renderメソッドを上書きして背景の描画を行なったが、実はこれは1秒間に60回行なわれていたのだ。

ここで、同じrenderメソッドの中で敵や味方のキャラクタを描くとしよう。そして、メソッドが呼び出されるたびに、描く位置を少しずつずらすのだ。これが、アニメーションとなる。ということは、キャラクタを描くときにその座標を覚えておき、renderメソッドの呼び出し、つまり1ステップ進む毎に座標の更新を行なえば、自在にキャラクタを動かす事ができるようになる。

敵キャラクタの描画

では、キャラクタの描画を行なってみよう。まず、敵キャラクタを描く。

用意したキャラクタ画像を読み込んで、テクスチャを生成しよう。これは、前回説明した背景画像の読み込みと同じ手順になるので、詳しい説明は省略する。

続いて描画に必要な情報を考えよう。敵を描画するためには、まずその座標を管理する必要があるだろう。今回は敵キャラクタの画像として2パターンを使っているので、そのどちらを使うかも決めておく必要がある。

さらに、敵を動かす事を考えよう。まずどのくらいの速度で動かすかを決めておかなくてはいけない。また、敵は背景にある道に沿って動かしたい。ということは、道のどこを歩いているかも管理できるようにしないといけないだろう。

これらの事を考えて、ES1Rendererクラスに、敵キャラの座標を管理するためのインスタンス変数を追加しよう。

List 2.

@interface ES1Renderer : NSObject <ESRenderer>
{
    ...

    // 敵の座標
    GLfloat enemyX;
    GLfloat enemyY;
    GLint   enemyImage;
    GLuint  enemyStep;
    GLint   enemyRoadIndex;

enemyXとenemyYは、描画を行なう座標だ。enemyImageは、2つのパターンのうちどちらを使うかを表すもので、0か1が入る。enemyStepは、速度の調節に使うもの。emenyRoadIndexは、道に沿って歩かせるために使うものだ。

これらを使って、敵の描画を行なってみよう。drawEnemyというメソッドを作る。

List 3.

- (void)drawEnemy
{
    // 頂点を作成する
    GLfloat vertices[] = {
        enemyX - 0.1f, enemyY - 0.1f,
        enemyX + 0.1f, enemyY - 0.1f,
        enemyX - 0.1f, enemyY + 0.1f,
        enemyX + 0.1f, enemyY + 0.1f,
    };
    glVertexPointer(2, GL_FLOAT, 0, vertices);

    // 色を設定する
    GLubyte colors[] = {
        255, 255, 255, 255,
        255, 255, 255, 255,
        255, 255, 255, 255,
        255, 255, 255, 255,
    };
    glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);

    // テクスチャの設定をする
    GLfloat coord[] = {
        enemyImage * 0.5f, 0.5f,
        (enemyImage + 1) * 0.5f, 0.5f,
        enemyImage * 0.5f, 0,
        (enemyImage + 1) * 0.5f, 0,
    };
    glTexCoordPointer(2, GL_FLOAT, 0, coord);

    // 描画を行なう
    glBindTexture(GL_TEXTURE_2D, charTexture);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glBindTexture(GL_TEXTURE_2D, 0);

    ...

基本的な流れは、背景の描画と同じだ。頂点の作成、色の設定、テクスチャの設定を行うことになる。頂点座標は、enemyXとenemyYを使って指定している。

少し気をつけてほしいのは、テクスチャの設定のところだ。今回は複数のキャラクタを1つのテクスチャにまとめているので、そのテクスチャ上での座標を適切に設定する事で、描画するキャラクタを決定している。ただし、敵キャラクタは2つのパターンがあるので、enemyImageの値に応じて座標位置を修正する必要があるのだ。

このようなソースコードで、敵キャラクタを描画することができるだろう。

長くなってしまったので、今回はここまでにしよう。次回は敵キャラクタを動かす事を考える。