"进击的贪吃蛇",基于cocos2dx3.0版本全新制作,使用全新特性,并非移植2.X代码

一开始搞错规则了,表示非常伤心~~~~~~~~~~~所以决定好好写下教程,不是为了混福利了哦~

环境是cocos2dx 3.0rc0版本,rc1版本需要修改一点地方,下面会提到改动.

那么开始吧:

首先开始界面有一个标题
让我们来创建一个标题

void GameHome::createTitle()
{
    auto title = Label::create("Greedy Snake","fonts/Marker Felt.ttf",50);
    title->setAnchorPoint(Point::ANCHOR_MIDDLE);
    title->setPosition(480,520);
    title->enableShadow(Color3B::RED);
    this->addChild(title);
}


```


这里用到3.0的一个新的Lable,具体请看论坛置顶帖,上面有仔细讲这个东西,我就不重复了,总之比以前的功能强大,可以描边,阴影,荧光.
我们这里用到了阴影,红色的阴影,然后锚点居中就OK了.
在rc1版本中这里是Color4B,使用rc1的童鞋,注意一下!其他下面用到enableShadow这个函数的地方同理.

接着我们需要一个开始游戏和关闭游戏的按钮
void GameHome::createMenu()
{
    auto menu = Menu::create(
        createMenuTTF("play",Point(0,30),CC_CALLBACK_1(GameHome::play,this)),
        createMenuTTF("close",Point(0,-100),CC_CALLBACK_1(GameHome::close,this)),
        nullptr);
    this->addChild(menu);
}

MenuItemLabel* GameHome::createMenuTTF( const std::string& name,const Point& pos,const ccMenuCallback& callback )
{
    auto closeLabel = Label::create(name,"fonts/Marker Felt.ttf",40);
    closeLabel->enableShadow(Color3B::GREEN);
    auto menuItemLabel = MenuItemLabel::create(closeLabel,callback);
    menuItemLabel->setPosition(pos);
    return menuItemLabel;
}

```

OK,这里我们也用到上面说的Lable,并且使用了全新的MenuItemLabel,同样我们使用了描边,让字体更加好看.

接下来我们跳转到游戏场景中
void Game::onEnter()
{
 Scene::onEnter();


 this->schedule(schedule_selector(Game::snakeMove),0.2f);


 _istouchSnakeDirection = false;
 _snakeDirection = RIGHT;
 initSnake();


 auto listener = EventListenerTouchOneByOne::create();
 listener->onTouchBegan = =](Touch* touch,Event*){ return true; };
 listener->onTouchEnded = =](Touch* touch,Event*){ touchSnakeDirection(touch->getLocation()); };
 this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);


 createApple();
}

```

使用onEnter初始化,这里说一下为什么没有使用init,个人认为init适合一些基础数据的初始化,不适合创建精灵和各种节点,因为在场景跳转中,需要先执行新场景init,然后删除旧场景,会瞬间导致存在两个场景,内存暴涨的情况,而onEnter是在删除前一个场景之后调用,缓和了那一帧的计算量和内存,有一个缓冲的效果.
回到游戏中..

this->schedule(schedule_selector(Game::snakeMove),0.2f); 添加定时器0.2秒,这个也是蛇的移动速度,每0.2秒移动一次.
_snakeDirection = RIGHT; 初始移动向右
 initSnake();初始化蛇
那一坨是添加touch事件
createApple();创建一个苹果...为什么取名字叫苹果呢..因为苹果被咬了~~囧

_snakeDirection一个东西是个枚举,结构是这样的,至于为什么是这样,后面会讲到...
    enum SnakeDirection
    {
        UP_OR_DOWN = 1,
        UP = 1,
        DOWN = -1,

        LEFT_OR_RIGHT = 2,
        LEFT = 2,
        RIGHT = -2
    };

```

也就是4个方向.
void Game::initSnake()
{
    _snake.clear();
    for (int i = 0; i < 3;++i)
    {
        auto drawNode = createDrawNode(Point(0-kSnakeNodeSize.width*i,0)+Point(kSnakeNodeSize*12.5f));
        _snake.push_back(drawNode);
    }
}

```

初始化蛇,这里面有一个createDrawNode,让我们来看看这个函数
DrawNode* Game::createDrawNode( const Point& pos )
{
    auto drawNode = DrawNode::create();
    Size size(kSnakeNodeSize);
    Point point = Point(-size.width/2,size.height/2);
    Point points = {
        point,
        Point(point.x,point.y-size.height),
        Point(point.x + size.width,point.y-size.height),
        Point(point.x+size.width,point.y)};
    drawNode->drawPolygon(points,4,Color4F::WHITE,1,Color4F::RED);
    drawNode->setPosition(pos);
    this->addChild(drawNode);
    return drawNode;
}

```

这里面使用DrawNode画了一个四边形,并且有描边,里面是白色,描边是红色.
这里需要注意的一个地方是,画多边形的第一个点是左上角,然后向下逆时针旋转的顶点排序,这样的话会使得我们的锚点看起来像是左上角的样子,不符合我们通常的思维,
这里Point point = Point(-size.width/2,size.height/2);等于让我们看起来锚点是在中心了,当然其实锚点一直是中心,只不过画是从左上角开始画,所以我们需要一点调整.
kSnakeNodeSize这是一个常量,就是蛇一节一节的大小.

下面我们说一下创建一个苹果...
void Game::createApple()
{
    Point pos;
    bool b = false;
    do 
    {
        Size size = Director::getInstance()->getVisibleSize();
        pos.x = ( (int)(CCRANDOM_0_1() * (size.width/kSnakeNodeSize.width-1)) + 0.5f ) * kSnakeNodeSize.width;
        pos.y = ( (int)(CCRANDOM_0_1() * (size.height/kSnakeNodeSize.height-1)) + 0.5f ) * kSnakeNodeSize.height;
        for (auto m : _snake)
        {
            if (m->getPosition().equals(pos))
            {
                b = true;
                break;
            }
            else
            {
                b = false;
            }
        }
    } while (b);
    _apple = createDrawNode(pos);
}

```

这里用到了一个随机数,我没有使用种子,会导致每次开始游戏都在那个位置..懒~~~

这里需要满足两个条件随机的坐标正确,还有一个是不能随机在蛇的身上.
当然没有判断蛇吃满整个屏幕的情况,会导致永远随机不到合适的位置...
或者在蛇占屏幕的最后几个格子的时候,可能会导致要很久才能随机到合适的位置
其实这里应该改为,把所有的合理点放在一个容器里,在这个容器里去随机一个点,这样不仅效率高,而且正确..当然我懒...囧..


然后到我们的交互了,我们的touch做了什么呢?
void Game::touchSnakeDirection( const Point& tochPoint )
{
    _istouchSnakeDirection = false;
    if (abs(_snakeDirection) == UP_OR_DOWN)
    {
        if (tochPoint.x < _snake->getPosition().x) 
        {
            _touchSnakeDirection = LEFT;
        }
        else
        {
            _touchSnakeDirection = RIGHT;
        }
        _istouchSnakeDirection = true;
    }
    else if (abs(_snakeDirection) == LEFT_OR_RIGHT)
    {
        if (tochPoint.y < _snake->getPosition().y) 
        {
            _touchSnakeDirection = DOWN;
        }
        else
        {
            _touchSnakeDirection = UP;
        }
        _istouchSnakeDirection = true;
    }
}
```

这里就用到了这个枚举的设计,如果这个时候蛇的方向是上下,那么点击蛇头的左边就是向左,点击蛇头的右边就是向右,当现在是方向是左右的时候同理.
枚举用了一个小技巧取绝对值,来判断当前的上下和左右.
然后根据现在蛇的方向与touch指出的方向
这里有个需要注意的问题,,这里我们明明已经判断出蛇的方向了,为什么还需要一个bool值来记录方向是否有改变,因为我们必须要在主循环中去改变当前的方向,因为如果我们在一次循环间隙中点击多次(这个游戏的主循环是0.2s很长,这种问题会非常明显,如果是1/60s,就比较难了,你需要在1/60秒之内点击2次以上,不要牺牲大量的效能来偷懒,在这里你不需要用到如此快的定时器,请记住,大多数情况你都不需要使用1/60s的定时器),就会导致你实际上改变了判断条件,条件是当前的方向,因为当前的方向已经被你上一次的touch改变了,但实际上你2次或者多次的改变都在这一次主循环中,要记住主循环是控制游戏正在变化,其他只是向主循环发出的更改请求,一次循环的其中一个属性只能改变一次,就比如你连续设置两次位置,你在视图是看不到两次位置的改变的.
这里我可能描述不是很清楚,但我表示已经江郎才尽了~~~

接下来就是我们蛇的移动了,也就是游戏的主循环

void Game::snakeMove( float )
{
    if (_istouchSnakeDirection)
    {
        _snakeDirection = _touchSnakeDirection;
    }

    for (size_t i = _snake.size()-1; i > 0 ;--i)
    {
        auto pos = _snake*->getPosition();
        _snake->setPosition(pos);
    }
    snakeHeadMove();

    if (snakeIsDie())
    {
        snakeDie();
    }
    if (snakeIsAapple())
    {
        createSnakeNode();
    }
}
```

判断布尔值,方向是否有发生改变,如果发生改变,则改变方向状态变量.

蛇的移动是从最后一个蛇节点向前面一个蛇节点移动,就是后面一个节点设置成前面一个节点的位置,从而达到移动的效果.
snakeHeadMove();蛇头的移动
void Game::snakeHeadMove()
{
    switch (_snakeDirection)
    {
    case Game::UP:
        _snake->setPositionY(_snake->getPositionY()+kSnakeNodeSize.height);
        break;
    case Game::DOWN:
        _snake->setPositionY(_snake->getPositionY()-kSnakeNodeSize.height);
        break;
    case Game::LEFT:
        _snake->setPositionX(_snake->getPositionX()-kSnakeNodeSize.width);
        break;
    case Game::RIGHT:
        _snake->setPositionX(_snake->getPositionX()+kSnakeNodeSize.width);
        break;
    default:
        break;
    }
}

```

根据蛇方向移动蛇头.

snakeIsDie()判断蛇是否死亡
bool Game::snakeIsDie()
{
    auto pos = _snake->getPosition();
    for (size_t i = 1; i < _snake.size(); ++i)
    {
        if (_snake->getPosition().equals(pos))
        {
            return true;
        }
    }
    auto size = Director::getInstance()->getVisibleSize();
    Rect rect(kSnakeNodeSize.width/2,kSnakeNodeSize.height/2,
        size.width - kSnakeNodeSize.width,size.height - kSnakeNodeSize.height);
    if (!rect.containsPoint(pos))
    {
        return true;
    }
    return false;
}

```

蛇死亡有两种情况,一种是自己咬死自己了,一种是撞墙了.
这里大概就不细说了,说下注意的地方_snake->getPosition().equals(pos) 点之前的判断使用equals而非==,因为浮点精度问题,往往不相等,通常情况不要对浮点数使用==,通常是不相等的,虽然你看起来他们很相等~~~~

snakeDie();蛇死亡
void Game::snakeDie()
{
    this->unscheduleAllSelectors();

    auto title = Label::create("Game Over","fonts/Marker Felt.ttf",50);
    title->setAnchorPoint(Point::ANCHOR_MIDDLE);
    title->setPosition(480,520);
    title->enableShadow(Color3B::RED);
    this->addChild(title);

    auto closeLabel = Label::create("Restart","fonts/Marker Felt.ttf",40);
    closeLabel->enableShadow(Color3B::GREEN);
    auto menuItemLabel = MenuItemLabel::create(closeLabel,](Ref*){
        Director::getInstance()->replaceScene(Game::create()); });
    auto menu = Menu::create(menuItemLabel,nullptr);
    this->addChild(menu);
}

```

蛇死亡关闭主循环,跳出死亡界面.这里就没什么讲的了..

snakeIsAapple(); 蛇是否吃到苹果了
bool Game::snakeIsAapple()
{
    auto posApple = _apple->getPosition();
    auto posSnake = _snake->getPosition();
    if (posApple.equals(posSnake))
    {
        _apple->removeFromParent();
        createApple();
        return true;
    }
    return false;
}

```

蛇吃到一个苹果,就删除这个苹果,然后创建一个新的苹果.OK

createSnakeNode(); 创建一个蛇节点,
void Game::createSnakeNode()
{
    auto drawNode = createDrawNode(Point::ZERO);
    _snake.push_back(drawNode);
}

```

其实非常简单,就是push一个NODE进去,位置都不用设置,由于主循环里面是会从最后一个点向前移动,所以就妥妥展开了...


好的,终于写完这个教程了,
原来已经写了这么..这么..这么长了.


可以向版主要福利了吗~~~呜呜~~~~(>_<)~~~~ 




*

占个沙发先:2:

不要放源码啊 混蛋 ~源码发给偶尔e往事 由他统一发

哦。。。我搞错规则了啊~~~~~~~~~擦

那我得把教程写一写了。。。,不然这帖子啥也没有

…全是体力活

哇~我写了好长的教程啊,会不会有人这么认真的看完啊~~~不知道后面为什么变成斜体了…好奇怪的现象啊

代码高亮好看多了…虽然用起来很麻烦…

楼主好强大,又弄到半夜2点,害的我一大早跑到公司看教程:8:

看着搂着的代码真亲切:6:

写到凌晨2点。。。:12::12::12:太感谢您的支持了,不过也要注意休息哦亲。

因为今天是周末啊,O(∩_∩)O哈哈哈~:14:

好东西,不错不错:8:

教程出来了,支持~

多谢支持!!

多多支持哈!!

多谢支持啊!!

谢谢夸奖哈!!!

:2: :2: :2: :2: :2: :2: :2:

小狮子前来支持