zoukankan      html  css  js  c++  java
  • 怎样制作一个相似Tiny Wings的游戏 Cocos2d-x 2.1.4

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

    过程例如以下:
    1.使用上一篇的project;
    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 1000

    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 = 0;
        float y = winSize.height / 2;
        for (int i = 0; i < kMaxHillKeyPoints; ++i)
        {
            _hillKeyPoints[i] = ccp(x, y);
            x += winSize.width / 2;
            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 (0);

        return bRet;
    }

    void Terrain::draw()
    {
        CCNode::draw();
        for (int i = 1; i < kMaxHillKeyPoints; ++i)
        {
            ccDrawLine(_hillKeyPoints[i - 1], _hillKeyPoints[i]);
        }
    }

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

    1
    2
    3
    4
    5
    void Terrain::setOffsetX(float newOffsetX)
    {
        _offsetX = newOffsetX;
        this->setPosition(ccp(-_offsetX * this->getScale(), 0));
    }

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

    1
    #include "Terrain.h"

    加入�例如以下变量: 

    1
    Terrain *_terrain;

    打开HelloWorldScene.cpp文件,在onEnter方法里,调用genBackground方法之前,添�例如以下代码: 

    1
    2
    _terrain = Terrain::create();
    this->addChild(_terrain, 1);

    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, 512512); 

        CCSize winSize = CCDirector::sharedDirector()->getWinSize();
        _background->setPosition(ccp(winSize.width / 2, winSize.height / 2));
        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, 5125124);
        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 = 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] = ccp(x, y);
            if (i == 0)
            {
                x = 0;
                y = winSize.height / 2;
            } 
            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 *= -1;
        }
    }

    这个算法运行的策略例如以下:

    • 在范围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 = 0;
    _toKeyPointI = 0;

    加入�例如以下方法: 

    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 = -1;
        static int prevToKeyPointI = -1;

        // key points interval for drawing
        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方法,代码例如以下: 

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void Terrain::draw()
    {
        CCNode::draw();
        for (int i = MAX(_fromKeyPointI, 1); i <= _toKeyPointI; ++i)
        {
            ccDrawColor4F(1.0001.0);
            ccDrawLine(_hillKeyPoints[i - 1], _hillKeyPoints[i]);
        }
    }

    如今,不是绘制全部点,而是仅仅绘制当前可见的点,这些点是前面计算得到的。另外,也把线的颜色改成红色,这样更易于分辨。接着,在init方法里面,最后面加入�例如以下代码: 

    1
    this->resetHillVertices();

    setOffsetX方法里面,最后面加入�例如以下代码: 

    1
    this->resetHillVertices();

    为了更easy看到,打开HelloWorldScene.cpp文件,在onEnter方法,最后面加入�例如以下代码:

    1
    this->setScale(0.25);

    编译执行,能够看到线段出现时才进行绘制,例如以下图所看到的:

    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 10

    然后,打开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(1.01.01.01.0);
    CCPoint p0 = _hillKeyPoints[i - 1];
    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) / 2;
    float ampl = (p0.y - p1.y) / 2;

    CCPoint pt0, pt1;
    pt0 = p0;
    for (int j = 0; j < hSegments + 1; ++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(1.0);

    编译执行,如今能够看到一条曲线连接着山丘,例如以下图所看到的:

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

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

    1
    2
    #define kMaxHillVertices 4000
    #define kMaxBorderVertices 800

    加入�类变量,代码例如以下: 

    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 = 0;
        _nBorderVertices = 0;
        CCPoint 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] = ccp(pt0.x, 0);
                _hillTexCoords[_nHillVertices++] = ccp(pt0.x / 5121.0f);
                _hillVertices[_nHillVertices] = ccp(pt1.x, 0);
                _hillTexCoords[_nHillVertices++] = ccp(pt1.x / 5121.0f);

                _hillVertices[_nHillVertices] = ccp(pt0.x, pt0.y);
                _hillTexCoords[_nHillVertices++] = ccp(pt0.x / 5120);
                _hillVertices[_nHillVertices] = ccp(pt1.x, pt1.y);
                _hillTexCoords[_nHillVertices++] = ccp(pt1.x / 5120);

                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(1.0f, 1.0f, 1.0f, 1.0f);
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, _hillVertices);
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, _hillTexCoords);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, (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 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.(译)怎样制作一个类似tiny wings的游戏:第一部分 http://www.cnblogs.com/zilongshanren/archive/2011/07/01/2095489.html
    很感谢以上资料,本样例源码附加资源下载地址http://download.csdn.net/detail/akof1314/5733037
    如文章存在错误之处,欢迎指出,以便改正。

  • 相关阅读:
    R语言:文本(字符串)处理与正则表达式
    RStudio版本管理 整合Git
    GIT 图形化操作指南
    RStudio 断点调试 进入for循环语句调试
    安装python Matplotlib 库
    win7 32位安装 python 及Numpy、scipy、matplotlib函数包
    ubuntu下hive-0.8.1配置
    我对PageRank的理解及R语言实现
    R 数据类型
    Pig Latin JOIN (inner) 与JOIN (outer)的区别
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/3931041.html
Copyright © 2011-2022 走看看