cocos2dx实现自定义2D地形

前端之家收集整理的这篇文章主要介绍了cocos2dx实现自定义2D地形前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

先来看看效果


对于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个字节,又不需要特殊的构造。

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

对于土地部分


  1. boolTerrainBottom::initTriangle(std::vector<Vec2>&points)
  2. {
  3. //triangles
  4. Vec2pt(0.f,0.f);
  5. for(std::size_ti=0;i<points.size();++i)
  6. pt.x=points[i].x;
  7. _triangles.push_back(pt);//先下方顶点
  8. _triangles.push_back(points[i]);//再上方顶点
  9. }
  10. _bUserTexCoord=false;//是否使用自定义纹理坐标
  11. returntrue;
  12. }

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

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

而对于表面(草地)部分


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


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

    boolTerrainSurface::initTriangle(std::vector<Vec2>&points)
  1. Vec2pt;
  2. pt=points[i];
  3. _triangles.push_back(Vec2(pt.x,pt.y-16.f));//向下平移
  4. _texCoords.push_back(Vec2(pt.x/256.f,0.f));
  5. //向上平移
  6. true; true;
  7. }

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

纹理的处理

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

    //texture
  1. Texture2D::TexParamstexParams;
  2. texParams.magFilter=GL_LINEAR;
  3. texParams.minFilter=GL_LINEAR;
  4. texParams.wrapS=GL_REPEAT;
  5. texParams.wrapT=GL_REPEAT;
  6. tex->setTexParameters(texParams);

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

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

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

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

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

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

继承Node,重载draw函数

    voidTerrainSprite::draw(Renderer*renderer,constMat4&transform,uint32_tflags)
  1. _customCmd.init(_globalZOrder,transform,flags);
  2. _customCmd.func=CC_CALLBACK_0(TerrainSprite::onDraw,this,flags);
  3. renderer->addCommand(&_customCmd);
  4. }

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

    voidTerrainSprite::onDraw(@H_674_502@ getGLProgramState()->apply(transform);
  1. glDrawArrays(GL_TRIANGLE_STRIP,_triangles.size());
  2. CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,_triangles.size());
  3. }

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

getGLProgramState()->setVertexAttribPointer,


    voidsetVertexAttribPointer(
  1. conststd::string&name,//属性名如attributevec4a_position
  2. GLintsize,//每个元素占几个type
  3. GLenumtype,0); background-color:inherit">//元素分量类型如Vec2占2个floattype=GL_FLOAT
  4. GLbooleannormalized,0); background-color:inherit">//是否为法线?
  5. GLsizeistride,0); background-color:inherit">//元素间隔如sizeof(Vec2)
  6. GLvoid*pointer//元素指针
  7. );

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

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

同理纹理坐标

"a_texCoord",&_texCoords[0])

可以参照tests的ShaderTests

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

组织方式 Terrain类管理地形的表面和土地

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

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

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

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

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

    lassTerrain:publicNode
  1. public:
  2. staticTerrain*create(conststd::string&fileName);//根据文件生成地形
  3. boolinit(//初始化
  4. private:
  5. boolloadTerrainData(//载入地形数据
  6. private:
  7. std::vector<Vec2>_points;//保存顶点数据
  8. TerrainSurface*_surface;//地形表面
  9. TerrainBottom*_bottom;//地形土地
  10. };

初始化代码

    boolTerrain::init(conststd::string&fileName)
  1. do
  2. CC_BREAK_IF(!Node::init());//基类初始化
  3. CC_BREAK_IF(!loadTerrainData(fileName));//载入数据
  4. _bottom=TerrainBottom::create(_points);//土地
  5. CC_BREAK_IF(_bottom==nullptr);
  6. this->addChild(_bottom);
  7. _surface=TerrainSurface::create(_points);//表面
  8. CC_BREAK_IF(_surface==nullptr);
  9. this->addChild(_surface);
  10. }while(0);
  11. false;
  12. 然后TerrainSprite,根据顶点数据构造三角顶点和纹理顶点,初始化shader,并绘制,头文件就不用看了,

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

      classTerrainSprite:staticTerrainSprite*create(std::vector<Vec2>&points,Texture2D*tex,153); font-weight:bold; background-color:inherit">conststd::string&vertFile,153); font-weight:bold; background-color:inherit">conststd::string&fragFile);
    1. boolinit(std::vector<Vec2>&points,153); font-weight:bold; background-color:inherit">conststd::string&fragFile);
    2. voiddraw(Renderer*renderer,uint32_tflags);
    3. voidonDraw(protected:
    4. virtualboolinitTriangles(std::vector<Vec2>&points)=0;
    5. boolinitShader(Texture2D*tex,153); font-weight:bold; background-color:inherit">protected:
    6. CustomCommand_customCmd;
    7. std::vector<Vec2>_triangles;
    8. std::vector<Vec2>_texCoords;
    9. bool_bUserTexCoord;
    10. 先来看看初始化,顶点数据,设置参数

        boolTerrainSprite::init(std::vector<Vec2>&points,153); font-weight:bold; background-color:inherit">conststd::string&fragFile)
      1. CC_BREAK_IF(!Node::init());
      2. CC_BREAK_IF(!initShader(tex,vertFile,fragFile));
      3. CC_BREAK_IF(!initTriangles(points));
      4. getGLProgramState()->setVertexAttribPointer("a_position",GL_FLOAT,GL_FALSE,153); font-weight:bold; background-color:inherit">sizeof(Vec2),&_triangles[0]);//顶点坐标
      5. if(_bUserTexCoord)
      6. {
      7. getGLProgramState()->setVertexAttribPointer("a_texCoord",&_texCoords[0]);//纹理坐标
      8. }
      9. 再来看看shader的初始化,改变纹理参数,向shader设置纹理及尺寸

          boolTerrainSprite::initShader(Texture2D*tex,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px; margin:0px!important; padding:0px 3px 0px 10px!important"> tex->setTexParameters(texParams);
        1. //shader
        2. autoglProgram=GLProgram::createWithFilenames(vertFile,fragFile);
        3. autoglProgramState=GLProgramState::getOrCreateWithGLProgram(glProgram);
        4. glProgramState->setUniformTexture("u_terrain",tex);
        5. glProgramState->setUniformVec2("u_texSize",Vec2(tex->getContentSize().width,tex->getContentSize().height));
        6. setGLProgramState(glProgramState);
        7. 最后分析TerrainSurface和TerrainBottom


            classTerrainSurface:publicTerrainSprite
          1. staticTerrainSurface*create(std::vector<Vec2>&points);
          2. boolinitTriangles(std::vector<Vec2>&points);
          3. };

          如何创建

            TerrainSurface*TerrainSurface::create(std::vector<Vec2>&points)
          1. autonode=new(std::nothrow)TerrainSurface;
          2. if(node!=nullptr)
          3. autotex=Director::getInstance()->getTextureCache()->addImage("surface.png");
          4. node->init(points,tex,"glsl/terrain_surface.vert","glsl/terrain_surface.frag");
          5. node->autorelease();
          6. returnnode;
          7. deletenode;
          8. returnnullptr;
          9. }

          重点是这句

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

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

          initTriangles,在文章前面已经提到过,51); font-family:Arial; font-size:14px; line-height:26px; margin-bottom:0pt; margin-top:0pt"> TerrainBottom类似,具体请在文章最后下载源码查看。

          Shader的编写

          顶点着色器

            attributevec4a_position;
          1. attributevec2a_texCoord;
          2. #ifdefGL_ES
          3. varyingmediumpvec2v_texCoord;
          4. #else
          5. varyingvec2v_texCoord;
          6. #endif
          7. uniformvec2u_texSize;//纹理尺寸,在bottom中用到
          8. voidmain()
          9. gl_Position=CC_MVPMatrix*a_position;//一定记住是CC_MVPMatrix
          10. v_texCoord=a_texCoord;//因为surface提供了纹理坐标
          11. 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。
          12. }

          片元着色器

            uniformsampler2Du_terrain;
          1. #ifdefGL_ES
          2. varyingmediumpvec2v_texCoord;
          3. #else
          4. varyingvec2v_texCoord;
          5. #endif
          6. gl_FragColor=texture2D(u_terrain,v_texCoord);
          7. 使用很简单

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

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


            源代码

            原文链接:https://www.f2er.com/cocos2dx/343828.html

            猜你在找的Cocos2d-x相关文章