Unity3D RPG Core | 25 设置可以扔出的石头

这一节我们实现石头人的远程攻击:让它扔出一块石头砸向角色人物。

1. 制作石头预制体

我们从素材中将石头拖拽到场景中。因为石头是被抛出来的,需要模拟重力,因此我们添加 Rigidbody 刚体组件。

同时石头与地面要产生碰撞,所以也要添加碰撞体。我们不使用之前使用过的盒子或胶囊碰撞体,范围太粗糙,无法模拟石头真实的碰撞效果。此处我们使用 Mesh Collider 碰撞体,如图 1 所示,需要勾选 Convex。可以在场景中看到 Mesh Collider 设置的范围很贴合。

图1 添加组件

2. 创建石头控制脚本

因为石头是需要实时生成的,它自己控制自己的生命周期逻辑会更方便一点,所以我们也会石头创建一个脚本,命名为 RockController

代码清单 1 是石头的控制器代码,在刚创建时(Satrt)就飞向角色人物(FlyToTarget)。飞向人物使用 Rigidbody.AddForce() 函数实现,其指明加的力(方向和大小)和力的类型。我们再往上面加力,防止石头坠落太快。

飞向的角色由外部指定,通过 SetTarget() 函数指定。

代码清单 1 石头控制器
  1. public class RockController : MonoBehaviour
  2. {
  3.     Rigidbody m_rigidbody;
  4.     GameObject m_target = null;
  5.  
  6.     public float m_force = 20;
  7.  
  8.     // Start is called before the first frame update
  9.     void Start()
  10.     {
  11.         m_rigidbody = GetComponent<Rigidbody>();
  12.         FlyToTarget();
  13.     }
  14.  
  15.     public void SetTarget(GameObject target)
  16.     {
  17.         m_target = target;
  18.     }
  19.  
  20.     void FlyToTarget()
  21.     {
  22.         if (m_target != null)
  23.         {
  24.             Vector3 direction = (m_target.transform.position - transform.position + Vector3.up).normalized;
  25.             m_rigidbody.AddForce(m_force * direction, ForceMode.Impulse);
  26.         }
  27.     }
  28. }

实现完代码后,我们将 RockController 拖拽到石头上,这时候石头的组件已经全部准备完毕。因为初始场景中不需要石头,我们将 Hierarchy 窗体中的石头拖拽到 Assets 窗体,制作成预制体,供后续生成。最后删除 Hierarchy 窗体中原本的石头对象。

3. 实现投掷功能

我们回到石头人控制器中实现投掷石头。投掷石头的时机也通过动画事件指定,如代码清单 2 所示,此处定义了 ThrowRock() 事件函数。

代码里声明了两个变量,一个是石头的预制体,一个是石头人右手的位置。这两个变量都在外部预先指定。投掷的逻辑是,在投掷时通过石头预制体创建石头,其位置在石头人左手处;接着设置角色的位置;然后在石头初始化完成后会飞向角色位置。

还是时序问题感觉拿捏不准:石头人处的 SetTarget() 需要在石头的 Start() 之前完成。这点不确定,但是运行起来没有问题。

代码清单 2 实现投掷功能
  1. public class GolemController : EnemyController
  2. {
  3.     public GameObject m_rockPrefab;
  4.     public Transform  m_rightHandTransform;
  5.  
  6.     // Animation Event
  7.     public void ThrowRock()
  8.     {
  9.         GameObject rock = Instantiate(m_rockPrefab, m_rightHandTransform.position, Quaternion.identity);
  10.  
  11.         GameObject target = m_playerObj;
  12.         if (target == null)
  13.             target = FindObjectOfType<PlayerController>().gameObject;
  14.         rock.GetComponent<RockController>().SetTarget(m_playerObj);
  15.     }
  16. }

逻辑实现好后,我们就可以在外部指定石头预制体和石头人的右手位置。如图 2 所示,右手位置可以使用模型上现有的位置大致指定。

图2 外部指定

修复无法攻击的问题

石头人的占地面积比较大,碰撞相关设置参数都比较大,会出现人物一直走不到指定敌人位置,而无法攻击的情况。我们通过修改 stoppingDistance 和判断条件来改善。

如代码清单 3 所示,我们在 Awake() 时记录初始 stoppingDistance 值。攻击时计算两者之间的碰撞距离,和攻击距离比较,选取其中的较大值。并以此更新 stoppingDistance 值和判断条件值。在人物移动的时候,恢复为初始的 stoppingDistance 值。

代码清单 3 修复无法攻击的问题
  1. public class PlayerController : MonoBehaviour
  2. {
  3.     float         m_initStoppingDistance;
  4.  
  5.     void DoMoveAction(Vector3 destination)
  6.     {
  7.         StopAllCoroutines();
  8.         m_navMeshAgent.isStopped = false;
  9.         m_navMeshAgent.stoppingDistance = m_initStoppingDistance;
  10.         // 人物角色没死才移动
  11.         if (m_characterData.CurrentHealth > 0)
  12.             m_navMeshAgent.destination = destination;
  13.     }
  14.  
  15.     IEnumerator CoroutineAttackEnemy(GameObject obj)
  16.     {
  17.         m_navMeshAgent.isStopped = false;
  18.  
  19.         transform.LookAt(obj.transform);
  20.  
  21.         float distance = obj.GetComponent<NavMeshAgent>().radius + m_navMeshAgent.radius + 1.0f;
  22.         distance = Mathf.Max(distance, m_attackData.AttackRange);
  23.         m_navMeshAgent.stoppingDistance = distance;
  24.    
  25.         while (Vector3.Distance(obj.transform.position, transform.position) > distance) // m_attackData.AttackRange
  26.         {
  27.             m_navMeshAgent.destination = obj.transform.position;
  28.             yield return null;
  29.         }
  30.     }
  31.  
  32.     void Awake()
  33.     {
  34.         m_initStoppingDistance = m_navMeshAgent.stoppingDistance;
  35.     }
  36. }

最终效果如图 3 所示,角色靠近石头人后会投掷石头。角色走近石头人攻击时速度有点奇怪。

碰撞范围大了之后,人物走近速度会变慢,效果看着非常奇怪。不知道哪里出了问题。

图3 最终效果