Unity3D RPG Core | 17 实现攻击数值计算

这节完善角色人物的攻击逻辑,并且实现攻击数值的计算。

1. 完善角色人物攻击逻辑

和史莱姆攻击一样,角色人物攻击时也会产生暴击。进入角色人物的动画控制器,如图 1 所示,也是类似的:选择另一个攻击动画作为暴击动画,并额外添加布尔变量 CriticalHit,用于指示是否发生暴击。

图1 角色人物动画控制器

设置完暴击动画之后,我们回到人物控制脚本中。如代码清单 1 所示,在之前的攻击协程的攻击逻辑基础上,先判断此次攻击是否是暴击(第 16 行),接着指示动画控制器是否产生暴击(第 17 行)。同时原本硬编码的冷却时间也可以切换到使用 ScriptableObject 指定了(第 19 行)。

代码清单 1 暴击逻辑
  1. public class PlayerController : MonoBehaviour
  2. {
  3.     IEnumerator CoroutineAttackEnemy(GameObject obj)
  4.     {
  5.         transform.LookAt(obj.transform);
  6.         while (Vector3.Distance(obj.transform.position, transform.position) > m_attackData.AttackRange)
  7.         {
  8.             m_navMeshAgent.destination = obj.transform.position;
  9.             yield return null;
  10.         }
  11.  
  12.         m_navMeshAgent.isStopped = true;
  13.  
  14.         if (m_attackCoolTime <= 0)
  15.         {
  16.             m_attackData.m_isCriticalHit = Random.value < m_attackData.CriticalChance;
  17.             m_animator.SetBool("CriticalHit", m_attackData.m_isCriticalHit);
  18.             m_animator.SetTrigger("Attack");
  19.             m_attackCoolTime = m_attackData.CoolDown;
  20.         }
  21.     }
  22. }
  23.  

2. 实现攻击数值计算

我们在原先的 AttackData 类中实现攻击数值的计算和状态更新。如代码清单 2 所示,TakeDamage() 代表攻击者对被攻击者产生攻击。因为攻击力是有范围的,所以我们调用 GetDamage() 来计算攻击力,同时也考虑到了暴击时的伤害(第 16 行)。

代码清单 2 攻击数值计算
  1. public class AttackData : MonoBehaviour
  2. {
  3.     public void TakeDamage(CharacterData defender)
  4.     {
  5.         int damage = Mathf.Max(GetDamage() - defender.CurrentDefence, 1);
  6.         defender.CurrentHealth = Mathf.Max(defender.CurrentHealth - damage, 0);
  7.  
  8.         //TODO:更新UI
  9.         //TODO:更新经验
  10.     }
  11.  
  12.     int GetDamage()
  13.     {
  14.         float damage = Random.Range(MinDamage, MaxDamage);
  15.         if (m_isCriticalHit)
  16.             damage *= CriticalMultiplier;
  17.         return (int)damage;
  18.     }
  19. }

2.1 角色人物产生攻击

为了让角色人物进行攻击时调用到上述的数值计算和状态更新函数,这里我们不使用碰撞检测的方法,而是使用动画事件的方法。即在攻击动画上的某一帧上绑定需要触发的事件函数,以此调用攻击数值计算和状态更新函数。

代码清单 3 中的 Hit() 是需要绑定的动画事件函数,它获取敌人的数据(第 16 行),并调用 TakeDamage() 函数(第 17 行)。因为动画事件外部不好“变化”传参,所以这边只能设置额外的成员变量记录正在攻击的敌人对象(第 3 行)。敌人对象在攻击事件中获取(第 8 行)。

代码清单 3 人物攻击动画事件
  1. public class PlayerController : MonoBehaviour
  2. {
  3.     GameObject    m_enemyObj = null;
  4.     void DoAttackAction(GameObject obj)
  5.     {
  6.         if (obj != null)
  7.         {
  8.             m_enemyObj = obj;
  9.             StartCoroutine(CoroutineAttackEnemy(obj));
  10.         }
  11.     }
  12.  
  13.     // Animation Event
  14.     void Hit()
  15.     {
  16.         CharacterData enemyCharacterData = m_enemyObj.GetComponent<CharacterData>();
  17.         m_attackData.TakeDamage(enemyCharacterData);
  18.     }
  19. }

下面介绍如何设置绑定动画事件。在菜单中选择 Window - Animation - Animation,会弹出如图 2 所示的窗体。我们滑动选择到我们想要绑定的一帧上,然后点击左侧的 Add Event 图标,接着在 Inspector 窗体中指定上述的 Hit() 函数。

图2 设置动画事件

Add Event 按键在图 2 中的左上侧红框圈出的地方。视频中操作比较快,一闪而过。

2.2 敌人角色产生攻击

编写敌人的动画事件函数,和角色人物的类似:

  1. // Animation Event
  2. void Hit()
  3. {
  4.     CharacterData playerCharacterData = m_playerObj.GetComponent<CharacterData>();
  5.     m_attackData.TakeDamage(playerCharacterData);
  6. }

设置动画事件的方式和角色人物的一样,但是史莱姆这边遇到了一个问题:提示动画是只读的,无法编辑。我们需要在原先的动画资源文件上按 Ctrl+D 复制出一份,如图 3 所示。复制出的动画文件是可以编辑的,然后我们用拖拽的方式更新之前在动画控制器中指定的攻击动画。

图3 重新设置史莱姆攻击动画

新的动画不用重新指定,只要在 Motion 项中覆盖指定就可以,非常方便。

3. 效果测试

最终的效果如图 4 所示,可以看到此时角色人物也可以产生暴击攻击了。检查攻击是否正确产生计算和更新,可以查看角色和史莱姆的 ScriptableObject 文件,观察攻击后里面的值是否正确更新。

图4 最终效果

测试的过程中还发现一些问题:血量为零后还需要实现角色死亡的逻辑;ScriptableObject 文件的更新貌似是“永久存储”的,下次运行还是上次的值。这块逻辑后续肯定是有相应说明的,持续关注。