1、本篇介绍玩家人物出拳攻击机器人,机器人受伤,血量减少,最后死亡。关键就是如何确定玩家精灵出拳是否击中了机器人。本例使用简单的矩形碰撞检测来实现该过程。下面接前一篇文章继续项目:
2、前面说过,玩家精灵和机器人都有5种状态(分别是Normal、Walk、Attack、Hurt和Dead),对应5个动作。这5个动作需要5个函数来加载这些动作所需的图片,并创建相应的动作。然后,在合理的地方调用这些动作。
这些动作的实现大同小异,直接将代码贴出:
(1)首先是父类SpriteActions中定义的5个动作的调用函数:
SpriteActions.h
// 5种动作相应的调用函数
void Normal();
void Walk_WithDirection(cocos2d::CCPoint direction);
void Attack();
void Hurt_WithDamage(float damage);
void Dead();
// 5种动作
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _normalAction, NormalAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _walkAction, WalkAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _attackAction, AttackAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _hurtAction, HurtAction);
CC_SYNTHESIZE_RETAIN(cocos2d::CCAction*, _deadAction, DeadAction);
SpriteActions.cpp(其中的有些变量是后添加的,暂时用不到,如果编译不过可以先注释掉,只保留Normal中类似的3句即可)
void SpriteActions::Normal()
{
// 停止所有动作
this->stopAllActions();
// 执行正常状态的动作
this->runAction(_normalAction);
// 修改当前状态
_spriteState = STATE_NORMAL;
}
void SpriteActions::Walk_WithDirection(cocos2d::CCPoint direction)
{
if (_spriteState == STATE_NORMAL)
{
this->stopAllActions();
this->runAction(_walkAction);
_spriteState = STATE_WALK;
}
if (_spriteState == STATE_WALK)
{
_velocity = ccp(direction.x * _walkSpeed, direction.y * _walkSpeed);
if(_velocity.x >= 0)
{
this->setScaleX(1.0); // 翻转精灵
}
else
{
this->setScaleX(-1.0);
}
}
}
void SpriteActions::Attack()
{
if (_spriteState == STATE_NORMAL || _spriteState == STATE_WALK || _spriteState == STATE_ATTACK)
{
this->stopAllActions();
this->runAction(_attackAction);
_spriteState = STATE_ATTACK;
}
}
void SpriteActions::Hurt_WithDamage(float damage)
{
if (_spriteState != STATE_ATTACK)
{
this->stopAllActions();
this->runAction(_hurtAction);
_spriteState = STATE_HURT;
_HP -= damage;
if (_HP <= 0)
{
this->Dead();
}
}
}
void SpriteActions::Dead()
{
this->stopAllActions();
this->runAction(_deadAction);
_HP = 0;
_spriteState = STATE_DEAD;
}
(2)创造5种动作,因为玩家精灵和机器人的5个动作是不一样的(主要是加载的动作图片不一样),所以要在SpritePlayer和SpriteRobot中分别实现(注:SpritePlayer和SpriteRobot是继承于SpriteActions类的,而SpriteActions类继承于CCSprite):
SpritePlayer.h
#pragma once
#include "cocos2d.h"
#include "SpriteActions.h"
class SpritePlayer : public SpriteActions
{
public:
SpritePlayer(void);
~SpritePlayer(void);
bool init();
CREATE_FUNC(SpritePlayer);
// 加载Normal动作的图片
bool loadNormalAnimation();
// 加载Walk动作图片
bool loadWalkAnimation();
// 加载Attack动作图片
bool loadAttackAnimation();
// 加载Hurt动作图片
bool loadHurtAnimation();
// 加载Dead动作图片
bool loadDeadAnimation();
};
SpritePlayer.cpp
#include "SpritePlayer.h"
USING_NS_CC;
SpritePlayer::SpritePlayer(void)
{
}
SpritePlayer::~SpritePlayer(void)
{
}
bool SpritePlayer::init()
{
bool bRct = false;
do
{
CC_BREAK_IF(! SpriteActions::initWithSpriteFrameName("hero_idle_00.png"));
CC_BREAK_IF(! loadNormalAnimation());
CC_BREAK_IF(! loadWalkAnimation());
CC_BREAK_IF(! loadAttackAnimation());
CC_BREAK_IF(! loadHurtAnimation());
CC_BREAK_IF(! loadDeadAnimation());
bRct = true;
} while (0);
return bRct;
}
bool SpritePlayer::loadNormalAnimation()
{
bool bRct = false;
do
{
CCArray *pNormalFrames = NULL;
pNormalFrames = CCArray::createWithCapacity(6);
CC_BREAK_IF(! pNormalFrames);
for (int i = 0; i < 6; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("hero_idle_%02d.png", i)->
getCString());
pNormalFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
CC_BREAK_IF(! pSpriteFrame);
}
CCAnimation *pNormalAnimation = NULL;
pNormalAnimation = CCAnimation::createWithSpriteFrames(pNormalFrames, 1.0 / 12.0);
CC_BREAK_IF(! pNormalAnimation);
this->setNormalAction(CCRepeatForever::create(CCAnimate::create(pNormalAnimation)));
bRct = true;
} while (0);
return bRct;
}
bool SpritePlayer::loadWalkAnimation()
{
bool bRct = false;
do
{
CCArray *pWalkFrames = NULL;
pWalkFrames = CCArray::createWithCapacity(8);
CC_BREAK_IF(! pWalkFrames);
for (int i = 0; i < 8; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("hero_walk_%02d.png", i)->
getCString());
pWalkFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
CC_BREAK_IF(! pSpriteFrame);
}
CCAnimation *pWalkAnimation = NULL;
pWalkAnimation = CCAnimation::createWithSpriteFrames(pWalkFrames, 1.0 / 12.0);
CC_BREAK_IF(! pWalkAnimation);
this->setWalkAction(CCRepeatForever::create(CCAnimate::create(pWalkAnimation)));
bRct = true;
} while (0);
return bRct;
}
bool SpritePlayer::loadAttackAnimation()
{
bool bRct = false;
do
{
CCArray *pAttackFrames = NULL;
pAttackFrames = CCArray::createWithCapacity(3);
CC_BREAK_IF(! pAttackFrames);
for (int i = 0; i < 3; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("hero_attack_00_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pAttackFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pAttackAnimation = NULL;
pAttackAnimation = CCAnimation::createWithSpriteFrames(pAttackFrames, 1.0 / 24.0);
CC_BREAK_IF(! pAttackAnimation);
this->setAttackAction(CCSequence::create(CCAnimate::create(pAttackAnimation), CCCallFunc::create(this, callfunc_selector(SpritePlayer::Normal)), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
bool SpritePlayer::loadHurtAnimation()
{
bool bRct = false;
do
{
CCArray *pHurtFrames = NULL;
pHurtFrames = CCArray::createWithCapacity(3);
CC_BREAK_IF(! pHurtFrames);
for (int i = 0; i < 3; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("hero_hurt_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pHurtFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pHurtAnimation = NULL;
pHurtAnimation = CCAnimation::createWithSpriteFrames(pHurtFrames, 1.0 / 12.0);
CC_BREAK_IF(! pHurtAnimation);
this->setHurtAction(CCSequence::create(CCAnimate::create(pHurtAnimation), CCCallFunc::create(this, callfunc_selector(SpritePlayer::Normal)), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
bool SpritePlayer::loadDeadAnimation()
{
bool bRct = false;
do
{
CCArray *pDeadFrames = NULL;
pDeadFrames = CCArray::createWithCapacity(5);
CC_BREAK_IF(! pDeadFrames);
for (int i = 0; i < 5; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("hero_knockout_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pDeadFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pDeadAnimation = NULL;
pDeadAnimation = CCAnimation::createWithSpriteFrames(pDeadFrames, 1.0 / 12.0);
CC_BREAK_IF(! pDeadAnimation);
this->setDeadAction(CCSequence::create(CCAnimate::create(pDeadAnimation), CCBlink::create(2.0, 10.0), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
SpriteRobot.h
#pragma once
#include "cocos2d.h"
#include "SpriteActions.h"
class SpriteRobot : public SpriteActions
{
public:
SpriteRobot(void);
~SpriteRobot(void);
bool init();
CREATE_FUNC(SpriteRobot);
// 加载Normal动作的图片
bool loadNormalAnimation();
// 加载Walk动作图片
bool loadWalkAnimation();
// 加载Attack动作图片
bool loadAttackAnimation();
// 加载Hurt动作图片
bool loadHurtAnimation();
// 加载Dead动作图片
bool loadDeadAnimation();
};
SpriteRobot.cpp
#include "SpriteRobot.h"
USING_NS_CC;
SpriteRobot::SpriteRobot(void)
{
}
SpriteRobot::~SpriteRobot(void)
{
}
bool SpriteRobot::init()
{
bool bRct = false;
do
{
CC_BREAK_IF(! SpriteActions::initWithSpriteFrameName("robot_idle_00.png"));
CC_BREAK_IF(! loadNormalAnimation());
CC_BREAK_IF(! loadWalkAnimation());
CC_BREAK_IF(! loadAttackAnimation());
CC_BREAK_IF(! loadHurtAnimation());
CC_BREAK_IF(! loadDeadAnimation());
bRct = true;
} while (0);
return bRct;
}
bool SpriteRobot::loadNormalAnimation()
{
bool bRct = false;
do
{
CCArray *pNormalFrames = NULL;
pNormalFrames = CCArray::createWithCapacity(5);
CC_BREAK_IF(! pNormalFrames);
for (int i = 0; i < 5; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("robot_idle_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pNormalFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pNormalAnimation = NULL;
pNormalAnimation = CCAnimation::createWithSpriteFrames(pNormalFrames, float(1.0 / 12.0));
CC_BREAK_IF(! pNormalAnimation);
this->setNormalAction(CCRepeatForever::create(CCAnimate::create(pNormalAnimation)));
bRct = true;
} while (0);
return bRct;
}
bool SpriteRobot::loadWalkAnimation()
{
bool bRct = false;
do
{
CCArray *pWalkFrames = NULL;
pWalkFrames = CCArray::createWithCapacity(6);
CC_BREAK_IF(! pWalkFrames);
for (int i = 0; i < 6; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("robot_walk_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pWalkFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pWalkAnimation = NULL;
pWalkAnimation = CCAnimation::createWithSpriteFrames(pWalkFrames, float(1.0 / 12.0));
CC_BREAK_IF(! pWalkAnimation);
this->setWalkAction(CCRepeatForever::create(CCAnimate::create(pWalkAnimation)));
bRct = true;
} while (0);
return bRct;
}
bool SpriteRobot::loadAttackAnimation()
{
bool bRct = false;
do
{
CCArray *pAttackFrames = NULL;
pAttackFrames = CCArray::createWithCapacity(5);
CC_BREAK_IF(! pAttackFrames);
for (int i = 0; i < 5; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("robot_attack_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pAttackFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pAttackAnimation = NULL;
pAttackAnimation = CCAnimation::createWithSpriteFrames(pAttackFrames, float(1.0 / 24.0));
CC_BREAK_IF(! pAttackAnimation);
//this->setAttackAction(CCRepeatForever::create(CCAnimate::create(pAttackAnimation))); // 注意:不是重复执行了
this->setAttackAction(CCSequence::create(CCAnimate::create(pAttackAnimation), CCCallFunc::create(this, callfunc_selector(SpriteRobot::Normal)), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
bool SpriteRobot::loadHurtAnimation()
{
bool bRct = false;
do
{
CCArray *pHurtFrames = NULL;
pHurtFrames = CCArray::createWithCapacity(3);
CC_BREAK_IF(! pHurtFrames);
for (int i = 0; i < 3; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("robot_hurt_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pHurtFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pHurtAnimation = NULL;
pHurtAnimation = CCAnimation::createWithSpriteFrames(pHurtFrames, float(1.0 / 12.0));
CC_BREAK_IF(! pHurtAnimation);
this->setHurtAction(CCSequence::create(CCAnimate::create(pHurtAnimation), CCCallFunc::create(this, callfunc_selector(SpriteRobot::Normal)), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
bool SpriteRobot::loadDeadAnimation()
{
bool bRct = false;
do
{
CCArray *pDeadFrames = NULL;
pDeadFrames = CCArray::createWithCapacity(5);
CC_BREAK_IF(! pDeadFrames);
for (int i = 0; i < 5; i++)
{
CCSpriteFrame *pSpriteFrame = NULL;
pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->
spriteFrameByName(CCString::createWithFormat("robot_knockout_%02d.png", i)->
getCString());
CC_BREAK_IF(! pSpriteFrame);
pDeadFrames->addObject(pSpriteFrame); // 别忘了添加到数组中
}
CCAnimation *pDeadAnimation = NULL;
pDeadAnimation = CCAnimation::createWithSpriteFrames(pDeadFrames, float(1.0 / 12.0));
CC_BREAK_IF(! pDeadAnimation);
this->setDeadAction(CCSequence::create(CCAnimate::create(pDeadAnimation), CCBlink::create(2.0, 10.0), NULL)); // 出拳动作执行一次后,变为Normal动作
bRct = true;
} while (0);
return bRct;
}
3、OK,现在动作都有了,问题剩下何时调用这些动作。分以下几点说明:
(1)玩家精灵的默认状态为Normal(循环执行左右晃动动作);
(2)当点击虚拟方向键不松开时,玩家精灵执行Walk动作(双腿交替);
(3)当点击窗口其它区域(非方向键)时,玩家精灵执行Attack动作(出拳);
(4)以上3个动作都是玩家精灵的,我们已经在前面的文章中实现了,下面我们介绍机器人的动作。
(5)因为机器人的动作只实现了Normal动作(上下抖动以及排气),所以现在机器人不会动,不会攻击。
(6)本文要实现的效果是:玩家精灵出拳攻击机器人,机器人执行相应的Hurt(受伤)动作,当机器人的HP小于零时,机器人执行Dead(死亡)动作。
4、为了确定玩家精灵出拳Attack后是否击中了机器人,可以用一个简单的碰撞检测来实现:
(1)我们可以定义这样一个结构,包含两个CCRect型变量:bodyRect(身体矩形)和 punchesRect(出拳矩形);
可以定义在GameDefines.h中:
typedef struct _SurroundRect
{
cocos2d::CCRect bodyRect; // 身体的矩形区域
cocos2d::CCRect punchesRect; // 出拳的矩形区域
}SurroundRect;
(2)只要将玩家精灵的punchesRect和机器人的bodyRect进行碰撞检测,就能确定玩家精灵是否击中了机器人。
(3)现在只需要获取玩家精灵punchesRect的位置和机器人bodyRect的位置就行了。
(4)显然,玩家精灵是会移动的,punchesRect的位置是不断改变的,而机器人目前是不会移动的。玩家要控制玩家精灵走到机器人边上,然后出拳,这样才符合常理。
(5)因为我们移动玩家精灵是通过在计时器中调用setPosition函数实现的,所以可以重载setPosition这个函数,在
原有功能上执行一个更新bodyRect和punchesRect的函数。
(6)因为SpritePlayer和SpriteRobot都需要更新bodyRect和punchesRect的位置,所以在父类SpriteActions中重载setPosition函数。
(7)具体如何确定bodyRect和punchesRect,实际上是基于精灵当前的位置坐标(getPosition)换算出来的,矩形的width和hight不会变,只是矩形左下角(原点)的坐标不断改变。
5、下面是具体实现:
(1)SpriteActions.h(别忘了包含GameDefines.h)
// 精灵的大小
CC_SYNTHESIZE(cocos2d::CCSize, _spriteSize, SpriteSize);
// 出拳的矩形范围
CC_SYNTHESIZE(cocos2d::CCRect, _punshesRect, PunchesRect);
// 碰撞检测矩形
CC_SYNTHESIZE(SurroundRect, _collisionDetectionRect, CollosionDetectionRect);
// 更新
void updateSurroundedRect();
// 重载
void setPosition(const cocos2d::CCPoint& pos);
(2)SpriteActions.cpp
void SpriteActions::updateSurroundedRect()
{
const CCPoint ptNow = this->getPosition();
_collisionDetectionRect.bodyRect.origin = ccp(ptNow.x - this->getSpriteSize().width / 2, ptNow.y - this->getSpriteSize().height / 2); // 原点
_collisionDetectionRect.bodyRect.size.width = this->getSpriteSize().width;
_collisionDetectionRect.bodyRect.size.height = this->getSpriteSize().height;
_collisionDetectionRect.punchesRect = this->getPunchesRect();
if (this->getScaleX() == 1.0)
{
_collisionDetectionRect.punchesRect.origin = ccp(ptNow.x + this->getSpriteSize().width / 2, ptNow.y);
}
else
{
_collisionDetectionRect.punchesRect.origin = ccp(ptNow.x - this->getSpriteSize().width / 2, ptNow.y);
}
}
void SpriteActions::setPosition(const cocos2d::CCPoint& pos)
{
CCSprite::setPosition(pos);
this->updateSurroundedRect();
}
(3)另外,碰撞矩形的长和宽是测量值,血量,速度等属性值需要在init函数中设置,同样定义在SpriteActions类中:
SpriteActions.h
// 属性
CC_SYNTHESIZE(float, _walkSpeed, WalkSpeed); // 速度
CC_SYNTHESIZE(float, _HP, HP);// 伤害点
CC_SYNTHESIZE(float, _onceDamage, OnceDamage); // 攻击力(一次攻击对方损失的HP)
(4)在SpritePlayer和SpriteRobot的init函数中分别初始化属性和碰撞矩形的大小:
SpritePlayer.cpp
bool SpritePlayer::init()
{
bool bRct = false;
do
{
CC_BREAK_IF(! SpriteActions::initWithSpriteFrameName("hero_idle_00.png"));
CC_BREAK_IF(! loadNormalAnimation());
CC_BREAK_IF(! loadWalkAnimation());
CC_BREAK_IF(! loadAttackAnimation());
CC_BREAK_IF(! loadHurtAnimation());
CC_BREAK_IF(! loadDeadAnimation());
// 设置属性
this->setWalkSpeed(140.0); // 速度
this->setSpriteSize(CCSizeMake(2 * 29, 2 * 39)); // 精灵大小
this->setHP(100); // 血量
this->setOnceDamage(20); // 伤害
this->setPunchesRect(CCRectMake(0, 0, 30, 20)); // 出拳矩形的大小
bRct = true;
} while (0);
return bRct;
}
SpriteRobot.cpp
bool SpriteRobot::init()
{
bool bRct = false;
do
{
CC_BREAK_IF(! SpriteActions::initWithSpriteFrameName("robot_idle_00.png"));
CC_BREAK_IF(! loadNormalAnimation());
CC_BREAK_IF(! loadWalkAnimation());
CC_BREAK_IF(! loadAttackAnimation());
CC_BREAK_IF(! loadHurtAnimation());
CC_BREAK_IF(! loadDeadAnimation());
// 设置属性
this->setSpriteSize(CCSizeMake(2 * 29, 2 * 39));
this->setHP(100);
bRct = true;
} while (0);
return bRct;
}
(5)在GameBasicLayer中进行碰撞检测,将检测的过程封装成一个函数collisionDetection:
void GameBasicLayer::collisionDetection()
{
if (_spritePlayer->getSpriteState() == STATE_ATTACK)
{
CCObject *pObj = NULL;
CCARRAY_FOREACH(_spriteRobot, pObj)
{
SpriteRobot *pRobot = (SpriteRobot*)pObj;
if (pRobot->getSpriteState() != STATE_DEAD)
{
if (fabsf(_spritePlayer->getPosition().y - pRobot->getPosition().y) < 10)
{
if (_spritePlayer->getCollosionDetectionRect().punchesRect.intersectsRect(pRobot->getCollosionDetectionRect().bodyRect))
{
pRobot->Hurt_WithDamage(_spritePlayer->getOnceDamage());
}
}
}
}
}
}
函数说明:最里面的if语句进行碰撞检测,由内至外倒数第二个if语句用于实现简单的三维效果,即当玩家精灵和机器人的前后距离超过一定界限(这里为10)时(不再一个平面内),出拳后无法击中目标。
(6)最后,在触摸响应事件中调用该函数:
void GameBasicLayer::ccTouchesBegan(cocos2d::CCSet *pTouches, cocos2d::CCEvent *pEvent)
{
_spritePlayer->Attack();
collisionDetection();
}
6、运行效果如下:
7、待续