怪物在地图上的位置是不变的,但它们都有对应的原地站立时的动画。大家可能想到使用前面创建的动画管理器来创建动画模板,然后播放各个怪物精灵的动画。这样做没错,但是想想就觉得麻烦:如果有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)); } }
下面请大家思考一下如何遍历所有怪物呢?由于遍历方法需要在定时器中调用,就必须尽可能优化其速度。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
//Enemy数组 CCArray* enemyArray; //更新怪物图块 void updateEnemyAnimation(float dt);
//返回怪物层 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; } } }
#include "Enemy.h" Enemy::Enemy(void) { } Enemy::~Enemy(void) { }
//析构函数 GameMap::~GameMap(void) { this->unscheduleAllSelectors(); enemyArray->release(); }