如何保留场景之间对象的完整状态?

时间:2019-07-04 03:00:29

标签: c# unity3d scene-manager

在加载新场景时,我遇到了鼠标拖动不移到下一个场景的麻烦,并且在加载新场景时不得不重新单击。

我希望鼠标单击可以无缝地转移到下一个场景,而不会引起玩家的注意,更一般地说,我想知道如何最好地保存某些游戏对象并使它们转移到下一个场景。

从本质上讲,我想做的是让整个游戏的行为像一个大场景,玩家可以玩低谷,但仍然可以分解为较小的场景,以后可以访问或转换为关卡。

谢谢。

这是我当前正在使用的代码

using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;

public class MoveBall : MonoBehaviour
{
    public static Vector2 mousePos = new Vector2();

    private void OnMouseDrag()
    {    
        mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        transform.position = mousePos;   

        DontDestroyOnLoad(this.gameObject);     
    }
} 

Bellow是负责场景加载的脚本:

public class StarCollision : MonoBehaviour
{

    private bool alreadyScored = false;

    private void OnEnable()
    {
        alreadyScored = false;
    }


    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.gameObject.CompareTag("White Ball"))
        {
            if (!alreadyScored)
            {
                ScoreScript.scoreValue += 1;
                StartCoroutine(ChangeColor());

                alreadyScored = true;
            }

        }

        if (ScoreScript.scoreValue > 4)
        {
            SceneManager.LoadScene(1);
        }

    }


    private IEnumerator ChangeColor()
    {

        ScoreScript.score.color = Color.yellow;
        yield return new WaitForSeconds(0.1f);
        ScoreScript.score.color = Color.white;
        gameObject.SetActive(false);

    }
}

1 个答案:

答案 0 :(得分:3)

我认为它不起作用的主要原因是您可能在新场景中还有另一个Camera

OnMouseDrag内部使用对象ColliderCamera发出的射线广播依赖于物理系统。现在,如果您切换场景,我猜想一台摄像机被禁用了,因此拖动操作会被中断。

也使用LoadScene而不是LoadSceneAsync会导致明显的滞后,并且可能与问题有关。


我可能有一个更复杂的解决方案,但这就是我通常要做的:

1。拥有一个全局场景“ MainScene”

此场景包含诸如MainCamera,全局ligthning,全局管理器组件,这些组件永远都不应销毁。

2。使用加性异步场景加载

您说您不希望用户在场景切换时不注意,所以我还是建议您使用SceneManager.LoadSceneAsync

然后,为了卸载前面提到的MainScene,您需要传递optional parameter LoadSceneMode.Additive。这使得新场景被加载到已经存在的场景之外。然后,您只需要通过卸载以前添加的场景即可进行交换。

为此我创建了一个非常简单的static管理器:

public static class MySceneManager
{
    // store build index of last loaded scene
    // in order to unload it later
    private static int lastLoadedScene = -1;

    public static void LoadScene(int index, MonoBehaviour caller)
    {
        caller.StartCoroutine(loadNextScene(index));
    }

    // we need this to be a Coroutine (see link below)
    // in order to correctly set the SceneManager.SetActiveScene(newScene);
    // after the scene has finished loading. So the Coroutine is required 
    // in order to wait with it until the reight moment
    private static IEnumerator loadNextScene(int index)
    {
        // start loading the new scene async and additive
        var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);

        // optionally prevent the scene from being loaded instantly but e.g.
        // display a loading progress
        // (in your case not but for general purpose I added it)
        _async.allowSceneActivation = false;
        while (_async.progress < 0.9f)
        {
            // e.g. show progress of loading

            // yield in a Coroutine means
            // "pause" the execution here, render this frame
            // and continue from here in the next frame
            yield return null;
        }

        _async.allowSceneActivation = true;
        // loads the remaining 10% 
        // (meaning it runs all the Awake and OnEnable etc methods)
        while (!_async.isDone)
        {
            yield return null;
        }

        // at this moment the new Scene is supposed to be fully loaded

        // Get the new scene
        var newScene = SceneManager.GetSceneByBuildIndex(index);

        // would return false if something went wrong during loading the scene
        if (!newScene.IsValid()) yield break;

        // Set the new scene active
        // we need this later in order to place objects back into the correct scene
        // if we do not want them to be DontDestroyOnLoad anymore
        // (see explanation in SetDontDestroyOnLoad)
        SceneManager.SetActiveScene(newScene);

        // Unload the last loaded scene
        if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);

        // update the stored index
        lastLoadedScene = index;
    }
}

MySceneManagerstatic class,因此它没有附加到任何GameObject或场景,而只是“存在”于Assets中。现在,您可以使用

从任何地方调用它
 MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);

类型为MonoBehaviour的第二个参数(基本上就是脚本)是必需的,因为必须由某人负责运行IEnumerator Coroutine,而{{ 1}}本身。

3。 DontDestroyOnLoad

当前,您要将随时拖动的任何GameObject添加到static class。但是您永远不会撤消此操作,因此从那一刻起,您接触到的任何东西都将永远持续下去。

我宁愿使用例如像

DontDestroyOnLoad

这是Extension Method,可让您简单地拨打电话

public static class GameObjectExtensions
{
    public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
    {
        if (value)
        {
            // Note in general if DontDestroyOnLoad is called on a child object
            // the call basically bubbles up until the root object in the Scene
            // and makes this entire root tree DontDestroyOnLoad
            // so you might consider if you call this on a child object to first do
            //gameObject.transform.SetParent(null);
            UnityEngine.Object.DontDestroyOnLoad(gameObject);
        }
        else
        {
            // add a new temporal GameObject to the active scene
            // therefore we needed to make sure before to set the
            // SceneManager.activeScene correctly
            var newGO = new GameObject();
            // This moves the gameObject out of the DontdestroyOnLoad Scene
            // back into the currently active scene
            gameObject.transform.SetParent(newGO.transform, true);
            // remove its parent and set it back to the root in the 
            // scene hierachy
            gameObject.transform.SetParent(null, true);
            // remove the temporal newGO GameObject
            UnityEngine.Object.Destroy(newGO);
        }
    }
}

在任何GameObject参考上。

然后我将您的脚本更改为

someGameObject.SetDontDestroyOnLoad(boolvalue);

在您的public class MoveBall : MonoBehaviour { public static Vector2 mousePos = new Vector2(); // On mouse down enable DontDestroyOnLoad private void OnMouseDown() { gameObject.SetDontDestroyOnLoad(true); } // Do your dragging part here private void OnMouseDrag() { // NOTE: Your script didn't work for me // in ScreenToWorldPoint you have to pass in a Vector3 // where the Z value equals the distance to the // camera/display plane mousePos = Camera.main.ScreenToWorldPoint(new Vector3( Input.mousePosition.x, Input.mousePosition.y, transform.position.z))); transform.position = mousePos; } // On mouse up disable DontDestroyOnLoad private void OnMouseUp() { gameObject.SetDontDestroyOnLoad(false); } } 脚本中,您只需要交换

StarCollision

使用

SceneManager.LoadScene(1);

演示

为了进行一些演示,我使用了两个简单的脚本来“伪造”它

这个在主场景中

MySceneManager.LoadScene(2, this);

另外一个场景中的这个

public class LoadFirstscene : MonoBehaviour
{
    // Start is called before the first frame update
    private void Start()
    {
        MySceneManager.LoadScene(1, this);
    }
}

有3个场景:

  • 主要:如前所述包含

    • MainCamera
    • DirectionalLight
    • public class LoadNextScene : MonoBehaviour { [SerializeField] private int nexSceneIndex; private void Update() { if (!Input.GetKeyDown(KeyCode.Space)) return; MySceneManager.LoadScene(nexSceneIndex, this); } }

    enter image description here

  • 测试:包含

    • 一个LoadFirstScene“领域”
    • MoveBall

    enter image description here

  • test2:包含

    • MoveBall“多维数据集”
    • LoadNextScene

    enter image description here

索引与构建设置匹配(确保LoadNextScene始终位于Main处))

enter image description here

我现在可以使用 Space 键在0test之间切换。

如果同时拖动其中一个对象,则可以将其继续到下一个场景中(一次只能一个)。我什至可以再次将其带回到第一个场景,例如我可以使用两个球形物体;)

enter image description here