Unity 5: Clean way to manage dynamically created GameObjects - c#

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:
Loads GameObjects from Resources (exact object is determined in script, they are not prefabs)
Attaches them to some portion of the scene (not necessarily to its own node)
Deletes when destroyed.
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.

Related

Null reference on object after scene change

I am making a multiplayer game in which the characters can change scenes. I have items scattered in different scenes which the player can pick up and add to his inventory. The items have an 'AddItem' script attached to them. If the player is in the first scene and picks up the item, it is added to the inventory, however when the player changes the scene and picks up an item in that scene (which is using same script), it throws null reference error. Here is the script:
public class AddItem : MonoBehaviour
{
public Item item;
DialogueConditions dc;
private GameObject player;
private void Start()
{
player = GameObject.Find("Engineer");
dc = player.GetComponent<DialogueConditions>();
}
void pickUp()
{
Debug.Log("Picking up : " + item.name);
HasBriefDoc();
bool itemPickedUp = Inventory.instance.Add(item);
if(itemPickedUp == true)
{
Destroy(gameObject);
}
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
pickUp();
}
}
public void HasBriefDoc()
{
if (item == null)
{
dc.hasBrief = false;
}
else if (item.name == "Brief Doc")
{
dc.hasBrief = true;
}
else
{
dc.hasBrief = false; //throws null reference
}
}
}
After debugging, I found out that after scene change, the object 'dc' is assigned with null reference in the Start() method. I also tried initializing it with FindObjectOfType<DialogueConditions>(); and adding the start code to the Awake() method, but the problem still persists. The only way it is working is when I drop the 'dc' object and directly use static variables, which is something I do not prefer.
To anyone who is facing this issue, my problem was occurring due to wrong execution order of functions. The 'AddItem' script was running before my player had even instantiated, on which this DialogueConditions script was attached. That is why it was returning null reference. I changed the order of execution by instantiating my player in the Awake() method, as it will then run before the Start() method of AddItem, hence the null reference exception was removed.

Grab objects in Unity like half life 2

I am doing a little, but I do not really understand the scripts. I want grab objects ingame like portal or half life 2 and I was trying to code a little script for that action. I made this:
using System.Collections.Generic;
using UnityEngine;
using System;
public class PIckUp : MonoBehaviour {
public Transform theDest;
void OnKeyDown()
{
GetComponent<Rigidbody>().useGravity = false;
this.transform.position = theDest.position;
this.transform.parent = GameObject.Find("Destination").transform;
}
void OnKeyUp()
{
this.transform.parent = null;
GetComponent<Rigidbody>().useGravity = true;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
OnKeyDown();
}
if (Input.GetKeyUp(KeyCode.F))
{
OnKeyUp();
}
}
}
I attach this script to empty object called Destination, when I add a component in grabbable object in "theDest" I target Destination. If i press F all objects are flying i no need aim to them and deform all boxes too i leave here a picture.
I dont find a easy solution, i follow this scripts but are to complex for me and i dont understand how use them or how attach them to a player or object.
https://answers.unity.com/questions/1459773/picking-upholding-objects-portal-style.html
Well the thing is each and every object in the entire scene with this script attached will react to the key press.
I would rather
put all your objects you want to be able to pick on a certain Layer e.g. "Pickable"
make sure they all have colliders
Never parent Rigidbody, rather use Joints e.g. ConfigurableJoint could be useful here, you can completely customize it and use e.g. it's connectedAnchor in order to force the object to move towards you
have a kinematic rigidbody on "theDest" so we can use it as anchor for the joint
have a collider on "theDest" so that we can check distances between colliders
Additional to the layer I would also use a dedicated script like
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(SpringJoint))]
public class Pickable : MonoBehaviour
{
[SerializeField] private Rigidbody rigidbody;
public Rigidbody Rigidbody => rigidbody;
private void Awake()
{
if(!rigidbody && !TryGetComponnet<Rigidbody>(out rigidbody))
{
Debug.LogError("This component requires a Rigidbody!", this);
}
}
public void MarkActive(bool active)
{
// Your customerhod for marking an object the currently active one
// e.g. change its color, add outline etc
}
}
This fulfills three purposes
allows to check if hit object even is pickable
precached components so we don't need GetComponent over and over again
can implement additional behavior such as the mark active visualization
And then have only one single script on your player and use e.g.
public class PIckUpController : MonoBehaviour
{
// Reference this via the Inspector
[SerializeField] private Rigidbody theDest;
// Adjust this in the Inspector, select your Layer(s) that should be Pickable
[SerializeField] private LayerMask pickableLayer;
// Maximum range for picking objects in units
// only Pickable objects within this range can be grabbed
[SerializeField] private float range = 1;
// How fast to attract picked objects
[SerializeField] private float dragSpeed = 1;
private Pickable currentHit;
private Pickable currentPicked;
private ConfigurableJoint currentJoint;
private void Awake ()
{
if(!theDest) theDest = GameObject.Find("Destination"). GetComponent<Rigidbody>();
}
void PickUp()
{
currentJoint = currentHit.AddComponent<ConfigurableJoint>();
// Todo Completely customizable behavior
currentPicked = currentHit;
}
void Release()
{
Destroy(currentJoint);
// TODO optional give it some impulse to throw it away here e.g.
// (Have to tweak the values of course)
currentPicked.Rigidbody.AddExplosionForce(10, theDest.position, 1f, 0.5f, ForceMode.Impulse));
currentPicked = null;
}
private void Update()
{
// Have we already picked up something?
if(!currentPicked)
{
// No -> we chekc if we are pointing on any object
// within the range
var ray = mainCamera.ScreenPointToRay(Input.mousePosition);
if(Physics.Raycast(ray, out var hit, range, pickableLayer)
{
// yes -> get the Pickable component
if(hit.gameObject.TryGetComponent<Pickable>(out var hitPickable))
{
// Is it a different one than the one we already have?
if(hitPickable != currentHit)
{
// yes -> deselect the current one
if(currentHit)
{
currentHit.MarkActive(false);
}
// And store and select the new one
currentHit = hitPickable;
currentHit.MarkActive(true);
}
}
else
{
// If there is no Pickable component we hit something else
// -> deselect and forget the current hit
if(currentHit)
{
currentHit.MarkActive(false);
currentHit = null;
}
}
}
else
{
// We don't hit anything
// -> deselect and forget the current hit
if(currentHit)
{
currentHit.MarkActive(false);
currentHit = null;
}
}
// Do we have a current hit?
if(currentHit)
{
// yes and pressing F -> pick it up
if (Input.GetKeyDown(KeyCode.F))
{
PickUp();
}
}
}
else
{
// We already have picked up something
// of release F -> release object
if (Input.GetKeyUp(KeyCode.F))
{
Release();
}
else
{
// Todo e.g. make the object move towards the "theDest"
}
}
}
}
This one is the main responsible and does
shoot a Raycast to check which object you are pointing at
can additionally (optional) store and mark the currently hit object (as far as I remember HL2 marks them with an outline)
controls the pickup and release of objects

How to Destroy multiple gameObject at runtime?

In unity3D I am creating and destroying capsule dynamically at run-time. I used space to create capsule and C for destroying.
I want create multiple object and destroy multiple object at time. When I pressed Space multiple times object is creating multiple time it fine.
But the problem is when I pressed C multiple times only one object is destroying. How I can achieve destroying multiple object? One by one.
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DynamicCreate : MonoBehaviour
{
public GameObject caps;
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.Space))
{
createObject();
}
if (Input.GetKeyDown(KeyCode.C))
{
destroyObject();
}
}
private void createObject()
{
caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
}
public void destroyObject()
{
Destroy(caps);
}
}
You can simply create a list of capsules.
Declare a list of capsule like under:
List<GameObject> Capsules;
void Start()
{
Capsules=new List<GameObject>();
}
Add any created capsule to the list:
private void createObject()
{
caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Add(caps);
}
You can then destroy the capsules in the list:
public void destroyObject()
{
foreach(GameObject capsule in Capsules)
{
Destroy(capsule);
}
}
See if that helps.
I would like to add more to bolkay answer. If you want to add and remove one by one best datastructure to use is a queue.
Queue<GameObject> Capsules;
void Start()
{
Capsules = new Queue<GameObject>();
}
private void createObject()
{
GameObject caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Enqueue(caps);
}
public void destroyObject()
{
if(Capsules.Count > 0)
Destroy(Capsules.Dequeue());
}
Queue data structure will give a first in first out. So it will give out first gameobject which you added to the queue at runtime and you wont need a variable to manage the index as in the state of List.
Note: This solution is still very basic but I would recommend you to use object pooling instead of creating and destroying objects at runtime as creating and destorying objects is quite intensive at runtime. So you should unless abosolutely necessary avoid creating and deleting.
Queue<GameObject> pooledObjects;
Queue<GameObject> Capsules;
void Start()
{
pooledObjects = new Queue<GameObject>();
Capsules = new Queue<GameObject>();
}
private void createObject()
{
if(pooledObjects.Count > 0)
{
GameObject fetchedObject = pooledObjects.Dequeue();
fetchedObject.SetActive(true);
//Do translations,scaling or other stuff on this reference
//Add it back to main list of things present in scene.
Capsules.Enqueue(fetchedObject);
} else
{
//Only create objects if necessary
GameObject caps = GameObject.CreatePrimitive(PrimitiveType.Capsule);
Capsules.Enqueue(caps);
}
}
public void destroyObject()
{
//here instead of destorying the object we will just disable them and keep them out if scene so we dont have to recreate them everytime
if(Capsules.Count > 0)
{
GameObject fetchedObject = Capsules.Dequeue();
fetchedObject.SetActive(false);//Turn the object off from scene
pooledObjects.Equals(fetchedObject);
}
}
Here is list of supported datastructures in C# you might want to use depending on your required case.

Error on setting a Pooled GameObject's parent back to pooled container

I have a GameObject that I am spawning from a pool, that when I try to return it to the pool I get the error:
Setting the parent of a transform which resides in a prefab is
disabled to prevent data corruption.
I have used my ObjectPooler in many projects and have never once encountered this error.
I have created simple test GameObjects and it still throws this error, using the below code.
I have a Bomb abstract class. I have a PowerBomb class that inherits from Bomb. Bomb conforms to an interface IExplodable.
When my player spawns a bomb, I put it as the child of the player object so the player can move around with the bomb.
My ObjectPool has the correct bomb gameobject prefab ( a PowerBomb ). and my player has the same prefab as a reference to spawn. They player also holds a reference so it knows what it is holding.
I have spent the better part of 2 days trying to figure out why my PowerBomb will not return to the object pool.
Any help?
Bomb.cs
public abstract class Bomb : MonoBehaviour, IExplodable {
//Interface
public virtual void OnDetonate() {
Die();
}
public virtual void Die() {
Debug.Log("Bomb Die()");
ObjectPool.instance.PoolObject(gameObject);
}
}
PowerBomb.cs
public class PowerBomb : Bomb {
[SerializeField]
private AudioClip sfxSpawn;
}
BombManager.cs
public GameObject bomb;
public void SpawnBomb(){
GameObject obj = ObjectPool.instance.GetObject(bomb);
if (obj == null) return;
obj.SetActive(true);
PlayerInstance.HoldObject(obj);
}
public void DetonateBomb(){
bomb.GetComponent<PowerBomb>().OnDetonate();
}
Player.cs
[SerializeField]
private bool isHolding = false;
public bool IsHolding {
get { return isHolding; }
set { isHolding = value; }
}
private GameObject holdingObject;
public GameObject HoldingObject {
get { return holdingObject; }
set { holdingObject = value;}
}
public void HoldObject(GameObject o) {
HoldingObject = o;
HoldingObject.transform.SetParent(transform);
HoldingObject.transform.position = holdingArea.transform.position;
IsHolding = true;
}
ObjectPool.cs
private GameObject containerObject;
private List<GameObject>[] pooledObjects;
//Code removed for brevity. Getting from the pool is fine.
public void PoolObject(GameObject obj) {
for (int i = 0; i < objectsToPool.Count; i++) {
if (objectsToPool[i].objectToPool.name.Equals(obj.name)) {
obj.SetActive(false);
//THIS LINE IS THROWING THE ERROR
obj.transform.SetParent(containerObject.transform);
pooledObjects[i].Add(obj);
return;
}
}
}
You don't and can't modify a prefab. It might work in the Editor if you use some Editor API to modify it but it won't during run-time and it won't change the changes. You are getting this error because obj is a prefab which you are trying to modify at obj.transform.SetParent(containerObject.transform);.
You don't even use prefabs in a pool. That's not normal. What you do do is use the Instantiate function to instantiate the prefab then put the instantiated object in a pool.
GameObject obj = Instantiate(prefab, new Vector3(i * 2.0F, 0, 0), Quaternion.identity);
//Add instantiated Object to your pool List
pooledObjects[i].Add(obj);

Destroy a deactivated object

I want simply to destroy a deactivated instance of a Quad prefab (hp bar) , am able to destroy activated ones with :
private GameObject correspondingHpBar;
private string correspondingHpBarName;
void Start()
{
correspondingHpBarName = "hpBar1"
}
void Update()
{
correspondingHpBar = GameObject.Find (correspondingHpBarName);
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
}
}
This doesn't work with the deactivated objects, i googled hard but failed to find an answer.
Deactivated object don't have their Start or Update method called (nor any coroutine for that matter). In fact when an object is deactivated it is like its own time is frozen.
What you could do is create a method that does the destruction and find a way to call it from another script (for example a kind of controller that keeps reference to all HP bars in your scene).
The following is some pseudo-code (didn't check if it compiles, but you should adapt it anyway):
// in script for HP bar
public Boolean TryDestroy()
{
if (shipHp <= 0)
{
Destroy (correspondingHpBar);
Destroy (gameObject);
return true;
}
return false;
}
// in another script
private List<HPBar> _allHPBars;
void Awake()
{
_allHPBars = new List<HPBar>(FindObjectsOfType(typeof(HPBar)));
}
void Update()
{
var destroyedHPBars = new List<HPBar>();
foreach (var hpBar in _allHPBars)
{
if (hpBar.TryDestroy())
{
destroyedHPBars .Add(hpBar);
}
}
foreach (var destroyedBar in destroyedHPBars)
{
_allHPBars.Remove(destroyedBar);
}
}

Categories

Resources