【三】仿微信飞机大战cocos2d-x3.0rc1

时间:2023-02-07 18:20:36

上一篇:【二】仿微信飞机大战cocos2d-x3.0rc1

今天的任务是:

1.我机子弹无限量发射

2.三种类型敌机的出现

3.敌机自己碰墙死掉


一、效果界面展示

【三】仿微信飞机大战cocos2d-x3.0rc1

暂时没有实现子弹打中敌机(当你打中敌机时,敌机因为太兴奋,以至于当场自爆,这些都是后话)

二、工程解决方案

【三】仿微信飞机大战cocos2d-x3.0rc1
子弹层:BulletLayer文件敌机层:EnemyLayer文件敌机精灵数据:Enemy文件

三、子弹无限量供应

飞机头尖尖处发出猥琐子弹,配上飞机诡异的身法,有一种让人欲罢不能的感觉。
BulletLayer.h文件:
#ifndef __BULLET_LAYER_H__
#define __BULLET_LAYER_H__
#pragma once
#include "cocos2d.h"
#include "PlaneLayer.h"

USING_NS_CC;

class BulletLayer : public Layer
{
public:
BulletLayer();
~BulletLayer();
virtual bool init();
CREATE_FUNC(BulletLayer);

public:
void BeginBulletShoot(float dt=0.0f);// 开启子弹射击
void StopBulletShoot();// 停止子弹射击
void addBullet(float dt);// 添加子弹
void removeBullet(Node* pNode);// 移除子弹
};

#endif //__BULLET_LAYER_H__
结构和之前的layer一样,无非加了一些自己的方法。
流程:1.想打飞机,则开启子弹开关  2.裤子都脱了,不能什么不干吧!发射子弹3.某些情况移除子弹 (你们是不是又想歪了)4.不想打飞机,就停止射击

我们可以在创建子弹层的时候,开启射击开关:
bool BulletLayer::init()
{
if (!Layer::init())
{
return false;
}

BeginBulletShoot();
return true;
}

射击开关实现如下:
void BulletLayer::BeginBulletShoot( float dt )
{
this->schedule(schedule_selector(BulletLayer::addBullet), 0.2f, kRepeatForever, dt);
}
每隔0.2秒,就发射一颗子弹(调用addBullet),kRepeatForever(永无止境的打飞机,少年这是不科学滴~请爱惜身体)。

子弹发射过程:
void BulletLayer::addBullet( float dt )
{
// 子弹
auto bullet = Sprite::createWithSpriteFrameName("bullet1.png");
if (NULL == bullet)
{
return;
}

this->addChild(bullet);

// 获得飞机的位置
Point planePos = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
Point bulletPos = Point(planePos.x + 2, planePos.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height/2);
bullet->setPosition(bulletPos);

// 飞行长度 飞行就是超出窗体
float flyLen = Director::getInstance()->getWinSize().height + bullet->getContentSize().height/2 - bulletPos.y;
float flyVelocity = 320/1; //飞行速度
float realFlyDuration = flyLen/flyVelocity; // 飞行时间
auto actionMove=MoveTo::create(realFlyDuration,Point(bulletPos.x,Director::getInstance()->getWinSize().height+bullet->getContentSize().height/2));
auto actionDone=CallFuncN::create(CC_CALLBACK_1(BulletLayer::removeBullet, this));

Sequence* sequence=Sequence::create(actionMove,actionDone,NULL);
bullet->runAction(sequence);
}
这里有个地方需要注意:PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition(); 这个地方,和我之前写的有点出入,因为那时候,我没考虑到飞机会被其他的模块调用。(飞机是在飞机头出现的,飞机又是可以移动的,所以子弹的位置需要根据飞机来定)不过也巧,这里我可以顺便给大家讲解下CREATE_FUNC宏。回到飞机层,下面展现下休整后的飞机层PlaneLayer.h头文件:
#pragma once
#include "cocos2d.h"
USING_NS_CC;

enum Enum_Plane
{
AIRPLANE = 1,
};

class PlaneLayer : public Layer
{
public:
PlaneLayer();

~PlaneLayer();

static PlaneLayer* create(); // 实现创建

virtual bool init();

public:
void checkBorder(float dt); // 边界检测

public:
static PlaneLayer* sharedPlane; // 提供全局的指针 类似单例
bool isAlive; // 飞机是否活着
};
少侠们,发现什么不同吗?yes,我提供出了一个static PlaneLayer* sharedPlane;全局的指针,这样的话,就可以被其他模块直接调用了。还有什么不同!没错~CREATE_FUNC(PlaneLayer)定义不见了。其实static PlaneLayer* create()和CREATE_FUNC(PlaneLayer)是一样的,只是之前被宏代替了,我们为什么不用CREATE_FUNC了呢?先给你们看下static PlaneLayer* create()的实现:
PlaneLayer* PlaneLayer::create()
{
// 不需要手动释放create出来的对象
PlaneLayer *pRet = new PlaneLayer();
if (pRet && pRet->init())
{
pRet->autorelease(); //将它交由内存管理器进行内存释放管理
sharedPlane = pRet; // 赋给指针
return pRet;
}
else
{
CC_SAFE_DELETE(pRet); // 使用delete操作符删除一个C++对象pRet,如果pRet为NULL,则不进行操作
return NULL;
}
}
CREATE_FUNC转换过来,其实也是这样的,不信?那就对比一下,CREATE_FUNC宏:
#define CREATE_FUNC(__TYPE__) \
static __TYPE__* create() \
{ \
__TYPE__ *pRet = new __TYPE__(); \
if (pRet && pRet->init()) \
{ \
pRet->autorelease(); \
return pRet; \
} \
else \
{ \
delete pRet; \
pRet = NULL; \
return NULL; \
} \
}
是不是咯。其实他们很像,只是我们多了sharedPlane = pRet; 把我们生成的layer层提供出来给静态成员,方便外界模块的调用,所以我们才重新编写函数实现。把上面的代码替换之后,别忘了PlaneLayer.cpp在里写下PlaneLayer* PlaneLayer::sharedPlane = NULL;
我们根据飞机当前的位置,很容易就算出了子弹此刻应该出现的位置,设置它:
Point planePos = PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getPosition();
Point bulletPos = Point(planePos.x + 2, planePos.y + PlaneLayer::sharedPlane->getChildByTag(AIRPLANE)->getContentSize().height/2);
bullet->setPosition(bulletPos);
接着让子弹按X坐标不变,再定一个Y的终点坐标,这样飞机就会按固定直线,飞离你的飞机到指定位置,然后调用子弹消亡函数removeBullet()。
解释一下MoveTo::create(5,Point(100,100)):用5秒的时间移动到Point(100,100)处。
移除子弹:子弹射的太远,有时候也不太好(你不销毁的话,会占用内存的,不用的,就释放掉),我们有必要毁灭证据。
void BulletLayer::removeBullet( Node* pNode )
{
if (NULL == pNode)
{
return;
}

Sprite* bullet=(Sprite*)pNode;
this->removeChild(bullet,true);
}
在飞行到指定目的地,触发回调后,我们用本layer层直接removeChild。true是停止被移除精灵的所有动作。

停止射击:
void BulletLayer::StopBulletShoot()
{
this->unschedule(schedule_selector(BulletLayer::addBullet));
}

是时候给让飞机打起来了!回到主场景GameScene下的GameLayer::init()。加入以下这段:
// 开启子弹
bulletLayer = BulletLayer::create();
this->addChild(bulletLayer);
当然别忘记在GameScene.h中加头文件:
#ifndef __GAME_SCENE_H__
#define __GAME_SCENE_H__
#include "cocos2d.h"
#include "PlaneLayer.h"
#include "BulletLayer.h"
#include "EnemyLayer.h"

USING_NS_CC;
哎呀,不小心把敌机的头文件也包含进来了。怎么办,怎么办~
没办法,只好也把敌机也给实现了吧。

四、群魔乱舞的敌机

老规矩撒~继续摆出头文件EnemyLayer.h:
#pragma once
#include "cocos2d.h"
#include "EnemyDefine.h"
#include "Enemy.h"

USING_NS_CC;

class EnemyLayer : public Layer
{
public:
EnemyLayer();
~EnemyLayer();
virtual bool init();
CREATE_FUNC(EnemyLayer);

public:
void addEnemy1(float dt); // 添加敌机1
void addEnemy2(float dt); // 添加敌机2
void addEnemy3(float dt); // 添加敌机3

void removeEnemy(Node *pNode, EnemyType eType);// 移除敌机pNode, eType是敌机类型,本来想用来加相应特效的,不过算了
public:

};
我们赶快来看看init()的实现!!
bool EnemyLayer::init()
{
if (!Layer::init())
{
return false;
}

// 敌机1被打爆动作帧
cocos2d::Vector<SpriteFrame*> vecTemp;
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
pAnimation1->setDelayPerUnit(0.1f);

// 添加到AnimationCache,并且命名为Enemy1Blowup
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

// 敌机2被打爆动作帧
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down4.png"));

Animation *pAnimation2 = Animation::createWithSpriteFrames(vecTemp);
pAnimation2->setDelayPerUnit(0.1f);
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy2Blowup");
this->schedule(schedule_selector(EnemyLayer::addEnemy2), 5.0f);

// 敌机3被打爆动作帧
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down4.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down5.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down6.png"));

Animation *pAnimation3 = Animation::createWithSpriteFrames(vecTemp);
pAnimation3->setDelayPerUnit(0.1f);
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy3Blowup");
this->schedule(schedule_selector(EnemyLayer::addEnemy3), 10.0f);

return true;
}

我了个去!怎么这么多!不是说好不说飞机爆炸的吗?好吧,我承认我手贱,我把飞机爆炸的先缓存起来了。既然写了,就必须给你们解释一下。
我们就拿出敌机1来解释吧。其他其实都一样:创建一个容器Vector:cocos2d::Vector<SpriteFrame*> vecTemp。这个Vector存储的只能是继承了Ref的对象,Node,Sprite等都继承了Ref。这个vecTemp.clear()。每次清理一下,我才放心。然后就是把爆炸后的四个状态对象加入到Vector中去:
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

接着就是让Animation把这些图像缓存起来,并且设置成0.1秒的间隔显示时间。
Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
pAnimation1->setDelayPerUnit(0.1f);

我们缓存起来之后,怎么取出来用呢?就用到了下面的这种形式:
// 添加到AnimationCache,并且命名为Enemy1Blowup
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

好了让我们回归到如何出现敌机上来:把第一架飞机设置成一秒一架的速率出现(有人说会不会太快了,你子弹0.2秒一个,还不许人家敌机1秒一次吗?):
this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

添加敌机void EnemyLayer::addEnemy1( float dt ):
void EnemyLayer::addEnemy1( float dt )
{
EnemySprite *pEnemySprite = EnemySprite::create();
pEnemySprite->setEnemyByType(Enemy1);
this->addChild(pEnemySprite);

// 设置运动轨迹 以及到终点时调用的函数
auto actionMove=MoveTo::create(3.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy1));
Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
pEnemySprite->runAction(sequence);
}
EnemySprite是等下我们要讲的,就先不管这个家伙,我们只要知道EnemySprite它继承了Node。这样我们就可以让EnemySprite加入到EnemyLayer中去。记住我们是让EnemyLayer层里面加入多个以及多种肆无忌惮的敌机,然后射死它们。另外pEnemySprite->getPositionX()与0 - pEnemySprite->getSprite()->getContentSize().height/2这两个是说敌机的X位置不变,Y的位置移动到负的敌机高度的一半。
addEnemy2,addEnemy3都一样,只是把枚举改一下就可以了。加入敌机1后,我们设置敌机的飞行轨迹actionMove,以及飞行完以后,需要擦屁股的函数EnemyLayer::removeEnemy。因为CC_CALLBACK_1可以额外带一个参数,所以我就带了个Enemy1,this是必带的。这里就实现了,敌机出现,以及敌机消亡的过程。那么敌机是怎么产生的呢?因为有我机,必需拿敌机来衬托我机的强大以及持久呗。好了,不废话,让我们看下敌机真正产生的过程:
/***********************************************

因为敌方飞机具有生命值等特性,需要附加属性

************************************************/
#include "cocos2d.h"
#include "EnemyDefine.h"

USING_NS_CC;


class EnemySprite : public Node
{
public:
EnemySprite();
~EnemySprite();
virtual bool init();
CREATE_FUNC(EnemySprite);

public:
void setEnemyByType(EnemyType enType);
Sprite* getSprite();

private:
Sprite *pEnemySprite;
int nLife;
};
我们这里的飞机有3种,每种的生命值都不一样,所以我们多了个nLife表示敌机生命值。而且我们还需要生成敌机,所以多了个Sprite。再让EnemySprite 继承 Node,这样就可以让敌机层加入这个Node,Node它自己也会addchild(Sprite)的。Layer中加入了Node,Node里面又加入了Sprite。这个你们应该能懂吧。
让我们看下init()的实现:
bool EnemySprite::init()
{
bool pRet = true;
if (!Node::init())
{
pRet = false;
}

return pRet;
}
我擦。居然什么都没有,创建敌机的方法跑哪里去了!!
看下之前被我们抛弃的那三行:
EnemySprite *pEnemySprite = EnemySprite::create();
pEnemySprite->setEnemyByType(Enemy2);
this->addChild(pEnemySprite);
用到了setEnemyByType函数,原来是通过敌机类型来产生不同类型的敌机的。
void EnemySprite::setEnemyByType( EnemyType enType )
{
switch (enType)
{
case Enemy1:
pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1.png"));
nLife = ENEMY1_MAXLIFE;
break;
case Enemy2:
pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2.png"));
nLife = ENEMY2_MAXLIFE;
break;
case Enemy3:
pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n1.png"));
nLife = ENEMY3_MAXLIFE;
break;
case Enemy4:
pEnemySprite = Sprite::createWithSpriteFrame(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_n2.png"));
nLife = ENEMY3_MAXLIFE;
break;
default:
return;
break;
}

this->addChild(pEnemySprite);
Size winSize = Director::getInstance()->getWinSize();
Size enemySize = pEnemySprite->getContentSize();
int minX=enemySize.width/2;
int maxX=winSize.width - enemySize.width/2;
int rangeX=maxX-minX;
int actualX=(rand()%rangeX) + minX;

// 设置敌机Node方位 Node包含Sprite
this->setPosition(Point(actualX, winSize.height + enemySize.height/2));
}
我们通过不同类型生成不同的敌机,并且赋予不同的生命。然后加入到Node里面去。接着就是设置这个Node的方位。ok,到这里,我们已经把敌机也产生了。最后剩下一个,就是我们让Node把精灵提供出来Sprite* EnemySprite::getSprite():
Sprite* EnemySprite::getSprite()
{
return pEnemySprite;
}

篇幅太长了,我提供了头文件,而且几乎每个方法都实现了一遍。这里就提供一下EnemyLayer.cpp:
#include "EnemyLayer.h"

EnemyLayer::EnemyLayer()
{

}

EnemyLayer::~EnemyLayer()
{

}

bool EnemyLayer::init()
{
if (!Layer::init())
{
return false;
}

// 敌机1被打爆动作帧
cocos2d::Vector<SpriteFrame*> vecTemp;
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy1_down4.png"));

Animation *pAnimation1 = Animation::createWithSpriteFrames(vecTemp);
pAnimation1->setDelayPerUnit(0.1f);

// 添加到AnimationCache,并且命名为Enemy1Blowup
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy1Blowup");

this->schedule(schedule_selector(EnemyLayer::addEnemy1), 1.0f); // 每1秒出现一架敌机1

// 敌机2被打爆动作帧
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy2_down4.png"));

Animation *pAnimation2 = Animation::createWithSpriteFrames(vecTemp);
pAnimation2->setDelayPerUnit(0.1f);
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy2Blowup");
this->schedule(schedule_selector(EnemyLayer::addEnemy2), 5.0f);

// 敌机3被打爆动作帧
vecTemp.clear();
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down1.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down2.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down3.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down4.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down5.png"));
vecTemp.pushBack(SpriteFrameCache::getInstance()->getSpriteFrameByName("enemy3_down6.png"));

Animation *pAnimation3 = Animation::createWithSpriteFrames(vecTemp);
pAnimation3->setDelayPerUnit(0.1f);
AnimationCache::getInstance()->addAnimation(pAnimation1, "Enemy3Blowup");
this->schedule(schedule_selector(EnemyLayer::addEnemy3), 10.0f);

return true;
}

void EnemyLayer::addEnemy1( float dt )
{
EnemySprite *pEnemySprite = EnemySprite::create();
pEnemySprite->setEnemyByType(Enemy1);
this->addChild(pEnemySprite);

// 设置运动轨迹 以及到终点时调用的函数
auto actionMove=MoveTo::create(3.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy1));
Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
pEnemySprite->runAction(sequence);
}

void EnemyLayer::addEnemy2( float dt )
{
EnemySprite *pEnemySprite = EnemySprite::create();
pEnemySprite->setEnemyByType(Enemy2);
this->addChild(pEnemySprite);

// 设置运动轨迹 以及到终点时调用的函数
auto actionMove=MoveTo::create(4.5f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy2));
Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
pEnemySprite->runAction(sequence);
}

void EnemyLayer::addEnemy3( float dt )
{
EnemySprite *pEnemySprite = EnemySprite::create();
pEnemySprite->setEnemyByType(Enemy3);
this->addChild(pEnemySprite);

// 设置运动轨迹 以及到终点时调用的函数
auto actionMove=MoveTo::create(6.0f,Point(pEnemySprite->getPositionX(), 0 - pEnemySprite->getSprite()->getContentSize().height/2));
auto actionDone = CallFuncN::create(CC_CALLBACK_1(EnemyLayer::removeEnemy, this, Enemy3));
Sequence* sequence=Sequence::create(actionMove, actionDone, NULL); // 按顺序执行 敌机飞到边缘,敌机移动结束
pEnemySprite->runAction(sequence);
}

void EnemyLayer::removeEnemy( Node *pNode , EnemyType eType)
{
EnemySprite* enemy=(EnemySprite*)pNode;
if (enemy!=NULL)
{
this->removeChild(enemy,true);
}
}


今天周五!!回家,明天搬到公司附近住~~