cocos2dx实例开发之flappybird(入门版)
来源:互联网 发布:js html2img 编辑:程序博客网 时间:2024/06/13 01:13
cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本。演示如下:
创建项目
VS2013+cocos2dx 3.2创建win32项目,由于只是学习,所以没有编译为安卓、ios或者WP平台的可执行文件。
最终的项目工程结构如下:
很简单,只有三个类,预加载类,游戏主场景类,应用代理类,新手刚入门喜欢将很多东西都写在尽量少的类里面。
游戏设计
游戏结构如下,游戏包含预加载场景和主场景,主场景中包含背景、小鸟、管道和各种UI界面。开发步骤
1,素材收集从apk文件里提取出来一些图片和音频,并用TexturePatcher拼成大图,导出plist文件。
2,预加载场景
新建一个LoadingScene,在里面添加一张启动图片,通过异步加载纹理并回调的方式把所有图片素材、小鸟帧动画以及音频文件都加入到缓存,加载完毕后跳转到游戏主场景。
//添加加载回调函数,用异步加载纹理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,游戏主场景
3.1,背景和logo
用图片精灵即可
//添加游戏背景Sprite *backGround = Sprite::createWithSpriteFrameName("bg.png");backGround->setPosition(visibleOrigin.x + visibleSize.width / 2, visibleOrigin.y + visibleSize.height / 2);this->addChild(backGround);//logoauto 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在游戏开始后要隐藏掉。
3.2,小鸟
3.3,地板
//小鸟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,地板
地板的左移是用两张错位的地板图片循环左移实现的。需要用到自定义调度器,注意调节移动速度。
//添加两个landland1 = 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,水管
一组水管由上下2半根组成,用Node包起来,弄个vector容器添加两组管道,每次出现在屏幕中的管子只有两组,当一组消失在屏幕范围内则重设置其横坐标,需要提前计算好各种间距或者高度。
//同屏幕出现的只有两根管子,放到容器里面,上下绑定为一根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,加入物理世界
cocos2dx 3.0后引入了自带的物理引擎,用法和box2D等差不多。
物理世界初始化
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);碰撞检测
现在层的init里面的事件分发器中加入碰撞侦听
//添加碰撞监测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)); //给一个向上的初速度
小鸟的旋转角度与纵向速度有关,写在update()里
//小鸟的旋转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,计分和数据存储
在默认的update()函数里对得分进行判断和更新,通过默认xml存储历史分数
//当游戏开始时,判断得分,这个其实也可以写在其他地方,比如管子滚动的更新函数里面或者触摸监测里面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));}结束后比较当前分数和历史分数,以便更新。
4.1,音频
音效文件已经加入到缓存,在适当的地方加上全局音频控制器播放音效即可
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); //这里懒得加特效了,直接转场}
效果图:
源代码
csdn下载:MyFlappyBird
github下载:MyFlappyBird
还有很多要完善的地方,比如没有加入图片数字以及社交分享等等。
还有很多要完善的地方,比如没有加入图片数字以及社交分享等等。
4 0
- cocos2dx实例开发之flappybird(入门版)
- cocos2dx实例开发之2048(添加动画版)
- cocos2dx实例开发之2048(添加动画版)
- cocos2dx 3.0 flappybird -----001
- cocos2dx 3.0 flappybird -----002
- cocos2dx 3.0 flappybird -----003
- cocos2dx 3.0 flappybird -----004
- cocos2dx 3.0 flappybird -----005
- 【Cocos2dx开发之锚点实例讲解】
- Cocos2dx开发之锚点实例
- Cocos2dx开发之锚点实例讲解
- cocos2dx实例开发之2D横版跑酷
- Cocos2dx开发之锚点实例讲解
- Cocos2dx 入门小游戏实例
- 【cocos2dx-3.0beta-制作flappybird】就这么开始--开发环境的搭建素材的准备
- 背景与小鸟--FlappyBird游戏开发教程之二
- Cocos2dx之入门基础
- cocos2dx游戏开发之安装cocos2dx-3.13(一)
- BZOJ 2049: [Sdoi2008]Cave 洞穴勘测 LCT
- Codevs 1076 题解
- 程序员编程技术迅速提高的终极攻略
- OpenCV入门(十五)-- 霍夫变换
- XML Schema学习札记(2)——简单类型介绍
- cocos2dx实例开发之flappybird(入门版)
- Imagemagick中解决convert: no decode delegate for this image format 问题
- Android SDK无法更新
- 1034
- fruitstrap安装app
- 去除Odoo主页中的提示: Your Odoo is not supported.
- java自制线程池
- 成功的背后!(给所有IT人)
- Velocity.js错误:Velocity is not defined