改写《魔塔》中篇04:重构代码之单例类

       写在前面:中期阶段我们只是按照书上的方式来介绍重构代码的过程,由于版本等原因,粘贴出来的代码已经不符合我们的需要。所以具体改动的代码以中期阶段项目里面的源码文件为准,项目源码下载请移步改写《魔塔》中篇06:善后工作和注意事项(附:中期阶段项目下载)。还有我们对原书中的项目进行了一些改动,包括用CCArray替换了CCMutableArray,增加了用于勇士初始化的getSpriteFrame方法等,因此,大家要注意项目源码改动的地方,并且留意文档注释。

       大家应该注意到上一篇还有两个小部分没有完成,一是动画相关的代码,二是在Hero类中如何访问GameMap对象。

       我们先来完成动画相关的代码。这一部分单独拿出来讲,是考虑到定义动画的工作很耗费代码行数,并且十分单调重复,没有必要放在具体的游戏对象里,可以专门设计一个动画管理器来管理游戏中所有的动画。在场景创建之初,将可能用到的动画,从配置文件一次性加载;在场景运行期间,就可以直接从管理器取得相应的动画资源了。在场景被销毁的时候,再控制动画管理器销毁不用的资源。

        好,在实现具体的动画管理器之前,我们需要多方位思考,请先考虑这几个问题:动画管理器究竟会以什么样的形式被调用?在游戏的各个对象中,哪些会有访问动画管理器的需求?不同的类需要访问不同的管理器实例吗?是否会有多线程同时访问?在仔细思考完这些问题以后,相信你已经得到了答案:我们在整个游戏过程中只需要一个动画管理器,它需要以尽可能方便的形式被所有游戏对象访问到,最后的多线程问题在这款游戏中不存在。所以让我们异口同声地说出心中的那两个字吧,没错!就是单例。

        我们先写一个单例模式的模板容器,以后就可以方便地实现单例了。模板容器的具体代码如下,其中定义了一个静态变量,用于保存单例的实例;有两个公用方法,用于获取唯一实例和释放操作。

#ifndef __SINGLETON_H__
#define __SINGLETON_H__

template <class T>
class Singleton
{
public:
	//获取类的唯一实例
	static inline T* instance();
	//释放类的唯一实例
	void release();
protected:
	Singleton(void){}
	~Singleton(void){}
	static T* _instance;
};

template <class T>
inline T* Singleton<T>::instance()
{
	if(!_instance)
		_instance=new T;
	return _instance;
}

template <class T>
void Singleton<T>::release()
{
	if(!_instance)
		return;
	delete _instance;
	_instance=0;
}

//cpp文件中需要先声明静态变量
#define DECLARE_SINGLETON_MEMBER(_Ty)\
	template <> _Ty* Singleton<_Ty>::_instance=NULL;

#endif//_SINGLETON_H

      然后我们新建一个AnimationManager类,继承Singleton类。可以直接重用基类的获取实例和销毁实例的方法。同时,它拥有一个动画映射表,用来缓存动画模板,可以根据int型的键值查询对应的动画模板。定义了一个枚举类型,罗列了目前所有动画对应的键值。对外暴露的公告方法有3个:initAnimationMap用于初始化动画模板资源到AnimationCache中,getAnimation可以根据键值返回对应的动画模板定义,createAnimate方法用来生成指定的动画实例,可以直接供CCNode的runAction调用。具体头文件定义如下:

#ifndef __ANIMATION_MANAGER_H__
#define __ANIMATION_MANAGER_H__

#include "MTGame.h"

typedef enum
{
	aDown=0, //向下行走动画
	aLeft, //向左行走动画
	aRight, //向右行走动画
	aUp, //向上行走动画
}AnimationKey; //动画模板键值

using namespace cocos2d;

class AnimationManager:public Singleton<AnimationManager>
{
public:
	AnimationManager();
	~AnimationManager();
	//初始化动画模板缓存表
	bool initAnimationMap();
	//根据名字得到一个动画模板
	CCAnimation* getAnimation(int key);
	//创建一个动画实例
	CCAnimate* createAnimate(int key);
protected:
	//加载勇士行走动画模板
	CCAnimation* createHeroMovingAnimationByDirection(HeroDirection direction);
};
//定义动画管理器实例的别名
#define sAnimationMgr AnimationManager::instance()
#endif

      各方法的具体实现如下,在initAnimationMap方法中,将勇士的行走动画加载到全局的CCAnimationCache中。getAnimation获取动画模板时,也是从CCAnimationCache中获取。

#include "AnimationManager.h"

DECLARE_SINGLETON_MEMBER(AnimationManager);

AnimationManager::AnimationManager()
{
}

AnimationManager::~AnimationManager()
{
	//CCDirector会自己清除AnimationCache
	//CCAnimationCache::purgeSharedAnimationCache();
}

bool AnimationManager::initAnimationMap()
{
	char temp[20];
	sprintf(temp,"%d",aDown);
	//加载勇士向下走的动画
	CCAnimationCache::sharedAnimationCache()->addAnimation(createHeroMovingAnimationByDirection(kDown),temp);
	sprintf(temp,"%d",aRight);
	//加载勇士向右走的动画
	CCAnimationCache::sharedAnimationCache()->addAnimation(createHeroMovingAnimationByDirection(kRight),temp);
	sprintf(temp,"%d",aLeft);
	//加载勇士向左走的动画
	CCAnimationCache::sharedAnimationCache()->addAnimation(createHeroMovingAnimationByDirection(kLeft),temp);
	sprintf(temp,"%d",aUp);
	//加载勇士向下走的动画
	CCAnimationCache::sharedAnimationCache()->addAnimation(createHeroMovingAnimationByDirection(kUp),temp);

	return true;
}

CCAnimation* AnimationManager::createHeroMovingAnimationByDirection(HeroDirection direction)
{
	CCTexture2D* heroTexture=CCTextureCache::sharedTextureCache()->addImage("hero.png");
	CCSpriteFrame *frame0,*frame1,*frame2,*frame3;
	//第二个参数表示显示区域的x,y,width,height,根据direction来确定显示的y坐标
	frame0=CCSpriteFrame::frameWithTexture(heroTexture,cocos2d::CCRectMake(32*0,32*direction,32,32));
	frame1=CCSpriteFrame::frameWithTexture(heroTexture,cocos2d::CCRectMake(32*1,32*direction,32,32));
	frame2=CCSpriteFrame::frameWithTexture(heroTexture,cocos2d::CCRectMake(32*2,32*direction,32,32));
	frame3=CCSpriteFrame::frameWithTexture(heroTexture,cocos2d::CCRectMake(32*3,32*direction,32,32));	

	CCMutableArray<CCSpriteFrame *> *animFrames=new CCMutableArray<CCSpriteFrame *>(4);
	animFrames->addObject(frame0);
	animFrames->addObject(frame1);
	animFrames->addObject(frame2);
	animFrames->addObject(frame3);

	CCAnimation *animation=new CCAnimation();

	//0.05f表示每帧动画间的间隔
	animation->initWithFrames(animFrames,0.05f);
	animFrames->release();

	return animation;
}

//获取指定动画模板
CCAnimation* AnimationManager::getAnimation(int key)
{
	char temp[20];
	sprintf(temp,"%d",key);
	return CCAnimationCache::sharedAnimationCache()->animationByName(temp);
}

//获取一个指定动画模板的实例
CCAnimate* AnimationManager::createAnimate(int key)
{
	//获取指定动画模板
	CCAnimation* anim=getAnimation(key);
	if(anim)
	{
	   //根据动画模板生成一个动画实例
		return cocos2d::CCAnimate::actionWithAnimation(anim);
	}
	return NULL;
}

      接下来,我们在哪里调用initAnimationMap方法呢?如果游戏资源比较多,最好创建一个加载(loading)界面,在后台加载资源,加载完毕后再进入游戏场景。如果资源不多,可以简单地在AppDelegate的applicationDidFinishLaunching中初始化AnimationManager,然后在AppDelegate的析构函数中释放它。

bool AppDelegate::applicationDidFinishLaunching()
{
    // initialize director
    CCDirector *pDirector = CCDirector::sharedDirector();
    pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());

    // sets landscape mode
    pDirector->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);

    // turn on display FPS
    pDirector->setDisplayFPS(false);

    // set FPS. the default value is 1.0/60 if you don't call this
    pDirector->setAnimationInterval(1.0 / 60);

	//初始化动画管理器
	sAnimationMgr->initAnimationMap();

    // create a scene. it's an autorelease object
	CCScene *pScene = GameScene::playNewGame();

    // run
    pDirector->runWithScene(pScene);
    return true;
}

AppDelegate::~AppDelegate()
{
    SimpleAudioEngine::end();

	//释放动画管理器
	sAnimationMgr->release();
}

在Hero类的move方法中,修改heroSprite的runAction方法如下:

heroSprite->runAction(sAnimationMgr->createAnimate(direction));

       上面我们用单例模式创建了一个动画管理器,在游戏的任意对象中都能方便地使用。接着我们再介绍一种单例模式的应用场景。大家可能已经发现,随着代码的重构,单独的类被拆分成多个功能相对单一的类,这样类之间的数据访问成了一个很大的问题。比如,在Hero类的checkCollision方法中需要用到GameMap对象,在moveDone事件回调中,需要访问GameLayer的setSceneScrollPosition方法。我们当然可以通过传递对象的方式来进行数据交互,但这种方式相对来说比较麻烦。如果可以保证各个对象的唯一性,比如当前游戏主场景、游戏地图和勇士等只可能有一个实例存在,那么最简便的方法就是使用单例模式创建一个类,保存全局都可以访问的唯一性变量。下面,新建一个Global类继承于Singleton类,其中包含了几个需要全局访问的游戏对象,即GameLayer、GameMap、和Hero等,具体头文件如下:

#ifndef _GLOBAL_H_
#define _GLOBAL_H_

#include "MTGame.h"

using namespace cocos2d;

class GameLayer;
class GameMap;
class Hero;

class Global:public Singleton<Global>
{
public:
	Global(void);
	~Global(void);

	//游戏主图层
	GameLayer* gameLayer;
	//游戏地图
	GameMap* gameMap;
	//勇士
	Hero* hero;
};

#define sGlobal Global::instance()
#endif

注意,要在各类的构造函数中对Global中的变量进行赋值:

Hero::Hero()
{
	sGlobal->hero=this;
}

GameMap::GameMap()
{
	sGlobal->gameMap=this;
}

GameLayer::GameLayer()
{
	sGlobal->gameLayer=this;
}

      由于我们只保存了上述3个对象的指针,所以对象本身的释放工作不需要Global类来完成,只需要在析构函数中将各个指针变量设置为NULL即可。

Global::~Global()
{
	this->gameScene=NULL;
	this->hero=NULL;
	this->gameMap=NULL;
	this->gameLayer=NULL;
}

创建好了Global类,修改Hero类中所有需要使用gameMap实例的地方,部分代码如下:

void Hero::onMoveDone(CCNode *pTarget,void *data)
{
	//将void*转换为int,再从int转换到枚举类型
	int direction=(int) data;
	setFaceDirection((HeroDirection)direction);	

	//移动完毕,设置移动状态为false
	isHeroMoving=false;
	sGlobal->gameLayer->setSceneScrollPosition(this->getPosition());
}

//判断碰撞类型
CollisionType Hero::checkCollision(CCPoint heroPosition)
{
	//cocos2dx坐标转换为Tilemap坐标
	CCPoint targetCoord=sGlobal->gameMap->tileCoordForPosition(heroPosition);
	//如果勇士坐标超出地图边界,则返回kWall,阻止其移动
	if(heroPosition.x<0||targetCoord.x>sGlobal->gameMap->getMapSize().width-1
		||targetCoord.y<0||targetCoord.y>sGlobal->gameMap->getMapSize().height-1)
		return kWall;
	//获取当前位置图块
	int tileGrid=sGlobal->gameMap->getWallLayer()->tileGIDAt(targetCoord);
	//如果图块ID不为0表示有墙
	if(tileGrid){
		return kWall;
       }
	//可以通行
	return kNone;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值