在开发准备前纠结自己算法不行会写出特别糟糕的代码,不过既然是技术交流还是决定按自己的想法写代码管那么多干嘛。代码撸得比较急,每天一小时左右搞了3天…
临时想的逻辑,可能存在bug,请不要吝啬指出。。。
环境:
cocos2d-x-3.0rc1
VS2012
首先看下自定义的生成蛇头蛇身体的精灵类:
class SnakeSprite : public cocos2d::Sprite
{
public:
virtual bool init();
CREATE_FUNC(SnakeSprite);
// 头部的图片不一样 所以有vip特权加个特别的函数
static SnakeSprite* create(const char*);
// 设置后面一个节点的坐标
void setNextPoint(cocos2d::Point); //
cocos2d::Point getNextPoint(); // 供后面的节点获取位置
protected:
cocos2d::Point nextPoint;
};
```
这个就很简单的了,就是继承Sprite实现蛇身体的节点了,为什么继承Sprite?重点在于nextPoint这个属性上了,就是靠它维护蛇移动的。
接下来就是游戏场景,游戏场景设计是960,640的,蛇移动区域是760 , 560,蛇的宽高是40.之前在设计的时候就考虑界面上四个按钮控制方向,后面发现不怎么好操作就加了个划屏控制方向。发现也不怎么好控制,好吧 纠结了。
代码:
enum DIRECTION // 四个方向tag和重新开始 菜单tag
{
RIGHT = 0 ,
LEFT ,
DOWN ,
UP ,
RESTART ,
MENU
};
#define UP_TIME .25f // 蛇身体移动的时间间隔
class GameScene : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(GameScene);
protected:
virtual void onEnter();
virtual void onExit();
virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event); // 监听屏幕触摸事件
virtual void onTouchEnded(cocos2d::Touch *pTouch, cocos2d::Event *pEvent);
private:
cocos2d::Layer* layerGameMap; // 蛇的活动范围
cocos2d::Sprite* spriteFood; // 食物
cocos2d::Sprite* spriteHeader; // 蛇的头部
cocos2d::Sprite* spriteFooter; // 蛇的默认节点 当作尾巴把
cocos2d::MenuItem* menuItemRestart; // 重新开始按钮
cocos2d::MenuItem* menuItemMenu; // 菜单按钮
cocos2d::Label* labelScore; // 分数label
cocos2d::Label* labelTime; // 时间label
int gameScore; // 当前分数
long gameTime; // 此局开始了多久
// 因为更新时间是固定的 怕当前更新蛇坐标还没完成又开始更新可能导致问题 所以加个标识符
// (看代码更容易理解)
bool isMoveEnd;
// 各种按钮CB函数
void menuCloseCallback(cocos2d::Ref* pSender);
// 默认头部和脚步 所以初始化会为2 用于记录蛇有多少节点
int bones;
// 蛇前进的方向
DIRECTION currDirection;
void changeDirection(DIRECTION);
// 开始结束
void start();
void end();
// 一定时间的回调,用于移动蛇节点和检查游戏状态
void gameTask(float);
void timeTask(float);
// 移动
void move();
//检查是否自己咬自己 或者捡到食物
void check();
// 就是改变食物坐标 没必要重复创建就复用了
void createFood();
// 添加蛇身体节点
void addBody();
};
```
#include "GameScene.h"
#include "SnakeSprite.h"
#include "SimpleAudioEngine.h"
#include "MenuScene.h"
USING_NS_CC;
using namespace CocosDenshion;
Scene* GameScene::createScene() {
auto scene = Scene::create();
auto layer = GameScene::create();
scene->addChild(layer);
return scene;
}
bool GameScene::init() {
if(!Layer::init()){
return false;
}
// 随机数因子
srand((unsigned)time(NULL));
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("image.plist");
auto visibleSize = Director::getInstance()->getVisibleSize();
auto layerMain = Layer::create();
layerMain->setContentSize(Size(960 , 640));
layerMain->ignoreAnchorPointForPosition(false);
layerMain->setPosition( visibleSize.width / 2 , visibleSize.height / 2 );
this->addChild(layerMain);
// 游戏背景图(算什么背景图 不都是黑色么。)
auto spriteBackground = Sprite::createWithSpriteFrameName("background.png");
spriteBackground->setPosition( 480 , 320 );
layerMain->addChild(spriteBackground);
// 游戏地图
layerGameMap = Layer::create();
layerGameMap->setContentSize(Size( 760 , 560 ));
layerGameMap->setAnchorPoint(Point(0 ,0));
layerGameMap->setPosition( 100 , 19.5f );
spriteBackground->addChild(layerGameMap);
// 添加食物
spriteFood = Sprite::createWithSpriteFrameName("food.png");
spriteFood->setAnchorPoint(Point(0,0));
layerGameMap->addChild(spriteFood);
// 创建默认蛇头部
spriteHeader = SnakeSprite::create("snake-head-up.png");
spriteHeader->setTag(0);
spriteHeader->setPosition( 40 , 0 );
layerGameMap->addChild(spriteHeader , 2);
// 给蛇添加个尾巴
spriteFooter = SnakeSprite::create();
spriteFooter->setPosition( 0 , 0 );
spriteFooter->setTag(1);
layerGameMap->addChild(spriteFooter);
// 时间分数
labelScore = Label::create("Score: 0" , "" , 40);
labelScore->setAnchorPoint(Point( 0 , .5f ));
labelScore->setPosition(Point( 100 , 610 ));
layerMain->addChild(labelScore);
labelTime = Label::create("Time: 00:00:00" , "" , 35);
labelTime->setAnchorPoint(Point( 0 , .5f ));
labelTime->setPosition(Point( 620 , 610 ));
layerMain->addChild(labelTime);
// 各种菜单item初始化 代码恶心 请勿细看
auto menuItemUp = MenuItemSprite::create(Sprite::createWithSpriteFrameName("up-normal.png") ,
Sprite::createWithSpriteFrameName("up-press.png") , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemUp->setTag(UP);
menuItemUp->setPosition( 50 , 80 );
auto menuItemDown = MenuItemSprite::create(Sprite::createWithSpriteFrameName("down-normal.png") ,
Sprite::createWithSpriteFrameName("down-press.png") , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemDown->setTag(DOWN);
menuItemDown->setPosition( visibleSize.width - 50 , 80 );
auto menuItemRight = MenuItemSprite::create(Sprite::createWithSpriteFrameName("right-normal.png") ,
Sprite::createWithSpriteFrameName("right-press.png") , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemRight->setTag(RIGHT);
menuItemRight->setPosition( visibleSize.width - 50 , 300 );
auto menuItemLeft = MenuItemSprite::create(Sprite::createWithSpriteFrameName("left-normal.png") ,
Sprite::createWithSpriteFrameName("left-press.png") , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemLeft->setTag(LEFT);
menuItemLeft->setPosition( 50 , 300 );
menuItemRestart = MenuItemFont::create("Restart" , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemRestart->setTag(RESTART);
menuItemRestart->setVisible(false);
menuItemRestart->setPosition( visibleSize.width / 2 , visibleSize.height / 2 );
menuItemMenu = MenuItemFont::create("ReturnMenu" , CC_CALLBACK_1(GameScene::menuCloseCallback,this));
menuItemMenu->setTag(MENU);
menuItemMenu->setVisible(false);
menuItemMenu->setPosition( visibleSize.width / 2 , visibleSize.height / 2 - 50 );
auto menuMain = Menu::create(menuItemUp , menuItemDown , menuItemRight , menuItemLeft ,menuItemRestart ,menuItemMenu , NULL);
menuMain->setPosition(Point::ZERO);
this->addChild(menuMain);
start();
return true;
}
void GameScene::menuCloseCallback(cocos2d::Ref* pSender) {
MenuItem* item = (MenuItem*)pSender;
DIRECTION direction = (DIRECTION)item->getTag();
switch (direction) {
case UP:
case DOWN:
case RIGHT:
case LEFT:
changeDirection(direction);
break;
case RESTART:
start();
break;
case MENU:
{
auto scene = MenuScene::createScene();
Director::getInstance()->replaceScene(scene);
}
break;
default:
break;
}
}
void GameScene::start() {
menuItemRestart->setVisible(false);
menuItemMenu->setVisible(false);
for (int i = 2 ; i < bones ; i++) {
layerGameMap->removeChildByTag(i);
}
gameScore = 0; // 分数
bones = 2; // 头 尾
gameTime = 0; // 时间
labelTime->setString("Time: 00:00:00");
labelScore->setString("Score: 0");
isMoveEnd = false;
currDirection = RIGHT;
this->schedule(schedule_selector(GameScene::gameTask), UP_TIME);
this->schedule(schedule_selector(GameScene::timeTask), 1.0f);
createFood();
spriteHeader->setSpriteFrame("snake-head-right.png");
spriteHeader->setPosition( 40 , 0 );
spriteFooter->setPosition( 0 , 0 );
}
void GameScene::gameTask(float t) {
if(isMoveEnd){ return; };
check();
move();
}
void GameScene::end() {
this->unschedule(schedule_selector(GameScene::gameTask));
this->unschedule(schedule_selector(GameScene::timeTask));
menuItemRestart->setVisible(true);
menuItemMenu->setVisible(true);
auto userData = UserDefault::getInstance();
int lastScore = userData->getIntegerForKey("score" , 0);
if(lastScore < gameScore){
userData->setIntegerForKey("score" , gameScore);
userData->flush();
}
}
void GameScene::move() {
isMoveEnd = true;
for (int i = 0 ; i < bones ; i++) {
auto snake = (SnakeSprite*)layerGameMap->getChildByTag(i);
auto snakePoint = snake->getPosition();
SnakeSprite* preSnake = NULL;
Point preSnakePoint;
if(i == 0) {
Point snakeCurrPoint;
switch (currDirection) {
case RIGHT:
snakeCurrPoint = snakePoint + CCPoint(40 , 0);
break;
case LEFT:
snakeCurrPoint = snakePoint + CCPoint(-40 , 0);
break;
case DOWN:
snakeCurrPoint = snakePoint + CCPoint(0 , -40);
break;
case UP:
snakeCurrPoint = snakePoint + CCPoint(0 , 40);
break;
default:
break;
}
if(snakeCurrPoint.x >= 760 || snakeCurrPoint.x < 0 || snakeCurrPoint.y >= 560 || snakeCurrPoint.y < 0) {
end();
return;
}
snake->setPosition(snakeCurrPoint);
snake->setNextPoint(snakePoint);
} else {
preSnake = (SnakeSprite*)layerGameMap->getChildByTag(i - 1);
preSnakePoint = preSnake->getNextPoint();
snake->setPosition(preSnakePoint);
snake->setNextPoint(snakePoint);
}
}
isMoveEnd = false;
}
void GameScene::check() {
// 如果头部跟实物坐标一致则代表吃了一个食物
if(spriteFood->getPosition() == spriteHeader->getPosition()) {
SimpleAudioEngine::getInstance()->playEffect("eat.wav");
addBody();
createFood();
gameScore += 5;
labelScore->setString(CCString::createWithFormat("Score: %d" , gameScore)->getCString());
}
// 检查当前头部是否与身体节点重叠 重叠为自己咬自己 游戏结束
for (int i = 1 ; i < bones ; i++) {
auto spriteSnake = (SnakeSprite*)layerGameMap->getChildByTag(i);
if(spriteHeader->getPosition() == spriteSnake->getPosition()) {
end();
}
}
}
void GameScene::addBody() {
auto spriteBody = SnakeSprite::create();
spriteBody->setTag(bones);
layerGameMap->addChild(spriteBody);
bones++;
}
void GameScene::createFood() {
// 760 / 40 560 / 40
int xNumder = (19 * CCRANDOM_0_1());
int yNumder = (14 * CCRANDOM_0_1());
Point pointFood = Point(xNumder * 40 , yNumder * 40);
// 防止食物生成在蛇身体节点上
for (int i = 0 ; i < bones ; i++) {
auto spriteSnake = (SnakeSprite*)layerGameMap->getChildByTag(i);
if(spriteSnake->getPosition() == pointFood) {
createFood();
return;
}
}
spriteFood->setPosition( xNumder * 40 , yNumder * 40);
}
void GameScene::timeTask(float) {
gameTime++;
int shi,fen,miao;
shi = gameTime / 3600;
fen = gameTime / 60 % 60;
miao = gameTime % 60;
labelTime->setString(CCString::createWithFormat("Time: %02d:%02d:%02d" , shi , fen , miao)->getCString());
}
void GameScene::onEnter() {
Layer::onEnter();
SimpleAudioEngine::getInstance()->preloadEffect("eat.wav");
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan=CC_CALLBACK_2(GameScene::onTouchBegan , this);
listener->onTouchEnded=CC_CALLBACK_2(GameScene::onTouchEnded,this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);
}
void GameScene::onExit() {
Layer::onExit();
SimpleAudioEngine::getInstance()->unloadEffect("eat.wav");
}
bool GameScene::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event) {
if(layerGameMap->getBoundingBox().containsPoint(touch->getLocation())){
return true;
}
return false;
}
void GameScene::onTouchEnded(cocos2d::Touch *pTouch, cocos2d::Event *unused_event) {
Point pointStart = pTouch->getStartLocation();
Point pointEnd = pTouch->getLocation();
int x = pointStart.x - pointEnd.x;
int y = pointStart.y - pointEnd.y;
if(abs(x) > abs(y)) {
if(x + 100 > 0) {
changeDirection(LEFT);
} else {
changeDirection(RIGHT);
}
} else {
if(y + 100 > 0) {
changeDirection(DOWN);
} else {
changeDirection(UP);
}
}
}
void GameScene::changeDirection(DIRECTION direction) {
switch (direction) {
case UP:
if( currDirection == UP || currDirection == DOWN ){ return; }
spriteHeader->setSpriteFrame("snake-head-up.png");
break;
case DOWN:
if( currDirection == UP || currDirection == DOWN ){ return; }
spriteHeader->setSpriteFrame("snake-head-down.png");
break;
case RIGHT:
if( currDirection == RIGHT || currDirection == LEFT ){ return; }
spriteHeader->setSpriteFrame("snake-head-right.png");
break;
case LEFT:
if( currDirection == RIGHT || currDirection == LEFT ){ return; }
spriteHeader->setSpriteFrame("snake-head-left.png");
break;
}
currDirection = direction;
}
```
移动蛇身体的逻辑很简单,每个节点维护着下个节点的坐标,就是每次节点移动都会把当前坐标赋值给nextPoint 属性,然后下面的节点从上面的节点获取下个点的坐标nextPoint ,这样只需要控制头的方向就能控制整个身体了。说这么多自己都看不懂的废话。。。还是看代码把。
void GameScene::move() {
isMoveEnd = true;
for (int i = 0 ; i < bones ; i++) {
auto snake = (SnakeSprite*)layerGameMap->getChildByTag(i);
auto snakePoint = snake->getPosition();
SnakeSprite* preSnake = NULL;
Point preSnakePoint;
if(i == 0) { // 0是头,我们只需要控制头的方向和坐标
Point snakeCurrPoint;
switch (currDirection) {
case RIGHT:
snakeCurrPoint = snakePoint + CCPoint(40 , 0);
break;
case LEFT:
snakeCurrPoint = snakePoint + CCPoint(-40 , 0);
break;
case DOWN:
snakeCurrPoint = snakePoint + CCPoint(0 , -40);
break;
case UP:
snakeCurrPoint = snakePoint + CCPoint(0 , 40);
break;
default:
break;
}
if(snakeCurrPoint.x >= 760 || snakeCurrPoint.x < 0 || snakeCurrPoint.y >= 560 || snakeCurrPoint.y < 0) { // 判断有没有撞墙
end();
return;
}
snake->setPosition(snakeCurrPoint);
snake->setNextPoint(snakePoint);
} else {
preSnake = (SnakeSprite*)layerGameMap->getChildByTag(i - 1); // 获取蛇身体的上个节点
preSnakePoint = preSnake->getNextPoint(); // 得到上个节点未移动前的坐标
snake->setPosition(preSnakePoint); // 移动到上个节点的位置
snake->setNextPoint(snakePoint); // 提供当前坐标给下个节点移动
}
}
isMoveEnd = false;
}
```
有了移动就考虑吃食物和吃自己了...同样代码实现很粗暴。。。判断蛇是否吃了食物很简单,就是判断蛇的头部和食物的坐标是否一致,如果一致则代表吃到了。需要加个蛇身体节点和食物和游戏积分。
检查自己咬自己就是把自己身体的节点循环一遍,看自己的头部的坐标是否与某个身体节点的坐标是否一致。
void GameScene::check() {
// 如果头部跟实物坐标一致则代表吃了一个食物
if(spriteFood->getPosition() == spriteHeader->getPosition()) {
SimpleAudioEngine::getInstance()->playEffect("eat.wav");
addBody();
createFood();
gameScore += 5;
labelScore->setString(CCString::createWithFormat("Score: %d" , gameScore)->getCString());
}
// 检查当前头部是否与身体节点重叠 重叠为自己咬自己 游戏结束
for (int i = 1 ; i < bones ; i++) {
auto spriteSnake = (SnakeSprite*)layerGameMap->getChildByTag(i);
if(spriteHeader->getPosition() == spriteSnake->getPosition()) {
end();
}
}
}
```
创建食物:因为蛇的移动区域是760,560.蛇节点的宽高是40.所以食物生成的坐标是可以固定的。看代码
void GameScene::createFood() {
// 760 / 40 560 / 40
int xNumder = (19 * CCRANDOM_0_1());
int yNumder = (14 * CCRANDOM_0_1());
Point pointFood = Point(xNumder * 40 , yNumder * 40);
// 防止食物生成在蛇身体节点上
for (int i = 0 ; i < bones ; i++) {
auto spriteSnake = (SnakeSprite*)layerGameMap->getChildByTag(i);
if(spriteSnake->getPosition() == pointFood) {
createFood();
return;
}
}
spriteFood->setPosition( xNumder * 40 , yNumder * 40);
}
```
貌似实现一个贪吃蛇的重点就这些了,创建蛇,移动蛇,检查撞墙,吃食物,吃自己,创建食物。
好了教程就这些了,不磨叽了,写代码还行写教程自己看了都晕。不懂请留言把。。。
没图说个屁啊:图来了。
apk文件。。
MyCppGame.zip (1475 KB)


…水中月飞天上月