Angry Birds風を作るという内容自体は二番煎じではありますが、ご容赦ください。
SpriteKitというXcode5から追加されたフレームワークを利用し、
Angry Birdsのようなゲームを作ってみたいと思います。
今回以下のような点を実現したいと思います。
- 横画面対応
- nodeの配置
- nodeのセンタリング
- nodeへの物理的力の適用
- 画面のズームイン/アウト
- パーティクルの利用
##横画面対応
まず注意しなければならない点としましては
iPhone自体の横向きとSKScene両方の事を考慮する必要があります。
###iPhoneの横向き設定
###SKScene向けの調整
- (void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
// Configure the view.
SKView * skView = (SKView *)self.view;
if (skView.scene == nil) {
skView.showsFPS = YES;
skView.showsNodeCount = YES;
// Create and configure the scene.
SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
NSLog(@"%@",NSStringFromCGSize(skView.bounds.size));
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[skView presentScene:scene];
}
}
このようにviewDidLoadに記述されている内容を
viewWillLayoutSubviewsを移動させてます。
詳しくはこちらにも書いております。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol6 〜 横画面表示について〜
##nodeの配置
今回は、力を加える(飛ばす)ボールと、障害物を用意します。
ボールについては、はじめから表示させておき、
障害物については、画面外に配置させ、あえてスクロールが必要な状態にしたいと
思います。
また、今回はボールに対して、センタリング処理を実現する予定ですので、
以下のようにworld/cameraの配置も行います。
world/camera については以下に記述しています。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol13 〜 スクロールについての考察 cameraとセンタリングについて〜
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];
self.anchorPoint = CGPointMake(0.5f, 0.5f);
SKNode *myWorld = [SKNode node];
myWorld.name = @"world";
[self addChild:myWorld];
SKNode *camera = [SKNode node];
camera.name = @"camera";
[myWorld addChild:camera];
}
return self;
}
- (void)didSimulatePhysics
{
//nameより、cameraノードを取得
SKNode *camera = [self childNodeWithName: @"//camera"];
[self centerOnNode: camera];
}
- (void) centerOnNode: (SKNode *) node
{
CGPoint cameraPositionInScene = [node.scene convertPoint:node.position fromNode:node.parent];
node.parent.position = CGPointMake(node.parent.position.x - cameraPositionInScene.x, node.parent.position.y - cameraPositionInScene.y);
}
###ボール・地面・障害物の配置
それぞれinitWithSizeにて、
//地面
SKSpriteNode *ground = [[SKSpriteNode alloc] initWithColor:[SKColor brownColor]
size:CGSizeMake(size.width*10,
size.height)];
ground.position = CGPointMake(0, -ground.size.height + 30);
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.dynamic = NO;
[myWorld addChild:ground];
//ボール
ball = [SKSpriteNode spriteNodeWithImageNamed:@"ball.png"];
ball.position = CGPointMake(0, -20);
ball.name = @"ball";
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
ball.physicsBody.dynamic = NO;
[myWorld addChild:ball];
ついでといってはアレですが、今回利用するメンバ変数やenum値については
先に書いておきます。
enum
{
kDragNone, //初期値
kDragStart, //Drag開始
kDragEnd, //Drag終了
};
@implementation MyScene
{
SKSpriteNode *ball;
SKSpriteNode *target;
int gameStatus;
CGPoint startPos;
}
これは衝突検出用に。
@interface MyScene : SKScene
<SKPhysicsContactDelegate>
@end
障害物については
//障害物
int start_x = 500;
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,80)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.physicsBody.categoryBitMask = 0x1 << 1;
sprite.position = CGPointMake(start_x + 100,-90);
[myWorld addChild:sprite];
sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,80)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.physicsBody.categoryBitMask = 0x1 << 1;
sprite.position = CGPointMake(start_x + 200,-90);
[myWorld addChild:sprite];
sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(150,15)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.physicsBody.categoryBitMask = 0x1 << 1;
sprite.position = CGPointMake(start_x + 150,-50);
[myWorld addChild:sprite];
sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,50)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.physicsBody.categoryBitMask = 0x1 << 0;
sprite.position = CGPointMake(start_x + 120,-20);
[myWorld addChild:sprite];
sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(15,50)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.physicsBody.categoryBitMask = 0x1 << 0;
sprite.position = CGPointMake(start_x + 180,-20);
[myWorld addChild:sprite];
sprite = [SKSpriteNode spriteNodeWithColor:[SKColor greenColor] size:CGSizeMake(100,15)];
sprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:sprite.size];
sprite.position = CGPointMake(start_x + 150,0);
sprite.physicsBody.categoryBitMask = 0x1 << 0;
[myWorld addChild:sprite];
target = [SKSpriteNode spriteNodeWithColor:[SKColor redColor] size:CGSizeMake(15,15)];
target.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:target.size];
target.physicsBody.contactTestBitMask = 0x1 << 0;
target.position = CGPointMake(start_x + 150,-35);
[myWorld addChild:target];
以下のような形を考えています。位置については数値決め打ちで申し訳ないですが。。
赤色が衝突対象物です。
また、今回の衝突については
上記のようなピンクで囲った箇所が赤色のNodeにぶつかったら
その箇所にパーティクルを表示させたいと思いますので、
ピンクで囲ったNodeとにtargetのNode(赤色)ついては、
categoryBitMaskを 1(0x1<<0)を
それ以外については
contactTestBitMaskを 2(0x1<<1)を指定しています。
衝突とmask値の関係については以下にまとめています。ざっくりですが。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol11 〜 ノード(剛体)の衝突とBitMaskについて〜
##タッチを検出し、nodeを移動させる
行うこととしましては
- タッチ開始時に、そこに、ballノードがあれば、その座標の保持とステータス変更。
- タッチ移動時には、ballの位置をそのタッチの位置に合わせて変更。
- タッチ終了時には、力を加えるということと、ズームアウトなどのアニメーションを実行します。
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if(node != nil && [node.name isEqualToString:@"ball"]) {
gameStatus = kDragStart;
startPos = location;
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if(gameStatus == kDragStart ){
UITouch *touch = [touches anyObject];
CGPoint touchPos = [touch locationInNode:self];
ball.position = touchPos;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if(gameStatus == kDragStart ){
gameStatus = kDragEnd;
UITouch *touch = [touches anyObject];
CGPoint endPos = [touch locationInNode:self];
//x,yの移動距離を算出
CGPoint diff = CGPointMake(startPos.x - endPos.x, startPos.y - endPos.y);
ball.physicsBody.dynamic = YES;
//yを少し大きく
[ball.physicsBody applyForce:CGVectorMake(diff.x * 20 , diff.y * 50)];
SKAction *scaleOut = [SKAction scaleTo:0.5 duration:0.2];
SKAction *moveUp = [SKAction moveByX:0 y:-100 duration:0.2];
SKAction *scale1 = [SKAction group:@[scaleOut,moveUp]];
SKAction *delay = [SKAction waitForDuration:1.0];
SKAction *scaleIn = [SKAction scaleTo:1 duration:1.0];
SKAction *moveDown = [SKAction moveByX:0 y:100 duration:1.0];
SKAction *scale2 = [SKAction group:@[scaleIn,moveDown]];
SKAction *moveSequence = [SKAction sequence:@[scale1, delay,scale2]];
[self runAction:moveSequence];
}
}
より上空にあがるようにするために、y向きに少し力を大きめにしています。
また、アニメーションについては
- ズームアウトと上への移動
- 待機
- ズームインと下への移動
をsequenceで実行しています。
SKActionの、groupとsequenceの違いについては
こちらにまとめています。
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol3 〜 SKActionの使い方その1 〜
ただ、このままではボールが画面外に出た場合に追跡できないため、
ballが中心より右側に移動した場合は、カメラの位置も、ballの位置になるように
変更したいと思います。ただし今回は、ballのxだけを引きづくようにします。
- (void)didSimulatePhysics
{
//nameより、cameraノードを取得
SKNode *camera = [self childNodeWithName: @"//camera"];
if(gameStatus == kDragEnd && ball.position.x > 0)
camera.position = CGPointMake(ball.position.x, camera.position.y);
[self centerOnNode: camera];
}
##パーティクルの実行について
衝突が合った場合は
//衝突開始時
- (void)didBeginContact:(SKPhysicsContact *)contact
{
NSString *path = [[NSBundle mainBundle] pathForResource:@"Spark" ofType:@"sks"];
SKEmitterNode *spark = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
spark.numParticlesToEmit = 50;
spark.particlePosition = contact.contactPoint;
[self addChild:spark];
[ball removeFromParent];
}
衝突が合った箇所にパーティクルを表示させ
赤色のNode(target)を非表示にしています。
パーティクルの利用については
【Xcode5】Xcodeの使い方 SpriteKit 編 Vol3 〜 SKActionの使い方その1 〜
ひとまず以上です。
細かい部分は微妙な所や決め打ちな所があって恐縮ですが、
何かの参考になれば幸いでございます。
あと以下にソースをアップしておりますので、よろしければ
AngryBirdsLikeGame