【问题标题】:How to preserve the full state of objects between scenes?如何在场景之间保留对象的完整状态?
【发布时间】:2019-07-05 11:30:08
【问题描述】:

在加载新场景时,我遇到了鼠标拖动无法延续到下一个场景并且在加载新场景时必须重新单击的问题。

我希望鼠标点击无缝地延续到下一个场景,而玩家不会注意到,更一般地说,我想知道如何最好地保存某些游戏对象并让它们延续到下一个场景。

本质上,我想做的是让整个游戏表现得像一个大场景,玩家可以玩这些大场景,但仍然可以分解成更小的场景,以便在稍后阶段访问或转换为关卡。

提前致谢。

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

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

下面是负责加载场景的脚本:

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

    }
}

【问题讨论】:

  • 您能否展示/解释一下您是如何加载场景的?我很确定您可以通过将此拖动的对象放入始终保留在那里的“DragScene”中来解决该问题,然后对其他场景使用附加场景加载和卸载。
  • @derHugo 我刚刚更新了代码并添加了负责场景变化的脚本

标签: c# unity3d scene-manager


【解决方案1】:

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

OnMouseDrag 依赖于物理系统内部使用对象Collider 和来自Camera 的光线投射。现在,如果您切换场景,我猜一个相机会被禁用,因此您的拖动会被中断。

同时使用LoadScene 而不是LoadSceneAsync 会导致明显的延迟,也可能与问题有关。


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

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

这个场景包含诸如MainCamera、全局光照、全局管理器组件,无论如何都不应该被销毁。

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

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

然后为了卸载前面提到的MainScene,你传递optional parameterLoadSceneMode.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;
    }
}

这个MySceneManager 是一个static class,所以它没有附加到任何游戏对象或场景,而只是“生活”在Assets 中。您现在可以使用

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

MonoBehaviour 类型的第二个参数(基本上是您的脚本)是必需的,因为必须有人负责运行 IEnumerator Coroutine,而这不能由 static class 本身完成。

3。 DontDestroyOnLoad

目前,您正在将您随时拖动的任何游戏对象添加到DontDestroyOnLoad。但是您永远不会撤消此操作,因此您同时触摸的任何内容都会从那一刻开始......永远。

我宁愿使用例如像

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

这是一个Extension Method,您只需拨打电话即可

someGameObject.SetDontDestroyOnLoad(boolvalue);

在任何 GameObject 引用上。

然后我将您的脚本更改为

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

还有这个在其他场景中

public class LoadNextScene : MonoBehaviour
{
    [SerializeField] private int nexSceneIndex;

    private void Update()
    {
        if (!Input.GetKeyDown(KeyCode.Space)) return;

        MySceneManager.LoadScene(nexSceneIndex, this);
    }
}

并有 3 个场景:

  • Main:如前所述包含

    • 主摄像头
    • 定向光
    • LoadFirstScene

  • 测试:包含

    • MoveBall“球体”
    • LoadNextScene

  • test2:包含

    • 一个移动球“立方体”
    • LoadNextScene

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

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

如果我同时拖动其中一个对象,我可以将它带到下一个场景中(但一次只能拖一个)。我什至可以将它再次带回第一个场景,以便拥有例如。我可以玩的两个球体;)

【讨论】:

  • 我刚刚对您的解决方案有疑问。在实现了一切之后,我现在可以将我的对象无缝地移动到下一个场景。然而,我遇到的一个问题似乎没有找到解决方案是新场景中的游戏对象似乎无法交互或响应脚本中对它们的引用以对白色做一些事情我从前一个屏幕移过来的球。你可能知道为什么会这样吗?
  • 例如,如果我让它们移动到白球的当前位置,什么也没发生,它们会生成但保持静止,尽管脚本告诉它们移动到那个位置
  • 我不确定你到底是什么意思..也许你应该打开一个新问题并提供相应的代码。一般来说,当然场景参考很难恢复。您可以在拖动对象时另外添加某个Tag,以便您可以使用FindObjectWithTag 来获取对场景中拖动对象的引用
  • 这就是我目前正在做的事情。在我的第一个场景中,我还附加了一个脚本,该脚本从该场景中生成了某些游戏对象,并在分数增加触发场景更改时使它们以某种方式运行我有第二个脚本附加到一个游戏对象,它使之前提到的相同游戏对象会生成,但是这些对象的行为由相同的脚本决定,而与场景无关。但是当场景切换时它似乎不再有反应了我将打开一个新问题来显示这一点
  • 这里是问题的链接:stackoverflow.com/questions/57012688/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-26
  • 2019-02-09
  • 1970-01-01
  • 2021-02-27
  • 1970-01-01
  • 2018-02-18
相关资源
最近更新 更多