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

原创 2017年01月03日 23:15:40

这一篇教程中我们继续来完成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的制作就介绍到这里,如果制作过程中有任何问题,欢迎留言讨论。

版权声明:本文为博主原创文章,未经博主允许不得转载。

JavaScript操作canvas制作前端H5小游戏——Flappy Bird

游戏查看源码和素材下载博主学习前端一年多一点,还是个新手,不过勇于尝试,才能不断进步,如果代码质量不好,欢迎提意见,下面开始讲解,首先贴张游戏界面图:游戏使用canvas画图制作,分析游戏确定有这几个...

啊哈 phaser制作小游戏flappy bird(1)

看到phaser的world和camera玩的已如此之好,果断自己操练了一个小游戏出来玩玩。记录一下啦,加深一下印象。首先想起他引擎一样。在html5中添加一个canvas然后实例化一个游戏对象来实现...

用Unity3D开发2D小游戏 Flappy Bird

简介: 最近在学习Unity3D,用了两天时间做了个2D小游戏打算放上了和大家分享一下,项目名定义为Flapping,是参考Flapper Bird做的,高手勿喷。 游戏效果图:...

Flappy bird 小游戏的实现

//3.0初速度需要60秒减少至0 const float MaxTime = 50; //加速度,方向向下 const float VG = 0.05; //初速度 const float MaxV...
  • w_sx_
  • w_sx_
  • 2014年08月25日 22:57
  • 527

用Phaser来制作一个html5游戏——flappy bird (二)

用Phaser来制作一个html5游戏——flappy bird (二) 在上一篇教程中我们完成了boot、preload、menu这三个state的制作,下面我们就要进入本游戏最核心的一个stat...

一个C语言写的小游戏(flappy bird)

最近在看知乎是发现了一个这一个专栏 https://zhuanlan.zhihu.com/c2game 从中获取的许多知识,本文中的游戏也是从里面学到的,不过本人又自己加了一些功能。 这是...

一个类似于flappy Bird的小游戏源码

  • 2017年12月09日 14:43
  • 2.72MB
  • 下载

Switf_Flappy Bird_小游戏

  • 2014年06月25日 14:21
  • 177KB
  • 下载

quick-cocos2d-x从零开始游戏开发笔记(四):参照Flappy Bird制作第一个游戏②

主角有了,下面应该参照原作让它上下微微的起伏。 在MenuScene:ctor()方法里面继续添加代码: local function titleBirdMove(dt) local sequenc...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:手把手教你制作那个风靡的flappy bird小游戏(二)
举报原因:
原因补充:

(最多只允许输入30个字)