Unity3D RPG Core | 31 实现同场景内传送

这节我们继续完善传送门逻辑,实现同场景下的传送。

1. 场景控制器

我们首先实现场景传送的功能,新建 C# 脚本,命名为 SceneController。该类也是一个单例类,以便方便得跨类调用。

如代码清单 1 所示,TransferToDestination 函数实现传送的功能,先实现同场景分支的功能。同场景传送核心实现是 SetPositionAndRotation() 函数,设置角色人物的位置和朝向。实现函数通过协程完成。

如何获取人物目的点的位置和朝向:这些信息我们已经在之前的传送门空对象坐标点设置过了,所以现在的人物就是获取到空对象所对应的目标点。对应的函数为 GetDestinationPortal(),我们通过 FindObjectsOfType()遍历所有传送门,寻找目标点对应的传送门实例。

代码清单 1 场景控制器
  1. public class SceneController : Singleton<SceneController>
  2. {
  3.     GameObject   m_player = null;
  4.     NavMeshAgent m_agent = null;
  5.  
  6.     public void TransferToDestination(PortalController portal)
  7.     {
  8.         switch (portal.m_transmissionType)
  9.         {
  10.             case PortalController.TransmissionType.TransmissionSameScene:
  11.                 StartCoroutine(Transfer(SceneManager.GetActiveScene().name, portal.m_destinationPortalName));
  12.                 break;
  13.             case PortalController.TransmissionType.TransmissionDifferentScene:
  14.                 break;
  15.         }
  16.     }
  17.  
  18.     IEnumerator Transfer(string sceneName, string destPortalName)
  19.     {
  20.         if (m_player == null)
  21.         {
  22.             m_player = GameManager.GetInstance().m_playerData.gameObject;
  23.             m_agent  = m_player.GetComponent<NavMeshAgent>();
  24.         }
  25.         PortalController destPortal = GetDestinationPortal(destPortalName);
  26.         m_agent.enabled = false;
  27.         m_player.transform.SetPositionAndRotation(
  28.             destPortal.m_point.position,
  29.             destPortal.m_point.rotation);
  30.         m_agent.enabled = true;
  31.         yield return null;
  32.     }
  33.  
  34.     PortalController GetDestinationPortal(string destPortalName)
  35.     {
  36.         PortalController[] portals = FindObjectsOfType<PortalController>();
  37.         foreach (PortalController portal in portals)
  38.         {
  39.             if (portal.m_portalName == destPortalName)
  40.                 return portal;
  41.         }
  42.         return null;
  43.     }
  44. }

在进行传送前,需要把人物的 agent 停止掉,要不会出现人物漂移的情形。和当时击退的逻辑类似,这边的做法是把 agent 停止掉(第 26 行)。

角色人物的对象在初始化时就获取会出问题,我们设置逻辑让其只获取一遍(第 20 至 24 行)。

还是单例类加载顺序的老问题。

代码完成后,我们在 Hierarchy 窗体中新建一个空对象存放这个单例脚本,以便可以运行到它。

2. 实现传送逻辑

场景控制器实现好了之后,我们就可以调用其中的传送功能了。如代码清单 2 所示,如果进入了传送门区域,并且按下了键盘 E 键,就可以进行传送。

代码清单 2 触发传送
  1. public class PortalController : MonoBehaviour
  2. {
  3.     // Update is called once per frame
  4.     void Update()
  5.     {
  6.         if (canTransfer && Input.GetKeyDown(KeyCode.E))
  7.         {
  8.             SceneController.GetInstance().TransferToDestination(this);
  9.         }
  10.     }
  11. }

2.1 鼠标设置

鼠标放在传送门上也需要特别的样式指定,可以看之前的 《Unity3D RPG Core | 06 设置鼠标指针》 文章温习一下。

代码清单 3 中设置了传送门对应的鼠标样式,以及点击传送门的逻辑。传送门需要设置新的标签 Portal

代码清单 3 设置传送鼠标
  1. public class MouseManager : Singleton<MouseManager>
  2. {
  3.     // 鼠标纹理
  4.     public Texture2D m_textureMove, m_textureAttack, m_texturePortal;
  5.  
  6.     public void SetCursorTexture(RaycastHit raycastHit)
  7.     {
  8.         string tag = raycastHit.collider.gameObject.tag;
  9.         if (tag == "Ground")
  10.             Cursor.SetCursor(m_textureMove, new Vector2(16, 16), CursorMode.Auto);
  11.         else if (tag == "Enemy")
  12.             Cursor.SetCursor(m_textureAttack, new Vector2(16, 16), CursorMode.Auto);
  13.         else if (tag == "Attackable")
  14.             Cursor.SetCursor(m_textureAttack, new Vector2(16, 16), CursorMode.Auto);
  15.         else if (tag == "Portal")
  16.             Cursor.SetCursor(m_texturePortal, new Vector2(16, 16), CursorMode.Auto);
  17.     }
  18.  
  19.     public void DoMouseEvent(RaycastHit raycastHit)
  20.     {
  21.         if (Input.GetMouseButtonDown(0))
  22.         {
  23.             string tag = raycastHit.collider.gameObject.tag;
  24.             if (tag == "Ground")
  25.                 OnMoveMouseClick.Invoke(raycastHit.point);
  26.             else if (tag == "Portal")
  27.                 OnMoveMouseClick.Invoke(raycastHit.point);
  28.             else if (tag == "Enemy")
  29.                 OnAttackMouseClick.Invoke(raycastHit.collider.gameObject);
  30.             else if (tag == "Attackable")
  31.                 OnAttackMouseClick.Invoke(raycastHit.collider.gameObject);
  32.         }
  33.     }
  34. }

还有需要注意的一点是,因为传送门比较高,碰撞区域也设置的很高的鼠标射线也会相交的比较远,导致走不进传送门。所以如图 1 所示,我们将传送门的碰撞区域设矮一点。

图1 传送门的碰撞区域

最终的传送效果如图 2 所示,注意设置对传送门的名字。

图2 最终效果