Unity 5:管理动态创建的GameObjects的简洁方法

时间:2015-03-30 19:24:02

标签: c# unity3d unity3d-editor

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

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

脚本以这种方式运行:

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

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

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

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

    我得到一堆新的对象树(那些应该被删除的树 - 树来自于与原始“资源”一起加载的对象,并被附加)在场景的顶层hieararchy。

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

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

    么?


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

2 个答案:

答案 0 :(得分:4)

我已经明白了。

问题是由这一行引起的:

instance.hideFlags = HideFlags.HideAndDontSave;

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

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

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

.....
                makeHierarchyHidden (newObject);
.....

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

答案 1 :(得分:2)

错误消息意味着您的代码会创建无限递归,因为您正在OnDestroy()内对一个对象进行破坏,这会再次 - 在此对象上调用OnDestroy()等等...... / p>

让我分享这段代码,我将其用作所有行为的基类:

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

    }
}

这里的诀窍是我收集List GameObjects MonoBehaviour绑定到此instantiate,如果'父'被销毁则应该销毁。{1}}使用此类的List时,创建的对象会自动添加到addLink,除非此方法中false设置为{{1}}。

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