cocos2dx实例开发之flappybird(入门版)
cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本。演示如下:
创建项目
游戏设计
游戏结构如下,游戏包含预加载场景和主场景,主场景中包含背景、小鸟、管道和各种UI界面。开发步骤
1,素材收集//添加加载回调函数,用异步加载纹理 Director::getInstance()->getTextureCache()->addImageAsync("game.png", CC_CALLBACK_1(LoadingScene::loadingCallBack, this));
void LoadingScene::loadingCallBack(Texture2D *texture) { //预加载帧缓存纹理 SpriteFrameCache::getInstance()->addSpriteFramesWithFile("game.plist", texture); //预加载帧动画 auto birdAnimation = Animation::create(); birdAnimation->setDelayPerUnit(0.2f); birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird1.png")); birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird2.png")); birdAnimation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("bird3.png")); AnimationCache::getInstance()->addAnimation(birdAnimation, "birdAnimation"); //将小鸟动画添加到动画缓存 //预加载音效 SimpleAudioEngine::getInstance()->preloadEffect("die.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("hit.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("point.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("swooshing.mp3"); SimpleAudioEngine::getInstance()->preloadEffect("wing.mp3"); //加载完毕跳转到游戏场景 auto gameScene = GameScene::createScene(); TransitionScene *transition = TransitionFade::create(0.5f, gameScene); Director::getInstance()->replaceScene(transition); }
3,游戏主场景
//添加游戏背景 Sprite *backGround = Sprite::createWithSpriteFrameName("bg.png"); backGround->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2); this->addChild(backGround); //logo auto gameLogo = Sprite::createWithSpriteFrameName("bird_logo.png"); gameLogo->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2+100); gameLogo->setName("logo"); this->addChild(gameLogo);logo在游戏开始后要隐藏掉。
//小鸟 birdSprite = Sprite::create(); birdSprite->setPosition(visibleOrigin.x + visibleSize.width / 3, visibleOrigin.y + visibleSize.height / 2); this->addChild(birdSprite); auto birdAnim = Animate::create(AnimationCache::getInstance()->animationByName("birdAnimation")); birdSprite->runAction(RepeatForever::create(birdAnim)); //挥翅动画 auto up = MoveBy::create(0.4f, Point(0, 8)); auto upBack = up->reverse(); if (gameStatus == GAME_READY) { swingAction = RepeatForever::create(Sequence::create(up, upBack, NULL)); birdSprite->runAction(swingAction); //上下晃动动画 }在准备界面下除了有扇翅膀的动作,还有上下浮动的动作。
3.3,地板
//添加两个land land1 = Sprite::createWithSpriteFrameName("land.png"); land1->setAnchorPoint(Point::ZERO); land1->setPosition(Point::ZERO); this->addChild(land1, 10); //置于最顶层 land2 = Sprite::createWithSpriteFrameName("land.png"); land2->setAnchorPoint(Point::ZERO); land2->setPosition(Point::ZERO); this->addChild(land2, 10);
Size visibleSize = Director::getInstance()->getVisibleSize(); //两个图片循环移动 land1->setPositionX(land1->getPositionX() - 1.0f); land2->setPositionX(land1->getPositionX() + land1->getContentSize().width - 2.0f); if (land2->getPositionX() <= 0) land1->setPosition(Point::ZERO);3.4,水管
//同屏幕出现的只有两根管子,放到容器里面,上下绑定为一根 for (int i = 0; i < 2; i++) { auto visibleSize = Director::getInstance()->getVisibleSize(); Sprite *pipeUp = Sprite::createWithSpriteFrameName("pipe_up.png"); Sprite *pipeDown = Sprite::createWithSpriteFrameName("pipe_down.png"); Node *singlePipe = Node::create(); //给上管绑定刚体 auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize()); pipeUpBody->setDynamic(false); pipeUpBody->setContactTestBitmask(1); pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE); pipeUp->setPhysicsBody(pipeUpBody); //给两个管子分开设置刚体,可以留出中间的空隙使得小鸟通过 //给下管绑定刚体 auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize()); pipeDownBody->setDynamic(false); pipeDownBody->setContactTestBitmask(1); pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE); pipeDown->setPhysicsBody(pipeDownBody); pipeUp->setPosition(0, PIPE_HEIGHT + PIPE_SPACE); singlePipe->addChild(pipeUp); singlePipe->addChild(pipeDown); //pipeDown默认加到(0,0),上下合并,此时singlePipe以下面的管子中心为锚点 singlePipe->setPosition(i*PIPE_INTERVAL + WAIT_DISTANCE, getRandomHeight() ); //设置初始高度 singlePipe->setName("newPipe"); this->addChild(singlePipe); //把两个管子都加入到层 pipes.pushBack(singlePipe); //两个管子先后添加到容器 }
//管子滚动 for (auto &singlePipe : pipes) { singlePipe->setPositionX(singlePipe->getPositionX() - 1.0f); if (singlePipe->getPositionX() < -PIPE_WIDTH/2) { singlePipe->setPositionX(visibleSize.width+PIPE_WIDTH/2); singlePipe->setPositionY(getRandomHeight()); singlePipe->setName("newPipe"); //每次重设一根管子,标为new } }3.5,加入物理世界
gameScene->getPhysicsWorld()->setGravity(Vec2(0, -900)); //设置重力场,重力加速度可以根据手感改小点
gameLayer->setPhysicWorld(gameScene->getPhysicsWorld()); //绑定物理世界小鸟绑定刚体
//小鸟绑定刚体 auto birdBody = PhysicsBody::createCircle(BIRD_RADIUS); //将小鸟当成一个圆,懒得弄精确的轮廓线了 birdBody->setDynamic(true); //设置为可以被物理场所作用而动作 birdBody->setContactTestBitmask(1); //必须设置这项为1才能检测到不同的物体碰撞 birdBody->setGravityEnable(false); //设置是否被重力影响,准备画面中不受重力影响 birdSprite->setPhysicsBody(birdBody); //为小鸟设置刚体地板绑定刚体
//设置地板刚体 Node *groundNode = Node::create(); auto groundBody = PhysicsBody::createBox(Size(visibleSize.width, land1->getContentSize().height)); groundBody->setDynamic(false); groundBody->setContactTestBitmask(1); groundNode->setAnchorPoint(Vec2::ANCHOR_MIDDLE); //物理引擎中的刚体只允许结点锚点设置为中心 groundNode->setPhysicsBody(groundBody); groundNode->setPosition(visibleOrigin.x+visibleSize.width/2,land1->getContentSize().height/2); this->addChild(groundNode);管道设置刚体,上下半根分别设置,留出中间的缝隙
//给上管绑定刚体 auto pipeUpBody = PhysicsBody::createBox(pipeUp->getContentSize()); pipeUpBody->setDynamic(false); pipeUpBody->setContactTestBitmask(1); pipeUp->setAnchorPoint(Vec2::ANCHOR_MIDDLE); pipeUp->setPhysicsBody(pipeUpBody); //给两个管子分开设置刚体,可以留出中间的空隙使得小鸟通过 //给下管绑定刚体 auto pipeDownBody = PhysicsBody::createBox(pipeDown->getContentSize()); pipeDownBody->setDynamic(false); pipeDownBody->setContactTestBitmask(1); pipeDown->setAnchorPoint(Vec2::ANCHOR_MIDDLE); pipeDown->setPhysicsBody(pipeDownBody);碰撞检测
//添加碰撞监测 auto contactListener = EventListenerPhysicsContact::create(); contactListener->onContactBegin = CC_CALLBACK_1(GameScene::onContactBegin, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);
//碰撞监测 bool GameScene::onContactBegin(const PhysicsContact& contact) { if (gameStatus == GAME_OVER) //当游戏结束后不再监控碰撞 return false; gameOver(); return true; }3.6,触摸检测
//触摸监听 bool GameScene::onTouchBegan(Touch *touch, Event *event)3.7,控制小鸟
birdSprite->getPhysicsBody()->setVelocity(Vec2(0, 250)); //给一个向上的初速度
//小鸟的旋转 auto curVelocity = birdSprite->getPhysicsBody()->getVelocity(); birdSprite->setRotation(-curVelocity.y*0.1 - 20); //根据竖直方向的速度算出旋转角度,逆时针为负
//游戏开始 void GameScene::gameStart() { gameStatus = GAME_START; score = 0;//重置分数 scoreLabel->setString(String::createWithFormat("%d", score)->getCString()); this->getChildByName("logo")->setVisible(false); //logo消失 scoreLabel->setVisible(true); //计分开始 this->scheduleUpdate();//启动默认更新 this->schedule(schedule_selector(GameScene::scrollLand), 0.01f); //启动管子和地板滚动 birdSprite->stopAction(swingAction); //游戏开始后停止上下浮动 birdSprite->getPhysicsBody()->setGravityEnable(true); //开始受重力作用 }3.9,计分和数据存储
//当游戏开始时,判断得分,这个其实也可以写在其他地方,比如管子滚动的更新函数里面或者触摸监测里面 if (gameStatus == GAME_START) { for (auto &pipe : pipes) { if (pipe->getName() == "newPipe") //新来一根管子就判断 { if (pipe->getPositionX() < birdSprite->getPositionX()) { score++; scoreLabel->setString(String::createWithFormat("%d", score)->getCString()); SimpleAudioEngine::getInstance()->playEffect("point.mp3"); pipe->setName("passed"); //标记已经过掉的管子 } } } }4.0,游戏结束
//游戏结束 void GameScene::gameOver() { gameStatus = GAME_OVER; //获取历史数据 bestScore = UserDefault::getInstance()->getIntegerForKey("BEST"); if (score > bestScore) { bestScore = score; //更新最好分数 UserDefault::getInstance()->setIntegerForKey("BEST", bestScore); } SimpleAudioEngine::getInstance()->playEffect("hit.mp3"); //游戏结束后停止地板和管道的滚动 this->unschedule(schedule_selector(GameScene::scrollLand)); }结束后比较当前分数和历史分数,以便更新。
SimpleAudioEngine::getInstance()->playEffect("hit.mp3");4.2,记分板
//加入记分板和重玩菜单 void GameScene::gamePanelAppear() { Size size = Director::getInstance()->getVisibleSize(); Vec2 origin = Director::getInstance()->getVisibleOrigin(); //用node将gameoverlogo和记分板绑在一起 Node *gameOverPanelNode = Node::create(); auto gameOverLabel = Sprite::createWithSpriteFrameName("gameover.png"); gameOverPanelNode->addChild(gameOverLabel); auto panel = Sprite::createWithSpriteFrameName("board.PNG");//注意这里是大写PNG,原图片用什么后缀这里就用什么,区分大小写 gameOverLabel->setPositionY(panel->getContentSize().height); //设置一下坐标 gameOverPanelNode->addChild(panel); //记分板上添加两个分数 auto curScoreTTF = LabelTTF::create(String::createWithFormat("%d", score)->getCString(), "Arial", 20); curScoreTTF->setPosition(panel->getContentSize().width-40, panel->getContentSize().height-45); curScoreTTF->setColor(Color3B(255, 0, 0)); panel->addChild(curScoreTTF); auto bestScoreTTF = LabelTTF::create(String::createWithFormat("%d", bestScore)->getCString(), "Arial", 20); bestScoreTTF->setPosition(panel->getContentSize().width - 40, panel->getContentSize().height - 90); bestScoreTTF->setColor(Color3B(0, 255, 0)); panel->addChild(bestScoreTTF); this->addChild(gameOverPanelNode); gameOverPanelNode->setPosition(origin.x + size.width / 2, origin.y + size.height ); //滑入动画 gameOverPanelNode->runAction(MoveTo::create(0.5f, Vec2(origin.x + size.width / 2, origin.y + size.height / 2))); SimpleAudioEngine::getInstance()->playEffect("swooshing.mp3"); //添加菜单 MenuItemImage *restartItem = MenuItemImage::create("start_btn.png", "start_btn_pressed.png", this,menu_selector(GameScene::gameRetart)); auto menu = CCMenu::createWithItem(restartItem); menu->setPosition(origin.x + size.width / 2, 150); this->addChild(menu); } //游戏重新开始 void GameScene::gameRetart(Ref *sender) { //重新回到初始画面 auto gameScene = GameScene::createScene(); Director::getInstance()->replaceScene(gameScene); //这里懒得加特效了,直接转场 }
效果图:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。