Cocos2d-x《雷电大战》(1)-双层地图无限滚动
本文要实现飞机射击游戏中的地图无限滚动的功能,这里分为两个层,一个层无限向下滚动,一个层无限向上滚动,这样子结合起来效果就非常有层次感,也非常逼真,这里我把地图层都写成一个类,自己把地图改下,就可以成为你自己的了!下面,我们开始吧
先来看看效果:

Cocos2d-x 版本:3.4
工程环境:VS30213
一、实现思路
其实就是两张图片,然后同时一起向下(向上)滚动,当一张图片完全出视野后,就把它调到最上面。形成两个图片交替出现,不过,一般为游戏中我们都感觉像是一张图片,那是因为两张图片的头尾连接处是连起来的。原理我画了些图:





二、代码
1、无限向下滚动 BackLayerDown 类
头文件:
# ifndef __BackLayerDown_H__
# define __BackLayerDown_H__
# include "cocos2d.h"
# define MAP_1_Tag 1 // 宏定义两个Map的Tag
# define MAP_2_Tag 2
class BackLayerDown : public cocos2d::Layer
{
public:
virtual bool init();
CREATE_FUNC(BackLayerDown);
private:
void update(float time);
virtual void onExit();
};
# endif // __BackLayerDown_H__
实现文件:
# include "BackLayerDown.h"
USING_NS_CC;
bool BackLayerDown::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
Sprite* map1 = Sprite::create("back3_1.png");
Sprite* map2 = Sprite::create("back3_2.png");
map1->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
map2->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height + origin.y + map2->getContentSize().height / 2));
this->addChild(map1, 0, MAP_1_Tag);
this->addChild(map2, 0, MAP_2_Tag);
this->scheduleUpdate();
return true;
}
//移動并判斷背景
void BackLayerDown::update(float time)
{
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
Sprite* temMap1 = (Sprite*)this->getChildByTag(MAP_1_Tag);
Sprite* temMap2 = (Sprite*)this->getChildByTag(MAP_2_Tag);
temMap1->setPositionY(temMap1->getPositionY() - 1);
temMap2->setPositionY(temMap2->getPositionY() - 1);
if (temMap1->getPositionY() + temMap1->getContentSize().height / 2 <= origin.y)
{
float offset = temMap1->getPositionY() + temMap1->getContentSize().height / 2 - origin.y;
temMap1->setPosition(Vec2(visibleSize.width / 2 + origin.x, temMap1->getContentSize().height / 2 + origin.y + visibleSize.height + offset));
}
if (temMap2->getPositionY() + temMap2->getContentSize().height / 2 <= origin.x)
{
float offset = temMap2->getPositionY() + temMap2->getContentSize().height / 2 - origin.y;
temMap2->setPosition(Vec2(visibleSize.width / 2 + origin.x, temMap2->getContentSize().height / 2 + origin.y + visibleSize.height + offset));
}
}
void BackLayerDown::onExit()
{
this->unscheduleUpdate();
Layer::onExit();
}
2、无限向上滚动 BackLayerUp 类
头文件:
# ifndef __BackLayerUp_H__
# define __BackLayerUp_H__
# include "cocos2d.h"
# define MAP_1_Tag 1 // 宏定义两个Map的Tag
# define MAP_2_Tag 2
class BackLayerUp : public cocos2d::Layer
{
public:
virtual bool init();
CREATE_FUNC(BackLayerUp);
private:
void update(float time);
virtual void onExit();
};
# endif // __BackLayerUp_H__
实现文件:
# include "BackLayerUp.h"
USING_NS_CC;
bool BackLayerUp::init()
{
if ( !Layer::init() )
{
return false;
}
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
Sprite* map1 = Sprite::create("back4_2.png");
Sprite* map2 = Sprite::create("back4_1.png");
map1->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
map2->setPosition(Vec2(visibleSize.width / 2 + origin.x, origin.y - map2->getContentSize().height / 2));
this->addChild(map1, 0, MAP_1_Tag);
this->addChild(map2, 0, MAP_2_Tag);
this->scheduleUpdate();
return true;
}
//移動并判斷背景
void BackLayerUp::update(float time)
{
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
Sprite* temMap1 = (Sprite*)this->getChildByTag(MAP_1_Tag);
Sprite* temMap2 = (Sprite*)this->getChildByTag(MAP_2_Tag);
temMap1->setPositionY(temMap1->getPositionY() + 1);
temMap2->setPositionY(temMap2->getPositionY() + 1);
if (temMap1->getPositionY() - temMap1->getContentSize().height / 2 >= visibleSize.height)
{
float offset = temMap1->getPositionY() - temMap1->getContentSize().height / 2 - visibleSize.height;
temMap1->setPosition(Vec2(visibleSize.width / 2 + origin.x, -temMap1->getContentSize().height / 2 - origin.y - offset));
}
if (temMap2->getPositionY() - temMap2->getContentSize().height / 2 >= visibleSize.height)
{
float offset = temMap2->getPositionY() - temMap2->getContentSize().height / 2 - visibleSize.height;
temMap2->setPosition(Vec2(visibleSize.width / 2 + origin.x, -temMap2->getContentSize().height / 2 - origin.y - offset));
}
}
void BackLayerUp::onExit()
{
this->unscheduleUpdate();
Layer::onExit();
}
3、说明
其实这两个类可以写在一起的,但是这里我为了能让不同的需要分开,把它们分别写开了,要注意上面判断的方法,无限向下和无限向上判断方法是不样的,而且,这里为了防止出现黑边,要记得设置位置时要加上一定的偏移量,如上面函数中的 offset,这里非常重要,如果没边上这个东东,有可能两张图片在切换时,有出现黑边。
三、使用方法
在要用到的地方,把头文件加上
#include "BackLayerDown.h"
#include "BackLayerUp.h"
然后在工程的 init()函数添加:
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
//这是地面图层
this->addChild(BackLayerUp::create());
//这是白云图层
this->addChild(BackLayerDown::create());
//加个飞机
Sprite *airplane_sprite = Sprite::create("air1.png");
airplane_sprite->setPosition(Vec2(visibleSize.width / 2, visibleSize.height/ 5));
this->addChild(airplane_sprite);
效果:

Cocos2d-x《雷电大战》(2)-精灵随手指移动,你点哪我走哪!
本文要实现飞机游戏中,人的手指按着飞机,就能拖着飞机走动,这里实现了当你手指按在手机的图片上,手指一直按着屏幕,飞机就会跟着你走。同时,还加入了边界判断条件,让飞机在你的视野内移动,实现的效果完全和我们手机上的飞机游戏一样。
效果:

Cocos2d-x 版本:3.4
工程环境:VS30213
一、代码编写
1、头文件 GameMain.h
/**
*@功能 游戏的主界面
*/
# ifndef __GameMain_H__
# define __GameMain_H__
# include "BackLayerDown.h"
# include "BackLayerUp.h"
# include "cocos2d.h"
USING_NS_CC;
class GameMain : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even);
CREATE_FUNC(GameMain);
private:
bool isHeroPlaneControl;//飞机是否被控制着
float mDeltaX;//英雄飞机随手指移动时的X偏移量
float mDeltaY;//英雄飞机随手指移动时的Y偏移量
Sprite *mHeroPlane;//英雄飞机
};
# endif // __GameMain_H__
然后在 GameMain.cpp 中增加:
# include "GameMain.h"
USING_NS_CC;
Scene* GameMain::createScene()
{
auto scene = Scene::create();
auto layer = GameMain::create();
scene->addChild(layer);
return scene;
}
bool GameMain::init()
{
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
//这是地面图层
this->addChild(BackLayerUp::create());
//这是白云图层
this->addChild(BackLayerDown::create());
//加个飞机
mHeroPlane = Sprite::create("air1.png");
mHeroPlane->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 5));
this->addChild(mHeroPlane, 1, 100);
isHeroPlaneControl = false;
//打开触摸,增加触摸监听事件
this->setTouchEnabled(true);
auto listen = EventListenerTouchOneByOne::create();
listen->onTouchBegan = CC_CALLBACK_2( GameMain::onTouchBegan,this);
listen->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved, this);
listen->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEened, this);
listen->onTouchCancelled = CC_CALLBACK_2(GameMain::onTouchCancelled, this);
listen->setSwallowTouches(false);//不截取触摸事件
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen,this);
return true;
}
bool GameMain::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event){
Point mHeroPos = mHeroPlane->getPosition();
Point mBeganPos = touch->getLocationInView();
mBeganPos = Director::getInstance()->convertToGL(mBeganPos);
//判断当前手指按下区域是否是英雄飞机的区域,并且计算飞机要移动时的偏移量
if (mBeganPos.x > mHeroPos.x - mHeroPlane->getContentSize().width / 2 && mBeganPos.x<mHeroPos.x + mHeroPlane->getContentSize().width / 2 &&
mBeganPos.y>mHeroPos.y - mHeroPlane->getContentSize().height / 2 && mBeganPos.y < mHeroPos.y + mHeroPlane->getContentSize().height / 2){
isHeroPlaneControl = true;
//計算偏移量
mDeltaX = mBeganPos.x - mHeroPos.x;
mDeltaY = mBeganPos.y - mHeroPos.y;
}
return true;
}
void GameMain::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event){
if (isHeroPlaneControl){
Point mMovedPos = touch->getLocationInView();
mMovedPos = Director::getInstance()->convertToGL(mMovedPos);
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
float x = mMovedPos.x - mDeltaX;//記得減去偏移量
float y = mMovedPos.y - mDeltaY;
if (x <= mHeroPlane->getContentSize().width / 2 + origin.x)//x到达屏幕左边界
x = mHeroPlane->getContentSize().width / 2 + origin.x;
else if (x >= visibleSize.width - mHeroPlane->getContentSize().width / 2)//x到达屏幕右边界
x = visibleSize.width - mHeroPlane->getContentSize().width / 2;
if (y <= mHeroPlane->getContentSize().height / 2 + origin.y)//y到达屏幕下边界
y = mHeroPlane->getContentSize().height / 2 + origin.y;
else if (y >= visibleSize.height - mHeroPlane->getContentSize().height / 2)//x到达屏幕上边界
y = visibleSize.height - mHeroPlane->getContentSize().height/ 2;
//飞机跟随手指移动
mHeroPlane->setPosition(Vec2(x,y));
}
}
void GameMain::onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event){
isHeroPlaneControl = false;
}
void GameMain::onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even){
isHeroPlaneControl = false;
}
这里再说一写主要函数:
头文件增加触摸事件:
virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even);
实现文件开启触摸事件监听:
//打开触摸,增加触摸监听事件
this->setTouchEnabled(true);
auto listen = EventListenerTouchOneByOne::create();
listen->onTouchBegan = CC_CALLBACK_2( GameMain::onTouchBegan,this);
listen->onTouchMoved = CC_CALLBACK_2(GameMain::onTouchMoved, this);
listen->onTouchEnded = CC_CALLBACK_2(GameMain::onTouchEened, this);
listen->onTouchCancelled = CC_CALLBACK_2(GameMain::onTouchCancelled, this);
listen->setSwallowTouches(false);//不截取触摸事件
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen,this);
然后就是触摸事件 的处理了:
bool GameMain::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event){
Point mHeroPos = mHeroPlane->getPosition();
Point mBeganPos = touch->getLocationInView();
mBeganPos = Director::getInstance()->convertToGL(mBeganPos);
//判断当前手指按下区域是否是英雄飞机的区域,并且计算飞机要移动时的偏移量
if (mBeganPos.x > mHeroPos.x - mHeroPlane->getContentSize().width / 2 && mBeganPos.x<mHeroPos.x + mHeroPlane->getContentSize().width / 2 &&
mBeganPos.y>mHeroPos.y - mHeroPlane->getContentSize().height / 2 && mBeganPos.y < mHeroPos.y + mHeroPlane->getContentSize().height / 2){
isHeroPlaneControl = true;
//計算偏移量
mDeltaX = mBeganPos.x - mHeroPos.x;
mDeltaY = mBeganPos.y - mHeroPos.y;
}
return true;
}
void GameMain::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event){
if (isHeroPlaneControl){
Point mMovedPos = touch->getLocationInView();
mMovedPos = Director::getInstance()->convertToGL(mMovedPos);
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
float x = mMovedPos.x - mDeltaX;//記得減去偏移量
float y = mMovedPos.y - mDeltaY;
if (x <= mHeroPlane->getContentSize().width / 2 + origin.x)//x到达屏幕左边界
x = mHeroPlane->getContentSize().width / 2 + origin.x;
else if (x >= visibleSize.width - mHeroPlane->getContentSize().width / 2)//x到达屏幕右边界
x = visibleSize.width - mHeroPlane->getContentSize().width / 2;
if (y <= mHeroPlane->getContentSize().height / 2 + origin.y)//y到达屏幕下边界
y = mHeroPlane->getContentSize().height / 2 + origin.y;
else if (y >= visibleSize.height - mHeroPlane->getContentSize().height / 2)//x到达屏幕上边界
y = visibleSize.height - mHeroPlane->getContentSize().height/ 2;
//飞机跟随手指移动
mHeroPlane->setPosition(Vec2(x,y));
}
}
void GameMain::onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event){
isHeroPlaneControl = false;
}
void GameMain::onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even){
isHeroPlaneControl = false;
}
方法很简单,代码量也很少,有需要的把上面的自己拿过去,把图片改改,把类名改改就可以了。
其实这里应该把英雄和移动事件单独写一个类,然后在GameMain里头来调用,因为英雄这个类我还构思好,所以先这样写,后头会把英雄飞机单独提取出来成为一个类,就不会在GameMain里头写这么多了;
效果:


效果很好,飞机能跟随移动并且不会跑出屏幕范围
二、思路说明
1、首先在 onTouchBegan 判断触摸点是否在英雄飞机的图片矩形内,若在这个范围内,刚将布尔型的 mHeroPlaneControl 设置为 true,并且计算触摸点的横纵坐标与英雄飞机的锚点坐标的差值。
2、因为要让英雄飞机移动,要修改锚点位置,必须知道锚点位置与触摸位置的偏移量,之后才可以通过这个偏移量设置主角的位置。
3、判断英雄飞机是否跑出屏幕范围了,如果是,就将它设置在边界处,详看上面的两个 if 判断。
4、在 onTouchMoved 中,若 mHeroPlaneControl 为 true,说明可以移动英雄飞机。
Cocos2d-x《雷电大战》(3)-子弹无限发射
本文要实现雷电游戏中,游戏一开始,英雄飞机就无限发射子弹的功能。这里的思想是单独给子弹弄一个层,在这个层不设置一个定时器,每隔一个时间,根据当前英雄飞机传入的位置,生成子弹,并设置子弹的移动事件,和移动后的事件(就是把子弹删除掉,节省内存)。
最终效果:

Cocos2d-x 版本:3.4
工程环境:VS30213
一、英雄子弹层
1、HeroBulletLayer.h
/**
*功能 创建子弹并初始化子弹的运动
*/
# include "cocos2d.h"
USING_NS_CC;
const float FlYVElOCITY = 500;//运行速度,可以自己控制,每秒所走的像素
class HeroBulletLayer : public cocos2d::Layer
{
public:
HeroBulletLayer(Node* heroPlane);
~HeroBulletLayer();
virtual bool init();
//根据英雄飞机创建子弹
static HeroBulletLayer* create(Node* heroPlane);
//移除超出屏幕可视范围的子弹或者碰撞后的子弹清除
void removeBullet(Node* pNode);
//发射子弹,在其中进行子弹的渲染和子弹的飞行动作,默认为单子弹
void ShootBullet(float dt);
//返回子弹列表
Vector <Sprite *>& GetBullet();
public:
Vector <Sprite *>vecBullet;//子弹容器
SpriteBatchNode* bulletBatchNode;//批次渲染节点
Node* heroPlane;//传入的英雄飞机
};
2、HeroBulletLayer.cpp
/**
*功能 创建子弹并初始化子弹的运动
*/
# include "HeroBulletLayer.h"
HeroBulletLayer::HeroBulletLayer(Node* heroPlane) {
this->heroPlane = heroPlane;
}
HeroBulletLayer::~HeroBulletLayer() {
}
/**
*创建子弹的静态方法
*@param heroPlane为英雄飞机
*/
HeroBulletLayer* HeroBulletLayer::create(Node* heroPlane){
HeroBulletLayer* pRet = new HeroBulletLayer(heroPlane);
if (pRet&&pRet->init()){
pRet->autorelease();
return pRet;
}
else{
delete pRet;
pRet = NULL;
return NULL;
}
}
bool HeroBulletLayer::init() {
bool bRet = false;
do {
CC_BREAK_IF(!Layer::init());
//创建BatchNode节点
bulletBatchNode = SpriteBatchNode::create("bullet1.png");
this->addChild(bulletBatchNode);
//每隔0.2S调用一次发射子弹函数
this->schedule(schedule_selector(HeroBulletLayer::ShootBullet), 0.2f);
bRet = true;
} while (0);
return bRet;
}
/**
*用缓存的方法创建子弹,并初始化子弹的运动和运动后的事件
*/
void HeroBulletLayer::ShootBullet(float dt) {
Size winSize = Director::getInstance()->getWinSize();
auto PlanePos = heroPlane->getPosition();
//从缓存中创建子弹
auto spritebullet = Sprite::createWithTexture(bulletBatchNode->getTexture());
//将创建好的子弹添加到BatchNode中进行批次渲染
bulletBatchNode->addChild(spritebullet);
//将创建好的子弹添加到容器
vecBullet.pushBack(spritebullet);
Point bulletPos = (Point(PlanePos.x,
PlanePos.y + heroPlane->getContentSize().height / 2 + 20));
spritebullet->setPosition(bulletPos);
spritebullet->setScale(0.8f);
float flyLen = winSize.height - PlanePos.y;
float realFlyDuration = flyLen / FlYVElOCITY;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕顶端
auto actionMove = MoveTo::create(realFlyDuration,
Point(bulletPos.x, winSize.height));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(HeroBulletLayer::removeBullet, this));
//子弹开始跑动
Sequence* sequence = Sequence::create(actionMove, actionDone, NULL);
spritebullet->runAction(sequence);
}
/**
* 移除子弹,将子弹从容器中移除,同时也从SpriteBatchNode中移除
*/
void HeroBulletLayer::removeBullet(Node* pNode) {
if (NULL == pNode) {
return;
}
Sprite* bullet = (Sprite*)pNode;
this->bulletBatchNode->removeChild(bullet, true);
vecBullet.eraseObject(bullet);
}
/**
*返回子弹列表,用来与敌机做碰撞检测
*/
Vector <Sprite *>& HeroBulletLayer::GetBullet(){
return vecBullet;
}
注意:
//创建BatchNode节点
bulletBatchNode = SpriteBatchNode::create("bullet1.png");
这里用了把子弹的图片加入到缓存中的方法,然后需要创建子弹时候调用
//从缓存中创建子弹
auto spritebullet = Sprite::createWithTexture(bulletBatchNode->getTexture());
如果不这样做的话,游戏会很耗内存,就会很卡!
这里重写了 create 方法,让它带一个参数,用它来传入英雄飞机
二、使用方法
//加子弹
HeroBulletLayer *mHeroBulletLayer = HeroBulletLayer::create(mHeroPlane);
this->addChild(mHeroBulletLayer,1);
效果:

要注意 mHeroPlane 是上一讲中跟随手指移动的英雄飞机,看这里 Cocos2d-x《雷电大战》(2)-精灵随手指移动,你点哪我走哪!
三、思路说明
1、上面的英雄子弹类很好用,你只要传入一个英雄飞机的位置,它就会生成英雄子弹层不断调用定时器生成子弹,同时加入到当前层中和Vector中(用来保存所有的子弹)
2、 根据当前英雄飞机的位置,计算子弹移动的距离,然后速度是自己设定的,就可以计算子弹要移动的直线距离的时间。
3、 当子弹移动到视野外后,就删除掉这个子弹,vector 中也要删除。
4、GetBullet();是用来得到当前的 vector 子弹集合,然后我们需要一个一个的取出来,判断是否和敌机相撞,如果是,就调用 removeBullet(Node* pNode);就是这样了。比如:
void GameMain::update(float dt){
auto *mEnemyPlane = getChildByTag(200);
Vector <Sprite *> mVecHeroBullet = mHeroBulletLayer->GetBullet();
for (int i = 0; i<mVecHeroBullet.size();i++){
if (mEnemyPlane->boundingBox().intersectsRect(mVecHeroBullet.at(i)->boundingBox())){
mHeroBulletLayer->removeBullet(mVecHeroBullet.at(i));
}
}
}
效果如下:这里要注意下 vector 的遍历用法,不能[],cocos2dx 没有重载这个,要用 at.

这里只是示例了下怎么用,子弹碰到飞机后就把它删除掉,敌机还没有做处理
Cocos2d-x《雷电大战》(4)-策略模式实现不同子弹切换!!
本文从设计模式中的策略模式入手,主讲了飞机大战中英雄飞机切换不同的子弹。这里分为三种子弹。第一种:每次发一个子弹,垂直发射;第二种:每次发两个子弹,两个都是垂直发射:第三种;每次发三个子弹,两边的子弹有一定的角度,而中间的子弹垂直发射;设计模式是游戏开发经常用到的思想,建议有兴趣的同学可以好好研究下!好了,下面开始吧。
效果如下:



Cocos2d-x 版本:3.4
工程环境:VS30213
一、策略模式(Stragegy Pattern)
1、简介
Strategy 模式也叫策略模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略)。Strategy 模式主要用来平滑地处理算法的切换 。
2、意图
定义一系列的算法,把它们一个个封装起来,并且它们可相互替换。使得算法可独立于使用它的客户而变化。
3、适用性
如果一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
一个系统需要动态的在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体算法类,并只持有一个数据类型是抽象算法的对象。
一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的和复杂的只与算法有关的数据。
如果一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移到它们各自的Strategy类中以代替这些条件语句。
更多详细的策略模式看这里吧: 24 天学会设计模式------策略模式 https://blog.csdn.net/evankaka/article/details/43373979
二.子弹类与子弹管理类的代码编写
首先来看下本文的类 UML 图:

这里的思路是把子弹发射当成是一个函数,基类子弹中定义成虚函数,然后实现类子弹中 HeroBulletOne、HeroBulletTwo、HeroBulletThree 分别实现不同的发射功能。然后在子弹管理类中 HeroBulletLayer 中有一个私有的成员变量 BulletStyle *mBulletStyle.当需要切换不同的子弹时,就将 new 不同的 HeroBulletOne 或 HeroBulletTwo 或 HeroBulletThree,赋给 mBulletStyle。
下面我们来看看代码吧!!!
2.1 首先是子弹的基类:
BulletStyle.h,这里注意到 virtual void shootBullet(float dt){}为虚函数,表示这里要根据的子类不同,发射不同的子弹数目;
/**
*功能 创建子弹的基类
*/
# pragma once
# ifndef __BulletStyle_H__
# define __BulletStyle_H__
# include "cocos2d.h"
USING_NS_CC;
class BulletStyle : public cocos2d::Node{
public:
~BulletStyle();
/**
* 移除所有的东西
*/
void removeAllObject();
/**
*移除超出屏幕可视范围的子弹或者碰撞后的子弹清除
*@param pNode 要删除的子弹
*/
void removeBullet(Node* pNode);
/**
*根据传入的飞机,子弹跟随发射
*@param plane为传入飞机,可为英雄飞机或敌机
*/
virtual void createBullet(Node* plane);
/**
*发射子弹,在其中进行子弹的渲染和子弹的飞行动作,默认为单子弹
*@param dt子弹间隔发时间
*/
virtual void shootBullet(float dt){}
protected:
//子弹容器
Vector <Sprite *> vecBullet;
//批次渲染节点
SpriteBatchNode* bulletBatchNode;
//传入的飞机
Node* plane;
};
# endif
实现文件 BulletStyle.cpp
/**
*功能 创建子弹的基类
*/
# include "BulletStyle.h"
BulletStyle::~BulletStyle(){
//removeAllObject();
}
/**
* 移除所有的东西
*/
void BulletStyle::removeAllObject(){
bulletBatchNode->removeAllChildren();
vecBullet.clear();
this->removeAllChildren();
}
/**
* 移除子弹,将子弹从容器中移除,同时也从SpriteBatchNode中移除
*/
void BulletStyle::removeBullet(Node* pNode) {
if (NULL == pNode) {
return;
}
Sprite* bullet = (Sprite*)pNode;
bulletBatchNode->removeChild(bullet, true);
vecBullet.eraseObject(bullet);
}
/**
*根据传入的飞机,子弹跟随发射
*@param plane为传入飞机,可为英雄飞机或敌机
*/
void BulletStyle::createBullet(Node* plane){
this->plane = plane;
//创建BatchNode节点
bulletBatchNode = SpriteBatchNode::create("bullet1.png");
this->addChild(bulletBatchNode);
//每隔0.2S调用一次发射子弹函数
schedule(schedule_selector(BulletStyle::shootBullet), 0.2f);//注意,这里的发射方法留给子类来实现!!!
}
2.2 只发射一个子弹的类
HeroBulletOne.h,注意,直接继承
/**
*功能 每次只发射一个子弹
*/
# pragma once
# ifndef __HeroBulletOne_H__
# define __HeroBulletOne_H__
# include "cocos2d.h"
# include "BulletStyle.h"
USING_NS_CC;
class HeroBulletOne : public BulletStyle {
public:
virtual void shootBullet(float dt);
};
# endif
实现文件:
/**
*功能 每次只发射一个子弹
*/
# include "HeroBulletOne.h"
void HeroBulletOne::shootBullet(float dt) {
Size winSize = Director::getInstance()->getWinSize();
auto PlanePos = plane->getPosition();
//从缓存中创建子弹
auto spritebullet = Sprite::createWithTexture(bulletBatchNode->getTexture());
//将创建好的子弹添加到BatchNode中进行批次渲染
bulletBatchNode->addChild(spritebullet);
//将创建好的子弹添加到容器
vecBullet.pushBack(spritebullet);
Point bulletPos = (Point(PlanePos.x,
PlanePos.y + plane->getContentSize().height / 2 + 20));
spritebullet->setPosition(bulletPos);
spritebullet->setScale(0.8f);
float flyVelocity = 500;//运行速度,可以自己控制,每秒所走的像素
float flyLen = winSize.height - PlanePos.y;
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕顶端
auto actionMove = MoveTo::create(realFlyDuration,
Point(bulletPos.x, winSize.height));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(HeroBulletOne::removeBullet, this));
//子弹开始跑动
Sequence* sequence = Sequence::create(actionMove, actionDone, NULL);
spritebullet->runAction(sequence);
}
2.3 发射二个子弹的类
HeroBulletTwo.h,注意,直接继承
/**
*功能 每次发射二个子弹
*/
# pragma once
# ifndef __HeroBulletTwo_H__
# define __HeroBulletTwo_H__
# include "cocos2d.h"
# include "BulletStyle.h"
USING_NS_CC;
class HeroBulletTwo : public BulletStyle {
public:
virtual void shootBullet(float dt);
};
# endif
实现文件:
/**
*功能 每次发射二个子弹
*/
# include "HeroBulletTwo.h"
void HeroBulletTwo::shootBullet(float dt) {
Size winSize = Director::getInstance()->getWinSize();
auto PlanePos = plane->getPosition();
//从缓存中创建子弹
auto spritebullet1 = Sprite::createWithTexture(bulletBatchNode->getTexture());
auto spritebullet2 = Sprite::createWithTexture(bulletBatchNode->getTexture());
//将创建好的子弹添加到BatchNode中进行批次渲染
bulletBatchNode->addChild(spritebullet1);
bulletBatchNode->addChild(spritebullet2);
//将创建好的子弹添加到容器
vecBullet.pushBack(spritebullet1);
vecBullet.pushBack(spritebullet2);
Point bulletPos1 = (Point(PlanePos.x - plane->getContentSize().width / 4,
PlanePos.y + plane->getContentSize().height / 2+10 ));
Point bulletPos2 = (Point(PlanePos.x + plane->getContentSize().width / 4,
PlanePos.y + plane->getContentSize().height / 2+10));
spritebullet1->setPosition(bulletPos1);
spritebullet1->setScale(0.8f);
spritebullet2->setPosition(bulletPos2);
spritebullet2->setScale(0.8f);
float flyVelocity = 500;//运行速度,可以自己控制,每秒所走的像素
float flyLen = winSize.height - PlanePos.y;
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕顶端
auto actionMove1 = MoveTo::create(realFlyDuration,
Point(bulletPos1.x, winSize.height));
auto actionMove2 = MoveTo::create(realFlyDuration,
Point(bulletPos2.x, winSize.height));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(HeroBulletTwo::removeBullet, this));
//子弹开始跑动
Sequence* sequence1 = Sequence::create(actionMove1, actionDone, NULL);
spritebullet1->runAction(sequence1);
Sequence* sequence2 = Sequence::create(actionMove2, actionDone, NULL);
spritebullet2->runAction(sequence2);
}
2.4 发射三个子弹的类
HeroBulletThree.h,注意,直接继承
/**
*功能 每次发射三个子弹
*/
# pragma once
# ifndef __HeroBulletThree_H__
# define __HeroBulletThree_H__
# include "cocos2d.h"
# include "BulletStyle.h"
USING_NS_CC;
class HeroBulletThree : public BulletStyle {
public:
virtual void shootBullet(float dt);
};
# endif
实现文件:
/**
*功能 每次发射三个子弹
*/
# include "HeroBulletThree.h"
void HeroBulletThree::shootBullet(float dt) {
Size winSize = Director::getInstance()->getWinSize();
auto PlanePos = plane->getPosition();
double angle = M_PI * 80 / 180;//旋轉的角度
//从缓存中创建子弹
auto spritebullet = Sprite::createWithTexture(bulletBatchNode->getTexture());
auto spritebullet1 = Sprite::createWithTexture(bulletBatchNode->getTexture());
spritebullet1->setRotation(-angle);
auto spritebullet2 = Sprite::createWithTexture(bulletBatchNode->getTexture());
spritebullet2->setRotation(angle);
//将创建好的子弹添加到BatchNode中进行批次渲染
bulletBatchNode->addChild(spritebullet);
bulletBatchNode->addChild(spritebullet1);
bulletBatchNode->addChild(spritebullet2);
//将创建好的子弹添加到容器
vecBullet.pushBack(spritebullet);
vecBullet.pushBack(spritebullet1);
vecBullet.pushBack(spritebullet2);
Point bulletPos = (Point(PlanePos.x,
PlanePos.y + plane->getContentSize().height / 2 + 20));
Point bulletPos1 = (Point(PlanePos.x - plane->getContentSize().width / 4-10,
PlanePos.y + plane->getContentSize().height / 2+10 ));
Point bulletPos2 = (Point(PlanePos.x + plane->getContentSize().width / 4+10,
PlanePos.y + plane->getContentSize().height / 2+10));
spritebullet->setPosition(bulletPos);
spritebullet->setScale(0.8f);
spritebullet1->setPosition(bulletPos1);
spritebullet1->setScale(0.8f);
spritebullet2->setPosition(bulletPos2);
spritebullet2->setScale(0.8f);
float flyVelocity = 500;//运行速度,可以自己控制,每秒所走的像素
float flyLen = winSize.height - PlanePos.y;
float flyLen1 = PlanePos.x / cos(angle);//按照度來算
float flyLen2 = (winSize.width - PlanePos.x) / cos(angle);
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
float realFlyDuration1 = flyLen1 / flyVelocity;//实际飞行的时间
float realFlyDuration2 = flyLen2 / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕顶端
auto actionMove = MoveTo::create(realFlyDuration,
Point(bulletPos.x, winSize.height));
auto actionMove1 = MoveTo::create(realFlyDuration1,
Point(0, PlanePos.x*tan(angle) + PlanePos.y));
auto actionMove2 = MoveTo::create(realFlyDuration2,
Point(winSize.width, (winSize.width - PlanePos.x)*tan(angle) + PlanePos.y));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(HeroBulletThree::removeBullet, this));
//子弹开始跑动
Sequence* sequence = Sequence::create(actionMove, actionDone, NULL);
spritebullet->runAction(sequence);
Sequence* sequence1 = Sequence::create(actionMove1, actionDone, NULL);
spritebullet1->runAction(sequence1);
Sequence* sequence2 = Sequence::create(actionMove2, actionDone, NULL);
spritebullet2->runAction(sequence2);
}
2.5、子弹管理器编写
/**
*功能 管理子弹、切换不同的子弹
*/
# pragma once
# ifndef __HeroBulletLayer_H__
# define __HeroBulletLayer_H__
# include "cocos2d.h"
# include "BulletStyle.h"
# include "HeroBulletOne.h"
# include "HeroBulletTwo.h"
# include "HeroBulletThree.h"
class HeroBulletLayer : public cocos2d::Layer
{
public:
HeroBulletLayer(Node* heroPlane);
virtual bool init();
//根据英雄飞机创建子弹
static HeroBulletLayer* create(Node* heroPlane);
//改变子弹
void changeBullet(int bulletNumber);
public:
Node* heroPlane;//传入的英雄飞机
BulletStyle *mBulletStyle;//子弹类型
int bulletNumber;//当前子弹编号
};
# endif
实现文件:
/**
*功能 管理子弹、切换不同的子弹
*/
# include "HeroBulletLayer.h"
HeroBulletLayer::HeroBulletLayer(Node* heroPlane) {
this->heroPlane = heroPlane;
mBulletStyle = NULL;
bulletNumber = 1;
}
/**
*创建子弹的静态方法
*@param heroPlane为英雄飞机
*/
HeroBulletLayer* HeroBulletLayer::create(Node* heroPlane){
HeroBulletLayer* pRet = new HeroBulletLayer(heroPlane);
if (pRet&&pRet->init()){
pRet->autorelease();
return pRet;
}
else{
delete pRet;
pRet = NULL;
return NULL;
}
}
bool HeroBulletLayer::init() {
bool bRet = false;
do {
CC_BREAK_IF(!Layer::init());
mBulletStyle = new HeroBulletOne();
mBulletStyle->autorelease();
mBulletStyle->createBullet(heroPlane);
this->addChild(mBulletStyle);
bRet = true;
} while (0);
return bRet;
}
/**
*切换不同的子弹
*@param number 表示子弹的数目
*/
void HeroBulletLayer::changeBullet(int number){
switch (number)
{
case 1:
if (bulletNumber != 1){
this->removeChild(mBulletStyle, true);
mBulletStyle = new HeroBulletOne();
bulletNumber = 1;
mBulletStyle->createBullet(heroPlane);
mBulletStyle->autorelease();
this->addChild(mBulletStyle);
}
break;
case 2:
if (bulletNumber != 2){
this->removeChild(mBulletStyle, true);
mBulletStyle = new HeroBulletTwo();
bulletNumber = 2;
mBulletStyle->createBullet(heroPlane);
mBulletStyle->autorelease();
this->addChild(mBulletStyle);
}
break;
case 3:
if (bulletNumber != 3){
this->removeChild(mBulletStyle, true);
mBulletStyle = new HeroBulletThree();
bulletNumber = 3;
mBulletStyle->createBullet(heroPlane);
mBulletStyle->autorelease();
this->addChild(mBulletStyle);
}
break;
default:
break;
}
}
2.6、调用方法
游戏入口主文件 GameMain.h 添加头文件
#include "HeroBulletLayer.h"//这是子弹管理的层
增加变量:
HeroBulletLayer *mHeroBulletLayer;
然后是实现方法中GameMain.cpp的init()函数中增加
//加子弹
mHeroBulletLayer = HeroBulletLayer::create(mHeroPlane);
this->addChild(mHeroBulletLayer,1);
注意 mHeroPlane 是你的英雄飞机类,是上文中可以跟随手指运动的手机,不懂看这里, Cocos2d-x《雷电大战》(2)-精灵随手指移动,你点哪我走哪!
然后就是切换子弹啦:
你只需要在要切换子弹的地方:
发射一个子弹
mHeroBulletLayer->changeBullet(1);
发射二个子弹
mHeroBulletLayer->changeBullet(2);
发射三个子弹
mHeroBulletLayer->changeBullet(3);
这里我为了测试,设置成每个 5 秒自动切换子弹类型:
GameMain.h 加个变量
int number;//表示当前子弹的类型
GameMain.cpp 中 init()函数中增加:
//每隔5S改變一次子子彈類型
number = 1;
schedule(schedule_selector(GameMain::changeBullet),5.0f);
下面是定时器的方法
void GameMain::changeBullet(float dt){
if (number == 1){
mHeroBulletLayer->changeBullet(2);
number = 2;
}
else if (number == 2){
mHeroBulletLayer->changeBullet(3);
number = 3;
}
else if (number == 3)
{
mHeroBulletLayer->changeBullet(1);
number = 1;
}
CCLOG("CHANGE");
}
效果:
这里它会自动每隔 5s 切换不同的子弹,由于上传图片的限制。只能这样了。


三、总结
是不是很方便呢?当我需要增加一个子弹类型时,我只需要继承 BulletStyle.然后重写函数 shootBullet(float dt) 即可。然后在需要更改子弹的位置 bool HeroBulletLayer::changeBullet(int number)增加每 4 种子弹。第 5 种子弹......这样就增加了代码的复用性,而且很容易懂。也省去了一大堆的 if-else 判断。
Cocos2d-x《雷电大战》(5)-单例模式英雄飞机闪亮登场!
本文将实现用单例模式实现一个英雄飞机类的设计,单例模式是游戏开发中最常用到的一种设计模式,原理也比较简单,仔细研究下就可以掌握好。
来看看效果:


英雄飞机一创建就闪烁 3 秒
英雄飞机创建后带有喷火的小尾巴动画
Cocos2d-x 版本:3.4
工程环境:VS30213
一、单例模式解析
单例模式也称为单件模式、单子模式,可能是使用最广泛的设计模式。其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出,GUI 应用必须是单鼠标,MODEM 的联接需要一条且只需要一条电话线,操作系统只能有一个窗口管理器,一台 PC 连一个键盘。
单例的一般实现比较简单,下面是代码和 UML 图。由于构造函数是私有的,因此无法通过构造函数实例化,唯一的方法就是通过调用静态函数 GetInstance。
UML图:

代码实现
1: //Singleton.h
2: class Singleton
3: {
4: public:
5: static Singleton* GetInstance();
6: private:
7: Singleton() {}
8: static Singleton *singleton;
9: };
10: //Singleton.cpp
11: Singleton* Singleton::singleton = NULL;
12: Singleton* Singleton::GetInstance()
13: {
14: if(singleton == NULL)
15: singleton = new Singleton();
16: return singleton;
17: }
特点:
- 类的构造函数外界不可访问.
- 提供了创建对象的接口.
二、单例模式优缺点
单例模式非常好实现,直接就可以在静态区初始化 instance,然后通过 getInstance 返回,这种就被称为饿汉式单例类。也有些写法是在 getInstance 中 new instance 然后返回,这种就被称为懒汉式单例类,但这涉及到第一次 getInstance 的一个判断问题。
优点
1.减少了时间和空间的开销(new 实例的开销)。
2.提高了封装性,使得外部不易改动实例。
缺点
1.懒汉式是以时间换空间的方式。
2.饿汉式是以空间换时间的方式。
三、Cocos2d-x 中设计单例模式的英雄飞机
这里我实现了一个单例模式的英雄飞机类,它还带了触摸跟随手指移动的功能,其实就是把 Cocos2d-x《雷电大战》(2)-精灵随手指移动,你点哪我走哪!这样就比较合理,这个英雄飞机类还包含有分数值、血量、攻击值、子弹类型等。这里只是一个粗略实现,后续的功能有可以还会有改动。而且飞机还带有喷火的功能,更加的逼真!这样子代码结构看起来就比较明了,而不会全放在 GameMain.cpp 中。然后需要创建时直接在 GameMain.cpp 来调用就行了。
下面来看看代码:
头文件 HeroPlane.h
/**
*功能 单例英雄飞机类
*/
# pragma once
# ifndef __HeroPlane_H__
# define __HeroPlane_H__
# include "cocos2d.h"
USING_NS_CC;
class HeroPlane :public cocos2d::Layer{
private:
HeroPlane();
public:
/**
*获得单例英雄飞机的方法
*@return 英雄飞机类
*/
static HeroPlane* getInstance();
virtual bool init();
/**
*取得当前英雄飞机中的精灵
*@return 飞机中精灵,实际的操作对像
*/
Sprite* getPlane();
/**
*底下四个分别为触摸按下时的事件
*/
virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event);
virtual void onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even);
public:
unsigned int planeHp;//英雄飞机生命值
unsigned int planeScore;//英雄飞机分数值
unsigned int planeBulletNum;//英雄飞机子弹类型
unsigned int planeAttack;//英雄飞机攻击值
unsigned int planeDefend;//英雄飞机保护值
private:
static HeroPlane* sharePlane;//英雄飞机静态变量
bool isHeroPlaneControl;//飞机是否被控制着
float mDeltaX;//英雄飞机随手指移动时的X偏移量
float mDeltaY;//英雄飞机随手指移动时的Y偏移量
Sprite* plane;//英雄飞机
};
# endif
英雄飞机实现类 HeroPlane.cpp
/**
*功能 单例英雄飞机类
*/
# include"HeroPlane.h"
USING_NS_CC;
HeroPlane* HeroPlane::sharePlane = NULL;//注意,静态变量的写法!
HeroPlane::HeroPlane(){
plane = NULL;
planeHp=10000;//英雄飞机生命值
planeScore=0;//英雄飞机分数值
planeBulletNum=1;//英雄飞机子弹类型,默认初始化为第一种
isHeroPlaneControl = false;
planeAttack=100;//英雄飞机攻击值
planeDefend=100;//英雄飞机保护值
}
//取得英雄飞机的方法,只能通过这个方法
HeroPlane* HeroPlane::getInstance()
{
if (NULL == sharePlane){
sharePlane = new HeroPlane();
sharePlane->init();
sharePlane->autorelease();
}
return sharePlane;
}
bool HeroPlane::init(){
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
SpriteFrameCache *frameCache = SpriteFrameCache::getInstance();
frameCache->addSpriteFramesWithFile("heroplane.plist", "heroplane.png");//加载全局资源
plane = Sprite::createWithSpriteFrameName("plane1.png");//生成飞机
plane->setPosition(Vec2(visibleSize.width / 2, visibleSize.height / 5));
this->addChild(plane, 0, 1);
Blink *blink = Blink::create(3,8);//闪烁动画
Animation* animation = Animation::create();
animation->setDelayPerUnit(0.1f);
animation->addSpriteFrame(frameCache->getSpriteFrameByName("plane1.png"));
animation->addSpriteFrame(frameCache->getSpriteFrameByName("plane2.png"));
Animate* animate = Animate::create(animation);//帧动画
plane->runAction(blink);//执行闪烁动画
plane->runAction(RepeatForever::create(animate));// 执行帧动画
//开启触摸事件,让飞机跟随手指移动
auto listen = EventListenerTouchOneByOne::create();
listen->onTouchBegan = CC_CALLBACK_2(HeroPlane::onTouchBegan, this);
listen->onTouchMoved = CC_CALLBACK_2(HeroPlane::onTouchMoved, this);
listen->onTouchEnded = CC_CALLBACK_2(HeroPlane::onTouchEened, this);
listen->onTouchCancelled = CC_CALLBACK_2(HeroPlane::onTouchCancelled, this);
listen->setSwallowTouches(false);
Director::getInstance()->getEventDispatcher()->addEventListenerWithSceneGraphPriority(listen, this);
return true;
}
Sprite* HeroPlane::getPlane(){
if (NULL == plane)
return NULL;
return plane;
}
bool HeroPlane::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event){
Point mHeroPos = plane->getPosition();
Point mBeganPos = touch->getLocationInView();
mBeganPos = Director::getInstance()->convertToGL(mBeganPos);
//判断当前手指按下区域是否是英雄飞机的区域,并且计算飞机要移动时的偏移量
if (mBeganPos.x > mHeroPos.x - plane->getContentSize().width / 2 && mBeganPos.x<mHeroPos.x + plane->getContentSize().width / 2 &&
mBeganPos.y>mHeroPos.y - plane->getContentSize().height / 2 && mBeganPos.y < mHeroPos.y + plane->getContentSize().height / 2){
isHeroPlaneControl = true;
//計算偏移量
mDeltaX = mBeganPos.x - mHeroPos.x;
mDeltaY = mBeganPos.y - mHeroPos.y;
}
return true;
}
void HeroPlane::onTouchMoved(cocos2d::Touch *touch, cocos2d::Event *unused_event){
if (isHeroPlaneControl){
Point mMovedPos = touch->getLocationInView();
mMovedPos = Director::getInstance()->convertToGL(mMovedPos);
Size visibleSize = Director::getInstance()->getVisibleSize();
Point origin = Director::getInstance()->getVisibleOrigin();
float x = mMovedPos.x - mDeltaX;//記得減去偏移量
float y = mMovedPos.y - mDeltaY;
if (x <= plane->getContentSize().width / 2 + origin.x)//x到达屏幕左边界
x = plane->getContentSize().width / 2 + origin.x;
else if (x >= visibleSize.width - plane->getContentSize().width / 2)//x到达屏幕右边界
x = visibleSize.width - plane->getContentSize().width / 2;
if (y <= plane->getContentSize().height / 2 + origin.y)//y到达屏幕下边界
y = plane->getContentSize().height / 2 + origin.y;
else if (y >= visibleSize.height - plane->getContentSize().height / 2)//x到达屏幕上边界
y = visibleSize.height - plane->getContentSize().height / 2;
//飞机跟随手指移动
plane->setPosition(Vec2(x, y));
}
}
void HeroPlane::onTouchEened(cocos2d::Touch *touch, cocos2d::Event *unused_event){
isHeroPlaneControl = false;
}
void HeroPlane::onTouchCancelled(cocos2d::Touch *touch, cocos2d::Event *unused_even){
isHeroPlaneControl = false;
}
这里的英雄飞机第一次获取时,会闪烁几下。并且带有喷火的小尾巴(其实就是不断的重复切换两张图片实现的,看上面的帧动画)
使用方法:
1: HeroPlane *heroPlane = HeroPlane::getInstance();
2: this->addChild(heroPlane);
每次要取得当前的英雄飞机类,用 HeroPlane::getInstance()即可,也就是说每次返回都是同一个实例,所以你就可以保存英雄的分数、血量、攻击值到一个外部的文件中,然后游戏每次启动时,从这个文件读取数据就可以,就可以接着上次的英雄飞机设定的参数了。这些功能我打算留在后头再来实现。


英雄飞机一创建就闪烁3秒
雄飞机创建后带有喷火的小尾巴动画
Cocos2d-x《雷电大战》(6) 智能敌机 AI 来袭--飞行路径算法设计与实现(上)
飞机类游戏设计中,智机的飞行路径设计和智能子弹的设计绝对一个飞行类游戏好坏是的核心。敌机智能也是分级别的。BOSS机就不说了,而飞行游戏由于其特殊性,还经常有那种一连串一起出现的敌机。这种又可分为以下两种:
跟随:相同的位置,相同的飞行路径,不同的启动时间,一般是按时间间隔。
并飞:不同的位置,相同的运行路径,相同的启动时间。
效果如下:

跟随,还未做碰撞判断

并飞,还未做碰撞判断
Cocos2d-x 版本:3.4
工程环境:VS30213
一、跟随飞行
在跟随飞行中,简单一点的跟随飞行路线就是直线了,比如从左到右,从一个角到另一个角。这种做法都比较简单,没什么难度。实际游戏开发中,也很少见这种的跟随飞行,比较多的还是变化的曲线。而这种曲线一般都是贝赛尔曲线。飞机中不仅要飞行,还是进行实时的角度变化,这样才更加模拟真实的游戏场景!
1.1 贝赛尔曲线简介
贝塞尔曲线是应用于二维图形应用程序的数学曲线。曲线的定义有四个点:起始点、终止点(也称锚点)以及两个相互分离的中间点。滑动两个中间点,贝塞尔曲线的形状会发生变化 .
P0、P1、P2、P3四个点在平面或在三维空间中定义了三次方贝塞尔曲线。曲线起始于P0走向P1,并从P2的方向来到P3。一般不会经过P1或P2;这两个点只是在那里提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。

p0 起点,p3 是终点,p1,p2 是控制点
1.2 游戏应用
我们可能需要在游戏中模拟导弹或箭的移动轨迹,用才 cocos2d-x 下的 bezier 可以轻松的模拟出来
cocos2d-x 下为我们提供了两个 action BezierBy 和 BezierTo,使用也很简单,只需要填充结构体:
//设置贝塞尔曲线参数
ccBezierConfig tr0;
tr0.endPosition = Vec2(0, 10);//终点
tr0.controlPoint_1 = Vec2(250, 300);//控制点1
tr0.controlPoint_2 = Vec2(180, 150);//控制点2
ActionInterval* bezierForward = BezierTo::create(3.f, tr0);//创建运行的贝塞尔曲线
我们只需要提供两个控制点和一个终点位置就可以了,这里要注意的是
CCBezier 这个 action 是以当前位置为起始点的,两个控制点和终点都是相对于起始点的偏移值
如:tr0.endPosition = ccp(280,240); 是相对于起始点的偏移
1.3 代码
这里只是要验证算法,所以代码还没有单独封装成类,而且也还没有将图像都打包成 plist 或用 SpriteBatchNode 来优化内存。笔者打算把敌机飞行路径和敌机子弹设计完成之后,再统一来优化内存!
在 GameMain.h 中添加一个定时器:
void enemyBuild1(float dt);//跟随
然后就是 GameMain.cpp 的 init()函数打开定时,这里设置每隔 0.5
//每隔0.5S调用一次
schedule(schedule_selector(GameMain::enemyBuild1), 0.5f);
最后就是实现了:
void GameMain::enemyBuild1(float dt){
Size winSize = Director::getInstance()->getWinSize();
auto spritePlane = Sprite::create("air3.png");
spritePlane->setRotation(90);
spritePlane->setPosition(Vec2(0,400));
spritePlane->setScale(0.25);
this->addChild(spritePlane);
//设置贝塞尔曲线参数
ccBezierConfig tr0;
tr0.endPosition = Vec2(0, 10);//终点
tr0.controlPoint_1 = Vec2(250, 300);//控制点1
tr0.controlPoint_2 = Vec2(180, 150);//控制点2
ActionInterval* bezierForward = BezierTo::create(3.f, tr0);//创建运行的贝塞尔曲线
ActionInterval *forwardBy = RotateBy::create(3.f,180); // 第二个参数:如果是正数则是顺时针,否则逆时针
Spawn* spawn = Spawn::create(bezierForward, forwardBy,NULL);//创建合成动作
//飞机执行完动作后进行函数回调,调用移除飞机函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(GameMain::enemyRemove, this));
//连续动作
Sequence* sequence = Sequence::create(spawn,actionDone, NULL);
spritePlane->runAction(sequence);
}
别看代码少,里面涉及到的内容不少呢!
其中删除飞机的函数:
void GameMain::enemyRemove(Node* pNode){
if (NULL == pNode) {
return;
}
Sprite* plane = (Sprite*)pNode;
this->removeChild(plane,true);
}
要记得先在 GameMain.h 中定义
void enemyRemove(Node* pNode);
最后就是运行了,效果如下:

二、并飞飞行
并飞就比简单了,因为是相同的路径方法。而且一般都不考虑到角度旋转的问题。游戏中最多出现的是左右并飞或者上下并飞。无非就是设置几架飞机在一排线上,然后设置飞行路径。最后执行就是了,下面直接来看看代码吧,注释很详细,有需要的直接拿过去,很方便自己扩展,把图像名改下就好。要记得,这里还未做内存优化,如果想做的话,有两种方法。一种是图像做成plist,另一种是用 SpriteBatchNode来做。在这里在,我推荐用前者。
首先 GameMain.h 添加定时器:
void enemyBuild2(float dt);//并飞
打开定时器:
//每隔3S调用一次
schedule(schedule_selector(GameMain::enemyBuild2), 3.0f);
最后就是实现了:
void GameMain::enemyBuild2(float dt){
Size winSize = Director::getInstance()->getWinSize();
Point origin = Director::getInstance()->getVisibleOrigin();
//生成精灵
auto spritePlane1 = Sprite::create("air4.png");
auto spritePlane2 = Sprite::create("air4.png");
auto spritePlane3 = Sprite::create("air4.png");
//得到精灵宽和高
float height = spritePlane1->getContentSize().height;
float width = spritePlane1->getContentSize().width;
//旋转的角度
spritePlane1->setRotation(180);
spritePlane2->setRotation(180);
spritePlane3->setRotation(180);
//设置缩放
//spritePlane1->setScale(0.3);
//spritePlane2->setScale(0.3);
//spritePlane3->setScale(0.3);
//设置位置
spritePlane1->setPosition(Vec2(width, winSize.height + height));
spritePlane2->setPosition(Vec2(winSize.width / 2, winSize.height - height));
spritePlane3->setPosition(Vec2(winSize.width - width, winSize.height + height));
//层中加入精灵
this->addChild(spritePlane1);
this->addChild(spritePlane2);
this->addChild(spritePlane3);
//计算飞行时间
float flyVelocity =200;//运行速度,可以自己控制,每秒所走的像素
float flyLen = winSize.height;
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕底部
auto actionMove1 = MoveBy::create(realFlyDuration, Point(0, -winSize.height - height));
auto actionMove2 = MoveBy::create(realFlyDuration, Point(0, -winSize.height -height));
auto actionMove3 = MoveBy::create(realFlyDuration, Point(0, -winSize.height - height));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(GameMain::enemyRemove, this));
//连续动作
Sequence* sequence1 = Sequence::create(actionMove1, actionDone, NULL);
Sequence* sequence2 = Sequence::create(actionMove2, actionDone, NULL);
Sequence* sequence3 = Sequence::create(actionMove3, actionDone, NULL);
//飞机开始跑动
spritePlane1->runAction(sequence1);
spritePlane2->runAction(sequence2);
spritePlane3->runAction(sequence3);
}
来看看效果:

都还没做碰撞检测,可以看到。敌机按照我们的要求生成并运动了。
这就是群飞飞机群的两种方式,也可以将并飞和跟随相结合。就可以生成很多种不同的飞机路径。这在后头我将会再来讲解,今天就先到这里了。敌机类最好是自己封装,这里还没有实现。有需要的可以自己把函数改改就OK了!最后,再放张图!

Cocos2d-x《雷电大战》(6) 智能敌机 AI 来袭--飞行路径算法设计与实现(下)
智能敌机AI来袭--飞行路径算法设计与实现(上) ,还是对游戏中的敌机路径进行一个设计和实现。这里笔者又实现了两种敌机路线。分别如下:
(1)敌机朝着英雄飞机的位置飞去
(2)左右两群飞机穿过,其实就是一大群飞机从左到右和从右到左的飞行。
本文效果:


Cocos2d-x 版本:3.4
工程环境:VS30213
一、敌机朝英雄飞机飞行
首先来讲讲敌机朝着英雄飞机的位置飞去,这里就比较简单,只要获取了英雄飞机的位置,再加了设置好敌机的初始位置,那么敌机的飞行路径就出来了。下面笔者画了张图,具体可以看如下图:

或者如下

其中英雄飞机的位置我们可以知道,就可以计算a和b的值,并且能得到角度mDegree。这里的mDegree主要是用来旋转敌机的,如果不旋转敌机的话,看起来效果就不怎么好。
现在知道原理了,就开始写代码来实现吧:
void GameMain::enemyBuild3(float dt){
Size winSize = Director::getInstance()->getWinSize();
auto spritePlane = Sprite::create("air2.png");
//得到精灵宽和高
float height = spritePlane->getContentSize().height;
float width = spritePlane->getContentSize().width;
//设置敌机位于右上角
spritePlane->setPosition(Vec2(winSize.width + width / 2, winSize.height + height/2));
spritePlane->setScale(0.25);
this->addChild(spritePlane);
//计算英雄飞机和对角点连起的线与边界的角度
float x = HeroPlane::getInstance()->getPlane()->getPosition().x;
float y = HeroPlane::getInstance()->getPlane()->getPosition().y;
float a = winSize.width - x;
float b = winSize.height - y;
// 弧度转角度
float radians = atanf(a / b);
float mDegree = CC_RADIANS_TO_DEGREES(radians);
spritePlane->setRotation(180+mDegree);
//计算敌机的最终位置
float endX = winSize.width-(a / b)*winSize.height;
float endY = 0;
//计算飞行时间
float flyVelocity = 200;//运行速度,可以自己控制,每秒所走的像素
float flyLen = sqrt((winSize.width - endX)*(winSize.width - endX)+(winSize.height - endY)*(winSize.height - endY));
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕底部
auto actionMove = MoveTo::create(realFlyDuration, Point(endX, endY));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(GameMain::enemyRemove, this));
//连续动作
Sequence* sequence = Sequence::create(actionMove, actionDone, NULL);
//飞机开始跑动
spritePlane->runAction(sequence);
}
注意,这里图片还是没有优化过的,敌机类也还没有单独写一个类,这里只是简单实现了下。
然后开一个定时器定时执行这个操作
//每隔0.5S调用一次
schedule(schedule_selector(GameMain::enemyBuild3), 0.5f);
好了,现在来看看结果:

敌机能朝着英雄飞机撞去,并能实时改变自己的角度,当然。这个角度是一来就计算好了的。
二、左右群飞的敌机
左右群飞就是左边和右边都有一排飞机,然后同时向左或向右运动。飞机路径不是问题,最主要的是设置好它们的起始位置,让它们都能并排一起。原理图如下:

这里要注意的地方就是设置敌机的位置时记得要加上敌机图片的偏移量,Coco2dx中设置精灵位置时默认是以图片中心点为原点,所以得加上这个偏移量。并且它们移动的距离都是一样的,所以可以用MoveBy来实现,Y轴方向运动为0,X轴方向运动量为屏幕宽+敌机宽度。左右敌机X轴方向运动量不一样。记得!并且,这里的 MoveBy的动作只能给一个敌机来使用,如果另一个敌机是相同的动作,那么就可以用clone()函数,而不用再重新创建一个MoveBy动作。
整体代码如下:
void GameMain::enemyBuild4(float dt){
Size winSize = Director::getInstance()->getWinSize();
Point origin = Director::getInstance()->getVisibleOrigin();
//生成左边敌机
auto spritePlane1 = Sprite::create("air5.png");
auto spritePlane2 = Sprite::create("air5.png");
auto spritePlane3 = Sprite::create("air5.png");
//生成边敌机
auto spritePlane4 = Sprite::create("air5.png");
auto spritePlane5 = Sprite::create("air5.png");
auto spritePlane6 = Sprite::create("air5.png");
//旋转的角度
spritePlane1->setRotation(90);
spritePlane2->setRotation(90);
spritePlane3->setRotation(90);
spritePlane4->setRotation(-90);
spritePlane5->setRotation(-90);
spritePlane6->setRotation(-90);
//设置缩放
//spritePlane1->setScale(0.3);
//spritePlane2->setScale(0.3);
// spritePlane3->setScale(0.3);
//得到精灵宽和高
float height = spritePlane1->getContentSize().height;
float width = spritePlane1->getContentSize().width;
//放置敌机位置
spritePlane1->setPosition(Vec2(-width / 2, winSize.height - height / 2-10));
spritePlane2->setPosition(Vec2(-width / 2, spritePlane1->getPosition().y - 2 * height - 10));
spritePlane3->setPosition(Vec2(-width / 2, spritePlane2->getPosition().y - 2 * height - 10));
spritePlane4->setPosition(Vec2(winSize.width + width / 2, spritePlane1->getPosition().y - height - 10));
spritePlane5->setPosition(Vec2(winSize.width + width / 2, spritePlane4->getPosition().y - 2 * height - 10));
spritePlane6->setPosition(Vec2(winSize.width + width / 2, spritePlane5->getPosition().y - 2 * height - 10));
//层中加入精灵
this->addChild(spritePlane1);
this->addChild(spritePlane2);
this->addChild(spritePlane3);
this->addChild(spritePlane4);
this->addChild(spritePlane5);
//计算飞行时间
float flyVelocity = 200;//运行速度,可以自己控制,每秒所走的像素
float flyLen = winSize.width+width;
float realFlyDuration = flyLen / flyVelocity;//实际飞行的时间
//子弹运行的距离和时间,从飞机处开始运行到屏幕底部
auto actionMove1 = MoveBy::create(realFlyDuration, Point(flyLen,0));
auto actionMove2 = MoveBy::create(realFlyDuration, Point(-flyLen, 0));
//子弹执行完动作后进行函数回调,调用移除子弹函数
auto actionDone = CallFuncN::create(
CC_CALLBACK_1(GameMain::enemyRemove, this));
//连续动作
Sequence* sequence1 = Sequence::create(actionMove1, actionDone, NULL);
Sequence* sequence2 = Sequence::create(actionMove1->clone(), actionDone, NULL);
Sequence* sequence3 = Sequence::create(actionMove1->clone(), actionDone, NULL);
Sequence* sequence4 = Sequence::create(actionMove2, actionDone, NULL);
Sequence* sequence5 = Sequence::create(actionMove2->clone(), actionDone, NULL);
//飞机开始跑动
spritePlane1->runAction(sequence1);
spritePlane2->runAction(sequence2);
spritePlane3->runAction(sequence3);
spritePlane4->runAction(sequence4);
spritePlane5->runAction(sequence5);
}
然后还是相同的原理,开个定时器,来看看效果:
//每隔0.5调用一次
schedule(schedule_selector(GameMain::enemyBuild4), 0.5f);

两种飞机一起来:

三、总结
这里飞机的路径设计了四种,因为还没有进行优化,所以内存占用会有点多,后面笔者将会把敌机类全都放在一个Plist中,这样子内存就会小点了。其实,在飞行游戏中。还有BOSS机,BOSS机的智能AI设计也是一个很好玩。当然,敌机子弹类也很重要,而这一部分的内容将会放在敌机类设计完成之后再来讲。下一讲中我们将来封装自己的敌机类。
♻️ 资源
大小: 1.78MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87415918
注:更多内容可关注微信公众号【神仙别闹】,如当前文章或代码侵犯了您的权益,请私信作者删除!

实现的射击类游戏&spm=1001.2101.3001.5002&articleId=159633162&d=1&t=3&u=ed2fe368f2f9483a8ad4e3a73cf9df8d)
3797

被折叠的 条评论
为什么被折叠?



