制作横版游戏KillBear第7课:攻击判定 伤害飘血

2015年03月22日 09:28 0 点赞 0 评论 更新于 2025-11-21 18:03

在游戏已经具备地图、英雄以及具有一定AI判定的敌人之后,战斗环节的实现就成为了必然需求。本节课将在之前的开发基础上,实现有效的攻击判定机制,并在英雄攻击敌人时显示伤害值。

开发环境

  • Win64 : vs2010
  • Cocos2d-x v3.4Final
  • TexturePackerGUI
  • MapEdit

代码实现

角色类相关

英雄类(Hero)

我们需要在英雄类的 .cpp.h 文件中添加返回判定相关的函数声明与实现。

.h 文件
// 普通攻击回调
void Hero::attackCallBackAction(Node* pSender);
// 数字淡出回调
void Hero::FontsCallBackAction(Node* pSender);
// 产生伤害数字动画
void Hero::damageDisplay(int number, Vec2 point);
.cpp 文件
void Hero::attackCallBackAction(Node* pSender) {
// 普通攻击回调
__Array* pEnemies = global->enemies;
Ref *enemyObj = NULL;

// 遍历所有怪物
CCARRAY_FOREACH(pEnemies, enemyObj) {
Enemy *pEnemy = (Enemy*)enemyObj;
// 判断英雄与怪物在Y轴上的距离是否小于20
if (fabsf(this->getPosition().y - pEnemy->getPosition().y) < 20) {
// 英雄攻击区域
Rect attackReck = m_hitBox.actual;
// 怪物受伤区域
Rect hurtReck = pEnemy->getBodyBox().actual;

// 判断攻击区域与受伤区域是否相交
if (attackReck.intersectsRect(hurtReck)) {
// 禁止怪物移动
pEnemy->setAllowMove(false);
// 怪物执行受伤动作
pEnemy->runHurtAction();

// 计算伤害值,伤害值在英雄攻击力的0.7倍到1.3倍之间随机
int damage = random(this->getDamageStrenth() * 0.7, this->getDamageStrenth() * 1.3);
// 减少怪物当前生命值
pEnemy->setCurtLifeValue(pEnemy->getCurtLifeValue() - damage);

// 判断怪物是否死亡
if (pEnemy->getCurtLifeValue() <= 0) {
// 怪物执行死亡动作
pEnemy->runDeadAction();
// 将怪物的碰撞盒设置为零
pEnemy->setBodyBox(createBoundingBox(Vec2::ZERO, Size::ZERO));
}
}
}
}
// this->runIdleAction();
}

同时,不要忘记在上方攻击序列创建的时候加入判定:

// 普通攻击A,分出招和收招,期间夹杂攻击判定。可以通过调节fps控制出招速度等
Animation *attackAnima1 = this->createAttackAnimation("boy_attack_00_d.png", 0, 4, 10);
Animation *attackAnima2 = this->createAttackAnimation("boy_attack_00_d.png", 4, 8, 15);

this->setNomalAttackA(Sequence::create(
Animate::create(attackAnima1),
CallFuncN::create(CC_CALLBACK_1(Hero::attackCallBackAction, this)),
Animate::create(attackAnima2),
Role::createIdleCallbackFunc(),
NULL
));

敌人(Enemy)

同样需要在敌人类中加入攻击判定。这里省略 .h 文件的展示,重点关注攻击判定函数的实现:

void Enemy::attackCallBackAction(Node* pSender) {
Hero* t_hero = global->hero;
// 怪物攻击区域
Rect attackReck = m_hitBox.actual;
// 英雄受伤区域
Rect hurtReck = t_hero->getBodyBox().actual;

// 判断攻击区域与受伤区域是否相交
if (attackReck.intersectsRect(hurtReck)) {
// 禁止英雄移动
t_hero->setAllowMove(false);
// 获取怪物的攻击力
int damage = this->getDamageStrenth();
// 英雄执行受伤动作
t_hero->runHurtAction();
// 减少英雄当前生命值
t_hero->setCurtLifeValue(t_hero->getCurtLifeValue() - damage);
}

// 判断英雄是否死亡
if (t_hero->getCurtLifeValue() <= 0) {
// 英雄执行死亡动作
t_hero->runDeadAction();
// 将英雄的碰撞盒设置为零
t_hero->setBodyBox(createBoundingBox(Vec2::ZERO, Size::ZERO));
}
}

伤害显示

我们在英雄攻击回调中添加一个伤害显示函数,用于显示伤害值,并在一段时间后自动消失:

void Hero::FontsCallBackAction(Node* pSender) {
// 数字淡出回调
global->gameLayer->removeChild(pSender);
}

void Hero::damageDisplay(int number, Vec2 point) {
// 产生数字动画
auto atLabel = Label::create();
// 设置伤害值显示文本
atLabel->setString(__String::createWithFormat("-%d", number)->getCString());
// 设置字体大小
atLabel->setSystemFontSize(18);
// 设置字体颜色
atLabel->setColor(Color3B(0, 0, 128));
// 设置显示位置
atLabel->setPosition(point);
// 将标签添加到游戏层
global->gameLayer->addChild(atLabel, 200);

// 创建回调函数
FiniteTimeAction * callFuncN = CallFuncN::create(atLabel, callfuncN_selector(Hero::FontsCallBackAction));
// 创建动作序列
FiniteTimeAction *sequence = Sequence::create(
ScaleTo::create(0.2f, 1.3f),
MoveBy::create(0.2f, Vec2(0, 20)),
FadeOut::create(0.5f),
callFuncN,
NULL
);
// 执行动作序列
atLabel->runAction(sequence);
}

同时,不要忘记在 attackCallBackAction 中加入伤害显示调用:

damageDisplay(damage, pEnemy->getBodyBox().actual.origin);

结语

攻击判定框在前面创建的时候已经设定好了。对于此类横版游戏而言,最复杂且困难的部分就是各种判定。本Demo仅为演示效果,全部的攻击判定仅使用了一个判定框。

最佳实践是为每一种动作单独创建受击和攻击判定,这是一项相当繁琐的工作。对于攻击动作,在动作进行过程中添加判定比在动作开始或结束后添加更加真实。此外,每个动作还可以设置多个攻击判定,以实现多段攻击效果,如击飞(沿攻击方向相反移动一定距离)、上挑(Y轴移动一定距离)。

下一篇文章将介绍如何添加一个有冷却时间的按钮,以实现英雄的技能攻击。

作者信息

boke

boke

共发布了 3994 篇文章