【cocos2d-x 3.0】入门样例《SimpleGame》源码解读【3】

下面我们来看中弹判定定时器,以及触摸发射子弹的部分

我觉得先看一下发射子弹比较符合理解的逻辑:)


在之前的代码中,我们设定了触摸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.csdn.net/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主所有

*转载请注明出处

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值