在第一篇《如何使用CCRenderTexture创建动态纹理》基础上,增加创建动态山丘,原文《How To Create A Game Like Tiny Wings with Cocos2D 2.X Part 1》,在这里继续以Cocos2d-x进行实现。有关源码、资源等在文章下面给出了地址。
步骤如下:2.添加地形类Terrain,派生自CCNode类。文件Terrain.h代码如下:
#include "cocos2d.h"
USING_NS_CC;
#define kMaxHillKeyPoints 1000
class Terrain:public Node {
public:
Terrain();
~Terrain();
CREATE_FUNC(Terrain);
void generateHills();
CC_SYNTHESIZE_RETAIN(Sprite*, _stripes, Stripes);
void draw();
bool init();
void setOffsetX(float newOffsetX);
private:
int _offsetX;
Point _hillKeyPoints[kMaxHillKeyPoints];
};
这里声明了一个
_hillKeyPoints
数组,用来存储每个山丘顶峰的点,同时声明了一个
_offsetX
代表当前地形滚动的偏移量。文件
Terrain.cpp
代码如下:
Terrain::Terrain()
{
_stripes = NULL;
_offsetX = 0;
}
Terrain::~Terrain()
{
CC_SAFE_RELEASE_NULL(_stripes);
}
void Terrain::generateHills()
{
Size winSize = Director::getInstance()->getWinSize();
float x = 0;
float y = winSize.height / 2;
for (int i = 0; i < kMaxHillKeyPoints; ++i) {
_hillKeyPoints[i] = Point(x, y);
x += winSize.width / 2;
y = rand() % (int) winSize.height;
}
}
这个方法用来生成随机的山丘顶峰的点。第一个点在屏幕的左侧中间,之后的每一个点,x轴方向移动半个屏幕宽度,y轴方向设置为0到屏幕高度之间的一个随机值。添加以下方法:
bool Terrain::init()
{
bool bRet = false;
do {
CC_BREAK_IF(!Node::init());
this->generateHills();
bRet = true;
} while (0);
return bRet;
}
void Terrain::draw()
{
Node::draw();
for (int i = 1; i < kMaxHillKeyPoints; ++i) {
DrawPrimitives::drawLine(_hillKeyPoints[i - 1], _hillKeyPoints[i]);
}
}
init
方法调用
generateHills
方法创建山丘,
draw
方法简单地绘制相邻点之间的线段,方便可视化调试。添加以下方法:
void Terrain::setOffsetX(float newOffsetX)
{
_offsetX = newOffsetX;
this->setPosition(Point(-_offsetX * this->getScale(), 0));
}
英雄沿着地形的x轴方法前进,地形向左滑动。因此,偏移量需要乘以-1,还有缩放比例。打开
HelloWorldScene.h
文件,添加头文件引用:
#include "Terrain.h"
添加如下变量:
CC_SYNTHESIZE_RETAIN(Terrain*, _terrain, Terrain);
打开
HelloWorldScene.cpp
文件,在
onEnter
方法里,调用
genBackground
方法之前,加入如下代码:
_terrain = Terrain::create();
this->addChild(_terrain , 1);
在
update
方法里,最后面添加如下代码:
_terrain->setOffsetX(offset);
修改
genBackground
方法为如下:
void HelloWorld::genBackground()
{
if (_background) {
_background->removeFromParentAndCleanup(true);
}
Color4F bgColor = this->randomBrightColor();
_background = this->spriteWithColor(bgColor, 512, 512);
Size winSize = Director::getInstance()->getWinSize();
_background->setPosition(Point(winSize.width / 2, winSize.height / 2));
Texture2D::TexParams tp = {GL_LINEAR,GL_LINEAR,GL_REPEAT,GL_REPEAT};
_background->getTexture()->setTexParameters(tp);
this->addChild(_background);
Color4F color3 = this->randomBrightColor();
Color4F color4 = this->randomBrightColor();
Sprite* stripes = this->spriteWithColor1(color3, color4, 512, 512, 4);
Texture2D::TexParams tp2 = {GL_LINEAR,GL_LINEAR,GL_REPEAT,GL_CLAMP_TO_EDGE};
stripes->getTexture()->setTexParameters(tp2);
_terrain->setStripes(stripes);
}
注意,每次触摸屏幕,地形上的条纹纹理都会随机生成一个新的条纹纹理,这方便于测试。此外,在
Update
方法里_background调用setTextureRect方法时,可以将offset乘以0.7,这样背景就会比地形滚动地慢一些。编译运行,可以看到一些线段,连接着山丘顶峰的点,如下图所示:
void Terrain::generateHills()
{
Size winSize = Director::getInstance()->getWinSize();
float minDX = 160;
float minDY = 60;
int rangeDX = 80;
int rangeDY = 40;
float x = -minDX;
float y = winSize.height / 2;
float dy,ny;
float sign = 1; // +1 - going up ,-1 - going down
float paddingTop = 20;
float paddingBottom = 20;
for (int i = 0; i < kMaxHillKeyPoints; ++i) {
_hillKeyPoints[i] = Point(x, y);
if (i == 0) {
x = 0;
y = winSize.height / 2;
}
else
{
x += rand() % rangeDX + minDX;
while (true) {
dy = rand() % rangeDX + minDX;
ny = y + dy * sign;
if (ny < winSize.height - paddingTop && ny > paddingBottom) {
break;
}
}
y = ny;
}
sign *= -1;
}
}
这个算法执行的策略如下:
打开Terrain.h文件,添加如下变量:
int _fromKeyPointI;
int _toKeyPointI;
打开Terrain.cpp文件,在构造函数里面添加如下代码:
_fromKeyPointI = 0;
_toKeyPointI = 0;
添加如下方法:
void Terrain::resetHillVertices()
{
Size winSize = Director::getInstance()->getWinSize();
static int prevFromKeyPointI = -1;
static int prevToKeyPointI = -1;
while (_hillKeyPoints[_fromKeyPointI + 1].x < _offsetX - winSize.width / 8 / this->getScale()) {
_fromKeyPointI++;
}
while (_hillKeyPoints[_toKeyPointI].x < _offsetX + winSize.width * 9 / 8 / this->getScale()) {
_toKeyPointI++;
}
}
这里,遍历每一个顶峰点(从0开始),将它们的x轴值拿来做比较。无论当前对应到屏幕左边缘的偏移量设置为多少,只要将它减去winSize.width/8。如果顶峰点的x轴值小于结果值,那么就继续遍历,直到找到一个大于结果值的,这个顶峰点就是显示的起始点。对于
toKeypoint
也采用同样的过程。修改
draw
方法,代码如下:
void Terrain::draw()
{
Node::draw();
for (int i = MAX(_fromKeyPointI, 1); i < _toKeyPointI; ++i) {
DrawPrimitives::setDrawColor4F(1.0, 0, 0, 1.0);
DrawPrimitives::drawLine(_hillKeyPoints[i - 1], _hillKeyPoints[i]);
}
}
现在,不是绘制所有点,而是只绘制当前可见的点,这些点是前面计算得到的。另外,也把线的颜色改成红色,这样更易于分辨。接着,在
init
方法里面,最后面添加如下代码:
this->resetHillVertices();
在
setOffsetX
方法里面,最后面添加如下代码:
this->resetHillVertices();
为了更容易看到,打开HelloWorldScene.cpp文件,在onEnter方法,最后面添加如下代码:
this->setScale(0.25);
编译运行,可以看到线段出现时才进行绘制,如下图所示:
#define kHillSegmentWidth 10
然后,打开
Terrain.cpp
文件,在
draw
方法里面,
ccDrawLine
之后,添加如下代码:
DrawPrimitives::setDrawColor4F(1.0f, 1.0f, 1.0f, 1.0f);
Point p0 = _hillKeyPoints[i - 1];
Point p1 = _hillKeyPoints[i];
int hSegment = floorf((p1.x - p0.x) / kHillSegmentWidth);
float dx = (p1.x - p0.x) / hSegment;
float da = M_PI / hSegment;
float ymid = (p0.y + p1.y) / 2;
float ampl = (p0.y - p1.y) / 2;
Point pt0,pt1;
pt0 = p0;
for (int j = 0; j < hSegment + 1; ++j) {
pt1.x = p0.x + j * dx;
pt1.y = ymid + ampl * cosf(da * j);
DrawPrimitives::drawLine(pt0, pt1);
pt0 = pt1;
}
打开
HelloWorldScene.cpp
文件,在
onEnter
方法,设置scale为1.0,如下代码:
this->setScale(1);
编译运行,现在可以看到一条曲线连接着山丘,如下图所示:
#define kMaxHillVertices 4000
#define kMaxBorderVertices 800
添加类变量,代码如下:
int _nHillVertices;
Point _hillVertices[kMaxHillVertices];
Point _hillTexCoords[kMaxHillVertices];
int _nBorderVertices;
Point _borderVertices[kMaxBorderVertices];
打开
Terrain.cpp
文件,在
resetHillVertices
方法里面,最后面添加如下代码:
if (prevFromKeyPointI != _fromKeyPointI || prevToKeyPointI != _toKeyPointI) {
_nHillVertices = 0;
_nBorderVertices = 0;
Point p0,p1,pt0,pt1;
p0 = _hillKeyPoints[_fromKeyPointI];
for (int i = _fromKeyPointI + 1; i < _toKeyPointI + 1; ++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) / 2;
float ampl = (p0.y - p1.y) / 2;
pt0 = p0;
_borderVertices[_nBorderVertices++] = pt0;
for (int j = 1; j < hSegments + 1; ++j) {
pt1.x = p0.x + j * dx;
pt1.y = ymid + ampl * cosf(da * j);
_borderVertices[_nBorderVertices++] = pt1;
_hillVertices[_nHillVertices] = Point(pt0.x, 0);
_hillTexCoords[_nHillVertices++] = Point(pt0.x / 512, 1.0f);
_hillVertices[_nHillVertices] = Point(pt1.x, 0);
_hillTexCoords[_nHillVertices++] = Point(pt1.x / 512, 1.0f);
_hillVertices[_nHillVertices] = Point(pt0.x, pt0.y);
_hillTexCoords[_nHillVertices++] = Point(pt0.x / 512, 0);
_hillVertices[_nHillVertices] = Point(pt1.x, pt1.y);
_hillTexCoords[_nHillVertices++] = Point(pt1.x / 512, 0);
pt0 = pt1;
}
p0 = p1;
}
prevFromKeyPointI = _fromKeyPointI;
prevToKeyPointI = _toKeyPointI;
}
这里的大部分代码,跟上面的使用余弦绘制山丘曲线一样。新的部分,是将山丘每个区段的顶点用来填充数组,每个条纹需要4个顶点和4个纹理坐标。在
draw
方法里面,最上面添加如下代码:
CC_NODE_DRAW_SETUP();
GL::bindTexture2D(_stripes->getTexture()->getName());
GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POSITION | GL::VERTEX_ATTRIB_FLAG_TEX_COORDS);
DrawPrimitives::setDrawColor4F(1.0f, 1.0f, 1.0f, 1.0f);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 2, GL_FLOAT, GL_FALSE, 0, _hillVertices);
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORDS, 2, GL_FLOAT, GL_FALSE, 0, _hillTexCoords);
glDrawArrays(GL_TRIANGLE_STRIP, 0, (GLsizei)_nHillVertices);
这里绑定条纹纹理作为渲染纹理来使用,传入之前计算好的顶点数组和纹理坐标数组,然后以
GL_TRIANGLE_STRIP
来绘制这些数组。此外,注释掉绘制山丘直线和曲线的代码。在
init
方法里面,调用
generateHills
方法之前,添加如下代码:
this->setShaderProgram(ShaderCache::getInstance()->getProgram(GLProgram::SHADER_NAME_POSITION_TEXTURE));
打开
HelloWorldScene.cpp
文件,在
spriteWithColor1
方法里面,注释
// Layer 4: Noise
里,更改混合方式,代码如下:
BlendFunc blendFunc = {GL_DST_COLOR,CC_BLEND_DST};
编译运行,可以看到不错的山丘了,如下图所示:
#define kHillSegmentWidth 5
通过减少每个区段的宽度,强制代码生成更多的区段来填充空间。编译运行,可以看到山丘看起来更好了。当然,代价是处理时间。效果如下图所示:
参考资料:
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. 出自 :http://blog.csdn.net/akof1314/article/details/9293797
3.(译)如何制作一个类似tiny wings的游戏:第一部分http://blog.csdn.net/qqmcy/article/details/16846855
非常感谢以上资料,本例子源代码附加资源下载地址:http://download.csdn.net/detail/akof1314/5733037
iOS代码例子下载:http://vdisk.weibo.com/s/BDn59yfnBVuM3
如文章存在错误之处,欢迎指出,以便改正。