まぁ!きれい!(感涙)
OpenGLで作るiPhone SDKゲームプログラミング - 3章
第2章で作成したプロジェクトにパーティクルシステムを追加しました。
パーティクルとは粒子のこと。
細かいテクスチャを大量に描画することによって、煙や雪などの不定形な要素を描画する。
だそうです。
GPUに重い負荷をかけることになりますが、少しのコードでリッチな演出を実現できるので、便利ですね〜。
もっと違う画像を用意したり、動きの付け方に工夫が色々できそうですが、自分の頭で思い描いた通りに使いこなすのは難しそうだ・・・
パーティクルシステムを実装するときのポイントは、
- パーティクルの数を適切に設定してやること
- パーティクルの寿命を適切に設定してやること
- 必要に応じてフレームごとに(あるいは数フレームに数個)パーティクルを追加していく
- 演出に応じた動きをつけてやること
例えば、動き回るオブジェクトの座標にフレームごとにパーティクルを追加していけば軌跡みたいになるし、1箇所に多数のパーティクルをいっぺんに追加して、広がるような動きにしてやれば敵を倒したような演出になります。左右に多少ぶれながら下に落ちていくようにして、寿命を長めにすれば、雪みたいになるでしょう。
参考までに、インターフェイスはObjective-C、中身は速度重視のためCで書いた実装です。
書籍のサンプル実装とはだいぶ違いますが、やってることはほぼ同じです。
Particle.h
typedef struct { float x, y, size; // 位置、大きさ float mx, my; // 移動量 bool active; // 活性フラグ int frame; // 生成からのフレーム数 int life; // 寿命フレーム数 } PARTICLE; // パーティクルクラス @interface Particle : NSObject { int _count; // 次回生成するパーティクルの数 int _memct; // メモリ内に読み込まれているパーティクルの数 int _life; // 1パーティクルの寿命フレーム数 PARTICLE* _particles; // パーティクル } @property(assign, nonatomic, readwrite) int count; @property(assign, nonatomic, readwrite) int life; // 初期化メソッド -(id)init; // 準備メソッド -(void)prepare; // パーティクル追加 -(void)addX:(float)x Y:(float)y size:(float)size moveX:(float)moveX moveY:(float)moveY; // 描画メソッド -(void)draw:(GLuint)texture;
Particle.mm
@implementation Particle @synthesize count = _count; @synthesize life = _life; // 初期化メソッド -(id)init { self = [super init]; if (self) { _count = 0; _memct = 0; _particles = NULL; } return self; } // 破棄メソッド -(void)dealloc { [self releaseParticles]; [super dealloc]; } // 準備メソッド -(void)prepare { if (_particles) { [self releaseParticles]; } _memct = _count; _particles = (PARTICLE*)malloc(sizeof(PARTICLE) * _memct); for (int i = 0; _memct > i; ++ i) { _particles[i].active = false; } } // パーティクル追加 -(void)addX:(float)x Y:(float)y size:(float)size moveX:(float)moveX moveY:(float)moveY { PARTICLE* p = _particles; // 非アクティブのパーティクルを1つアクティブにする for (int i = 0; _memct > i; ++ i) { if (!p->active) { p->active = true; p->x = x; p->y = y; p->size = size; p->mx = moveX; p->my = moveY; p->frame = 0; p->life = _life; break; } ++ p; } } // 描画メソッド // ここでは移動制御、寿命管理と描画をひとまとめにしてしまっている。 -(void)draw:(GLuint)texture { // アクティブなパーティクルの情報を更新しながら、数を数える int activeParticleCount = 0; PARTICLE* p = _particles; for (int i = 0; _memct > i; ++ i) { if (p->active) { // パーティクルを移動 p->x += p->mx; p->y += p->my; // 寿命更新 p->frame ++; if (p->life <= p->frame) { p->active = false; } else { ++ activeParticleCount; } } ++ p; } // 頂点、色、マッピングのための配列を作成する // 正方形を2つの三角形で表すので、頂点の数は6×2次元となる GLfloat* vertices = (GLfloat*)malloc(sizeof(GLfloat) * 6 * 2 * activeParticleCount); if (!vertices) { NSLog(@"failed to memory allocation."); return; } GLubyte* colors = (GLubyte*)malloc(sizeof(GLubyte) * 6 * 4 * activeParticleCount); if (!colors) { free(vertices); NSLog(@"failed to memory allocation."); return; } GLfloat* coords = (GLfloat*)malloc(sizeof(GLfloat) * 6 * 2 * activeParticleCount); if (!coords) { free(vertices); free(colors); NSLog(@"failed to memory allocation."); return; } // 高速にイテレーションするためのポインタ変数を用意する p = _particles; // すべてのパーティクル構造体へのポインタ GLfloat* vp = vertices; // アクティブなパーティクルの頂点座標へのポインタ GLubyte* lp = colors; // アクティブなパーティクルの色情報へのポインタ GLfloat* rp = coords; // アクティブなパーティクルのテクスチャマッピング座標へのポインタ // 座標配列を構築する for (int i = 0; _memct > i; ++ i) { if (p->active) { // 座標計算 float centerX = p->x; float centerY = p->y; float size = p->size; float vLeft = -0.5f * size + centerX; float vRight = 0.5f * size + centerX; float vTop = 0.5f * size + centerY; float vBottom = -0.5f * size + centerY; // ポリゴン1 *vp++ = vLeft; *vp++ = vTop; // 左上座標 *vp++ = vRight; *vp++ = vTop; // 右上座標 *vp++ = vLeft; *vp++ = vBottom; // 左下座標 // ポリゴン2 *vp++ = vRight; *vp++ = vTop; // 右上座標 *vp++ = vLeft; *vp++ = vBottom; // 左下座標 *vp++ = vRight; *vp++ = vBottom; // 右下座標 // 現在のフレームがパーティクル寿命のどの段階にあるのかを判断 float lifePos = (float)p->frame / (float)p->life; int alpha; // 寿命が半分以上ある場合はフェイドイン if (0.5f >= lifePos) { alpha = (int)(round(lifePos * 2.0f * 255.0f)); } // 半分以下の場合はフェイドアウト else { alpha = 255 - (int)(round(lifePos * 255.0f)); } // 色(6頂点分) // for文よりもべた書きの方が若干だが速い・・・はず *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; *lp++ = 255; *lp++ = 255; *lp++ = 255; *lp++ = alpha; // マッピング座標 *rp++ = 0.0f; *rp++ = 0.0f; // ポリゴン1 - 左上 *rp++ = 1.0f; *rp++ = 0.0f; // ポリゴン1 - 右上 *rp++ = 0.0f; *rp++ = 1.0f; // ポリゴン1 - 左下 *rp++ = 1.0f; *rp++ = 0.0f; // ポリゴン2 - 右上 *rp++ = 0.0f; *rp++ = 1.0f; // ポリゴン2 - 左下 *rp++ = 1.0f; *rp++ = 1.0f; // ポリゴン2 - 右下 } ++ p; } // OpenGLに描画を指示する glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texture); glVertexPointer(2, GL_FLOAT, 0, vertices); glEnableClientState(GL_VERTEX_ARRAY); glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors); glEnableClientState(GL_COLOR_ARRAY); glTexCoordPointer(2, GL_FLOAT, 0, coords); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glDrawArrays(GL_TRIANGLES, 0, activeParticleCount * 6); // メモリ解放 free(vertices); free(colors); free(coords); } // パーティクル解放 -(void)releaseParticles { if (_particles) { free(_particles); _particles = NULL; } _memct = 0; } @end
そんなわけで、ついにハエたたきゲーム完成しました!
ハエの軌跡は白、叩いたときのエフェクトは青にしてみました。
このゲーム面白い!