注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

风的驿站

一徐清风,半指烛光,觥筹已净,只余茶香。残卷一章,妙趣非常,忽闻帘响,愿闻其详?

 
 
 

日志

 
 
关于我

喜欢写生 编程 音乐 设计 喜欢把自己的想法变成实实在在的东西 喜欢安静的做一些事情 CSDN博客:http://blog.csdn.net/qwertyupoiuytr

网易考拉推荐

【原创】手把手教你制作那个风靡的flappy bird小游戏(一)  

2014-06-12 12:05:09|  分类: Cocos2d |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

前一阵子很火的一款手游flappy bird,不知道大家有没有玩到“根本停不下来”的程度,不过刚刚学会了cocos2dobj-c的我准备拿这款游戏练练手,熟练一下自己对cocos2d的使用。

首先我们当然还是要秉承一贯的风格,自己做美工。当然,为了尊重原作,我们在网上搜了一些游戏截图,参照着绘制完成。当然我也简单的学习了一下像素画的绘制方法和技巧,有兴趣的朋友可以看一下我的另一篇博文中的介绍。

开发用的环境是Xcode 5.1.1,语言是object-c

好了,闲话不多说,先来图片素材:

bird-01-04.png(每个都是68×48):

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

bg-buildings.png640×98):

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

bg-trees.png640×72):

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

bg-cloud.png640×205):

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

bg-bottom.png32×128):

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

tube-body.png105×700):

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

tube-lower.png115×55):

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

tube-upper.png115×55):

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

由于上传图片大小限制,我们在后面标注上实际尺寸,如果图片大小有不一致的,以后边括号中的尺寸为准。

 

接着我们就准备开始coding了。

在动键盘之前,我们依然是先划分工程结构。程序大致上由几个主要元素构成(我们不详细制作开始界面和GameOver的界面),首先是那只flappy的鸟,以及鸟需要穿过的水管,还有背景,分数等,当然音乐神马的就不算了。好了,这些确定好之后,我们先把大体的类创建出来。额,好吧,我们还没有创建工程,由于这里不打算用Box2D中的物理引擎来做模拟,所以我们创建cocos2d 2.0的工程就可以了,Device类型选择iphone,我们想要最终的程序发布为手机版本。

创建好工程后,我们只保留AppDelegate.hAppDelegate.m,删掉其余的.h.m文件。然后我们创建一个GameLayer类,继承自CCLayer,作为我们的游戏场景。

接着我们创建Bird类,Tube类和ParallaxBackground类,这三个类均派生自CCNode。这样我们程序的基本骨架就大致搭建完成了。

接着我们为GameLayer添加一个静态方法:

+ (id)scene;

方法定义:

+ (id)scene{

    CCScene* scene = [CCScene node];

    GameLayer* gameLayer = [GameLayer node];

    [scene addChild:gameLayer];

   

    return scene;

}

接着我们修改AppDelegate类中下面这个方法:

-(void) directorDidReshapeProjection:(CCDirector*)director

{

      if(director.runningScene == nil) {

           [director runWithScene: [GameLayer scene]];

      }

}

编译通过。

 

接着我们来制作滚动背景。对于滚动背景,思路是对于每一个滚动元素,使用两张相同的元素图片交替在屏幕中滚动,当一张离开屏幕后立即被放置到另一张的右侧(屏幕外)继续滚动。

我们在ParallaxBackground类中添加如下方法:

- (void)initStaticBackground{

    CCLayerColor *bgcolor = [CCLayerColor layerWithColor:ccc4(113, 197, 207, 255)];

    [self addChild:bgcolor z:-1];

   

    screenWidth = [[CCDirector sharedDirector] winSize].width;

   

    CCSprite* cloud = [CCSprite spriteWithFile:@"bg-cloud.png"];

    cloud.anchorPoint = CGPointZero;

    cloud.position = ccp(0, 50);

    [self addChild:cloud z:3 tag:BG_CLOUD];

   

    CCSprite* cloud1 = [CCSprite spriteWithFile:@"bg-cloud.png"];

    cloud1.anchorPoint = CGPointZero;

    cloud1.position = ccp(screenWidth-1, 50);

    [self addChild:cloud1 z:3 tag:BG_CLOUD1];

   

    CCSprite* buildings = [CCSprite spriteWithFile:@"bg-buildings.png"];

    buildings.anchorPoint = CGPointZero;

    buildings.position = ccp(0, 71);

    [self addChild:buildings z:4 tag:BG_BUILDINGS];

   

    CCSprite* buildings1 = [CCSprite spriteWithFile:@"bg-buildings.png"];

    buildings1.anchorPoint = CGPointZero;

    buildings1.position = ccp(screenWidth-1, 71);

    [self addChild:buildings1 z:4 tag:BG_BUILDINGS1];

   

    CCSprite* trees = [CCSprite spriteWithFile:@"bg-trees.png"];

    trees.anchorPoint = CGPointZero;

    trees.position = ccp(0, 50);

    [self addChild:trees z:5 tag:BG_TREES];

   

    CCSprite* trees1 = [CCSprite spriteWithFile:@"bg-trees.png"];

    trees1.anchorPoint = CGPointZero;

    trees1.position = ccp(screenWidth-1, 50);

    [self addChild:trees1 z:5 tag:BG_TREES1];

}

screenWidth是在类中定义的一个成员,用来存储屏幕宽度,提高效率。我们为背景添加了一个CCLayerColor作为其背景色,接着我们将背景的树,房子和云的精灵对象都放置到场景中,每一个元素都对应了两个精灵,一个在屏幕中,一个在屏幕外,形成循环滚动的效果。

注意我们这里没有将bg-bottom.png,也就是画面最下方的地面元素添加到滚动背景中,我们是这样考虑的:在小鸟向前飞行的时候,场景中的管子和地面是同速向画面左侧移动的,背景也是向左侧滚动的,我们现在将背景抽象成了单独的类,这样这个类有自己独立的update循环,而我们的GameLayer也有自己独立的update循环,两个循环是不能同步的,所以为了保证管子和地面是同速移动,我们必须将他们放在一起,也就是都放到GameLayer中(因为管子在GameLayer中,要做碰撞判断)。另外还有一个原因,就是地面要将下边的管子长处来的部分遮住,这样管子就要在地面和背景之间,所以如果将地面也放到ParallaxBackground中,就无法做到这一点了。

接着,我们在ParallaxBackground的初始化方法里添加上initStaticBackground的调用,然后在GameLayer的初始化方法中添加下面的代码:

ParallaxBackground* background = [ParallaxBackground node];

[self addChild:background z:1];

 

注:上面代码中用到的枚举结构:

typedef enum{

    BG_BUILDINGS,

    BG_TREES,

    BG_CLOUD,

    BG_BUILDINGS1,

    BG_TREES1,

    BG_CLOUD1

} BG_ELEMENTES;

 

完成后,得到效果如下:

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

 

接着我们为ParallaxBackground添加下面的方法:

- (void)moveElement:(CCNode*)node movedLength:(float) length{

    CGPoint curPos = node.position;

    if (curPos.x - length < -screenWidth) {

        [node setPosition:ccp(curPos.x - length + screenWidth * 2 - 2, curPos.y)];

    }else{

        [node setPosition:ccp(curPos.x - length, curPos.y)];

    }

}

这个方法用来移动背景元素,在移动的时候会进行判断,如果元素移出了背景,就将其放置到右侧屏幕外以备下一次滚动。

然后实现update方法:

- (void)update:(ccTime)delta{

    float treeMove = screenWidth/20*delta;

    float buildingMove = treeMove/2;

    float cloudMove = treeMove/3;

    [self moveElement:[self getChildByTag:BG_TREES] movedLength:treeMove];

    [self moveElement:[self getChildByTag:BG_TREES1] movedLength:treeMove];

    [self moveElement:[self getChildByTag:BG_BUILDINGS] movedLength:buildingMove];

    [self moveElement:[self getChildByTag:BG_BUILDINGS1] movedLength:buildingMove];

    [self moveElement:[self getChildByTag:BG_CLOUD] movedLength:cloudMove];

    [self moveElement:[self getChildByTag:BG_CLOUD1] movedLength:cloudMove];

}

update方法中,不同的元素以不同的速率进行移动,移动范围根据帧的时间(delta)而计算,这样保证匀速移动。然后我们在init方法中调用scheduleUpdate方法。

现在运行我们就能够看到背景层的滚动效果了。

 

我们继续来制作管子和地面。flappy bird中,鸟每次要飞过一组管子,一组管子由两个管子组成,上下相对,管口距离为定值。所以我们定义的时候,一个tube对象也是一组管子,管口之间的空隙的位置在一个区间内随机生成,我们在Tube类中加入下面的成员:

CCSprite* tubeBodyUpper;

CCSprite* tubeCapUpper;

CCSprite* tubeBodyLower;

CCSprite* tubeCapLower;

float screenWidth;

float scale;

上面四个对象分别对应上下两个管子的管口和管身,最后一个scale用来调整retina屏和非retina屏的比例关系。

接着我们添加初始化方法,这里我们定义下面的初始化方法:

- (id)initWithPosition:(float) xPosition{

    if (self = [super init]){

        screenWidth = [[CCDirector sharedDirector] winSize].width;

        srand(time(nil));

        tubeCapUpper = [CCSprite spriteWithFile:@"tube-upper.png"];

        tubeCapUpper.anchorPoint = CGPointZero;

        tubeCapLower = [CCSprite spriteWithFile:@"tube-lower.png"];

        tubeCapLower.anchorPoint = CGPointZero;

        scale = [tubeCapUpper contentSize].width / 115;

        tubeBodyUpper = [CCSprite spriteWithFile:@"tube-body.png"];

        tubeBodyUpper.anchorPoint = CGPointZero;

        tubeBodyLower = [CCSprite spriteWithFile:@"tube-body.png"];

        tubeBodyLower.anchorPoint = ccp(0, 0.3f);

        [self reset:xPosition];

        [self addChild:tubeBodyUpper];

        [self addChild:tubeBodyLower];

        [self addChild:tubeCapUpper];

        [self addChild:tubeCapLower];

    }

   

    return self;

}

初始化的时候需要传入一个x位置值,用来设置管子的初始位置。

reset方法定义如下:

- (void)reset:(float) x{

    float y = -CCRANDOM_0_1() * 420 * scale + 100 * scale;

    tubeBodyLower.position = ccp(x, y);

    tubeCapLower.position = ccp(x - 2, y + 460 * scale);

    tubeCapUpper.position = ccp(x - 2, y + 735 * scale);

    tubeBodyUpper.position = ccp(x, y + 790 * scale);

}

用来设置每个管子的位置。

我们在GameLayer中添加下面的成员:

float scale;

Tube* tube1;

Tube* tube2;

Tube* tube3;

然后在初始化方法中添加下面的语句:

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

int startPos = 100 * scale;

tube1 = [[[Tube alloc] initWithPosition:startPos] autorelease];

tube2 = [[[Tube alloc] initWithPosition:startPos + scale * 355] autorelease];

tube3 = [[[Tube alloc] initWithPosition:startPos + scale * 710] autorelease];

[self addChild:tube1 z:2];

[self addChild:tube2 z:2];

[self addChild:tube3 z:2];

完成后我们便以运行一下,发现已经能够看到初始化好的管子了:

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

 

接着我们在GameLayer的初始化方法中将地面对象添加上:

CGRect repeatRect = CGRectMake(0, 0, screenWidth, scale * 128);

ccTexParams params = {

    GL_LINEAR,

    GL_LINEAR,

    GL_REPEAT,

    GL_REPEAT

};

 

CCSprite* bottom = [CCSprite spriteWithFile:@"bg-bottom.png" rect:repeatRect];

bottom.anchorPoint = CGPointZero;

bottom.position = CGPointZero;

[bottom.texture setTexParameters:&params];

[self addChild:bottom z:5 tag:BG_BOTTOM];

 

CCSprite* bottom1 = [CCSprite spriteWithFile:@"bg-bottom.png" rect:repeatRect];

bottom1.anchorPoint = CGPointZero;

bottom1.position = ccp(screenWidth-1, 0);

[bottom1.texture setTexParameters:&params];

[self addChild:bottom1 z:5 tag:BG_BOTTOM1];

这里用到了重复纹理,通过不断重复当前的地面纹理来铺满整个地面区域。

用到的枚举定义如下:

typedef enum{

    BG_BOTTOM,

    BG_BOTTOM1,

    BIRD_TAG,

    ScoreLabelTag,

    MsgLabelTag,

    HighScoreLabelTag

} GAMESCENE_OBJECTS;

完成后,地面已经制作完成了。

然后我们为Tube类添加下面两个方法:

- (void)move:(float) length{

    [self moveTubeElement:length tubeElement:tubeBodyUpper];

    [self moveTubeElement:length tubeElement:tubeCapLower];

    [self moveTubeElement:length tubeElement:tubeCapUpper];

    [self moveTubeElement:length tubeElement:tubeBodyLower];

}

 

- (void)moveTubeElement:(float) length tubeElement:(CCSprite*) tubeElement{

    float capWidth = [tubeCapUpper contentSize].width;

    CGPoint position = tubeElement.position;

    position.x = position.x - length;

    if (position.x < -capWidth) {

        position.x += 1065 * scale;

    }

    tubeElement.position = position;

}

其中move方法用来同步移动一组管子的4个元素,moveTubeElement方法用来移动管子的管口或者管身,当这个元素消失在屏幕外的时候,将这个元素向右移动三个管子的距离(1065px),这样我们就重复利用3个管子来模拟无穷无尽的管子了。

接着我们在GameLayer中添加方法:

- (void)moveElement:(CCNode*)node movedLength:(float) length{

    CGPoint curPos = node.position;

    if (curPos.x - length < -screenWidth) {

        [node setPosition:ccp(curPos.x - length + screenWidth * 2 - 2, curPos.y)];

    }else{

        [node setPosition:ccp(curPos.x - length, curPos.y)];

    }

}

用这个方法来移动地面元素(和ParallaxBackground方法中的相同,不多解释了)。

然后在GameLayerupdate方法中添加:

float bottomMove = screenWidth/4*delta;

[self moveElement:[self getChildByTag:BG_BOTTOM] movedLength:bottomMove];

[self moveElement:[self getChildByTag:BG_BOTTOM1] movedLength:bottomMove];

   

[tube1 move:bottomMove];

[tube2 move:bottomMove];

[tube3 move:bottomMove];

然后在初始化方法中调用scheduleUpdate方法。

现在编译运行一下,可以看到管子和地面的运动了。

 

好了,第一部分先到这里,在下一篇中我们来制作完成flappy bird


手把手教你制作那个风靡的flappy bird小游戏(二)
  评论这张
 
阅读(314)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017