如何在场景之间保留对象的完整状态?
How to preserve the full state of objects between scenes?
加载新场景时,我 运行 遇到了鼠标拖动不能转移到下一个场景并且在加载新场景时必须重新单击的麻烦。
我希望鼠标点击能够在玩家不注意的情况下无缝地转移到下一个场景,更一般地说,我想知道如何最好地保留某些游戏对象并让它们转移到下一个场景。
本质上,我想要做的是让整个游戏就像一个大场景,玩家可以玩这个场景,但仍然可以分解成更小的场景,以后可以访问或转换成关卡阶段。
提前致谢。
这是我目前使用的代码
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);
}
}
我认为它不起作用的主要原因是您可能在新场景中还有另一个 Camera
。
OnMouseDrag
依赖物理系统在内部使用对象 Collider
和来自 Camera
的光线投射。现在,如果您切换场景,我猜相机会被禁用,因此您的拖动会被中断。
同时使用 LoadScene
而不是 LoadSceneAsync
会导致明显的滞后,也可能与该问题有关。
我有一个可能有点复杂的解决方案,但这是我通常做的:
1。拥有一个全局场景 "MainScene"
此场景包含诸如MainCamera、global lightthning、global manager 组件无论如何都不应该被销毁。
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;
}
}
这个 MySceneManager
是一个 static class
,因此它没有附加到任何游戏对象或场景,只是 Assets
中的 "lives"。您现在可以使用
从任何地方调用它
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);
演示
为了演示,我 "faked" 它使用两个简单的脚本
主场景中的这个
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 个场景:
主要内容:如前所述包含
- 主摄像头
- 一个定向光源
LoadFirstScene
测试:包含
- 一个
MoveBall
"Sphere"
LoadNextScene
test2:包含
- 一个移动球"Cube"
LoadNextScene
索引与构建设置匹配(确保 Main
始终处于 0
;))
我现在可以使用 Space 键在 test
和 test2
之间切换。
如果我同时拖动其中一个对象,我可以将它带到下一个场景(但一次只能拖动一个)。我什至可以再次回到第一个场景,例如我可以玩的两个球体对象 ;)
加载新场景时,我 运行 遇到了鼠标拖动不能转移到下一个场景并且在加载新场景时必须重新单击的麻烦。
我希望鼠标点击能够在玩家不注意的情况下无缝地转移到下一个场景,更一般地说,我想知道如何最好地保留某些游戏对象并让它们转移到下一个场景。
本质上,我想要做的是让整个游戏就像一个大场景,玩家可以玩这个场景,但仍然可以分解成更小的场景,以后可以访问或转换成关卡阶段。
提前致谢。
这是我目前使用的代码
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);
}
}
我认为它不起作用的主要原因是您可能在新场景中还有另一个 Camera
。
OnMouseDrag
依赖物理系统在内部使用对象 Collider
和来自 Camera
的光线投射。现在,如果您切换场景,我猜相机会被禁用,因此您的拖动会被中断。
同时使用 LoadScene
而不是 LoadSceneAsync
会导致明显的滞后,也可能与该问题有关。
我有一个可能有点复杂的解决方案,但这是我通常做的:
1。拥有一个全局场景 "MainScene"
此场景包含诸如MainCamera、global lightthning、global manager 组件无论如何都不应该被销毁。
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;
}
}
这个 MySceneManager
是一个 static class
,因此它没有附加到任何游戏对象或场景,只是 Assets
中的 "lives"。您现在可以使用
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);
演示
为了演示,我 "faked" 它使用两个简单的脚本
主场景中的这个
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 个场景:
主要内容:如前所述包含
- 主摄像头
- 一个定向光源
LoadFirstScene
测试:包含
- 一个
MoveBall
"Sphere" LoadNextScene
- 一个
test2:包含
- 一个移动球"Cube"
LoadNextScene
索引与构建设置匹配(确保 Main
始终处于 0
;))
我现在可以使用 Space 键在 test
和 test2
之间切换。
如果我同时拖动其中一个对象,我可以将它带到下一个场景(但一次只能拖动一个)。我什至可以再次回到第一个场景,例如我可以玩的两个球体对象 ;)