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

来源:互联网 发布:电路cam程序软件 编辑:程序博客网 时间:2024/05/16 00:26

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

      首先我们准备好了一个战斗动画效果:一个舞动的刀光,即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;//获取当前坐标位置的图块IDint tileGid=sGlobal->gameMap->getWallLayer()->tileGIDAt(targetTileCoord);//如果图块ID不为0,表示有墙if(tileGid){    return kWall;}//获取怪物层对应坐标的图块IDtileGid=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,//地图的zOrderkZNPC,//NPCkZTeleport,//传送点kZHero,//勇士精灵的zOrderkZTip,//提示信息的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中加载勇士战斗的动画。编译运行程序,现在我们的勇士终于可以与怪物战斗了。

                










原创粉丝点击