我们来实现勇士与怪物战斗的效果。我们希望勇士在遭遇敌人时,可以显示战斗动画,并且怪物身上显示被打击的效果,勇士头上冒出一行文字提示损失的生命值;战斗结束后,怪物从地图上消失。
首先我们准备好了一个战斗动画效果:一个舞动的刀光,即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);
}
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中加载勇士战斗的动画。编译运行程序,现在我们的勇士终于可以与怪物战斗了。