I needed to find inactive objects in Unity3D using C#.
I have 64 objects, and whenever I click a button then it activates / inactivates objects for the corresponding button at runtime. How can I find inactive objects at this time?
Since Unity 2020
In the years since this question was asked, Unity put in the exact thing you need. At least, the exact thing I needed. Posting here for future peoples.
To find an object of a certain type whether it's on an active or inactive GameObject, you can use FindObjectsOfType<T>(true)
Objects attached to inactive GameObjects are only included if inactiveObjects is set to true.
Therefore, just use it like you regularly would, but also pass in true.
The following code requires System.Linq:
SpriteRenderer[] onlyActive = GameObject.FindObjectsOfType<SpriteRenderer>();
SpriteRenderer[] activeAndInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true);
// requires "using System.Linq;"
SpriteRenderer[] onlyInactive = GameObject.FindObjectsOfType<SpriteRenderer>(true).Where(sr => !sr.gameObject.activeInHierarchy).ToArray();
The first array includes only SpriteRenderers on active GameObjects, the second includes both those on active and inactive GameObjects, and the third uses System.Linq to only include those on inactive GameObjects.
See this answers for Unity 2020 and higher.
Before Unity 2020
Well, using GameObject.Find(...) will never return any inactive objects. As the documentation states:
This function only returns active gameobjects.
Even if you could, you'd want to keep these costly calls to a minimum.
There are "tricks" to finding inactive GameObjects, such as using a Resources.FindObjectsOfTypeAll(Type type) call (though that should be used with extreme caution).
But your best bet is writing your own management code. This can be a simple class holding a list of objects that you might want to find and use at some point. You can put your object into it on first load. Or perhaps add/remove them on becoming active or inactive. Whatever your particular scenario needs.
If you have parent object (just empty object that plays role of a folder) you can find active and inactive objects like this:
this.playButton = MainMenuItems.transform.Find("PlayButton").gameObject;
MainMenuItems - is your parent object.
Please note that Find() is slow method, so consider using references to objects or organize Dictionary collections with gameobjects you need access very often
Good luck!
For newer Unity versions this answer provides probably a better solution!
First of all
In general any usage of Find or it's variants should be avoided.
Actually they are never really required but only a "hot-fix" used to cover an implementation "laziness".
Usually from the beginning storing and passing on required references is always the better approach.
Especially in your case you seem to have a fix amount of objects so you could probably already reference them all in a certain "manager" component and store them in a list or array (them you can get a reference by index) or even a Dictionary<string, GameObject> (then you can also get the according reference by name - you can find an example below).
Workarounds
There are alternative solutions (FindObjectsWithTag, FindObjectsOfType) but it will always be quite expensive (though most of the Find variants are expensive anyway).
You could e.g. also "manually" iterate through all objects in the scene using Scene.GetRootGameObjects
Returns all the root game objects in the Scene.
And then search through them until you find your object. This way you get also inactive GameObject.
public static GameObject Find(string search)
{
var scene = SceneManager.GetActiveScene();
var sceneRoots = scene.GetRootGameObjects();
GameObject result = null;
foreach(var root in sceneRoots)
{
if(root.name.Equals(search)) return root;
result = FindRecursive(root, search);
if(result) break;
}
return result;
}
private static GameObject FindRecursive(GameObject obj, string search)
{
GameObject result = null;
foreach(Transform child in obj.transform)
{
if(child.name.Equals(search)) return child.gameObject;
result = FindRecursive (child.gameObject, search);
if(result) break;
}
return result;
}
But ofcourse this should be strongly avoided and the usage of such deep searches reduced to a minimum!
What I would do
Another way - in my eyes the best approach here - could be to have a certain component attached to all your objects and actually store all the references once as said before in a dictionary like e.g.
public class FindAble : MonoBehaviour
{
private static readonly Dictionary<string, GameObject> _findAbles = new Dictionary<string, GameObject>();
public static GameObject Find(string search)
{
if(!_findAbles.ContainsKey(search)) return null;
return _findAbles[search];
}
private IEnumerator Start()
{
// Wait one frame
// This makes it possible to spawn this object and
// assign it a different name before it registers
// itself in the dictionary
yield return null;
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
yield break;
}
_findAbles.Add(name, gameObject);
}
private void OnDestroy ()
{
if(_findAbles.ContainsKey(name))
{
_findAbles.Remove(name);
}
// Optionally clean up and remove entries that are invalid
_findAbles = _findAbles.Where(kvp => kvp.Value).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
}
and then use it like
var obj = FindAble.Find("SomeName");
if(obj)
{
// ...
}
Also for this the component would need to be enabled at least once so Start is called.
Again an alternative would be to have instead a
public void Initialize(string newName)
{
if(_findAbles.ContainsKey(name))
{
Debug.LogError($"Another object with name /"{name}/" is already registered!", this);
return;
}
name = newName;
_findAbles.Add(name, gameObject);
}
which you could call also after e.g. spawning an inactive object.
You can use Predicates.
Just get the gameObjects and check them whith a Predicate as below:
public List<GameObject> FindInactiveGameObjects()
{
GameObject[] all = GameObject.FindObjectsOfType<GameObject> ();//Get all of them in the scene
List<GameObject> objs = new List<GameObject> ();
foreach(GameObject obj in all) //Create a list
{
objs.Add(obj);
}
Predicate inactiveFinder = new Predicate((GameObject go) => {return !go.activeInHierarchy;});//Create the Finder
List<GameObject> results = objs.FindAll (inactiveFinder);//And find inactive ones
return results;
}
and don't forget using System; using System.Collections.Generic;
You can do this at runtime by having your inactive gameobject under an active parent object as previously mentioned; slightly different from what was mentioned, this is an approach I've used for activating/deactivating menus that should be inactive by default:
canvas = GameObject.FindGameObjectWithTag("GameMenu").GetComponentInChildren<Canvas>().gameObject;
Now you can change its activeSelf to toggle it in a method/event listener of your choice:
canvas.SetActive(!canvas.activeSelf);
Even while it is inactive, you can still use the tag property of it and use it for a filter, if getting multiple components of the same type. I haven't tested this using GetComponentsInChildren, but you could probably use a 'Single' linq query, and get the object by tag name which would require creating a tag for every gameobject you want to do this to.
Although its not the correct answer, but this is what I did in my case.
1) Attach a script to (inactive) game objects and instead of setting then inactive keep it active.
2) Position them out of the scene somewhere.
3) Set a flag in the script which says inactive.
4) In Update() check for this inactive flag and skip function calls if false.
5) When needed the object, position it at the proper place and set the flag active.
It will be a bit of a performance issue but that's the only workaround I could think of so far.
Related
I have 2 Targets in the world that I want to add to a list when the Player comes close to them.
I first use the Physics OverlapBox method to return an array of colliders. After this, I run a for loop in which the 2 targets should get added to the list. Only 2 target objects are in the scene but the list gets occupied with hundreds of copies of those objects.
Code Down below
private void TrySelectTarget(bool switchInput)
{
targetArray = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore);
for (int i = 0; i < targetArray.Length ; i++)
{
if (targetArray[i].TryGetComponent<Target>(out Target target))
{
availableTargets.Add(target);
}
}
}
I did a deblug.Log on targetarray.Length and it returned 2, so I don't understand why so many objects are being added to the availableTargets List.
I am calling the TrySelectTarget() method in Update().
I am new to c# and programming, so apologies if I am making a stupid mistake.
Thank you for the help.
With your code everytime Physics.OverlapBox returns hits you add the same objects that have already references stored in the list, again. To simply solve the issue of not having duplicates being stored you should check if an object is already referenced (has an entry in the list). Do that by doing:
if (targetArray[i].TryGetComponent<Target>(out Target target))
{
if (!availableTargets.Contains(target)) availableTargets.Add(target);
}
That will not solve the issue of targets not being removed when not in range anymore though. If that is needed then you should change your code so that the list gets cleared before any new references are being added. You could do:
availableTargets.Clear();
targetArray = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore);
for (int i = 0; i < targetArray.Length ; i++)
...
The better solution to solve for this problem in general is to make use of OnTriggerEnter() and OnTriggerExit() messages provided through a Rigidbody component. Those Methods only get invoked if at least one of the interacting objects has a Rigidbody component. Add a Rigidbody to your player object and a collider with the size of the detection range/zone size and set this collider to be IsTrigger. If you dont want that physics affects the object just check the IsKinematic option on the Rigidbody component. In a script on the player then do:
private void OnTriggerEnter (Collider other)
{
if (other.TryGetComponent<Target>(out Target target))
{
// check if not present already to be 100% sure not to get duplicates
// even though it generally shouldn't be happening, better safe than sorry
if (!availableTargets.Contains(target))
{
availableTargets.Add(target);
}
}
}
private void OnTriggerExit (Collider other)
{
if (other.TryGetComponent<Target>(out Target target))
{
availableTargets.Remove(target);
}
}
I think you either want to
availableTargets.Clear();
first so you start with an empty list every time, as anyway you seem to only be interested in the targets overlapping in that instance.
Or you could use Link and do e.g.
using System.Linq;
...
private void TrySelectTarget(bool switchInput)
{
availableTargets = Physics.OverlapBox(transform.position, range, Quaternion.identity, targetLayer, QueryTriggerInteraction.Ignore)
.Select(col => col.GetComponent<Target>())
.Where(target => target)
.ToList();
}
I'm trying to both align my object with the surrounding objects and have the object try and get the average position of it's surrounding objects,I am trying to do this by creating a list of all the objects that enter a sphere and using there transform to do the calculations however unity only accepts lists of colliders. I would really appreciate some help or advice, further info is that this script is on 200 clones of the same gameobject and the script below gives the error
Cannot implicitly convert type 'UnityEngine.Collider[]' to 'System.Collections.Generic.List'
IEnumerator Flock()
{
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
foreach (Collider Boid in NearbyBoids)
{
List<Transform> context = NearbyBoids;
}
yield return null;
}
To reduce the number of iterations your Boids have to make, it might make sense to just have them keep a context list of other Boids that enter or leave a trigger volume you set up in the inspector as an "area of sight". Then you can have each Boid evaluate it's own up to date context in Update.
Something along the lines of:
using System.Collections.Generic;
using UnityEngine;
public class Boid : MonoBehaviour
{
private List<Transform> context = new List<Transform>();
private void OnTriggerEnter(Collider other)
{
// good if you want to call the other Boid component
Boid boid = other.gameObject.GetComponent<Boid>();
if (boid != null)
{
context.Add(other.transform);
}
}
private void OnTriggerExit(Collider other)
{
// Pretty efficient, requires tagging of boid objects
if (other.CompareTag("boidTag"))
{
context.Remove(other.transform);
}
}
private void Update()
{
foreach(Transform otherBoid in context)
{
// doing some stuff here based on boids within context
}
}
}
Two ways to go about this.
Quick way is to use LINQ. To do this you need the using namespace declaration
using System.Linq;
Collider[] arrayOfNearbyTransforms = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
List<Transform> listOfAllNearbyTransforms = arrayOfNearbyTransforms.Select(x => x.transform).ToList();
The issue with the code you've posted is that you're creating a list within your loop. Variables declared within a loop only exist within that loop's execution, so you're essentially creating as many lists as there are colliders, while using none of them.
Instead, you should create the list outside your loop, and add transform components from within the loop
void Flock()
{
// This is the array of colliders you've gathered from nearby objects
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
// This is a brand new list. It's empty at the moment
List<Transform> listOfAllNearbyTransforms = new List<Transform>();
// We're looping through every collider in the array.
foreach (Collider currentColliderReference in NearbyBoids)
{
// Everything that happens in here happens once for every collider.
// The variable currentColliderReference refers to the collider we're looking at during this part of the loop. So throughout the loops execution, it will change to refer to every collider in your array, one at a time.
// We get a reference to the current collider's transform component
Transform transformOfCurrentCollider = currentColliderReference.transform;
// We add that to the list of transform component
listOfAllNearbyTransforms.Add(transformOfCurrentCollider);
}
// At this point listOfAllNearbyTransforms will be a list of all transforms within the area specified in your OverlapSphere() call
}
As you can tell, I've also changed the return type to void. There's no reason for this function to be a coroutine
While I wouldnt do this as a CoRoutine at this stage, Ive kept your code as similar as possible. Unless you really imagine a lot of units 1000's the longest part of the code to run is the overlapsphwere.
List<Transform> context = new List<Transform>();
IEnumerator Flock()
{
context.Clear(); // if there are 1000s this could be costly
Collider[] NearbyBoids = Physics.OverlapSphere(BoidVec, VisionRange, BoidMask, QueryTriggerInteraction.Collide);
foreach (Collider Boid in NearbyBoids)
{
context.Add(NearbyBoids.transform);
}
yield return null;
}
this gives you a class level list of context, which you can access from elsewhere.
Then your context list will have current transforms, but I wouldnt want to run this too often, personally Id use a list and triggers... So, add to list onenter, and remove from list onleave..
Usually you would instantiate a clone of a prefab in a script using
var obj = (GameObject)Instantiate(....);
and than change it's parent using
var obj.transform.SetParent(...);
However I'm writing on a little editor script for instantiating prefabs over the menu entries that should keep their link to the prefab. I do it in a static class like:
public static class EditorMenu
{
// This is a reference that I already have and is not null
privtae static Transform exampleParent;
[MenuItem("Example/Create Clone")]
private static void CreateClone()
{
var prefab = AssetDatabase.LoadAssetAtPath("Example/Path/myObject.prefab", typeof(GameObject));
var obj = (GameObject)PrefabUtility.InstantiatePrefab(prefab);
obj.transform.position = Vector3.zero;
obj.transform.rotation = Quaternion.identity;
Selection.activeGameObject = obj;
}
}
I had to use
(GameObject)PrefabUtility.InstantiatePrefab(prefab);
because Instantiate() doesn't keep the link to the prefab but creates a clone.
So the above code works great so far and the object is inserted to the scene as if I would drag and drop it.
Now I would like to change this newly instantiated objects parent to parent so I added the line
obj.transform.SetParent(exampleParent, true);
I also tried
obj.transform.parent = exampleParent;
But both throw an exception:
Setting the parent of a transform which resides in a prefab is disabled to prevent data corruption.
UnityEngine.Transform:SetParent(Transform)
prefab and thereby obj are the topmost GameObject of the prefab so I thought I am setting the parent of the whole instantiated object not any transform inside of the prefab hierachy.
How can I change the parent of a GameObject instantiated via PrefabUtility.InstantiatePrefab?
Update
A workarround I just tried was to actually use Instantiate and do
var obj = Object.Instantiate(prefab, cancel.transform);
// remove the added "(clone)" suffix
obj.name = prefab.name;
obj.transform.SetParent(cancel.transform);
however as mentioned before this doesn't completely keep the prefb functionality intact .. so it only allows me to Revert but not to Apply changes. As workaround it might be good enough however since in my case I use it as a shortcut for instantiating a prefab which users are not really supposed to change later...
Sorry I just found the issue:
I didn't add it since the error message was confusing/bad formulated.
I was using Resources.FindObjectsOfTypeAll for checking if the exampleParent of type SomeType was in the scene.
Following Unity's example which should exclude prefabs I used
// Validation for the Menu item
[MenuItem("Example/Create Clone", true]
private bool TargetAvailable()
{
foreach (var target in (SomeType[])Resources.FindObjectsOfTypeAll(typeof(SomeType))
{
if (target.hideFlags == HideFlags.NotEditable || target.hideFlags == HideFlags.HideAndDontSave)
continue;
if (!EditorUtility.IsPersistent(target.transform.root.gameObject))
continue;
exampleParent = target.transform;
return true;
}
exampleParent = null;
return false;
}
But this seems actually to be wrong and not working since it allways returned me the SomeType reference from the prefabs! (I already found it a bit strange that they do
!EditorUtility.IsPersistent(target.transform.root.gameObject))
continue;
I'm not sure if that ! maybe is a type in their example code?!
So the error which sounds like setting the parent was not allowed actually means and should say
setting the parent to a Transform which resides in a prefab is not allowed ...
than I would have found the actual issue in the first place.
So again as a workaround until I can figure out that FindObjectsOfTypeAll thing I switched to Object.FindObjectOfType instead assuming my target will always be active in the scene. And using SetParent works now together with PrefabUtitlity.InstantiatePrefab.
How can I change the parent of a GameObject instantiated via PrefabUtility.InstantiatePrefab?
This is right, but make sure cancel is also an instantiated object.
obj.transform.parent = cancel.transform;
I am working on a AR project, where the virtual objects will be shown/hide in the scene based on information found in a text file. The text files will be updated from an external service. So I need to read the file on a frequent interval and update the scene. As a result I only have the Camera object and I am rendering the scene in OnPreCull() method.
The text files contain many objects but not all the objects are within the scene at any instance of time. I was looking for a way to render only those objects that are within the scene.
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Will creating and placing the gameobjects in the OnPreCull() method crate any performance issue?
Yes absolutely ... so would it if you do it in Update or any other repeatedly called method.
Instead you should rather Instantiate objects in Awake and only activate or deactivate them.
Let's say you have 3 objects A, B and C than I would make a kind of controller class that looks like
public class ObjectsController : MonoBehaviour
{
// Define in which intervals the file should be read/ the scene should be updated
public float updateInterval;
// Prefabs or simply objects that are already in the Scene
public GameObject A;
public GameObject B;
public GameObject C;
/* Etc ... */
// Here you map the names from your textile to according object in the scene
private Dictionary<string, GameObject> gameObjects = new Dictionary<string, gameObjects>();
private void Awake ()
{
// if you use Prefabs than instantiate your objects here; otherwise you can skip this step
var a = Instantiate(A);
/* Etc... */
// Fill the dictionary
gameObjects.Add(nameOfAInFile, a);
// OR if you use already instantiated references instead
gameObjects.Add(nameOfAInFile, A);
}
}
private void Start()
{
// Start the file reader
StartCoroutine (ReadFileRepeatedly());
}
// Read file in intervals
private IEnumerator ReadFileRepeatedly ()
{
while(true)
{
//ToDo Here read the file
//Maybe even asynchronous?
// while(!xy.done) yield return null;
// Now it depends how your textile works but you can run through
// the dictionary and decide for each object if you want to show or hide it
foreach(var kvp in gameObjects)
{
bool active = someConditionDependingOnTheFile;
kvp.value.SetActive(active);
// And e.g. position it only if active
if (active)
{
kvp.value.transform.position = positionFromFile;
}
}
// Wait for updateInterval and repeat
yield return new WaitForSeconds (updateInterval);
}
}
If you have multiple instances of the same prefab you also should have a look at Object Pooling
I'd recommend adding each of the game objects to a registry and the switching them on or off (dis/enable SetActive) via the registry class's Update() cycle.
One Update() process to retrieve and handle the server file, another Update() process to dis/enable objects. Might sound oversimplified however it's the fastest way I think of getting the result.
Good Luck!
Thanks for help in advance. Here is a short snippet of the code that I am having an issue with.
GameObject[] allMotor_array;
public List<GameObject> BrokenMotor_list = new List<GameObject>();
void Start()
{
allMotor_array = GameObject.FindGameObjectsWithTag ("Motors");
}
void Update()
{
foreach (GameObject motor in allMotor_array)
{
if(motor.GetComponent<Pump_event>().damaged)
{
BrokenMotor_list.Add(motor);
}
}
}
I have an array of Gameobjects that is created on Start, each of the gameobjects in the array have a script called Pump_event. What I want to do is add the gameobject with a true boolean (damaged) to the list so that I can create a GUI list of all the motors that are damaged (and then take further action on those motors).
With the current code it instantiates the array fine, but when One of the motors boolean changes to true the list tends to continuously add the motor gameobject to the list on each update cycle. So what I want is to figure out a way of adding the gameobject to the list ONCE.
Having it in the update() is probably not the best method but I really am stuck on how to approach this.
G
The Solution to my problem
Thanks for your answers, you all had well thought out responses. I appreciate it. I didn't go with 1 persons method but instead adapted logical approaches found here to work with my script/s.
Here is what I did.
In my pump_event script the events are sorted in a Case and switch as damage increased on the pump the event would escalate. So I added in a section to that script to include "reporting" the damage.
public class Pump_event : MonoBehaviour
//The damage has taken place and event_category=0\\
switch (event_category)
{
case 0:
Master_script.GetComponent<Control_room>().AddtoList (gameObject);
event_category = 1;
break;
I took advice not to insert these types of programing and placed it into its separate class which works out well.
public class Master_script: MonoBehaviour
public void AddtoList(GameObject motor_tobadded)
{
BrokenMotor_list.Add(motor_tobadded);
}
This also eliminated the need on having an array holding all of the pump event controllers as well.
Now the script all works fine. It may not be most efficient but it is doing its job.
Thank you again to all that helped.
In your Pump_event Script you can have a event Action which you register in this snippet and whenever damaged is set true you need to fire the event.
Example:
// in Pump_event Class
public static event Action<GameObject> OnDamagedValueChanged;
private bool _damaged;
public bool Damaged
{
get { return _damaged;}
set
{
_damaged = value;
if(_damaged)
{
if(OnDamagedValueChanged != null)
OnDamagedValueChanged(gameObject);
}
}
}
In your Current Class where you have array of GameObjects:
void OnEnable()
{
Pump_event.OnDamagedValueChanged += HandleOnDamagedValueChanged;
}
void OnDisable()
{
Pump_event.OnDamagedValueChanged -= HandleOnDamagedValueChanged;
}
void HandleOnDamagedValueChanged(GameObject obj)
{
if (!BrokenMotor_list.Contains (obj))
{
BrokenMotor_list.Add (obj);
}
}
Using Actions is a better approach than doing it in Update Method. It is not good for performance to keep checking for a bool in iteration in update method. and try to avoid GetComponent and Find/FindObjectWithTag Methods in Update. It is not good practice. I hope this is helpful.
According to the code you have posted, the problem lies within the fact that the damaged property is never reset. One solution would be to reset this property once you add it to the list, like so:
if(motor.GetComponent<Pump_event>().damaged)
{
motor.GetComponent<Pump_event>().damaged = false;
BrokenMotor_list.Add(motor);
}
However, multiple copies of the same object could still be added to your list if the motor is damaged again.
To go around this, you could use a HashSet. The hash set will allow only one copy of an object to exist within it, thus, if an object is already present is will not be added again.
The catch is that you will need to override the GetHashCode and Equals methods for your GameObject class since these will be used internally by the hash set to place items within itself and identify duplicates.
check if list already contains motor.
if(motor.GetComponent<Pump_event>().damaged)
{
if(BrokenMotor_list.Contains(motor))
{
BrokenMotor_list.Add(motor);
}
}
although on msdn describes how to implement IEquatable in case if you want compare different objects(with different references) https://msdn.microsoft.com/ru-ru/library/vstudio/bhkz42b3%28v=vs.100%29.aspx
if (!BrokenMotor_list.Contains (motor)) {
BrokenMotor_list.Add (motor);
}
You'd better do this after damage event occur by add a delegate.