このゲーム面白れえ!

OpenGLで作るiPhone SDKゲームプログラミング - 2章

ハエたたき

2章まで読み終えました。
2章は、OpenGLを使って、実際に動いて遊べる2Dゲームを作るという構成になっています。
このゲーム、なかなかどうして面白い。
面白いものを作るのは、やはり面白い。
わずか50ページそこらで、これだけの物を作れるなんてすごいじゃないか!

技術的には、1章で学んだことのおさらい、応用、+サウンドちょこっとといったところです。
この章に関してのみ言えば、Open GLの技術を身につけるというよりも、
ゲームプログラミングのノウハウについて、学べることが多かったです。
私はゲームプログラマになるためにこの本を読んでいるわけではありませんが、
それでも勉強になる部分や、面白いと思う点はたくさんありました。

例えば、-2.0〜2.0の範囲のランダムな値を得るというのは、ビジネスプログラミング脳では、
パッとは出て来ないのではないでしょうか。
(そもそもfloatなんて使う機会がほとんどない・・・)

#define RANDF()    ((float)(rand() % 1001) * 0.001f)
targetAngle = RANDF() * 4.0f - 2.0f;

その他、ちょっとためになった箇所をメモ書きしておきます。


Cocoa座標系をOpenGL座標系に変換する

※事前に、OpenGLの座標系を2:3にしておく(iPhone5ではアスペクト比が異なる)

glOrthof(-1.0f, 1.0f, -1.5f, 1.5f, -0.5f, 0.5f);

Cocoa座標をx, y、OpenGL座標をx', y'とすると、

x' = x / 320.0f * 2.0f - 1.0f;
y' = y / 480.0f * -3.0f + 1.5f;

Y座標については正方向が逆なので、掛け算と足し算の符号が逆になる。
以下の式の方が直感的に分かりやすいかもしれない。

x' = (x - 160.0f) / 160.0f;
y' = (240.0f - y) / 160.0f;

160, 240はx, yの原点までの距離である。(原点移動)
160で割っているのは、OpenGLのX座標が-1.0〜1.0までの2.0の幅で、
Cocoaの座標系の幅が320pxであるため。320 / 2で、Cocoaが160倍の
幅を持っているので、その補正ということである。
同様に、Y座標は-1.5〜1.5までの3.0の高さで480pxだから、
480 / 3で、160倍の長さということだ。
考えてみれば、アスペクト比を合わせてあるのだから、幅が160倍ならば
高さも160倍になっているのは至極当然のことである。


正方形のオブジェクトに対する当たり判定は、近似円でやると簡単

「標的の中心座標とタッチされた点との距離が半径以内ならば当たったとする」

// x, y タッチ座標
// targetX, targetY 標的座標
// targetSize 標的の大きさ
float dx = x - targetX;
float dy = y - targetY;
float distance = sqrt(dx * dx, dy * dy);    // 標的からタッチ座標までの距離

if (targetSize / 2 >= distance) {
    // 当たり
}

画面の中央から2.0離れた円周上のどこか(ランダム)

// 画面の中央から2.0離れた円周上のどこかの点
distance = 2.0f;

// ランダムなラジアン角度
float theta = (float)(rand() % 360) / 180.0f * M_PI;

// 三角関数で座標を計算する
targetX = cos(theta) * distance;
targetY = sin(theta) * distance;

その他、自分なりにカスタマイズしてみたところ

全体的にオブジェクト指向にしてみた

なんだか、Rendererにロジックがごっそりと入ってしまっていたので、
標的以外にも、得点計算や時間経過、サウンド処理など、
細々としたクラスにわけてみました。
あと、C++ではなく、Objective-Cクラスにしてみました。
まあ、たいした修正ではないです。


得点計算をカスタマイズしてみた

書籍では1匹100点固定の実装になっていたが、ゲーム性を少し増すために
①ハエが小さいほど高得点とする
②ハエのスピードが速いほど高得点とする
という仕様にしてみました。

// 得点加算メソッド
-(void)hit:(Fly*)target {
	// 倒した標的のサイズが小さいほど高得点とする
	float point = (target.size * -1.0f + [Fly maxSize] + 0.05f) * 100;
	
	// 倒した標的の速度が早いほど高得点とする
	point *= target.speed * 100;
	
	_score += (int)point;
}

これは得点計算を行うScoreクラスのメソッドで、Flyは標的クラスです。


転生処理

上の得点計算の仕様変更に関わる部分で、標的を倒したときに、画面外に移動させるだけでなく、
向き、大きさ、速度をランダムに初期化する転生メソッドを作成しました。
こうしないと、乱数の偏りで、大きなハエばかりになったり、速度の遅いハエばかりに
なった場合に高得点が狙いにくくなってしまうので。

// 転生メソッド
-(void)transmigrate {
	// 向き、大きさ、速度をランダムに初期化する
	_angle = (float)(rand() % 360);
	_size = RANDF() * 0.25f + 0.25f;
	_speed = RANDF() * 0.04f + 0.01f;
	
	// 画面外のランダム地点に移動
	[self moveToRandomPosition:2.0f];
}

リトライボタン

たいした問題ではありませんが、サンプルではViewとRendererを循環参照させているようでした。
別にそれでも動くからいいんですけど、
この場合、UIButtonをRendererにも参照させてやれば良いだけだと思います。
RendererのプロパティにUIButton*を持たせてやって、
Rendererの初期化後に、Viewがセットしてやる。
Renderer側で必要なのは、ボタンの表示非表示の制御だけで、
あとはViewがtouchUpInsideに応じて、RendererにstartNewGameを指示して
やれば良いわけですね。
RendererにViewをまるごと渡してしまうと、ViewのstartAnimationとかをRendererから
呼び出せてしまうわけで、ちょっと良くないかなぁと思いました。

EAGLView.h(抜粋)

@interface EAGLView : UIView
{    
    // ゲームリトライボタン
    UIButton* retryButton;
}

-(void)retryGame:(id)sender;
@end

EAGLView.mm(抜粋、initWithCoder内)

retryButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
retryButton.frame = CGRectMake(100.0f, 300.0f, 120.0f, 40.0f);
retryButton.hidden = YES;
[retryButton setTitle:@"Retry" forState:UIControlStateNormal];
[retryButton addTarget:self action:@selector(retryGame:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:retryButton];
renderer.retryButton = retryButton;

ES1Renderer.h(抜粋)

@interface ES1Renderer : NSObject<ESRenderer>
{
    // リトライボタン
    UIButton* _retryButton;
}

@property(retain, nonatomic, readwrite) UIButton* retryButton;
@end

ES1Renderer.mm(抜粋)

@implementation ES1Renderer
@synthesize retryButton = _retryButton;

-(void)dealloc {
    // リトライボタンを解放する
    self.retryButton = nil;

    // ...
}

// ゲーム描画
-(void)renderMain {
    // ...

    // ゲームオーバー表示
    if ([timer isGameOver]) {
        // ...

        // リトライボタン表示
        if (_retryButton.hidden) {
            _retryButton.hidden = NO;
        }
	
        // ...	
}

// ゲーム開始メソッド
-(void)startNewGame {
    // ...

    // リトライボタンを非表示にする
    _retryButton.hidden = YES;
	
    // ...
}
@end

サウンド再生について

サウンドについては、ほんの少ししか説明されていませんでした。
こちら(↓)の本は、

基礎からのiOS SDK

基礎からのiOS SDK

OpenGLについては一切触れられていませんが、サウンド再生に関しては
そこそこ丁寧に説明されていたと思います。
iPod(というかiPhone内のミュージック)に収納されている曲との連携とかも紹介されています。




ところで、このOpenGLで作るiPhone SDKゲームプログラミング・・・

いえ、買う前から気づいていたんですけどね。

3Dに関する説明が最後の40ページ弱しかないんですね。

もう少し本が厚くなってもいいから、3Dを充実させて欲しかったなぁ・・・と。