ゲーム作ってます。
本日はイラストレーターで電車を描きました。
モチーフは総武線(E231系)です。
eggmobile
SpriteBuilderとcocos2dを使ったゲーム開発もついに最終回です!長い間お疲れ様でした!
今回は、物理演算を使って当たり判定を導入し、ポイントのカウントもします。最後なので少し書くコードの量が多目ですが、スクリーンショットやサンプルコードを見ながら頑張って実装してみてください。
Obstacle.mにcollisionTypeを設定します。
[code language=”objc” title=”Obstacle.m”]
// CCBファイルからロード
– (void)didLoadFromCCB {
// 上のパイプの当たり判定を設定
_topPipe.physicsBody.collisionType = @"level";
_topPipe.physicsBody.sensor = TRUE;
// 下のパイプの当たり判定を設定
_bottomPipe.physicsBody.collisionType = @"level";
_bottomPipe.physicsBody.sensor = TRUE;
}
[/code]
ここで設定した「level」という名前は、パイプや地面といったキャラクターが触れたら死ぬオブジェクトすべてに適応します。
続いてMainScene.hを編集します。CCPhysicsCollisionDelegateを実装します。
このデリゲートはふたつのオブジェクトが衝突した際に呼ばれます。
[code language=”objc” title=”MainScene.h” highlight=”2″]
#import "CCNode.h"
@interface MainScene : CCNode <CCPhysicsCollisionDelegate>
@end
[/code]
先ほどObstacle.mでも上下のパイプに当たり判定の設定(@levelで指定)をしましたが、MainScene.mでも同様に主人公と地面に対して当たり判定設定を行います。
didLoadFromCCBに追記していきましょう。
[code language=”objc” title=”MainScene.m” highlight=”15,16,21,22,23,24″]
// CCBファイルからロード
– (void)didLoadFromCCB {
// タッチ操作可能にする。
self.userInteractionEnabled = TRUE;
// 地面その1とその2を配列に追加
_grounds = @[_ground1, _ground2];
// 最初の障害物を設置する
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
// 地面の表示順を変更する
for (CCNode *ground in _grounds) {
// 地面の当たり判定設定
ground.physicsBody.collisionType = @"level";
ground.zOrder = DrawingOrderGround;
}
_hero.zOrder = DrawingOrdeHero;
// デリゲートとして設定する
_physicsNode.collisionDelegate = self;
// 主人公の当たり判定設定
_hero.physicsBody.collisionType = @"hero";
}
[/code]
続いて、SpriteBuilderでの設定に移ります。
上下のパイプを選択し、物理演算を可能にします。また、Dynamic→Staticに変更します。
最後に衝突時の処理を実装します。
[code language=”objc” title=”MainScene.m”]
// 衝突時の処理
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair hero:(CCNode *)hero level:(CCNode *)level {
NSLog(@"Game Over");
return TRUE;
}
[/code]
それではビルドして確認してみましょう。
障害物に接触するか、地面に接触すると、「Game Over」とログを出します。
障害物にあたった際の動きを確認できたので、今度はゲームオーバーとリスタートのしくみを実装します。実際のゲームでは、
SpriteBuilderで、リスタートボタンを設置します。
コネクションとセレクタを設定します。
あとでリスタートボタンが押された時に反応するようXcode側でも設定します。
最後に位置を中央に設定し、表記をRestartに変更します。そして、Visibleのチェックを外し、見えないようにします。
リスタートボタンは、ゲームオーバーになった時に表示されるようにします。
Xcodeを開き、MainScene.mに追記していきます。
まずは変数を宣言します。
[code language=”objc” title=”MainScene.m” highlight=”9″]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
CCNode *_ground1; // SpriteBuilderと接続する地面その1
CCNode *_ground2; // SpriteBuilderと接続する地面その2
NSArray *_grounds; // 地面ループ処理用のArray
NSTimeInterval _sinceTouch; // 最後にタッチしてからどれだけ経過したか
NSMutableArray *_obstacles; // 障害物を格納する配列
CCButton *_restartButton; // リスタートボタン
}
[/code]
つぎに、衝突時の処理をするメソッドに、リスタートボタンを表示させるように設定します。
[code language=”objc” title=”MainScene.m” highlight=”4,5″]
// 衝突時の処理
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair hero:(CCNode *)hero level:(CCNode *)level {
NSLog(@"Game Over");
// リスタートボタンを表示させる
_restartButton.visible = TRUE;
return TRUE;
}
[/code]
最後に、リスタートの処理を実装します。
[code]
– (void)restart {
CCScene *scene = [CCBReader loadAsScene:@"MainScene"];
[[CCDirector sharedDirector] replaceScene:scene];
}
[/code]
これでリスタートが実装できました。
ただ、このままではゲームオーバー後に地を這いながら進んでしまっています。ゲームオーバー後はスクロールが止まるよう修正しましょう。
ゲームオーバーの判定をする変数と、スクロールスピードを格納する変数を新たに宣言します。
[code language=”objc” title=”MainScene.m” highlight=”10,11″]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
CCNode *_ground1; // SpriteBuilderと接続する地面その1
CCNode *_ground2; // SpriteBuilderと接続する地面その2
NSArray *_grounds; // 地面ループ処理用のArray
NSTimeInterval _sinceTouch; // 最後にタッチしてからどれだけ経過したか
NSMutableArray *_obstacles; // 障害物を格納する配列
CCButton *_restartButton; // リスタートボタン
BOOL _gameOver; // ゲームオーバーかどうか
CGFloat _scrollSpeed; // スクロールスピード
}
[/code]
MainScene.mでscrollSpeedと書いた箇所はすべて_scrollSpeedに置き換えます。
また、定数宣言をコメントアウトします。
[code language=”objc” title=”MainScene.m” highlight=”1″]
//static const CGFloat scrollSpeed = 80.f; // スクロール速度の定数を定義
static const CGFloat firstObstaclePosition = 280.f; // 最初の障害物の位置
static const CGFloat distanceBetweenObstacles = 160.f; // 障害物と次の障害物の距離
[/code]
さらに、didLoadFromCCBにスクロールスピードを追記します。
[code language=”objc” title=”MainScene.m” highlight=”6,7″]
// CCBファイルからロード
– (void)didLoadFromCCB {
// タッチ操作可能にする。
self.userInteractionEnabled = TRUE;
// 略…
// スクロールスピードを設定
_scrollSpeed = 80.f;
}
[/code]
続いてゲームオーバー時に実行されるメソッドを作成しましょう。
[code]
// ゲームオーバーになった時に実行する
– (void)gameOver {
if (!_gameOver) {
// スクロールを泊める
_scrollSpeed = 0.f;
// ゲームオーバーのフラグを立てる
_gameOver = TRUE;
// ボタンを見えなくする
_restartButton.visible = TRUE;
_hero.rotation = 90.f;
_hero.physicsBody.allowsRotation = FALSE;
[_hero stopAllActions];
CCActionMoveBy *moveBy = [CCActionMoveBy actionWithDuration:0.2f position:ccp(-2, 2)];
CCActionInterval *reverseMovement = [moveBy reverse];
CCActionSequence *shakeSequence = [CCActionSequence actionWithArray:@[moveBy, reverseMovement]];
CCActionEaseBounce *bounce = [CCActionEaseBounce actionWithAction:shakeSequence];
[self runAction:bounce];
}
}
[/code]
そして、衝突時のメソッドを書き換えます
[code language=”objc” title=”MainScene.m” highlight=”3,4,5,6,7,8″]
// 衝突時の処理
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair hero:(CCNode *)hero level:(CCNode *)level {
// NSLog(@"Game Over");
// // リスタートボタンを表示させる
// _restartButton.visible = TRUE;
// ゲームオーバー処理を実行
[self gameOver];
return TRUE;
}
[/code]
ゲームオーバー時にはタッチに反応しないように修正します。
[code language=”objc” title=”MainScene.m” highlight=”3,4,11″]
// 画面をタッチした時の挙動
– (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// ゲームオーバー時にはタッチに反応しない
if (!_gameOver){
// 力積を与える
[_hero.physicsBody applyImpulse:ccp(0, 400.f)];
// 角力積を設定
[_hero.physicsBody applyAngularImpulse:10000.f];
// いまタッチしたので、経過時間を0にする
_sinceTouch = 0.f;
}
}
[/code]
これで、ゲームオーバー時にストップするようになりました。
いよいよ完成です。最後に、スコアを表示するようにしましょう。
SpriteBuilderで画面上にラベルを設置します。位置を調整して、表記を「0」に、サイズを50にします。
続いて、コネクションの設定をします。_scoreLabelと記入して下さい。
さらにObstacleを開き、パイプとパイプの間においてあるノードに対して、カスタムクラスとしてGoalと名前をつけます。
さらに、物理演算を可能にして、Staticにチェックを入れます。
続いてXcodeに移り、Goalクラスを作ります。
Goal.mにメソッドを追加します。
[code language=”objc” title=”Goal.m”]
// CCBファイルからロード
– (void)didLoadFromCCB {
self.physicsBody.collisionType = @"goal";
self.physicsBody.sensor = TRUE;
}
[/code]
続いて、MainScene.mに変数を追加します。
[code language=”objc” title=”MainScene.m” highlight=”4,5″]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
// 略…
NSInteger _points; // ポイントをカウント
CCLabelTTF *_scoreLabel; // ポイント表示用のラベル
}
[/code]
そして、これでほんとうに最後。ゴール時のメソッドに追記をします。
[code language=”objc” title=”MainScene.m” highlight=”4,5″]
// ゴールした時に実行する
-(BOOL)ccPhysicsCollisionBegin:(CCPhysicsCollisionPair *)pair hero:(CCNode *)hero goal:(CCNode *)goal {
[goal removeFromParent];
_points++;
_scoreLabel.string = [NSString stringWithFormat:@"%d", _points];
return TRUE;
}
[/code]
SpriteBuilderとcocos2dを使ったiphoneアプリ開発講座、いかがでしたでしょうか。
私も現在、SpriteBuilderを使ってゲームアプリを開発しています。簡単ツールを使って、iPhoneアプリをじゃんじゃん量産しましょう!
SpriteBuilderをつかって、障害物を設置します。さらに、障害物の上下の高さをランダムにして出現させます。みためだけだと、ほぼ完成と言えるような状態になります。
SpriteBuilderで新しくファイルを作ります。 Nodeを選択し、名前はObstacle.ccbとします。 続いて、作ったObstacle.ccsに画像やNodeを追加していきます。 まずはObstacle.ccsを開きましょう。(ダブルクリック) 続いて、画像を置いていきます。 次に、CCNodeを設置します。 位置やサイズはこの後に設定するので、とりあえず置いてみてください。 サイズを調整します。 まずはRootとなるCCNodeのサイズを調整します。(最後に追加したCCNodeではありません。) 幅80, 高さ568と指定します。 次に、pipe_topの位置を調整します。 Positionを左上に指定し、Xを50「%」、Yを128に指定します。 Anchor pointのx,yを0.50と0.00にします。 同様にpipe_bottomの位置も調整します。 Positionを左上に指定し、Xを50「%」、Yを228に指定します。 Anchor pointのx,yを0.50と1.00にします。 最後にカウント用のCCNodeの設定です。 Positionを左上に指定し、Xを22、Yを574に指定し、サイズを幅36と高さ790にします。 それではこのObstacle.ccbを保存し、MainScene.ccbの上に設置してみましょう。 上図のように設置できることが確認できたら、設置した障害物を一度削除します。障害物の設置は、SpriteBuilder上ではなく、Xcodeで行います。 それではSpriteBuilderでPublishしたあと、Xcodeに移りましょう。 障害物を格納するための配列を作成します。
[code language=”objc” title=”MainScene.m” highlight=”8″]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
CCNode *_ground1; // SpriteBuilderと接続する地面その1
CCNode *_ground2; // SpriteBuilderと接続する地面その2
NSArray *_grounds; // 地面ループ処理用のArray
NSTimeInterval _sinceTouch; // 最後にタッチしてからどれだけ経過したか
NSMutableArray *_obstacles; // 障害物を格納する配列
}
[/code]
続いて、定数を追加します。
[code language=”objc” title=”MainScene.m” highlight=”2,3″]
static const CGFloat scrollSpeed = 80.f; // スクロール速度の定数を定義
static const CGFloat firstObstaclePosition = 280.f; // 最初の障害物の位置
static const CGFloat distanceBetweenObstacles = 160.f; // 障害物と次の障害物の距離
[/code]
最初の障害物の座標と、障害物と障害物の間の距離を定義します。 続いて、障害物を生成するメソッドを作成します。
[code language=”objc” title=”MainScene.m”]
// 障害物を生成する
– (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleXPosition = previousObstacle.position.x;
if (!previousObstacle) {
// これが一番最初の障害物オブジェクトになります
previousObstacleXPosition = firstObstaclePosition;
}
// CCBファイルから障害物オブジェクトを読み込み、生成します
CCNode *obstacle = [CCBReader load:@"Obstacle"];
obstacle.position = ccp(previousObstacleXPosition + distanceBetweenObstacles, 0);
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
[/code]
CCBファイルを読み込んで障害物オブジェクトを生成し、先ほど定義した距離の分だけ離して配置しています。 さて、障害物生成メソッドは作成したので、最初の障害物だけ設置しましょう。 didLoadFromCCBに以下を追加します。
[code language=”objc” title=”MainScene.m” highlight=”7,8,9,10,11″]
// CCBファイルからロード
– (void)didLoadFromCCB {
// 地面その1とその2を配列に追加
_grounds = @[_ground1, _ground2];
// タッチ操作可能にする。
self.userInteractionEnabled = TRUE;
// 最初の障害物を設置する
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
}
[/code]
これで3つの障害物を設置することが出来ました。 それでは、障害物が永遠に出続けるようにアップデートメソッドを更新します。
[code language=”objc” title=”MainScene.m”]
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
// 略…
// 障害物の新規作成
// 画面外に出た障害物を格納する配列
NSMutableArray *offScreenObstacles = nil;
// _obstaclesの中にある障害物のうち、左の画面外に出たものをoffScreenObstaclesに格納する
for (CCNode *obstacle in _obstacles) {
CGPoint obstacleWorldPosition = [_physicsNode convertToWorldSpace:obstacle.position];
CGPoint obstacleScreenPosition = [self convertToNodeSpace:obstacleWorldPosition];
if (obstacleScreenPosition.x < -obstacle.contentSize.width) {
if (!offScreenObstacles) {
offScreenObstacles = [NSMutableArray array];
}
[offScreenObstacles addObject:obstacle];
}
}
// offScreenObstaclesに入っている障害物を親ノードから取り除く
for (CCNode *obstacleToRemove in offScreenObstacles) {
[obstacleToRemove removeFromParent];
[_obstacles removeObject:obstacleToRemove];
// 障害物を取り除いたら、新しい障害物を生成する
[self spawnNewObstacle];
}
}
[/code]
障害物をX軸上に規則的に配置することができたので、今度は障害物のY軸の位置がランダムになるようにしましょう。 そのためには障害物のクラスを作成する必要があります。 SpriteBuilderのObstacle.ccbのCustom ClassにObstacleと記述します。 さらに、Code Connectionの設定も行いましょう。 上側のパイプは_topPipe、下側のパイプは_bottomPipeとします。 保存してPublishしたら、Xcodeでの作業に移ります。 Xcodeでは、新しいクラスを作成します。CCNodeのサブクラスとしてObstacleを作成しましょう。 Obstacleが作成できました。 続いて実装をしていきましょう。 3.5インチでも4インチでも大丈夫なよう定数を調整しています。
[code language=”objc” title=”Obstacle.m”]
#import "Obstacle.h"
@implementation Obstacle {
CCNode *_topPipe; // 上のパイプ
CCNode *_bottomPipe; // 下のパイプ
}
#define ARC4RANDOM_MAX 0x100000000
// 上のパイプの最小値。3.5インチiPhoneにも考慮しています
static const CGFloat minimumYPositionTopPipe = 128.f;
// 下のパイプの最大値。3.5インチiPhoneにも考慮しています
static const CGFloat maximumYPositionBottomPipe = 440.f;
// 上下のパイプの間の距離
static const CGFloat pipeDistance = 142.f;
// 上のパイプの範囲を計算する
static const CGFloat maximumYPositionTopPipe = maximumYPositionBottomPipe – pipeDistance;
// 障害物の位置をランダムに設定
– (void)setupRandomPosition {
// 乱数は0.f〜1.fの範囲で変動
CGFloat random = ((double)arc4random() / ARC4RANDOM_MAX);
CGFloat range = maximumYPositionTopPipe – minimumYPositionTopPipe;
// 上のパイプの位置を指定
_topPipe.position = ccp(_topPipe.position.x, minimumYPositionTopPipe + (random * range));
// 下のパイプの位置を指定
_bottomPipe.position = ccp(_bottomPipe.position.x, _topPipe.position.y + pipeDistance);
}
@end
[/code]
Obstacle.hにもメソッドの宣言をします。
[code language=”objc” highlight=”5″ title=”Obstacle.h”]
#import <Foundation/Foundation.h>
#import "cocos2d.h"
@interface Obstacle : CCNode {
}
– (void)setupRandomPosition;
@end
[/code]
最後にMainScene.mを修正しましょう。 障害物を生成する際に、位置がランダムになるよう修正します。 まずはクラスをインポートします。
[code language=”objc” title=”MainScene.m” highlight=”2″]
#import "MainScene.h"
#import "Obstacle.h" // 障害物クラスをインポート
[/code]
続いてメソッドに書き加えましょう。
[code language=”objc” title=”MainScene.m” highlight=”10, 12,13″]
// 障害物を生成する
– (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleXPosition = previousObstacle.position.x;
if (!previousObstacle) {
// これが一番最初の障害物オブジェクトになります
previousObstacleXPosition = firstObstaclePosition;
}
// CCBファイルから障害物オブジェクトを読み込み、生成します
Obstacle *obstacle = (Obstacle *)[CCBReader load:@"Obstacle"];
obstacle.position = ccp(previousObstacleXPosition + distanceBetweenObstacles, 0);
// 位置をランダムにします
[obstacle setupRandomPosition];
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
[/code]
障害物が地面の前に出てしまっているので、コードを加えて表示順を調整します。 定数の宣言の下あたりに、以下のコードを加えます。
[code language=”objc” title=”MainScene.m”]
typedef NS_ENUM(NSInteger, DrawingOrder) {
DrawingOrderPipes,
DrawingOrderGround,
DrawingOrdeHero
};
[/code]
さらに、didLoadFromCCBにZ軸の表示順を追記します。
[code language=”objc” title=”MainScene.m” highlight=”13,14,15,16,17″]
// CCBファイルからロード
– (void)didLoadFromCCB {
// 地面その1とその2を配列に追加
_grounds = @[_ground1, _ground2];
// タッチ操作可能にする。
self.userInteractionEnabled = TRUE;
// 最初の障害物を設置する
_obstacles = [NSMutableArray array];
[self spawnNewObstacle];
[self spawnNewObstacle];
[self spawnNewObstacle];
// 地面の表示順を変更する
for (CCNode *ground in _grounds) {
ground.zOrder = DrawingOrderGround;
}
_hero.zOrder = DrawingOrdeHero;
}
[/code]
最後にspawnNewobstacleを修正します。
[code language=”objc” title=”MainScene.m” highlight=”12,13″]
// 障害物を生成する
– (void)spawnNewObstacle {
CCNode *previousObstacle = [_obstacles lastObject];
CGFloat previousObstacleXPosition = previousObstacle.position.x;
if (!previousObstacle) {
// これが一番最初の障害物オブジェクトになります
previousObstacleXPosition = firstObstaclePosition;
}
// CCBファイルから障害物オブジェクトを読み込み、生成します
Obstacle *obstacle = (Obstacle *)[CCBReader load:@"Obstacle"];
obstacle.position = ccp(previousObstacleXPosition + distanceBetweenObstacles, 0);
// 表示順を変更します。
obstacle.zOrder = DrawingOrderPipes;
// 位置をランダムにします
[obstacle setupRandomPosition];
[_physicsNode addChild:obstacle];
[_obstacles addObject:obstacle];
}
[/code]
以上で表示順の設定が終わりました。
次回は当たり判定とゲームオーバー、リスタートの設定を行います。
いままで作りこんできた主人公をついに飛ばします。タッチ操作に合わせて、ぴょこぴょこ飛びます。また、ぴょこぴょこ飛ぶのに合わせて、キャラクターが上下に傾きます。今回でかなりゲームっぽくなりますよ。
まずは重力を-100から-700へ変更します。 保存してPublishしたらXcodeに移ります。
didLoadFromCCBに”self.userInteractionEnabled = TRUE;”を追加して、タッチイベントを取るようにします。
[code language=”objc” title=”MainScene.m”]
// CCBファイルからロード
– (void)didLoadFromCCB {
// 地面その1とその2を配列に追加
_grounds = @[_ground1, _ground2];
// タッチ操作可能にする。
self.userInteractionEnabled = TRUE;
}
[/code]
つぎに、タッチした時の挙動を追加します。 以下のコードを追加し、touchBegan:withEvent:をオーバーライドします。
[code language=”objc” title=”MainScene.m”]
// 画面をタッチした時の挙動
– (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// 力積を与える
[_hero.physicsBody applyImpulse:ccp(0, 400.f)];
}
[/code]
ビルドしてみるとわかると思いますが、素早く画面をタッチすると、主人公が画面外へ大きく飛び出てしまいます。こうならないために、微調整をする必要があります。
物理演算を使用する際はこのような微調整がとても大事です。
[code language=”objc” title=”MainScene.m”]
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
// 略….
// 速度をclamp。y軸の速度が200.fを超えた場合は200.fにする。
float yVelocity = clampf(_hero.physicsBody.velocity.y, -1 * MAXFLOAT, 200.f);
_hero.physicsBody.velocity = ccp(0, yVelocity);
}
[/code]
clampとは値を一定の範囲内に収める操作を言います。今回の場合は、上限を200.fと定めて、それ以上になってしまった場合は200.fとするようにして、_hero.physicsBody.velocityのyに代入しています。xは手動で設定しているので0で良いです。
いまは主人公が水平のまま上昇したり下降したりしていますが、躍動感を加えるために主人公を回転させてみましょう。
まずは、最後にタッチした時間からどれだけ経っているかを記録する変数(NSTimeInterval _sinceTouch)を作ります。
[code language=”objc” title=”MainScene.m”]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
CCNode *_ground1; // SpriteBuilderと接続する地面その1
CCNode *_ground2; // SpriteBuilderと接続する地面その2
NSArray *_grounds; // 地面ループ処理用のArray
NSTimeInterval _sinceTouch; // 最後にタッチしてからどれだけ経過したか
}
[/code]
続いて、タッチした時のメソッドに追記します。
[code language=”objc” title=”MainScene.m”]
// 画面をタッチした時の挙動
– (void)touchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
// 力積を与える
[_hero.physicsBody applyImpulse:ccp(0, 400.f)];
// 角力積を設定
[_hero.physicsBody applyAngularImpulse:10000.f];
// いまタッチしたので、経過時間を0にする
_sinceTouch = 0.f;
}
[/code]
AngularImpulseの値を大きくすると、角度の変化が素早くなります。
さて、最後にupdateメソッドを更新します。
角度の範囲を指定し、タッチしていない時は下向きに回転するようにします。
[code language=”objc” title=”MainScene.m”]
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
// 略….
// 主人公の角度を変える
// 経過時間を追加
_sinceTouch += delta;
// 角度を上限(90度)と下限(-30度)の中に収める
_hero.rotation = clampf(_hero.rotation, -30.f, 90.f);
// 回転が可能かチェック。(死んだときは回転ができなくなっている)
if (_hero.physicsBody.allowsRotation) {
// 角速度を設定
float angularVelocity = clampf(_hero.physicsBody.angularVelocity, -2.f, 1.f);
_hero.physicsBody.angularVelocity = angularVelocity;
}
// 経過時間が0.5秒を超えると、急速に下に向くようにする。
if ((_sinceTouch > 0.5f)) {
[_hero.physicsBody applyAngularImpulse:-40000.f*delta];
}
}
[/code]
最初に_sinceTouchに時間を加えて、タッチしてからの経過時間を更新します。
次に、回転が可能かどうかをチェックします。あとで設定をしますが、死んだ時は回転が不可能なため。回転可能なとき、-30度から90度の範囲で回転できます。
最後に、経過時間が0.5秒以上だった場合は、急速に下向きに回転するようにします。
これで回転の設定は終わりました。ビルドしてみましょう。
だいぶゲームっぽくなりました!
次回は障害物を設置します。
今回は、キャラクターの動きに合わせて背景を動かします。
これまでSpriteBuilderで配置した主人公や背景を、ついに動かします。
今回はcocos2dのコードも書きます。といっても普通に開発する場合と比べるとコードを書く量はかなり少ないですが。
地面の無限ループなども実装します。
SpriteBuilderで設定したオブジェクトとXcodeで書くコードをつなげます。
主人公のノードを選択し、右カラムの左から2番目のタブを選択肢ます。
この画面でXcode上のコードと接続します。プルダウンで「Doc root var」を選択して、右には「_hero」と記述します。
保存をしてPublishボタン(左上のボタン)を押した後、Xcodeに移ります。
[code language=”objc” title=”MainScene.m”]
#import "MainScene.h"
static const CGFloat scrollSpeed = 80.f; // スクロール速度の定数を定義
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
}
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
}
@end
[/code]
これで、主人公が動くようになります。
早速ビルドしてみましょう。
起動と同時に主人公が右に飛んでいきます。
続いて、主人公の動きに合わせてカメラが動くように設定します。
ただcocos2dにはカメラという機能はないので、主人公に合わせて地面などが動くように設定します。
まずSpriteBuilderのMainScene.ccbを開き、CCPhysicsNodeを選択します。
続いてCode ConnectionsタブでDoc root varを選択し、_physicsNodeと記述します。
保存してPublishしたらSpriteBuilder側はこれで完了。
続いてはXcodeにコードを書いていきます。
@implementationの部分に以下のように追記します。これでSpriteBuilderの_physicsNodeとXcode側の接続が完了しました。
[code language=”objc” title=”MainScene.m”]
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
}
[/code]
続いて、画面と主人公の位置が連動するようにコードを書きます。
[code language=”objc” title=”MainScene.m”]
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
// 主人公の座標に合わせて_physicsNode全体を逆方向(左方向)に動かす
_physicsNode.position = ccp(_physicsNode.position.x – (scrollSpeed *delta), _physicsNode.position.y);
}
@end
[/code]
これで画面が主人公を追いかけるようになりました。
まだ地面をループさせていないので、しばらくすると奈落へ落ちていってしまいますが。
主人公が地獄に落ちないよう、地面をループさせます。
まずはスプライトビルダーの表示設定を変更して、画面外も見られるようにしましょう。
Document > Stage Border > None
と選択し、画面境界の表示を消します。
これで画面外にスプライトを設置できるようになりました。
続いて、2番目の地面を画面上に設置します。ArtPackからground.pngをドラッグアンドドロップし、その後画像の位置を変更します。座標を(348, 12)に、アンカーポイントのX座標を0にします。
また、このときCCPhysicsNodeの子ノードに入っていない場合は、下画面でドラッグアンドドロップして、groundがCCPhysicsNodeの中に入っているようにしましょう。
続いて、物理演算を可能にします。
Enable physicsにチェックを入れ、Staticを選択します。このあたりは1つ目の地面を設定した時と同じですね。
次に、ループを実装するため、connectionを設定する必要があります。
最初に設定したground.pngのスプライトに「_ground1」、さきほど設定した方に「_ground2」と設定します。
これでSpriteBuilderは完了。保存してPublishしたら、Xcode側に移ります。
[code language=”objc” title=”MainScene.m”]
#import "MainScene.h"
static const CGFloat scrollSpeed = 80.f; // スクロール速度の定数を定義
@implementation MainScene{
CCSprite *_hero; // SpriteBuilderと接続する主人公のスプライト
CCPhysicsNode *_physicsNode; // SpriteBuilderと接続する全画面の物理ノード
CCNode *_ground1; // SpriteBuilderと接続する地面その1
CCNode *_ground2; // SpriteBuilderと接続する地面その2
NSArray *_grounds; // 地面ループ処理用のArray
}
// CCBファイルからロード
– (void)didLoadFromCCB {
// 地面その1とその2を配列に追加
_grounds = @[_ground1, _ground2];
}
// アップデート
– (void)update:(CCTime)delta {
// 主人公のX座標を右に進める
_hero.position = ccp(_hero.position.x + delta * scrollSpeed, _hero.position.y);
// 主人公の座標に合わせて_physicsNode全体を逆方向(左方向)に動かす
_physicsNode.position = ccp(_physicsNode.position.x – (scrollSpeed *delta), _physicsNode.position.y);
// 地面をループさせる
for (CCNode *ground in _grounds) {
// 地面のワールド座標を取得
CGPoint groundWorldPosition = [_physicsNode convertToWorldSpace:ground.position];
// 地面のスクリーン城の位置を取得
CGPoint groundScreenPosition = [self convertToNodeSpace:groundWorldPosition];
// ひとつの地面が画面の左端から完全に外れたら、それを右に移動させる。
if (groundScreenPosition.x <= (-1 * ground.contentSize.width)) {
ground.position = ccp(ground.position.x + 2 * ground.contentSize.width, ground.position.y);
}
}
}
@end
[/code]
これで地面が永遠につづくようになりました。
次回は画面をタッチして主人公を飛ばします。
今回は物理演算を実装します。
cocos2d v3では物理演算がデフォルトで入るようになりました。さらに、SpriteBuilderを使うと、ワンクリックで物理演算を導入できるようになります。これは本当にすごい。
まずは画面全体に物理演算ノードを設置します。
MainSceneを開き左カラムの立方体のタブを開きます。
そこからPhysicsNodeという項目を画面下の「CCNode」の上にドラッグアンドドロップします。
続いて、画面全体をカバーするようサイズを変更します。
幅と高さを100%に変更します。
地面のスプライトを選択し、右カラムの物理演算のタブ(四角が跳ねてるアイコン)を選択します。
ここで「Enable physics」を選択することで、対象ノードが物理演算可能となります。
また、ラジオボタンはStaticを選択します。
続いて、groundをCCPhysicsNodeの子ノードにします。
groundをCCPhysicsNodeの上にドラッグアンドドロップすることで、子ノードにすることができます。
最後に、CCPhysicsNodeの表示順を変更します。
(すでにCCPhysicsNodeが最下部にある場合は変更する必要はないです。)
ついに前回作った主人公を設置します。
Hero.ccbをMainSceneの画面上にドラッグ&ドロップします。
(ドラッグアンドドロップの前に、Hero.ccbを保存しておきましょう。)
つぎに、追加した主人公のノードをCCPhysicsNodeの子ノードにします。
続いて、対象物の物理演算にチェックを入れます。
地面とは異なり、Dynamicで良いです。
最後に茂みを設置しましょう。
まずドラッグアンドドロップをして。
続いて、位置を修正します。(左下、160,106)
さらに、表示順を変更します。
CCPhysicsNodeの上になるように。
それではビルドしてみましょう!
左上のPublishボタンを押します。
保存が済んでない場合は上記の表示が出てくるので、Save Allを押します。
今度はXcodeからビルドしてみましょう。
SpriteBuilderのプロジェクトが入っているフォルダを開き、プロジェクトファイルを開きます。
ビルドします。
シュミレーターではこんなかんじで表示されるはず。
というわけで今回はここまで!
次回
前回までで背景の設置が終わりました。今回は主人公を設置します。
SpriteBuilderを使えばアニメーションを簡単に作れます。普通の開発ではコードを書いてコマ送りを実装ますが、SpriteBuilderでは、動作を見ながらアニメーションを作ることができます。
Heroという名前で、「Sprite」を選択してファイルを作成します。
作成したファイルに画像を設定します。
続いて、時間間隔を設定します。
10秒を1秒に変更します。
アニメーション作成のため、Sprite Frameを追加します。
タイムラインを動かしながら、SpriteFrameを6つ追加します。
つぎに、Sprite Frameを一つおきにfly2.pngに変更し、羽がパタパタするアニメーションを作成します。
最後に動画がループするように設定します。
これで主人公のアニメーションが作成できました。
次回
Cocos2d-iPhone 3.0と SpriteBuilderでFlappyBirdっぽいゲームを作ります。
というか、こちらの記事の和訳+補足になります。
https://www.makegameswith.us/gamernews/369/build-your-own-flappy-bird-with-spritebuilder-and
↑こういったアプリが作れます。
まず以下の2つをDLし、インストールしましょう。
両方共インストールが完了したら早速始めちゃいましょう。
参照元は「チュートリアルはやっておいてね」と書いていたのですが、無視します。
SpriteBuliderを起動して、プロジェクトを作ります。
「FlappyFly」という名前をつけて、デスクトップに保存。
(保存先はどこでも良いです。)
SpriteBuilderという文字と、その後ろの青い背景を消します。
これで何もなくなりました。
ちなみにオブジェクトを選択してdeleteボタン押しても消せます。
横画面だと具合がわるいので、縦にします。
File > ProjectSettings… を選択し、
Default scaling from: を 2x(iPhonehd)に、
Orientation: を Portraitにして
「Done」を押します。
これで画面が縦になりました。
続いてはSpriteBuilderで背景画像を設置します。
こちらのページから画像をダウンロードします
https://s3.amazonaws.com/mgwu-misc/FlappyFlyArtPack.zip
ダウンロードして解凍したフォルダを、プロジェクトに追加します。
ドラッグアンドドロップで追加できます。
追加した画像から、背景画像(background.png)を設置します。
左側の追加したフォルダを開き、ドラッグアンドドロップで画面上に画像を設置します。
設置した画像を選択すると、右側のバーに位置などの設定画面が出ます。
そこのPosition, AnchorPointを変更します。
まずPositonは「Top-left」で、Xが「0.0」Yも「0.0」に、
Anchor Pointは、Xを「0.0」に、Yを「1.0」に設定します。
これで位置を整列できました。
Top-Left、つまり左上を基準として設定したわけですが、こうすることで、iPhone4でもiPhone5でも対応できるようになります。
iPhone4での見え方を確認したい場合はこちらの設定から確認できます。
背景画像と同様に、地面も設置します。
ground.pngをドラッグアンドドロップします。
その後、先程と同様に位置指定をします。
PositionはBottom-leftでXが0.0、Yが12.0。
Anchor pointはXが0.00、Yが0.50。
同様に、雲も設置してみます。
PositionはTop-leftでXが187.0、Yが134.0。
というわけで今日はここまで。
次回
cocos2d-xばかりで、cocos2d-iPhoneについてどこも書いてないので、メモしておきます。
参照元はこちら。
https://www.makegameswith.us/gamernews/359/cocos2d-30-a-brief-transition-guide
おっと、こちらに日本語で詳しく書かれていました。。。
実機でテストしようとしたら以下の様なエラーが出てしまいました。
Unknown register name ‘q0’ in asm
そんなときの対処法。
__ARM_NEON__
↑このように書いてある箇所を↓のように変更します。
__ARM_NEON_7
mat4.cとneon_matrix_impl.cで1箇所ずつ書かれている箇所があるので、両方修正します。
参考元 : http://stackoverflow.com/questions/21510187/unknown-register-name-q0-in-asm