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_
#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__
第一个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
}
效果图:
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));
}
初始化了背景、左上角的暂停、分数、左下角的炸弹、炸弹数量、战机、战机动画、子弹
背景添加了两个方便做滚屏。
效果图::
左下角的炸弹和炸弹数量刚开始是隐藏的。
滚屏和战机移动
滚屏需要放在update里,所以在初始化里少不了this->scheduleUpdate();
void GameLayer::update(float dt)
{
if (!isGamePause)
{
scrollBg();
fire();
addEnemyPlane();
moveEnemyPlane();
collisionDetection();
addChangeBullet();
bigBulletLastTime();
}
}
//滚屏
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--;
}
}
战机的移动,我在初始化里直接设置了单点触摸,因此这里不需要创建移动监听,直接重写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中主要是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();
}
}
}
生成敌机
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里主要函数是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);
}
碰撞检测主要包含子弹与敌机、敌机与战机、空降物与战机。
获得空降物和使用空降物
空降物也是单独使用了一个类来产生
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));
}
}
}
最终效果图:
这个项目开始之前搜索素材就花了一天的时间,很多都是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++定义变常量直接赋值可以,但最好在初始化时去赋值,需要自行改一下,有些编译器通不过。