2015-03-30 13 views
5

W Unity 5, jaki jest "czysty" sposób zarządzania dynamicznie tworzonymi obiektami gry?Unity 5: Czysty sposób na zarządzanie dynamicznie stworzonym GameObjects

Napisałem komponent (MonoBehavior), który tworzy/niszczy kilka GameObjects. Obiekty są ładowane jako część niestandardowego systemu dostosowywania, który wybiera fragmenty postaci - włosy/ubrania itp. Oznacza to, że są widoczne dla gracza, widoczne w edytorze, ale nie powinny być edytowalne w edytorze. Załadowane obiekty to siatki ze szkieletami.

Skrypt zachowuje się w ten sposób:

  1. Ładunki GameObjects z zasobów (dokładny przedmiot jest określony w scenariuszu, nie są one prefabrykatów)
  2. przymocowanie ich do pewnej części sceny (niekoniecznie do jej własny węzeł)
  3. Usuwa po zniszczeniu.

Usunięcie:

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

utworzenia:

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; 

obsługi Zniszczenie:

void OnDestroy(){ 
    unloadLastResource(); 
} 

To wydaje się działać prawidłowo w edytorze, ale po przełączeniu z trybu gier z powrotem w trybie edito otrzymuję wiele ostrzeżeń:

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

I dostać grono nowych obiektów drzew (te, które mają być usunięte - drzew pochodzą od obiektu, który został załadowany wraz z oryginalnymi „zasobów” i został dołączony) na górnym poziomie sceny hieararchy.

Więc, jak mogę obsługiwać dynamicznie stworzone obiekty gry?

Muszę wiedzieć, które flagi muszę ustawić i jakie kroki należy wykonać, aby upewnić się, że obiekt nie "wycieknie" do sceny i zostanie poprawnie zniszczony po usunięciu komponentu, który je utworzył.

Porady?


Pełna klasa zasada stosowana przez „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(); 
    } 
} 

Odpowiedz

4

Wymyśliłem to.

Problem jest spowodowany tym wierszu:

instance.hideFlags = HideFlags.HideAndDontSave; 

W hierarchii, flaga ta wpływa tylko jeden obiekt i nie wpływa na dzieci obiektu. W rezultacie, jeśli ustawisz tę flagę dla obiektu głównego w hierarchii, a scena zostanie zapisana, root zostanie zniszczony, ale jego dzieci zostaną przekształcone do postaci szeregowej (co oznacza, że ​​pojawią się w inspektorze po ponownym wczytaniu sceny) I dostaniesz mnóstwo ostrzeżenia o wielokrotnym niszczeniu tego samego obiektu.

Aby poradzić sobie z problemem, należy przejść przez całą hierarchię i ustawić flagę dla wszystkich obiektów w hierarchii. Który rozwiązuje problem i eliminuje wszystkie błędy.

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

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

Nie jest jasne, czy dzieje się tak tylko podczas ładowania nieprefabrykatów z dysku, czy ogólnie w przypadku wszystkich obiektów hierarchicznych.

2

Komunikat o błędzie oznacza, że ​​kod będzie tworzyć nieskończoną rekurencję, ponieważ jesteś destorying obiekt od wewnątrz OnDestroy() które - po raz kolejny - połączenia OnDestroy() na tym obiekcie i tak dalej ...

Pozwól mi podzielić ten kawałek kodu, który używam jako klasa bazowa dla wszystkich moich zachowań:

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

    } 
} 

Sztuką jest to, że mogę zebrać List z GameObjects, które są związane z tym MonoBehaviour i powinny zostać zniszczone, jeśli „rodzic” zostanie zniszczony.Podczas korzystania z klasy instantiate tej klasy utworzony obiekt zostanie automatycznie dodany do List, chyba że w tej metodzie ustawiono addLink na .

Może możesz użyć tego lub coś podobnego.

+0

Dzięki za urywek. Obecnie analizuję to i szukam różnic w porównaniu do mojego kawałka kodu (przechowuję również instancję obiektu w zmiennej, więc powinny robić to samo) – SigTerm

+0

Sprawdziłem twój skrypt i odkryłem, że działa dokładnie tak jak kopalnia. Pomyślałem o tym problemie, jeśli jesteś zainteresowany. Zobacz odpowiedź. – SigTerm