Enabling Game objects - c#

So I've been trying to access game objects in my scene (which are disabled), to enable them. But I'm getting an error: "Object reference not set to an instance of an object"
private List<Character> characters = new List<Character>();
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(GameObject.Find(characters[i].CharacterName));
characterGameObjects[i].SetActive(true);
}
}
}

You can't find disabled gameobjects.
A solution is to either reference them in inspector or find them all first when they are enabled, then disable those you don't need.

I think your characters list is empty.
If you don't Instantiate GameObjects you can fill characters list with drag and drop so you can change code like this.
public List<Character> characters = new List<Character>(); ///Thanks to public
//you can see list in Unity Editor
private List<GameObject> characterGameObjects = new List<GameObject> ();
public void loadSceneCharacters(){
if (characters != null) {
for(int i = 0; i < characters.Count; i++){
characterGameObjects.Add(characters[i])); //get and add gameobject
characterGameObjects[i].SetActive(true);
}
}
}
If you have dynamically created GameObjects you can fill the list with GameObject.Find("CharacterName");
However, i dont suggest that find every gameobject with name.Instead of that, During the Instantiate state you can add new gameObject instance to your character list.

Even if the character list would be empty this code would not throw an exception.
The problem is probably that you can not find disabled gameobject using the find methods (at least that was my experience, correct me if i am wrong guys).
What i usually do as a workaround instead of searching is to add the gameobjects via drag and drop. If this is not possible you can either, search for the gameobjects in Awake or Start, add them to your list and disable them. Or do some sort of adding when you instanciate them... Basicly you have to somehow get the reference to the gameobjects before you disable them.
Hope this helps.

Something that can help you is to create an empty object, then put all your characters inside the empty object.. then by code do something like:
foreach(Character c in MyEmptyObject.GetComponentsInChildren(Character, true))//the true in this
//line indicates that it should search for inactive objects
{
c.gameObject.SetActive(true);
}
this assuming that your characters have an script called "Character"

Related

FindObjectsWithTag bringing back objects in random order in Unity?

I'm attempting to make a level select that requires as little upkeep as possible as I intend on adding updates to add more levels (Unity Scenes) in unity.
To account for this I'm attempting to get the level select to create buttons for each level in the Unity Build settings, and then create a template object from a prefab in which it can map buttons it creates onto.
I have it mostly working, but for some reason, it's mapping the buttons in the wrong order, I'm trying to go down to up and Unity appears to be grabbing the Gameobjects is a random order.
Here is my code:
private Scene[] levels;
private int currentButtonId = 1;
public Transform buttonsHolder;
public GameObject buttonPrefab;
public GameObject buttonSlotsPrefab;
private GameObject[] levelButtonSlots;
private int currentLevelSlot = 0;
private int numSlotsToMove = 0;
private void Start()
{
var sceneCount = SceneManager.sceneCountInBuildSettings;
levels = new Scene[sceneCount];
for (var i = 0; i < sceneCount; i++)
{
// Beginning Setup
levels[i] = SceneManager.GetSceneByBuildIndex(i);
// Look for Level Placement Slots
levelButtonSlots = GameObject.FindGameObjectsWithTag("Level Slot");
// If there aren't enough Level Placement Slots make more by creating a template
if(levelButtonSlots.Length < levels.Length)
{
GameObject buttonSlots = Instantiate(buttonSlotsPrefab);
buttonSlots.transform.position = new Vector2(0, 10 * numSlotsToMove);
numSlotsToMove++;
}
// Go get those new placement slots
levelButtonSlots = GameObject.FindGameObjectsWithTag("Level Slot");
// Create Button
GameObject currentButton = Instantiate(buttonPrefab, buttonsHolder);
// Move it to the next slot
currentButton.transform.position = levelButtonSlots[currentLevelSlot].transform.position;
currentLevelSlot++;
// Add Text to a new button
TextMeshProUGUI buttonText = currentButton.GetComponentInChildren<TextMeshProUGUI>();
buttonText.text = (currentButtonId.ToString());
// Setup what which scene clicking a button will do
ButtonManager buttonScript = currentButton.GetComponentInChildren<ButtonManager>();
buttonScript.sceneToLoad = currentButtonId;
currentButtonId++;
}
}
The buttonsHolder variable is set in the editor and is the canvas.
The buttonPrefab is a TextMeshPro button prefab I set in the editor, it has the level Buttons tag and a simple script that loads the specified scene when clicked.
And the buttonSlotsPrefab is a gameobject prefab that I set in the editor, it has the Button Placement Slot tag and it contains 8 other empty gameobjects each with the level slot tag, I use these 8 objects as the guides as to where the buttons should be placed on runtime.
Again, my goal is to place the buttons going from bottom to top, but instead, Unity spits out this on runtime for no apparent reason:
I'm sorry about the naming convention some variables have, I'm tired and stressed as I've been at this 2 two days straight. I will fix those up once I get things working.
After further testing, I have noticed that when I first create buttonSlotPrefab, everything works perfectly, however, after restarting Unity (Not changing any files) when I run the game again after a restart the order gets randomized. Could this be a bug in the Unity Engine?
If i understant your problem, you have a problem between number of button and slot: you have slot spaced on axis y and you havent always the levelsolt[0] at the bottom of screen, so
you could order (ascending or descending i dunno) the levelslot on y axis before creating button: i am using Linq so add using System.Linq;
if (levelButtonSlots.Any())//Same thing than levelButtonSlots.Length > 0
{
levelButtonSlots = GameObject.FindGameObjectsWithTag("Level Slot").OrderBy(go => go.transform.position.y).ToArray();
}
or
levelButtonSlots = GameObject.FindGameObjectsWithTag("Level Slot").OrderByDescending(go => go.transform.position.y).ToArray();
I have added a test if no value in array, but maybe the error doesnt exist, i havent tested that

assign class objects instead of the actual script class to unity game objects in unity c#

I want to create a game in Unity C# in which the player will add as much as drones he needs as game objects to the game world. Therefore, I made a drone prefab and later in runtime I ask the number of drones the player wants to have (e.x. n numbers) and instantiate the prefab n times.
The script which is attached to the drone prefab is called drone_script.
Therefore, I am having a drone_script class as a general class. This class has an attribute (let's call it subscriber) which should have different unique values for each drone. So, it is created as a null reference in drone_script general class and later in runtime, I will initialize it.
During runtime, I create n (the same numbers as the drone game objects) objects from this class and assign their subscriber attribute different values.
This is how it looks:
some_class
{
for (int i = 0; i < number_of_Drones; ++i)
{
drones_script[i] = new drone_script();
\\ here I am creating n objects of my general drone_script class
drones_script[i].drone_subscriber = a unique value;
\\ here I am assigning to each of the drone objects' drone_subscriber attribute a different value.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(Resources.Load("drone"), drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
\\I instatiate the drone prefab in unity game world here.
}
}
drone_script
{
public ROSBridgeSubscriber drone_subscriber;
void Start() { \\some code }
void FixedUpdate () { \\some code }
}
My problem is when the drone game objects are created in unity, the general class drone_script is attached to them and because of this their subscriber attribute is null. However, I need the drone_script[i] object to be assigned to them as their script, not the general drone_script itself. Because only then each drone game object has its corresponding subscriber attribute value from before even the start function is being called.
Am I thinking correctly?
If yes, how can I do it?
First of all, I suggest you to assign dronePrefab using editor or in Start method of "some_class" because Resources.Load("drone") might be resource consuming, since you load a drone resource "n" times.
Now your specific problem, you don't need to create new instances of "drone_script" because as you mentioned yourself, each drone that you create has his own "drone_script". So basically what we need to do is:
Instantiate drone and save his reference (as GameObject)
Get that drone's "drone_script" (using GetComponent<>(); )
Save that script reference in our array so we can use it later
Adjust some variables of that script now or later in the game
Something like that should work. In code everything should be something like this (didn't test, might be some miss types)
some_class
{
public GameObject dronePrefab;
for (int i = 0; i < number_of_Drones; ++i)
{
// Step 1.
drco = new Vector3((float)0, (float)1, (float)0);
drones[i] = Instantiate(dronePrefab, drco,
Quaternion.Euler(-90, 0, 90)) as GameObject;
// Steps 2 & 3
drones_script[i] = drones[i].GetComponent<drone_script>();
// Step 4
drones_script[i].drone_subscriber = a unique value;
}
}
EDIT:
Just to point it out: drone_script has to be attached to drone prefab

Using foreach loop to find GameObjects that are children of something else (Unity3d)

I am making a video game, and while I have an existing code in generic C#, I now have to move it to Unity. I have some basic knowledge with generic C#, but I just started to learn the Unity way of coding.
For starters, I want to write a code that positions all game areas to correct positions, then turn them invisible. Yes, don't be surprised, they need to be all in same places.
Areas can have three size options, I called them Small, Medium and Large. Small and large areas have to be written manually.
List <GameObject> SmallAreas = new List<GameObject>();
void DefineSmallAreas()
{
SmallAreas.Add(areaConfirmLoad);
SmallAreas.Add(areaConfirmQuit);
SmallAreas.Add(areaConfirmSave);
SmallAreas.Add(areaGameSaved);
SmallAreas.Add(areaSave);
SmallAreas.Add(areaLoad);
}
Same with large areas.
Now, all other areas, are medium, and there is a large number of them.
So, I want to go through all game objects that are children of "areaContainer", check if their names start with "area", and if they do, I want to add them to MediumAreas list.
That's how I tried it:
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (GameObject thisObject in areaContainer)
{
char[] a = thisObject.Name.ToCharArray();
if (a.Length >= 4)
{
char[] b = { a[0], a[1], a[2], a[3] };
string thisObjectType = new string(b);
(if (thisObjectType == "area")&&(!(SmallAreas.Contains(thisObject))
&&(!(LargeAreas.Contains(thisObject)))
{
MediumAreas.Add(thisObject);
}
}
}
This however shows an error, that "areaContainer" can't be used that way, I don't have access to Unity now, so can't copy exact message. I think that it's something like "Gameobject doesn't have IEnumerator".
I did try to google for the better approach, and found something called "transform".
foreach(Transform child in transform)
{
Something(child.gameObject);
}
What I don't understand, is how to use this "transform" in my specific situation.
Please don't get angry at me if this question is silly, I am very new to Unity, and have to learn it from scratch.
And a small second question. Will this work of turning object invisible work:
foreach(GameObject thisObject in MediumAreas)
{
thisObject.position = MediumVector;
thisObject.GetComponent<Renderer>().enabled = false;
}
MediumVector is location where the object must be moved to, and it seems to be working.
You can do this: foreach(Transform child in transform)
because the Transform class implements IEnumerable and have some mechanism that enables you to access the child GameObjects with the foreach loop.
Unfortunately, you can't do this: foreach (GameObject thisObject in areaContainer)
because areaContainer is a GameObject and this implementation is not done for the GameObject class. That's why you are getting this error:
foreach statement cannot operate on variables of type
'UnityEngine.GameObject' because 'UnityEngine.GameObject' does not
contain a public definition for 'GetEnumerator'
To fix it, change your loop to use Transform after finding the GameObject:
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform){}
The complete code:
List<GameObject> MediumAreas = new List<GameObject>();
void DefineMediumAreas()
{
GameObject areaContainer = GameObject.Find("areaContainer");
foreach (Transform thisObject in areaContainer.transform)
{
//Check if it contains area
if (thisObject.name.StartsWith("area"))
{
//Add to MediumAreas List
MediumAreas.Add(thisObject.gameObject);
}
}
}
There is multiple approaches to your problem. One of them is using Tags. Simply mark your MediumArea prefab with some Tag and then you can find all tagged GameObjects with FindGameObjectsWithTag(string) (Unity Docs). Then you can populate your collection like that:
MediumAreas.AddRange(FindGameObjectsWithTag("MediumArea"));
Second approach could be finding all objects with same attached script FindObjectsOfType<T>() (Unity Docs). This is usefull when you are searching for instances of same type, like Medium Area.
Lets say that you have an Area script
public class Area : MonoBehaviour {
public AreaSize Size; // AreaSize is Enum
}
Then you can simply find your areas like:
var allAreas = FindGameObjectsOfType<Area>();
var mediumAreas = allAreas.Where(e => e.Size == AreaSize.Medium); // using System.Linq;
I created a project to answer your question, the final result will be like this :
As you can see I have created a game object called "areaContainer" and added 3 children with respective names : "area01", "area02" and "anotherObject".
The script that manage to get all "areaContainer" children that start with "area" looks like :
public GameObject areaContainer;
public List<GameObject> MediumAreas = new List<GameObject>();
private void Start()
{
DefineMediumAreas();
}
void DefineMediumAreas()
{
for (int i = 0; i < areaContainer.transform.childCount; i++)
{
var childGameObject = areaContainer.transform.GetChild(i).gameObject;
if (childGameObject.name.StartsWith("area"))
MediumAreas.Add(childGameObject);
}
}
1- I ended up referencing the areaContainer object in a script rather than using GameObject.Find because it's more performant.
2- To get a child of a game object you need to access to its transform and call GetChild(index). So by iterating through the parent container which is "areaContainer" we are getting its childCount.
3- To check if the name start with "area", simply use .StartsWith("area") which return true or false.
For your second question, you can hide object disabling the Renderer or by deactivating it (thisObject.SetActive(false);
I hope this help you; Happy coding!
You want to access all game objects that are children of "areaContainer"
In void DefineMediumAreas() function, you need to Transform[] to get an array of childeren. Use this:
Transform[] areaContainer = GameObject.Find("areaContainer").GetComponentsInChildren<Transform>();
foreach(Transform thisTransform in areaContainer)
{
...
}
I hope it helps you

Unity3D Prevent adding certain built-in components

I have made my own component which conflicts with some Unity built-in components (like Rigidbody conflicts with Rigidbody2D). So I need to be sure that those components will not exist together in the same GameObject. Is there a way to do it? It seems to be easy to check when my own component is added (by Reset), but what to do if Unity' built-in component is added? Is there some callback, message, or event sent when new component attached to the GameObject?
Precisions
I do not need to hide components it in the editor, or prevent adding my own components. I am asking about preventing adding certain Unity' built-in components while my component is attached. From both Editor GUI (by "add component" button) and Unity API (by GameObject.AddComponent).
There is the [DisallowMultipleComponent] attribute which prevents two of the same type from being added to the same game object. This works for subtypes as well (which is how Rigidbody and Rigidbody2d are handled).
I am not sure if this will work for you or not, as you haven't said your components are related to each other, but it is what I can find.
Is there some callback, message, or event sent when new component
attached to the GameObject?
No.
Is there a way to do it?
Yes, but a bit complicated.
If you want to prevent your custom script from being added, that would have been easy and this question should handle that.
This is complicated because you want to prevent a component written by another person(built-in) from being added to a GameObject which means that you first need a way to detect when that component has been added to a GameObject then destroy it. This has to be done every frame (Both in the Editor and during run-time).
You can call the components you don't want to be added to a GameObject blacklisted components.
Here are the steps:
1.Store the blacklisted components in an array.
private static Type[] blacklistedComponents =
{
typeof(Rigidbody),
typeof(Rigidbody2D)
//...
};
2.Get the root GameObjects in the scene and store them in a List.
private static List<GameObject> rootGameObjects = new List<GameObject>();
Scene.GetRootGameObjects(rootGameObjects);
3.Loop through each root GameObject and use GetComponentsInChildren to get all the components attached to each GameObject under that root GameObject.
private static List<Component> allComponents = new List<Component>();
currentLoopRoot.GetComponentsInChildren<Component>(true, allComponents);
4.During the loop from #3, loop through the retrieved components and check if it has any blacklisted component. If it does, destroy that blacklisted component.
for (int i = 0; i < allComponents.Count; i++)
{
//Loop through each blacklisted Component and see if it is present
for (int j = 0; j < blacklistedComponents.Length; j++)
{
if (allComponents[i].GetType() == blacklistedComponents[j])
{
Debug.Log("Found Blacklisted Component: " + targetComponents[i].GetType().Name);
Debug.Log("Removing Blacklisted Component");
//Destroy Component
DestroyImmediate(allComponents[i]);
Debug.LogWarning("This component is now destroyed");
}
}
}
That's it. You or others may have few questions about this answer.
Q 1.Wonder why FindObjectsOfType and FindObjectsOfTypeAll are not used?
A 1.These functions are usually used to simplify getting everything in the scene but the problem is that they return array. Calling these functions every frame will kill your game performance since it allocates memory and will cause garbage collector to run more often.
This is why Scene.GetRootGameObjects is used which you can pass a List inside it and it will fill the list for you. It does not return array.
Q 2.Why did you pass List to GetComponentsInChildren and not return the result from it?
A 2. Technically the-same reason I explained above. I used a version of the GetComponentsInChildren function that does not allocate memory. Simply pass List to it and it will fill it up with every component it found. This prevents it from returning an array which is expensive.
I wrote a complete working code for this below but you need to improve it. That's why I explained every process so that you can either improve or rewrite it yourself. It currently prevents Rigidbody and Rigidbody2D from being added from the Editor or from code in the Editor or in a build.You can add more components you want to block to the blacklistedComponents variable. It runs in the Editor also during runtime. UNITY_EDITOR is used to remove the Editor codes and make sure that it compiles for platforms.
1.Create a script called ComponentDetector and copy every code below into it.
2.Save and go back to the Editor. That's it. You don't have to attach it to any Object. You should never be able to add Rigidbody and Rigidbody2D to any GameObject.
using System.Collections.Generic;
using UnityEngine;
using System;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
public class ComponentDetector : MonoBehaviour
{
//Add the blacklisted Components here
private static Type[] blacklistedComponents =
{
typeof(Rigidbody),
typeof(Rigidbody2D)
//...
};
private static List<Component> allComponents = new List<Component>();
private static List<GameObject> rootGameObjects = new List<GameObject>();
private static void GetAllRootObject()
{
Scene activeScene = SceneManager.GetActiveScene();
activeScene.GetRootGameObjects(rootGameObjects);
}
private static void GetAllComponentsAndCheckIfBlacklisted()
{
for (int i = 0; i < rootGameObjects.Count; ++i)
{
GameObject obj = rootGameObjects[i];
//Debug.Log(obj.name);
//Get all child components attached to this GameObject
obj.GetComponentsInChildren<Component>(true, allComponents);
//Remove component if present in the blacklist array
RemoveComponentIfBlacklisted(allComponents, blacklistedComponents);
}
}
private static void RemoveComponentIfBlacklisted(List<Component> targetComponents, Type[] blacklistedList)
{
//Loop through each target Component
for (int i = 0; i < targetComponents.Count; i++)
{
//Debug.Log(targetComponents[i].GetType());
//Loop through each blacklisted Component and see if it is present
for (int j = 0; j < blacklistedList.Length; j++)
{
if (targetComponents[i].GetType() == blacklistedList[j])
{
Debug.Log("Found Blacklisted Component: " + targetComponents[i].GetType().Name);
Debug.LogError("You are not allowed to add the " + targetComponents[i].GetType().Name + " component to a GameObject");
Debug.Log("Removing Blacklisted Component");
//Destroy Component
DestroyImmediate(targetComponents[i]);
Debug.LogWarning("This component is now destroyed");
}
}
}
}
public static void SearchAndRemoveblacklistedComponents()
{
//Get all root GameObjects
GetAllRootObject();
//Get all child components attached to each GameObject and remove them
GetAllComponentsAndCheckIfBlacklisted();
}
void Awake()
{
DontDestroyOnLoad(this.gameObject);
}
// Update is called once per frame
void Update()
{
//Debug.Log("Update: Run-time");
SearchAndRemoveblacklistedComponents();
}
}
#if UNITY_EDITOR
[InitializeOnLoad]
class ComponentDetectorEditor
{
static ComponentDetectorEditor()
{
createComponentDetector();
EditorApplication.update += Update;
}
static void Update()
{
//Debug.Log("Update: Editor");
ComponentDetector.SearchAndRemoveblacklistedComponents();
}
static void createComponentDetector()
{
GameObject obj = GameObject.Find("___CDetector___");
if (obj == null)
{
obj = new GameObject("___CDetector___");
}
//Hide from the Editor
obj.hideFlags = HideFlags.HideInHierarchy;
obj.hideFlags = HideFlags.HideInInspector;
ComponentDetector cd = obj.GetComponent<ComponentDetector>();
if (cd == null)
{
cd = obj.AddComponent<ComponentDetector>();
}
}
}
#endif
If you're trying to determine if the component exists before runtime, which I assume you already know, you can use the Start() method. But, like I said, I assume you already know that, so the only other way to check something like that during runtime would be to continually check for it in the Update() method, every frame. Although, I am not sure as to why a Unity component might be added to a gameobject during runtime. If this is an issue, maybe the component in question could be added to a child or parent gameobject instead?
If you really feel like making some changes, which may require a lot of refactoring for your project, you could always create a ComponentManager class that handles adding and removing components to GameObjects and create your own callbacks.

Selecting random positions from array without duplicates

In my game, there are many enemies and have this script on it, where I am set their navmesh destination by randomly selecting an element from an array of positions. However, sometimes all enemies go at same position, so I am trying to skip previously generated array indices and find a new index. Any suggestions for this? I don't want to use the goto statement.
Here is code I have done so far:
void move_on_fired_positions()
{
navmesh.Resume ();
again:
new_position = Random.Range (0, firedpoints.Length);
if (prev_num != new_position) {
navmesh.SetDestination (firedpoints [new_position].position);
prev_num = new_position;
} else
{
goto again;
}
}
One thing you can try is keeping a dynamic list of available positions, rather than an array. (Don't worry - they're both serialized and modified the same way in the Unity editor interface.) This way, you can remove positions from the list as they are assigned to enemies, ensuring you never assign duplicates. Here's an idea of how that might look:
public List<Transform> firedpoints;
// Returns available firing position, if any
public Transform GetRandomFiringPosition()
{
if (firedpoints.Count > 0)
{
// Get reference to transform, then remove it from list
int newPositionIndex = Random.Range (0, firedpoints.Length);
Transform returnedPosition = firedpoints[newPositionIndex];
firedpoints.RemoveAt(newPositionIndex);
return returnedPosition;
}
else
{
// Or you can specify some default transform
return null;
}
}
// Makes firing position available for use
public void RegisterFiringPosition(Transform newPosition)
{
firedpoints.Add(newPosition);
}
This code should be in a script on a single object in the scene, which the enemies should have a reference to (or, you can change the code a little and make the class into a singleton, so its methods can be called without a direct reference). Whenever an enemy needs a new position, it can call GetRandomFiringPosition() and store the returned Transform in a variable for future reference.
You haven't determined what conditions make a position available for use again, but when you have, you can call RegisterFiringPosition() to get it back into the list. As a nice side effect, this also makes it possible to assign brand new positions to enemies, for example in response to player-triggered events.
Hope this helps! Let me know if you have any questions.

Categories

Resources