Objective-C - ForeFrontier

Objective-Cでシューティングゲーム
を作成してみる
使用する技術
OpenGLESを使用して作成していきます。
http://ja.wikipedia.org/wiki/OpenGL_ES
※OpenGLESに関わる部分は解説致しません。
Objective-Cの仕様に関わる部分を基本に
解説致します。
使用するツール : Xcode
作成するファイル
・①ViewController : 画面描画を管理するクラス
・②Sprite : 画面に表示されるオブジェクトを表現するクラス
処理の流れ
ViewController
Sprite
viewDidLoad
initWithFile
glkView
render
update
update
ゲームのルール
・主人公は自動的に上下に動く。
・画面をタップすると弾を発射する。
・弾を敵に当てれば敵を倒せる。
・敵は5体。登場する位置、動くスピードはランダム。
・敵が主人公に触れる、もしくは弾切れ(5発)になると負け。
・敵を全て倒すと勝ち。
Objective-Cのクラスの作成方法
・
Objective-Cではクラスは「インターフェース」と「実装部分」を
分けて記述します。(Javaのinterfaceとその実装みたいなもの)
・
インターフェース部には、クラスのインスタンス変数と、
実装すべきメソッドを宣言します。
・
インターフェースはヘッダファイル(拡張子 .h)として作成し、
実装部分(拡張子 .m)のファイル内でインクルードします。
ex. Testクラスを作成しようと思ったら、Test.h と Test.mを
作成することになります。
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) {
[sprite render];
}
// 弾が当たった時のエフェクト描画
for (Sprite * sprite in self.dogezas) {
[sprite render];
}
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の仕様を詳細に解説して下さっています。
ボリュームは多いです。