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);
            }
        }
    }
}

门的逻辑起始很简单,如果玩家进入了门的碰撞器范围内,就分两种情况:
一是跨场景的数据不存在,那么此时就是传送之前,玩家想要进行传送了。此时,就创建好需要跨场景保存的数据,比如玩家相对门的位置,玩家想要去哪个场景,如果目标场景中有多个门,那要明确目标门的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;
    }
}

因为玩家一次只可能使用一个门,所以,这个跨场景传送数据的物体,被设计为单例模式,他唯一的用途,就是在老场景中被创建出来,然后记录玩家相对于源门的相对位置和相对朝向,以及目标门的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);
}

场景的加载,有个过渡动画,并且有个进度条,但是我这三个场景都太小了,加载是秒加载的,所以,这个过渡就会一闪而过,如果场景足够大,加载时间长一点,那么这个过渡动画就变的很有必要了。
异步加载使用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));
}

关于门的特效

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

猜你喜欢

转载自blog.csdn.net/sdhexu/article/details/127414465