• Unity实现跨场景的传送门


    Unity实现跨场景的传送门

    引言

    之前写过一篇文章——《Unity第一人称可视化传送门制作》,实现了传送门的基本操作,而且,可以通过传送门观察对面的画面,但一个缺陷是无法跨场景传送。那么,如何实现跨场景的传送呢?
    事实上,所谓跨场景传送,本质上就是场景加载,关键是要传送到什么位置去,在在老场景中事先确定好位置,然后新场景加载后,把角色设置到新的目标位置和朝向即可。本质上,这就是跨场景传送数据的问题,Unity中,有个DontDestroyOnLoad方法,可以将一个物体设置为“切换场景时不要销毁”,那么这个物体就会保留到下一个场景中,我们需要的数据就可以保存在这个物体上,等数据使用完了,再把这个物体销毁即可。但是,跨场景的传送门,无法像“同场景中的传送门”一样,可以提前观察到对面的情况,因为在没有进入门之前,场景还没有加载,肯定是无法观察到的,这也是没有办法的。

    视频效果

    注意观察视频中的Hierarchy窗口,每次传送,场景都真实的切换了。

    Unity跨场景传送门

    具体实现

    public class ProtalDoor : MonoBehaviour
    {
        public LayerMask TransLayer;
        public string TargetSceneName;
        public string TargetDoorTag;
        
        private void OnTriggerEnter(Collider other)
        {
            if (((1 << other.gameObject.layer ) & TransLayer) != 0)
            {
                if (TransDataToScene.IsExists)
                {
                    // 如果存在数据,表示此时为玩家进入传送后新场景的门
                    // 启动玩家控制,销毁跨场景数据
                    PlayerController.Active(true);
                    TransDataToScene.Destroy();
                }
                else
                {
                    // 数据不存在,表示玩家刚进入传送门,此为传送前。
                    // 需要禁用玩家控制,然后创建跨场景数据,加载新场景
                    PlayerController.Active(false);
                    TransDataToScene.SaveData(transform, other.transform, TargetDoorTag);
                    HxSceneLoader.LoadScene(TargetSceneName);
                }
            }
        }
    }
    
    • 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

    门的逻辑起始很简单,如果玩家进入了门的碰撞器范围内,就分两种情况:
    一是跨场景的数据不存在,那么此时就是传送之前,玩家想要进行传送了。此时,就创建好需要跨场景保存的数据,比如玩家相对门的位置,玩家想要去哪个场景,如果目标场景中有多个门,那要明确目标门的TAG,然后就加载场景。
    二是如果跨场景的数据已经存在了,那么就表示此时为传送之后了(传送之后,玩家会被设置到目标门附近,所以也会触发OnTriiggerEnter),此时要做的就是销毁数据,以便下次传送。

    数据
    public class TransDataToScene : MonoBehaviour
    {
        private static TransDataToScene _instance;
        public static bool IsExists => _instance != null;
    
        public static Vector3 localPos { get; private set; }
        public static Vector3 localRot { get; private set; }
        public static string targetDoorTag { get; private set; }
    
        public static ProtalDoor targetDoor => string.IsNullOrWhiteSpace(targetDoorTag)
            ? null
            : GameObject.FindWithTag(targetDoorTag)?.GetComponent<ProtalDoor>();
    
        public static void Destroy()
        {
            if (IsExists)
            {
                Destroy(_instance.gameObject);
                _instance = null;
            }
        }
    
        public static void SaveData(Transform door, Transform player, string targetTag)
        {
            if (_instance == null)
            {
                GameObject obj = new GameObject("TransData");
                _instance = obj.AddComponent<TransDataToScene>();
                DontDestroyOnLoad(obj);
            }
    
            localPos = door.InverseTransformPoint(player.position);
            localRot = door.InverseTransformDirection(player.eulerAngles);
            targetDoorTag = targetTag;
        }
    }
    
    • 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

    因为玩家一次只可能使用一个门,所以,这个跨场景传送数据的物体,被设计为单例模式,他唯一的用途,就是在老场景中被创建出来,然后记录玩家相对于源门的相对位置和相对朝向,以及目标门的TAG,然后新场景加载完成后,用它记录好的数据,找到新场景中的目标门物体,最后进行玩家位置的恢复,恢复到和目标门一样的相对位置。

    场景异步加载
    public static void LoadScene(string name)
    {
        // 首先启用加载器,然后启动加载协程。
        Instance.gameObject.SetActive(true);
        Instance.StartCoroutine(Instance.RealLoad(name));
    }
    
    private IEnumerator RealLoad(string sceneName)
    {
        // 播放界面淡入动画,显示进度条,开始加载
        _animator.SetTrigger(StartLoad);
        var asy = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Single);
        asy.allowSceneActivation = true;
    
        while (!asy.isDone)
        {
            _text.text = ((asy.progress + 0.09f) * 100f).ToString("0");
            _slider.value = asy.progress + 0.09f;
            yield return null;
        }
    
        // 加载完成,设置玩家在新场景中的位置,然后UI淡出。
        PlayerController.SetTransform();
        _animator.SetTrigger(LoadDone);
    }
    
    • 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

    场景的加载,有个过渡动画,并且有个进度条,但是我这三个场景都太小了,加载是秒加载的,所以,这个过渡就会一闪而过,如果场景足够大,加载时间长一点,那么这个过渡动画就变的很有必要了。
    异步加载使用LoadSceneAsync API。
    玩家恢复数据的代码:

    public static void SetTransform()
    {
        // 获取目标门
        ProtalDoor door = TransDataToScene.targetDoor;
        if (!door )
            return;
            
        // 参照玩家相对源门的位置、朝向,恢复到目标门的相对位置和朝向。
        Instance.transform.position = door.transform.TransformPoint(TransDataToScene.localPos);
        Instance.transform.rotation = Quaternion.Euler(door.transform.TransformDirection(TransDataToScene.localRot));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    关于门的特效

    这个门的模型是自己用Blender做的,美术功底不太好。但这不重要,特效分两部分,一个是门内平面本身的渲染,用Shader Graph连了简单的半透明效果:
    在这里插入图片描述
    然后就是一个粒子特效,用Visual Effect Graph连的:
    在这里插入图片描述
    起始特效方面,完全可以做到更炫酷,比如第三人称的话,可以让角色消融、变成粒子飞升。。。

  • 相关阅读:
    听GPT 讲Rust源代码--library/std(13)
    智能洗地机哪个牌子好用?智能洗地机品牌排行榜
    超越GPT-3,DeepMind推出新宠Gato,却被质疑“换汤不换药”?
    如何配置远程访问以在外部网络中使用公司内部的OA办公系统——“cpolar内网穿透”
    时间复杂度与空间复杂度详解
    开箱评测:双十一刚买的云服务器,到底好不好用?
    观测云接入 NewRelic .NET 探针
    白平衡简介
    spring事务里面开启线程插入,报错了是否会回滚?
    遥感原理与应用(一)什么是遥感?
  • 原文地址:https://blog.csdn.net/sdhexu/article/details/127414465