• 热门专题

cocos2d-x学习笔记05 MySpaceWar1.0

作者:qxbailv15的专栏  发布日期:2013-12-25 21:21:09
Tag标签:cocos2d-x  学习笔记  05  
  •  

    上面2篇文章的组合设计替代继承的思想非常精彩,虽然刚开始实现会麻烦点,但是后期,绝对越来越简单,需要什么,组装什么。麻雀虽小,五脏齐全,千万别小看了这些设计。对面向对象能力是个很好的考验。好的设计不会让自己走着走着就再也走不动了。花了刚好一星期,写了个MySpaceWar1.0,同样是飞行游戏,这是写的第一个android游戏,平常上课下课的时候就可以自己玩自己的游戏了,真棒,哈哈。

    我的类图基本上是这样的:

     

    在C++ 中没有接口类,可以用抽象类模拟,即被画成紫色的类。 采用PowerDesigner 画类图。

     

    我的MySpaceWar1.0版 的刚开始的设计也是基于上面2篇文章的思想,多次企图修改,但最好不得不改回去,因为我考虑的不够多,最后发现基本改不了。只能一步步和作者走向重合。但是还是觉得他们的关于子弹管理器的设计过于复杂了,互相设置监听类的方法,难于为所有怪物加上子弹管理器,非常复杂。既要考虑到数据的封装,又要考虑接口的合理性,不乱设计方法的参数的情况下为怪物装上子弹管理器,让怪物有发射子弹的能力是很难的。 于是,必须稍微放弃点作者原有的设计,才能打破僵局, 也就是说需要重构。之所以局部 重构的理由是我要为每只怪物都能复合(组合)一个子弹管理器,让怪物也能有发射子弹功能。用原有的监听模式,过于复杂,只能一时的想清楚整个逻辑,但是过一会儿,又把这些设计逻辑忘了。所以必须有一个简单易懂的设计,无用的抽象类删掉。

     

    于是我试着设计了个碰撞管理器类(CollisionManager),现在有3个管理类了,怪物管理器类,子弹管理器类。 当然我们应该让 itemsLayer 去 复合 碰撞管理器类(CollisionManager)。

    删掉了他们原先的Shooter抽象类,CollisionListener抽象类,BulletListener抽象类。其实这些设计监听者的核心思想是,被监听者复合监听者的指针,然后在被监听者的update()函数里,通过复合的监听者指针不断去调用监听者的函数,通过这样模拟一种“回调函数”的效果。很值得学习,但是却不能滥用。

     

    同时让Collidable类,多一个方法CollisionDectected().

     

    class Collidable
    {
    public:
    	virtual bool isCollidedWith(Collidable* target) = 0;  // 这里是判断是否和其他可碰撞的对象碰撞
    	virtual CCPoint GetPosition() = 0; // 此函数放在这似乎不太合适,任何entity按道理自己应该有这2个函数,但是isCollidedWith的函数实现会告诉你你不得不这么做,不然就得用dynamic_cast 强制转换为子类指针后调用GetPosition()和CollisionDetected(),2种选其一啦。
    	virtual const CCSize GetContainSize() = 0;
    
    	virtual void CollisionDetected() = 0;        // 2013年12月24日12:37:54 add
    };

     

    只需要传进player*和 monsterManager* 指针,同时让CollisonManager成为Player,MonsterManager,BulletManager它们的友元类。 让CollisonManager要风得风,要雨得雨,取上将的头颅如探囊取物尔。不然CollisionManager 无法获取他们的数据,就没办法统一管理了。

    或许我们会想到让MonsterManager实现一个方法GetMonsters()获取其怪物数组,但是这样的话,monsters数组就和没封装一样的,暴露给了所有其他类。 所以我能想到的最好的办法是只暴露给CollisionManager ,毕竟它为你承担了碰撞检测的艰巨的负担,让人家成为你的朋友——友元类,不过分吧。同样让CollisionManager成为BulletManager,Player的朋友.

     

    void CollisionManager::update(float dt) // 做3种情况的碰撞检测,会不会导致还没检测到碰撞,子弹就已经穿过怪物了,尤其是手机比较卡的时候
    {
    	/* 1.检测player 和 monster是否碰撞了 */
    	CCObject* obj = NULL;
    	CCObject* obj2 = NULL;
    	CCARRAY_FOREACH(mMonsterManager->mMonsterArray, obj )
    	{
    		Monster* monster = (Monster*)obj;
    		if (monster && monster->getIsAlive() && monster->isCollidedWith(mPlayer) )
    		{
    			monster->CollisionDetected();
    			mPlayer->CollisionDetected();
    		}
    	}
    
    	CCARRAY_FOREACH( mMonsterManager->mMonsterArray,obj)
    	{
    		Monster* monster = (Monster*)obj;
    		CCARRAY_FOREACH( monster->mBulletManager->mBulletArray,obj2)
    		{
    			Bullet* bullet = (Bullet*)obj2;
    			if (bullet && bullet->getIsAlive() && mPlayer && mPlayer->isCollidedWith(bullet))
    			{
    				mPlayer->CollisionDetected();
    				bullet->CollisionDetected();
    			}
    		}
    	}
    
    	/* 3.monster 和 player 的子弹碰撞 */
    	CCARRAY_FOREACH(mPlayer->mBulletManager->mBulletArray, obj)
    	{
    		Bullet* bullet = (Bullet*)obj;
    		if (bullet && bullet->getIsAlive())        // 用if稍微过滤下,减少调用次数
    		{
    			CCARRAY_FOREACH(mMonsterManager->mMonsterArray, obj2)
    			{
    				Monster* monster = (Monster*)obj2;
    				if (monster->getIsAlive() && monster->isCollidedWith(bullet))
    				{
    					monster->CollisionDetected();  
    					mPlayer->mScore ++ ;          // 让玩家的积分 + 1
    					mPlayer->refreshUiValue();    // 刷新得分
    					bullet->CollisionDetected();
    				}
    			}
    		}
    	}

     

    因为子弹的精灵图片有多种,比如player和monster的子弹就不一样,如何 让一个bullet类既能承担player的子弹的显示的精灵,又能显示monster的子弹的精灵呢。或许很容易想到用2个子类继承bullet,不就行了。这样当然不行,假如有10种子弹呢,岂不是要10个继承自bullet子类了。我想的办法就是通过在bullet类里传入枚举类型,构造时先判断这是谁的子弹,来响应地用哪种图片。总之利用枚举,可以达到一个类适合多种情况的效果。如下:

    class Bullet : public Entity
    {
    public:
    	enum BULLET_TYPE
    	{
    		PLAYER_BULLET = 1,
    		MONSTER_BULLET
    	};
    	bool init(CCSpriteBatchNode* batchNode, BULLET_TYPE type);
        static Bullet* createWithBatchNode(CCSpriteBatchNode* batchNode, BULLET_TYPE type);
    
    	virtual void CollisionDetected();
    };

     

    bool Bullet::init(CCSpriteBatchNode* batchNode, BULLET_TYPE type)
    {
    	bool bRet = false;
    	do 
    	{
    		char fileName[60] = {0};
    		sprintf(fileName, "bullet_%02d.png",  type);   // 没有写死,传个枚举打破僵局,也避免了if else 的可维护性差
    	//	CCSprite* bulletSprite = CCSprite::createWithSpriteFrameName("bullet_01.png");  写死了
    		CCSprite* bulletSprite = CCSprite::createWithSpriteFrameName(fileName); 
    		CC_BREAK_IF(! bulletSprite);
    	
    		batchNode->addChild(bulletSprite);
    		this->setVisual(bulletSprite);
    
    		/* 为子弹加上一个简单移动控制器,让它能飞起来 */
    		SimpleMoveController* moveController = SimpleMoveController::create();
    		
    		if ( type == PLAYER_BULLET )      
    		{
    			moveController->setYSpeed(400);
    		}
    		else if (type == MONSTER_BULLET)
    		{
    			moveController->setYSpeed(-200);
    		}
    		else
    		{
    			;
    		}
    		this->setController(moveController);
    
    		bRet = true;
    	} while (0);
    	return bRet;
    }

    更多知识和设计上的体会以后有时间再多写点吧。

About IT165 - 广告服务 - 隐私声明 - 版权申明 - 免责条款 - 网站地图 - 网友投稿 - 联系方式
本站内容来自于互联网,仅供用于网络技术学习,学习中请遵循相关法律法规