6.5 实战:利用定时器优化实现人物行走的效果
上一章曾经实现了通过动画实现人物在场景中行走的效果,不过当时也提到,它还存在着一定的缺陷。但是在学习了与定时器有关的内容后,这一缺陷将得到很好的解决。那就是通过一个定时器以一定的间隔对人物角色进行更新,当用户单击屏幕时,如果状态相同(即原本就在行走且方向不改变),则保持原有的动作行为,而仅改变其前进方向;如果状态不同时,则对动画及方向都进行改变。通过这样的改进,就能够很好地解决Cocos2d-x中人物前进时“跛脚”的问题。
本范例还将继续使用上一章最后实例所使用的素材,但是为了让效果明显一些,本例不再采用触摸单击的方式,而改用之前介绍过的摇杆方式来实现指挥人物行走方向的效果。具体实现方法可以参照源文件中项目ChapterSix05中的内容。
首先找到第4章实现的摇杆HRocker。本例的原理大致就是每次摇杆被操纵时都会记录下当前用户操作的方向和位移等信息,然后每次调用update方法时,根据这些信息来判断应当使用哪一组动画,然后直接通过setPosition方法来改变人物角色的位置。
为了保存摇杆当前的状态,需要在摇杆类的onTouchMoved方法中加入如范例6-3所示的内容。
【范例6-3】使外部能够获取摇杆当前的状态。
01 void HRocker::onTouchMoved(Touch *touch, Event *event) 02 { 03 if (!isCanMove) 04 { 05 return; 06 } 07 //此处省略部分内容,具体见源文件 08 //获取方向,是在本范例中新加入的功能,需要在类中加入新的属性 09 auto basePosition = ((Sprite*)getChildByTag(tag_rockerBG))-> getPosition(); 10 this->dx = sp->getPosition().x - basePosition.x; //摇杆x方向的偏移 11 this->dy = sp->getPosition().y - basePosition.y; //摇杆y方向的偏移 12 float r = sqrt(dx*dx+dy*dy); //获取摇杆与摇杆原点的距离 13 if (dx > 0 && fabs(dy / r) < sin(15 * PI / 180)) //计算摇杆的方向,用整数表示 14 { 15 this->direction = 8; 16 } 17 else if (dx < 0 && fabs(dy / r) < sin(15 * PI / 180)) 18 { 19 this->direction = 6; 20 } 21 else if (dy > 0 && fabs(dx / r) < sin(15 * PI / 180)) 22 { 23 this->direction = 7; 24 } 25 else if (dy < 0 && fabs(dx / r) < sin(15 * PI / 180)) 26 { 27 this->direction = 5; 28 } 29 else if (dx > 0 && dy > 0) 30 { 31 this->direction = 4; 32 } 33 else if (dx > 0 && dy < 0) 34 { 35 this->direction = 1; 36 } 37 else if (dx < 0 && dy < 0) 38 { 39 this->direction = 2; 40 } 41 else if (dx < 0 && dy > 0) 42 { 43 this->direction = 3; 44 } 45 }
然后实现Hero类,如范例6-4所示。
【范例6-4】类Hero的实现。
01 Hero* Hero::createHeroSprite(Point position, int direction, const char* name) 02 { 03 Hero* hero = new Hero(); 04 if (hero && hero->init()) 05 { 06 hero->autorelease(); 07 hero->heroInit(position, direction, name); 08 return hero; 09 } 10 CC_SAFE_DELETE(hero); 11 return NULL; 12 } 13 bool Hero::init() 14 { 15 if (!Layer::init()) 16 { 17 return false; 18 } 19 return true; 20 } 21 void Hero::heroInit(Point position, int direction, const char* name) 22 { 23 this->isRun = false; //标记人物当前状态 24 this->position = position; //人物坐标 25 sprite = Sprite::create(String::createWithFormat("%s11.png", name)->getCString()); 26 sprite->setPosition(position); //设定人物显示位置 27 addChild(sprite); 28 auto* action = createAnimate(1, "stand", 7); 29 action->setTag(100); //用于实现对动画的改动 30 sprite->runAction(action); 31 } 32 Animate* Hero::createAnimate(int direction, const char *action, int num) 33 { //创建动画 34 auto* m_frameCache = SpriteFrameCache::getInstance(); 35 m_frameCache->addSpriteFramesWithFile("hero.plist", "hero. png"); 36 Vector<SpriteFrame*> frameArray; 37 for (int i = 1; i <= num; i++) 38 { 39 auto* frame = m_frameCache->getSpriteFrameByName( 40 String::createWithFormat("%s%d%d.png", action, direction, i)->getCString()); 41 frameArray.pushBack(frame); 42 } 43 Animation* animation = Animation::createWithSpriteFrames (frameArray); 44 animation->setLoops(-1); //表示无限循环播放 45 animation->setDelayPerUnit(0.1f); //设置动画播放每两帧切换的时间间隔 46 //将动画包装成一个动作并返回 47 return Animate::create(animation); 48 } 49 void Hero::setAction(int direction, const char *action, int num) 50 { //首先移除原有动作再加入新的动作,为了便于这些操作,要使用setTag方法对动 作进行标记 51 sprite->stopActionByTag(100); 52 auto* animate = createAnimate(direction, action, num); 53 animate->setTag(100); 54 sprite->runAction(animate); 55 } 56 void Hero::moveTo(float x, float y) 57 { //本范例新加入的方法,用于实现人物随着摇杆的操作而移动 58 float r = sqrt(x*x + y*y); 59 position.x += x / r; 60 position.y += y / r; 61 sprite->setPosition(position); 62 }
通过阅读代码可以发现,相对于上一章中实现人物在屏幕上行走的例子,Hero类精简了不少,只加入了一个新的moveTo方法,用来实现人物位置的移动。核心思想就是通过勾股定理计算出人物小范围移动时横纵坐标要改变的数字,然后以一定的时间间隔来调用,就会给玩家以人物其实是在行走的错觉。
这时就需要面对之前的那个问题了,既然是在update方法中对人物的行为进行操作,如果用户一直将摇杆拖动至某个方向(比如右边),那么每次都更新动作的话将重新导致人物跛脚的状况。而且,之前的例子是只有当用户连续单击屏幕时才会出现跛脚,而本例将会出现一直跛脚的情况。
不过没有问题,因为在程序运行的各个方法中已经标记了摇杆和人物的状态,因此可以通过这些状态来判断是否需要更新人物的动画。比如人物原本是站立状态,此时移动摇杆需要对动作进行更新;如果人物此时正在行走,摇杆方向发生变化,那么也需要对动作进行更新;如果人物行走方向与摇杆方向保持一致,则只需要对其更新位置而无需改变动作。
因此,update方法中的内容使用如范例6-5所示。
【范例6-5】对update方法的实现。
01 void HelloWorld::update(float dt) 02 { 03 if (rocker->isCanMove == true) //首先判断摇杆当前是否被操作 04 { 05 if (hero->isRun == false) //如果当前人物站立 06 { //使人物开始移动 07 hero->isRun = true; 08 hero->direction = rocker->direction; 09 hero->setAction(hero->direction, "run", 8); 10 } 11 else if (hero->isRun == true) //如果当前人物在行走状态 12 { 13 if (hero->direction != rocker->direction) //判断即将前进的方向与当前方向是否相同 14 { //如果不同则改变方向,否则保持原状态前进 15 hero->direction = rocker->direction; 16 hero->setAction(hero->direction, "run", 8); 17 } 18 } 19 hero->moveTo(rocker->dx, rocker->dy); //移动人物的位置 20 } 21 else if (hero->isRun==true&&rocker->isCanMove==false) //摇杆不再操作,人物恢复站立 22 { 23 hero->isRun = false; 24 hero->setAction(hero->direction, "stand", 7); 25 } 26 }
运行程序之后即可拖动摇杆使人物在屏幕上行走,如图6-7、图6-8和图6-9所示。
图6-7 程序开始,人物站立在屏幕中央
图6-8 拖动摇杆人物开始行走
图6-9 放开摇杆人物恢复静止站立
也可以利用类似的方法来修改上一章中人物行走的例子以解决“跛脚”的问题。在实际使用时,如果在行走和站立之外能够再加入一些其他的动作,那实际上就相当于实现了一款类似于《热血传奇》的ARPG游戏的模板了。