下面我们来看中弹判定定时器,以及触摸发射子弹的部分
我觉得先看一下发射子弹比较符合理解的逻辑:)
在之前的代码中,我们设定了触摸listener的回调函数,也就是触摸屏幕之后要执行的方法是:
// cpp with cocos2d-x
void HelloWorld::onTouchesEnded(const std::vector<Touch*>& touches, Event* event)
{
// Choose one of the touches to work with
// [RN] 获得第一点触控的位置
Touch* touch = touches[0];
Point location = touch->getLocation();
log("++++++++after x:%f, y:%f", location.x, location.y);
// Set up initial location of projectile
// [RN] 创建子弹精灵并设定初始位置在主角处
Size winSize = Director::getInstance()->getVisibleSize();
auto origin = Director::getInstance()->getVisibleOrigin();
Sprite *projectile = Sprite::create("Projectile.png", Rect(0, 0, 20, 20));
projectile->setPosition( Point(origin.x+20, origin.y+winSize.height/2) );
// Determine offset of location to projectile
// [RN] 建立触摸点到主角处的向量(offX, offY)
float offX = location.x - projectile->getPosition().x;
float offY = location.y - projectile->getPosition().y;
// Bail out if we are shooting down or backwards
// [RN] 如果触摸位置在主角左面,什么都不做直接返回
if (offX <= 0) return;
// Ok to add now - we've double checked position
// [RN] 将子弹加入到屏幕
this->addChild(projectile);
// Determine where we wish to shoot the projectile to
// [RN] 根据相似三角形性质,计算子弹最终飞出屏幕的位置
float realX = origin.x + winSize.width + (projectile->getContentSize().width/2);
float ratio = offY / offX;
float realY = (realX * ratio) + projectile->getPosition().y;
Point realDest = Point(realX, realY);
// Determine the length of how far we're shooting
// [RN] 建立子弹飞行终点到主角处的向量(offRealX, offRealY)
float offRealX = realX - projectile->getPosition().x;
float offRealY = realY - projectile->getPosition().y;
// [RN] 计算飞行长度
float length = sqrtf((offRealX * offRealX) + (offRealY*offRealY));
// [RN] 设定飞行速度每秒480像素
float velocity = 480/1; // 480pixels/1sec
// [RN] 计算飞行时间 = 飞行长度 / 飞行速度
float realMoveDuration = length/velocity;
// Move projectile to actual endpoint
// [RN] 执行子弹飞行的动作,飞行完毕之后执行回调
projectile->runAction( Sequence::create(
MoveTo::create(realMoveDuration, realDest),
CallFuncN::create(CC_CALLBACK_1(HelloWorld::spriteMoveFinished, this)),
NULL) );
// Add to projectiles array
// [RN] 将子弹对象加入子弹数组,并设定子弹的TAG为2
projectile->setTag(2);
_projectiles->addObject(projectile);
// [RN] 简单播放一个piu的发射声音
CocosDenshion::SimpleAudioEngine::getInstance()->playEffect("pew-pew-lei.wav");
}
其中子弹飞行完毕的回调函数,与之前敌人飞行完毕的回调是同一个
接下来就是中弹判定的部分了,也就是在init()方法中的第二个定时器,它会控制每一帧都执行下面的updateGame方法
void HelloWorld::updateGame(float dt)
{
// [RN] 新建一个需要删除的子弹数组并初始化
Array *projectilesToDelete = new Array();
projectilesToDelete->init();
Object* it = NULL;
Object* jt = NULL;
// [RN] 遍历每颗飞行中的子弹
// for (it = _projectiles->begin(); it != _projectiles->end(); it++)
CCARRAY_FOREACH(_projectiles, it)
{
auto projectile = dynamic_cast<Sprite*>(it);
// [RN] 获得一颗子弹的攻击范围
auto projectileRect = Rect(
projectile->getPosition().x - (projectile->getContentSize().width/2),
projectile->getPosition().y - (projectile->getContentSize().height/2),
projectile->getContentSize().width,
projectile->getContentSize().height);
// [RN] 新建一个需要删除的敌人数组并初始化
auto targetsToDelete = new Array();
targetsToDelete->init();
// [RN] 遍历每个屏幕中的敌人
// for (jt = _targets->begin(); jt != _targets->end(); jt++)
CCARRAY_FOREACH(_targets, jt)
{
auto target = dynamic_cast<Sprite*>(jt);
// [RN] 获得一个敌人的受攻击判定范围
auto targetRect = Rect(
target->getPosition().x - (target->getContentSize().width/2),
target->getPosition().y - (target->getContentSize().height/2),
target->getContentSize().width,
target->getContentSize().height);
// [RN] 如果子弹攻击范围和敌人判定范围有相交,则敌人被击中,把这位放进待删除的敌人数组
// if (Rect::RectIntersectsRect(projectileRect, targetRect))
if (projectileRect.intersectsRect(targetRect))
{
targetsToDelete->addObject(target);
}
}
// [RN] 遍历待删除的敌人数组
// for (jt = targetsToDelete->begin(); jt != targetsToDelete->end(); jt++)
CCARRAY_FOREACH(targetsToDelete, jt)
{
auto target = dynamic_cast<Sprite*>(jt);
// [RN] 从场景类的敌人数组中删除这个敌人
_targets->removeObject(target);
// [RN] 从屏幕上删除这个敌人
this->removeChild(target, true);
// [RN] 累加击败敌人数,如果击败敌人>=5则游戏胜利
_projectilesDestroyed++;
if (_projectilesDestroyed >= 5)
{
// [RN] 游戏胜利,建立对应的GameOver场景并跳转
auto gameOverScene = GameOverScene::create();
gameOverScene->getLayer()->getLabel()->setString("You Win!");
Director::getInstance()->replaceScene(gameOverScene);
}
}
// [RN] 如果这颗子弹击中了敌人,则把这颗子弹加入待删除子弹列表
if (targetsToDelete->count() > 0)
{
projectilesToDelete->addObject(projectile);
}
// [RN] 不要忘记释放targetsToDelete数组
targetsToDelete->release();
}
// [RN] 所有子弹遍历完成之后,将所有待删除子弹数组中的子弹清除(场景类中以及屏幕中)
// for (it = projectilesToDelete->begin(); it != projectilesToDelete->end(); it++)
CCARRAY_FOREACH(projectilesToDelete, it)
{
auto projectile = dynamic_cast<Sprite*>(it);
_projectiles->removeObject(projectile);
this->removeChild(projectile, true);
}
// [RN] 不要忘记释放projectilesToDelete数组
projectilesToDelete->release();
}
至此,关于发射子弹以及击中敌人的机制就完全清楚了 :)
最后我们再来看一下GameOverSence游戏结束画面的一些相关实现
首先是GameOverScene.h
class GameOverLayer : public cocos2d::LayerColor
{
public:
GameOverLayer():_label(NULL) {};
virtual ~GameOverLayer();
bool init();
CREATE_FUNC(GameOverLayer);
void gameOverDone();
// [RN] 这个宏创建了一个名为_label的LabelTTF文字标签(权限为protected),并设定了一个getter函数getLabel
CC_SYNTHESIZE_READONLY(cocos2d::LabelTTF*, _label, Label);
};
class GameOverScene : public cocos2d::Scene
{
public:
GameOverScene():_layer(NULL) {};
~GameOverScene();
bool init();
CREATE_FUNC(GameOverScene);
// [RN] 这个宏创建了一个名为_layer的层成员(权限为protected),并设定了一个getter函数getLayer
CC_SYNTHESIZE_READONLY(GameOverLayer*, _layer, Layer);
};
上篇我们分析过,这个场景类的实现是直接继承了Scene类
其中包含一个GameOverLayer的成员_layer(继承了LayerColor)
而GameOverLayer类中又包含了一个文字标签成员_label(类型为LabelTTF)
外部直接调用GameOverScene::create()即可顺便把我们要用到的Layer(作为成员)和Label(作为成员的成员)都创建出来
这部分对应的代码:
using namespace cocos2d;
// [RN] 不要忘记这个方法是由create()直接调用的哦
bool GameOverScene::init()
{
if( Scene::init() )
{
this->_layer = GameOverLayer::create();
// [RN] *注意1* 这个retain()方法表示:宣告_layer的内存将手动释放!不受autoRelease控制!
this->_layer->retain();
// [RN] *注意2* 其实addChild方法会自动调用对象的retain方法
this->addChild(_layer);
return true;
}
else
{
return false;
}
}
GameOverScene::~GameOverScene()
{
// [RN] 析构函数,对应于上面的retain()我们需要手动释放_layer的内存
if (_layer)
{
_layer->release();
_layer = NULL;
}
}
bool GameOverLayer::init()
{
// [RN] 初始化层,创建_label文字标签对象并设定它的属性和动作
if ( LayerColor::initWithColor( Color4B(255,255,255,255) ) )
{
auto winSize = Director::getInstance()->getWinSize();
this->_label = LabelTTF::create("","Artial", 32);
_label->retain();
_label->setColor( Color3B(0, 0, 0) );
_label->setPosition( Point(winSize.width/2, winSize.height/2) );
this->addChild(_label);
this->runAction( Sequence::create(
DelayTime::create(3),
CallFunc::create( CC_CALLBACK_0(GameOverLayer::gameOverDone, this)),
NULL));
return true;
}
else
{
return false;
}
}
void GameOverLayer::gameOverDone()
{
// [RN] 源自_label标签动作的回调,表示GameOver场景播放完毕,切回游戏主场景(重新开始游戏)
Director::getInstance()->replaceScene( HelloWorld::scene() );
}
GameOverLayer::~GameOverLayer()
{
// [RN] 析构函数,手动释放_label的内存(因为前面addChild方法默认调用了_label->retain方法)
if (_label)
{
_label->release();
_label = NULL;
}
}
这部分代码涉及到了一个之前没有讨论过的问题,就是关于内存管理
上面这段代码其实对刚入门的同学了解cocos2dx内存管理机制很有帮助,其实所有又create()方法创建的对象,我们”顺宏摸瓜“发现
#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; \
} \
}
其实它会给create出来的对象赋予一个autoRelease的特性,也就是内存将被自动回收!
这一点是cocos2dx引擎特有的一个内存管理特性,也就是说大多数对象在通过create()静态方法创建之后,你不必担心它的内存泄露问题,它会自动释放
但它并不像JAVA的垃圾回收那么智能,它有可能在你并不打算释放这个对象的时候,就把它释放了!导致你程序崩溃
所以如果你想要告诉引擎,我将手动回收对象的内存,那么就请在create()之后调用对象的retain()方法来声明它将由你手动管理内存。
但有细心的同学会发现,为什么HelloWorldScene的实现中并没有出现retain()方法的调用呢?
其实是因为addChild方法会自动调用对象的retain()方法,保证屏幕上的东西不会被莫名其妙的自动回收!
而上面这份代码虽然也调用了addChild但为什么还要retain()呢?我想是作者想要故意引出这个问题,让大家了解cocos2dx的内存管理吧,用心良苦啊- -
(这里有一篇不错的关于cocos2dx内存管理入门的文章:http://blog.youkuaiyun.com/musicvs/article/details/8689345)
最后我们回到HelloWorldScene类看一下它的析构函数
HelloWorld::~HelloWorld()
{
// [RN] 析构函数,若游戏终止后有敌人对象或子弹对象没有释放,则在此释放防止内存泄露
if (_targets)
{
_targets->release();
_targets = NULL;
}
if (_projectiles)
{
_projectiles->release();
_projectiles = NULL;
}
// cpp don't need to call super dealloc
// virtual destructor will do this
}
看!与GameOverScene的析构函数做了相同的事情嘛,一下就明朗了是吗?
当你点击按钮关闭游戏时,最终调用了menuClose菜单对象的回调函数
void HelloWorld::menuCloseCallback(Object* sender)
{
// "close" menu item clicked
Director::getInstance()->end();
}
我想不用解释大家都能看懂 :) 这就是大导演宣布游戏结束的方式啦!
至此,大家是不是和UP主一样对cocos2dx引擎有了一个初步的了解了呢?
赶快去尝试自己做一个小游戏试试吧!
And as always,
HAVE A NICE DAY!
(全文完)
*版权由UP主所有
*转载请注明出处