cocos2dx实例开发之flappybird(入门版)

cocos2dx实例开发之flappybird(入门版),第1张

概述cocos2dx社区里有个系列博客完整地复制原版flappybird的所有特性,不过那个代码写得比较复杂,新手学习起来有点捉摸不透,这里我写了个简单的版本。演示如下: 创建项目 VS2013+cocos2dx 3.2创建win32项目,由于只是学习,所以没有编译为安卓、ios或者WP平台的可执行文件。 最终的项目工程结构如下: 很简单,只有三个类,预加载类,游戏主场景类,应用代理类,新手刚入门喜欢

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()->addSpriteFramesWith@R_403_6852@("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,小鸟
//小鸟	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,记分板 游戏结束后滑入记分板,并显示重玩按钮。
//加入记分板和重玩菜单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));	panel->addChild(curscoreTTF);	auto bestscoreTTF = LabelTTF::create(String::createWithFormat("%d",bestscore)->getCString(),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
还有很多要完善的地方,比如没有加入图片数字以及社交分享等等。 总结

以上是内存溢出为你收集整理的cocos2dx实例开发之flappybird(入门版)全部内容,希望文章能够帮你解决cocos2dx实例开发之flappybird(入门版)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1005865.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-22
下一篇 2022-05-22

发表评论

登录后才能评论

评论列表(0条)

保存