欢迎大家来到IT世界,在知识的湖畔探索吧!
这一节我们给大家介绍FlappyBird这款游戏的最重要的部分,游戏逻辑及碰撞检测。
这一节我们还是将代码实现过程分成两部分
UI部分和功能部分
UI部分包括
背景、按钮、小鸟
功能部分
碰撞检测、定时更新、触摸点击检测、游戏状态标识
UI部分
先看下面运行的效果图
游戏背景、坐上角暂停按钮、顶部计数的精灵
下面是代码实现
//随机产生背景图
int i = random();
if(i%2 == 0)
{
background = Sprite::create(“pic/morning.png”);
}else
{
background = Sprite::create(“pic/night.png”);
}
background->setAnchorPoint(Point(0, 0));
background->setPosition(Point(origin.x,origin.y + visibleSize.height – background->getContentSize().height));
this->addChild(background, 0);
//添加开始吧这个准备精灵
effectNode = NodeGrid::create();
this->addChild(effectNode, 1);
ready = Sprite::create(“pic/readyBird.png”);
ready->setPosition(Point(visibleSize.width/2, visibleSize.height*4/7));
effectNode->addChild(ready, 10);
//添加地板精灵
floor = Sprite::create(“pic/floor.png”);
floor->setAnchorPoint(Point(0, 0));
floor->setPosition(Point(origin.x, origin.y));
this->addChild(floor);
Size floorSize = floor->getContentSize();
floor->runAction(RepeatForever::create(Sequence::create(
MoveTo::create(0.5, Point(-120, 0)),MoveTo::create(0, Point(0, 0)),NULL)));
//添加计数的label
pLabelAtlas = LabelAtlas::create(“0″,”pic/number.png”, 48, 64,’0′);
pLabelAtlas->setPosition(Point(visibleSize.width/2,visibleSize.height/5*4));
this->addChild(pLabelAtlas,4);
//添加左上角按钮精灵,并且注册监听,触摸响应调用BirdLayer::onTouchPause
pause = Sprite::create(“button/pause.png”);
pause->setPosition(Point(32, 928));
this->addChild(pause,10);
EventListenerTouchOneByOne* listenerPause = EventListenerTouchOneByOne::create();
listenerPause->setSwallowTouches(true);
listenerPause->onTouchBegan = CC_CALLBACK_2(BirdLayer::onTouchPause, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listenerPause, pause);
以上是游戏开始的准备UI,我们可以想象一下,游戏开始后,我们还需要的UI元素精灵包括,飞行中的小鸟,还有上下不断移动的管道,下面我们就来实现小鸟和管道的初始化。
小鸟的初始化
在initBird()中实现,在这个实现过程中,我们会接触到cocos2d中动画相关的知识点
void BirdLayer::initBird()
{ //首先我们创建精灵帧缓存的实例,通过SpriteFrameCache::getInstance()。
SpriteFrameCache* sfc = SpriteFrameCache::getInstance();
//将精灵帧缓存必须的.plist文件和拼合的png文件添加
sfc->addSpriteFramesWithFile(“pic/bird.plist”, “pic/birdP.png”);
//构建字符创形式的数组,用于创建动画时使用
std::string animBird[3] =
{
“bird1.png”,
“bird2.png”,
“bird3.png”
};
//声明精灵帧的向量
Vector<SpriteFrame*> animFrames;
//精灵帧向量中添加精灵,通过getSpriteFrameByName()
animFrames.pushBack(sfc->getSpriteFrameByName(animBird[0]));
animFrames.pushBack(sfc->getSpriteFrameByName(animBird[1]));
animFrames.pushBack(sfc->getSpriteFrameByName(animBird[2]));
//用精灵帧向量来创建动画
Animation *anim = Animation::createWithSpriteFrames(animFrames, 0.1f);
//创建动画的动作
animAc = Animate::create(anim);
animAc->retain();
}
大家可以通过dash去查一下这里面涉及到的这些类和方法的使用,更加了解其用法
SpriteFrameCache
Animation
Animate
小鸟的初始化完成后,我们要把他添加到当前的场景中来,看代码实现
//创建小鸟的精灵、注意这里并未添加图片,只是创建了精灵,设定位置,添加到场景中,只有在最后一步runAction时通过动画来创建动作时,才将精灵的图片真正添加进来,实际上是以动画的形式添加
bird = Sprite::create();
bird->setPosition(Point(140 + origin.x, origin.y +floorSize.height + backgroundSize.height/2));
this->addChild(bird, 2);
bird->runAction(RepeatForever::create(animAc));
管道的初始化
管道的初始化分为前面的管道1和随后跟着出来的管道2,原理基本一样
initColumn1()
void BirdLayer::initColumn1()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
Size backSize = background->getContentSize();
Size floorSize = floor->getContentSize();
//设定上下管道的高度
int i = random();
int height1 = 400/i;
int height2 = backSize.height – height1 – 196;
//创建上下管道的节点
SpriteBatchNode* columnNode1 = SpriteBatchNode::create(“pic/column1.png”);
SpriteBatchNode* columnNode2 = SpriteBatchNode::create(“pic/column2.png”);
columnFlag1 = true;
//创建下管道的精灵
columnUnder1 = Scale9Sprite::create();
columnUnder1->updateWithBatchNode(columnNode1, Rect(0, 0, 96, 400), false, Rect(0, 30, 96, 400));
columnUnder1->setAnchorPoint(Point(0, 0));
//设置管道的contentsize
columnUnder1->setContentSize(Size(96, height1));
if(count>0)
{ //游戏中columnUnder1的位置
columnUnder1->setPosition(Point(visibleSize.width, floorSize.height));
}else
{//游戏开始前columnUnder1的初始位置
columnUnder1->setPosition(Point(visibleSize.width*2, floorSize.height));
}
//同样的方式创建顶部的管道
columnOn1 = Scale9Sprite::create();
columnOn1->updateWithBatchNode(columnNode2, Rect(0, 0, 96, 400), false, Rect(0, 0, 96, 370));
columnOn1->setAnchorPoint(Point(0, 0));
columnOn1->setContentSize(Size(96, height2));
if(count>0)
{
columnOn1->setPosition(Point(visibleSize.width, visibleSize.height – height2));
}else
{
columnOn1->setPosition(Point(visibleSize.width*2, visibleSize.height – height2));
}
this->addChild(columnUnder1, 0);
this->addChild(columnOn1, 0);
// 创建的上下管道做重复的运动在起始点的基础上,每次都左移25,实现管道的滚动,通过上面的随机值i,来变化管道的高度
columnUnder1->runAction(RepeatForever::create(MoveBy::create(0.1, Point(-25, 0))));
columnOn1->runAction(RepeatForever::create(MoveBy::create(0.1, Point(-25, 0))));
}
从上图看,前面的第一个管道就是column1,后面就是column2,逻辑基本一样。
UI基本介绍到这里,我们现在开始介绍功能和整体逻辑
功能介绍和游戏逻辑
-
游戏界面完成初始化 birdLayer::init()
-
点击界面开始游戏startGame()
-
碰撞检测、游戏是否结束
游戏界面完成初始化 birdLayer::init()
1.游戏状态标识的初始化
stopFlag = false;//游戏停止
gameFlag = true;//游戏中
readyFlag = true;//游戏准备开始
overFlag = false;//越过管道
2.背景、小鸟、管道、暂停、触摸监听(暂停和游戏开始)
暂停的响应函数为onTouchPause
bool BirdLayer::onTouchPause(Touch* touch, Event* event)
{
if(!gameFlag)//判断是否已经暂停,如果已经暂停,直接返回false
{
return false;
}
auto target = static_cast<Sprite*>(event->getCurrentTarget());//通过触摸事件获取到的目标,转换为精灵
auto location = target->convertToNodeSpace(touch->getLocation());
auto size = target->getContentSize();
auto rect = Rect(0, 0, size.width, size.height);
if(!stopFlag && rect.containsPoint(location) )//判断是否点击到暂停的精灵和游戏是否停止
{
stopFlag = true;
pause->setTexture(“button/continue_pause.png”);//将pause的纹理图设定为继续
Director::getInstance()->pause();//调用导演实例,暂停
pauseBack = Sprite::create(“pic/setBackground.png”);
pauseBack->setPosition(Point(270, 600));
this->addChild(pauseBack, 10);
Sprite* music = Sprite::create(“pic/music.png”);
pauseBack->addChild(music, 1);
music->setPosition(Point(120, 300));
CheckBox* checkMusic = CheckBox::create( “button/sound_on.png”,”button/sound_off.png”,
“button/sound_off.png”, “button/sound_stop.png”,
“button/sound_stop.png” );
pauseBack->addChild(checkMusic, 1);
checkMusic->setPosition(Point(350, 300));
checkMusic->setSelectedState(!MainLayer::musicFlag);
checkMusic->addEventListener(CC_CALLBACK_2(BirdLayer::selectedEvent0, this));
Sprite* sound = Sprite::create(“pic/sound.png”);
pauseBack->addChild(sound, 1);
sound->setPosition(Point(120, 200));
CheckBox* checkSound = CheckBox::create(“button/sound_on.png”,”button/sound_off.png”,”button/sound_off.png”,”button/sound_stop.png”,”button/sound_stop.png”);
pauseBack->addChild(checkSound, 1);
checkSound->setPosition(Point(350, 200));
checkSound->setSelectedState(!MainLayer::soundFlag);
checkSound->addEventListener(CC_CALLBACK_2(BirdLayer::selectedEvent1, this));
//继续按钮
MenuItemImage* conItem = MenuItemImage::create(“button/continue.png”,”button/continue_off.png”,CC_CALLBACK_1(BirdLayer::menuCallbackItem3, this) );
conItem->setPosition(Point(80, 80));
//返回按钮
MenuItemImage* backItem = MenuItemImage::create(“button/menu.png”,”button/menu_off.png”,CC_CALLBACK_1(BirdLayer::menuCallbackItem2, this) );
backItem->setPosition(Point(225, 80));
//重新开始按钮
MenuItemImage* againItem = MenuItemImage::create(“button/replay.png”,”button/replay_off.png”,CC_CALLBACK_1(BirdLayer::menuCallbackItem4, this) );
againItem->setPosition(Point(370, 80));
Menu* menu = Menu::create(againItem, conItem, backItem, NULL);
menu->setPosition(Point::ZERO);
pauseBack->addChild(menu,1);
return true;
}
else
{
return false;
}
触摸界面游戏开始
bool BirdLayer::onTouchBegan(Touch* touch, Event* event)
{
Point birdPosition = bird->getPosition();
if(gameFlag)//游戏标志位判断,如果已经在游戏了,就跳过,直接执行下面小鸟跳动的效果
{
if(readyFlag)//是否准备好
{
startGame();开始游戏
readyFlag = false;//将准备标志位表示为fasle,表示游戏已经开始了
}
if(MainLayer::soundFlag)
{
wingSound();
}
int move = 105;
//创建瞬时动作,点击跳动,同时角度调整
auto action = Spawn::createWithTwoActions(MoveTo::create(0.2, Point(birdPosition.x, birdPosition.y + move)), RotateTo::create(0, -30));
bird->stopAllActions();//让小鸟恢复平稳位置
//执行初始化时的动画播放
bird->runAction(RepeatForever::create(animAc));
//小鸟执行跳动的动作,并通过设置overFlag进行计分
bird->runAction(Sequence::create(CallFunc::create(CC_CALLBACK_0(BirdLayer::setRunFlag1, this)),action,DelayTime::create(0.05 ),CallFunc::create(CC_CALLBACK_0(BirdLayer::setRunFlag2, this)),RotateTo::create(2.0, 90),NULL));
}
return true;
}
开始游戏startGame()
从下面可以看出,点击界面开始游戏后,才进行的管道的初始化,然后执行birdRun(),然后获取定时器,通过定时器,每隔0.05秒更新,执行update_column 和update_bird
void BirdLayer::startGame()
{
Director::getInstance()->setDepthTest(false);
effectNode->runAction(SplitRows::create(0.5f, 30));
initColumn1();
initColumn2();
birdRun();//执行游戏开始后,小鸟的第一次跳动动作,因为后续的touch不在执行stateGame函数
auto director = Director::getInstance();
auto sched = director->getScheduler();
sched->schedule(schedule_selector(BirdLayer::update_column), this, 0.05, false);
sched->schedule(schedule_selector(BirdLayer::update_bird), this, 0.05, false);
}
关于管道和小鸟的更新
判断管道是否移动出当前屏幕可视范围,一旦移出,就remove该精灵,同时生成新的管道精灵
void BirdLayer::update_column(float delta)
{
Point columnPosition1 = columnUnder1->getPosition();
Point columnPosition2 = columnUnder2->getPosition();
Size columnSize = columnUnder1->getContentSize();
if(columnPosition1.x<=-columnSize.width)
{
removeChild(columnUnder1);
removeChild(columnOn1);
initColumn1();
}
if(columnPosition2.x<=-columnSize.width)
{
removeChild(columnUnder2);
removeChild(columnOn2);
initColumn2();
}
}
该游戏的核心就在小鸟的更新函数,这里会更新小鸟的位置,对小鸟和管道进行碰撞检测判断,并对碰撞检测后的结果做出响应。如果检测到碰撞就游戏结束gameOver,如果没有碰撞到,积分就要+1.
这个过程中最重要就是计算小鸟和管道的位置和,各自精灵的size区域
void BirdLayer::update_bird(float delta)
{ //首先判断是否在游戏中,并且小鸟在跳动,如果不是,则让小鸟动起来,就是最开始的阶段
if(gameFlag && !runFlag)
{
birdRun();
}
Point birdPosition = bird->getPosition();
Size birdSize = bird->getContentSize();
Size floorSize = floor->getContentSize();
Point columnPosition1 = columnUnder1->getPosition();
Point columnPosition2 = columnOn1->getPosition();
Point columnPosition3 = columnUnder2->getPosition();
Point columnPosition4 = columnOn2->getPosition();
Size columnSize1 = columnUnder1->getContentSize();
Size columnSize2 = columnOn1->getContentSize();
Size columnSize3 = columnUnder2->getContentSize();
Size columnSize4 = columnOn2->getContentSize();
//如果小鸟的X坐标大于 管道1的X坐标,且column的标志在运动的时候设置为真,就计分,并呈现在计分位置的label上
if(birdPosition.x>columnPosition1.x && columnFlag1)
{
count++;
string num =StringUtils::toString(count);
pLabelAtlas->setString(num);
if(MainLayer::soundFlag)
{
pointSound();
}
columnFlag1 = false;
}
//如果小鸟的X坐标大于 管道2的X坐标,且column的标志在运动的时候设置为真,就计分,并呈现在计分位置的label上
if(birdPosition.x>columnPosition3.x && columnFlag2)
{
count++;
string num =StringUtils::toString(count);
pLabelAtlas->setString(num);
if(MainLayer::soundFlag)
{
pointSound();
}
columnFlag2 = false;
}
//以小鸟当前位置和地板、columnUnder1、columnUnder2、columnOn1、columnOn2
进行碰撞检测
bool check = collision((birdPosition.x – birdSize.width), (birdPosition.y – birdSize.height), (birdPosition.x + birdSize.width), (birdPosition.y + birdSize.height),
0, 0, floorSize.width, floorSize.height);
bool check1 = collision((birdPosition.x – birdSize.width/3), (birdPosition.y – birdSize.height/3), (birdPosition.x + birdSize.width/3), (birdPosition.y + birdSize.height/3),
columnPosition1.x, columnPosition1.y, (columnPosition1.x + columnSize1.width), (columnPosition1.y + columnSize1.height));
bool check2 = collision((birdPosition.x – birdSize.width/3), (birdPosition.y – birdSize.height/3), (birdPosition.x + birdSize.width/3), (birdPosition.y + birdSize.height/3),
columnPosition2.x, columnPosition2.y, (columnPosition2.x + columnSize2.width), (columnPosition2.y + columnSize2.height));
bool check3 = collision((birdPosition.x – birdSize.width/3), (birdPosition.y – birdSize.height/3), (birdPosition.x + birdSize.width/3), (birdPosition.y + birdSize.height/3),
columnPosition3.x, columnPosition3.y, (columnPosition3.x + columnSize3.width), (columnPosition3.y + columnSize3.height));
bool check4 = collision((birdPosition.x – birdSize.width/3), (birdPosition.y – birdSize.height/3), (birdPosition.x + birdSize.width/3), (birdPosition.y + birdSize.height/3),
columnPosition4.x, columnPosition4.y, (columnPosition4.x + columnSize4.width), (columnPosition4.y + columnSize4.height));
if(check || check1 || check2 || check3 || check4)
{//如果检测到碰撞,播放碰撞的音效,并且游戏结束
if(gameFlag)
{
if(MainLayer::soundFlag)
{
hitSound();
}
if(check1 || check2 || check3 || check4)
{
if(MainLayer::soundFlag)
{
dieSound();//播放小鸟死掉的音效
}
}
}
gameOver();//游戏结束
}
}
这里我们详细再讲解一下碰撞检测。看下面的图,碰撞检测实际上先以X坐标区域将碰撞区域划分为左、中、右;然后在以Y坐标分别对上下进行测试,是在上部碰撞,还是下部碰撞。
static bool collision(double sMinX, double sMinY, double sMaxX, double sMaxY, double gMinX, double gMinY, double gMaxX, double gMaxY)
{
//重叠区域
double xOverlap = 0;
double yOverlap = 0;
//ÖصþÃæ»ý
int impacetArea = 0;
//碰撞检测逻辑,先按水平方向划分左、右、包含,然后分别区分上下碰撞,根据坐标重叠进行计算
if(sMinX<=gMaxX && sMinX>=gMinX && sMaxX>gMaxX)//左边
{
xOverlap = abs(gMaxX – sMinX);
if(sMaxY>gMinY && sMaxY<gMaxY)
{
yOverlap = abs(gMinY – sMaxY);
}else if(sMinY<gMaxY && sMinY>gMinY)
{
yOverlap = abs(gMaxY – sMinY);
}
}else if(sMaxX>=gMinX && sMaxX<=gMaxX && sMinX<=gMinX)//左边
{
xOverlap = abs(gMinX – sMaxX);
//ÉÏ·½Åöײ
if(sMaxY>gMinY && sMaxY<gMaxY)
{
yOverlap = abs(gMinY – sMaxY);
}else if(sMinY<gMaxY && sMinY>gMinY)
{
yOverlap = abs(gMaxY – sMinY);
}
}else if(sMaxX<=gMaxX && sMaxX>=gMinX && sMinX>=gMinX && sMaxX>=gMinX)//中间
{
xOverlap = abs(sMaxX – sMinX);
if(sMaxY>gMinY && sMaxY<gMaxY)
{
yOverlap = abs(gMinY – sMaxY);
}else if(sMinY<gMaxY && sMinY>gMinY)
{
yOverlap = abs(gMaxY – sMinY);
}
}
impacetArea = xOverlap * yOverlap;//计算重叠区域,大于0就表示碰撞到了
if(impacetArea>0)
{
return true;
}
return false;
}
到这里整个FlappyBird的核心过程都讲解完了,在上一节已经将完整代码的百度网盘下载地址分享了,后面我们再以一种更高级的方式,利用Cocos2d中自带的物理刚体、碰撞来重写这个游戏,你会发现比这个简单很多
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/17568.html