改写《魔塔》后篇02:添加怪物动画

      怪物在地图上的位置是不变的,但它们都有对应的原地站立时的动画。大家可能想到使用前面创建的动画管理器来创建动画模板,然后播放各个怪物精灵的动画。这样做没错,但是想想就觉得麻烦:如果有100种怪物的话,难道要定义100个动画模板吗?答案是否定的。那么该如何实现怪物原地站立的动画呢?从提供的怪物图片素材可以发现:每个怪物的动作由4帧动画组成,且每帧尺寸一致,都正好是一个图块的大小。是否可以定时地更新怪物对应的图块,从而产生动画的效果?结论是可以。cocos2d-x工程下test项目中的TMXReadWriteTest例子演示了如何动态改变TileMap上的图块,以及用定时器不停地重复更新图块的工作。关键代码如下:

//创建定时器,反复更新图块
schedule(schedule_selector(TMXReadWriteTest::repaintWithGID), 2.0f);

void TMXReadWriteTest::repaintWithGID(float dt)
{
    //获取TileMap地图对象
    CCTMXTiledMap* map = (CCTMXTiledMap*)getChildByTag(kTagTileMap);
	//获取第0层
    CCTMXLayer *layer = (CCTMXLayer*)map->getChildByTag(0);
    CCSize s = layer->getLayerSize();
    //遍历一行
    for( int x=0; x<s.width;x++) 
    {
	//倒数第二行
        int y = (int)s.height-1;
	//获取指定位置当前的图块ID
        unsigned int tmpgid = layer->tileGIDAt( ccp((float)x, (float)y) );
	//更新指定位置的图块ID
        layer->setTileGID(tmpgid+1, ccp((float)x, (float)y));
    }
}

      明白了上面的代码我们就对实现怪物动画有清晰的思路了:创建一个定时器,在定时器中遍历所有怪物,令其图块ID加一,如果动画完成,则将图块ID置回初始的数值。

      下面请大家思考一下如何遍历所有怪物呢?由于遍历方法需要在定时器中调用,就必须尽可能优化其速度。TestCpp中的例子使用了二维数组的遍历。但设想一下,如果地图很大,遍历起来会很耗时。另外,怪物的分布是稀疏的,二维数组中的大部分元素都有可能为空。所以直接在定时器中遍历地图上的每个格子是不明智的。

      那么我们可以在地图初始化完毕后,先做一个预处理:将每个怪物的方位,初始时的图块ID存放到一个数组中。然后在定时器中遍历这个数组就可以了。

      首先创建一个Enemy类,用于存放怪物的方位和初始的图块ID,将来还会存放怪物的属性等信息:

#ifndef __ENEMY_H__
#define __ENEMY_H__

#include "cocos2d.h"

using namespace cocos2d;

class Enemy:public CCObject
{
public:
	Enemy(void);
	~Enemy(void);

	//怪物在TileMap上的方位
	CCPoint position;

	//怪物的图块ID
	int startGID;
};

#endif

       然后修改GameMap的初始化方法,遍历TileMap上的所有怪物,生成Enemy对象,添加到一个CCArray中。随后启动一个定时器,每隔0.2s更新一次怪物动画。首先在GameMap.h里面声明一个变量,即怪物层,添加代码“CC_PROPERTY_READONLY(CCTMXLayer*,enemyLayer,EnemyLayer);”,然后在“protected:”下面添加需要用到的数组和方法,代码如下:

//Enemy数组
CCArray* enemyArray;

//更新怪物图块
void updateEnemyAnimation(float dt);

接下来在GameMap.cpp文件里面实现怪物层的getter方法:

//返回怪物层
CCTMXLayer *GameMap::getEnemyLayer()
{
	return enemyLayer;
}

现在我们可以修改初始化方法了,修改后的代码如下:

//TiledMap额外的初始化方法
void GameMap::extraInit()
{
	//开启各个图层的纹理抗锯齿
	enableAnitiAliasForEachLayer();
	//初始化地板层和墙壁层对象
	floorLayer=this->layerNamed("floor");
	wallLayer=this->layerNamed("wall");
	
	//获取怪物层
	enemyLayer=this->layerNamed("enemy");
	CCSize s=enemyLayer->getLayerSize();
	enemyArray=CCArray::create();
	//遍历enemy层,将存在的怪物保存到数组中
	for(int x=0;x<s.width;x++){
		for(int y=0;y<s.height;y++){
			int gid=enemyLayer->tileGIDAt(ccp(x,y));
			if(gid!=0){
			Enemy* enemy=new Enemy();
			//保存怪物坐标
			enemy->position=ccp(x,y);
			//保存怪物起始的图块ID
			enemy->startGID=gid;
			//添加怪物对象到数组
			enemyArray->addObject(enemy);
			}
		}
	}
	//别忘了retain()
	enemyArray->retain();
	//用于更新敌人动画
	schedule(schedule_selector(GameMap::updateEnemyAnimation),0.2f);
}

在updateEnemyAnimation中,需要遍历enemyArray,并计算下一帧对应的图块ID,具体代码如下:

//更新怪物的图块
void GameMap::updateEnemyAnimation(float dt)
{
	//遍历保存所有怪物对象的数组
	CCObject* pObject;
	Enemy* enemy;
	CCARRAY_FOREACH(enemyArray,pObject){
	  enemy=(Enemy*)pObject;
	  if(enemy!=NULL){
	      //获取怪物当前的图块ID
		  int gid=enemyLayer->tileGIDAt(enemy->position);
		  gid++;
		  //如果结束,设置为起始图块ID
		  if(gid-enemy->startGID>3){
			  gid=enemy->startGID;
		  }
		  //给怪物设置新的图块
		  enemyLayer->setTileGID(gid,enemy->position);
	  }else{
	     break;
	  }
	}
}

还有,别忘了写Enemy.cpp文件,它要对.h里面声明的构造函数和析构函数进行实现,代码如下:

#include "Enemy.h"

Enemy::Enemy(void)
{
}

Enemy::~Enemy(void)
{
}

现在我们编译运行,发现地图上的怪物们都动起来了。注意,在GameMap的析构方法里要释放enemyArray和定时器:

//析构函数
GameMap::~GameMap(void)
{
	this->unscheduleAllSelectors();
	enemyArray->release();
}







你可能感兴趣的:(cocos2d-x,魔塔)