ぶーーーーん
OpenGLで作るiPhone SDKゲームプログラミング - 4章
レースゲーム(2D)
この第4章では、前のハエたたきと違って、ユーザー操作を主体としたゲームアプリの基盤を作りました。
左下の左右ボタンがハンドル操作、右下のアクセルボタンで加速し、慣性が働くようになっています。
ここ数回に渡って、ごちゃごちゃと日記を書いていく中で、ほぼ4章の終わりまでの作り込みができました。
・テクスチャの読み込み
・車クラスの実装
・チェックポイントの表示
・周回数とラップタイムの表示
・パーティクルの表示
これを制御するゲームクラスと、レンダラーなどの上位クラスを用意するだけで完成。
ゲームクラスの定義
#define CHECK_POINT_NUM 8 class CpGameFacilitator { typedef std::vector<CpCheckPoint*> CheckPointArray; public: CpGameFacilitator(); ~CpGameFacilitator(); CpCar& GetCar(); void SetLeftOn(); void SetLeftOff(); void SetRightOn(); void SetRightOff(); void SetAccelOn(); void SetAccelOff(); void Start(); void Update(); CpTextureObjectsDrawer& SetCheckPointsToDrawer(CpTextureObjectsDrawer&); CpTextureObjectsDrawer& SetNumbersToDrawer(CpTextureObjectsDrawer&); CpTextureObjectsDrawer& SetParticleToDrawer(CpTextureObjectsDrawer&); private: CpCar _car; // ユーザーが操作する車 CpLapCounter _lap; // 周回管理オブジェクト CpParticleSystem _particle; // パーティクルシステムオブジェクトポインタ CheckPointArray _checkPoints; // チェックポイント int _nextCheckPointIndex; // 次のチェックポイントのインデックス }; // 車の軌跡を表示するパーティクル部品クラス class CpCarOrbit : public CpParticle { typedef CpParticle inherited; public: // 生成 CpCarOrbit(float x, float y) : inherited(30) { // x, y座標は車の座標 _x = x, _y = y; // それ以外は自身で決定する _width = _height = 0.05f; _mx = (RANDF() - 0.5f) * 0.002f; _my = (RANDF() - 0.5f) * 0.002f; }; // 1フレームあたりの更新処理 void Move() { // 最初に決定したベクトルに従って移動 _x += _mx; _y += _my; // 残り寿命に応じてフェードイン、アウト float lifePos = (float)_frame / (float)_life; if (0.5f >= lifePos) { _a = (int)(round(lifePos * 2.0f * 255.0f)); } else { _a = 255 - (int)(round(lifePos * 255.0f)); } } private: float _mx, _my; // 1フレームあたりの移動量 };
ゲームクラスの初期化
CpGameFacilitator::CpGameFacilitator() : _particle(30, [&]()->CpParticle*{return new CpCarOrbit(_car.GetPosX(), _car.GetPosY());}) { // チェックポイントを初期化する _checkPoints.push_back(new CpCheckPoint(0.75f, -0.5f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(0.4f, -0.25f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(0.0f, 0.8f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(-0.85f, 0.5f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(-0.3f, 0.3f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(-0.9f, -0.2f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(-0.8f, -0.9f, 0.2f)); _checkPoints.push_back(new CpCheckPoint(0.0f, -0.9f, 0.2f)); _nextCheckPointIndex = 0; CpCheckPoint& cp = *_checkPoints[_nextCheckPointIndex]; cp.SetNext(true); _car._posX = _checkPoints[CHECK_POINT_NUM - 1]->_x; _car._posY = _checkPoints[CHECK_POINT_NUM - 1]->_y; // パーティクルシステムを初期化する _particle.Alloc(); }
ゲームクラスの1フレームごとの更新処理
void CpGameFacilitator::Update() { // ラップタイムを計測する _lap.Update(); // 車の現在位置にパーティクルを発生させる _particle.Generate(); // パーティクルを更新する _particle.Update(); // 車を1コマ分動かす _car.Move(); // チェックポイントの通過をチェックする CpCheckPoint& nextCheckPoint = *_checkPoints[_nextCheckPointIndex]; float dx = _car.GetPosX() - nextCheckPoint.GetX(); float dy = _car.GetPosY() - nextCheckPoint.GetY(); // 処理負荷軽減のため、平方根を取らずに、対象半径の2乗の値と比較する float distance = dx * dx + dy * dy; if (nextCheckPoint.GetRadius() * nextCheckPoint.GetRadius() >= distance) { // チェックポイント通過 nextCheckPoint.SetNext(false); ++ _nextCheckPointIndex; if (CHECK_POINT_NUM <= _nextCheckPointIndex) { _lap.Round(); _nextCheckPointIndex = 0; } _checkPoints[_nextCheckPointIndex]->SetNext(true); } }
レンダラ
-(void)renderMain { CpGameFacilitator& game = *_game; CpTextureObjectsDrawer drawer; // ゲーム時間を1コマ進める game.Update(); // コースを描画する _bg.Draw(_courseTexture.raw); // チェックポイントを描画する glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); game.SetCheckPointsToDrawer(drawer).Draw(_checkPointTexture.raw); glDisable(GL_BLEND); // 車の軌道を描画する glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); game.SetParticleToDrawer(drawer).Draw(_particleTexture.raw); glDisable(GL_BLEND); // 車を描画する glPushMatrix(); glTranslatef(game.GetCar().GetPosX(), game.GetCar().GetPosY(), 0.0f); glRotatef(game.GetCar().GetAngle(), 0.0f, 0.0f, 1.0f); glScalef(game.GetCar().GetSize(), game.GetCar().GetSize(), 1.0f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); game.GetCar().Draw([_carTexture raw]); glDisable(GL_BLEND); glPopMatrix(); // ゲーム状態の数値を画面に表示する game.SetNumbersToDrawer(drawer).Draw(_numberTexture.raw); }
意外と面倒だったのが、ラップタイムの計測処理でした。
書籍のサンプルではNSDateを使っていましたが、今回私は、ゲームクラスを始めとしてレンダラ配下にあるすべてのクラスをC++で実装したので、ラップタイムの計測、表示処理を自力でコーディングする必要がありました。
Cでのミリ秒取得関数はこちらのメモにまとめました。
【ミリ秒を3桁で取得】
// 引数でマイクロ秒を受け取り、ミリ秒の上3桁を返却する関数 int MicroSecondsToMillis(__darwin_suseconds_t t) { // マイクロ秒 → ミリ秒 int milli = t * 1000; // ミリ秒の桁数を数える int milliFigures = 1; for (int i = 10; 0 != milli / i; i *= 10) { ++ milliFigures; } // ミリ秒の上3桁を取得する int milli3 = 0; for (int i = 2; 0 <= i; -- i) { // 先頭1桁の値 int n = (int)(milli / pow(10, milliFigures - 1)); // 桁調整して結果に加算 milli3 += n * pow(10, i); // 取得した桁の値を元の値から減算 milli -= n * (int)pow(10, milliFigures - 1); // 桁数調整 -- milliFigures; } return milli3; }
う〜ん、ここは素直に秒のfloatに変換して、
int milli = (int)floor(lapTime * 1000) % 1000;
と3桁にしちゃえば良かったんですね。
ちょっとこの発想が出て来なかったので、上記のようなガチ手続きを書いてしまいました。
さて、一応見た目はレースゲームっぽいのですが、まだ当たり判定も何もない状態です。
次回、第5章は「衝突判定と衝突応答」とのことです。
楽しみです。