手把手教你制作那个风靡的flappy bird小游戏(二)

news/2024/7/20 20:21:58 标签: IOS, cocos2d, 游戏, flappy bird

这一篇教程中我们继续来完成flappy bird的制作。

 

首先来制作Bird,我们在Bird类中添加一个成员:

CCSprite* sprite;

float scale;

然后在初始化方法中初始化这两个成员:

sprite = [CCSpritespriteWithFile:@"bird-01.png"];

sprite.anchorPoint = CGPointZero;

scale = [[CCSpritespriteWithFile:@"bg-bottom.png"] contentSize].height / 128;

[self addChild:sprite];

[self reset];

在Bird类中定义下面的两个方法:

+ (id)bird{

    return[[[Bird alloc] init] autorelease];

}

 

- (void)reset{

    CGSizescreenSize = [[CCDirector sharedDirector] winSize];

    sprite.texture= [[CCTextureCache sharedTextureCache] textureForKey:@"bird-01.png"];

    sprite.position= ccp(60, screenSize.height/2 + 30);

    sprite.rotation= 0;

}

接着我们在GameLayer中添加一个成员:

Bird* bird;

并且在初始化方法中添加下面的初始化语句:

bird = [Bird bird];

[self addChild:bird z:3tag:BIRD_TAG];

编译运行,结果如图:

2014年06月12日 - 远行的风 - 风的驿站

 

下面我们来定义一个动画效果,也就是当点击屏幕的时候,鸟向上弹起的那个动作,这个动作包括两个动画,一个是鸟的旋转动画,一个是鸟拍打翅膀的动画。首先在Bird类中添加成员:

CCSequence* flapAni;

然后定义初始化动画的方法:

- (void)initAnimations{

    NSMutableArray*frames = [NSMutableArray array];

    [framesaddObject:[self spriteFrameWithFileName:@"bird-01.png"]];

    [framesaddObject:[self spriteFrameWithFileName:@"bird-02.png"]];

    [framesaddObject:[self spriteFrameWithFileName:@"bird-03.png"]];

    [framesaddObject:[self spriteFrameWithFileName:@"bird-04.png"]];

    CCAnimation*flap = [CCAnimation animationWithSpriteFrames:frames delay:0.1f];

    CCRotateTo*rotate = [CCRotateTo actionWithDuration:0.05f angle:-20];

    flapAni =[[CCSequence actions:rotate, [CCAnimate actionWithAnimation:flap], nil]retain];

}

 

spriteWithFileName方法:

-(CCSpriteFrame*)spriteFrameWithFileName:(NSString*) name{

    CCTexture2D*texture = [[CCTextureCache sharedTextureCache] addImage:name];

    CGSizetextureSize = [texture contentSize];

    CGRecttextureRect = CGRectMake(0, 0, textureSize.width, textureSize.height);

    return[CCSpriteFrame frameWithTexture:texture rect:textureRect];

}

在初始化方法中调用initAnimations方法,然后添加flap方法:

- (void)flap{

    [spritestopAllActions];

    [spriterunAction:flapAni];

}

stopAllActions是为了防止连续的flap动作时,前一个动作还没有结束。接着我们修改GameLayer,让GameLayer遵循CCTouchOneByOneDelegate协议:

@interface GameLayer : CCLayer<CCTouchOneByOneDelegate>{

    …

}

在GameLayer中重载下面两个方法,注册touch事件:

- (void)onEnterTransitionDidFinish{

    [[[CCDirectorsharedDirector] touchDispatcher] addTargetedDelegate:self priority:1swallowsTouches:YES];

}

 

- (void)onExit{

    [[[CCDirectorsharedDirector] touchDispatcher] removeDelegate:self];

}

并且重载ccTouchBegin方法:

- (BOOL)ccTouchBegan:(UITouch*)touch withEvent:(UIEvent *)event{

    [birdflap];

    

    return YES;

}

编译运行,可以看到在我们点击屏幕的时候,小鸟会向上拍打翅膀。

 

我们继续为Bird类添加下落的方法,这里为了模拟真实地物理运动效果,我们为Bird类添加一个speed成员,用来记录小鸟的即时速度,规定速度向上为正向:

float speed;

同时再定义下面几个成员:

float topLimit;

BOOL isAlive;

float grandHeight;

其中topLimit定义了小鸟的飞行高度上限,防止其飞出屏幕顶端。

grantHeight是地面高度,isAlive用来记录小鸟的状态,如果小鸟摔到地面了,那我们认为游戏失败,将isAlive置为false。

topLimit用下面的语句初始化:

topLimit = [[CCDirectorsharedDirector] winSize].height - 30;

grandHeight的初始化语句:

grandHeight = [[CCSpritespriteWithFile:@"bg-bottom.png"] contentSize].height;

接着添加fall方法:

- (void)fall:(float)deltaTime{

    CGPointposition = sprite.position;

    float y =90 * scale + sprite.contentSize.width * sinf(2 * PI * sprite.rotation / 360);

    floatlength = speed - 0.5f * 290 * deltaTime * deltaTime;

    speed =speed - 13 * deltaTime;

    if (speed< 0) {

        floatr = deltaTime * 100;

        floatrotation = sprite.rotation + r;

        if(rotation > 90) {

            rotation= 90;

        }

        sprite.rotation= rotation;

    }

    floatresultY = position.y + length;

    if (resultY> topLimit) {

        y= topLimit;

    }

    else if(resultY > y) {

        y= resultY;

    } else {

        isAlive= false;

    }

    sprite.position= ccp(position.x, y);

}

fall方法的参数delta为帧的时间间隔,我们通过这个参数结合小鸟的speed成员来计算小鸟在这段时间内的位移,设置其位置,同时对小鸟的角度做一些调整。

float y = 90 * scale +sprite.contentSize.width * sinf(2 * PI * sprite.rotation /360);这行语句计算了小鸟和地面发生碰撞时的理论高度(因为小鸟可能是有倾斜角的,所以取正弦值)。如果小鸟的飞行高度高于topLimit,则不允许其继续升高,如果小鸟的高度低于与地面的碰撞高度,则将isAlive置为false。

我们在flap方法中添加一行语句:

speed = 6.8f;

使小鸟获得一定的上升速度。

 

接着我们来修改GameLayer类,为其添加一个成员:

ccTime dropTime;

这个变量用来统计小鸟的下落时间,在ccTouchBegin方法中置0(也就是说,这个值记录的是每一次小鸟跃起到下一次跃起前的下落事件)。

接着修改GameLayer的update方法,添加更新小鸟位置的代码:

ccTime time1 = dropTime + delta;

if (time1 > 0.03f) {

    [birdfall:delta];

}

dropTime = time1;

这里我们设置在弹起的0.03秒内,小鸟不会下落,这样让小鸟的动作更平滑。

好了,编译运行一下,现在能通过点击屏幕来控制小鸟的飞行了:

2014年06月12日 - 远行的风 - 风的驿站

  

接着我们来处理碰撞。

我们看到,小鸟是一个接近椭圆形的不规则形状,如果使用一个矩形区域(也就是Box2D中的AABB)来检测的话,会有很大的误差,因此我们通过检测小鸟外轮廓线上的“突出点”来检测小鸟的碰撞:

2014年06月12日 - 远行的风 - 风的驿站

上面的途中红色的像素点为我们的碰撞检测点,当这些点中任何一个点和管道发生碰撞的时候,就说明小鸟和管道发生碰撞了。

这些点相对于小鸟锚点(0,0)的相对坐标如下:

(0, 16)(4, 12)(8, 8)(12, 4)(20,0)(39, 0)(59, 4)(63, 8)(67, 16)(67, 19)(63, 23)(59, 35)(55, 39)(51, 43)(47,47)(24, 47)(16, 43)(12, 39)(8, 35)(4, 31)(0, 23),我们在Bird中定义一个数组成员,用来存储这些点:

NSMutableArray* collisionPoints;

初始化方法(在init方法中调用):

- (void)initCollisionPoints{

    collisionPoints= [[NSMutableArray array] retain];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(0, 16)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(4, 12)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(8, 8)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(12, 4)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(20, 0)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(39, 0)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(59, 4)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(63, 8)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(67, 16)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(67, 19)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(63, 23)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(59, 35)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(55, 39)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(51, 43)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(47, 47)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(24, 47)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(16, 43)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(12, 39)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(8, 35)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(4, 31)]];

    [collisionPointsaddObject:[NSValue valueWithCGPoint:ccp(0, 23)]];

}

定义好碰撞点之后,我们考虑一下,小鸟在飞行的过程中,会发生顺时针或者逆时针的转动,这时候,上面定义的这些个相对坐标就应该对应旋转角发生坐标转换,我们定义转换函数如下:

-(CGPoint)convertPointCoordWithRotation:(NSValue*) point{

    CGPointoriginalPoint = [point CGPointValue];

    float a = 2* PI * sprite.rotation / 360;

    floatsinVal = sinf(a);

    floatcosVal = cosf(a);

    float x =originalPoint.x * scale;

    float y =originalPoint.y * scale;

    returnccp(x * cosVal + y * sinVal + birdPosX, y * cosVal - x * sinVal +sprite.position.y);

}

其中birdPosX为小鸟的水平位置,因为小鸟水平方向是不动的,所以我们用一个成员变量来记录这个值:

birdPosX = sprite.position.x;

关于游戏中的旋转坐标变换,可以参考我的另一篇博文《详解游戏中的旋转坐标变换》

定义好之后,我们来定义碰撞检测方法:

- (BOOL)isCollideWithRect:(CGRect)target{

    for(NSValue *point in collisionPoints) {

        if(CGRectContainsPoint(target, [self convertPointCoordWithRotation:point])) {

            returntrue;

        }

    }

    

    returnfalse;

}

这里由于水管是由4个矩形组成的,因此我们通过CGRectContainsPoint来依次判断小鸟的碰撞点是否被其中某个矩形包括,如果包括,则认为小鸟与管子发生了碰撞。

接着我们在Tube类中添加下面的方法:

- (BOOL)isCollideWithBird:(Bird*)bird{

    if(tubeCapLower.position.x < 0 || tubeCapLower.position.x > (68 * scale +60)) {

        returnfalse;

    }

    

    if ([birdisCollideWithRect:[tubeCapLower boundingBox]]

        ||[bird isCollideWithRect:[tubeCapUpper boundingBox]]

        ||[bird isCollideWithRect:[tubeBodyLower boundingBox]]

        ||[bird isCollideWithRect:[tubeBodyUpper boundingBox]]) {

        returntrue;

    }

    

    returnfalse;

}

这个方法用来检测管子是否和小鸟发生碰撞,之所以又在Tube类中定义一个碰撞检测方法,是因为这样我们就不需要将管子的4个组成元素暴露给GameLayer了。

 

我们继续在GameLayer中添加游戏状态检查的方法:

- (BOOL)isDead{

    if (![birdalive] || [tube1 isCollideWithBird:bird]

        ||[tube2 isCollideWithBird:bird]

        ||[tube3 isCollideWithBird:bird]) {

        returntrue;

    }

    

    returnfalse;

}

其中bird的alive属性的定义:

- (BOOL)alive{

    returnisAlive;

}

 

定义完成后,我们在update方法的开头添加游戏状态检测的代码:

if ([self isDead]) {

    [selfunscheduleUpdate];

    

    return;

}

完成后,我们现在可以看到,当小鸟碰到水管或者地面的时候,就不能在继续运动了(也就是游戏结束了)。

到这里,游戏主要的部分就写完了,后面我们还可以继续为游戏添加分数,声音特效,初始界面,GameOver界面等细节,有兴趣的朋友可以接着我们已经做好的例子继续完善(计算分数是使用水平位移来计算的,因为水管的间距是一定的),声音可以用一款小的音效制作软件cfxr来制作,或者在网上下载flappy的音效,使用SimpleAudioEngine类来进行播放。

 

好了,关于flappybird的制作就介绍到这里,如果制作过程中有任何问题,欢迎留言讨论。


http://www.niftyadmin.cn/n/878309.html

相关文章

手把手教你开发一款IOS飞行射击游戏(一)

今天带来的是一个IOS上的飞行射击游戏&#xff0c;通过整个开发过程&#xff0c;帮助和我一样的新手初步了解Cocos2d游戏开发的一些基础知识以及开发流程&#xff0c;熟悉开发过程中使用到的各种工具。由于刚刚接触IOS开发不到3个月时间&#xff0c;有不对的地方或者代码有不完…

java阻塞线程池_线程池解决阻塞方法

一、序言当我们需要使用线程的时候&#xff0c;我们可以新建一个线程&#xff0c;然后显式调用线程的start()方法&#xff0c;这样实现起来非常简便&#xff0c;但在某些场景下存在缺陷&#xff1a;如果需要同时执行多个任务(即并发的线程数量很多)&#xff0c;频繁地创建线程会…

手把手教你开发一款IOS飞行射击游戏(二)

我们接着上一篇文章继续做我们的GameScene。 首先&#xff0c;我们来制作游戏的背景。这里我们希望我们的游戏背景不是那种死气沉沉的一动不动的背景&#xff0c;最好是动态的&#xff0c;并且有点层次感的背景&#xff08;好吧我承认&#xff0c;因为我做的是这样的&#xff0…

汉诺塔算法 java_汉诺塔算法java实现详解

import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;public class HanRuoTa {/*** 汉诺塔算法*/public static void main(String[] args) {int n 0;BufferedReader buf;buf new BufferedReader(new InputStreamReader(System.in));S…

手把手教你开发一款IOS飞行射击游戏(三)

这篇里面我们开始制作我们的飞船。 首先介绍一个有用的工具&#xff0c;TexturePacker。 首先我们简单介绍一下使用TexturePacker处理图片素材的好处。这里我们就不得不提一下Cocos2d中Texture的处理方式&#xff0c;在Cocos2d中&#xff0c;CCSprite&#xff08;精灵&#xff…

java 解压缩zip_java解压缩zip

依赖的包&#xff1a;org.apache.antant1.8.2package com.jd.dlink.service.utils;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.util.Enumeration;import o…

手把手教你开发一款IOS飞行射击游戏(四)

好了&#xff0c;我们继续我们的IOS之旅。 想必大家玩儿手游的时候都用过虚拟摇杆&#xff0c;通过虚拟摇杆在控制屏幕上的元素移动&#xff0c;做各种动作等等。今天我们就通过引用一个开源的代码来完成虚拟摇杆和控制按键的制作&#xff0c;并通过摇杆来控制我们的飞船进行移…

umap算法_UMAP的初步了解及与t-SNE的比较

降维是机器学习中的可视化和理解高维数据的强大工具。t-SNE是最广泛使用的可视化技术之一&#xff0c;但其性能在大型数据集中会受到影响。UMAP是McInnes等人的一项新技术。与t-SNE相比&#xff0c;它具有许多优势&#xff0c;最显著的是提高了速度并更好地保存了数据的全局结构…