【问题标题】:Unity 5: Clean way to manage dynamically created GameObjectsUnity 5:管理动态创建的游戏对象的简洁方式
【发布时间】:2015-03-30 19:24:02
【问题描述】:

在 Unity 5 中,管理动态创建的游戏对象的“干净”方式是什么?

我编写了一个组件 (MonoBehavior) 来创建/销毁多个 GameObjects。这些对象作为自定义自定义系统的一部分加载,该系统选择角色的部分 - 头发/衣服等。这意味着它们对玩家可见,在编辑器中可见,但不应该在编辑器中可编辑。正在加载的对象是带有骨架的网格。

脚本以这种方式运行:

  1. 从资源中加载游戏对象(确切的对象在脚本中确定,它们不是预制件)
  2. 将它们附加到场景的某些部分(不一定附加到它自己的节点)
  3. 销毁时删除。

删除:

protected void unloadLastResource(){
    if (lastResourceInstance){
        if (Application.isEditor){
            GameObject.DestroyImmediate(lastResourceInstance);
        }
        else
            GameObject.Destroy(lastResourceInstance);
        Resources.UnloadUnusedAssets();
        lastResourceInstance = null;
    }
}

创作:

    GameObject target = getEffectiveTargetNode();
    Object resource = Resources.Load(newResourceName);
    instance = Instantiate(resource) as GameObject;

    instance.hideFlags = HideFlags.HideAndDontSave;
    instance.transform.parent = target.transform;
    instance.transform.localPosition = Vector3.zero;
    instance.transform.localRotation = Quaternion.identity;
    instance.transform.localScale = Vector3.one;

销毁处理程序:

void OnDestroy(){
    unloadLastResource();
}

这在编辑器中似乎工作正常,但是当我从游戏模式切换回编辑模式时,我收到很多警告:

Destroying object multiple times. Don't use DestroyImmediate on the same object in OnDisable or OnDestroy.
UnityEngine.Object:DestroyImmediate(Object)

我在场景层次结构的顶层获得了一堆新的对象树(那些应该被删除的树——树源自与原始“资源”一起加载并附加的对象)。

那么,我如何干净地处理动态创建的游戏对象?

我需要知道我需要设置哪些标志,以及我应该执行哪些步骤来确保对象不会“泄漏”到场景中并且在我删除创建它的组件时被正确销毁。

建议?


“ResourceLoader”使用的完整基类

public class BaseResourceLoader : MonoBehaviour {
    public GameObject targetNode = null;

    protected GameObject lastTargetNode{
        get{return lastTargetNodeInternal;}
    }
    private GameObject lastTargetNodeInternal = null;
    protected bool targetNodeChanged(){
        return targetNode != lastTargetNode;
    }
    protected string lastResourceName{
        get{return lastResourceNameInternal;}
    }
    private string lastResourceNameInternal = "";
    //private Object lastResource;
    private GameObject lastResourceInstance;

    protected GameObject getEffectiveTargetNode(){
        if (targetNode == null)
            return this.gameObject;
        return targetNode;
    }

    public void reloadResource(){
        loadNewResource(lastResourceNameInternal, true);
    }

    protected void unloadLastResource(){
        if (lastResourceInstance){
            if (Application.isEditor){
                GameObject.DestroyImmediate(lastResourceInstance);
            }
            else
                GameObject.Destroy(lastResourceInstance);
            Resources.UnloadUnusedAssets();
            lastResourceInstance = null;
        }
        lastResourceNameInternal = "";
    }

    protected void loadNewResource(string newResourceName, bool forceReload){
        if ((newResourceName == lastResourceNameInternal) && !forceReload)
            return;

        GameObject instance = null;
        if (newResourceName != ""){
            GameObject target = getEffectiveTargetNode();
            Object resource = Resources.Load(newResourceName);
            instance = Instantiate(resource) as GameObject;

            instance.hideFlags = HideFlags.HideAndDontSave;
            instance.transform.parent = target.transform;
            instance.transform.localPosition = Vector3.zero;
            instance.transform.localRotation = Quaternion.identity;
            instance.transform.localScale = Vector3.one;
        }

        unloadLastResource ();

        lastResourceInstance = instance;
        lastResourceNameInternal = newResourceName;
        lastTargetNodeInternal = targetNode;
    }

    void OnDestroy(){
        unloadLastResource();
    }
}

【问题讨论】:

    标签: c# unity3d unity3d-editor


    【解决方案1】:

    我想通了。

    问题是由这一行引起的:

    instance.hideFlags = HideFlags.HideAndDontSave;
    

    在层次结构中,此标志仅影响 ONE 对象,不影响对象的子对象。结果,如果您为层次结构中的根对象设置此标志并保存场景,根将被销毁,但其子项将被序列化(意味着它们将在场景重新加载时出现在检查器中)并且您将获得大量关于多次破坏同一个对象的警告。

    要处理这个问题,需要遍历整个层次结构,并为层次结构中的所有对象设置标志。这解决了问题并消除了所有错误。

    void makeHierarchyHidden(GameObject obj){
        obj.gameObject.hideFlags = HideFlags.HideAndDontSave;
        foreach(Transform child in obj.transform){
            makeHierarchyHidden(child.gameObject);
        }
    }
    
    .....
                    makeHierarchyHidden (newObject);
    .....
    

    目前尚不清楚这是否仅在从磁盘加载非预制件时发生,或者只是在所有分层对象的一般情况下发生。

    【讨论】:

      【解决方案2】:

      错误消息意味着您的代码将创建一个无限递归,因为您正在从OnDestroy() 中破坏一个对象,这将再次调用该对象上的OnDestroy() 等等...

      让我分享一下我用作所有行为的基类的这段代码:

      using UnityEngine;
      using System.Collections.Generic;
      
      namespace de.softfun.drawntogether {
          public class EnhancedBehavior : MonoBehaviour {
              private List<GameObject> linkedObjects = new List<GameObject>();
      
              protected void destroy(params GameObject[] objects) {
                  foreach (GameObject o in objects) {
                      try {
                          Destroy(o);
                      } catch {
                          continue;
                      }
                  }
              }
      
              public void LinkObjects(params GameObject[] objects) {
                  foreach (GameObject o in objects) {
                      linkedObjects.Add(o);
                  }
              }
      
              void OnDestroy() {
                  foreach (GameObject o in linkedObjects) {
                      destroy(o);
                  }
              }
      
              protected T instantiate<T>(T prefab, bool addLink = true) where T : Object {
                  T o =  (T)Instantiate(prefab);
                  if (addLink && (o is GameObject)) {
                      linkedObjects.add(o);
                  }
                  return o;
              }
      
          }
      }
      

      这里的诀窍是我收集了一个GameObjectsList,它们绑定到这个MonoBehaviour,如果'父'被破坏,应该被破坏。当使用该类的instantiate 时,创建的对象会自动添加到List,除非在此方法中将addLink 设置为false

      也许你可以使用这个或类似的东西。

      【讨论】:

      • 感谢 sn-p。我目前正在分析它并寻找与我的代码相比的差异(我还将对象实例存储在变量中,所以它们应该做同样的事情)
      • 我调查了你的脚本,发现它和我的完全一样。不过,如果您有兴趣,我已经解决了这个问题。查看答案。
      猜你喜欢
      • 2019-07-26
      • 1970-01-01
      • 1970-01-01
      • 2016-11-03
      • 1970-01-01
      • 1970-01-01
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多