Unity3D RPG Core | 31 实现同场景内传送
这节我们继续完善传送门逻辑,实现同场景下的传送。
1. 场景控制器
我们首先实现场景传送的功能,新建 C# 脚本,命名为 SceneController。该类也是一个单例类,以便方便得跨类调用。
如代码清单 1 所示,TransferToDestination 函数实现传送的功能,先实现同场景分支的功能。同场景传送核心实现是 SetPositionAndRotation() 函数,设置角色人物的位置和朝向。实现函数通过协程完成。
如何获取人物目的点的位置和朝向:这些信息我们已经在之前的传送门空对象坐标点设置过了,所以现在的人物就是获取到空对象所对应的目标点。对应的函数为 GetDestinationPortal(),我们通过 FindObjectsOfType()遍历所有传送门,寻找目标点对应的传送门实例。
- public class SceneController : Singleton<SceneController>
- {
- GameObject m_player = null;
- NavMeshAgent m_agent = null;
- public void TransferToDestination(PortalController portal)
- {
- switch (portal.m_transmissionType)
- {
- case PortalController.TransmissionType.TransmissionSameScene:
- StartCoroutine(Transfer(SceneManager.GetActiveScene().name, portal.m_destinationPortalName));
- break;
- case PortalController.TransmissionType.TransmissionDifferentScene:
- break;
- }
- }
- IEnumerator Transfer(string sceneName, string destPortalName)
- {
- if (m_player == null)
- {
- m_player = GameManager.GetInstance().m_playerData.gameObject;
- m_agent = m_player.GetComponent<NavMeshAgent>();
- }
- PortalController destPortal = GetDestinationPortal(destPortalName);
- m_agent.enabled = false;
- m_player.transform.SetPositionAndRotation(
- destPortal.m_point.position,
- destPortal.m_point.rotation);
- m_agent.enabled = true;
- yield return null;
- }
- PortalController GetDestinationPortal(string destPortalName)
- {
- PortalController[] portals = FindObjectsOfType<PortalController>();
- foreach (PortalController portal in portals)
- {
- if (portal.m_portalName == destPortalName)
- return portal;
- }
- return null;
- }
- }
在进行传送前,需要把人物的 agent 停止掉,要不会出现人物漂移的情形。和当时击退的逻辑类似,这边的做法是把 agent 停止掉(第 26 行)。
角色人物的对象在初始化时就获取会出问题,我们设置逻辑让其只获取一遍(第 20 至 24 行)。
还是单例类加载顺序的老问题。
代码完成后,我们在 Hierarchy 窗体中新建一个空对象存放这个单例脚本,以便可以运行到它。
2. 实现传送逻辑
场景控制器实现好了之后,我们就可以调用其中的传送功能了。如代码清单 2 所示,如果进入了传送门区域,并且按下了键盘 E 键,就可以进行传送。
- public class PortalController : MonoBehaviour
- {
- // Update is called once per frame
- void Update()
- {
- if (canTransfer && Input.GetKeyDown(KeyCode.E))
- {
- SceneController.GetInstance().TransferToDestination(this);
- }
- }
- }
2.1 鼠标设置
鼠标放在传送门上也需要特别的样式指定,可以看之前的 《Unity3D RPG Core | 06 设置鼠标指针》 文章温习一下。
代码清单 3 中设置了传送门对应的鼠标样式,以及点击传送门的逻辑。传送门需要设置新的标签 Portal。
- public class MouseManager : Singleton<MouseManager>
- {
- // 鼠标纹理
- public Texture2D m_textureMove, m_textureAttack, m_texturePortal;
- public void SetCursorTexture(RaycastHit raycastHit)
- {
- string tag = raycastHit.collider.gameObject.tag;
- if (tag == "Ground")
- Cursor.SetCursor(m_textureMove, new Vector2(16, 16), CursorMode.Auto);
- else if (tag == "Enemy")
- Cursor.SetCursor(m_textureAttack, new Vector2(16, 16), CursorMode.Auto);
- else if (tag == "Attackable")
- Cursor.SetCursor(m_textureAttack, new Vector2(16, 16), CursorMode.Auto);
- else if (tag == "Portal")
- Cursor.SetCursor(m_texturePortal, new Vector2(16, 16), CursorMode.Auto);
- }
- public void DoMouseEvent(RaycastHit raycastHit)
- {
- if (Input.GetMouseButtonDown(0))
- {
- string tag = raycastHit.collider.gameObject.tag;
- if (tag == "Ground")
- OnMoveMouseClick.Invoke(raycastHit.point);
- else if (tag == "Portal")
- OnMoveMouseClick.Invoke(raycastHit.point);
- else if (tag == "Enemy")
- OnAttackMouseClick.Invoke(raycastHit.collider.gameObject);
- else if (tag == "Attackable")
- OnAttackMouseClick.Invoke(raycastHit.collider.gameObject);
- }
- }
- }
还有需要注意的一点是,因为传送门比较高,碰撞区域也设置的很高的鼠标射线也会相交的比较远,导致走不进传送门。所以如图 1 所示,我们将传送门的碰撞区域设矮一点。

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