Objective-Cでシューティングゲーム を作成してみる
使用する技術 OpenGLESを使用して作成していきます。 http://ja.wikipedia.org/wiki/OpenGL_ES Objective-Cの仕様に関わる部分を基本に 解説致します。
使用するツール : Xcode
・①ViewController : 画面描画を管理するクラス ・②Sprite : 画面に表示されるオブジェクトを表現するクラス 作成するファイル ・①ViewController : 画面描画を管理するクラス ・②Sprite : 画面に表示されるオブジェクトを表現するクラス
処理の流れ ViewController Sprite viewDidLoad initWithFile glkView render update update
ゲームのルール ・主人公は自動的に上下に動く。 ・画面をタップすると弾を発射する。 ・弾を敵に当てれば敵を倒せる。 ・敵は5体。登場する位置、動くスピードはランダム。 ・敵が主人公に触れる、もしくは弾切れ(5発)になると負け。 ・敵を全て倒すと勝ち。
Objective-Cのクラスの作成方法 分けて記述します。(Javaのinterfaceとその実装みたいなもの) ・ インターフェース部には、クラスのインスタンス変数と、 実装すべきメソッドを宣言します。 ・ インターフェースはヘッダファイル(拡張子 .h)として作成し、 実装部分(拡張子 .m)のファイル内でインクルードします。 ex. Testクラスを作成しようと思ったら、Test.h と Test.mを 作成することになります。
ViewController.h #import <UIKit/UIKit.h> #import <GLKit/GLKit.h> #import "Sprite.h" @interface ViewController : GLKViewController ~ Frameworkに関わるプロパティは省略 // 主人公 @property Sprite * hero; // 敵配列 @property NSMutableArray * enemies; // 全ての描画オブジェクトを管理する配列 @property NSMutableArray * sprites; // 弾配列 @property NSMutableArray * bullets; // 弾が当たった後の画像を保持する配列 @property NSMutableArray * dogezas; // 弾残数 @property int bulletCount; // 敵残数 @property int enemyCount; @end
ViewController.h 解説 @interface クラス名 : スーパークラス名 ここにインスタンス変数やメソッドを宣言します。 @end @propertyと書いた後に変数名を記述すると、ゲッタとセッタも定義したことになります。 NSMutableArrayは変更可能な配列。NSArrayとすると変更不可の配列となります。
Sprite.h① #import <Foundation/Foundation.h> #import <GLKit/GLKit.h> @interface Sprite : NSObject ~ Frameworkに関わるプロパティは省略 // 座標 @property GLKVector2 position; // 加速度 @property GLKVector2 velocity; // 拡大率 @property float scale; // オブジェクトのサイズ @property CGSize contentSize; // 透明度 @property GLKVector4 color; // 透明度の変化率 @property GLKVector4 colorVelocity; ※頭にGLと付くものは、OpenGLESのライブラリで定義されているクラスです。
Sprite.h② ~ 続き // 初期化処理 - (id)initWithFile:(NSString *)fileName effect:(GLKBaseEffect *)effect; // 描画処理 - (void)render; // 毎フレームオブジェクトの位置を計算するメソッド - (void)update:(float)dt; // 当り判定 - (CGRect)boundingBox; @end
Sprite.h② 解説 メソッドの定義方法 - (戻り値の型)メソッド名:(引数の型)引数名; 頭に「-」を付けると、メソッドを定義したことになります。 (id)のidは型で、オブジェクトはどのクラスでも、idという特別な型で表現されます。 そのため、initWithFileはオブジェクトを返却するメソッドということになります。 NSStringは変更不可な文字列クラスです。 NSMutableStringは変更可能な文字列クラスです。 尚、Objective-Cでは文字列は以下の様な形式で表現します。 @"文字列" 後ほど使用例が登場します。
ViewController.m : viewDidLoad (初期処理) ~ Frameworkの初期処理は省略 self.hero = [[Sprite alloc] initWithFile:@"hero.png" effect:self.effect]; /// 主人公作成 self.hero.position = GLKVector2Make(860, 320); // 主人公の位置を設定 self.hero.velocity = GLKVector2Make(0, 300); // 主人公の移動速度を設定 [self.sprites addObject:self.hero]; // 管理用配列に追加 self.enemies = [NSMutableArray array]; // 敵管理配列生成 for (int i = 0; i < 5; i++) { // 敵オブジェクト生成 Sprite * enemy = [[Sprite alloc] initWithFile:@"jyoumu.png" effect:self.effect]; int rand = arc4random() % 640 + 10; enemy.position = GLKVector2Make(0, rand) // 敵の位置をランダムに決める; rand = arc4random() % 100 + 10; enemy.velocity = GLKVector2Make(rand, 0); // 敵の速度をランダムに決める; [self.sprites addObject:enemy]; [self.enemies addObject:enemy]; } self.bulletCount = 5; self.enemyCount = 5; self.bullets = [NSMutableArray array]; self.dogezas = [NSMutableArray array];
ViewController.m : viewDidLoad (初期処理) 解説① Objective-Cでは「メッセージ式」というものが頻繁に登場します。 例えば以下のようなオブジェクト、objが宣言されていたとします。 id obj; このobjが、msgというメソッドを持っていた場合、以下のように呼び出します。 [obj msg]; これはobjに対し、msgというメッセージを送信して、msgメソッドを呼び出しています。 これが「メッセージ式」と呼ばれるものです。 メッセージ式は、オブジェクトが受信したメッセージを処理した値を返却します。 この場合、[obj msg]が、msgメソッドの戻り値そのものとなります。
ViewController.m : viewDidLoad (初期処理) 解説② インスタンスの生成は以下の様に行います。 [クラス名 alloc] よって、以下の式はSpriteクラスのインスタンスを生成した後、 initWithFile, effectメソッドを呼び出しています。 引数は 「: 引数」のように表します。 生成したインスタンスをself.heroという変数に代入しています。 self.hero = [[Sprite alloc] initWithFile:@"hero.png" effect:self.effect]; /// 主人公作成 尚、変数selfはその処理を行っているオブジェクト自身を指します。 (Javaでいう「this」のようなものです) この場合、ViewController.mのインスタンスを指します。
ViewController.m : viewDidLoad (初期処理) 解説③ オブジェクトを生成したら、その管理用の配列に追加しています。 以下の例で言えば、自身のプロパティspritesは配列なので、 spritesのaddObjectメソッドを呼び出し、自身のプロパティのheroを引数として渡し、 配列の要素としています。 self.hero = [[Sprite alloc] initWithFile:@"hero.png" effect:self.effect]; /// 主人公作成 self.hero.position = GLKVector2Make(860, 320); // 主人公の位置を設定 self.hero.velocity = GLKVector2Make(0, 300); // 主人公の移動速度を設定 [self.sprites addObject:self.hero]; // 管理用配列に追加
ViewController.m : glkView (描画処理) // 弾描画 for (Sprite * sprite in self.bullets) { [sprite render]; } // 主人公描画 NSLog(@"hero.position.y == %f", self.hero.position.y); if (self.hero.position.y >= 600) { self.hero.velocity = GLKVector2Make(0, -400); } else if (self.hero.position.y <= 30) { self.hero.velocity = GLKVector2Make(0, 400); [self.hero render]; // 敵描画 for (Sprite * sprite in self.enemies) { // 弾が当たった時のエフェクト描画 for (Sprite * sprite in self.dogezas) {
ViewController.m : glkView (描画処理) 解説 ログ出力はNSLogクラスを使用します。 使い方は書式文字列と、それに対する引数を渡して使用します。 C言語のprintf()と良く似ています。 NSLog(@"hero.position.y == %f", self.hero.position.y); 上記の場合、「%f」の箇所に、引数の「self.hero.position.y」が展開されます。 配列は以下のように要素を巡回して操作することができます。 for (Sprite * sprite in self.enemies) { [sprite render]; } 敵配列の要素を巡回して、全ての要素の描画処理を呼び出しています。
ViewController.m : update (更新処理①) for (Sprite * sprite in self.sprites) { [sprite update:self.timeSinceLastUpdate]; } NSMutableArray * bulletsToDelete = [NSMutableArray array]; NSMutableArray * enemiesToDelete = [NSMutableArray array]; for(Sprite * enemy in self.enemies) { if ( CGRectIntersectsRect(self.hero.boundingBox, enemy.boundingBox)) { [self viewAlertTitle:@"ゲームオーバー!!" viewAlertBody:@"出向になった。。。"]; for (Sprite * bullet in self.bullets) { if ( CGRectIntersectsRect(enemy.boundingBox, bullet.boundingBox)) { [bulletsToDelete addObject:bullet]; [enemiesToDelete addObject:enemy]; [self generateDogeza:bullet.position]; self.enemyCount--;
ViewController.m : update (更新処理②) for( Sprite * bullet in bulletsToDelete ){ [self.bullets removeObject:bullet]; [self.sprites removeObject:bullet]; } for( Sprite * enemy in enemiesToDelete ){ [self.enemies removeObject:enemy]; [self.sprites removeObject:enemy]; NSMutableArray * toDelete = [NSMutableArray array]; for( Sprite * dogeza in self.dogezas ) { if ( dogeza.color.w < 0 ) { [toDelete addObject:dogeza]; for ( Sprite * dogeza in toDelete ) { [self.dogezas removeObject:dogeza]; [self.sprites removeObject:dogeza]; if (self.enemyCount == 0) { [self viewAlertTitle:@"ゲームクリア!!" viewAlertBody:@"出向を阻止した!!"];
ViewController.m : generateDogeza (弾が当たった時の画像表示処理) - (void)generateDogeza:(GLKVector2)position { Sprite * dogeza = [[Sprite alloc]initWithFile:@"dogeza.png" effect:self.effect]; GLKVector2 emptyVector2 = GLKVector2Make(0, 0); dogeza.position = GLKVector2Add(position, emptyVector2); dogeza.colorVelocity = GLKVector4Make(0, 0, 0, -1); [self.sprites addObject:dogeza]; [self.dogezas addObject:dogeza]; }
ViewController.m : touchesBegan (画面をタップした時の処理) - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (self.bulletCount == 0) { [self viewAlertTitle:@"ゲームオーバー!!" viewAlertBody:@"声が出なくなった。。。"]; } [self shotBullets]; self.bulletCount--; GLKViewControllerを継承したクラス内 (正確にはUIResponder, またはUIViewを継承しているクラス)で、 上記名前のメソッドを実装すれば、画面をタップした時に自動的に呼び出されるようになります。
ViewController.m : shotBullets (画面をタップして、弾を撃つ処理) - (void)shotBullets { Sprite * bullet = [[Sprite alloc]initWithFile:@"message.png" effect:self.effect]; bullet.position = self.hero.position; bullet.velocity = GLKVector2Make(-200, 0); [self.sprites addObject:bullet]; [self.bullets addObject:bullet]; }
Sprite.m : update (更新処理) - (void)update:(float)dt { GLKVector2 deltaX = GLKVector2MultiplyScalar(self.velocity, dt); self.position = GLKVector2Add(self.position, deltaX); GLKVector4 deltaC = GLKVector4MultiplyScalar(self.colorVelocity, dt); self.color = GLKVector4Add(self.color, deltaC); } ※他殆どFramework的な処理だったので省略
サンプルを持ってきたので試して見て下さい。 動作確認 サンプルを持ってきたので試して見て下さい。
参考 ・簡単なiPhoneゲーム制作の解説 http://kozukamahiro.blog.fc2.com/blog-entry-3.html OpenGLESを使う為の方法や、ゲームを作る上でのテクニックなど、殆どの部分を参考にさせて頂きました。 ・詳解 Objective-C 2.0 第3版 http://p.tl/mrKA Objective-Cの仕様を詳細に解説して下さっています。 ボリュームは多いです。