Unity-场景的异步加载
为什么需要异步加载
在诸多大型游戏里,场景渲染精度都是动态的,随着场景与角色距离的增加,渲染精度也在递减,这样极大的减少了硬件性能的消耗。
但如果角色使用了某些传送技能,将自己传送到为渲染的地点,游戏可能就会因为需要瞬间渲染大量的场景而卡顿。此时就需要用到 场景的异步加载 了。
异步加载,指的就是在加载角色之前,事先将角色周围的场景渲染好,防止角色传送后出现严重的卡顿,以提高玩家的游戏体验。
场景加载构想
在提供直接的代码之前,我们需要对基本的传送进行一个简单的构思。首先我们需要一个传送门以确定我们 能够触发传送 的地点;传送门前还需要设置一点以确定 传送地点,并将其作为传送门的子物体。
注意这两者的区别:传送门 只是确定 触发传送的地点,而角色需要前往的地点由 传送门前的点 决定。
编写代码,分别给传送门与传送点设置对应的属性(传送门:设置传送模式、传送场景、传送终点名字,以及编写 判断何时传送的函数)(传送点:设置传送点名字),这样简易的传送门就设置好了。
关键在于场景传送代码的逻辑:
- 我们需要创建一个控制场景的代码,因为Unity自带 SceneManager 类,所以我们在这里将场景控制的代码命名为 SceneController ,同样将其写成单例模式。
- 就如同 GameManager 一样,SceneController 也一样不需要自己执行代码,需要别的类在外部调用。我们只需要在 SceneController 中写上必要的参数(PlayerPrefab:player的预制体,用于在其他场景生成角色),一个进行 异步加载的协程,一个 查找传送终点 的函数,一个 传送函数 供传送门调用即可。
- SceneController 中的传送函数需要用到传送门的参数,所以传送函数需要一个 传送门类 作为参数。传送开始前,首先需要判断传送类型(同场景 or 异场景),然后将 传送终点名、终点场景名 传给协程,同时不论同异场景,都需要在协程中将 传送终点名 传给 查找传送终点位置的函数,以获得传送终点的位置信息,在获得终点信息后才能进行场景的异步加载。
实例代码
在上面的简单构思中,已经对基本的传送逻辑进行了简单的梳理,现在就是将逻辑实现的时候了。
传送目标点
传送终点的代码十分简单,只是通过枚举(enum)对传送点进行记录,枚举要在Unity窗口中自行选择
1 2 3 4 5 6 7 8 9
| public class TransitionDestination : MonoBehaviour { public enum DestinationTag { ENTER, A, B, C }
public DestinationTag destinationTag; }
|
传送门(可传送地点)
传送门需要判定角色是否在可传送范围内,如果是,则可以在按下某键时进行传送
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| public class TransitionPoint : MonoBehaviour { public enum TransitionType { SameScene, DifferentScene }
[Header("Transition Info")] public string sceneName; public TransitionType transitionType; public TransitionDestination.DestinationTag destinationTag; private bool canTrans; private void Update() { if(Input.GetKeyDown(KeyCode.E) && canTrans) { if(SceneController.Instance != null) { SceneController.Instance.TransitionToDestination(this); } } }
private void OnTriggerStay(Collider other) { if(other.gameObject.CompareTag("Player")) { canTrans = true; } }
private void OnTriggerExit(Collider other) { if(other.gameObject.CompareTag("Player")) { canTrans = false; } } }
|
ScenController
SceneController 的详细介绍已经在场景转换逻辑写清楚了,在这不赘述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| public class SceneController : Singleton<SceneController> { public GameObject playerPrefab; GameObject player; NavMeshAgent playerAgent;
protected override void Awake() { base.Awake(); DontDestroyOnLoad(this); }
public void TransitionToDestination(TransitionPoint transitionPoint) { switch(transitionPoint.transitionType) { case TransitionPoint.TransitionType.SameScene: StartCoroutine(Transition(SceneManager.GetActiveScene().name, transitionPoint.destinationTag)); break; case TransitionPoint.TransitionType.DifferentScene: StartCoroutine(Transition(transitionPoint.sceneName, transitionPoint.destinationTag)); break; } }
IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag) { if(SceneManager.GetActiveScene().name != sceneName) { yield return SceneManager.LoadSceneAsync(sceneName); yield return Instantiate(playerPrefab, GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation); yield break; } else { player = GameManager.Instance.playerStats.gameObject; playerAgent = player.GetComponent<NavMeshAgent>(); playerAgent.isStopped = true; player.transform.SetPositionAndRotation(GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation); yield return null; } }
private TransitionDestination GetDestination(TransitionDestination.DestinationTag destinationTag) { var entrances = FindObjectsOfType<TransitionDestination>(); foreach (var item in entrances) { if(item.destinationTag == destinationTag) { return item; } }
return null; } }
|