现在的位置: 首页 > 综合 > 正文

cocos2d-x 学习笔记05——MySpaceWar1.0

2014年01月04日 ⁄ 综合 ⁄ 共 4841字 ⁄ 字号 评论关闭

推荐看2篇讲述飞机游戏的绝好文章:

地球人04 http://www.cocoachina.com/gamedev/gameengine/2012/0618/4367.html

笨木头04 http://blog.csdn.net/musicvs/article/details/8135705#comments

类图基础知识:http://blog.csdn.net/qxbailv15/article/details/9113283

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

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

               

      

           在C++ 中没有接口类,可以用抽象类模拟,即被画成紫色的类。 采用PowerDesigner  画类图。(其实我把关联和复合混淆了,把它当成了一回事。A类关联B类,是指A类里有B类的指针,而复合是不仅A类里有B类的指针,而且他们是同时产生的,比如小鸟和它的翅膀,翅膀指针需要在小鸟的构造函数里去赋值,但是玩家和控制器就不同了,它们是关联的关系,控制器不是玩家本身的一部分,不能在玩家构造函数的里面进行初始化,而应当在玩家的init函数里调用额外的函数setControlller()
 
)

          

 

     我的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;
}

 

更多知识和设计上的体会以后有时间再多写点吧。下面是游戏部分截图:

                                                                                                                                 菜单场景

 

                                                                                                                                游戏场景                       

 

                                                                             

点击此处下载源码和apk

 

 

抱歉!评论已关闭.