在第一篇《如何使用CCRenderTexture创建动态纹理》基础上,增加创建动态山丘,原文《How To Create A Game Like Tiny Wings with Cocos2D 2.X Part 1》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。

步骤如下:
1.使用上一篇的工程;

2.添加地形类
Terrain,派生自
CCNode类。文件
Terrain.h代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

 
#pragma once


#include 
"cocos2d.h"

#define kMaxHillKeyPoints

class Terrain : 
public cocos2d::CCNode

{


public:

    Terrain(
void);

    ~Terrain(
void);

CREATE_FUNC(Terrain);

    CC_SYNTHESIZE_RETAIN(cocos2d::CCSprite*, _stripes, Stripes);

private:

    
int _offsetX;

    cocos2d::CCPoint _hillKeyPoints[kMaxHillKeyPoints];

};

这里声明了一个_hillKeyPoints数组,用来存储每个山丘顶峰的点,同时声明了一个_offsetX代表当前地形滚动的偏移量。文件Terrain.cpp代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

 
#include 
"Terrain.h"


using 
namespace cocos2d;

Terrain::Terrain(
void)

{

    _stripes = 
NULL;

    _offsetX = 0;

}

Terrain::~Terrain(
void)

{

    CC_SAFE_RELEASE_NULL(_stripes);

}

增加如下方法:

1

2

3

4

5

6

7

8

9

10

11

12

 
void Terrain::generateHills()

{

    CCSize winSize = CCDirector::sharedDirector()->getWinSize();

    
float x = 
;

    
float y = winSize.height / 
;

    
for (
int i = 
; i < kMaxHillKeyPoints; ++i)

    {

        _hillKeyPoints[i] = ccp(x, y);

        x += winSize.width / 
;

        y = rand() % (
int)winSize.height;

    }

}

这个方法用来生成随机的山丘顶峰的点。第一个点在屏幕的左侧中间,之后的每一个点,x轴方向移动半个屏幕宽度,y轴方向设置为0到屏幕高度之间的一个随机值。添加以下方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

 
bool Terrain::init()

{

    
bool bRet = 
false;

    
do 

    {

        CC_BREAK_IF(!CCNode::init());

this->generateHills();

bRet = 
true;

    } 
while (
);

return bRet;

}

void Terrain::draw()

{

    CCNode::draw();

    
for (
int i = 
; i < kMaxHillKeyPoints; ++i)

    {

        ccDrawLine(_hillKeyPoints[i - 
], _hillKeyPoints[i]);

    }

}

init方法调用generateHills方法创建山丘,draw方法简单地绘制相邻点之间的线段,方便可视化调试。添加以下方法:

1

2

3

4

5

 
void Terrain::setOffsetX(
float newOffsetX)

{

    _offsetX = newOffsetX;

    
this->setPosition(ccp(-_offsetX * 
this->getScale(), 
));

}

英雄沿着地形的x轴方法前进,地形向左滑动。因此,偏移量需要乘以-1,还有缩放比例。打开HelloWorldScene.h文件,添加头文件引用:

1

 
#include 
"Terrain.h"

添加如下变量:

1

 
Terrain *_terrain;

打开HelloWorldScene.cpp文件,在onEnter方法里,调用genBackground方法之前,加入如下代码:

1

2

 
_terrain = Terrain::create();


this->addChild(_terrain, 
);

update方法里,最后面添加如下代码:

1

 
_terrain->setOffsetX(offset);

修改genBackground方法为如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

 
void HelloWorld::genBackground()

{

    
if (_background)

    {

        _background->removeFromParentAndCleanup(
true);

    }

ccColor4F bgColor = 
this->randomBrightColor();

    _background = 
this->spriteWithColor(bgColor, 

);

CCSize winSize = CCDirector::sharedDirector()->getWinSize();

    _background->setPosition(ccp(winSize.width / 
, winSize.height / 
));

    ccTexParams tp = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};

    _background->getTexture()->setTexParameters(&tp);

this->addChild(_background);

ccColor4F color3 = 
this->randomBrightColor();

    ccColor4F color4 = 
this->randomBrightColor();

    CCSprite *stripes = 
this->spriteWithColor1(color3, color4, 


);

    ccTexParams tp2 = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_CLAMP_TO_EDGE};

    stripes->getTexture()->setTexParameters(&tp2);

    _terrain->setStripes(stripes);

}

注意,每次触摸屏幕,地形上的条纹纹理都会随机生成一个新的条纹纹理,这方便于测试。此外,在Update方法里_background调用setTextureRect方法时,可以将offset乘以0.7,这样背景就会比地形滚动地慢一些。编译运行,可以看到一些线段,连接着山丘顶峰的点,如下图所示:

当看到山丘滚动,可以想象得到,这对于一个Tiny Wings游戏,并不能很好的工作。由于采用y轴随机值,有时候山丘太高,有时候山丘又太低,而且x轴也没有足够的差别。但是现在已经有了这些测试代码,是时候用更好的算法了。
3.更好的山丘算法。使用Sergey的算法来进行实现。打开Terrain.cpp文件,修改generateHills方法为如下:

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

 
void Terrain::generateHills()

{

    CCSize winSize = CCDirector::sharedDirector()->getWinSize();

float minDX = 
;

    
float minDY = 
;

    
int rangeDX = 
;

    
int rangeDY = 
;

float x = -minDX;

    
float y = winSize.height / 
;

float dy, ny;

    
float sign = 

// +1 - going up, -1 - going  down
    
float paddingTop = 
;

    
float paddingBottom = 
;

for (
int i = 
; i < kMaxHillKeyPoints; ++i)

    {

        _hillKeyPoints[i] = ccp(x, y);

        
if (i == 
)

        {

            x = 
;

            y = winSize.height / 
;

        } 

        
else

        {

            x += rand() % rangeDX + minDX;

            
while (
true)

            {

                dy = rand() % rangeDY + minDY;

                ny = y + dy * sign;

                
if (ny < winSize.height - paddingTop && ny > paddingBottom)

                {

                    
break;

                }

            }

            y = ny;

        }

        sign *= -
;

    }

}

这个算法执行的策略如下:

  • 在范围160加上0-80之间的随机数进行递增x轴。
  • 在范围60加上0-40之间的随机数进行递增y轴。
  • 每次都反转y轴偏移量。
  • 不要让y轴值过于接近顶部或底部(paddingTop, paddingBottom)。
  • 开始于屏幕外的左侧,硬编码第二个点为(0, winSize.height/2),所以左侧屏幕外有一个山丘。

编译运行,现在可以看到一个更好的山丘算法,如下图所示:




4.一次只绘制部分。在更进一步之前,需要做出一项重大的性能优化。现在,绘制出了山丘的1000个顶峰点,即使每次都只有少数在屏幕上看得到。所以,可以根据屏幕区域来计算哪些顶峰点会被显示出来,然后只显示那些点,如下图所示:




打开
Terrain.h文件,添加如下变量:

1

2

 
int _fromKeyPointI;


int _toKeyPointI;

打开Terrain.cpp文件,在构造函数里面添加如下代码:

1

2

 
_fromKeyPointI = 
;

_toKeyPointI = 
;

添加如下方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

 
void Terrain::resetHillVertices()

{

    CCSize winSize = CCDirector::sharedDirector()->getWinSize();

static 
int prevFromKeyPointI = -
;

    
static 
int prevToKeyPointI = -
;

// key points interval for drawing
    
while (_hillKeyPoints[_fromKeyPointI + 
].x < _offsetX - winSize.width / 
 / 
this->getScale())

    {

        _fromKeyPointI++;

    }

    
while (_hillKeyPoints[_toKeyPointI].x < _offsetX + winSize.width * 9 / 
 / 
this->getScale())

    {

        _toKeyPointI++;

    }

}

这里,遍历每一个顶峰点(从0开始),将它们的x轴值拿来做比较。无论当前对应到屏幕左边缘的偏移量设置为多少,只要将它减去winSize.width/8。如果顶峰点的x轴值小于结果值,那么就继续遍历,直到找到一个大于结果值的,这个顶峰点就是显示的起始点。对于toKeypoint也采用同样的过程。修改draw方法,代码如下:

1

2

3

4

5

6

7

8

9

 
void Terrain::draw()

{

    CCNode::draw();

    
for (
int i = MAX(_fromKeyPointI, 
); i <= _toKeyPointI; ++i)

    {

        ccDrawColor4F(
.



.
);

        ccDrawLine(_hillKeyPoints[i - 
], _hillKeyPoints[i]);

    }

}

现在,不是绘制所有点,而是只绘制当前可见的点,这些点是前面计算得到的。另外,也把线的颜色改成红色,这样更易于分辨。接着,在init方法里面,最后面添加如下代码:

1

 
this->resetHillVertices();

setOffsetX方法里面,最后面添加如下代码:

1

 
this->resetHillVertices();

为了更容易看到,打开HelloWorldScene.cpp文件,在onEnter方法,最后面添加如下代码:

1

 
this->setScale(
.
);

编译运行,可以看到线段出现时才进行绘制,如下图所示:

5.制作平滑的斜坡。山丘是有斜坡的,而不是这样直上直下的直线。一个办法是使用余弦函数让山丘弯曲。回想一下,余弦曲线就如下图所示:

因此,它是从1开始,每隔PI长度,曲线下降到-1。但怎么利用这个函数来创建一个漂亮的曲线连接顶峰点呢?先只考虑两个点的情况,如下图所示:

首先,需要分段绘制线,因此,需要每10个点创建一个区段。同样的,想要一个完整的余弦曲线,因此,可以将PI除以区段的数量,得到每个点的角度。然后,让cos(0)对应p0的y轴值,而cos(PI)对应p1的y轴值。要做到这一点,将调用cos(angle),乘以p1和p0之间距离的一半(图上的ampl)。由于cos(0)=1,而cos(PI)=-1,所以,ampl在p0,而-ampl在p1。将它加上中点坐标,就可以得到想要的y轴值。打开Terrain.h文件,添加区段长度定义,如下代码:

1

 
#define kHillSegmentWidth

然后,打开Terrain.cpp文件,在draw方法里面,ccDrawLine之后,添加如下代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

 
ccDrawColor4F(
.

.

.

.
);

CCPoint p0 = _hillKeyPoints[i - 
];

CCPoint p1 = _hillKeyPoints[i];


int hSegments = floorf((p1.x - p0.x) / kHillSegmentWidth);


float dx = (p1.x - p0.x) / hSegments;


float da = M_PI / hSegments;


float ymid = (p0.y + p1.y) / 
;


float ampl = (p0.y - p1.y) / 
;

CCPoint pt0, pt1;

pt0 = p0;


for (
int j = 
; j < hSegments + 
; ++j)

{

    pt1.x = p0.x + j * dx;

    pt1.y = ymid + ampl * cosf(da * j);

ccDrawLine(pt0, pt1);

pt0 = pt1;

}

打开HelloWorldScene.cpp文件,在onEnter方法,设置scale为1.0,如下代码:

1

 
this->setScale(
.
);

编译运行,现在可以看到一条曲线连接着山丘,如下图所示:

6.绘制山丘。用上一篇文章生成的条纹纹理来绘制山丘。计划是对山丘的每个区段,计算出两个三角形来渲染山丘,如下图所示:

还将设置每个点的纹理坐标。对于x坐标,简单地除以纹理的宽度(因为纹理重复)。对于y坐标,将山丘的底部映射为0,顶部映射为1,沿着条带的方向分发纹理高度。打开Terrain.h文件,添加如下代码:

1

2

 
#define kMaxHillVertices

#define kMaxBorderVertices

添加类变量,代码如下:

1

2

3

4

5

 
int _nHillVertices;

cocos2d::CCPoint _hillVertices[kMaxHillVertices];

cocos2d::CCPoint _hillTexCoords[kMaxHillVertices];


int _nBorderVertices;

cocos2d::CCPoint _borderVertices[kMaxBorderVertices];

打开Terrain.cpp文件,在resetHillVertices方法里面,最后面添加如下代码:

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

 
if (prevFromKeyPointI != _fromKeyPointI || prevToKeyPointI != _toKeyPointI)

{

    
// vertices for visible area
    _nHillVertices = 
;

    _nBorderVertices = 
;

    CCPoint p0, p1, pt0, pt1;

    p0 = _hillKeyPoints[_fromKeyPointI];

    
for (
int i = _fromKeyPointI + 
; i < _toKeyPointI + 
; ++i)

    {

        p1 = _hillKeyPoints[i];

// triangle strip between p0 and p1
        
int hSegments = floorf((p1.x - p0.x) / kHillSegmentWidth);

        
float dx = (p1.x - p0.x) / hSegments;

        
float da = M_PI / hSegments;

        
float ymid = (p0.y + p1.y) / 
;

        
float ampl = (p0.y - p1.y) / 
;

        pt0 = p0;

        _borderVertices[_nBorderVertices++] = pt0;

        
for (
int j = 
; j < hSegments + 
; ++j)

        {

            pt1.x = p0.x + j * dx;

            pt1.y = ymid + ampl * cosf(da * j);

            _borderVertices[_nBorderVertices++] = pt1;

_hillVertices[_nHillVertices] = ccp(pt0.x, 
);

            _hillTexCoords[_nHillVertices++] = ccp(pt0.x / 

.0f);

            _hillVertices[_nHillVertices] = ccp(pt1.x, 
);

            _hillTexCoords[_nHillVertices++] = ccp(pt1.x / 

.0f);

_hillVertices[_nHillVertices] = ccp(pt0.x, pt0.y);

            _hillTexCoords[_nHillVertices++] = ccp(pt0.x / 

);

            _hillVertices[_nHillVertices] = ccp(pt1.x, pt1.y);

            _hillTexCoords[_nHillVertices++] = ccp(pt1.x / 

);

pt0 = pt1;

        }

p0 = p1;

    }

prevFromKeyPointI = _fromKeyPointI;

    prevToKeyPointI = _toKeyPointI;

}

这里的大部分代码,跟上面的使用余弦绘制山丘曲线一样。新的部分,是将山丘每个区段的顶点用来填充数组,每个条纹需要4个顶点和4个纹理坐标。在draw方法里面,最上面添加如下代码:

1

2

3

4

5

6

7

8

9

10

 
CC_NODE_DRAW_SETUP();

ccGLBindTexture2D(_stripes->getTexture()->getName());

ccGLEnableVertexAttribs(kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords);

ccDrawColor4F(
.0f, 
.0f, 
.0f, 
.0f);

glVertexAttribPointer(kCCVertexAttrib_Position, 
, GL_FLOAT, GL_FALSE, 
, _hillVertices);

glVertexAttribPointer(kCCVertexAttrib_TexCoords, 
, GL_FLOAT, GL_FALSE, 
, _hillTexCoords);

glDrawArrays(GL_TRIANGLE_STRIP, 
, (GLsizei)_nHillVertices);

这里绑定条纹纹理作为渲染纹理来使用,传入之前计算好的顶点数组和纹理坐标数组,然后以GL_TRIANGLE_STRIP来绘制这些数组。此外,注释掉绘制山丘直线和曲线的代码。在init方法里面,调用generateHills方法之前,添加如下代码:

1

 
this->setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTexture));

打开HelloWorldScene.cpp文件,在spriteWithColor1方法里面,注释// Layer 4: Noise里,更改混合方式,代码如下:

1

 
ccBlendFunc blendFunc = {GL_DST_COLOR, CC_BLEND_DST};

编译运行,可以看到不错的山丘了,如下图所示:

7.还不完善?仔细看山丘,可能会注意到一些不完善的地方,如下图所示:

增加水平区段数量,可以提高一些质量。打开Terrain.h文件,修改kHillSegmentWidth为如下:

1

 
#define kHillSegmentWidth

通过减少每个区段的宽度,强制代码生成更多的区段来填充空间。编译运行,可以看到山丘看起来更好了。当然,代价是处理时间。效果如下图所示:

在第二部分,将会实现海豹飞翔。
参考资料:
1.How To Create A Game Like Tiny Wings with Cocos2D 2.X Part 1 http://www.raywenderlich.com/32954/how-to-create-a-game-like-tiny-wings-with-cocos2d-2-x-part-1
2.(译)如何制作一个类似tiny wings的游戏:第一部分 http://www.cnblogs.com/zilongshanren/archive/2011/07/01/2095489.html
非常感谢以上资料,本例子源代码附加资源下载地址http://download.csdn.net/detail/akof1314/5733037
如文章存在错误之处,欢迎指出,以便改正。

如何制作一个类似Tiny Wings的游戏 Cocos2d-x 2.1.4的更多相关文章

  1. 如何制作一个类似Tiny Wings的游戏(2) Cocos2d-x 2.1.4

    在第二篇<如何制作一个类似Tiny Wings的游戏>基础上,增加添加主角,并且使用Box2D来模拟主角移动,原文<How To Create A Game Like Tiny Wi ...

  2. 怎样制作一个相似Tiny Wings的游戏 Cocos2d-x 2.1.4

    在第一篇<怎样使用CCRenderTexture创建动态纹理>基础上,添加�创建动态山丘,原文<How To Create A Game Like Tiny Wings with C ...

  3. 制作一个类似苹果VFL的格式化语言来描述UIStackView

    在项目中总是希望页面上各处的文字,颜色,字体大小甚至各个视图控件布局都能够在发版之后能够修改以弥补一些前期考虑不周,或者根据统计数据能够随时进行调整,当然是各个版本都能够统一变化.看到这样的要求后,第 ...

  4. [0]尝试用Unity3d制作一个王者荣耀(持续更新)->游戏规划

    太得闲了于是想写个农药,虽然可能会失败但是还是要试一试. 因为是自学的不是Unity专业的可能表达语言会有些不标准!望见谅! 结构: 以组件式(比如说摇杆控制和玩家部分的编写是分离的,可以自由拼装)作 ...

  5. 怎样制作一个横版格斗过关游戏 Cocos2d-x 2.0.4

     本文实践自 Allen Tan 的文章<How To Make A Side-Scrolling Beat 'Em Up Game Like Scott Pilgrim with Coco ...

  6. three.js 制作一个三维的推箱子游戏

    今天郭先生发现大家更喜欢看我发的three.js小作品,今天我就发一个3d版本推箱子的游戏,其实webGL有很多框架,three.js并不合适做游戏引擎,但是可以尝试一些小游戏.在线案例请点击博客原文 ...

  7. 教你如何用python和pygame制作一个简单的贪食蛇游戏,可自定义

    1.效果图 2.完整的代码 #第1步:导出模块 import pygame, sys, random from pygame.locals import * # 第2步:定义颜色变量,在pygame中 ...

  8. 如何制作一个类似jquery插件的vue插件

    vue拿来写插件,会不会太那啥? 请跟我念,"不会,符合业务需求才是你的老板最想要的." 如何封装一个可以全局调用的vue插件 其原理其实相当简单,通过new Vue(vueCom ...

  9. 使用CocosSharp制作一个游戏 - CocosSharp中文教程

    注:本教程翻译自官方<Walkthrough - Building a game with CocosSharp>,官方教程有很多地方说的不够详细,或者代码不全,导致无法继续,本人在看了G ...

随机推荐

  1. Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 1

    Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 序章 Asp.Net MVC4 + Oracle + EasyUI + Bootstrap 序章 -- 新建微软实 ...

  2. 表单元素的外观改变(webkit and IE10)

    1.禁止表单默认外观: input,select{ -webkit-appearance:none; appearance:none; }2.伪元素改变ie10表单元素默认外观 select::-ms ...

  3. 轻量级IOC框架Guice

    java轻量级IOC框架Guice Guice是由Google大牛Bob lee开发的一款绝对轻量级的java IoC容器.其优势在于: 速度快,号称比spring快100倍. 无外部配置(如需要使用 ...

  4. 如何查找Linux的函数定义的位置?

    网上的许多站点提供这样的服务,如下面这个: http://lxr.free-electrons.com/ident?v=3.10 Linux的错误返回值:3.10版本 Linux/include/ua ...

  5. js判断浏览器类型(手机和电脑终端)

    工作中经常会用到通过js来判断浏览器的功能!今天这里通过js来判断浏览器是来自移动设备还是pc设备! 代码如下: var browser={ versions:function(){ var u = ...

  6. C#压缩字符串

    在论坛上看到一个压缩字符串的问题,特此记录以备后用! static string GetStringR(string inputStr) { return Regex.Replace(inputStr ...

  7. WebService和AngularJS实现模糊过滤查询

    WebService和AngularJS实现模糊过滤查询   [概要] 网上看到一个不错的帖子,用WebService获取json,然后在前端使用AngularJs进行过滤搜索,看完文章后,按自己的想 ...

  8. Java中的嵌套类和内部类

    以前看<Java编程思想>的时候,看到过嵌套类跟内部类的区别,不过后来就把它们的概念给忘了吧.昨天在看<数据结构与算法分析(Java语言版)>的时候,又遇到了这个概念,当时就很 ...

  9. 【分享】SAS统计分析软件学习教程电子书合集下载

    SAS是著名的统计分析软件,全称为Statistics Analysis System,最早由北卡罗来纳大学的两位生物统计学研究生编制,并于1976年成立了SAS软件研究所,正式推出了SAS软件. 转 ...

  10. RoleManager 进行角色管理

    ASP.NET Identity 使用 RoleManager 进行角色管理 (VS2013RC) 注:本文系作者原创,但可随意转载. 最近做一个Web平台系统,系统包含3个角色,“管理员, 企业用户 ...