flappy bird 游戏实现

在博主还是学生的时候,flappyBird这款游戏非常火爆,最后等到Android版的出来之后,也是很痴迷的玩了一把。可是,博主游戏天赋一直平平,几度玩得想摔手机。本文主要介绍如何开发iOS平台的flappyBird,游戏中使用了原本软件的图片资源,仅作学习交流使用。博主实现的flappyBird游戏包含游戏等级设定,排行榜,音效等功能。

技术点

flappyBird是单机游戏,主要涉及界面逻辑、图片资源、游戏动画、得分排行。

为了实现这几个功能,需要使用以下几个技术框架:

  • AVFoundation
  • 归档
  • 模态视图
  • NSTimer
  • 视图控件,包括UIImageView、UILabel、UITableView等

实现过程

1、创建工程

1)打开Xcode,点击新建工程,选择Single View Application模板

create

 2)填写工程信息

fill

2、移除Main.storyboard文件

remove

上图是flappyBird的文件目录,因为Xcode6使用模板创建工程时会自动生成Main.storyboard文件,而工程中本人使用代码布局,所以可以移除Main.storyboard文件。具体操作方法可以参看本人另一篇文章:
《iOS学习之移除Main.storyboard》

3、游戏界面布局

整体效果图如下

result

需要说明的是,Game Over这个界面,首先需要隐藏或者等到游戏结束才创建。本人是选择在游戏判定结束时才创建并显示。

4、游戏运行
这款游戏的两个关键点:

  • 使用定时器驱动游戏界面运行,即游戏界面中的柱子高低变化与柱子的消失与产生。
  • 游戏结束的判定,这里涉及两个问题,一是碰撞检测,二是计分统计。

具体实现部分代码

1、计分统计

1
2
3
4
5
6
-(void)columnLabelClick {    
if (topPipeFrame.origin.x == (100 + 30 - 70)) {
columnNumber++;
columnLabel.text = [NSString stringWithFormat:@"%zi",columnNumber];
}
}

2、绘制柱子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
-(void)pipe {
//通道高度
NSInteger tunnelHeight = 0;
//根据游戏难度设定通道高度
if([[DataTool stringForKey:kRateKey] isEqualToString:@"ordinary"]) {
tunnelHeight = 100;
}else if([[DataTool stringForKey:kRateKey] isEqualToString:@"general"]) {
tunnelHeight = 90;
}else if([[DataTool stringForKey:kRateKey] isEqualToString:@"difficult"]) {
tunnelHeight = 80;
}else if([[DataTool stringForKey:kRateKey] isEqualToString:@"hard"]) {
tunnelHeight = 75;
} else if([[DataTool stringForKey:kRateKey] isEqualToString:@"crazy"]) {
tunnelHeight = 70;
}
//柱子图像
NSInteger tall = arc4random() % 200 + 40;

topPipe = [[UIImageView alloc]initWithFrame:CGRectMake(320, -20, 70, tall)];
topPipe.image = [UIImage imageNamed:@"pipe"];
[self.view addSubview:topPipe];

bottomPipe = [[UIImageView alloc]initWithFrame:CGRectMake(320, tall + tunnelHeight, 70, 400)];
bottomPipe.image = [UIImage imageNamed:@"pipe"];
[self.view addSubview:bottomPipe];
//把底部图片视图放在柱子视图上面
[self.view insertSubview:roadView aboveSubview:bottomPipe];
}

3、使用定时器,驱动游戏界面运行,并进行碰撞检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//添加定时器
timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(onTimer) userInfo:nil repeats:YES];
//定时器操作
-(void)onTimer {
//底部动画移动
CGRect frame = roadView.frame;
if (frame.origin.x == -15) {
frame.origin.x = 0;
}
frame.origin.x--;
roadView.frame = frame;
//上升
if (isTap == NO) {
CGRect frame = birdsView.frame;
frame.origin.y -= 3;
number += 3;
birdsView.frame = frame;
if (number >= 60) {
isTap = YES;
}
}
//下降
if(isTap == YES && birdsView.frame.origin.y < 370){
CGRect frame = birdsView.frame;
frame.origin.y++;
number -= 2;
birdsView.frame = frame;
number = 0;
}
//柱子移动
topPipeFrame = topPipe.frame;
CGRect bottomPipeFrame = bottomPipe.frame;
topPipeFrame.origin.x--;
bottomPipeFrame.origin.x--;
topPipe.frame = topPipeFrame;
bottomPipe.frame = bottomPipeFrame;
if (topPipeFrame.origin.x < -70) {
[self pipe];
}
//碰撞检测(交集)
bool topRet = CGRectIntersectsRect(birdsView.frame, topPipe.frame);
bool bottomRet = CGRectIntersectsRect(birdsView.frame, bottomPipe.frame);
if (topRet == true || bottomRet == true) {
[self.soundTool playSoundByFileName:@"punch"];
[self onStop];
}
if (topPipeFrame.origin.x == (100 + 30 - 70)) {
[self.soundTool playSoundByFileName:@"pipe"];
[self columnLabelClick];
}
}

4、更新分数,更新最佳分数与排行榜分数,并使用归档将数据持久化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
-(void)updateScore {
//更新最佳成绩
if (columnNumber > [DataTool integerForKey:kBestScoreKey]) {
[DataTool setInteger:columnNumber forKey:kBestScoreKey];
}
//更新本局分数
[DataTool setInteger:columnNumber forKey:kCurrentScoreKey];
//更新排行榜
NSArray *ranks = (NSArray *)[DataTool objectForKey:kRankKey];
NSMutableArray *newRanksM = [NSMutableArray array];
NSInteger count = ranks.count;
BOOL isUpdate = NO;
for (NSInteger i = 0; i < count; i++) {
NSString *scoreStr = ranks[i];
NSInteger score = [scoreStr integerValue];
if (score < columnNumber && isUpdate == NO) {
scoreStr = [NSString stringWithFormat:@"%zi", columnNumber];
[newRanksM addObject:scoreStr];
isUpdate = YES;
i--;
} else {
scoreStr = [NSString stringWithFormat:@"%zi", score];
[newRanksM addObject:scoreStr];
}
}
if (newRanksM.count > count) {
[newRanksM removeLastObject];
}
[DataTool setObject:newRanksM forKey:kRankKey];
}

5、绘制GameOver提示显示

1
2
3
4
5
6
-(void)pullGameOver {
//游戏结束操作界面
gameOver = [[GameOverView alloc] initWithFrame:CGRectMake(20, 160, 280, 300)];
gameOver.delegate = self;
[self.view addSubview:gameOver];
}

6、游戏停止操作

1
2
3
4
5
6
7
8
-(void)onStop {
//更新分数
[self updateScore];
//停止定时器
[timer setFireDate:[NSDate distantFuture]];
//弹出游戏结束操作界面
[self pullGameOver];
}

小结

这款游戏的实现还是很简单的,主要使用UIImageView自带的动画实现方式,即可实现bird的动画效果。使用NSTimer即可实现游戏场景的柱子移动,至于柱子的高度,则可以使用随机数方式在一定范围内实现高低变化。最后可以使用CGRectIntersectsRect来实现边界碰撞检测来判定游戏是否结束。

以上是博主开发iOS版flappyBird的简要过程介绍,其中只包含了关键点的代码实现,具体完整游戏源代码地址:https://github.com/CharsDavy/flappyBird