Unity3D RPG Core | 25 设置可以扔出的石头
这一节我们实现石头人的远程攻击:让它扔出一块石头砸向角色人物。
1. 制作石头预制体
我们从素材中将石头拖拽到场景中。因为石头是被抛出来的,需要模拟重力,因此我们添加 Rigidbody 刚体组件。
同时石头与地面要产生碰撞,所以也要添加碰撞体。我们不使用之前使用过的盒子或胶囊碰撞体,范围太粗糙,无法模拟石头真实的碰撞效果。此处我们使用 Mesh Collider 碰撞体,如图 1 所示,需要勾选 Convex。可以在场景中看到 Mesh Collider 设置的范围很贴合。

2. 创建石头控制脚本
因为石头是需要实时生成的,它自己控制自己的生命周期逻辑会更方便一点,所以我们也会石头创建一个脚本,命名为 RockController。
代码清单 1 是石头的控制器代码,在刚创建时(Satrt)就飞向角色人物(FlyToTarget)。飞向人物使用 Rigidbody.AddForce() 函数实现,其指明加的力(方向和大小)和力的类型。我们再往上面加力,防止石头坠落太快。
飞向的角色由外部指定,通过 SetTarget() 函数指定。
- public class RockController : MonoBehaviour
- {
- Rigidbody m_rigidbody;
- GameObject m_target = null;
- public float m_force = 20;
- // Start is called before the first frame update
- void Start()
- {
- m_rigidbody = GetComponent<Rigidbody>();
- FlyToTarget();
- }
- public void SetTarget(GameObject target)
- {
- m_target = target;
- }
- void FlyToTarget()
- {
- if (m_target != null)
- {
- Vector3 direction = (m_target.transform.position - transform.position + Vector3.up).normalized;
- m_rigidbody.AddForce(m_force * direction, ForceMode.Impulse);
- }
- }
- }
实现完代码后,我们将 RockController 拖拽到石头上,这时候石头的组件已经全部准备完毕。因为初始场景中不需要石头,我们将 Hierarchy 窗体中的石头拖拽到 Assets 窗体,制作成预制体,供后续生成。最后删除 Hierarchy 窗体中原本的石头对象。
3. 实现投掷功能
我们回到石头人控制器中实现投掷石头。投掷石头的时机也通过动画事件指定,如代码清单 2 所示,此处定义了 ThrowRock() 事件函数。
代码里声明了两个变量,一个是石头的预制体,一个是石头人右手的位置。这两个变量都在外部预先指定。投掷的逻辑是,在投掷时通过石头预制体创建石头,其位置在石头人左手处;接着设置角色的位置;然后在石头初始化完成后会飞向角色位置。
还是时序问题感觉拿捏不准:石头人处的 SetTarget() 需要在石头的 Start() 之前完成。这点不确定,但是运行起来没有问题。
- public class GolemController : EnemyController
- {
- public GameObject m_rockPrefab;
- public Transform m_rightHandTransform;
- // Animation Event
- public void ThrowRock()
- {
- GameObject rock = Instantiate(m_rockPrefab, m_rightHandTransform.position, Quaternion.identity);
- GameObject target = m_playerObj;
- if (target == null)
- target = FindObjectOfType<PlayerController>().gameObject;
- rock.GetComponent<RockController>().SetTarget(m_playerObj);
- }
- }
逻辑实现好后,我们就可以在外部指定石头预制体和石头人的右手位置。如图 2 所示,右手位置可以使用模型上现有的位置大致指定。

修复无法攻击的问题
石头人的占地面积比较大,碰撞相关设置参数都比较大,会出现人物一直走不到指定敌人位置,而无法攻击的情况。我们通过修改 stoppingDistance 和判断条件来改善。
如代码清单 3 所示,我们在 Awake() 时记录初始 stoppingDistance 值。攻击时计算两者之间的碰撞距离,和攻击距离比较,选取其中的较大值。并以此更新 stoppingDistance 值和判断条件值。在人物移动的时候,恢复为初始的 stoppingDistance 值。
- public class PlayerController : MonoBehaviour
- {
- float m_initStoppingDistance;
- void DoMoveAction(Vector3 destination)
- {
- StopAllCoroutines();
- m_navMeshAgent.isStopped = false;
- m_navMeshAgent.stoppingDistance = m_initStoppingDistance;
- // 人物角色没死才移动
- if (m_characterData.CurrentHealth > 0)
- m_navMeshAgent.destination = destination;
- }
- IEnumerator CoroutineAttackEnemy(GameObject obj)
- {
- m_navMeshAgent.isStopped = false;
- transform.LookAt(obj.transform);
- float distance = obj.GetComponent<NavMeshAgent>().radius + m_navMeshAgent.radius + 1.0f;
- distance = Mathf.Max(distance, m_attackData.AttackRange);
- m_navMeshAgent.stoppingDistance = distance;
- while (Vector3.Distance(obj.transform.position, transform.position) > distance) // m_attackData.AttackRange
- {
- m_navMeshAgent.destination = obj.transform.position;
- yield return null;
- }
- }
- void Awake()
- {
- m_initStoppingDistance = m_navMeshAgent.stoppingDistance;
- }
- }
最终效果如图 3 所示,角色靠近石头人后会投掷石头。角色走近石头人攻击时速度有点奇怪。
碰撞范围大了之后,人物走近速度会变慢,效果看着非常奇怪。不知道哪里出了问题。
