zoukankan      html  css  js  c++  java
  • cocos2dx实例开发之flappybird(入门版)

    cocos2dx社区里有个系列博客完整地复制原版flappybird的全部特性。只是那个代码写得比較复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本号。演演示样例如以下:



    创建项目

    VS2013+cocos2dx 3.2创建win32项目。因为仅仅是学习,所以没有编译为安卓、ios或者WP平台的可运行文件。
    终于的项目project结构例如以下:

    非常easy,仅仅有三个类,预载入类。游戏主场景类,应用代理类。新手刚入门喜欢将非常多东西都写在尽量少的类里面。

    游戏设计

    游戏结构例如以下,游戏包括预载入场景和主场景,主场景中包括背景、小鸟、管道和各种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);
    //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在游戏開始后要隐藏掉。

    3.2。小鸟
    //小鸟
    	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,水管
    一组水管由上下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);  //依据竖直方向的速度算出旋转角度。逆时针为负
    3.8,游戏開始
    開始后启动各种定时器
    //游戏開始
    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,记分板
    游戏结束后滑入记分板,并显示重玩button。
    //加入记分板和重玩菜单
    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
    还有非常多要完好的地方,比方没有增加图片数字以及社交分享等等。


  • 相关阅读:
    数据库 concat 与 ||
    mysql时间戳详解及运用
    mysql数据库事务的操作与理解
    数据分析实战——03丨Python基础语法:开始你的Python之旅
    数据分析实战——02丨学习数据挖掘的最佳路径是什么?
    数据分析实战——01丨数据分析全景图及修炼指南
    数据分析实战——开篇词 | 你为什么需要数据分析能力?
    从0开始学大数据学习笔记——37.如何对数据进行分类和预测?
    坚毅(GRIT)阅读笔记
    Make over monday – 每周动手实践的Tableau社区网站
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5359921.html
Copyright © 2011-2022 走看看