進撃の贪食蛇 完整教程 by:zbycookie

第五节 初始化蛇

在GameLayer.h中添加 void SetSnake()函数
代码如下

void GameLayer::SetSnake(){
    body.clear();
    Size visiblesize = Director::getInstance()->getVisibleSize();
    head = SnakeHead::create();
    head->setNode(Sprite::create("SnakeHead.png"));
    head->setPosition(Point(visiblesize.width/2-head->getNode()->getContentSize().width/2,visiblesize.height/2-head->getNode()->getContentSize().height/2));
    this->addChild(head,1);
    
    for (int i=1 ; i < 4; i++) {
        Snake* bodynode = Snake::create();
        bodynode->setNode(Sprite::create("Snake.png"));
        bodynode->setPosition(Point(head->getPosition().x+i*bodynode->getNode()->getContentSize().width,head->getPosition().y));
        body.pushBack(bodynode);
        this->addChild(bodynode);
    }
    
}

```

我们先初始化蛇头,然后设置蛇头的位置,注意到addchid函数中后面的参数了吧,假设其为z,z指该成员的层级(也可以说深度),z值大的成员在z值小的成员的上面;默认的z是0。除了z,addchild还有一个tag参数,这个参数可以让你可以方便的找到需要的成员,不过我们这里用不到。

好了初始化完蛇头之后,我们开始初始化蛇的身体,一开始,我们定义3个蛇的身体节点,和蛇头一样,先初始化蛇的身体节点,然后设置节点位置,最后将身体节点加入body中,并添加到layer里,这样整条蛇的初始化就结束了,将SetSnake函数添加到init函数中去吧。

本节结束(这节好短);
放一张效果图吧。
 

好帖,浅显易懂,给的链接也有很大的阅读价值。

第六节 实现蛇的运动

我们往GameLayer.h中添加
成员函数:


void MoveStep();
void MoveBody();
virtua void update(float dt);

我们先写MoveBody这个函数。
MoveBody这个函数的作用是移动蛇所有的身体节点。
他的原理就是将每个身体节点的位置设为前一个身体节点的位置,其中如果是第一个身体节点的话,就用蛇头的位置来设置身体节点的位置。
代码如下

void GameLayer::MoveBody(){
    int n=body.size();
    Point HeadPosition = head->getPosition();
    Point a,temp;
    for (int i = 0; i < n; i++) {
        if(i==0){
            a = body.at(i)->getPosition();
            body.at(i)->setPosition(HeadPosition);
        }
        else {
            temp = a;
            a = body.at(i)->getPosition();
            body.at(i)->setPosition(temp);
         }
    }
    
}

```


接着,实现MoveStep函数
代码如下
void GameLayer::MoveStep(){
    movedflag = true;
    Direction temp = head->getDirec();
    Point po = head->Node::getPosition();
    switch (temp) {
        case up:
            //log("upupup");
            po.y += 20;
            break;
        case down:
            po.y -= 20;
            break;
        case left:
            po.x -= 20;
            break;
        case right:
            po.x += 20;
            break;
            
        default:
            break;
    }
    MoveBody();
    head->setPosition(po);
    
}

```


函数中,我们先获得蛇头运动方向,然后根据蛇头运动的方向来设置蛇头接下来的位置。

接着将MoveStep加入到update函数中,并在init函数中添加下面这行代码

```
this->schedule(schedule_selector(GameLayer::update), 0.6);
```


好了,我们现在已经实现蛇的运动啦。

第六节结束

第七节 放置食物及吃掉食物的判定

实现蛇的运动后我们就开始放置食物了,首先在GameLayer.h中添加
成员


Sprite* food;
Point lastbodyposi;

函数


void SetFood();
Point RandomPosition()
bool ifCollideBody(Point pos);
bool ifGetFood();
void AddBody();

首先是SetFood函数
代码如下

void GameLayer::SetFood(){
    this->removeChild(food);
    Point foodposi = RandomPosition();
    Point headposi = head->getPosition();
    while (foodposi==headposi||ifCollideBody(foodposi)) {
        foodposi = RandomPosition();
    }
    food = Sprite::create("Food.png");
    food->setPosition(foodposi);
    this->addChild(food);
}


```

里面有用到RandomPosition和ifCollideBody两个函数,
先说RandomPosition,代码如下
Point GameLayer::RandomPosition(){
    int x = (arc4random()%24);
    int y = (arc4random()%16);
    Point position = Point(x*20+10,y*20+10);
    return position;
}


```

这个函数返回的是一个Point类型的随机点

再是ifCollideBody函数,代码如下
bool GameLayer::ifCollideBody(Point pos){
    bool value = false;
    Snake* node;
    for (int i =0; igetPosition();
        if(nodepos==pos){
            value = true;
        }
    }
    return value;
}


```

这个函数的作用主要是检查给定的位置是否与蛇的身体节点重合,重合返回true,不重合返回false。

回到SetFood这个函数,因为整个游戏画面上同时只能存在一个食物,因此,当每执行SetFood的时候,要把前面设置的食物从layer中移除,所以要执行一下removeChildh函数。
接下来先获取蛇头的位置,然后产生一个随机点,检查该随机点是否与蛇头和蛇身体重合,如果重合,则继续生成随机点,直到生成的随机点与蛇头蛇身不重合为止。然后设置食物的位置为该随机点,设置食物的步骤就结束了。

接下来我们判断食物是否被吃掉,用到的是ifGetFood这个函数
代码如下
bool GameLayer::ifGetFood(){
    bool value = false;
    if(food->getPosition() == head->getPosition()){
        value = true;
    }
    return value;
}


```

非常简单吧
如果食物被吃掉,则调用AddBody函数代码如下
void GameLayer::AddBody(){
    head->setPosition(food->getPosition());
    Snake* node = Snake::create();
    node->setNode(Sprite::create("Snake.png"));
    node->setPosition(lastbodyposi);
    body.pushBack(node);
    this->addChild(node);
}


```

我们要在蛇的末尾加入一个身体节点,就要用到lastbodyposi这个成员了,这个成员保存的是上一步运动过程中,蛇的最后一个身体节点的位置。
这个lastbodyposi是在update函数中更新的,我们先来看一下update函数
void GameLayer::update(float dt){
        MoveStep();
        if(ifGetFood()){
            AddBody();
            SetFood();
        }
        lastbodyposi = body.at(body.size()-1)->getPosition();   
}

```


第七节结束

第八节 游戏结束的判定

贪食蛇里GameOver的原因只有2种,1是蛇头碰到身体,2是蛇头碰到墙壁

我们在GameLayer.h里添加
成员

 bool ifgameover;

函数


bool OutofRange();
bool HeadCollideBody(Direction headdirec);
void GameOver();
void PauseGame();
void SetSnakeVisible(bool value);

先说OutofRange函数,代码如下

bool GameLayer::OutofRange(){
    Point po = head->getPosition();
    if (po.x < 0||po.x > visiblesize.width||po.y < 0||po.y > visiblesize.height) {
        return true;
    }
    return  false;
}


```

这个函数用来判断蛇头是否超出游戏区域。

接下来是HeadCollideBody,代码如下
bool GameLayer::HeadCollideBody(Direction headdirec){
    float x = head->getPosition().x;
    float y = head->getPosition().y;
    bool iscollide = false;
    switch (headdirec) {
        case up:
            y += 20;
            break;
        case down:
            y -= 20;
            break;
        case left:
            x -= 20;
            break;
        case right:
            x += 20;
            break;
        default:
            break;
    }
    Point headnextpos = Point (x,y);
    iscollide = ifCollideBody(headnextpos);
    return  iscollide;
}

```

这个函数是用来判断蛇头下一步移动的位置是否与身体相撞。

再下来是GameOver函数,代码如下
void GameLayer::GameOver(){
    PauseGame();
    SetSnakeVisible(false);
    playbutton->setPosition(Point(visiblesize.width/2,visiblesize.height/2-gameover->getContentSize().height));
    gameover->setVisible(true);
    ifgameover = true;
}


```

里面又用到了另外两个函数,PauseGame和SetSnakeVisible。
PauseGame函数的作用主要是用来停止游戏,代码如下
void GameLayer::PauseGame(){
    
    this->pause();
    SetSnakeVisible(false);
    playbutton->setVisible(true);
    
}


```

SetSnakeVisible就是用来显示或隐藏蛇和食物的,代码如下
void GameLayer::SetSnakeVisible(bool val){
    food->setVisible(val);
    head->getNode()->setVisible(val);
        for (int i =0; isetVisible(val);
        }
}

```

回到GameOver函数,为了美观,我们需要调整一下开始按钮的位置,因为我们还要在开始按钮上面显示“GameOver!”的label,接着将ifgameover设置为true。
最后将update函数改为下面这个样子
void GameLayer::update(float dt){
    if (!HeadCollideBody(head->getDirec()) && !OutofRange()) {
         MoveStep();
        if(ifGetFood()){
            AddBody();
            SetFood();
        }
        lastbodyposi = body.at(body.size()-1)->getPosition();
    }
    else{
        GameOver();
    }
}


```


第八节结束

第九节 游戏的暂停和恢复

我们往GameLayer.h里添加
函数

void StartGame();

StartGame主要是用来恢复游戏的,是playbutton的回调函数,先看代码

void GameLayer::StartGame(){
    if(!ifgameover){
        SetSnakeVisible(true);
        this->resume();
    playbutton->setVisible(false);
    }else{
        this->unscheduleAllSelectors();
        this->removeAllChildren();
        body.clear();
        this->init();
        
    }
}

```


游戏过程中,在两种情况下我们会去点击开始游戏按钮
第一种是人为的暂停,恢复游戏时就要恢复游戏暂停前的状态
第二种是GameOver了,我们需要重新开始游戏。

这里我们用到了上一节的ifgameover来判断具体是哪种情况。
注意,在init函数中我们要将ifgameover设置为false。
bool GameLayer::init(){
    bool bRect = false;
    
    do {
        ifgameover = false;
        SetApperance();
        SetSnake();
        SetFood();
        this->schedule(schedule_selector(GameLayer::update), 0.6);
        bRect = true;
    } while (0);
    
    return bRect;
}



```


第九节 结束

最终章 一个小bug的解决

在玩的过程中发现了一个小bug,如果方向切换的过快,就会造成HeadCollideBody的误判
解决方法如下

在GameLayer.h中添加一个成员

bool movedflag;

然后修改 SetDirection函数

void GameLayer::SetDirection(Ref* psender,Direction direc){
    Direction headdir = head->getDirec();
    if (movedflag && direc!= headdir) {
        switch (direc) {
            case up:
                if(headdir==left||headdir==right){
                    head->setDirec(up);
                }
                break;
            case down:
                if(headdir==left||headdir==right){
                    head->setDirec(down);
                }
                break;
            case left:
                if(headdir==up||headdir==down){
                    head->setDirec(left);
                }
                break;
            case right:
                if(headdir==up||headdir==down){
                    head->setDirec(right);
                }
                break;
            default:
                break;
        }
        movedflag = false;
    }else{
    
    }    
}


```


然后在MoveStep函数的第一行添加一行代码

```
movedflag = true;
```


解决!

 

哇你这个好都告诉写在哪个文件了,对我这种菜鸟来说太好了,不过这是ios的么?适用于android吗

楼主,我只能点32个赞!

太棒了!这个修改一下直接就可以商业化了。:801::801::801:

适用的哦,就是导出app的时候方式不一样

还想了好几个点子呢,有一个就是蛇的节点是有磁铁组成的分 N级和S级,食物产生的时候也有N级和S级之分,具体怎么用这个还没想好
嘛,这只是个开始啦~

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

大赞,学习,学习!!

真详细啊,吓得我都趴地上了

学习了。。

感谢!非常详细!非常适合新手!

楼主能发个项目压缩包给我吗,你这个文件我打不开。476855582@qq.com 多谢给赞

新手学习下

新手用2.21,还是好多错误,完全改不来,难道真是智商捉急。

新手学习.+32.