Flappy Birdのクローンやパロディやオマージュはたくさん作られているが、私もSprite Kitで試してみた。
コードはこちら:https://github.com/oinariman/RMFlappyBall
要件
まず、Flappy Birdゲームを構成する要素を分解してみた。
以下の要件を満たせば、Flappy Birdみたいになるはず。
①トリ
- 中央からやや左に位置。
- 重力に従って落ちる。
- タップすると少し上に跳ね上がる。
②床
- ずっと右から左へ等速でスクロールしている。
③壁
- ランダムにすきまの空いた壁が、定期的に画面の右端に現れる。
- 壁は画面の左へ等速で移動する。
④点数
- 壁がトリより左側へ行くたびに1点加算される。
⑤終了条件
- トリが床や壁にぶつかったらゲームオーバー。
実装
①トリ
丸いスプライトをトリということにして、中央からやや左に置く。
SKPhysicsBodyのインスタンスを与えて、重力に従って落ちるようにする。
画面を触られたら、トリに上向き速度を与えるようにする:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
SKNode *ball = [self childNodeWithName:@"ball"];
[[ball physicsBody] setVelocity:
CGVectorMake(0.0, kFlappingVelocityY)];
}
②床
縞模様が右から左に流れていると、ずっと右に進んでいるように見える。
縞模様テクスチャのスプライトを作り、右から左へ移動してまた元に戻る動作を繰り返させた。
// 右から左へ行ってまたもとに戻る動きを永遠に繰り返す。
[floor runAction:[SKAction repeatActionForever:
[SKAction sequence:
@[[SKAction moveTo:CGPointMake(0.0, size.height/2.)
duration:interval],
[SKAction moveTo:CGPointMake(self.size.width, size.height/2.) duration:0.0]]]]];
床を静止させておいてテクスチャのアニメーションで済ますことも考えたが、壁の速度と合わせる調整がやりづらいのでやめた。
③壁
壁の配置
上の壁と下の壁を、それぞれランダムに高さを変えて置く。
画面の高さを15分割して、上の壁・穴・下の壁にランダムに割り振ることにした。
これでゲームのチューニングがしやすかった。
- (void)putWalls {
// 画面の高さを15分割
CGFloat unit = self.size.height / 15.0;
// 上の壁の高さ = 高さの最小値 + 乱数
CGFloat upperWallHeight =
unit * (arc4random() % (kUpperWallHeightMax - kUpperWallHeightMin)
+ kUpperWallHeightMin);
// すきまの高さは固定
// 下の壁の高さ = 画面の高さ - (上の壁の高さ + すきまの高さ)
CGFloat bottomWallHeight =
self.size.height - upperWallHeight - unit * kHoleHeight;
[self putWallWithHeight:upperWallHeight
y:self.size.height - upperWallHeight / 2.];
[self putWallWithHeight:bottomWallHeight
y:bottomWallHeight / 2.];
}
でも、よく考えたらこの実装じゃ、3.5インチと4インチで難易度が変わっちゃう。
壁の一生
ゲームの開始から終了まで、定期的に壁が作られ続ける。
それぞれの壁は、画面の右端に生まれ、左向きに等速で画面を通過し、左端で消え去る。
壁の移動速度は、画面を通過するのにかかる時間によって決めることとした。
- (void)putWallWithHeight:(CGFloat)height y:(CGFloat)y {
SKSpriteNode *wall =
[SKSpriteNode spriteNodeWithColor:wallColor_
size:CGSizeMake(kWallWidth, height)];
[wall setPosition:CGPointMake(self.size.width + kWallWidth / 2., y)];
SKPhysicsBody *body = [SKPhysicsBody bodyWithRectangleOfSize:wall.size];
[body setAffectedByGravity:NO];
[body setDynamic:NO];
[wall setPhysicsBody:body];
// 右から左へ動かした後消す
[wall runAction:[SKAction sequence:
@[[SKAction moveTo:
CGPointMake(-kWallWidth / 2., y) duration:kTimeTakenForWallGoThroughScreen],
[SKAction removeFromParent]]]];
[self addChild:wall];
}
なお、床と壁の移動速度が違うとずれて見えるので、壁の移動速度を基準に計算して、床の移動速度を設定している。
④点数
壁がトリより左側に行けば1点入る。
トリは水平方向に動かないので、壁が右端からトリを越えるまでの時間はあらかじめ計算できる。
そこで、壁を1個作ったあと、その壁が右端からトリに到達するまでの時間が経ったら自動的に1点入るようにした。
// 定期的に壁を作る
- (void)putWallsPeriodically {
[self runAction:
[SKAction repeatActionForever:
[SKAction sequence:
@[[SKAction waitForDuration:kIntervalBetweenWallProductions],
[SKAction runBlock:^{
// 壁を1セット作って置く
[self putWalls];
// 壁が右端からトリに到達するまでの時間が経ったら自動的に1点入れる
[self runAction:
[SKAction sequence:
@[[SKAction waitForDuration:kTimeTakenForWallGoThroughScreen * 0.75],
[SKAction runBlock:^{
SKNode *ball = [self childNodeWithName:@"ball"];
// 上空を乗り越えるのは禁止
if ([ball position].y > self.size.height) {
[self gameOver];
}
// 1点追加
else {
[self incrementPoints];
}
}]]]];
}]]]]];
}
⑤終了条件
SKPhysicsContactDelegateを使って、トリが床か壁にぶつかったら処理が呼ばれるようにする。
以上の実装に若干の調整と演出を加えて、Flappy Birdっぽくなった。
もっとゲームを面白くするためには、壁の速度やすきまの大きさ、重力の強さ等々をチューニングすることが必要。
ゲームが愛されるには、かわいいグラフィックやアニメーション演出も超大事。
作者が嫌いになっても、私はFlappy Birdが大好きです。