In Unity 5, what is "clean" way to manage dynamically created game objects?
I've written a component (MonoBehavior
) that creates/destroys several GameObjects
. The objects are loaded as part of custom customization system that selects portions of character - hair/clothes, etc. Meaning they're visible to player, visible in editor, but are not supposed to be editable in editor. The objects being loaded are meshes with skeletons.
The script behaves in this fashion:
Deletion:
protected void unloadLastResource(){
if (lastResourceInstance){
if (Application.isEditor){
GameObject.DestroyImmediate(lastResourceInstance);
}
else
GameObject.Destroy(lastResourceInstance);
Resources.UnloadUnusedAssets();
lastResourceInstance = null;
}
}
Creation:
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;
Destruction handler:
void OnDestroy(){
unloadLastResource();
}
That seems to work fine in editor, but when I switch from gaming mode back to edito mode, I get a lot of warnings:
Destroying object multiple times. Don't use DestroyImmediate on the same object in OnDisable or OnDestroy.
UnityEngine.Object:DestroyImmediate(Object)
And I get bunch of new object trees (the ones that are supposed to be deleted - trees originate from object that was loaded along with original "resources" and was attached) at the top level of scene hieararchy.
So, how do I cleanly handle dynamically created gameobjects?
I need to know which flags I need to set, and which steps I should do to ensure that object does not "leak" into scene and is properly destroyed when I delete component that created it.
Advice?
Full base class used by "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();
}
}
I've figured it out.
The problem is caused by this line:
instance.hideFlags = HideFlags.HideAndDontSave;
In hierarchy, this flag affects only ONE object and does not affect object's children. As a result, if you set this flag for the root object in hierarchy and scene gets saved, root will be destroyed, but its children will get serialized (meaning they'll appear in inspector upon scene reload) AND you'll get tons of warnings about destroying same object many times.
To deal with the problem, it is necessary to walk through the entire hierarchy and set the flag to all objects in hierarchy. Which fixes the problem and eliminates all errors.
void makeHierarchyHidden(GameObject obj){
obj.gameObject.hideFlags = HideFlags.HideAndDontSave;
foreach(Transform child in obj.transform){
makeHierarchyHidden(child.gameObject);
}
}
.....
makeHierarchyHidden (newObject);
.....
It is unclear if this only happens when loading non-prefabs from disk, or just in general case with all hierarchical objects.
The error message means that your code would create an infinite recursion, because you are destorying an object from within OnDestroy()
which would - again - call OnDestroy()
on this object and so forth...
Let me share this piece of code which I use as the base class for all my Behaviors:
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;
}
}
}
The trick here is that I collect a List
of GameObjects
which are bound to this MonoBehaviour
and should be destroyed if the 'parent' gets destroyed. When using instantiate
of this class, the created object gets automatically added to the List
, unless addLink
is set to false
in this method.
Maybe you can use this or something similar.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With