改写《魔塔》中篇03:重构代码之分离勇士类

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

       接下来我们开始分离勇士类,暂命名为Hero类。Hero类中应包括用于显示的精灵、各种属性(攻击、防御和生命值)和勇士移动的方法等。

       首先,请大家思考一个问题:Hero类是否应该继承于CCSprite?我们以前实现的勇士移动,仅仅使用了一个CCSprite,那么现在要往里面添加额外的方法,继承自CCSprite应该就可以满足了。这种考虑没错,确实就目前要实现的功能来说,这个方法是可行的。但是如果能看得稍微远一点,就会发现这个方案缺乏灵活性。设想一下,如果我们想在勇士的头顶上加一个用于显示生命值的血条,要求它能随勇士移动而移动,难道需要写一个update方法,不停地刷新生命条的坐标吗?这显然不现实,也不合理。同理还有勇士的装备和光环buff的技能效果等。所以我们让Hero类继承CCNode,然后将用于显示的精灵作为孩子节点添加进去,而游戏主界面将Hero整体作为一个CCNode保存为孩子节点。这样针对Hero主体的缩放、移动、执行动作、显示和隐藏等操作都会影响到Hero类内部的所有显示节点。下面来看下Hero类头文件的定义:

#ifndef __HERO_H__
#define __HERO_H__

#include "MTGame.h"

using namespace cocos2d;

//勇士类继承自CCNode
class Hero:public cocos2d::CCNode
{  
public:
	Hero();
	~Hero();
	//静态方法,用于创建勇士实例
	static Hero* heroWithinLayer();
	//初始化方法
	bool heroInit();
	//用于显示勇士形象的精灵
	CCSprite* heroSprite;
	//标识勇士是否在移动状态
	bool isHeroMoving;
	//让勇士向指定方向移动一格
	void move(HeroDirection direction);
	//移动完成后的回调函数
	void onMoveDone(CCNode* pTarget,void* data);
	//设置勇士朝向
	void setFaceDirection(HeroDirection direction);
};

#endif

      静态方法heroWithinLayer用于创建 勇士实例,仍然遵循cocos2d-x的对象创建方式。与GameMap类中的gameMapWithTMXFile方法作用类似,相信大家对此都已经比较了解。具体代码如下:

//静态方法,用于生成Hero实例
Hero* Hero::heroWithinLayer()
{
	//new一个对象
	Hero* pRet=new Hero();
	//调用init方法
	if(pRet&&pRet->heroInit())
	{
		//将实例放入autorelease池,统一由引擎控制对象的生命周期
		pRet->autorelease();
		return pRet;
	}
	CC_SAFE_DELETE(pRet);
	return NULL;
}

        heroInit是用来初始化Hero类的,主要完成heroSprite的新建、添加、设置锚点以及一些标志位的赋值等。具体代码如下。注意,CCSprite::createWithSpriteFrame方法并没有给完全,暂时先放在这里,因为我们将在下一篇中引入一个新类来管理动画。

bool Hero::heroInit()
{
	//根据向下行走动画的第一帧创建精灵
	heroSprite=CCSprite::createWithSpriteFrame(/*根据指定动画帧创建对象*/);
	//设置锚点
	heroSprite->setAnchorPoint(CCPointZero);
	//将用于显示的heroSprite加到自己的节点下
	this->addChild(heroSprite);
	//一开始不处于move状态
	isHeroMoving=false;
	return true;
}

       Hero的move方法除了照搬HelloWorldScene中的menuCallBackMove以外,还需要做一些修改。原来我们为了对单个CCSprite实现移动和播放动画同时进行的效果,使用了CCSpawn。现在由于Hero类可能会承载多个精灵,不能单纯地只移动heroSprite一个精灵,而是要将作为CCNode的Hero主体类整体进行移动。所以heroSprite仅需要播放行走动画,而Hero类进行位移动作。注意,原来heroSprite->getPosition()的地方都需要改成this->getPosition()。另外,为了保证Hero是在移动到目标位置后再执行onMoveDone,我们将其与Hero节点本身的CCMoveBy动作拼接成为一个串行操作。move方法的部分代码如下:

void Hero::move(HeroDirection direction)
{
	if(isHeroMoving)
		return;
	//若干操作
	......
	//计算目标坐标,用当前勇士坐标加上移动距离
	CCPoint targetPosition=ccpAdd(this->getPosition(),moveByPosition);
	//调用checkCollision检测碰撞类型,如果是墙壁,则只需要设置勇士的朝向
	if(checkCollision(targetPosition)==kWall)
	{
		setFaceDirection((HeroDirection)direction);
		return;
	}
	//heroSprite仅播放行走动画
	heroSprite->runAction(/*获取指定动画对象*/);
	//主体进行位移,结束时调用onMoveDone方法
	CCAction* action=CCSequence::create(
		CCMoveBy::create(0.2f,moveByPosition),
		//把方向消息传递给onMoveDone方法
		CCCallFuncND::create(this,
		callfuncND_selector(Hero::onMoveDone),(void*)direction),
		NULL);
	this->runAction(action);
	isHeroMoving=true;
}

      onMoveDone和setFaceDirection方法基本和原来HelloWorldScene中的实现类似,注意要将用到heroSprite->getPosition()的地方都需要改成this->getPosition()。

      接下来开始改写checkCollision方法,大家会发现里面用到了gameMap对象。如何在Hero类中访问GameMap对象呢?我们下一篇会介绍一种方法。

      好了,目前为止Hero类的功能已经基本完成,现在需要修改一下GameLayer中初始化勇士的方法:

//调用Hero类的静态方法创建实例
	hero=Hero::heroWithinLayer();
	//设置Hero的起始位置
	hero->setPosition(map->positionForTileCoord(ccp(1,11)));
	//将Hero加入GameLayer
	addChild(hero);

很简单吧,仅需要3行代码就在游戏中创建出了一个勇士。



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值