Cocos2dx项目实战(5)——微信飞机大战

本章将介绍一款正式的作品,也是很欢迎的游戏。

本文仅供个人记录和复习,不用于其他用途

致谢

http://blog.csdn.net/column/details/jackyairplane.html

游戏效果展示

游戏流程设计

我们的游戏有加载场景、游戏场景、结束场景这三个。加载场景不必多说,我们主要来说说游戏场景。我们操控的飞机就是英雄,它会不断地发射子弹,击毁迎面而来的敌人。当然,不同的敌人有不同的生命值,越大的生命值越多。游戏的过程中还会出现两种补给,一种可以帮我们清理所有的敌人,一种可以让我们的飞机发射双排子弹。至于结束场景,就是显示当前得分和历史最佳得分,当然还要加上再来一次的按钮。

介绍了整个流程之后,相必整个游戏的结构已经很清楚了。借助上一章的设计模式,我们要将各种各样的元素分为不同的层,最后综合到游戏场景中。

WelcomeScene

加载场景,用于完成相应的初始化工作。

WelcomeScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "cocos2d.h"
#include "WelcomeLayer.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
class WelcomeScene : public Scene
{
public:
WelcomeScene(void);
~WelcomeScene(void);
virtual bool init();
CREATE_FUNC(WelcomeScene);
void PreloadMusic();
public:
WelcomeLayer* _welcomeLayer;
};

WelcomeScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include "WelcomeScene.h"
WelcomeScene::WelcomeScene(void)
{
_welcomeLayer = nullptr;
}
WelcomeScene::~WelcomeScene(void)
{
}
bool WelcomeScene::init()
{
bool bRet=false;
do
{
CC_BREAK_IF(!Scene::init());
_welcomeLayer = WelcomeLayer::create();
CC_BREAK_IF(!_welcomeLayer);
this->addChild(_welcomeLayer);
PreloadMusic();
bRet = true;
} while (0);
return bRet;
}
void WelcomeScene::PreloadMusic()
{
CocosDenshion::SimpleAudioEngine::getInstance()->preloadBackgroundMusic("sound/game_music.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/bullet.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/enemy1_down.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/enemy2_down.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/enemy3_down.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/game_over.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/get_bomb.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/get_double_laser.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/use_bomb.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/big_spaceship_flying.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/achievement.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/out_porp.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->preloadEffect("sound/button.mp3");
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/game_music.mp3",true);
}

场景的工作很简单,加载WelcomeLayer和音乐,同时播放背景音乐。

WelcomeLayer

用于显示开始界面。

WelcomLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "cocos2d.h"
USING_NS_CC;
class WelcomeLayer : public Layer
{
public:
WelcomeLayer(void);
~WelcomeLayer(void);
virtual bool init();
CREATE_FUNC(WelcomeLayer);
void loadingDone(Node* pNode);
bool isHaveSaveFile();
void getHighestHistorySorce();
};

WelcomeLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include "WelcomeLayer.h"
#include "GameScene.h"
#include "GameOverLayer.h"
WelcomeLayer::WelcomeLayer(void)
{
}
WelcomeLayer::~WelcomeLayer(void)
{
}
bool WelcomeLayer::init()
{
bool bRet=false;
do
{
CC_BREAK_IF(!Layer::init());
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("ui/shoot_background.plist");
SpriteFrameCache::getInstance()->addSpriteFramesWithFile("ui/shoot.plist");
auto winSize = Director::getInstance()->getWinSize();
auto background = Sprite::createWithSpriteFrameName("background.png");
background->setPosition(Point(winSize.width/2,winSize.height/2));
this->addChild(background);
auto copyright = Sprite::createWithSpriteFrameName("shoot_copyright.png");
copyright->setAnchorPoint(Point::ANCHOR_MIDDLE_BOTTOM);
copyright->setPosition(Point(winSize.width/2,winSize.height/2));
this->addChild(copyright);
auto loading = Sprite::createWithSpriteFrameName("game_loading1.png");
loading->setPosition(Point(winSize.width/2,winSize.height/2-40));
this->addChild(loading);
auto animation = Animation::create();
animation->setDelayPerUnit(0.2f);
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("game_loading1.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("game_loading2.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("game_loading3.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("game_loading4.png"));
auto animate = Animate::create(animation);
auto repeat = Repeat::create(animate,2);
auto repeatdone = CallFuncN::create(CC_CALLBACK_1(WelcomeLayer::loadingDone,this));
auto sequence = Sequence::create(repeat, repeatdone, nullptr);
loading->runAction(sequence);
getHighestHistorySorce();
bRet = true;
} while (0);
return bRet;
}
void WelcomeLayer::loadingDone(Node* pNode)
{
auto pScene = GameScene::create();
auto animateScene = TransitionMoveInB::create(0.5f,pScene);
Director::getInstance()->replaceScene(animateScene);
}
bool WelcomeLayer::isHaveSaveFile()
{
if(!UserDefault::getInstance()->getBoolForKey("isHaveSaveFileXml"))
{
UserDefault::getInstance()->setBoolForKey("isHaveSaveFileXml", true);
UserDefault::getInstance()->setIntegerForKey("HighestScore",0);
UserDefault::getInstance()->flush();
return false;
}
else
{
return true;
}
}
void WelcomeLayer::getHighestHistorySorce()
{
if (isHaveSaveFile())
{
GameOverLayer::highestHistoryScore=UserDefault::getInstance()->getIntegerForKey("HighestScore",0);
}
}

代码看似很多,其实也就做了几件事。加载背景图片以及动画就不多说了,loadingDonerepeat编在了一个序列,为的就是在加载完毕后立即跳转到游戏场景。这里有一个getHighestHistorySorce(),其实就是获取历史最高成绩,使用了数据存储的功能。注意,这里的转换场景用到了特殊的效果,大家可以自己进行尝试,选择喜欢的效果。

GameScene

游戏主场景,加载游戏层。

GameScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "cocos2d.h"
#include "GameLayer.h"
USING_NS_CC;
class GameScene : public Scene
{
public:
GameScene(void);
~GameScene(void);
CREATE_FUNC(GameScene);
virtual bool init();
GameLayer* _gameLayer;
};

GameScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include "GameScene.h"
GameScene::GameScene(void)
{
_gameLayer = nullptr;
}
GameScene::~GameScene(void)
{
}
bool GameScene::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Scene::init());
_gameLayer = GameLayer::create();
CC_BREAK_IF(!_gameLayer);
this->addChild(_gameLayer);
bRet = true;
} while(0);
return bRet;
}

工作非常简单,就是加载游戏层GameLayer

PlaneLayer

英雄层,也就是我们操纵的飞机。

PlaneLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include "cocos2d.h"
USING_NS_CC;
const int AIRPLANE=747;
class PlaneLayer : public Layer
{
public:
PlaneLayer(void);
~PlaneLayer(void);
static PlaneLayer* create();
virtual bool init();
void MoveTo(Point location);
void Blowup(int passScore);
void RemovePlane();
public:
static PlaneLayer* sharedPlane;
bool isAlive;
int score;
};

稍微讲解一下这里的成员。MoveTo()主要用于飞机的触摸移动,Blowup()是飞机爆炸的方法,RemovePlane()则是移除飞机并转换到游戏结束的场景。isAlive表示飞机是否存活,score自然就是玩家的得分。

PlaneLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#include "PlaneLayer.h"
#include "GameOverScene.h"
PlaneLayer* PlaneLayer::sharedPlane = nullptr;
PlaneLayer::PlaneLayer(void)
{
isAlive = true;
score = 0;
}
PlaneLayer::~PlaneLayer(void)
{
}
PlaneLayer* PlaneLayer::create()
{
PlaneLayer *pRet = new PlaneLayer();
if (pRet && pRet->init())
{
pRet->autorelease();
sharedPlane = pRet;
return pRet;
}
else
{
CC_SAFE_DELETE(pRet);
return nullptr;
}
}
bool PlaneLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Layer::init());
auto winSize = Director::getInstance()->getWinSize();
auto plane = Sprite::createWithSpriteFrameName("hero1.png");
plane->setPosition(Point(winSize.width/2, plane->getContentSize().height/2));
this->addChild(plane, 0, AIRPLANE);
auto blink = Blink::create(1,3);
auto animation = Animation::create();
animation->setDelayPerUnit(0.1f);
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero1.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero2.png"));
auto animate = Animate::create(animation);
plane->runAction(blink);
plane->runAction(RepeatForever::create(animate));
bRet = true;
} while (0);
return bRet;
}
void PlaneLayer::MoveTo(Point location)
{
if(isAlive && !Director::getInstance()->isPaused())
{
auto winSize = Director::getInstance()->getWinSize();
auto planeSize = this->getChildByTag(AIRPLANE)->getContentSize();
if (location.x < planeSize.width/2)
{
location.x = planeSize.width/2;
}
if (location.x > winSize.width-planeSize.width/2)
{
location.x = winSize.width-planeSize.width/2;
}
if (location.y < planeSize.height/2)
{
location.y = planeSize.width/2+10;
}
if (location.y > winSize.height-planeSize.height/2)
{
location.y = winSize.height-planeSize.height/2;
}
this->getChildByTag(AIRPLANE)->setPosition(location);
}
}
void PlaneLayer::Blowup(int passScore)
{
if(isAlive)
{
isAlive = false;
score = passScore;
auto animation = Animation::create();
animation->setDelayPerUnit(0.2f);
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero_blowup_n1.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero_blowup_n2.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero_blowup_n3.png"));
animation->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("hero_blowup_n4.png"));
auto animate = Animate::create(animation);
auto removePlane = CallFunc::create(CC_CALLBACK_0(PlaneLayer::RemovePlane, this));
auto sequence = Sequence::create(animate,removePlane, nullptr);
this->getChildByTag(AIRPLANE)->stopAllActions();
this->getChildByTag(AIRPLANE)->runAction(sequence);
}
}
void PlaneLayer::RemovePlane()
{
this->removeChildByTag(AIRPLANE,true);
auto pScene = GameOverScene::create(score);
auto animateScene = TransitionMoveInT::create(0.8f,pScene);
Director::getInstance()->replaceScene(animateScene);
}

init()方法制作了两个动作,一个是飞机出场时闪三下,还有一个就是循环播放的动画,通过切换两张图片来达到飞机喷气的效果。

MoveTo()接受的是触摸点的坐标,逻辑判断的主要目的就是为了让飞机保持在屏幕中,而不是被我们操控着飞出屏幕。Blowup()其实也好理解,如果isAlivetrue,那么就把它改为false,代表着英雄死亡,后面的只不过是加载飞机的爆炸动画,在播放了动画之后移除飞机。不过要注意的是,我们应该先暂停飞机之前所有的动作,然后再执行动作序列,这样会使得飞机显得自然一点。

BulletLayer

子弹层,专门用于生成子弹。

BulletLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
class BulletLayer : public Layer
{
public:
BulletLayer(void);
~BulletLayer(void);
CREATE_FUNC(BulletLayer);
virtual bool init();
void AddBullet(float dt);
void bulletMoveFinished(Node* pSender);
void RemoveBullet(Sprite* bullet);
void StartShoot(float delay = 0.0f);
void StopShoot();
public:
__Array* m_pAllBullet;
};

BulletLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include "BulletLayer.h"
#include "PlaneLayer.h"
BulletLayer::BulletLayer(void)
{
m_pAllBullet = __Array::create();
m_pAllBullet->retain();
}
BulletLayer::~BulletLayer(void)
{
m_pAllBullet->release();
m_pAllBullet = nullptr;
}
bool BulletLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Layer::init());
bRet = true;
} while (0);
return bRet;
}
void BulletLayer::StartShoot(float delay)
{
this->schedule(schedule_selector(BulletLayer::AddBullet), 0.20f, kRepeatForever, delay);
}
void BulletLayer::StopShoot()
{
this->unschedule(schedule_selector(BulletLayer::AddBullet));
}
void BulletLayer::AddBullet(float dt)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/bullet.mp3");
auto bullet = Sprite::createWithSpriteFrameName("bullet1.png");
this->addChild(bullet);
this->m_pAllBullet->addObject(bullet);
Point planePosition = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
Point bulletPosition = Point(planePosition.x, planePosition.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height / 2);
bullet->setPosition(bulletPosition);
float length = Director::getInstance()->getWinSize().height + bullet->getContentSize().height / 2 - bulletPosition.y;
float velocity = 320 / 1; // 320pixel/sec
float realMoveDuration = length / velocity;
auto actionMove = MoveTo::create(realMoveDuration, Point(bulletPosition.x,Director::getInstance()->getWinSize().height+bullet->getContentSize().height / 2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(BulletLayer::bulletMoveFinished, this));
auto sequence = Sequence::create(actionMove, actionDone, nullptr);
bullet->runAction(sequence);
}
void BulletLayer::bulletMoveFinished(Node* pSender)
{
auto bullet = (Sprite *)pSender;
this->removeChild(bullet, true);
this->m_pAllBullet->removeObject(bullet);
}
void BulletLayer::RemoveBullet(Sprite* bullet)
{
if (bullet != nullptr)
{
this->removeChild(bullet, true);
this->m_pAllBullet->removeObject(bullet);
}
}

首先,我们没有使用Vector,也就意味着我们需要手动为Array数组调用retain()。原因很简单,Array数组使用的是autorelease(),那么Array数组就会在一帧之后被释放掉,这显然不是我们想要看到的。删除数组也很简单,为它调用release()即可。

StartShoot()StopShoot()就是开关定时器,不断地调用AddBullet()添加子弹。其实查看过源代码后,我们会发现子弹其实就是生成在飞机的头部位置,然后使用MoveTo动作不断往前飞。这里的速度已经规定为320,所以飞出屏幕用的时间自然就是飞行长度除以时间。

bulletMoveFinished()是有参回调函数,参数就是调用这个函数的对象,然后将子弹从渲染树和数组中删除。另外,这里还有一个RemoveBullet(),其实这个是提供给外部使用的函数,在子弹与敌人碰撞时被调用,删除子弹。

MutiBulletsLayer

这个层是双排子弹层,为了方便还是单独做了一个层。

MutiBulletsLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
class MutiBulletsLayer : public Layer
{
public:
MutiBulletsLayer(void);
~MutiBulletsLayer(void);
CREATE_FUNC(MutiBulletsLayer);
virtual bool init();
void AddMutiBullets(float dt);
void mutiBulletsMoveFinished(Node* pSender);
void RemoveMutiBullets(Sprite* mutiBullets);
void StartShoot();
void StopShoot();
public:
__Array* m_pAllMutiBullets;
};

MutiBulletsLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include "MutiBulletsLayer.h"
#include "PlaneLayer.h"
MutiBulletsLayer::MutiBulletsLayer(void)
{
m_pAllMutiBullets = __Array::create();
m_pAllMutiBullets->retain();
}
MutiBulletsLayer::~MutiBulletsLayer(void)
{
m_pAllMutiBullets->release();
m_pAllMutiBullets = nullptr;
}
bool MutiBulletsLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCLayer::init());
bRet = true;
} while (0);
return bRet;
}
void MutiBulletsLayer::StartShoot()
{
this->schedule(schedule_selector(MutiBulletsLayer::AddMutiBullets),0.2f,30,0.0f);
}
void MutiBulletsLayer::StopShoot()
{
this->unschedule(schedule_selector(MutiBulletsLayer::AddMutiBullets));
}
void MutiBulletsLayer::AddMutiBullets(float dt)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/bullet.mp3");
auto bulletLeft = Sprite::createWithSpriteFrameName("bullet2.png");
auto bulletRight = Sprite::createWithSpriteFrameName("bullet2.png");
this->addChild(bulletLeft);
this->addChild(bulletRight);
this->m_pAllMutiBullets->addObject(bulletLeft);
this->m_pAllMutiBullets->addObject(bulletRight);
auto planePosition = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
auto bulletLeftPosition = Point(planePosition.x-33, planePosition.y+35);
auto bulletRightPosition = Point(planePosition.x+33, planePosition.y+35);
bulletLeft->setPosition(bulletLeftPosition);
bulletRight->setPosition(bulletRightPosition);
float length = Director::getInstance()->getWinSize().height+bulletLeft->getContentSize().height/2-bulletLeftPosition.y;
float velocity = 420 / 1; // 420pixel/sec
float realMoveDuration = length/velocity;
auto actionLeftMove = MoveTo::create(realMoveDuration, Point(bulletLeftPosition.x, Director::getInstance()->getWinSize().height + bulletLeft->getContentSize().height/2));
auto actionLeftDone = CallFuncN::create(CC_CALLBACK_1(MutiBulletsLayer::mutiBulletsMoveFinished, this));
auto sequenceLeft = Sequence::create(actionLeftMove, actionLeftDone, nullptr);
auto actionRightMove = MoveTo::create(realMoveDuration, Point(bulletRightPosition.x, Director::getInstance()->getWinSize().height + bulletRight->getContentSize().height/2));
auto actionRightDone = CallFuncN::create(CC_CALLBACK_1(MutiBulletsLayer::mutiBulletsMoveFinished, this));
auto sequenceRight = Sequence::create(actionRightMove, actionRightDone, nullptr);
bulletLeft->runAction(sequenceLeft);
bulletRight->runAction(sequenceRight);
}
void MutiBulletsLayer::mutiBulletsMoveFinished(Node* pSender)
{
auto mutiBullets = (Sprite*)pSender;
m_pAllMutiBullets->removeObject(mutiBullets);
this->removeChild(mutiBullets, true);
}
void MutiBulletsLayer::RemoveMutiBullets(Sprite* mutiBullets)
{
if (mutiBullets != nullptr)
{
this->m_pAllMutiBullets->removeObject(mutiBullets);
this->removeChild(mutiBullets, true);
}
}

基本逻辑和单排子弹层一样,只是改变了一下子弹的位置。

Enemy

我们把所有敌人共有的特性归纳到一起,方便我们生成不同样式的敌机。

Enemy.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "cocos2d.h"
USING_NS_CC;
class Enemy : public Node
{
public:
Enemy(void);
~Enemy(void);
static Enemy* create();
void bindSprite(Sprite* sprite, int life);
Sprite* getSprite();
int getLife();
void loseLife();
Rect getBoundingBox();
private:
Sprite* m_sprite;
int m_life;
};

Enemy.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "Enemy.h"
Enemy::Enemy(void)
{
m_sprite = nullptr;
m_life = 0;
}
Enemy::~Enemy(void)
{
}
Enemy* Enemy::create()
{
Enemy* pRet = new Enemy;
if (pRet != nullptr) {
pRet->autorelease();
return pRet;
}
else
return nullptr;
}
void Enemy::bindSprite(Sprite* sprite, int life)
{
m_sprite = sprite;
m_life = life;
this->addChild(m_sprite);
}
Sprite* Enemy::getSprite()
{
return m_sprite;
}
int Enemy::getLife()
{
return m_life;
}
void Enemy::loseLife()
{
m_life--;
}
Rect Enemy::getBoundingBox()
{
auto rect = m_sprite->boundingBox();
auto pos = this->convertToWorldSpace(rect.origin);
Rect enemyRect(pos.x,pos.y,rect.size.width,rect.size.height);
return enemyRect;
}

Enemy继承自Node节点类,为的是能让Sprite接收Enemy的信息。定义了bindSprite()getSprite()getLife()loseLife()getBoundingBox()这五种方法。

bindSprite()主要用于添加敌机的图片,初始化敌机的生命值,也就是相当于init()方法。

getSprite()getLife()loseLife()不用多说,这里只讲一讲getBoundingBox()。为什么将Enemy的坐标转换成世界坐标呢?很简单,Enemy只是节点,我们的GameLayer才是主场景,addChild()只是把精灵加入了Enemy

EnemyLayer

用于管理敌机的层。

EnemyLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "cocos2d.h"
#include "Enemy.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
const int ENEMY1_MAXLIFE=1;
const int ENEMY2_MAXLIFE=2;
const int ENEMY3_MAXLIFE=5;
enum Level
{
EASY,
MIDDLE,
HARD,
};
class EnemyLayer : public Layer
{
public:
EnemyLayer(void);
~EnemyLayer(void);
CREATE_FUNC(EnemyLayer);
virtual bool init();
void addEnemy1(float dt);
void enemy1MoveFinished(Node* pSender);
void enemy1Blowup(Enemy* enemy1);
void removeEnemy1(Node* pTarget);
void removeAllEnemy1();
void addEnemy2(float dt);
void enemy2MoveFinished(Node* pSender);
void enemy2Blowup(Enemy* enemy2);
void removeEnemy2(Node* pTarget);
void removeAllEnemy2();
void addEnemy3(float dt);
void enemy3MoveFinished(Node* pSender);
void enemy3Blowup(Enemy* enemy3);
void removeEnemy3(Node* pTarget);
void removeAllEnemy3();
void removeAllEnemy();
__Array* m_pAllEnemy1;
__Array* m_pAllEnemy2;
__Array* m_pAllEnemy3;
private:
SpriteFrame* enemy1SpriteFrame;
SpriteFrame* enemy2SpriteFrame;
SpriteFrame* enemy3SpriteFrame_1;
SpriteFrame* enemy3SpriteFrame_2;
};

看起来很多,其实功能都是重复的,这里我就拿Enemy3作为例子,其他的两个可以以此类推。addEnemy3()用于添加大飞机;enemy3MoveFinished()是在大飞机飞出屏幕后移除它;enemy3Blowup()是执行爆炸动画,同时调用removeEnemy3()来移除飞机;removeAllEnemy3()removeAllEnemy()自然就不用多说了。

EnemyLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
#include "EnemyLayer.h"
#include "GameLayer.h"
EnemyLayer::EnemyLayer(void)
{
enemy1SpriteFrame = nullptr;
enemy2SpriteFrame = nullptr;
enemy3SpriteFrame_1 = nullptr;
enemy3SpriteFrame_2 = nullptr;
m_pAllEnemy1 = __Array::create();
m_pAllEnemy1->retain();
m_pAllEnemy2 = __Array::create();
m_pAllEnemy2->retain();
m_pAllEnemy3 = __Array::create();
m_pAllEnemy3->retain();
}
EnemyLayer::~EnemyLayer(void)
{
m_pAllEnemy1->release();
m_pAllEnemy1 = nullptr;
m_pAllEnemy2->release();
m_pAllEnemy2 = nullptr;
m_pAllEnemy3->release();
m_pAllEnemy3 = nullptr;
}
bool EnemyLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCLayer::init());
enemy1SpriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1.png");
enemy2SpriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2.png");
enemy3SpriteFrame_1 = SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n1.png");
enemy3SpriteFrame_2 = SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n2.png");
auto animation1 = Animation::create();
animation1->setDelayPerUnit(0.1f);
animation1->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
animation1->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
animation1->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
animation1->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));
auto animation2 = Animation::create();
animation2->setDelayPerUnit(0.1f);
animation2->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down1.png"));
animation2->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down2.png"));
animation2->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down3.png"));
animation2->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down4.png"));
auto animation3 = Animation::create();
animation3->setDelayPerUnit(0.1f);
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down1.png"));
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down2.png"));
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down3.png"));
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down4.png"));
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down5.png"));
animation3->addSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down6.png"));
AnimationCache::getInstance()->addAnimation(animation1, "Enemy1Blowup");
AnimationCache::getInstance()->addAnimation(animation2, "Enemy2Blowup");
AnimationCache::getInstance()->addAnimation(animation3, "Enemy3Blowup");
this->schedule(schedule_selector(EnemyLayer::addEnemy1),0.5f);
this->schedule(schedule_selector(EnemyLayer::addEnemy2),3.0f);
this->schedule(schedule_selector(EnemyLayer::addEnemy3),6.0f);
bRet = true;
} while (0);
return bRet;
}
void EnemyLayer::addEnemy1(float dt)
{
auto enemy1 = Enemy::create();
enemy1->bindSprite(Sprite::createWithSpriteFrame(enemy1SpriteFrame), ENEMY1_MAXLIFE);
auto enemy1Size = enemy1->getSprite()->getContentSize();
auto winSize = Director::getInstance()->getWinSize();
int minX = enemy1Size.width/2;
int maxX = winSize.width-enemy1Size.width/2;
int rangeX = maxX-minX;
int actualX = (rand()%rangeX)+minX;
enemy1->setPosition(Point(actualX, winSize.height+enemy1Size.height/2));
this->addChild(enemy1);
this->m_pAllEnemy1->addObject(enemy1);
float minDuration, maxDuration;
switch(GameLayer::getCurLevel())
{
case EASY:
minDuration = 2.0f;
maxDuration = 4.0f;
break;
case MIDDLE:
minDuration = 1.8f;
maxDuration = 3.6f;
break;
case HARD:
minDuration = 1.6f;
maxDuration = 3.2f;
break;
default:
minDuration = 2.0f;
maxDuration = 4.0f;
break;
}
int rangeDuration = maxDuration-minDuration;
int actualDuration = (rand()%rangeDuration)+minDuration;
auto actionMove = MoveTo::create(actualDuration, Point(actualX, 0-enemy1->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::enemy1MoveFinished, this));
auto sequence = Sequence::create(actionMove, actionDone, nullptr);
enemy1->runAction(sequence);
}
void EnemyLayer::enemy1MoveFinished(Node* pSender)
{
auto enmey1 = (Enemy*)pSender;
this->removeChild(enmey1, true);
this->m_pAllEnemy1->removeObject(enmey1);
}
void EnemyLayer::enemy1Blowup(Enemy* enemy1)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/enemy1_down.mp3");
auto animation = AnimationCache::getInstance()->getAnimation("Enemy1Blowup");
auto animate = Animate::create(animation);
auto removeEnemy1 = CallFunc::create(CC_CALLBACK_0(EnemyLayer::removeEnemy1, this, enemy1));
auto sequence = Sequence::create(animate, removeEnemy1, nullptr);
enemy1->getSprite()->runAction(sequence);
}
void EnemyLayer::removeEnemy1(Node* pTarget)
{
auto enemy1 = (Enemy*)pTarget;
if (enemy1 != nullptr)
{
m_pAllEnemy1->removeObject(enemy1);
this->removeChild(enemy1, true);
}
}
void EnemyLayer::removeAllEnemy1()
{
Ref* obj;
CCARRAY_FOREACH(m_pAllEnemy1, obj)
{
auto enemy1 = (Enemy*)obj;
if (enemy1->getLife() > 0)
{
enemy1Blowup(enemy1);
}
}
}
void EnemyLayer::addEnemy2(float dt)
{
auto enemy2 = Enemy::create();
enemy2->bindSprite(Sprite::createWithSpriteFrame(enemy2SpriteFrame), ENEMY2_MAXLIFE);
auto enemy2Size = enemy2->getSprite()->getContentSize();
auto winSize = Director::getInstance()->getWinSize();
int minX = enemy2Size.width/2;
int maxX = winSize.width-enemy2Size.width/2;
int rangeX = maxX-minX;
int actualX = (rand()%rangeX)+minX;
enemy2->setPosition(Point(actualX,winSize.height+enemy2Size.height/2));
this->addChild(enemy2);
this->m_pAllEnemy2->addObject(enemy2);
float minDuration, maxDuration;
switch(GameLayer::getCurLevel())
{
case EASY:
minDuration = 3.0;
maxDuration = 6.0;
break;
case MIDDLE:
minDuration = 2.7;
maxDuration = 5.4;
break;
case HARD:
minDuration = 2.5;
maxDuration = 5.0;
break;
default:
minDuration = 3.0;
maxDuration = 6.0;
break;
}
int rangeDuration = maxDuration-minDuration;
int actualDuration = (rand()%rangeDuration)+minDuration;
auto actionMove = MoveTo::create(actualDuration,Point(actualX, 0-enemy2->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::enemy2MoveFinished, this));
auto sequence = Sequence::create(actionMove, actionDone, nullptr);
enemy2->runAction(sequence);
}
void EnemyLayer::enemy2MoveFinished(Node* pSender)
{
auto enmey2 = (Enemy*)pSender;
this->removeChild(enmey2, true);
this->m_pAllEnemy2->removeObject(enmey2);
}
void EnemyLayer::enemy2Blowup(Enemy* enemy2)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/enemy2_down.mp3");
auto animation = AnimationCache::getInstance()->getAnimation("Enemy2Blowup");
auto animate = Animate::create(animation);
auto removeEnemy2 = CallFunc::create(CC_CALLBACK_0(EnemyLayer::removeEnemy2, this, enemy2));
auto sequence = Sequence::create(animate, removeEnemy2, nullptr);
enemy2->getSprite()->runAction(sequence);
}
void EnemyLayer::removeEnemy2(Node* pTarget)
{
auto enemy2 = (Enemy*)pTarget;
if (enemy2 != nullptr)
{
m_pAllEnemy2->removeObject(enemy2);
this->removeChild(enemy2, true);
}
}
void EnemyLayer::removeAllEnemy2()
{
Ref* obj;
CCARRAY_FOREACH(m_pAllEnemy2,obj)
{
auto enemy2 = (Enemy*)obj;
if (enemy2->getLife()>0)
{
enemy2Blowup(enemy2);
}
}
}
void EnemyLayer::addEnemy3(float dt)
{
auto enemy3 = Enemy::create();
enemy3->bindSprite(Sprite::createWithSpriteFrame(enemy3SpriteFrame_1), ENEMY3_MAXLIFE);
// minX和maxX是飞机出现的范围,这里的意思是不允许大飞机超出两边的屏幕
auto enemy3Size = enemy3->getSprite()->getContentSize();
auto winSize = Director::getInstance()->getWinSize();
int minX = enemy3Size.width/2;
int maxX = winSize.width-enemy3Size.width/2;
int rangeX = maxX-minX;
// 随机的出生位置
int actualX = (rand()%rangeX)+minX;
// 在屏幕最上面的actualX处生成一架大飞机
enemy3->setPosition(Point(actualX, winSize.height+enemy3Size.height/2));
this->addChild(enemy3);
this->m_pAllEnemy3->addObject(enemy3);
// 这两个代表飞机飞出屏幕所用时间,虽然也是随机取数,但是maxDuration越小,飞机越有可能飞得快
float minDuration, maxDuration;
switch(GameLayer::getCurLevel())
{
case EASY:
minDuration = 4.0;
maxDuration = 8.0;
break;
case MIDDLE:
minDuration = 3.6;
maxDuration = 7.2;
break;
case HARD:
minDuration = 3.2;
maxDuration = 6.4;
break;
default:
minDuration = 4.0;
maxDuration = 8.0;
break;
}
int rangeDuration = maxDuration-minDuration;
int actualDuration = (rand()%rangeDuration)+minDuration;
auto actionMove = MoveTo::create(actualDuration, Point(actualX, 0-enemy3->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::enemy3MoveFinished, this));
auto sequence = Sequence::create(actionMove, actionDone, nullptr);
enemy3->runAction(sequence);
// 类似于飞机的喷气动画
auto animation = Animation::create();
animation->setDelayPerUnit(0.2f);
animation->addSpriteFrame(enemy3SpriteFrame_1);
animation->addSpriteFrame(enemy3SpriteFrame_2);
auto animate = Animate::create(animation);
enemy3->getSprite()->runAction(RepeatForever::create(animate));
}
void EnemyLayer::enemy3MoveFinished(Node* pSender)
{
auto enmey3 = (Enemy*)pSender;
this->removeChild(enmey3, true);
this->m_pAllEnemy3->removeObject(enmey3);
}
void EnemyLayer::enemy3Blowup(Enemy* enemy3)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/enemy3_down.mp3");
auto animation = AnimationCache::getInstance()->getAnimation("Enemy3Blowup");
auto animate = Animate::create(animation);
auto removeEnemy3 = CallFunc::create(CC_CALLBACK_0(EnemyLayer::removeEnemy3, this, enemy3));
auto sequence = Sequence::create(animate, removeEnemy3, nullptr);
enemy3->getSprite()->runAction(sequence);
}
void EnemyLayer::removeEnemy3(Node* pTarget)
{
auto enemy3 = (Enemy*)pTarget;
if (enemy3 != nullptr)
{
enemy3->stopAllActions();
m_pAllEnemy3->removeObject(enemy3);
this->removeChild(enemy3, true);
}
}
void EnemyLayer::removeAllEnemy3()
{
Ref* obj;
CCARRAY_FOREACH(m_pAllEnemy3, obj)
{
auto enemy3 = (Enemy*)obj;
if (enemy3->getLife()>0)
{
enemy3Blowup(enemy3);
}
}
}
void EnemyLayer::removeAllEnemy()
{
removeAllEnemy1();
removeAllEnemy2();
removeAllEnemy3();
}

首先先来讲一讲init()干了什么。其实很简单,加入了三个飞机的精灵帧(大飞机有两张图片,用来做动态效果),然后加载了三个飞机的爆炸动画。因为我们要频繁使用这些动画,所以要把它们加入精灵帧缓存,之后加入到动画缓存中(记得为动画命名)。三个飞机的添加方法是作为定时器的回调事件,我们可以根据需要调整定时器的时间。一般来说,大飞机的添加间隔要长一些。

有人可能奇怪,为什么要有removeEnemy3()?因为我们必须要等爆炸动画播放完之后,再执行移除操作。另外,我们在GameLayer中还有敌机的碰撞检测,单独写一个销毁方法也是必要的。

removeAllEnemy3()自然是为removeAllEnemy()提供的方法,释放了炸弹之后需要将所有的飞机全部移除。

UFOLayer

用来管理补给品的添加和运动。

UFOLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
class UFOLayer : public Layer
{
public:
UFOLayer(void);
~UFOLayer(void);
virtual bool init();
CREATE_FUNC(UFOLayer);
void AddMutiBullets(float dt);
void mutiBulletsMoveFinished(Node* pSender);
void AddBigBoom(float dt);
void bigBoomMoveFinished(Node* pSender);
void RemoveMutiBullets(Sprite* mutiBullets);
void RemoveBigBoom(Sprite* bigBoom);
public:
__Array* m_pAllMutiBullets;
__Array* m_pAllBigBoom;
};

方法看起来很多,其实大部分都是类似的。这里我们就拿双排子弹的补给品作为样例。

UFOLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "UFOLayer.h"
UFOLayer::UFOLayer(void)
{
m_pAllMutiBullets = __Array::create();
m_pAllMutiBullets->retain();
m_pAllBigBoom = __Array::create();
m_pAllBigBoom->retain();
}
UFOLayer::~UFOLayer(void)
{
m_pAllMutiBullets->release();
m_pAllMutiBullets = nullptr;
m_pAllBigBoom->release();
m_pAllBigBoom = nullptr;
}
bool UFOLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Layer::init());
this->schedule(schedule_selector(UFOLayer::AddMutiBullets),20.0);
this->schedule(schedule_selector(UFOLayer::AddBigBoom),20.0,kRepeatForever,5.0);
bRet = true;
} while (0);
return bRet;
}
void UFOLayer::AddMutiBullets(float dt)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/out_porp.mp3");
auto mutiBullets = Sprite::createWithSpriteFrameName("ufo1.png");
auto mutiBlletsSize = mutiBullets->getContentSize();
auto winSize = Director::getInstance()->getWinSize();
int minX = mutiBlletsSize.width/2;
int maxX = winSize.width-mutiBlletsSize.width/2;
int rangeX = maxX-minX;
int actualX = (rand()%rangeX)+minX;
mutiBullets->setPosition(Point(actualX, winSize.height+mutiBlletsSize.height/2));
this->addChild(mutiBullets);
this->m_pAllMutiBullets->addObject(mutiBullets);
auto move1 = MoveBy::create(0.5f, Point(0, -150));
auto move2 = MoveBy::create(0.3f, Point(0, 100));
auto move3 = MoveBy::create(1.0f, Point(0, 0-winSize.height-mutiBlletsSize.height/2));
auto moveDone = CallFuncN::create(CC_CALLBACK_1(UFOLayer::mutiBulletsMoveFinished, this));
auto sequence = Sequence::create(move1, move2, move3, moveDone, nullptr);
mutiBullets->runAction(sequence);
}
void UFOLayer::mutiBulletsMoveFinished(Node* pSender)
{
auto mutiBullets = (Sprite*)pSender;
this->removeChild(mutiBullets, true);
this->m_pAllMutiBullets->removeObject(mutiBullets);
}
void UFOLayer::AddBigBoom(float dt)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/out_porp.mp3");
auto bigBoom = Sprite::createWithSpriteFrameName("ufo2.png");
auto bigBoomSize = bigBoom->getContentSize();
auto winSize = Director::getInstance()->getWinSize();
int minX = bigBoomSize.width/2;
int maxX = winSize.width-bigBoomSize.width/2;
int rangeX = maxX-minX;
int actualX = (rand()%rangeX)+minX;
bigBoom->setPosition(Point(actualX, winSize.height+bigBoomSize.height/2));
this->addChild(bigBoom);
this->m_pAllBigBoom->addObject(bigBoom);
auto move1 = MoveBy::create(0.5, Point(0, -150));
auto move2 = MoveBy::create(0.3, Point(0, 100));
auto move3 = MoveBy::create(1.0, Point(0, 0-winSize.height-bigBoomSize.height/2));
auto moveDone = CallFuncN::create(CC_CALLBACK_1(UFOLayer::bigBoomMoveFinished, this));
auto sequence = Sequence::create(move1, move2, move3, moveDone, nullptr);
bigBoom->runAction(sequence);
}
void UFOLayer::bigBoomMoveFinished(Node* pSender)
{
auto bigBoom = (Sprite*)pSender;
this->removeChild(bigBoom, true);
this->m_pAllBigBoom->removeObject(bigBoom);
}
void UFOLayer::RemoveMutiBullets(Sprite* mutiBullets)
{
this->removeChild(mutiBullets, true);
this->m_pAllMutiBullets->removeObject(mutiBullets);
}
void UFOLayer::RemoveBigBoom(Sprite* bigBoom)
{
this->removeChild(bigBoom, true);
this->m_pAllBigBoom->removeObject(bigBoom);
}

构造函数和析构函数就是给Array数组添加计数,init()方法开启了两个定时器,不断地加入两种补给品。我们先看AddMutiBullets(),它前半部分的代码是修改精灵的属性,和敌机的添加其实是一样的。至于move1move2move3这几个动作,就是先往下再往上再往下的运动,可以根据自己的喜好设定不同的动作,让玩家更难或者更容易拿到补给品。

mutiBulletsMoveFinished()就是补给品飞出屏幕后,对精灵进行的删除操作,之前已经讲过类似的。

RemoveMutiBullets()用于碰撞检测,英雄捡到补给品后删除这个精灵。

ControlLayer

游戏的控制层,用于添加暂停按钮以及炸弹图片的显示。

ControlLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
#include "NoTouchLayer.h"
USING_NS_CC;
const int MAX_SCORE=1000000000;
class ControlLayer : public Layer
{
public:
ControlLayer(void);
~ControlLayer(void);
virtual bool init();
CREATE_FUNC(ControlLayer);
void menuPauseCallback(Ref* pSender);
void updateScore(int score);
public:
Label* scoreItem;
MenuItemSprite* pPauseItem;
NoTouchLayer* noTouchLayer;
};

ControlLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "ControlLayer.h"
ControlLayer::ControlLayer(void)
{
scoreItem = nullptr;
pPauseItem = nullptr;
}
ControlLayer::~ControlLayer(void)
{
}
bool ControlLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Layer::init());
auto winSize = Director::getInstance()->getWinSize();
auto normalPause = Sprite::createWithSpriteFrameName("game_pause_nor.png");
auto pressedPause = Sprite::createWithSpriteFrameName("game_pause_pressed.png");
pPauseItem = MenuItemSprite::create(normalPause, pressedPause, CC_CALLBACK_1(ControlLayer::menuPauseCallback, this));
pPauseItem->setPosition(Point(normalPause->getContentSize().width/2+10, winSize.height-normalPause->getContentSize().height/2-10));
auto menuPause = Menu::create(pPauseItem, nullptr);
menuPause->setPosition(Point::ZERO);
this->addChild(menuPause,101);
scoreItem = Label::createWithBMFont("font/font.fnt", "0");
scoreItem->setColor(Color3B(143,146,147));
scoreItem->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
scoreItem->setPosition(Point(pPauseItem->getPositionX()+normalPause->getContentSize().width/2+5, pPauseItem->getPositionY()));
this->addChild(scoreItem);
bRet = true;
} while (0);
return bRet;
}
void ControlLayer::menuPauseCallback(Ref* pSender)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/button.mp3");
if(!CCDirector::getInstance()->isPaused())
{
pPauseItem->setNormalImage(Sprite::createWithSpriteFrameName("game_resume_nor.png"));
pPauseItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_resume_pressed.png"));
CocosDenshion::SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
CocosDenshion::SimpleAudioEngine::getInstance()->stopAllEffects();
Director::getInstance()->pause();
noTouchLayer = NoTouchLayer::create();
this->addChild(noTouchLayer);
}
else
{
pPauseItem->setNormalImage(Sprite::createWithSpriteFrameName("game_pause_nor.png"));
pPauseItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_pause_pressed.png"));
CocosDenshion::SimpleAudioEngine::getInstance()->resumeBackgroundMusic();
Director::getInstance()->resume();
this->removeChild(noTouchLayer,true);
}
}
void ControlLayer::updateScore(int score)
{
if (score>=0 && score<=MAX_SCORE)
{
Value strScore(score);
scoreItem->setString(strScore.asString());
}
}

这里用到了一个类:MenuItemSprite。这个是按键特有的精灵,有正常、点中、禁用三种状态,主要也是为了增加按钮的动态效果。当然,这里我们用不到禁用状态。

pPauseItem进行了初始化等基本操作后,我们就需要把它添加到Menu中。Menu绑定的回调函数有一个判断,那就是游戏未暂停时,调用导演类单例暂停游戏,并且将暂停按钮的图片改为继续按钮的图片。如果游戏处于暂停状态,那么就继续游戏,并且将继续按钮改回暂停按钮。

scoreItem其实是用于记录分数,显示在暂停按钮的旁边。至于updateScore则是用于更新分数,我们击毁敌机时就要调用这个方法。这里我们没有使用.ttf文件创建Label,因为标签要经常改变,所以最好使用.font文件创建它

注意,我们这里有一个noTouchLayer。这个层是用于暂停游戏时,阻止玩家进行触摸。如果游戏继续,那么就把这个层移除掉即可。

NoTouchLayer

暂停游戏时,停止触摸。

NoTouchLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "cocos2d.h"
USING_NS_CC;
class NoTouchLayer : public Layer
{
public:
virtual bool init();
// implement the "static node()" method manually
CREATE_FUNC(NoTouchLayer);
virtual bool onTouchBegan(Touch *touch, Event *event);
};

NoTouchLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "NoTouchLayer.h"
bool NoTouchLayer::init(){
if (!Layer::init() )
{
return false;
}
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(NoTouchLayer::onTouchBegan, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
listener->setSwallowTouches(true);
return true;
}
bool NoTouchLayer::onTouchBegan(Touch *touch, Event *event)
{
return true;
}

我们可以看到,这个层虽然添加了监听器,但是并没有对单点触摸事件作出任何的实现,所以无论我们怎么点击,都不会有任何反应。那么有人可能会问,如果
添加了一个不可触摸的层,那么我们还怎么点击继续按钮呢?倒回去看ControlLayer的代码,我们会发现menuPausezOrder被设置为101,也就是说继续按钮是在不可触摸层的上面。

GameLayer

游戏主场景,管理游戏所有的元素。

GameLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
#include "PlaneLayer.h"
#include "BulletLayer.h"
#include "MutiBulletsLayer.h"
#include "ControlLayer.h"
#include "UFOLayer.h"
#include "EnemyLayer.h"
USING_NS_CC;
const int ENEMY1_SCORE = 1000;
const int ENEMY2_SCORE = 6000;
const int ENEMY3_SCORE = 30000;
const int MAX_BIGBOOM_COUNT = 100000;
const int TAG_BIGBOOM_MENUITEM = 1;
const int TAG_BIGBOOMCOUNT_LABEL = 2;
class GameLayer : public Layer
{
public:
GameLayer(void);
~GameLayer(void);
CREATE_FUNC(GameLayer);
virtual bool init();
void backgroundMove(float dt);
bool onTouchBegan(Touch* touch, Event *event);
void onTouchMoved(Touch* touch, Event *event);
void onTouchEnded(Touch* touch, Event *event);
void update(float dt);
void menuBigBoomCallback(Ref* pSender);
void updateBigBoomItem(int bigBoomCount);
static Level getCurLevel();
private:
Sprite* background1;
Sprite* background2;
PlaneLayer* planeLayer;
BulletLayer* bulletLayer;
MutiBulletsLayer* mutiBulletsLayer;
ControlLayer* controlLayer;
UFOLayer* ufoLayer;
EnemyLayer* enemyLayer;
Menu* menuBigBoom;
Label* bigBoomCountItem;
int score;
int bigBoomCount;
static Level level;
};

GameLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
#include "GameLayer.h"
#include "Enemy.h"
Level GameLayer::level = EASY;
GameLayer::GameLayer(void)
{
background1 = nullptr;
background2 = nullptr;
planeLayer = nullptr;
bulletLayer = nullptr;
mutiBulletsLayer = nullptr;
controlLayer = nullptr;
enemyLayer = nullptr;
score = 0;
bigBoomCount = 0;
}
GameLayer::~GameLayer(void)
{
}
bool GameLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!CCLayer::init());
level = EASY;
if (!CocosDenshion::SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
{
CocosDenshion::SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/game_music.mp3",true);
}
background1 = Sprite::createWithSpriteFrameName("background.png");
background1->setAnchorPoint(Point::ZERO);
background1->setPosition(Point::ZERO);
background1->getTexture()->setAliasTexParameters();
this->addChild(background1);
background2 = Sprite::createWithSpriteFrameName("background.png");
background2->setAnchorPoint(Point::ZERO);
background2->setPosition(Point(0,background2->getContentSize().height-2));
background2->getTexture()->setAliasTexParameters();
this->addChild(background2);
this->planeLayer = PlaneLayer::create();
this->addChild(planeLayer);
this->bulletLayer = BulletLayer::create();
this->addChild(bulletLayer);
this->bulletLayer->StartShoot();
this->mutiBulletsLayer = MutiBulletsLayer::create();
this->addChild(mutiBulletsLayer);
this->enemyLayer = EnemyLayer::create();
this->addChild(enemyLayer);
this->controlLayer = ControlLayer::create();
this->addChild(controlLayer);
this->ufoLayer = UFOLayer::create();
this->addChild(ufoLayer);
auto listener = EventListenerTouchOneByOne::create();
listener->onTouchBegan = CC_CALLBACK_2(GameLayer::onTouchBegan, this);
listener->onTouchMoved = CC_CALLBACK_2(GameLayer::onTouchMoved, this);
listener->onTouchEnded = CC_CALLBACK_2(GameLayer::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
this->schedule(schedule_selector(GameLayer::backgroundMove),0.01f);
this->scheduleUpdate();
bRet = true;
} while (0);
return bRet;
}
void GameLayer::backgroundMove(float dt)
{
background1->setPositionY(int(background1->getPositionY())-2);
background2->setPositionY(int(background1->getPositionY())+int(background1->getContentSize().height)-2);
if (background2->getPositionY() == 0)
{
background1->setPositionY(0);
}
}
bool GameLayer::onTouchBegan(Touch* touch, Event *event)
{
return true;
}
void GameLayer::onTouchMoved(Touch* touch, Event *event)
{
if (this->planeLayer->isAlive)
{
Point beginPoint = touch->getLocation();
//juggle the area of drag
Rect planeRect = this->planeLayer->getChildByTag(AIRPLANE)->boundingBox();
planeRect.origin.x -= 15;
planeRect.origin.y -= 15;
planeRect.size.width += 30;
planeRect.size.height += 30;
if(planeRect.containsPoint(this->getParent()->convertTouchToNodeSpace(touch)))
{
Point endPoint = touch->getPreviousLocation();
Point offSet = beginPoint - endPoint;
Point toPoint = this->planeLayer->getChildByTag(AIRPLANE)->getPosition() + offSet;
this->planeLayer->MoveTo(toPoint);
}
}
}
void GameLayer::onTouchEnded(Touch* touch, Event *event)
{
}
void GameLayer::update(float dt)
{
if (level == EASY && score >= 1000000)
{
level = MIDDLE;
}
else if (level == MIDDLE && score >= 2000000)
{
level = HARD;
}
auto bulletsToDelete = __Array::create();
bulletsToDelete->retain();
Ref* bt,*et,*ut;
//enemy1 & bullet CheckCollosion
CCARRAY_FOREACH(this->bulletLayer->m_pAllBullet, bt)
{
auto bullet = (Sprite*)bt;
auto enemy1sToDelete = CCArray::create();
enemy1sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy1, et)
{
auto enemy1 = (Enemy*)et;
if (bullet->boundingBox().intersectsRect(enemy1->getBoundingBox()))
{
if (enemy1->getLife() == 1)
{
enemy1->loseLife();
bulletsToDelete->addObject(bullet);
enemy1sToDelete->addObject(enemy1);
score += ENEMY1_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy1sToDelete, et)
{
auto enemy1 = (Enemy*)et;
this->enemyLayer->enemy1Blowup(enemy1);
}
enemy1sToDelete->release();
}
CCARRAY_FOREACH(bulletsToDelete, bt)
{
auto bullet = (Sprite*)bt;
this->bulletLayer->RemoveBullet(bullet);
}
bulletsToDelete->removeAllObjects();
//enemy2 & bullet CheckCollosion
CCARRAY_FOREACH(this->bulletLayer->m_pAllBullet, bt)
{
auto bullet = (Sprite*)bt;
auto enemy2sToDelete = __Array::create();
enemy2sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy2, et)
{
auto enemy2 = (Enemy*)et;
if (bullet->boundingBox().intersectsRect(enemy2->getBoundingBox()))
{
if (enemy2->getLife() > 1)
{
enemy2->loseLife();
bulletsToDelete->addObject(bullet);
}
else if(enemy2->getLife() == 1)
{
enemy2->loseLife();
bulletsToDelete->addObject(bullet);
enemy2sToDelete->addObject(enemy2);
score += ENEMY2_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy2sToDelete, et)
{
auto enemy2 = (Enemy*)et;
this->enemyLayer->enemy2Blowup(enemy2);
}
enemy2sToDelete->release();
}
CCARRAY_FOREACH(bulletsToDelete, bt)
{
auto bullet = (Sprite*)bt;
this->bulletLayer->RemoveBullet(bullet);
}
bulletsToDelete->removeAllObjects();
//enemy3 & bullet CheckCollosion
CCARRAY_FOREACH(this->bulletLayer->m_pAllBullet, bt)
{
auto bullet = (Sprite*)bt;
auto enemy3sToDelete=__Array::create();
enemy3sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy3, et)
{
auto enemy3 = (Enemy*)et;
if (bullet->boundingBox().intersectsRect(enemy3->getBoundingBox()))
{
if (enemy3->getLife()>1)
{
enemy3->loseLife();
bulletsToDelete->addObject(bullet);
}
else if(enemy3->getLife() == 1)
{
enemy3->loseLife();
bulletsToDelete->addObject(bullet);
enemy3sToDelete->addObject(enemy3);
score += ENEMY3_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy3sToDelete, et)
{
auto enemy3 = (Enemy*)et;
this->enemyLayer->enemy3Blowup(enemy3);
}
enemy3sToDelete->release();
}
CCARRAY_FOREACH(bulletsToDelete, bt)
{
auto bullet = (Sprite*)bt;
this->bulletLayer->RemoveBullet(bullet);
}
bulletsToDelete->removeAllObjects();
bulletsToDelete->release();
auto mutiBulletsToDelete = __Array::create();
mutiBulletsToDelete->retain();
Ref* mbt;
////enemy1 & mutiBullets CheckCollosion
CCARRAY_FOREACH(this->mutiBulletsLayer->m_pAllMutiBullets, mbt)
{
auto mutiBullets = (Sprite*)mbt;
auto enemy1sToDelete = __Array::create();
enemy1sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy1,et)
{
auto enemy1 = (Enemy*)et;
if (mutiBullets->boundingBox().intersectsRect(enemy1->getBoundingBox()))
{
if (enemy1->getLife() == 1)
{
enemy1->loseLife();
mutiBulletsToDelete->addObject(mutiBullets);
enemy1sToDelete->addObject(enemy1);
score += ENEMY1_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy1sToDelete, et)
{
auto enemy1 = (Enemy*) et;
this->enemyLayer->enemy1Blowup(enemy1);
}
enemy1sToDelete->release();
}
CCARRAY_FOREACH(mutiBulletsToDelete, mbt)
{
auto mutiBullets = (Sprite*)mbt;
this->mutiBulletsLayer->RemoveMutiBullets(mutiBullets);
}
mutiBulletsToDelete->removeAllObjects();
//enemy2 & mutiBullets CheckCollosion
CCARRAY_FOREACH(this->mutiBulletsLayer->m_pAllMutiBullets, mbt)
{
auto mutiBullets = (Sprite*)mbt;
auto enemy2sToDelete = __Array::create();
enemy2sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy2, et)
{
auto enemy2 = (Enemy*)et;
if (mutiBullets->boundingBox().intersectsRect(enemy2->getBoundingBox()))
{
if (enemy2->getLife() > 1)
{
enemy2->loseLife();
mutiBulletsToDelete->addObject(mutiBullets);
}
else if(enemy2->getLife() == 1)
{
enemy2->loseLife();
mutiBulletsToDelete->addObject(mutiBullets);
enemy2sToDelete->addObject(enemy2);
score += ENEMY2_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy2sToDelete, et)
{
auto enemy2 = (Enemy*)et;
this->enemyLayer->enemy2Blowup(enemy2);
}
enemy2sToDelete->release();
}
CCARRAY_FOREACH(mutiBulletsToDelete, mbt)
{
auto mutiBullets = (Sprite*)mbt;
this->mutiBulletsLayer->RemoveMutiBullets(mutiBullets);
}
mutiBulletsToDelete->removeAllObjects();
//enemy3 & mutiBullets CheckCollosion
CCARRAY_FOREACH(this->mutiBulletsLayer->m_pAllMutiBullets, mbt)
{
auto mutiBullets = (Sprite*)mbt;
auto enemy3sToDelete = __Array::create();
enemy3sToDelete->retain();
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy3, et)
{
auto enemy3 = (Enemy*)et;
if (mutiBullets->boundingBox().intersectsRect(enemy3->getBoundingBox()))
{
if (enemy3->getLife() > 1)
{
enemy3->loseLife();
mutiBulletsToDelete->addObject(mutiBullets);
}
else if(enemy3->getLife() == 1)
{
enemy3->loseLife();
mutiBulletsToDelete->addObject(mutiBullets);
enemy3sToDelete->addObject(enemy3);
score += ENEMY3_SCORE;
this->controlLayer->updateScore(score);
}
else ;
}
}
CCARRAY_FOREACH(enemy3sToDelete, et)
{
auto enemy3 = (Enemy*)et;
this->enemyLayer->enemy3Blowup(enemy3);
}
enemy3sToDelete->release();
}
CCARRAY_FOREACH(mutiBulletsToDelete, mbt)
{
auto mutiBullets = (Sprite*)mbt;
this->mutiBulletsLayer->RemoveMutiBullets(mutiBullets);
}
mutiBulletsToDelete->removeAllObjects();
mutiBulletsToDelete->release();
auto airplaneRect = this->planeLayer->getChildByTag(AIRPLANE)->boundingBox();
airplaneRect.origin.x += 30;
airplaneRect.size.width -= 60;
//enemy1 & airplane CheckCollosion
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy1, et)
{
auto enemy1 = (Enemy*)et;
if (enemy1->getLife() > 0)
{
if (airplaneRect.intersectsRect(enemy1->getBoundingBox()))
{
this->unscheduleAllSelectors();
this->bulletLayer->StopShoot();
this->mutiBulletsLayer->StopShoot();
this->planeLayer->Blowup(score);
return;
}
}
}
//enemy2 & airplane CheckCollosion
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy2, et)
{
auto enemy2 = (Enemy*)et;
if (enemy2->getLife() > 0)
{
if (airplaneRect.intersectsRect(enemy2->getBoundingBox()))
{
this->unscheduleAllSelectors();
this->bulletLayer->StopShoot();
this->mutiBulletsLayer->StopShoot();
this->planeLayer->Blowup(score);
return;
}
}
}
//enemy3 & airplane CheckCollosion
CCARRAY_FOREACH(this->enemyLayer->m_pAllEnemy3, et)
{
auto enemy3 = (Enemy*)et;
if (enemy3->getLife() > 0)
{
if (airplaneRect.intersectsRect(enemy3->getBoundingBox()))
{
this->unscheduleAllSelectors();
this->bulletLayer->StopShoot();
this->mutiBulletsLayer->StopShoot();
this->planeLayer->Blowup(score);
return;
}
}
}
//mutiBullets & airplane CheckCollision
CCARRAY_FOREACH(this->ufoLayer->m_pAllMutiBullets, ut)
{
auto mutiBullets = (Sprite*)ut;
if (this->planeLayer->getChildByTag(AIRPLANE)->boundingBox().intersectsRect(mutiBullets->boundingBox()))
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/get_double_laser.mp3");
this->ufoLayer->RemoveMutiBullets(mutiBullets);
this->bulletLayer->StopShoot();
this->mutiBulletsLayer->StartShoot();
this->bulletLayer->StartShoot(6.2f);
}
}
//bigBoom & airplane CheckCollision
CCARRAY_FOREACH(this->ufoLayer->m_pAllBigBoom, ut)
{
auto bigBoom = (Sprite*)ut;
if (this->planeLayer->getChildByTag(AIRPLANE)->boundingBox().intersectsRect(bigBoom->boundingBox()))
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/get_bomb.mp3");
this->ufoLayer->RemoveBigBoom(bigBoom);
bigBoomCount++;
updateBigBoomItem(bigBoomCount);
}
}
}
void GameLayer::updateBigBoomItem(int bigBoomCount)
{
auto normalBomb = Sprite::createWithSpriteFrameName("bomb.png");
auto pressedBomb = Sprite::createWithSpriteFrameName("bomb.png");
if (bigBoomCount < 0)
{
return;
}
else if (bigBoomCount == 0)
{
if(this->getChildByTag(TAG_BIGBOOM_MENUITEM))
{
this->removeChildByTag(TAG_BIGBOOM_MENUITEM, true);
}
if (this->getChildByTag(TAG_BIGBOOMCOUNT_LABEL))
{
this->removeChildByTag(TAG_BIGBOOMCOUNT_LABEL, true);
}
}
else if (bigBoomCount == 1)
{
if (!this->getChildByTag(TAG_BIGBOOM_MENUITEM))
{
auto pBigBoomItem = MenuItemSprite::create(normalBomb, pressedBomb, nullptr, CC_CALLBACK_1(GameLayer::menuBigBoomCallback, this));
pBigBoomItem->setPosition(Point(normalBomb->getContentSize().width/2+10, normalBomb->getContentSize().height/2+10));
menuBigBoom = Menu::create(pBigBoomItem, nullptr);
menuBigBoom->setPosition(Point::ZERO);
this->addChild(menuBigBoom, 0, TAG_BIGBOOM_MENUITEM);
}
if (this->getChildByTag(TAG_BIGBOOMCOUNT_LABEL))
{
this->removeChildByTag(TAG_BIGBOOMCOUNT_LABEL,true);
}
}
else
{
if (!this->getChildByTag(TAG_BIGBOOM_MENUITEM))
{
auto pBigBoomItem = MenuItemSprite::create(normalBomb, pressedBomb, nullptr, CC_CALLBACK_1(GameLayer::menuBigBoomCallback, this));
pBigBoomItem->setPosition(Point(normalBomb->getContentSize().width/2+10, normalBomb->getContentSize().height/2+10));
menuBigBoom = Menu::create(pBigBoomItem, nullptr);
menuBigBoom->setPosition(Point::ZERO);
this->addChild(menuBigBoom, 0, TAG_BIGBOOM_MENUITEM);
}
if (this->getChildByTag(TAG_BIGBOOMCOUNT_LABEL))
{
this->removeChildByTag(TAG_BIGBOOMCOUNT_LABEL,true);
}
if (bigBoomCount >= 0 && bigBoomCount <= MAX_BIGBOOM_COUNT)
{
auto strScore = __String::createWithFormat("X%d", bigBoomCount);
bigBoomCountItem = Label::createWithBMFont("font/font.fnt", strScore->getCString());
bigBoomCountItem->setColor(Color3B(143, 146, 147));
bigBoomCountItem->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
bigBoomCountItem->setPosition(Point(normalBomb->getContentSize().width+15, normalBomb->getContentSize().height/2+5));
this->addChild(bigBoomCountItem, 0, TAG_BIGBOOMCOUNT_LABEL);
}
}
}
void GameLayer::menuBigBoomCallback(Ref* pSender)
{
if(bigBoomCount > 0 && !Director::getInstance()->isPaused())
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/use_bomb.mp3");
bigBoomCount--;
score += this->enemyLayer->m_pAllEnemy1->count() * ENEMY1_SCORE;
score += this->enemyLayer->m_pAllEnemy2->count() * ENEMY2_SCORE;
score += this->enemyLayer->m_pAllEnemy3->count() * ENEMY3_SCORE;
this->enemyLayer->removeAllEnemy();
updateBigBoomItem(bigBoomCount);
this->controlLayer->updateScore(score);
}
}
Level GameLayer::getCurLevel()
{
return level;
}

到目前为止,游戏的基本元素我们都介绍完了,现在是到了要把它们综合在一起的时候了。由于代码较多,接下来只讲各种方法的功能,不会针对语句进行分析。

GameLayer的各种逻辑实现

无限长的背景图

我们开启了一个自定义定时器,用于移动背景图片。由于我们的背景图是有限长的(也不可能无限长),所以我们创建了两张背景图,将它们上下连接起来(高度-2是为了连接紧密),然后让它们同时往下移动,当第一张图片完全移动出屏幕时,我们将这两张图片恢复原位,开始新一轮的移动。具体效果如下:

我们查看backgroundMove()会发现,第二张图片始终以第一张图片的位置为准,所以只要第一张图片回归原位,第二张图片也会跟着回归,这也是一种比较巧妙的方法。

英雄的触摸移动

我们首先要获取到英雄,这里就通过已经定好的Tag值进行获取(也可以把英雄做成单例)。随后就是调整Rect的大小,将飞机的触摸体积适当增大,方便玩家拖动飞机。在判断中我们进行了坐标转换,不懂的请翻看学习笔记7。

getPreviousLocation()是获取上一次的触摸坐标,但其实我们可以使用getDelta(),它能够获取前后两次移动的偏移量,更加方便。我们将当前触摸坐标减去之前的坐标,就是偏移量。然后我们将飞机的坐标加上偏移量,就是飞机的位置。注意,这里改变飞机的位置是使用类方法MoveTo(),为的是限制飞机的移动范围。

碰撞检测

一般来说,游戏的碰撞检测每帧检测一次就足够了,因为游戏的帧率通常能够达到60帧。随着游戏时间变长,游戏的难度也应该增加。具体的表现就是修改敌机的移动速度,让它们飞得更快。之前在讲EnemyLayer时,敌机接受一个难度系数,修改敌机飞行速度的上限,从而出现更多飞行速度快的敌机。这里是通过判断玩家获得的分数,不断地增加难度。

关于碰撞检测有几种情况:

  • 敌机和子弹碰撞
  • 敌机和英雄碰撞
  • 补给品和英雄碰撞

这里我们拿大飞机作为例子,其他的两种飞机可以类推。我们首先要创建两个个数组,一个用于存储所有需要被删除的子弹,另一个用于存储需要被删除的大飞机。然后循环检测所有大飞机,是否被子弹击中。当被击中时,如果大飞机血量大于一,那么就扣一滴血,并且将子弹添加入数组。如果只有一滴血,那么就将大飞机和子弹加入数组,并且修改得分,同时调用updateScore()更新分数。

请注意我们的循环顺序,我们先检查一颗子弹,判断他们有没有和敌机碰撞。当一颗子弹被检查完后,删除被击中的大飞机。当所有子弹被检查完后,删除掉发生碰撞的子弹。至于补给品和英雄、敌机和英雄,其实都是类似的,这里就不再赘述。

更新炸弹数量

当玩家捡到炸弹时,我们要在右下角显示炸弹的图片,并且修改炸弹数量。如果没有炸弹,我们就将炸弹图片移除,如果炸弹数量为一,我们就要先判断是捡到炸弹还是用掉了炸弹。当然,你觉得这样很复杂的话,我们可以直接让炸弹图片一直显示,只修改数量即可。

炸弹效果

当我们点击炸弹后,将炸弹数量减一,并且根据enemyLayer中的数组,获取当前屏幕上的飞机数量,从而修改得分,并且销毁所有飞机。

GameOverScene

GameOverScene.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef __GameOverScene__H__
#define __GameOverScene__H__
#include "cocos2d.h"
#include "GameOverLayer.h"
USING_NS_CC;
class GameOverScene : public Scene
{
public:
GameOverScene(void);
~GameOverScene(void);
virtual bool init();
static GameOverScene* create(int passScore);
public:
GameOverLayer* gameOverLayer;
int score;
};
#endif

GameOverScene.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include "GameOverScene.h"
GameOverScene::GameOverScene(void)
{
gameOverLayer = nullptr;
score = 0;
}
GameOverScene::~GameOverScene(void)
{
}
GameOverScene* GameOverScene::create(int passScore)
{
GameOverScene *pRet = new GameOverScene();
pRet->score = passScore;
if (pRet && pRet->init())
{
pRet->autorelease();
}
else
{
delete pRet;
pRet = nullptr;
}
return pRet;
}
bool GameOverScene::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Scene::init());
gameOverLayer = GameOverLayer::create(score);
this->addChild(gameOverLayer);
bRet = true;
} while (0);
return bRet;
}

GameOverLayer

游戏结束画面,显示玩家的得分和再来一次的按钮。

GameOverLayer.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
class GameOverLayer : public Layer
{
public:
GameOverLayer(void);
~GameOverLayer(void);
virtual bool init();
static GameOverLayer* create(int passScore);
void menuBackCallback(Ref* pSender);
void beginChangeHighestScore(Node* pNode);
void showAD();
public:
int score;
static int highestHistoryScore;
Label* highestScore;
};

GameOverLayer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "GameOverLayer.h"
#include "GameScene.h"
int GameOverLayer::highestHistoryScore = 0;
GameOverLayer::GameOverLayer(void)
{
score = 0;
highestScore = nullptr;
}
GameOverLayer::~GameOverLayer(void)
{
}
GameOverLayer* GameOverLayer::create(int passScore)
{
GameOverLayer *pRet = new GameOverLayer();
pRet->score = passScore;
if (pRet && pRet->init())
{
pRet->autorelease();
}
else
{
delete pRet;
pRet = nullptr;
}
return pRet;
}
bool GameOverLayer::init()
{
bool bRet = false;
do
{
CC_BREAK_IF(!Layer::init());
if (CocosDenshion::SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
{
CocosDenshion::SimpleAudioEngine::getInstance()->stopBackgroundMusic();
}
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/game_over.mp3");
auto winSize = Director::getInstance()->getWinSize();
Sprite* background = Sprite::createWithSpriteFrameName("gameover.png");
background->setPosition(Point(winSize.width/2, winSize.height/2));
this->addChild(background);
auto normalBackToGame = Sprite::createWithSpriteFrameName("btn_finish.png");
auto pressedBackToGame = Sprite::createWithSpriteFrameName("btn_finish.png");
auto pBackItem = MenuItemSprite::create(normalBackToGame,
pressedBackToGame,
nullptr,
CC_CALLBACK_1(GameOverLayer::menuBackCallback, this));
pBackItem->setPosition(Point(winSize.width-normalBackToGame->getContentSize().width/2-10, normalBackToGame->getContentSize().height/2+10));
auto menuBack = Menu::create(pBackItem, nullptr);
menuBack->setPosition(Point::ZERO);
this->addChild(menuBack);
Value strScore(score);
auto finalScore = Label::createWithBMFont("font/font.fnt", strScore.asString());
finalScore->setColor(Color3B(143,146,147));
finalScore->setPosition(Point(winSize.width/2, winSize.height/2));
this->addChild(finalScore);
auto delay = DelayTime::create(1.0f);
auto scalebig = ScaleTo::create(1.0f,3.0f);
auto scalelittle = ScaleTo::create(0.3f,2.0f);
auto showAD = CallFunc::create(CC_CALLBACK_0(GameOverLayer::showAD, this));
auto sequence = Sequence::create(delay, scalebig, scalelittle, showAD, nullptr);
finalScore->runAction(sequence);
Value strHighestScore(highestHistoryScore);
highestScore = Label::createWithBMFont("font/font.fnt", strHighestScore.asString());
highestScore->setColor(Color3B(143,146,147));
highestScore->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
highestScore->setPosition(Point(140,winSize.height-30));
this->addChild(highestScore);
if (score>highestHistoryScore)
{
UserDefault::getInstance()->setIntegerForKey("HighestScore", score);
highestHistoryScore = score;
auto delayChange = DelayTime::create(1.3f);
auto moveOut = MoveBy::create(0.1f, Point(0,100));
auto beginChange = CallFuncN::create(CC_CALLBACK_1(GameOverLayer::beginChangeHighestScore, this));
auto moveIn = MoveBy::create(0.1f,Point(0,-100));
auto sequence = Sequence::create(delayChange, moveOut, beginChange, moveIn, nullptr);
highestScore->runAction(sequence);
}
bRet = true;
} while (0);
return bRet;
}
void GameOverLayer::menuBackCallback(Ref* pSender)
{
auto pScene = GameScene::create();
auto animateScene = TransitionSlideInL::create(1.0f, pScene);
Director::getInstance()->replaceScene(animateScene);
}
void GameOverLayer::beginChangeHighestScore(Node* pNode)
{
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("sound/achievement.mp3");
Value changeScore(score);
highestScore->setString(changeScore.asString());
}
void GameOverLayer::showAD()
{
}

代码看起来很多,但其实都是添加各种元素并修改属性。实现的方式并不复杂,我就不再多说了。

总结

微信飞机大战是一个正式的游戏,我们之前的项目实战只能算是练习作品。虽然玩法很简单,但我们能够看到,真正做一款游戏是比较复杂的,哪怕这个游戏很小,也需要严谨的逻辑。这个游戏的代码最具代表性的地方,就是将不同的元素分隔到一个个层中,这么做不仅逻辑清晰,还十分利于分工合作,所以做游戏的时候尽量学习这种方式。