死不了的贪食蛇制作教程(通过重力控制方向)

近来cocos2dx的论坛刮起了一阵制作贪食蛇的潮流,我也打算来凑个热闹。不过主要目的还是因为在写3.0过渡篇的系列博客时讲的都是理论,缺少实践。这次就用贪食蛇的例子较为系统的介绍3.0与2.0的一些不同之处。(当然了,有的人肯定会说我是冲沈大海老师的书来了,这种事坚决不能忍!,我慎重说明:我的收货地址是…)

贪食蛇嘛,大家都懂的,就是那条又长又细、可伸缩自如外加弹性还OK的…蛇啦。

首先介绍下游戏制作流程:
1、游戏中有三个场景,分别是主菜单界面(HelloWorld),帮助界面(GameHelp),游戏界面(GameLayer)。
2、进入游戏场景要处理的事件有:
a、开启重力感应,在onAcceleration()回调函数里判断蛇应该往哪个方向移动;
b、用draw()方法来自定义图层显示内容,如界面中的格子,蛇头,身体,食物等;
c、通过update定时器来实时更新蛇的位置
3、请继续往下看…

代码实现如下:
先看.h头文件:

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__


#include "cocos2d.h"


USING_NS_CC;


//枚举类型DIR_DEF,分别标识贪食蛇的移动方向
typedef enum {
    UP=1,
    DOWN,
    LEFT,
    RIGHT
}DIR_DEF;


//蛇每个节点都有自己的移动方向,因此,在节点类SnakeNode的定义中包含了行、列和方向3个成员
class SnakeNode :public cocos2d::Ref
{
public:
    int row;//行
    int col;//列
    int dir;//方向
};


//游戏欢迎画面,这个大家很熟的
class HelloWorld : public cocos2d::Layer
{
public:
    static cocos2d::Scene* createScene();//获取欢迎画面的Scene
    virtual bool init();  
    virtual void onEnter();
    virtual void onExit();
    
    CREATE_FUNC(HelloWorld);
};


//游戏帮助画面
class GameHelp :public cocos2d::Layer
{
public :
    virtual bool init();
    virtual void onEnter();
    virtual void onExit();
    static cocos2d::Scene * createScene();//获取帮助画面
    CREATE_FUNC(GameHelp);
};


//游戏画面
class GameLayer :public cocos2d::Layer
{
public :
    static cocos2d::Scene * createScene();//获取游戏画面
    virtual bool init();
    virtual void onEnter();
    virtual void onExit();


    virtual void draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated) override;//实现当前Layer的定义


    void onAcceleration(Acceleration* acc, Event* event);//重力事件的回调
    
    void logic01(float t);//update的回调
    
    CREATE_FUNC(GameLayer);


protected:
    SnakeNode *sHead;  //贪食蛇px py
    SnakeNode *sFood;  //食物
    cocos2d::Vector allBody;//蛇的身体,放到容器Vector中
//    cocos2d::Texture2D * chead;
};


#endif // __HELLOWORLD_SCENE_H__
```


头文件的注释描述的还算清楚,所以大家看完后应该会对游戏的流程有了一定的概念,继续往下走。


1、创建主菜单界面的主要代码如下:
//添加项菜单进入游戏游戏、帮助、退出游戏的按钮
auto labelstart = LabelTTF::create("startGame","宋体",24);
auto labelhelp = LabelTTF::create("GameHelp","宋体",24);
auto labelexit = LabelTTF::create("exitGame","宋体",24);

//进入游戏按钮
auto mi01 = MenuItemLabel::create(labelstart,](Ref* sender)
{
    CCLOG("go to game");
    Director::getInstance()->replaceScene(GameLayer::createScene());//跳转到游戏场景
});
mi01->setPosition(Point(100,200)); 


//帮助按钮
auto mi02 = MenuItemLabel::create(labelhelp,](Ref* sender)
{
    CCLOG("go to help");
    Director::getInstance()->replaceScene(GameHelp::createScene());//跳转到帮助场景
});
mi02->setPosition(Point(100,150));


//结束游戏
auto mi03 = MenuItemLabel::create(labelexit,](Ref* sender)
{
    CCLOG("exit the game");
    Director::getInstance()->end();//退出游戏
});
mi03->setPosition(Point(100,50));
auto pMenu = Menu::create(mi01,mi02,mi03, NULL);
pMenu->setPosition(Point::ZERO);
this->addChild(pMenu, 1);
```



2、帮助界面其实就一menu,所以我就不啰嗦介绍了,直接看下游戏界面的代码实现:

1)首先在onEnter()中打开重力感应
void GameLayer::onEnter()
{
    Layer::onEnter();
    CCLOG("GameLayer onEnter");


    Device::setAccelerometerEnabled(true);//打开设备的重力感应
    auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(HelloWorld::onAcceleration, this));//创建一个重力监听事件
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);//将listener放到事件委托中
}
```



2)在init()中初始化蛇头和食物的坐标,并开启定时器实时更新蛇的坐标
bool GameLayer::init()
{
    if ( !Layer::init() )
    {
        return false;
    }


    auto labhelp = LabelTTF::create("this is game","宋体",15);
    labhelp->setPosition(Point(0,340));
    this->addChild(labhelp);


    auto labback = LabelTTF::create("MainMenu","宋体",15);
    auto miback = MenuItemLabel::create(labback,](Ref* sender)
    {
        Director::getInstance()->replaceScene(HelloWorld::createScene());
    });
    miback->setPosition(Point(360,200));


    //chead=::CCTextureCache::sharedTextureCache()->addImage("head.png");


    //初始化蛇头坐标和食物的坐标,用下面这种方法随机出来的坐标每次运行时都是一样一样的......
    sHead = new SnakeNode();
    sHead->row = rand()%10;
    sHead->col = rand()%10;


    //初始化食物的坐标
    sFood = new SnakeNode();
    sFood->row = rand()%10;
    sFood->col = rand()%10;


    //执行定时任务
    this->schedule(schedule_selector(GameLayer::logic01),0.5);
    return true;
}
```


//定时器
void GameLayer::logic01(float t)
{   
    //移动蛇的身体
    for(int i = allBody.size()-1; i>=0; i--)
    { 
        SnakeNode * sn = (SnakeNode *)allBody.at(i);//获取蛇身体上的某个节点
        
        if(i>0) 
        { 
            //如果该节点不是第一个节点,那么该节点的下一个坐标就是其前一个点的坐标(这里不用多解释,玩过蛇的都懂)
            SnakeNode * snpre = (SnakeNode *)allBody.at(i-1);//获取前一个节点,把前一个节点的方向,坐标传给当前节点
            sn->dir = snpre->dir;
            sn->row = snpre->row;
            sn->col = snpre->col;
        }
        else if(i==0)
        {
            //如果i=0则是第一个节点,蛇头的坐标便是该节点的坐标
            sn->dir = sHead->dir;
            sn->row = sHead->row;
            sn->col = sHead->col;
        }
    }


    //移动蛇头,根据dir来判断蛇头的移动方向
    switch(sHead->dir)
    {
    case DIR_DEF::UP:
        sHead->row++;//上移
        if(sHead->row >= 10)
        {
            sHead->row=0;//超过顶部边界后便从底部出来
        }
        break;
    case DIR_DEF::DOWN:
        sHead->row--;
        if(sHead->row < 0)
        {
            sHead->row=9;
        }
        break;
    case DIR_DEF::LEFT:
        sHead->col--;
        if(sHead->col < 0)
        {
            sHead->col=9;
        }
        break;
    case DIR_DEF::RIGHT:
        sHead->col++;
        if(sHead->col >= 10)
        {
            sHead->col=0;
        }
        break;
    };  


    //碰撞检测
    //如果蛇头的横、列位置一样,说明蛇吃到了这个食物
    if(sHead->row == sFood->row && sHead->col == sFood->col)
    { 
        //食物从当前位置消失,随机出现在下一个坐标
        sFood->row = rand()%10;
        sFood->col = rand()%10;


        //添加身体到集合
        SnakeNode * sn = new SnakeNode();//创建一个新的节点(也就是吃掉的那个食物),将其放到蛇的尾巴上
        SnakeNode * lastNode = NULL;
        //获取蛇的最后一个节点,如果allBody的size()为0,则说明蛇是第一次捕食,那么它的最后一个节点也就是蛇头啦。
        if(allBody.size()>0)
            lastNode = (SnakeNode *)allBody.back();
        else
            lastNode = sHead;//最后一个节点是蛇头


        //通过最后一个节点的方向来个新的节点初始化横、列坐标
        switch(lastNode->dir)
        {
        case DIR_DEF::UP:
            sn->row = lastNode->row-1;
            sn->col = lastNode->col;
            break;
        case DIR_DEF::DOWN:
            sn->row = lastNode->row+1;
            sn->col = lastNode->col;
            break;
        case DIR_DEF::LEFT:
            sn->row = lastNode->row;
            sn->col = lastNode->col+1;
            break;
        case DIR_DEF::RIGHT:
            sn->row=lastNode->row;
            sn->col=lastNode->col-1;
            break;
        }
        this->allBody.pushBack(sn);//将新的节点加入到蛇的身体中。
    }
}
```



3)通过draw()绘制游戏界面的格子与食物等
void GameLayer::draw(Renderer *renderer, const kmMat4 &transform, bool transformUpdated)
{
    ///绘制形状
    ::glLineWidth(2);//设定画线的宽度
    for(int i=0;i<11;i++)
    {
        DrawPrimitives::drawLine(Point(0,i*32),Point(320,i*32));//绘制条横线
        DrawPrimitives::drawLine(Point(i*32,0),Point(i*32,320));//绘制条竖线
    }


    //                    RGBA
    //DrawPrimitives::drawColor4B(ccc4(255,0,0,255));//设定画线的颜色


    //绘制蛇头
    DrawPrimitives::drawSolidRect(Point(sHead->col*32+2,sHead->row*32+2),
        Point(sHead->col*32+32,sHead->row*32+32),
        Color4F(Color3B(255,0,0)));


    //绘制食物
    DrawPrimitives::drawSolidRect(Point(sFood->col*32+2,sFood->row*32+2),
        Point(sFood->col*32+32,sFood->row*32+32),
        Color4F(Color3B(0,0,255)));


    //绘制身体
    for(int i=0;icol*32+2,node->row*32+2),
        Point(node->col*32+32,node->row*32+32),
        Color4F(Color3B(0,0,255)));
    }


    /*Rect r(340,0,57,57);
    chead->drawInRect(r);
    Layer::draw();*/
}
```



4)通过重力的回调函数来更新蛇的移动方向
void GameLayer::onAcceleration(Acceleration* acc, Event* event)
{
    //0.5这东西很微妙的说
    if(acc->x<=-0.5)
    {
        sHead->dir=DIR_DEF::LEFT;
        log("LEFT");
    }
    else if(acc->x>=0.5)
    {
        sHead->dir=DIR_DEF::RIGHT;
        log("RIGHT");
    }
    else if(acc->y<=-0.5)
    {
        sHead->dir=DIR_DEF::DOWN;
        log("DOWN");
    }
    else if(acc->y>=0.5)
    {
        sHead->dir = DIR_DEF::UP;
        log("UP");
    }
    else
    {
        ;
    }
}
```



恩,差不都就是这样了。最后附上游戏截图(
如果觉得游戏画面您还满意的话,请给我点32个赞!谢谢!~:)

![](http://img.blog.csdn.net/20140415164426906?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3RhcnQ1MzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
   
![](http://img.blog.csdn.net/20140415164439156?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3RhcnQ1MzA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)




看到这里有的人可能要吐槽了,为什么没有写当蛇撞到尾巴会结束游戏的处理。原因很简单,因为我...懒...。就如开头说的那样,该篇的主要目的是通过一个例子来较为系统的介绍cocos2dx2.0与3.0一些不同的地方。

效果貌似不是很好,还是附上CSDN的链接吧:http://blog.csdn.net/start530/article/details/23707985

重力感应的贪吃蛇也来了。。。:11::11::11:这可怜的贪吃蛇要被玩坏了。。。:12::12::12:

今天没什么事,我是专门来抢沙发的。
LZ不厚道,自己坐了:6:

期待物理引擎版 :14:

呵呵,,呵呵 :12::13:

您的文章已被推荐到CocoaChina首页热门文章精选,感谢您的分享。:896::896:

thanks too

:810:好熟的用户名啊,,代码为什吗没有高亮

代码还可以高亮吗…:12:

— Begin quote from ____

引用第9楼star特530于2014-04-16 12:02发表的 回 8楼(阿花君霸占路人) 的帖子 :
代码还可以高亮吗…:12: http://www.cocoachina.com/bbs/job.php?action=topost&tid=198178&pid=928532

— End quote

看看现在是不是漂亮多啦~~

额。我竟然毫不知情…话说为什么看到你的ID我只看到“霸王硬上弓”几个字呢…:3:

你这不是开玩笑么 star
我打算写重力控制的 你这让我怎么玩

把显示器抬起来转一转就可以啦,一样一样的:12:

试了下贪吃蛇用重力感应玩感觉不方便,不过牛人啊

看了这么多人的贪吃蛇,还是您的最容易懂

:866:无处不在 赞