cocos2dx实现自定义2D地形

cocos2dx实现自定义2D地形,第1张

概述先来看看效果: 对于2D地形的生成,可以采用2种方法, 1.使用建模软件将2D的地形模型构建好直接使用Sprite3D导入   优点:不需要太多程序控制,只是简单的导入 缺点:需要建模,而且还要转换成2D坐标,很难使用chipmunk加入物理特性   2.根据自定义数据使用顶点数据和shader。 下面来讲解如何实现,之后分析优缺点。 推荐一篇博客 如何制作一个类似Tiny Wings的游戏 Co

先来看看效果:



对于2D地形的生成,可以采用2种方法,

1.使用建模软件将2D的地形模型构建好直接使用Sprite3D导入

优点:不需要太多程序控制,只是简单的导入

缺点:需要建模,而且还要转换成2D坐标,很难使用chipmunk加入物理特性

2.根据自定义数据使用顶点数据和shader。

下面来讲解如何实现,之后分析优缺点。

推荐一篇博客

如何制作一个类似TinyWings的游戏Cocos2d-x2.1.4

如果你看懂了上面的博客,那么就跟容易理解笔者讲解的。

首先准备,图片资源


草地(地形表面) 土地(地形底部)

地形数据,其实是由许多点连成线组成的,如图


只要获取这些点就可以得到一个比较平滑的地形,如果点的间隔比较大,得到的地形就没有那么平滑,所以两个点的水平距离要根据需要设置。



由于主要讲解如何根据点生成地形,这里就不讲解如何生成这么多点,有兴趣的同学可以加

QQ:842723654,一起讨论。


有了数据,那么应该如何生成地形呢?

我们都知道大多数3D模型是由许多点组成的(有些模型也包含曲线),这些点就组成了许多三角形,

这些三角形就组成了一个模型,给这个模型赋予贴图,就得到了一个完整的模型。

同样利用这个道理,我们可以自己构造一个2D模型。

对于上面由线段组成的曲线,如果将每一个点复制后向下平移一定的距离,就能得到


许多矩形(实际就多了一倍的顶点,目前还是点),现在就要把这些矩形拆分成三角形,

每个矩形沿对角线切割,就得到了三角形。


让我们想想opengl绘制三角形的方法,

glBegin(GL_TRIANGLES);.....glEnd();这种方法又慢又费资源。

glBegin(GL_TRIANGLE_STRIP);.....glEnd();这种方法比刚才的快了一点

glDrawArrays(GL_TRIANGLE_STRIP,);这种一次绘制的就是我们想要的,

其实cocos2dx也是利用glDrawArrays来减少绘制次数的,尤其是对于粒子,一次性

传入所有粒子的顶点,一次性绘制,岂能不快。

这里也提一下,假如提供了1,2,3,4,5,6,6个顶点:

GL_TRIANGLES,每三个顶点构成一个三角形,(1,3),(4,6),绘制2个三角形

GL_TRIANGLE_STRIP,前面的两个顶点与当前顶点构成一个三角形,

(1,(2,4),(3,5),绘制4个三角形

可见如果要节约资源,采用GL_TRIANGLE_STRIP是个不错的选择。

关于地形顶点数据的存储,笔者采用文件存储,前4个字节,表示顶点的个数,

以后每8个字节表示一个顶点,因为Vec2就占8个字节,正好可以读入。

如下,载入数据:

autodata=fileUtils::getInstance()->getDataFromfile(filename);

CC_BREAK_IF(data.isNull());

intnumPoint=*((int*)data.getBytes()); //读取顶点个数

_points.resize(numPoint);

memcpy(&_points[0],data.getBytes()+4,data.getSize()-4); //读取顶点数据

这里_points是std::vector<Vec2>_points;

vector的数据是连续的,而且Vec2又占8个字节,又不需要特殊的构造。


下一步就是将顶点数据转换成三角形顶点

对于土地部分


bool TerrainBottom::initTriangle(std::vector<Vec2>& points){	// triangles	Vec2 pt(0.f,0.f);	for (std::size_t i=0; i< points.size(); ++i)	{		pt.x = points[i].x;		_triangles.push_back(pt);		// 先下方顶点		_triangles.push_back(points[i]);	// 再上方顶点	}	_bUserTexCoord = false;  // 是否使用自定义纹理坐标	return true;}

只要保证顶部地形的走势就可以,底部要做的就是一直拖到屏幕最下方,

所以根据上面标有数字的图片,按照数字顺序添加顶点就可以了。

而对于表面(草地)部分


实际上就是给土地镶嵌一层花边,如图中的黑色粗线


只要将每个顶点的Y坐标上下平移一小段距离,得到图中的橘色多边形就可以了

bool TerrainSurface::initTriangle(std::vector<Vec2>& points){	// triangles	Vec2 pt;	for (std::size_t i=0; i< points.size(); ++i)	{		pt = points[i];		_triangles.push_back(Vec2(pt.x,pt.y - 16.f));  // 向下平移		_texCoords.push_back(Vec2(pt.x / 256.f,0.f));		_triangles.push_back(Vec2(pt.x,pt.y + 16.f));  // 向上平移		_texCoords.push_back(Vec2(pt.x / 256.f,1.f));	}	_bUserTexCoord = true;		// 是否使用自定义纹理坐标	return true;}

这样土地和表面的三角顶点数据就构造好了

纹理的处理

纹理采用的是GL_REPEAT,对于地形的显示至关重要。

// textureTexture2D::TexParams texParams;texParams.magFilter = GL_liNEAR;texParams.minFilter = GL_liNEAR;texParams.wrapS = GL_REPEAT;texParams.wrapT = GL_REPEAT;tex->setTexParameters(texParams);

那么纹理坐标应该怎么生成呢?

土地可以根据(坐标/图像的尺寸)得到。

表面水平方向/图像的宽带,竖直方向不能根据坐标,因为纹理是重复的,

只要将上顶点设为0.f,下顶点设为1.0f,就可以,纹理坐标Y坐标与opengl坐标系是相反的。

顶点坐标纹理都已准备好,下面就是如何绘制了.

Shader+绘制函数就可以搞定了,

继承Node,重载draw函数

voID Terrainsprite::draw(Renderer *renderer,const Mat4 &transform,uint32_t flags){	_customCmd.init(_globalZOrder,transform,flags);	_customCmd.func = CC_CALLBACK_0(Terrainsprite::onDraw,this,flags);	renderer->addCommand(&_customCmd);}

定义一个CustomCommand,目的调用自己的绘制方法,

voID Terrainsprite::onDraw(const Mat4 &transform,uint32_t flags){	getGLProgramState()->apply(transform);	glDrawArrays(GL_TRIANGLE_STRIP,_triangles.size());	CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,_triangles.size());}

我们并没有使用glVertexPointer,glTexCoordPointer,因为可以利用cocos2dx的shader设置顶点属性

getGLProgramState()->setVertexAttribPointer,


voID setVertexAttribPointer(const std::string &name,// 属性名 如attribute vec4 a_positionGlint size,// 每个元素占几个type GLenum type,// 元素分量类型  如Vec2 占2个float type = GL_floatGLboolean normalized,// 是否为法线? GLsizei strIDe,// 元素间隔 如 sizeof(Vec2)GLvoID *pointer          // 元素指针);

可以这样提供给shader顶点坐标,前提顶点着色器要有attributevec4a_position

getGLProgramState()->setVertexAttribPointer("a_position",GL_float,138); Font-size:9.5pt; Font-family:新宋体">GL_FALSE,sizeof(Vec2),&_triangles[0]);

同理纹理坐标

getGLProgramState()->setVertexAttribPointer("a_texCoord",&_texCoords[0])

可以参照tests的ShaderTests

到此我们已经知道地形绘制的方法,具体怎么实现呢?

组织方式

Terrain类管理地形的表面和土地

TerrainSurface类和TerrainBottom类分别显示地形的表面和土地

两者继承Terrainsprite类,Terrainsprite类显示自定义网格,

考虑一下Surface和Bottom不同之处:纹理,三角形数据,shader

仅仅几个参数不同,所以可以合并到Terrainsprite.

先来分析Terrain,载入地形数据,初始化表面和土地

lass Terrain : public Node{public:	static Terrain* create(const std::string &filename); // 根据文件生成地形	bool init(const std::string &filename);	// 初始化private:	bool loadTerrainData(const std::string &filename); // 载入地形数据private:	std::vector<Vec2> _points;  // 保存顶点数据	TerrainSurface* _surface;	 // 地形表面	TerrainBottom* _bottom;    // 地形土地};

初始化代码:

bool Terrain::init(const std::string &filename){	do 	{		CC_BREAK_IF(!Node::init());	// 基类初始化		CC_BREAK_IF(!loadTerrainData(filename)); // 载入数据		_bottom = TerrainBottom::create(_points); // 土地		CC_BREAK_IF(_bottom == nullptr);		this->addChild(_bottom);		_surface = TerrainSurface::create(_points); // 表面		CC_BREAK_IF(_surface == nullptr);		this->addChild(_surface);		return true;	} while(0);	return false;}

然后Terrainsprite,根据顶点数据构造三角顶点和纹理顶点,初始化shader,并绘制,头文件就不用看了,

由于三角形的处理不同所以提供了一个纯虚函数,让子类实现。

class Terrainsprite : public Node{public:	static Terrainsprite* create(std::vector<Vec2>& points,Texture2D* tex,const std::string& vertfile,const std::string& fragfile);	bool init(std::vector<Vec2>& points,const std::string& fragfile);		voID draw(Renderer *renderer,uint32_t flags);	voID onDraw(const Mat4 &transform,uint32_t flags);protected:	virtual bool initTriangles(std::vector<Vec2>& points) = 0;	bool initShader(Texture2D* tex,const std::string &vertfile,const std::string& fragfile);	protected:	CustomCommand _customCmd;	std::vector<Vec2> _triangles;	std::vector<Vec2> _texCoords;	bool _bUserTexCoord;};

先来看看初始化,顶点数据,设置参数

bool Terrainsprite::init(std::vector<Vec2>& points,const std::string& fragfile){		do 	{		CC_BREAK_IF(!Node::init());		CC_BREAK_IF(!initShader(tex,vertfile,fragfile));		CC_BREAK_IF(!initTriangles(points));				getGLProgramState()->setVertexAttribPointer("a_position",GL_float,GL_FALSE,sizeof(Vec2),&_triangles[0]);	// 顶点坐标		if (_bUserTexCoord)		{			getGLProgramState()->setVertexAttribPointer("a_texCoord",&_texCoords[0]); // 纹理坐标		}		return true;	} while (0);	return false;}

再来看看shader的初始化,改变纹理参数,向shader设置纹理及尺寸

bool Terrainsprite::initShader(Texture2D* tex,const std::string& fragfile){	// texture	Texture2D::TexParams texParams;	texParams.magFilter = GL_liNEAR;	texParams.minFilter = GL_liNEAR;	texParams.wrapS = GL_REPEAT;	texParams.wrapT = GL_REPEAT;	tex->setTexParameters(texParams);	// shader	auto glProgram = GLProgram::createWithfilenames(vertfile,fragfile);	auto glProgramState = GLProgramState::getorCreateWithGLProgram(glProgram);	glProgramState->setUniformTexture("u_terrain",tex);        glProgramState->setUniformVec2("u_texSize",Vec2(tex->getContentSize().wIDth,tex->getContentSize().height));	setGLProgramState(glProgramState);	return true;}

最后分析TerrainSurface和TerrainBottom


class TerrainSurface : public Terrainsprite{public:	static TerrainSurface* create(std::vector<Vec2>& points);protected:	virtual bool initTriangles(std::vector<Vec2>& points);	};

如何创建

TerrainSurface* TerrainSurface::create(std::vector<Vec2>& points){	auto node = new (std::nothrow) TerrainSurface;	if (node != nullptr)	{		auto tex = Director::getInstance()->getTextureCache()->addImage("surface.png");		node->init(points,tex,"glsl/terrain_surface.vert","glsl/terrain_surface.frag");		node->autorelease();		return node;	}	delete node;	return nullptr;}

重点是这句

node->init(points,"glsl/terrain_surface.vert",21); Font-size:10.5pt; Font-family:新宋体">"glsl/terrain_surface.frag");

纹理,shader,其实create还可以提供一个Texture2D*的参数以便得到不同的纹理。

initTriangles,在文章前面已经提到过,

TerrainBottom类似,具体请在文章最后下载源码查看。

Shader的编写

顶点着色器

attribute vec4 a_position;attribute vec2 a_texCoord;#ifdef GL_ESvarying mediump vec2 v_texCoord;#elsevarying vec2 v_texCoord;#endifuniform vec2 u_texSize;	// 纹理尺寸,在bottom中用到voID main(){    gl_position = CC_MVPMatrix * a_position; // 一定记住是CC_MVPMatrixv_texCoord = a_texCoord;  // 因为surface提供了纹理坐标v_texCoord = vec2(a_position.x / u_texSize.x,a_position.y / u_texSize.y); // bottom需要根据顶点计算使用a_position而不使用gl_position,a_position是应用程序传递的坐标,而gl_position是计算后在模型视图的位置,试着替换为gl_position。}

片元着色器

uniform sampler2D u_terrain;#ifdef GL_ESvarying mediump vec2 v_texCoord;#elsevarying vec2 v_texCoord;#endifvoID main(){    gl_Fragcolor = texture2D(u_terrain,v_texCoord);}

使用很简单

_terrain = Terrain::create("map.data");this->addChild(_terrain);

可见自定义数据,可以通过shader轻松更改显示特效,同时还可以添加物理特性.

源码下载

总结

以上是内存溢出为你收集整理的cocos2dx实现自定义2D地形全部内容,希望文章能够帮你解决cocos2dx实现自定义2D地形所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1040343.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-24
下一篇 2022-05-24

发表评论

登录后才能评论

评论列表(0条)

保存