cocos2d-x 3.2 仿微信的大战飞机

cocos2dx的基础已经学有一段时间了,为了巩固和练手,选择了做仿照微信的打飞机。最初玩这个游戏时,感觉挺休闲也挺有感觉的,虽说是个黑白简单的打飞机,整个功能恰恰适合我现在的这个对cocos2dx的掌握程度。首先要分析一下打飞机包含的整个内容,我这个肯定是本地的了,微信上的是联网的,需要读好友的相关信息。我按功能分了六个阶段来做:初始化UI及动画、滚屏和战机移动、生成敌机、碰撞检测、空降物、游戏结束等一些细节。

生成项目

开发环境:win7 + vs2013 + cocos2d-x 3.2(应该是目前最新版本了)

 win7上装vs2013,IE需要升级到10。。。,cocos2d-x 3.2生成的sln解决方案是vs2012的,用vs2013打开提示需要升级,直接点确定,不会影响项目。是可以去改某个文件里的版本信息的就可以直接生成vs2013,懒的去折腾了。

cocos2d-x生成项目,我自己做了个bat文件,每次改一下项目名就可以了。

安装Python 2.7(3.0的听说有问题)

 生成项目的脚本:

@echo off
E:
cd E:\cocos2d-x\cocos2d-x_version\cocos2d-x-3.2\cocos2d-x-3.2\tools\cocos2d-console\bin
cocos new -p net.cocoagame -l cpp -d E:\cocos2d-x\qhg Planes
pause
 在cmd先进入cocos2d-x 3.2的bin目录下,然后执行命令:cocos new -p 命名空间 -l cpp -d 项目所在目录 项目名

罗嗦这么多,开始进入编码。

初始化UI及动画

首先是工具类:MyUtility.h

#ifndef  _MY_UTILITY_H_
#define  _MY_UTILITY_H_

#include "cocos2d.h"

using namespace std;

#define S_STARTGME "开始游戏"
#define S_RESUMEGAME "继续"
#define S_NEWGAME "重新开始"
#define S_SCORE "飞机大战分数"
#define S_EXIT "退出游戏"

class MyUtility 
{
public:
	static string getCurrentTime();

	static string gbk_2_utf8(const string text);

	static float random_0_1(const float start, const float end);

	/**
	* 返回MenuItemToggle指针对象
	*
	* @param spriteFrameNameNormal		默认图片
	* @param spriteFrameNameSelected	选中图片
	* @param callback					回调函数
	* @param xx							x
	* @param yy							y
	*
	*/
	static cocos2d::MenuItemToggle* initToggleMenuItem(const cocos2d::ccMenuCallback& callback, const std::string& spriteFrameNameNormal, const std::string& spriteFrameNameSelected, const float xx, const float yy);

	static cocos2d::MenuItemLabel* createMenuItem(const cocos2d::ccMenuCallback& callback, const std::string str, const float xx, const float yy);
	static cocos2d::MenuItemLabel* createMenuItem(const cocos2d::ccMenuCallback& callback, const std::string str);

	static cocos2d::LabelBMFont* createBMF(const std::string str, const cocos2d::Vec2& position /* = Vec2::ZERO */);
};

#endif // _MY_UTILITY_H_

MyUtility.cpp

#include "MyUtility.h"
USING_NS_CC;

//获取当前系统时间
string MyUtility::getCurrentTime()
{
	time_t t;
	time(&t);
	char tmp[64];
	strftime(tmp, sizeof(tmp), "%Y-%m-%d %X", localtime((&t)));

	string timeStr = tmp;
	return timeStr;
}


//在Win32平台下,将GBK编码转换为UTF-8
string MyUtility::gbk_2_utf8(const string text)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
	//采用Lambda表达式,将string转换成wstring
	wstring tes = [=]() {
		setlocale(LC_ALL, "chs");
		const char* _Source = text.c_str();
		size_t _Dsize = text.size() + 1;
		wchar_t *_Dest = new wchar_t[_Dsize];
		wmemset(_Dest, 0, _Dsize);
		mbstowcs(_Dest, _Source, _Dsize);
		std::wstring result = _Dest;
		delete[]_Dest;
		setlocale(LC_ALL, "C");
		return result;
	}();

	int asciSize = WideCharToMultiByte(CP_UTF8, 0, tes.c_str(), tes.size(), NULL, 0, NULL, NULL);
	if (asciSize == ERROR_NO_UNICODE_TRANSLATION || asciSize == 0)
	{
		return string();
	}

	char *resultString = new char[asciSize];
	int conveResult = WideCharToMultiByte(CP_UTF8, 0, tes.c_str(), tes.size(), resultString, asciSize, NULL, NULL);
	if (conveResult != asciSize)
	{
		return string();
	}
	string buffer = "";
	buffer.append(resultString, asciSize);

	delete[] resultString;
	return buffer;

#else
	return text;
#endif

}

float MyUtility::random_0_1(const float start, const float end)
{
	//struct timeval tv;
	//cocos2d::gettimeofday(&tv, NULL);
	//unsigned int tsrans = tv.tv_sec * 1000 + tv.tv_usec / 1000;
	//srand(tsrans);

	return CCRANDOM_0_1()*(end - start) + start;
}

MenuItemToggle* MyUtility::initToggleMenuItem(const ccMenuCallback& callback, const std::string& spriteFrameNameNormal, const std::string& spriteFrameNameSelected, const float xx, const float yy)
{
	auto normalSprite = Sprite::createWithSpriteFrameName(spriteFrameNameNormal);
	auto selectedSprite = Sprite::createWithSpriteFrameName(spriteFrameNameSelected);
	auto normalToggleMenuItem = MenuItemSprite::create(normalSprite, normalSprite);
	auto selectedToggleMenuItem = MenuItemSprite::create(selectedSprite, selectedSprite);
	MenuItemToggle* toggleMenuItem = MenuItemToggle::createWithCallback(
		callback,
		normalToggleMenuItem,
		selectedToggleMenuItem,
		NULL
		);
	toggleMenuItem->setPosition(Vec2(xx, yy));
	return toggleMenuItem;
}

MenuItemLabel* MyUtility::createMenuItem(const ccMenuCallback& callback, const std::string str, const float xx, const float yy)
{

	auto label = Label::createWithBMFont("fonts/fnt.fnt", MyUtility::gbk_2_utf8(str));
	auto menuLabel = MenuItemLabel::create(
		label,
		callback
		);
	menuLabel->setPosition(Vec2(xx, yy));
	return menuLabel;
}

MenuItemLabel* MyUtility::createMenuItem(const ccMenuCallback& callback, const std::string str)
{

	auto label = Label::createWithBMFont("fonts/fnt.fnt", MyUtility::gbk_2_utf8(str));
	auto menuLabel = MenuItemLabel::create(
		label,
		callback
		);
	return menuLabel;
}

LabelBMFont* MyUtility::createBMF(const std::string str, const Vec2& position)
{
	LabelBMFont* txtLabel = LabelBMFont::create(MyUtility::gbk_2_utf8(str), "fonts/fnt.fnt");
	txtLabel->setPosition(position);

	return txtLabel;
}

这种封装意义不是很大,只是有多处需要调用,才做了个封装函数。

MenuItemToggle

制作按钮的一种方式,比如在游戏里要设置背景音乐的开关,就可以使用这种方式,与MenuItemSprite还是有很大的差别的。MenuItemSprite是显示默认和点下时的效果,而MenuItemToggle则可以两种不同的图片效果切换。比如打开背景音乐就可以由“OFF”切换成“ON”。需要放到Menu里使用。


MenuItemLabel

字符按钮,可以通过fnt纹理集文件来自定义各种好看的文字按钮,也是解决乱码的好办法。


LabelBMFont

这个与MenuItemLabel的区别是一个有回调函数,一个没有回调函数,通过fnt显示动态文字,比如游戏分数。


第一个layer依然沿用HelloWorldScene

HelloWorldScene.h

#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__

#include "cocos2d.h"



class HelloWorld : public cocos2d::Layer
{
public:
    // there's no 'id' in cpp, so we recommend returning the class instance pointer
    static cocos2d::Scene* createScene();

    // Here's a difference. Method 'init' in cocos2d-x returns bool, instead of returning 'id' in cocos2d-iphone
    virtual bool init();  
    
    // implement the "static create()" method manually
    CREATE_FUNC(HelloWorld);

	void startGameCallback(cocos2d::Ref* pSender);

	void exitGameCallback(cocos2d::Ref* pSender);

};

#endif // __HELLOWORLD_SCENE_H__

这里要注意的是,回调函数必须带有Ref的指针对象,做这第一个项目练手,经常把这个给忘记了,导致编译出错还愣了半天。。

第一个layer主要是一个界面,上面有两个按钮“开始游戏”和“退出游戏”。


HelloWorldScene.cpp

#include "HelloWorldScene.h"
#include "GameLayer.h"
#include "MyUtility.h"

USING_NS_CC;

Scene* HelloWorld::createScene()
{
	// 'scene' is an autorelease object
	auto scene = Scene::create();

	// 'layer' is an autorelease object
	auto layer = HelloWorld::create();

	// add layer as a child to scene
	scene->addChild(layer);

	// return the scene
	return scene;
}

// on "init" you need to initialize your instance
bool HelloWorld::init()
{
	//////////////////////////////
	// 1. super init first
	if (!Layer::init())
	{
		return false;
	}

	Size visibleSize = Director::getInstance()->getVisibleSize();

	SpriteFrameCache::getInstance()->addSpriteFramesWithFile("gameArts-hd.plist");

	auto bg = Sprite::createWithSpriteFrameName("background_2.png");
	bg->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2));
	this->addChild(bg, 0);

	auto logoSprite = Sprite::create("BurstAircraftLogo.png");
	logoSprite->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 + 100));
	this->addChild(logoSprite, 0);

	auto startMenu = MyUtility::createMenuItem(
		CC_CALLBACK_1(HelloWorld::startGameCallback, this),
		S_STARTGME,
		visibleSize.width / 2, 
		visibleSize.height / 2.5
		);

	auto exitMenu = MyUtility::createMenuItem(
		CC_CALLBACK_1(HelloWorld::exitGameCallback, this),
		S_EXIT,
		visibleSize.width / 2,
		visibleSize.height / 3.5
		);

	Menu* mn = Menu::create(startMenu, exitMenu, NULL);
	mn->setPosition(Vec2::ZERO);
	this->addChild(mn);

	return true;
}

void HelloWorld::startGameCallback(Ref* pSender)
{
	auto sc = GameLayer::createScene();
	auto tsc = TransitionFade::create(1.0f, sc);
	Director::getInstance()->replaceScene(tsc);
}

//退出游戏
void HelloWorld::exitGameCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
	MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.", "Alert");
	return;
#endif

	Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
	exit(0);
#endif
}

将plist文件放到缓存里以便其他层可以用。添加背景精灵、logo精灵、两个按钮精灵。

效果图:

cocos2d-x 3.2 仿微信的大战飞机_第1张图片


GameLayer.cpp

// on "init" you need to initialize your instance
bool GameLayer::init()
{
	// 1. super init first
	if (!Layer::init())
	{
		return false;
	}

	initData();

	resetBullet();

	this->scheduleUpdate();

	//单点触摸
	this->setTouchEnabled(true);
	this->setTouchMode(Touch::DispatchMode::ONE_BY_ONE);

	return true;
}

//初始化背景、战机、战机飞行动画、子弹
void GameLayer::initData()
{
	visibleSize = Director::getInstance()->getVisibleSize();
	bgHeight = visibleSize.height;

	//分数
	scoreLabel = MyUtility::createBMF("000", Vec2(90, visibleSize.height - 50));
	this->addChild(scoreLabel, 4);

	bg1 = Sprite::createWithSpriteFrameName("background_2.png");
	bg1->setAnchorPoint(Vec2(0.5, 0));
	bg1->setPosition(Vec2(visibleSize.width / 2, 0));
	this->addChild(bg1, 0);

	bg2 = Sprite::createWithSpriteFrameName("background_2.png");
	bg2->setAnchorPoint(Vec2(0.5, 0));
	bg2->setPosition(Vec2(visibleSize.width / 2, visibleSize.height));
	this->addChild(bg2, 0);

	//暂停
	pauseToggleMenuItem = MyUtility::initToggleMenuItem(
		CC_CALLBACK_1(GameLayer::pauseGameCallback, this),
		"game_pause.png",
		"game_pause_hl.png",
		40,
		visibleSize.height - 50
		);

	//炸弹
	auto bombSprite = Sprite::createWithSpriteFrameName("bomb.png");
	auto toggleMenuItem = MenuItemSprite::create(bombSprite, bombSprite);
	useBombToggleMenuItem = MenuItemToggle::createWithCallback(
		CC_CALLBACK_1(GameLayer::useBombCallback, this),
		toggleMenuItem,
		toggleMenuItem,
		NULL
		);
	useBombToggleMenuItem->setPosition(Vec2(bombSprite->getContentSize().width / 2, bombSprite->getContentSize().height / 2));
	useBombToggleMenuItem->setVisible(false);

	//炸弹数量
	bombNumLabel = MyUtility::createMenuItem(CC_CALLBACK_1(GameLayer::useBombCallback, this), "游戏");
	bombNumLabel->setVisible(false);

	auto bombMenu = Menu::create(pauseToggleMenuItem, useBombToggleMenuItem, bombNumLabel, NULL);
	bombMenu->setPosition(Vec2::ZERO);
	this->addChild(bombMenu, 4);

	player = Sprite::createWithSpriteFrameName("hero_fly_1.png");
	player->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 2 - 200));
	this->addChild(player, 3);

	//战机动画
	Animation* animation = Animation::create();
	for (int i = 1; i <= PLAN_COUNT; i++)
	{
		__String* str = __String::createWithFormat("hero_fly_%d.png", i);
		SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str->getCString());
		animation->addSpriteFrame(frame);
	}

	//战机大小
	playerSize = Vec2(player->getContentSize().width, player->getContentSize().height);
	//每个帧的间隔
	animation->setDelayPerUnit(0.1f);
	Animate* animate = Animate::create(animation);
	player->runAction(RepeatForever::create(animate));

	madeBullet();
}
//子弹
void GameLayer::madeBullet()
{
	bullet = Sprite::createWithSpriteFrameName(!isBigBulletTime ? "bullet1.png" : "bullet2.png");

	this->addChild(bullet);
}
//重置子弹
void GameLayer::resetBullet()
{
	//根据双子弹状态变换子弹
	if (isChangeBullet)
	{
		//从父节点中删除当前子节点 如果Cleanup为true则删除当前节点的所有动作
		bullet->removeFromParentAndCleanup(false);
		//重新制造子弹
		madeBullet();
		//停止变换子弹
		isChangeBullet = false;
	}

	//子弹速度
	bulletSpeed = (visibleSize.height - (player->getPosition().y + 50)) / 10;

	if (10 > bulletSpeed)
	{
		bulletSpeed = 10;
	}
	//子弹初始位置
	bullet->setPosition(Vec2(player->getPosition().x, player->getPosition().y + 50));
}

初始化了背景、左上角的暂停、分数、左下角的炸弹、炸弹数量、战机、战机动画、子弹

背景添加了两个方便做滚屏。

效果图::

cocos2d-x 3.2 仿微信的大战飞机_第2张图片

左下角的炸弹和炸弹数量刚开始是隐藏的。


滚屏和战机移动

滚屏需要放在update里,所以在初始化里少不了this->scheduleUpdate();

void GameLayer::update(float dt)
{
	if (!isGamePause)
	{
		scrollBg();

		fire();

		addEnemyPlane();

		moveEnemyPlane();

		collisionDetection();

		addChangeBullet();

		bigBulletLastTime();
	}
}

滚屏主要函数:scrollBg();

//滚屏
void GameLayer::scrollBg()
{
	if (!isGameOver)
	{
		if (0 >= bgHeight)
		{
			bgHeight = visibleSize.height;
		}
		bg1->setPosition(Vec2(bg1->getPosition().x, bgHeight - visibleSize.height));
		bg2->setPosition(Vec2(bg2->getPosition().x, bgHeight - 1));

		bgHeight--;
	}
}

主要是利用两个背景精灵做Position高度的改变,通过一个变量bgHeight递减


战机的移动,我在初始化里直接设置了单点触摸,因此这里不需要创建移动监听,直接重写onTouchMoved

重写onTouchMoved时,要保证onTouchBegan返回的是true,isGameOver游戏是否结束默认是false,

最初我做了触摸检测,只能触摸到战机时才能移动战机,后来回头看了下微信上的是只要在屏幕上移动就可以移动战机,毕竟这个游戏只有一个战机操控。

bool GameLayer::onTouchBegan(Touch* touch, Event* event)
{
	//检测触摸的屏幕点是否在一个范围内
	//Vec2 nodeWorld = touch->getLocation();
	//Vec2 nodeLocal = player->convertToNodeSpace(nodeWorld);

	//Size planeSize = player->getContentSize();
	//Rect planeRect = Rect(0, 0, planeSize.width, planeSize.height);
	//if (planeRect.containsPoint(nodeLocal))
	//{
	//	return true;
	//}
	//return false;

	return !isGameOver;
}

//移动战机
void GameLayer::onTouchMoved(Touch* touch, Event* event)
{
	if (!isGameOver && !isGamePause)
	{
		//战机位置
		Vec2 playerPos = player->getPosition();
		//偏移量
		Vec2 diff = touch->getDelta();
		bulletWidthSpeed = diff.x;
		//战机偏移后的位置
		Vec2 newPos = Vec2(playerPos.x + diff.x, playerPos.y + diff.y);

		/*保证战机偏移后位置还在屏幕内*/
		//左边界
		if (newPos.x < playerSize.width / 2)
		{
			newPos.x = playerSize.width / 2;
		}
		else if (newPos.x > visibleSize.width - playerSize.width / 2)//右边界
		{
			newPos.x = visibleSize.width - playerSize.width / 2;
		}

		//下边界
		if (newPos.y < playerSize.height / 2)
		{
			newPos.y = playerSize.height / 2;
		}
		else if (newPos.y > visibleSize.height - playerSize.height / 2 - 20)//上边界
		{
			newPos.y = visibleSize.height - playerSize.height / 2 - 20;
		}

		player->setPosition(newPos);
	}
}

下面是子弹的动画,原理是在update里子弹做高速度bulletSpeed的递加

在update中主要是fire();函数

//开火
void GameLayer::fire()
{
	if (!isGameOver)
	{
		//子弹加速度
		bullet->setPosition(Vec2(bullet->getPosition().x + bulletWidthSpeed / 1.5, bullet->getPosition().y + bulletSpeed));

		//达到屏幕高度重置子弹到初始位置
		if (bullet->getPosition().y > visibleSize.height + 10)
		{
			this->resetBullet();
		}
	}
}

这里我做了两个速度变量,宽bulletWidthSpeed和高bulletSpeed,以便使子弹随着战机移动时产生漂移的感觉。


生成敌机

EnemyPlane.cpp

//通过参数值生成敌机对象
EnemyPlane* EnemyPlane::createEnemyPlane(const std::string& spriteFrameName, const EnemyPlaneType planeType, const Size visibleSize, int hp, float speed)
{
	auto pSprite = new EnemyPlane();
	if (pSprite && pSprite->initWithSpriteFrameName(spriteFrameName))
	{
		/*方法内新建一个对象,然后返回对象时,按照谁拥有谁施放。
		*对象是在方法内部新建的,方法退出前不再拥有,所以要在方法退出前释放。
		但是又要在退出时返回该对象,所以使用autorelease*/
		pSprite->autorelease();

		pSprite->planeType = planeType;
		pSprite->hp = hp;
		pSprite->speed = speed;

		float start = pSprite->getContentSize().width / 2 + 15;
		float end = visibleSize.width - pSprite->getContentSize().width / 2 - 15;
		//随机的x坐标在屏幕宽度内
		float x = MyUtility::random_0_1(start, end);
		//加上精灵的高度,以免飞机突然在屏幕中出现
		float y = visibleSize.height + pSprite->getContentSize().height;

		pSprite->setPosition(Vec2(x, y));

		return pSprite;
	}

	CC_SAFE_DELETE(pSprite);
	return NULL;
}

这里单独做了个生成敌机一些参数的类,比如敌机的类型(小、中、大)、血量、速度。

再回到GameLayer.cpp

/*===============添加敌机=======================*/
void GameLayer::addEnemyPlane()
{
	if (!isGameOver)
	{
		/*间隔不同的时间出现不同的敌机*/
		smallPlaneTime++;
		mediumPlaneTime++;
		bigPlaneTime++;

		if (SMALLPLANE_TIME < smallPlaneTime)
		{
			//获得敌机精灵
			EnemyPlane* smallPlane = EnemyPlane::createEnemyPlane(
				"enemy1_fly_1.png",
				EnemyPlaneType::kSmallPane, visibleSize,
				1, CCRANDOM_0_1() * 2 + 2
				);

			this->addChild(smallPlane, 3);
			//添加到敌机集合
			planeList.pushBack(smallPlane);
			smallPlaneTime = 0;
		}

		if (MEDIUMPLANE_TIME < mediumPlaneTime)
		{
			EnemyPlane* mediumPlane = EnemyPlane::createEnemyPlane(
				"enemy3_fly_1.png",
				EnemyPlaneType::kMediumPlane, visibleSize,
				30, 3
				);

			this->addChild(mediumPlane, 3);
			planeList.pushBack(mediumPlane);
			mediumPlaneTime = 0;
		}

		if (BIGPLANE_TIME < bigPlaneTime)
		{
			EnemyPlane* bigPlane = EnemyPlane::createEnemyPlane(
				"enemy2_fly_1.png",
				EnemyPlaneType::kBigPlane, visibleSize,
				55, 2
				);

			this->addChild(bigPlane, 3);
			planeList.pushBack(bigPlane);

			bigPlaneAction(bigPlane);
			bigPlaneTime = 0;
		}
	}
}

//大敌机的动画
void GameLayer::bigPlaneAction(EnemyPlane* plane)
{
	Animation* animation = Animation::create();
	for (int i = 1; i <= PLAN_COUNT; i++)
	{
		__String* str = __String::createWithFormat("enemy2_fly_%i.png", i);
		SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str->getCString());

		animation->addSpriteFrame(frame);
	}
	animation->setDelayPerUnit(0.1f);
	Animate* animate = Animate::create(animation);

	plane->runAction(RepeatForever::create(animate));
}

//超过屏幕底部移除集合里的敌机
void GameLayer::moveEnemyPlane()
{
	for (int i = 0; i < planeList.size(); i++)
	{
		EnemyPlane* tmpPlane = planeList.at(i);
		tmpPlane->setPosition(Vec2(tmpPlane->getPosition().x, tmpPlane->getPosition().y - tmpPlane->speed));
		if (tmpPlane->getPosition().y <= (-(tmpPlane->getContentSize().height)))
		{
			//移除元素object    true表示移除最先找到的哪个  false表示移除所有object元素
			planeList.eraseObject(tmpPlane);
			//从父节点中删除当前子节点 如果Cleanup为true则删除当前节点的所有动作
			tmpPlane->removeFromParentAndCleanup(true);
		}
	}

	//游戏结束,敌机全部飞出屏幕,则停止update
	if (isGameOver && planeList.size() == 0)
	{
		unscheduleUpdate();
	}
}

在update里的主要函数是addEnemyPlane();和moveEnemyPlane();

通过不同的间隔出现不同的敌机,并且大敌机带有行走动画,同时敌机超过屏幕底部时从敌机集合里移除掉。


碰撞检测

在update里主要函数是collisionDetection();

/*===================碰撞检测==================================*/
void GameLayer::collisionDetection()
{
	if (!isGameOver)
	{
		playerCollEnemyPlane();
		//空降物与战机碰撞检测
		changeBullet();
	}
}

//子弹和敌机碰撞、战机与敌机碰撞
void GameLayer::playerCollEnemyPlane()
{
	//子弹边框
	Rect bulletRect = bullet->getBoundingBox();
	Rect playerRect = player->getBoundingBox();
	///???矩阵的碰撞,由于飞机是无规则形状,碰撞点有问题???///
	for (int i = 0; i < planeList.size(); i++)
	{
		EnemyPlane* tmpPlane = planeList.at(i);

		//判断矩阵是否相交
		if (bulletRect.intersectsRect(tmpPlane->getBoundingBox()))
		{
			//子弹重置到初始位置
			resetBullet();

			//不同的子弹扣血不同
			tmpPlane->hp -= (isBigBulletTime ? 2 : 1);

			//敌机炸毁
			if (tmpPlane->hp <= 0)
			{
				enemyPlaneBlowupAnimation(tmpPlane);
				planeList.eraseObject(tmpPlane);
			}
			else//敌机被击中的动画
			{
				enemyPlaneHitAnimation(tmpPlane);
			}
			break;
		}
		else if (playerRect.intersectsRect(tmpPlane->getBoundingBox()))
		{
			//敌机炸毁动画
			enemyPlaneBlowupAnimation(tmpPlane);
			//战机炸毁动画,游戏结束
			playerBlowupAnimation();
			//从敌机集合中移除敌机
			planeList.eraseObject(tmpPlane);
			break;
		}
	}
}

//敌机爆炸动画
void GameLayer::enemyPlaneBlowupAnimation(EnemyPlane* enemyPlane)
{
	//爆炸动画帧数
	int animateNum = 0;
	int num = 0;

	if (enemyPlane->planeType == EnemyPlaneType::kSmallPane)
	{
		animateNum = 4;
		//不同敌机炸毁获得分数
		num = 100;
	}
	else if (enemyPlane->planeType == EnemyPlaneType::kMediumPlane)
	{
		animateNum = 4;
		num = 500;
	}
	else
	{
		animateNum = 7;
		num = 2000;
	}

	if (!isGameOver)
	{
		scoreNum += num;
	}

	__String* str = __String::createWithFormat("%i", scoreNum);
	//分数榜
	scoreLabel->setString(str->getCString());


	//先停止敌机的所有动画
	enemyPlane->stopAllActions();

	//敌机爆炸动画
	Animation* animation = Animation::create();
	for (int i = 1; i <= animateNum; i++)
	{
		__String* blowStr = __String::createWithFormat("enemy%i_blowup_%i.png", enemyPlane->planeType, i);

		SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(blowStr->getCString());
		animation->addSpriteFrame(frame);
	}

	animation->setDelayPerUnit(0.1f);
	Animate* animate = Animate::create(animation);

	FiniteTimeAction* acf = CallFuncN::create(CC_CALLBACK_1(GameLayer::blowupEndCallback, this));

	enemyPlane->runAction(Sequence::create(animate, acf, NULL));
}

//爆炸结束回调
void GameLayer::blowupEndCallback(Ref* pSender)
{
	EnemyPlane* enemyPlane = (EnemyPlane*)pSender;

	//从父节点中删除当前子节点 如果Cleanup为true则删除当前节点的所有动作
	enemyPlane->removeFromParentAndCleanup(true);
}

//敌机被击中动画
void GameLayer::enemyPlaneHitAnimation(EnemyPlane* enemyPlane)
{
	if (enemyPlane->planeType == EnemyPlaneType::kMediumPlane)
	{
		hitAnimation(2, enemyPlane);
	}
	else if (enemyPlane->planeType == EnemyPlaneType::kBigPlane)
	{
		hitAnimation(1, enemyPlane);
	}
}

//不同的敌机被击中动画
void GameLayer::hitAnimation(int animateNum, EnemyPlane* enemyPlane)
{
	Animation* animation = Animation::create();
	for (int i = 1; i <= animateNum; i++)
	{
		__String* str = __String::createWithFormat("enemy%i_hit_%i.png", enemyPlane->planeType, i);

		SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str->getCString());

		animation->addSpriteFrame(frame);
	}

	animation->setDelayPerUnit(0.1f);
	Animate* animate = Animate::create(animation);

	enemyPlane->runAction(animate);
}


主要是利用精灵的getBoundingBox();判断两个精灵的矩阵是否有相交

碰撞检测主要包含子弹与敌机、敌机与战机、空降物与战机。

获得空降物和使用空降物

空降物也是单独使用了一个类来产生

ChangeBullet.cpp

#include "ChangeBullet.h"
#include "MyUtility.h"

USING_NS_CC;

//创建空降物对象
ChangeBullet* ChangeBullet::create()
{
	ChangeBullet* res = new ChangeBullet();
	if (res && res->init())
	{
		/*方法内新建一个对象,然后返回对象时,按照谁拥有谁施放。
		*对象是在方法内部新建的,方法退出前不再拥有,所以要在方法退出前释放。
		但是又要在退出时返回该对象,所以使用autorelease*/
		res->autorelease();
		return res;
	}

	CC_SAFE_DELETE(res);
	return NULL;
}

//初始化炸弹、双子弹精灵
void ChangeBullet::initWithResType(ResType resType, Size visibleSize)
{
	this->resType = resType;

	__String* str = __String::createWithFormat("enemy%i_fly_1.png", resType);

	resSprite = Sprite::createWithSpriteFrameName(str->getCString());

	float start = resSprite->getContentSize().width / 2 + 15;
	float end = visibleSize.width - resSprite->getContentSize().width / 2 - 15;
	//防止空降物出现在屏幕宽度之外
	float x = MyUtility::random_0_1(start, end);
	//防止空降物突然出现在屏幕上或者半身卡在屏幕顶部
	float y = visibleSize.height + resSprite->getContentSize().height / 2;

	resSprite->setPosition(Vec2(x, y));
}

//炸弹、双子弹出现时的移动动画
void ChangeBullet::resAnimation(Size visibleSize)
{
	float y = resSprite->getPosition().y;
	MoveTo* mvt1 = MoveTo::create(0.5f, Vec2(resSprite->getPosition().x, visibleSize.height / 1.5));
	MoveTo* mvt2 = MoveTo::create(0.3f, Vec2(resSprite->getPosition().x, visibleSize.height / 2.5));
	MoveTo* mvt3 = MoveTo::create(0.2f, Vec2(resSprite->getPosition().x, visibleSize.height / 6));
	MoveTo* mvt4 = MoveTo::create(2.0f, Vec2(resSprite->getPosition().x, y));

	resSprite->setTag(1);
	resSprite->runAction(Sequence::create(mvt1, mvt2, mvt3, mvt4, NULL));
}
主要是生成空降物的对象和动画,初始出现的x随机产生,由MyUtility::random_0_1返回CCRANDOM_0_1()*(end - start) + start;

是为了防止x随机产生会出现在屏幕之外。

再回到GameLayer.cpp

/*===========双子弹、炮弹=====================*/
//出现空降物
void GameLayer::addChangeBullet()
{
	if (!isGameOver)
	{
		//出现空降物计时
		resTime++;
		if (RES_DISPLAY_TIME < resTime)
		{
			//创建对象
			changeRes = ChangeBullet::create();
			//初始化空降物精灵
			changeRes->initWithResType((ResType)((int)MyUtility::random_0_1(1, 10) % 2 + 4), visibleSize);
			this->addChild(changeRes->resSprite);
			//空降物出现时的动画
			changeRes->resAnimation(visibleSize);

			//防止在changeBullet()里的changeRes->resSprite被清掉
			changeRes->retain();
			resTime = 0;
			isVisible = true;
		}
	}
}

//碰撞检测空降物
void GameLayer::changeBullet()
{
	//出现空降物
	if (isVisible)
	{
		//战机矩阵
		Rect playerRect = player->getBoundingBox();
		//空降物矩阵
		Rect resRect = changeRes->resSprite->getBoundingBox();
		//战机碰到空降物
		if (playerRect.intersectsRect(resRect))
		{
			//切换到不出现空降物
			isVisible = false;
			//先停止空降物所有动作
			changeRes->resSprite->stopAllActions();
			//从父节点中删除当前子节点 如果Cleanup为true则删除当前节点的所有动作
			changeRes->resSprite->removeFromParentAndCleanup(true);

			//双子弹
			if (changeRes->resType == ResType::kBullet)
			{
				bigBulletTime = 600;
				//双子弹开始计时
				isBigBulletTime = true;
				//战机变换成双子弹
				isChangeBullet = true;
			}
			else//炸弹
			{
				addBomb();
			}
		}
	}
}

//子弹变换持续时间
void GameLayer::bigBulletLastTime()
{
	if (!isGameOver && isBigBulletTime)
	{
		if (0 < bigBulletTime)
		{
			//倒计时
			bigBulletTime--;
		}
		else
		{
			bigBulletTime = 600;
			//双子弹时间到
			isBigBulletTime = false;
			//通过resetBullet()变换子弹
			isChangeBullet = true;
		}
	}
}

//增加炸弹
void GameLayer::addBomb()
{
	bombNum++;
	implBomb();
	if (!(useBombToggleMenuItem->isVisible()))
	{
		useBombToggleMenuItem->setVisible(true);
		bombNumLabel->setVisible(true);
	}
}

void GameLayer::implBomb()
{
	__String* str = __String::createWithFormat(" x %i", bombNum);

	bombNumLabel->setString(str->getCString());
	bombNumLabel->setPosition(Vec2(useBombToggleMenuItem->getContentSize().width + bombNumLabel->getContentSize().width / 2, bombNumLabel->getContentSize().height / 2));
}

//使用炸弹
void GameLayer::useBombCallback(Ref* pSender)
{
	if (!isGameOver && !isGamePause)
	{
		bombNum--;

		if (bombNum <= 0)
		{
			useBombToggleMenuItem->setVisible(false);
			bombNumLabel->setVisible(false);
		}

		implBomb();

		//通过循环炸毁集合里所有敌机
		for (int i = 0; i < planeList.size(); i++)
		{
			EnemyPlane* enemyPlane = planeList.at(i);
			enemyPlaneBlowupAnimation(enemyPlane);
		}
		planeList.clear();
	}
}

碰撞检测主要做了敌机炸毁动画、战机炸毁动画、获得空降物,其中获得双子弹时子弹变换成双子弹,使用炸弹时当前全屏敌机全部炸毁。


最后是战机被炸毁后游戏结束以及其他小功能的一些事情

/*==============战机被炸毁,游戏结束=============================*/
//战机炸毁动画
void GameLayer::playerBlowupAnimation()
{
	bullet->stopAllActions();
	//停止战机所有动画
	player->stopAllActions();

	Animation* animation = Animation::create();
	for (int i = 1; i <= 4; i++)
	{
		__String* str = __String::createWithFormat("hero_blowup_%i.png", i);
		SpriteFrame* frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(str->getCString());
		animation->addSpriteFrame(frame);
	}

	animation->setDelayPerUnit(0.1f);
	Animate* animate = Animate::create(animation);

	FiniteTimeAction* acf = CallFuncN::create(CC_CALLBACK_1(GameLayer::gameOverCallback, this));

	//执行炸毁动画
	player->runAction(Sequence::create(animate, acf, NULL));
}

//游戏结束
void GameLayer::gameOverCallback(Ref* pSender)
{
	isGameOver = true;
	SimpleAudioEngine::getInstance()->stopBackgroundMusic();
	SimpleAudioEngine::getInstance()->stopAllEffects();
	//移除子弹和战机
	bullet->removeFromParentAndCleanup(true);
	player->removeFromParentAndCleanup(true);

	//暂停按钮禁用
	pauseToggleMenuItem->setEnabled(false);

	//弹出结束后的画面,包含分数重新开始、退出游戏按钮
	int layerColorWidth = visibleSize.width / 1.2;
	LayerColor* gameOverLayer = LayerColor::create(Color4B(215, 221, 222, 255), layerColorWidth, layerColorWidth + 10);

	gameOverLayer->setPosition(Vec2((visibleSize.width - layerColorWidth) / 2, visibleSize.height / 2 - (layerColorWidth + 10) / 2));
	this->addChild(gameOverLayer, 4);

	LabelBMFont* txtLabel = MyUtility::createBMF(S_SCORE, Vec2(layerColorWidth / 2, layerColorWidth / 1.2));
	gameOverLayer->addChild(txtLabel);

	LabelBMFont* scoreOverLabel = MyUtility::createBMF(scoreLabel->getString(), Vec2(layerColorWidth / 2, layerColorWidth / 1.8));
	gameOverLayer->addChild(scoreOverLabel);

	auto startMenu = MyUtility::createMenuItem(
		CC_CALLBACK_1(GameLayer::startGameCallback, this),
		S_NEWGAME,
		80,
		layerColorWidth / 4
		);

	auto exitMenu = MyUtility::createMenuItem(
		CC_CALLBACK_1(GameLayer::exitGameCallback, this),
		S_EXIT,
		layerColorWidth - 80,
		layerColorWidth / 4
		);

	Menu* mn = Menu::create(startMenu, exitMenu, NULL);
	mn->setPosition(Vec2::ZERO);
	gameOverLayer->addChild(mn);
}

//退出游戏
void GameLayer::exitGameCallback(Ref* pSender)
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_WP8) || (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT)
	MessageBox("You pressed the close button. Windows Store Apps do not implement a close button.", "Alert");
	return;
#endif

	Director::getInstance()->end();

#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
	exit(0);
#endif
}

//重新开始
void GameLayer::startGameCallback(Ref* pSender)
{
	auto sc = GameLayer::createScene();
	auto tsc = TransitionFade::create(1.0f, sc);
	Director::getInstance()->replaceScene(tsc);
}

//暂停
void GameLayer::pauseGameCallback(Ref* pSender)
{
	//游戏没有结束
	if (!isGameOver)
	{
		isGamePause = !isGamePause;
		//有暂停所有的Action,没有恢复所有的?
		if (isGamePause)
		{
			//同时暂停空降物的动作
			Director::getInstance()->getActionManager()->pauseTarget(this->getChildByTag(1));

			//弹出暂停画面
			int layerColorWidth = visibleSize.width / 1.2;
			gamePauseLayer = LayerColor::create(Color4B(215, 221, 222, 255), layerColorWidth, layerColorWidth + 10);
			gamePauseLayer->setPosition(Vec2((visibleSize.width - layerColorWidth) / 2, visibleSize.height / 2 - (layerColorWidth + 10) / 2));
			this->addChild(gamePauseLayer, 4);

			auto resumeMenu = MyUtility::createMenuItem(
				CC_CALLBACK_1(GameLayer::pauseGameCallback, this),
				S_RESUMEGAME
				);

			auto startMenu = MyUtility::createMenuItem(
				CC_CALLBACK_1(GameLayer::startGameCallback, this),
				S_NEWGAME
				);

			auto exitMenu = MyUtility::createMenuItem(
				CC_CALLBACK_1(GameLayer::exitGameCallback, this),
				S_EXIT
				);

			Menu* mn = Menu::create(resumeMenu, startMenu, exitMenu, NULL);
			mn->setPosition(Vec2(layerColorWidth / 2, (layerColorWidth + 10)/2));
			mn->alignItemsVertically();
			mn->alignItemsVerticallyWithPadding(50);
			gamePauseLayer->addChild(mn);
		}
		else
		{
			if (gamePauseLayer != nullptr)
			{
				//移除暂停画面
				gamePauseLayer->removeFromParentAndCleanup(true);
			}
			//恢复空降物的动作
			Director::getInstance()->getActionManager()->resumeTarget(this->getChildByTag(1));
		}
	}
}


最终效果图:

 


整个功能至此差不多,做了也快一个星期了,纯粹是为了练手,毕竟才开始学习cocos2d-x,需要做大量的项目来熟悉,持之以恒!

这个项目开始之前搜索素材就花了一天的时间,很多都是ios的,plist文件有的也用不成,只能找到全部单独的素材重新生成plist。

好不容易找到的空降物竟然也是黑白的,微信里的似乎是彩色的,只好自己随便涂了下颜色凑合着用。

用到的辅助工具有photoshop给图片加颜色生成透明png,TexturePackerGUI生成plist用,bmfont用来生成fnt文件。

留给自己的问题:

1在真机上打开会很慢,要先黑屏几秒,按说第一个layer没有什么内容

2开始游戏后游戏画面会卡顿一下

3性能优化,占用的内存挺高,这个有待后面的学习

4真机上如何做适配,android有自动适配不同的分辨率的目录文件,但我想不可能这么简单,有待后面的学习

5部署到android真机上,操纵战机时战机偶尔会突然自动移动到左上角。这个问题微信上也有类似。真机环境三星S5830+2.3.4。


Demo:http://download.csdn.net/detail/qhgccy/7868207
在VS13里c++定义变常量直接赋值可以,但最好在初始化时去赋值,需要自行改一下,有些编译器通不过。




你可能感兴趣的:(cocos2d-x)