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

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);
//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,记分板

游戏结束后滑入记分板,并显示重玩按钮。

//加入记分板和重玩菜单
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); //这里懒得加特效了,直接转场
}

效果图:

 

 

源代码:MyFlappyBird

还有很多要完善的地方,比如没有加入图片数字以及社交分享等等。

时间: 2024-08-02 11:04:02

cocos2dx实例开发之flappybird(入门版)的相关文章

cocos2dx实例开发之2048(添加动画版)

网上找了好多教程写2048,不过都没有实现卡片的移动动画,自己写了一个不太完美的带动画版. 开发步骤: 1,设计一个CardSprite类. 2,设计主游戏场景GameScene,实现游戏逻辑,添加动画逻辑. 3,添加游戏胜利或者游戏失败的层. 4,添加声音等其他元素,专门弄了一个声音预加载的场景. 贴上主场景关键代码: GameScene.h #pragma once #include "cocos2d.h" #include "cardSprite.h" #in

Cocos2d-x游戏开发之lua编辑器 subime 搭建,集成cocos2dLuaApi和自有类

Sublime Text http://baike.baidu.com/view/10701920.htm?from_id=8130415&type=syn&fromtitle=Sublime&fr=aladdin 简介 Sublime Text 是一个代码编辑器(Sublime Text 2是收费软件,但可以无限期试用),也是HTML和散文先进的文本编辑器.Sublime Text是由程序员Jon Skinner于2008年1月份所开发出来,它最初被设计为一个具有丰富扩展功能的V

Cocos2d-x游戏开发之lua工程创建

操作系统:OS X 10.85 Cocos2d-x 版本: 2.2.1 使用Cocos2d-x 可以创建lua工程,已经使用cpp创建的工程也可以继承lua进行开发,但是lua并不支持mac工程(因为一些框架的问题). 支持的工程文件如下: 所有使用创建工程create.py language 为cpp的工程,后集成lua及其工具的时候,要注意这一点. 撒 现在进入cocos2d-x 目录之下,通过cd 进入文件目录 进入之后,如果忘记了命令,可以直接运行 create_project.py 如

安卓实战开发之JNI入门及高效的配置(android studio一键生成.h,so及方法签名)

前言 以前也讲过NDK开发,但是开始是抱着好玩的感觉去开始的,然后呢会helloWord就觉得大大的满足,现在静下来想这NDK开发到底是干什么呢? NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西:另外就是为了效率更加高效些但是在java与C相互调用时平白又增大了开销(其实效率不见得有所提高),然后呢,基于安全性的考虑也是为了防止代码被反编译我们为了安全起见,使用C语言来编写这些重要的部分来增大系统的安全性,最后呢生成so库便于给人提供方便. 好了,我们来看一下qq的结构,我们就

webapp开发之bui入门环境搭建及执行npm命令报错解决

引言: BUI是一个WebApp开发使用的框架,使用这个框架开发呢的app支持跨多个平台部署,这样方便很多.要使用BUI,首先要安装以及部署环境,过程中确实很让新手头疼,记录一下,望后人能少踩坑. BUI的官方网站是:BUI(含快速入门教程) 1.下载完整的工程模板 : 步骤:下载多页开发包 开发包下载--解压. ps:网页中两个选项,单页开发包和多页开发包.选择多页开发包下载,下载后解压.可以看到一个完整的工程目录. 但是此时的情况就像是在idea编辑的java项目一样,仅仅是把代码写好了,或

JAVA微信开发之weixin4j入门视频

weixin4j入门公开课视频 第一课<weixin4j入门视频-新手接入> 视频下载地址: http://pan.baidu.com/s/1o63MdPw 第二课<weixin4j入门视频-接收消息> 视频下载地址: http://pan.baidu.com/s/1i3qzbgT 第三课<weixin4j入门视频-回复消息> 视频下载地址: http://pan.baidu.com/s/1i3qzbgT 视频中的项目源码,请到官方QQ群众获取! 2015年5月26日-

Cocos2d-x游戏开发之Lua学习笔记

下载链接 什么是Cocos2d-x 一个开源的移动2D游戏框架,MIT许可证下发布. 可以利用C++.Lua及Javascript来进行部署. 跨平台:iOS,Android,Blackberry,Tizen等. 使用Cocos开发的应用 版权声明:本文原创,转载请注明出处:http://blog.csdn.net/zhoumushui

Cocos2d-x游戏开发之luaproject创建

操作系统:OS X 10.85 Cocos2d-x 版本号: 2.2.1 使用Cocos2d-x 能够创建luaproject,已经使用cpp创建的project也能够继承lua进行开发,可是lua并不支持macproject(由于一些框架的问题). 支持的project文件例如以下: 全部使用创建projectcreate.py language 为cpp的project,后集成lua及其工具的时候,要注意这一点. 撒 如今进入cocos2d-x 文件夹之下,通过cd 进入文件文件夹 进入之后

Cocos2d-x 3.x游戏开发之旅

Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x 3.x游戏开发之旅>是<Cocos2d-x游戏开发之旅>的升级版,修改了Cocos2d-x 2.0版进阶到3.0版后的一些内容,新增了对CocoStudio.UI编辑器.Cocos2d-x 3.x新特性以及网络方面的知识点.主要介绍常用的API使用方式:如何通过官方Demo获取更多关于Coc