Using Unity, I'm working on a game where all Gameobjects with a certain tag vanish/reappear fairly regularly (every 10 seconds on average). I use GameObject.FindGameObjectsWithTag() to create a Gameobject[] through which I enumerate every time that the objects need to be made visible/invisible. I cannot call it once, on Start, as new Gameobjects are created while playing. I thought that it would be worse to access and change the Gameobject[] every time something got created/destroyed. Is there a better way to handle this. I know how bad of an impact on performance the GameObject.Find methods make...
Yes, there is a better way to do this. Have a script with List variable that can store GameObjects. Making it a singleton is better but not necessary.
This singleton script should be attached to an empty GameObject:
public class GameObjectManager : MonoBehaviour
{
//Holds instance of GameObjectManager
public static GameObjectManager instance = null;
public List<GameObject> allObjects = new List<GameObject>();
void Awake()
{
//If this script does not exit already, use this current instance
if (instance == null)
instance = this;
//If this script already exit, DESTROY this current instance
else if (instance != this)
Destroy(gameObject);
}
}
Now, write a script that registers itself to the List in the GameObjectManager script. It should register itself in the Awake function and un-register itself in the OnDestroy function.
This script must be attached to each prefab you want to add to the List. Simply do that from the Editor:
public class GameObjectAutoAdd : MonoBehaviour
{
void Awake()
{
//Add this GameObject to List when it is created
GameObjectManager.instance.allObjects.Add(gameObject);
}
void OnDestroy()
{
//Remove this GameObject from the List when it is about to be destroyed
GameObjectManager.instance.allObjects.Remove(gameObject);
}
}
If the GameObjects are not prefabs but GameObjects created through code, simply attach the GameObjectAutoAdd script to them once they are created:
GameObject obj = new GameObject("Player");
obj.AddComponent<GameObjectAutoAdd>();
You can now access your GameObjects in the List with GameObjectManager.instance.allObjects[n]; where n is the index number and you don't have to use any of the Find functions to find the GameObject anymore.
It's true that calling to GameObject.Find consumes a lot of recourses.
The point should be to save the result and use it always but I understand that you can't do that.
Other option is to have all this gameobjects as a child of one gameobject let's call it handlerGameobject. It will have a script that doesn't use GameObeject.Find it could getChild or transform.Find that uses less resources.
Hope it helps!
Related
I have a prefab object called Beam, which contains several things but one is an object that when an instance of it is collided with and triggered, should destroy itself.
Currently, I have the script that generates all of the instances on a variable called Beams. Shown here:
When that runs, it creates clones within it. Seen here:
You will also see in the last image, the Beam prefab that contains the Cookie in it. That cookie is where I have a script that says, if I hit it, destroy. That code looks like this:
...
public class Collectibles : MonoBehaviour
{
GameManager game;
// Start is called before the first frame update
void Start()
{
game = FindObjectOfType<GameManager> ();
}
...
void OnTriggerEnter2D(Collider2D other) {
if(other.tag == "Player"){
string coinType = "Cookie";
game.AddCollectible(coinType);
Destroy(gameObject);
}
}
}
Currently, when I run into a cookie, it runs Destroy(gameObject) and destroys ALL instances of the cookie (one per each Clone).
This code lives on Cookie, not on Beams. Is that correct? Should I have the code somewhere else? I also tried Destroy(this) but that doesn't do what I thought it would do (just the instance).
Is it possible that from where I was calling Destroy, the script doesn't have access to the instances, or am I missing something? Thank you in advance!
If I understand your question, you want that when the "player" collides with an instance of "beam" only destroy the instance of Cookie (or the gameobject that contains the script), in this case it would do so with the tag:
public GameObject[] arrayofcookie;
public int destroyedinstances=1;
//this int will tell how many instances you want to be destroyed (from the last instantiated to the first)
//for this example the last instance will be deleted
public void destroyCookie()
{
arrayofcookie= GameObject.FindGameObjectsWithTag("Cookie");
for (int i = 0; i < destroyedinstances; i++)
{
Destroy(arrayofcookie[i].gameObject);
}
}
You call this method in the cookie script, in the collider or if you prefer with an invoke method after N seconds.
I do not think I have understood your question very well, but in these problems I prefer to use the label and it also depends on the nature of your game
I am making a maze game and the keys which are needed to be collected to complete it wont appear again if the game restarts, I get the following error;
MissingReferenceException: The object of type 'MazeDirectives' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
I am just disabling the MazeKey object, not destroying it, can anyone help? Below is my code;
MazeKey.cs
using UnityEngine;
using System.Collections;
public class MazeKey : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
transform.parent.SendMessage("OnKeyFound", SendMessageOptions.DontRequireReceiver);
gameObject.SetActive(false);
}
}
MazeDirectives.cs
MazeGoal mazeGoal;
MazeKey mazeKey;
void StartDirectives()
{
mazeGoal = Instantiate(mazeGoalPrefab, MazeGenerator.instance.mazeGoalPosition, Quaternion.identity) as MazeGoal;
mazeGoal.transform.SetParent(transform);
mazeKeyPositions = MazeGenerator.instance.GetRandomFloorPositions(keysToFind);
for (int i = 0; i < mazeKeyPositions.Count; i++)
{
MazeKey mazeKey = Instantiate(mazeKeyPrefab, mazeKeyPositions[i], Quaternion.identity) as MazeKey;
mazeKey.transform.SetParent(transform);
}
}
To restart the game I use the code below;
void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Player")
{
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
gameObject.SetActive(true);
}
}
MazeGoal.cs
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MazeGoal : MonoBehaviour
{
public Sprite closedGoalSprite;
public Sprite openedGoalSprite;
void Start()
{
GetComponentInChildren<SpriteRenderer>().sprite = closedGoalSprite;
}
public void OpenGoal()
{
GetComponentInChildren<SpriteRenderer>().sprite = openedGoalSprite;
}
void OnTriggerEnter2D()
{
transform.parent.SendMessage("OnGoalReached", SendMessageOptions.DontRequireReceiver);
}
Explenation
The exception you get is not talking about the MazeKey object but rather the MazeDirectives component.
Unfortunately you hit the most important information in the comments:
private void Awake()
{
MazeGenerator.OnMazeReady += StartDirectives;
}
so OnMazeReady seems to be static and not instanced so it will not be destroyed when a new Scene is loaded but keeps intact bloating into the new Scene!
When you call
MazeGenerator.OnMazeReady += StartDirectives;
you add the call to the StartDirectives method of an instance of MazeDirectives as listener to that static event.
Now when you reload the Scene all GameObjects and thereby their instances of components are destroyed
=> so is the instance of MazeGenerator ... BUT the static event OnMazeReady is not destroyed!
so after the next Awake call you now have two listeners
The one from the "second"/new loaded Scene
Still the "old" one you added the first time
But since the instance of MazeDirectives you added the first listener for is destroyed when the scene is reload and a new instance generated, you get that exception
MissingReferenceException: The object of type 'MazeDirectives' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
when the method tries to access the transform value of the destroyed instance.
Solution 1a
So you should remove the listener when you destroy the instance
private void OnDestroy()
{
MazeGenerator.OnMazeReady -= StartDirectives;
}
Solution 1b
or overwrite it with only exactly one listener at a time
private void Awake()
{
MazeGenerator.OnMazeReady = StartDirectives;
}
this second aproach obviously is only useful when there is no other instance or class listening to that event. The question is how much sense does it make to use an event than? And I would than anyway remove it if not needed just to be sure
private void OnDestroy()
{
MazeGenerator.OnMazeReady = null;
}
Solution 2
I would prefere this solution.
Don't make MazeGenerator.OnMazeReady static at all. Since anyway I see that you are using a Singleton pattern e.g. in
MazeGenerator.instance.mazeGoalPosition
you could instead just make OnMazeReady Non-static and instead use it the same way:
private void Awake()
{
MazeGenerator.instance.OnMazeReady += startDirectives;
}
so it will be destroyed together with that instance of MazeGenerator.
General note
I would always remove all listeners I ever added as soon as possible to avoid exactly the issue you have.
You could additionally remove it e.g. already inside of StartDirectives to make sure the method is executed only once even if the same Scene "accidentely" invoked OnMazeReady twice.
Hint: I said additionally since it is always save/possible to remove a listener even if it wasn't added before and you should allways leave the one in OnDestroy in case the StartDirectives is never called before the object is destroyed.
Update:
This answer is wrong at first place. The exception is complaining about accessing the MazeDirectives's transform, not mazeGoal object.
But the comments below did give some useful info. So I'm keeping this post for references.
For complete solution, see here.
From the line mazeGoal.transform.SetParent(transform); throws the exception:
MissingReferenceException: The object of type 'MazeDirectives' has been
destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
From here:
The load of a new Scene destroys all current Scene objects.
The mazeGoal has been destroyed when you called the
SceneManager.LoadScene(SceneManager.GetActiveScene().name);
to restart the game.
And from MonoBehaviour.Awake(),
Awake is called only once during the lifetime of the script instance.
Since you only assign the mazeGoal variable inside StartDirectives function which been called in Awake, after loading the same scene again, the actual object of mazeGoal has been destroyed.
If you want to reuse the same object when loading a new scene, you can use DontDestroyOnLoad to keep the mazeGoal object.
Or you can move the StartDirectives to Start function which will be called every time the gameobject is created and reinitialize your mazeGoal.
I have the following code:
void Start()
{
gameObject.SetActive(false);
StartCoroutine(Load());
}
IEnumerator Load()
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(true);
}
This gives me an error that says:
Coroutine couldn't be started because the the game object 'NameOfObj'
is inactive!
This makes sense, since the game object has been set to deactivate before running the script. Even still, what is it that I'm supposed to do then? I tried moving gameObject.SetActive(false) to the coroutine, before WaitForSeconds(). Doing this stopped the game object from loading at all.
From my understanding, when the line gameObject.SetActive(false) is executed, the script stops running until the game object is reactivated. However, if this is the case, would it not be impossible to then reactivate the game object (as the script is disabled)?
Regardless, how would I delay my game object from loading until 2-3 (or any arbitrary length of time) after the game has started?
You cannot start a coroutine function from a script that has its GameObject de-activated.
The StartCoroutine function is a function under the MonoBehaviour class. When you have to start a coroutine on a deactivated GameObject, you need a reference to a MonoBehaviour object that has an active GameObject.
Two ways to do this:
1. Use an already existing GameObject that's unlikely to be deactivated. In my case, I usually use the camera. I access the camera's MonoBehaviour since it's likely to be activated then use it to start the coroutine function.
I suggest you use this method.
Replace the code in your Start function with the one below:
//De-activate this GameObject
gameObject.SetActive(false);
//Get camera's MonoBehaviour
MonoBehaviour camMono = Camera.main.GetComponent<MonoBehaviour>();
//Use it to start your coroutine function
camMono.StartCoroutine(Load());
2. Attach the script to an empty GameObject and the script on the empty GameObject will control or be able to activate/de-activate the other GameObject.
The script with the coroutine function you expect to run on a de-activated GameObject (Attach it to the GameObject you wish to de-activate):
public class YourDeactivatableScript: MonoBehaviour
{
public IEnumerator Load()
{
yield return new WaitForSeconds(waitTime);
gameObject.SetActive(true);
}
}
Now, let's say that you want to deactivate a GameObject named "Cube" that has the YourDeactivatableScript script attached to it but still be able to start its Load coroutine function, create an empty GameObject with a new script, then start the Load function from it.
Create an empty GameObject then attach this script to it:
public class LoadFuncCallerScript: MonoBehaviour
{
GameObject targetObject;
public void Start()
{
//Find the GameObject you want to de-activate
targetObject = GameObject.Find("Cube");
//De-activate it
targetObject.SetActive(false);
//Get it's component/script
YourDeactivatableScript script = targetObject.GetComponent<YourDeactivatableScript>();
//Start coroutine on the other script with this MonoBehaviour
StartCoroutine(script.Load());
}
}
The coroutine is now started from another script named LoadFuncCallerScript.
What I do to avoid stopping coroutine is have a game object that does not get deactivated.
public class CoroutineHandler : MonoBehaviour
{
private static CoroutineHandler instance = null;
public static CoroutineHandler Instance
{
get
{
if(instance == null)
{
GameObject inst = new GameObject("CoroutineHandler");
DontDestroyOnLoad(inst);
instance = inst.AddComponent<CoroutineHandler>();
}
return instance;
}
}
}
Then use this for such coroutines by using CoroutineHandler.Instance.StartCoroutine(RoutineMethodHere());.
If you do not want something like this, since it can go wrong or cause leaks if not handled correctly, you can try using Invoke("MethodName", delay);
I am trying to move my player to destroy monsters and the first time I destroy a monster I have the following error:
MissingReferenceException: The object of type 'GameObject' has been destroyed but you are still trying to access it.
Your script should either check if it is null or you should not destroy the object.
How can I make it to duplicate itself as a new GameObject?
This is my script:
using System.Collections.Generic;
using UnityEngine;
public class TimeMap: MonoBehaviour {
public GameObject selectedUnit;
public GameObject selectedMonster;
public TileType[] tileTypes;
try{
if(selectedMonster.GetComponent<Monster>().tileX == selectedUnit.GetComponent<Unit>().tileX && selectedMonster.getComponent<Monsters>().tileY == selectedUnit..GetComponent<Unit>())
Destroy(selectedMonster);
}
public void SpawnMon() {
if(counter % 5 == 0){
Debug.Log(" counter % 5 == 0");
Instantiate(selectedMonster.GetComponent<Monsters>().Monster, new Vector3(Random.Range(1,8), Random.Range(1,8), -1), Quaternion.identity);
}
}
}
So I try to create the same object or a clone of it every 5 moves, but I am having errors while doing it.
Problems:
You are trying to access your monsters like this:
selectedMonster.GetComponent<Monster>()
It means if you have in your scene more than one GameObject of type Monster, Unity will not be sure which one you are referring to.
Also when you instantiate a monster, you use just
Instantiate(selectedMonster.GetComponent<Monsters>().Monster, new Vector3(Random.Range(1,8), Random.Range(1,8), -1), Quaternion.identity);
So you will not be able to distinguish one instance from another in your scene.
Solutions:
In case you wanted to continue with this approach, where you check if tileX and tileY of monster match with your Unit (which I assume is your hero or similar), you should have all monsters inside an array so you can iterate them all in a way you refer easily to the one you want to destroy.
You could try FindObjectsOfType and there pass your Monster type:
Monster [] monsters= FindObjectsOfType(typeof(Monster )) as Monster [];
Then you iterate
foreach (Monster thisMonster in monsters){
//check things here
}
Another option is to store the monsters in an array when you instantiate them
//Define as global variable a list of Monsters
List<GameObject> monsterList = new List<GameObject>();
//Then you instantiate then like
monsterList .add((GameObject)Instantiate(selectedMonster.GetComponent<Monsters>().Monster, new Vector3(Random.Range(1,8), Random.Range(1,8), -1), Quaternion.identity));
And you iterate them using the foreach as before
Better Solution (From my point of view)
However, since your main goal, correct me if I am wrong, is to detect when the position of a Unit match with the position of a monster, to later destroy that particular monster. I would use colliders instead. And in the GameObject you call Unit I would add a TriggerEnter. Since you use the TileX and TileY I suspect you are creating a 2D game, so it would be something like this:
void OnTriggerEnter2D(Collider2D other) {
Destroy(other.gameObject);
}
With this you destroy any monster that touches your "Unit" and you will not have reference problems.
This is semi complicated of a question but I'll do my best to explain it:
I am making this mobile game in which you have to shoot four cubes. I'm trying to make it so when the cubes are shot by a bullet, they're destroyed and a UI text says 1/4, to 4/4 whenever a cube is shot. But it's being really weird and only counts to 1/4 even when all four cubes are shot and destroyed. I put these two scripts on the bullets (I made two separate scripts to see if that would do anything, it didn't)
And to give a better idea of what I'm talking about, here's a screenshot of the game itself.
I've been using Unity for about 6 days, so I apologize for anything I say that's noob-ish.
EDIT
So I combined the two scripts onto an empty gameobject and here's the new script:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class GameManagerScript : MonoBehaviour {
public GameObject cubes;
public Text countText;
public int cubeCount;
public Transform target;
// Use this for initialization
void Start () {
}
void OnTriggerEnter(Collider other)
{
cubes = other.gameObject;
}
// Update is called once per frame
void Update () {
cubes.transform.position = Vector3.MoveTowards(transform.position, target.position, 1f * Time.deltaTime);
if (cubes.gameObject.tag == "BULLET")
{
cubeCount = cubeCount + 1;
countText.text = cubeCount + "/4";
cubes.SetActive(false);
}
}
}
ANOTHER EDIT
I tried everything, so is there a way to detect when all the children in a parent on the Hierarchy are destroyed? Instead of counting up? This can give a better idea:
So I want to be able to detect when Cube, Cube1, Cube2, and Cube3 have all been destroyed.
The answer is pretty simple: Since every individual bullet has that script, each bullet has its own score.
For something like a score you want a single spot to store it, e.g. a script on an empty gameobject that serves as game controller. Just access that in the collision and increase the score (maybe have a look on singletons here).
You can combine those two scripts and actually it might be better to not have this on the bullet, but on the target because there are probably less of them which will save you some performance. (And it does more sense from a logical point of view.)
Edit:
I assume you create the bullets using Instantiate with a prefab. A prefab (= blueprint) is not actually in the game (only objects that are in the scene/hierarchy are in the game). Every use of Instantiate will create a new instance of that prefab with it's own version of components. A singleton is a thing that can only exist once, but also and that is why I mention it here, you can access it without something like Find. It is some sort of static. And an empty gameobject is just an object without visuals. You can easily create one in unity (rightclick > create empty). They are typically used as container and scriptholders.
Edit:
What you want is:
An empty gameobject with a script which holds the score.
A script that detects the collision using OnTriggerEnter and this script will either be on the bullets or on the targets.
Now, this is just a very quick example and can be optimized, but I hope this will give you an idea.
The script for the score, to be placed on an empty gameobject:
public class ScoreManager : MonoBehaviour
{
public Text scoreText; // the text object that displays the score, populate e.g. via inspector
private int score;
public void IncrementScore()
{
score++;
scoreText.text = score.ToString();
}
}
The collision script as bullet version:
public class Bullet : MonoBehaviour
{
private ScoreManager scoreManager;
private void Start()
{
scoreManager = GameObject.FindWithTag("GameManager").GetComponent<ScoreManager>(); // give the score manager empty gameobject that tag
}
private void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Target") == true)
{
// update score
scoreManager.IncrementScore();
// handle target, in this example it's just destroyed
Destroy(other.gameObject);
}
}
}