5.创建机器人。我们已经创建了精灵的基本模型:ActionSprite。我们可以重用它来创建游戏中电脑控制的角色。新建Robot类,派生自ActionSprite类,增加如下方法:
1
2 |
CREATE_FUNC(Robot);
bool init(); |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
bool Robot::init()
{ bool bRet = false; do { CC_BREAK_IF(!ActionSprite::initWithSpriteFrameName( "robot_idle_00.png")); int i; //idle animation CCArray *idleFrames = CCArray::createWithCapacity( 5); for (i = 0; i < 5; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat( "robot_idle_%02d.png", i)->getCString()); idleFrames->addObject(frame); } CCAnimation *idleAnimation = CCAnimation::createWithSpriteFrames(idleFrames, float( 1. 0 / 12. 0)); this->setIdleAction(CCRepeatForever::create(CCAnimate::create(idleAnimation))); //attack animation CCArray *attackFrames = CCArray::createWithCapacity( 5); for (i = 0; i < 5; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat( "robot_attack_%02d.png", i)->getCString()); attackFrames->addObject(frame); } CCAnimation *attackAnimation = CCAnimation::createWithSpriteFrames(attackFrames, float( 1. 0 / 24. 0)); this->setAttackAction(CCSequence::create(CCAnimate::create(attackAnimation), CCCallFunc::create( this, callfunc_selector(Robot::idle)), NULL)); //walk animation CCArray *walkFrames = CCArray::createWithCapacity( 6); for (i = 0; i < 6; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( CCString::createWithFormat( "robot_walk_%02d.png", i)->getCString()); walkFrames->addObject(frame); } CCAnimation *walkAnimation = CCAnimation::createWithSpriteFrames(walkFrames, float( 1. 0 / 12. 0)); this->setWalkAction(CCRepeatForever::create(CCAnimate::create(walkAnimation))); this->setWalkSpeed( 80. 0); this->setCenterToBottom( 39. 0); this->setCenterToSides( 29. 0); this->setHitPoints( 100. 0); this->setDamage( 10. 0); bRet = true; } while ( 0); return bRet; } |
跟英雄一样,以上代码创建一个带有3个动作的机器人:空闲、出拳、行走。它也有两个测量值:centerToBottom和centerToSides。注意到机器人的属性比英雄低一点,这是合乎逻辑的,不然英雄永远打不赢机器人。让我们开始添加一些机器人到游戏中去。打开GameLayer.h文件,添加如下代码:
1
|
CC_SYNTHESIZE_RETAIN(cocos2d::CCArray*, _robots, Robots);
|
1
|
#include
"Robot.h"
|
1
|
_robots =
NULL;
|
1
|
this->initRobots();
|
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void GameLayer::initRobots()
{ int robotCount = 50; this->setRobots(CCArray::createWithCapacity(robotCount)); for ( int i = 0; i < robotCount; i++) { Robot *robot = Robot::create(); _actors->addChild(robot); _robots->addObject(robot); int minX = SCREEN.width + robot->getCenterToSides(); int maxX = _tileMap->getMapSize().width * _tileMap->getTileSize().width - robot->getCenterToSides(); int minY = robot->getCenterToBottom(); int maxY = 3 * _tileMap->getTileSize().height + robot->getCenterToBottom(); robot->setScaleX(- 1); robot->setPosition(ccp(random_range(minX, maxX), random_range(minY, maxY))); robot->setDesiredPosition(robot->getPosition()); robot->idle(); } } |
这些代码做了以下事情:
-
创建一个包含50个机器人的数组,并把它们添加到精灵表单中。
-
使用Defines.h里面的随机函数随机放置50个机器人到地图地板上。同时,让最小随机值大于屏幕宽度,以确保不会有任何机器人出现在起点处。
-
让每个机器人都处于空闲状态。
编译运行,让英雄向前走,直到看到地图上的机器人,如下图所示:
试着走到机器人区域中,你会发现机器人的绘制有些不对。如果英雄是在机器人的下面,那么他应该被绘制在机器人的前面,而不是在后面。我们需要明确的告诉游戏,哪个对象先绘制,这就是Z轴来进行控制的。添加英雄和机器人时,并没有明确指定其Z轴,默认下,后面添加的对象会比前面的对象Z轴值高,这就是为什么机器人挡住了英雄。为了解决这个问题,我们需要动态的处理Z轴顺序。每当精灵在屏幕上垂直移动时,它的Z轴值应该有所改变。屏幕上越高的精灵,其Z轴值应越低。打开GameLayer.cpp文件,添加如下方法:
1
2 3 4 5 6 7 8 9 |
void GameLayer::reorderActors()
{ CCObject *pObject = NULL; CCARRAY_FOREACH(_actors->getChildren(), pObject) { ActionSprite *sprite = (ActionSprite*)pObject; _actors->reorderChild(sprite, (_tileMap->getMapSize().height * _tileMap->getTileSize().height) - sprite->getPosition().y); } } |
1
|
this->reorderActors();
|

6.出拳猛击机器人,碰撞检测。为了让英雄能够出拳,并且能够实际上打在了机器人身上,需要实现一种方式的碰撞检测。在这篇文章中,我们使用矩形创建一个非常简单的碰撞检测系统。在这个系统中,我们为每个角色定义两种矩形/盒子:
-
Hit box:代表精灵的身体
-
Attack box:代表精灵的手
假如某个ActionSprite的Attack box碰撞到另一个ActionSprite的Hit box,那么这就是一次碰撞发生。这两个矩形之间的区别,将帮助我们知道谁打了谁。Defines.h文件中的BoundingBox定义,包含两种矩形:实际的,和原始的:
①原始矩形,每个精灵的基本矩形,一旦设置后就不会改变。
②实际矩形,这是位于世界空间中的矩形,当精灵移动时,实际的矩形也跟着变动。
打开ActionSprite.h文件,添加如下代码:
1
2 3 4 |
CC_SYNTHESIZE(BoundingBox, _hitBox, Hitbox);
CC_SYNTHESIZE(BoundingBox, _attackBox, AttackBox); BoundingBox createBoundingBoxWithOrigin(cocos2d::CCPoint origin, cocos2d::CCSize size); |
以上创建了ActionSprite的两个包围盒:Hit box和Attack box。还定义了一个方法,用于根据给定的原点和大小来创建一个BoundingBox结构体。打开ActionSprite.cpp文件,添加如下方法:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
BoundingBox ActionSprite::createBoundingBoxWithOrigin(CCPoint origin, CCSize size)
{ BoundingBox boundingBox; boundingBox.original.origin = origin; boundingBox.original.size = size; boundingBox.actual.origin = ccpAdd( this->getPosition(), ccp(boundingBox.original.origin.x, boundingBox.original.origin.y)); boundingBox.actual.size = size; return boundingBox; } void ActionSprite::transformBoxes() { _hitBox.actual.origin = ccpAdd( this->getPosition(), ccp(_hitBox.original.origin.x, _hitBox.original.origin.y)); _attackBox.actual.origin = ccpAdd( this->getPosition(), ccp(_attackBox.original.origin.x + ( this->getScaleX() == - 1 ? (- _attackBox.original.size.width - _hitBox.original.size.width) : 0), _attackBox.original.origin.y)); } void ActionSprite::setPosition(CCPoint position) { CCSprite::setPosition(position); this->transformBoxes(); } |
第一个方法创建一个新的包围盒,这有助于ActionSprite的子类创建属于它们自己的包围盒。第二个方法,基于精灵的位置、比例因子,和包围盒原本的原点和大小来更新每个包围盒实际测量的原点和大小。之所以要用到比例因子,是因为它决定着精灵的方向。位于精灵右侧的盒子,当比例因子设置为-1时,将会翻转到左侧。打开Hero.cpp文件,在init函数后面添加如下代码:
1
2 3 |
//createBoundingBoxWithOrigin第一个参数的意义为什么是负的中心到边界的距离和到底边的距离 //因为在createBoundingBoxWithOrigin计算的中心点的方法是当前精灵的坐标加上第一个参数 //只有减去中心到边界的距离和到底边的距离才能将精灵框住
this->setHitbox(
this->createBoundingBoxWithOrigin(ccp(-
this->getCenterToSides(), -
this->getCenterToBottom()),
CCSizeMake( this->getCenterToSides() * 2, this->getCenterToBottom() * 2))); this->setAttackBox( this->createBoundingBoxWithOrigin(ccp( this->getCenterToSides(), - 10), CCSizeMake( 20, 20))); |
1
2 3 |
this->setHitbox(
this->createBoundingBoxWithOrigin(ccp(-
this->getCenterToSides(), -
this->getCenterToBottom()),
CCSizeMake( this->getCenterToSides() * 2, this->getCenterToBottom() * 2))); this->setAttackBox( this->createBoundingBoxWithOrigin(ccp( this->getCenterToSides(), - 5), CCSizeMake( 25, 20))); |

无论何时,当一个attack box(红色)跟一个hit box(蓝色)交叉,即一次碰撞发生。在开始编写代码,检测包围盒交叉前,需要确保 ActionSprite 能够对被击中有所反应。我们已经添加了空闲、出拳、行走动作,但还未创建受伤和死亡动作。打开 ActionSprite.cpp 文件,实现如下方法:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void ActionSprite::hurtWithDamage(
float damage)
{ if (_actionState != kActionStateKnockedOut) { this->stopAllActions(); this->runAction(_hurtAction); _actionState = kActionStateHurt; _hitPoints -= damage; if (_hitPoints <= 0) { this->knockout(); } } } void ActionSprite::knockout() { this->stopAllActions(); this->runAction(_knockedOutAction); _hitPoints = 0; _actionState = kActionStateKnockedOut; } |
只要精灵还未死亡,被击中时状态将会切换到受伤状态,执行受伤动画,并且精灵的生命值将会减去相应的伤害值。如果生命值少于0,那么死亡的动作将会触发。为了完成这两个动作,我们还需更改Hero类和Robot类。打开Hero.cpp文件,在init函数walk animation后面添加如下代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//hurt animation CCArray *hurtFrames = CCArray::createWithCapacity( 3); for (i = 0; i < 3; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "hero_hurt_%02d.png", i)->getCString()); hurtFrames->addObject(frame); } CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float( 1. 0 / 12. 0)); this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create( this, callfunc_selector(Hero::idle)), NULL)); //knocked out animation CCArray *knockedOutFrames = CCArray::createWithCapacity( 5); for (i = 0; i < 5; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "hero_knockout_%02d.png", i)->getCString()); knockedOutFrames->addObject(frame); } CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float( 1. 0/ 12. 0)); this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create( 2. 0, 10. 0), NULL)); |
打开Robot.cpp文件,在init函数walk animation后面添加如下代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//hurt animation CCArray *hurtFrames = CCArray::createWithCapacity( 3); for (i = 0; i < 3; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "robot_hurt_%02d.png", i)->getCString()); hurtFrames->addObject(frame); } CCAnimation *hurtAnimation = CCAnimation::createWithSpriteFrames(hurtFrames, float( 1. 0 / 12. 0)); this->setHurtAction(CCSequence::create(CCAnimate::create(hurtAnimation), CCCallFunc::create( this, callfunc_selector(Robot::idle)), NULL)); //knocked out animation CCArray *knockedOutFrames = CCArray::createWithCapacity( 5); for (i = 0; i < 5; i++) { CCSpriteFrame *frame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(CCString::createWithFormat( "robot_knockout_%02d.png", i)->getCString()); knockedOutFrames->addObject(frame); } CCAnimation *knockedOutAnimation = CCAnimation::createWithSpriteFrames(knockedOutFrames, float( 1. 0/ 12. 0)); this->setKnockedOutAction(CCSequence::create(CCAnimate::create(knockedOutAnimation), CCBlink::create( 2. 0, 10. 0), NULL)); |
以上代码应该不陌生了。我们用创建其他动作同样的方式创建了受伤和死亡动作。受伤动作结束时,会切换到空闲状态。死亡动作结束时,精灵进行闪烁。打开GameLayer.cpp文件,添加碰撞处理,在ccTouchesBegan函数后面添加如下代码:
//检测英雄是否处于攻击状态,以及机器人是否处于非死亡状态。
if (_hero->getActionState() == kActionStateAttack)
{
CCObject *pObject = NULL;
//遍历所有敌人数组
CCARRAY_FOREACH(_robots, pObject)
{
Robot *robot = (Robot*)pObject;
//如果敌人没有死亡
if (robot->getActionState() != kActionStateKnockedOut)
{
//检测英雄的位置和机器人的位置垂直相距在10个点以内。这表明它们在同一平面上站立。
if (fabsf(_hero->getPosition().y - robot->getPosition().y) < 10)
{
//检测英雄的attack box是否与机器人的hit box进行交叉。
if (_hero->getAttackBox().actual.intersectsRect(robot->getHitbox().actual))
{
robot->hurtWithDamage(_hero->getDamage());
}
}
}
}
}
以上代码通过三个简单步骤来检测碰撞:
①.检测英雄是否处于攻击状态,以及机器人是否处于非死亡状态。②.检测英雄的位置和机器人的位置垂直相距在10个点以内。这表明它们在同一平面上站立。
③.检测英雄的attack box是否与机器人的hit box进行交叉。
如果这些条件都成立,那么则一次碰撞发生,机器人执行受伤动作。英雄的伤害值作为参数进行传递,这样该方法就会知道需要减去多少生命值。
7.编译运行,出拳攻击机器人吧,效果如下图所示: