Cocos2dx游戏开发笔记21:动手学习《Flappy Bird》



懒骨头(http://blog.youkuaiyun.com/iamlazybone QQ:124774397 )

《Flappy Bird》


关于这个游戏骨头不多说了

直接开始学习吧(山寨不好听)

正好前段时间看了几个DEMO拿这个游戏练练手

开搞!


报环境:

vs2013+cocos2dx3.0beta2

首先下载apk,找到资源文件,裁剪几个图片:


用脚本新建一个空的Cocos2dx项目

新建一个Scene类

  1. #include "cocos2d.h"  
  2. #include "Obstacle.h"  
  3.   
  4. class FlyBirdGame :public cocos2d::Layer  
  5. {  
  6. public:  
  7.     static cocos2d::Scene* createScene();  
  8.     virtual bool init();  
  9.     CREATE_FUNC(FlyBirdGame);  
  10.     void initUI();  
  11.     void gameStart(Object* pSender);   
  12.     void update(float time);  
  13.     Obstacle* obstacle;  
  14. };  
#include "cocos2d.h"
#include "Obstacle.h"

class FlyBirdGame :public cocos2d::Layer
{
public:
	static cocos2d::Scene* createScene();
	virtual bool init();
	CREATE_FUNC(FlyBirdGame);
	void initUI();
	void gameStart(Object* pSender); 
	void update(float time);
	Obstacle* obstacle;
};

  1. #include "cocos2d.h"  
  2. #include "FlyBirdGame.h"  
  3. #include "resource.h";   
  4.   
  5. USING_NS_CC;  
  6.   
  7. Scene* FlyBirdGame::createScene()  
  8. {  
  9.     auto scene = Scene::create();  
  10.     auto layer = FlyBirdGame::create();  
  11.     scene->addChild(layer);  
  12.     return scene;  
  13. }  
  14.   
  15. bool FlyBirdGame::init()  
  16. {  
  17.     if (!Layer::init())  
  18.     {  
  19.         return false;  
  20.     }  
  21.     initUI();  
  22.     return true;  
  23. }  
#include "cocos2d.h"
#include "FlyBirdGame.h"
#include "resource.h"; 

USING_NS_CC;

Scene* FlyBirdGame::createScene()
{
	auto scene = Scene::create();
	auto layer = FlyBirdGame::create();
	scene->addChild(layer);
	return scene;
}

bool FlyBirdGame::init()
{
	if (!Layer::init())
	{
		return false;
	}
	initUI();
	return true;
}

initUI里是一些UI初始化方法:

  1.        // win size  
  2. auto winSize = Director::getInstance()->getVisibleSize();  
  3.   
  4. // game bg  
  5. auto bg = Sprite::create(bird_bg);  
  6. bg->setPosition(winSize.width / 2, winSize.height / 2);  
  7. bg->setScale(winSize.width / bg->getContentSize().width, winSize.height / bg->getContentSize().height);  
  8. this->addChild(bg);  
  9.   
  10. // start btn  
  11. auto startBtn = MenuItemImage::create(bird_start_btn, bird_start_btn_pressed, CC_CALLBACK_1(FlyBirdGame::gameStart, this));  
  12. auto menu = Menu::create(startBtn, NULL);  
  13. menu->setTag(100);  
  14. this->addChild(menu);  
  15.   
  16. // hero  
  17. auto hero = Sprite::create(bird_hero);  
  18. hero->setPosition(winSize.width / 5, winSize.height*0.8);  
  19. hero->setVisible(false);  
  20. hero->setTag(200);  
  21. this->addChild(hero);  
        // win size
	auto winSize = Director::getInstance()->getVisibleSize();

	// game bg
	auto bg = Sprite::create(bird_bg);
	bg->setPosition(winSize.width / 2, winSize.height / 2);
	bg->setScale(winSize.width / bg->getContentSize().width, winSize.height / bg->getContentSize().height);
	this->addChild(bg);

	// start btn
	auto startBtn = MenuItemImage::create(bird_start_btn, bird_start_btn_pressed, CC_CALLBACK_1(FlyBirdGame::gameStart, this));
	auto menu = Menu::create(startBtn, NULL);
	menu->setTag(100);
	this->addChild(menu);

	// hero
	auto hero = Sprite::create(bird_hero);
	hero->setPosition(winSize.width / 5, winSize.height*0.8);
	hero->setVisible(false);
	hero->setTag(200);
	this->addChild(hero);

开始游戏按钮绑定的gameStart方法:

  1. void FlyBirdGame::gameStart(Object* pSender)  
  2. {  
  3.     auto btn = this->getChildByTag(100);  
  4.     btn->setVisible(false);  
  5.     auto hero = this->getChildByTag(200);   
  6.     Size win = Director::getInstance()->getWinSize();  
  7.     obstacle->gameStart = true;  
  8. }  
void FlyBirdGame::gameStart(Object* pSender)
{
	auto btn = this->getChildByTag(100);
	btn->setVisible(false);
	auto hero = this->getChildByTag(200); 
	Size win = Director::getInstance()->getWinSize();
	obstacle->gameStart = true;
}

隐藏开始按钮,显示小鸟,水管开始移动

还有更新方法:

  1. scheduleUpdate();  
  2. void FlyBirdGame::update(float time)  
  3. {  
  4.     obstacle->update();  
  5. }  
scheduleUpdate();
void FlyBirdGame::update(float time)
{
	obstacle->update();
}

=======================================

水管类:Obstacle.cpp

update方法里判断游戏是游戏是否开始


  1. void Obstacle::update()  
  2. {  
  3.     if (gameStart == false)  
  4.         return;  
  5.     addCount++;  
  6.     if (addCount == 60)  
  7.     {  
  8.         addOne(0);  
  9.         addCount = 0;  
  10.     }  
  11.     for (int i = obstacleList->count() - 1; i >= 0; i--)  
  12.     {  
  13.         auto s = (Sprite*)obstacleList->getObjectAtIndex(i);  
  14.         s->setPositionX(s->getPositionX() - 3);  
  15.         if (s->getPositionX() < -s->getContentSize().width / 2)  
  16.         {  
  17.             obstacleList->removeObjectAtIndex(i);  
  18.             this->removeChild(s);  
  19.         }  
  20.     }  
  21. }  
void Obstacle::update()
{
	if (gameStart == false)
		return;
	addCount++;
	if (addCount == 60)
	{
		addOne(0);
		addCount = 0;
	}
	for (int i = obstacleList->count() - 1; i >= 0; i--)
	{
		auto s = (Sprite*)obstacleList->getObjectAtIndex(i);
		s->setPositionX(s->getPositionX() - 3);
		if (s->getPositionX() < -s->getContentSize().width / 2)
		{
			obstacleList->removeObjectAtIndex(i);
			this->removeChild(s);
		}
	}
}
水管类的更新方法里,每60帧(1秒)添加一对水管

并且遍历水管列表

出边界的化销毁

接下来是addOne方法:添加水管方法:

  1. void Obstacle::addOne(int offsetX)  
  2. {  
  3.     Size size = Director::getInstance()->getWinSize();  
  4.     auto sprite = Sprite::create(bird_obstacle_up);  
  5.     Size spriteSize = sprite->getContentSize();  
  6.     obstacleList->addObject(sprite);  
  7.     this->addChild(sprite);  
  8.     auto sprite2 = Sprite::create(bird_obstacle_down);  
  9.     Size spriteSize2 = sprite->getContentSize();  
  10.     obstacleList->addObject(sprite2);  
  11.     this->addChild(sprite2);  
  12.     // set positon  
  13.     int maxUpY = size.height + spriteSize.height / 4;  
  14.     int minUpY = size.height - spriteSize.height / 4;  
  15.     int y1 = CCRANDOM_0_1()*(maxUpY - minUpY) + minUpY;  
  16.     int maxDownY = spriteSize.height / 4;  
  17.     int minDownY = -spriteSize.height / 4;  
  18.     int y2 = CCRANDOM_0_1()*(maxDownY - minDownY) + minDownY;  
  19.     if (y1 - y2 - spriteSize.height < 160)  
  20.     {  
  21.         y2 = y1 - spriteSize.height - 160;  
  22.     }  
  23.     sprite->setPosition(ccp(size.width + spriteSize.width / 2 + offsetX, y1));  
  24.     sprite2->setPosition(ccp(size.width + spriteSize2.width / 2 + offsetX, y2));  
  25. }  
void Obstacle::addOne(int offsetX)
{
	Size size = Director::getInstance()->getWinSize();
	auto sprite = Sprite::create(bird_obstacle_up);
	Size spriteSize = sprite->getContentSize();
	obstacleList->addObject(sprite);
	this->addChild(sprite);
	auto sprite2 = Sprite::create(bird_obstacle_down);
	Size spriteSize2 = sprite->getContentSize();
	obstacleList->addObject(sprite2);
	this->addChild(sprite2);
	// set positon
	int maxUpY = size.height + spriteSize.height / 4;
	int minUpY = size.height - spriteSize.height / 4;
	int y1 = CCRANDOM_0_1()*(maxUpY - minUpY) + minUpY;
	int maxDownY = spriteSize.height / 4;
	int minDownY = -spriteSize.height / 4;
	int y2 = CCRANDOM_0_1()*(maxDownY - minDownY) + minDownY;
	if (y1 - y2 - spriteSize.height < 160)
	{
		y2 = y1 - spriteSize.height - 160;
	}
	sprite->setPosition(ccp(size.width + spriteSize.width / 2 + offsetX, y1));
	sprite2->setPosition(ccp(size.width + spriteSize2.width / 2 + offsetX, y2));
}
这段代码比较凌乱,就是找到水管上下位置的范围

然后随机一下,并且保证上下连个水管有个最小的距离

效果如下:



=============================

此时的游戏还没触摸和碰撞逻辑

马上添加:(刚才抽空玩了把魔方:五阶的我只能搞定一个面,虽然有官方规律但是那样好像比的是记忆力)

听说cocos2dx3.0的事件监听方式改变了

先在FlyBirdGame.h里声明俩方法:

  1. void onTouchesEnded(const vector<Touch*>& touches, Event* event);  
  2. void onTouchesBegan(const vector<Touch*>& touches, Event* event);  
	void onTouchesEnded(const vector<Touch*>& touches, Event* event);
	void onTouchesBegan(const vector<Touch*>& touches, Event* event);

在cpp文件的初始化里绑定事件:

  1. // touch  
  2. auto dispatcher = Director::getInstance()->getEventDispatcher();  
  3. auto listener = EventListenerTouchAllAtOnce::create();  
  4. listener->onTouchesEnded = CC_CALLBACK_2(FlyBirdGame::onTouchesEnded, this);  
  5. listener->onTouchesBegan = CC_CALLBACK_2(FlyBirdGame::onTouchesBegan, this);  
  6. dispatcher->addEventListenerWithSceneGraphPriority(listener, this);  
	// touch
	auto dispatcher = Director::getInstance()->getEventDispatcher();
	auto listener = EventListenerTouchAllAtOnce::create();
	listener->onTouchesEnded = CC_CALLBACK_2(FlyBirdGame::onTouchesEnded, this);
	listener->onTouchesBegan = CC_CALLBACK_2(FlyBirdGame::onTouchesBegan, this);
	dispatcher->addEventListenerWithSceneGraphPriority(listener, this);

在两个事件方法里改变标记位,在小鸟的update方法里根据这个标记位来改变高度

(哲哲喊我休息了,先到这吧,待续。。。)

=============================

碰撞检测:

记得cocos2dx3.0以前都是自己写一个根据CCSprite获取CCRect的方法

现在直接用 Sprite的getBoundingBox()方法

不过如果有特殊需求还得自己写,比如想扩大或缩小碰撞区域

在FlyBirdGame.cpp文件里

update算主逻辑方法:

  1. void FlyBirdGame::update(float time)  
  2. {  
  3.     auto winSize = Director::getInstance()->getVisibleSize();  
  4.     auto hero = this->getChildByTag(TAG_HERO);  
  5.     Rect rHero = ((Sprite*)hero)->getBoundingBox();  
  6.   
  7.     switch (GAME_STATUS)  
  8.     {  
  9.     case GAME_STATUS_PLAYING:  
  10.         obstacle->update();  
  11.         // update bird positionY  
  12.         if (isFlying&&hero->getPositionY() < winSize.height)  
  13.         {  
  14.             hero->setPositionY(hero->getPositionY() + v);  
  15.         }  
  16.         else if (hero->getPositionY()>0)  
  17.         {  
  18.             hero->setPositionY(hero->getPositionY() - v);  
  19.         }  
  20.         //check collision  
  21.         for (int i = 0; i < obstacle->obstacleList->count(); i++)  
  22.         {  
  23.             Sprite* obstacleRect = (Sprite*)obstacle->obstacleList->getObjectAtIndex(i);  
  24.             bool pia = rHero.intersectsRect(obstacleRect->getBoundingBox());  
  25.             if (pia == true)  
  26.             {  
  27.                 GAME_STATUS = GAME_STATUS_GAME_OVER;  
  28.                 break;  
  29.             }  
  30.         }  
  31.         break;  
  32.     case GAME_STATUS_GAME_OVER:  
  33.         CCLog("over");  
  34.         this->getChildByTag(TAG_OVER)->setVisible(true);  
  35.         break;  
  36.     case GAME_STATUS_RESTART:  
  37.         //reset game  
  38. <span style="white-space: pre;">        </span>obstacle->removeAllChildren();  
  39.         obstacle->obstacleList->removeAllObjects();  
  40.         // reset hero  
  41.         hero->setPosition(winSize.width / 5, winSize.height*0.8);  
  42.         // show btn  
  43.         auto btn = this->getChildByTag(TAG_START_BTN);  
  44.         btn->setVisible(true);  
  45.         // show logo  
  46.         auto logo = this->getChildByTag(TAG_LOGO);  
  47.         logo->setVisible(true);  
  48.         break;  
  49.     }  
  50. }  
void FlyBirdGame::update(float time)
{
	auto winSize = Director::getInstance()->getVisibleSize();
	auto hero = this->getChildByTag(TAG_HERO);
	Rect rHero = ((Sprite*)hero)->getBoundingBox();

	switch (GAME_STATUS)
	{
	case GAME_STATUS_PLAYING:
		obstacle->update();
		// update bird positionY
		if (isFlying&&hero->getPositionY() < winSize.height)
		{
			hero->setPositionY(hero->getPositionY() + v);
		}
		else if (hero->getPositionY()>0)
		{
			hero->setPositionY(hero->getPositionY() - v);
		}
		//check collision
		for (int i = 0; i < obstacle->obstacleList->count(); i++)
		{
			Sprite* obstacleRect = (Sprite*)obstacle->obstacleList->getObjectAtIndex(i);
			bool pia = rHero.intersectsRect(obstacleRect->getBoundingBox());
			if (pia == true)
			{
				GAME_STATUS = GAME_STATUS_GAME_OVER;
				break;
			}
		}
		break;
	case GAME_STATUS_GAME_OVER:
		CCLog("over");
		this->getChildByTag(TAG_OVER)->setVisible(true);
		break;
	case GAME_STATUS_RESTART:
		//reset game
		obstacle->removeAllChildren();
		obstacle->obstacleList->removeAllObjects();
		// reset hero
		hero->setPosition(winSize.width / 5, winSize.height*0.8);
		// show btn
		auto btn = this->getChildByTag(TAG_START_BTN);
		btn->setVisible(true);
		// show logo
		auto logo = this->getChildByTag(TAG_LOGO);
		logo->setVisible(true);
		break;
	}
}

根据游戏状态处理。

碰撞检测方法:intersectsRect

bool pia = rHero.intersectsRect(obstacleRect->getBoundingBox());
if (pia == true)
{
GAME_STATUS = GAME_STATUS_GAME_OVER;
break;
}


游戏状态定义在 resource.h 文件里

  1. static const int GAME_STATUS_START = 10;  
  2. static const int GAME_STATUS_PLAYING = 20;  
  3. static const int GAME_STATUS_GAME_OVER = 30;  
  4. static const int GAME_STATUS_RESTART = 40;  
static const int GAME_STATUS_START = 10;
static const int GAME_STATUS_PLAYING = 20;
static const int GAME_STATUS_GAME_OVER = 30;
static const int GAME_STATUS_RESTART = 40;


整个游戏流程已经通了:开始,游戏,结束,重新开始

接下来可以优化很多很多东西

(1)普通用户端(全平台) 音乐播放核心体验: 个性化首页:基于 “听歌历史 + 收藏偏好” 展示 “推荐歌单(每日 30 首)、新歌速递、相似曲风推荐”,支持按 “场景(通勤 / 学习 / 运动)” 切换推荐维度。 播放页功能:支持 “无损音质切换、倍速播放(0.5x-2.0x)、定时关闭、歌词逐句滚动”,提供 “沉浸式全屏模式”(隐藏冗余控件,突出歌词与专辑封面)。 多端同步:自动同步 “播放进度、收藏列表、歌单” 至所有登录设备(如手机暂停后,电脑端打开可继续播放)。 音乐发现与管理: 智能搜索:支持 “歌曲名 / 歌手 / 歌词片段” 搜索,提供 “模糊匹配(如输入‘晴天’联想‘周杰伦 - 晴天’)、热门搜索词推荐”,结果按 “热度 / 匹配度” 排序。 歌单管理:创建 “公开 / 私有 / 加密” 歌单,支持 “批量添加歌曲、拖拽排序、一键分享到社交平台”,系统自动生成 “歌单封面(基于歌曲风格配色)”。 音乐分类浏览:按 “曲风(流行 / 摇滚 / 古典)、语言(国语 / 英语 / 日语)、年代(80 后经典 / 2023 新歌)” 分层浏览,每个分类页展示 “TOP50 榜单”。 社交互动功能: 动态广场:查看 “关注的用户 / 音乐人发布的动态(如‘分享新歌感受’)、好友正在听的歌曲”,支持 “点赞 / 评论 / 转发”,可直接点击动态中的歌曲播放。 听歌排行:个人页展示 “本周听歌 TOP10、累计听歌时长”,平台定期生成 “全球 / 好友榜”(如 “好友中你本周听歌时长排名第 3”)。 音乐圈:加入 “特定曲风圈子(如‘古典音乐爱好者’)”,参与 “话题讨论(如‘你心中最经典的钢琴曲’)、线上歌单共创”。 (2)音乐人端(创作者中心) 作品管理: 音乐上传:支持 “无损音频(FLAC/WAV)+ 歌词文件(LRC)+ 专辑封面” 上传,填写 “歌曲信息
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值