改写《魔塔》后篇03:与怪物进行战斗

      我们来实现勇士与怪物战斗的效果。我们希望勇士在遭遇敌人时,可以显示战斗动画,并且怪物身上显示被打击的效果,勇士头上冒出一行文字提示损失的生命值;战斗结束后,怪物从地图上消失。

      首先我们准备好了一个战斗动画效果:一个舞动的刀光,即sword.png文件。我们把文件复制到我们项目的Resource目录下,打开图片,它实际起作用的是4,6,8,10,13,15,17,19,20,22帧,并且第17帧和第19帧在y方向上有-8的偏移量。在GameConstants.h中新增一个动画模板键值aFight,然后在动画管理器中新增一个方法createFightAnimation,用于创建战斗动画模板。在.h文件中添加“CCAnimation *createFightAnimation();”然后在.cpp文件中实现此方法:

//创建战斗动画模板
CCAnimation* AnimationManager::createFightAnimation()
{
	//定义每帧的序号
	int fightAnim[]=
	{
	  4,6,8,10,13,15,17,19,20,22
	};
	CCArray* animFrames=new CCArray();
	CCTexture2D* texture=CCTextureCache::sharedTextureCache()->addImage("sword.png");
	CCSpriteFrame* frame;
	int x,y;
	for(int i=0;i<10;i++)
	{
	    //计算每帧在整个纹理中的偏移量
		x=fightAnim[i]%5-1;
		y=fightAnim[i]/5;
		frame=CCSpriteFrame::createWithTexture(texture,CCRectMake(192*x,192*y,192,192));
		//第17帧和第19帧在y方向上有-8的偏移量
		if(fightAnim[i]==17||fightAnim[i]==19)
		{
			frame->setOffsetInPixels(ccp(0,-8));
		}
		animFrames->addObject(frame);
	}

	animation = CCAnimation::createWithSpriteFrames(animFrames, 0.1f);
	//若不retain()则会出现错误
    animation->retain();

	return animation;
}


先把准备好的刀光动画放在一边,下面我们来实现勇士遇到怪物时的检测碰撞。方法与检测墙壁碰撞基本一致:将勇士移动的目标位置由cocos2d-x坐标系转换为TileMap坐标系,通过CCTMXLayer的tileGIDAt方法获得图块ID,判断碰撞类型。修改Hero的checkCollision方法如下:

//判断碰撞类型
CollisionType Hero::checkCollision(CCPoint heroPosition)
{
	//cocos2d-x坐标转换为Tilemap坐标
	
    targetTileCoord=sGlobal->gameMap->tileCoordForPosition(heroPosition);
	//如果勇士坐标超过地图边界,返回kWall类型,阻止其移动
	if(heroPosition.x<0||targetTileCoord.x>sGlobal->gameMap->getMapSize().width-1
		||targetTileCoord.y<0||targetTileCoord.y>sGlobal->gameMap->getMapSize().height-1)
		return kWall;

	//获取当前坐标位置的图块ID
	int tileGid=sGlobal->gameMap->getWallLayer()->tileGIDAt(targetTileCoord);
	//如果图块ID不为0,表示有墙
	if(tileGid){
	    return kWall;
	}
	//获取怪物层对应坐标的图块ID
	tileGid=sGlobal->gameMap->getEnemyLayer()->tileGIDAt(targetTileCoord);
	//如果图块ID不为0,表示有怪物
	if(tileGid){
	    //开始战斗
		fight();
		return kEnemy;
	}
	//可以通行
	return kNone;
}


其中fight方法封装了勇士和怪物开始战斗后的逻辑,包括显示怪物受打击的效果,播放战斗动画,显示损失的生命值等。

      第一步先来实现怪物受打击效果——每隔0.4s显示一次红色的状态,共重复3次。我们在GameMap中新建一个方法showEnemyHitEffect,根据怪物在TileMap上所在的坐标取得对应位置的CCSprite对象,调用一个间隔为0.2s的定时器,将CCSprite对象的颜色在白色和红色之间切换,反复切换5次后取消定时器。首先在GameMap.h文件里添加“void showEnemyHitEffect(CCPoint tileCoord);”和“void updateEnemyHitEffect(float dt);”,然后.cpp文件中具体的代码如下:

//显示怪物打击
void GameMap::showEnemyHitEffect(CCPoint tileCoord)
{
	//更新次数
	fightCount=0;
	//获取怪物对应的CCSprite对象
	fightingEnemy=enemyLayer->tileAt(tileCoord);
	if(fightingEnemy==NULL)
		return;
	//设置怪物sprite颜色为红色
	fightingEnemy->setColor(ccRED);
	//启动定时器更新打击状态
	this->schedule(schedule_selector(GameMap::updateEnemyHitEffect),0.18f);
}

//更新怪物战斗时的颜色状态
void GameMap::updateEnemyHitEffect(float dt)
{
	//更新次数加一
	fightCount++;
	if(fightCount%2==1){
	   //设置怪物精灵颜色为白色
		fightingEnemy->setColor(ccWHITE);
	}else{
	   //设置怪物精灵颜色为红色
		fightingEnemy->setColor(ccRED);
	}
	//切换5次后取消定时器
	if(fightCount==5)
	{
		unschedule(schedule_selector(GameMap::updateEnemyHitEffect));
	}
}

当然,别忘了在GameMap.h文件里声明用到的变量。“int fightCount;”和“CCSprite *fightingEnemy;”。

       第二步我们希望在战斗的同时,勇士头上会飘出一行文字,即诸如“生命值:-100”类的提示。在GameLayer中实现一个公有方法showTip,创建一个CCLabelTTF对象,让它执行向上移动进入、停顿、淡出的动画序列。在动画结束的回调函数中,将CCLabelTTF对象删除。在GameLayer.h中添加用到的方法声明,“void showTip(const char *tip,CCPoint startPosition);”和“void onShowTipDone(CCNode *pSender);”。然后在GameLayer.cpp最后添加下面代码:

//显示提示信息
void GameLayer::showTip(const char* tip,CCPoint startPosition)
{
	//新建一个文本标签
	CCLabelTTF* tipLabel=CCLabelTTF::create(tip,"Arial",20);
	tipLabel->setPosition(ccpAdd(startPosition,ccp(16,16)));
	this->addChild(tipLabel,kZTip,kZTip);
	//定义动画效果
	CCAction* action=CCSequence::create(
		CCMoveBy::create(0.5f,ccp(0,32)),
		CCDelayTime::create(0.5f),
		CCFadeOut::create(0.2f),
		CCCallFuncN::create(this,callfuncN_selector(GameLayer::onShowTipDone)),
		NULL);
	tipLabel->runAction(action);
}

//提示消息显示完后的回调
void GameLayer::onShowTipDone(CCNode* pSender)
{
	//删掉文本标签
	this->getChildByTag(kZTip)->removeAllChildrenWithCleanup(true);
}

我们还要在GameConstants.h文件中新增一个枚举类型,用来存放各个层的zOrder及tag。代码如下:

enum 
{
	kZMap = 0,//地图的zOrder
	kZNPC,	//NPC
	kZTeleport,	//传送点
	kZHero,//勇士精灵的zOrder
	kZTip,//提示信息的zOrder
};//GameLayer中各部分的显示zOrder及tag

    第三步我们来实现战斗时的动画效果,这时之前准备好的刀光动画就可以派上用场了。在heroInit方法中定义一个空的CCSprite用于显示战斗动画,在fight方法中,让该精灵执行定义好的刀光动画,在战斗结束的回调函数中,删除怪物对应位置的图块。另外,显示怪物打击效果以及损失生命值的方法也在下面列出。注意,方法的开头会判断勇士是否已经在战斗状态,如果是,则不响应新的战斗请求。当然我们需要先在Hero.h中声明方法,即添加“void fight();”和“void onFightDone(CCNode *pSender);”。我们还设置了一个bool类型变量isHeroFighting和一个临时对象fightSprite,并且要在Hero的heroInit方法中将 isHeroFighting和fightSprite初始化。

//开始战斗
void Hero::fight()
{
	//已经在战斗中,避免重复战斗
	if(isHeroFighting)
		return;
	isHeroFighting=true;
	//显示怪物受到打击的效果
	sGlobal->gameMap->showEnemyHitEffect(targetTileCoord);
	//显示损失的生命值,先用数据替代一下
	char temp[30]={0};
	sprintf(temp,"lost hp: -%d",100);
	sGlobal->gameLayer->showTip(temp,getPosition());
	//将用于显示战斗动画的精灵设置为可见
	fightSprite->setVisible(true);
	//计算显示战斗动画的位置为勇士和怪物的中间点
	CCPoint pos=ccp((targetPosition.x-getPosition().x)/2+16,(targetPosition.y-getPosition().y)/2+16);
	fightSprite->setPosition(pos);
	//创建战斗动画
	CCAction *action=CCSequence::create(
		sAnimationMgr->createAnimate(aFight),
		CCCallFuncN::create(this,callfuncN_selector(Hero::onFightDone)),
		NULL);
	fightSprite->runAction(action);
}

//战斗结束的回调
void Hero::onFightDone(CCNode* pSender)
{
	//删除怪物对应的图块,表示已经被消灭
	sGlobal->gameMap->getEnemyLayer()->removeTileAt(targetTileCoord);
	isHeroFighting=false;
	fightSprite->setVisible(false);
}

      注意,在战斗结束后,除了从TileMap地图上删除图块以外,还需要更新GameMap中保存怪物对象的数组enemyArray。最好不要直接删除,因为有一个定时器在不停地更新怪物动画,正确地办法是在updateEnemyAnimation中判断是否有怪物的图块ID为0,有则说明已经被删除了,记录下次元素,在循环外再进行删除操作。修改updateEnemyAnimation方法如下:

//更新怪物的图块
void GameMap::updateEnemyAnimation(float dt)
{
	//遍历保存所有怪物对象的数组
	CCObject* pObject;
	Enemy* enemy;
	Enemy* needRemove=NULL;
	CCARRAY_FOREACH(enemyArray,pObject){
	  enemy=(Enemy*)pObject;
	  if(enemy!=NULL){
	      //获取怪物当前的图块ID
		  int gid=enemyLayer->tileGIDAt(enemy->position);
		  //如果怪物被删除了,需要把它在enemyArray中也删除
		  if(gid==0)
		  {
		     needRemove=enemy;
			 continue;
		  }
		  gid++;
		  //如果结束,设置为起始图块ID
		  if(gid-enemy->startGID>3){
			  gid=enemy->startGID;
		  }
		  //给怪物设置新的图块
		  enemyLayer->setTileGID(gid,enemy->position);
	  }
	}
	//删除消除的怪物
	if(NULL!=needRemove)
	{
		enemyArray->removeObject(needRemove,true);
	}
}

此外,记得在AnimationManager中加载勇士战斗的动画。编译运行程序,现在我们的勇士终于可以与怪物战斗了。

                改写《魔塔》后篇03:与怪物进行战斗_第1张图片










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