Unity3D提供了一个不删除前一个场景中的某一个对象或者脚本的API——DontDestoryOnLoad(对象或者某个脚本)。它是为了在游戏开发中可以创建多个场景,但又不会因为场景过度而删除对象。
DontDestoryOnLoad问题: 问: 在使用DontDestoryOnLoad的时候我们会发现一个很奇怪的问题就是:如果A里面放了个东西o,当到场景B的时候,o 也会出现在B场景中,这里一看,感觉还是对的,然后你再返回到场景A, 你就会惊讶的发现,A里面出现了两个o,然后你到B, 看到B里面也出现了两个o, 再回到A, A里面出现了3个o, 一直递增上去。
这个问题的原因是使用了DontDestroyOnLoad,并且在加载场景时没有对旧场景的物体进行清理。当从场景A切换到场景B时,场景A中被标记为DontDestroyOnLoad的物体会被保留,因此在场景B中仍然存在。当再次返回到场景A时,场景A中原本的物体仍然存在,而且新创建的物体也会被保留,导致出现了多个相同的物体。
解决这个问题的方法是,在场景B加载时,手动清理场景A中的物体。可以在场景切换时使用SceneManager.LoadScene()函数的异步加载机制,并在加载完成后使用SceneManager.UnloadScene()函数来卸载旧场景。同时,对于被标记为DontDestroyOnLoad的物体,可以使用Destroy()函数来手动销毁,以确保在切换场景时它们不会被保留。
以下是一个示例代码,用于在场景切换时对DontDestroyOnLoad的物体进行清理:
using UnityEngine;
using UnityEngine.SceneManagement;
public class SceneController : MonoBehaviour
{
private string previousScene;
void Start()
{
previousScene = SceneManager.GetActiveScene().name;
}
public void LoadScene(string sceneName)
{
StartCoroutine(LoadSceneAsync(sceneName));
}
IEnumerator LoadSceneAsync(string sceneName)
{
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName);
while (!asyncLoad.isDone)
{
yield return null;
}
ClearScene(previousScene);
previousScene = sceneName;
}
void ClearScene(string sceneName)
{
Scene sceneToClear = SceneManager.GetSceneByName(sceneName);
GameObject[] sceneObjects = sceneToClear.GetRootGameObjects();
foreach (GameObject obj in sceneObjects)
{
if (obj.CompareTag("DontDestroyOnLoad"))
{
Destroy(obj);
}
}
SceneManager.UnloadScene(sceneToClear);
}
}
在这个示例代码中,在加载新场景之前,会先清理旧场景。通过标记DontDestroyOnLoad的物体的Tag为“DontDestroyOnLoad”,然后在清理时根据Tag进行筛选并销毁。在场景切换完成后,会卸载旧场景。这样就能解决使用DontDestroyOnLoad时物体会重复出现的问题。
DontDestoryOnLoad使用时还有其它问题吗,解决思路
在使用DontDestroyOnLoad时,可能会遇到以下问题:
多次调用DontDestroyOnLoad:如果在多个场景中多次调用DontDestroyOnLoad方法,会导致物体重复加载。为了解决这个问题,可以使用一个标志变量来标记物体是否已经被加载过,只在第一次调用时使用DontDestroyOnLoad。
内存泄漏:由于DontDestroyOnLoad方法使得物体在场景切换时不被销毁,如果不需要的物体一直存在,可能会造成内存泄漏。解决这个问题的思路是,当物体不再需要时,手动调用Object.Destroy方法来销毁。
层级问题:使用DontDestroyOnLoad后,物体的层级关系可能会与场景中其他物体产生冲突。为了解决这个问题,可以使用一个空物体来作为根节点,将需要保持的物体作为子物体放置在该空物体下。
物体位置和状态问题:DontDestroyOnLoad会使得物体在场景切换时位置和状态保持不变。如果需要重置物体的位置和状态,可以在场景切换前手动设置。
总的来说,DontDestroyOnLoad的使用需要注意物体的加载次数、内存泄漏、层级关系以及位置和状态等问题。合理地处理这些问题,可以正确地使用DontDestroyOnLoad方法。
如果需要重置物体的位置和状态,可以在场景切换前手动设置的代码示例
当你使用 DontDestroyOnLoad 时,如果需要在场景切换前手动重置物体的位置和状态,你可以在合适的时机调用脚本中的函数来实现。以下是一个简单的示例代码,展示如何在场景切换前手动重置物体的位置和状态:
using UnityEngine;
using UnityEngine.SceneManagement;
public class ResetObjectOnSceneChange: MonoBehaviour
{
private Vector3 initialPosition;
private Quaternion initialRotation;
private Vector3 initialScale;
private void Awake()
{
// 在物体被 DontDestroyOnLoad 之前记录初始的位置、旋转和缩放
initialPosition = transform.position;
initialRotation = transform.rotation;
initialScale = transform.localScale;
}
private void Start()
{
// 将物体设置为 DontDestroyOnLoad
DontDestroyOnLoad(gameObject);
// 注册场景切换的回调方法
SceneManager.activeSceneChanged += ResetObject;
}
private void ResetObject(Scene previousScene, Scene newScene)
{
// 在场景切换时重置位置、旋转和缩放
transform.position = initialPosition;
transform.rotation = initialRotation;
transform.localScale = initialScale;
}
}
如何判断物体是否已经DontDestroyOnLoad
要判断一个物体是否已经被设置为 DontDestroyOnLoad,可以使用 Object 的 IsPersistent 方法。以下是一个代码示例:
using UnityEngine;
public class CheckIfDontDestroyOnLoad : MonoBehaviour
{
private void Start()
{
if (IsDontDestroyOnLoad(gameObject))
{
Debug.Log("物体已经设置为 DontDestroyOnLoad。");
}
else
{
Debug.Log("物体没有被设置为 DontDestroyOnLoad。");
}
}
private bool IsDontDestroyOnLoad(GameObject gameObject)
{
return gameObject.scene.buildIndex == -1;
}
}
在这个示例代码中,我们定义了一个 IsDontDestroyOnLoad 方法用于判断物体是否已经被设置为 DontDestroyOnLoad。该方法通过检查物体所在的场景编号(buildIndex)是否为 -1 来判断物体是否为 DontDestroyOnLoad。
Scene-buildIndex
在 Start 方法中,我们调用 IsDontDestroyOnLoad 方法来检查当前物体是否已经被设置为 DontDestroyOnLoad。根据结果输出相应的日志信息。
确保将这个脚本添加到需要进行判断的物体上。当物体被加载到场景中时,它将判断是否已经设置为 DontDestroyOnLoad 并输出相应信息。