Efficient way to not delete gameobject reference of singleton after scene reload - c#

I have a singleton class and everytime I reload a scene the object reference I store in variable is destroyed
public class GameManager : MonoBehaviour
{
private void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
}
DontDestroyOnLoad(this);
Debug.Log("Scene reloaded");
}
void Start()
{
shapeSpawnerGO = GameObject.Find("SpawnShapesObj");
scoreGO = GameObject.Find("ScoreText");
lifeGo = GameObject.Find("LifeText");
}
public bool RedShapeStatus(int rcv_RedShapeIndex)
{
if (shapeSpawnerGO == null)
{
shapeSpawnerGO = GameObject.Find("SpawnShapesObj");
}
return shapeSpawnerGO.GetComponent<ShapeSpawnerChild>().listofRedShape[rcv_RedShapeIndex].activeSelf;
}
}
What I've done is check if shapeSpawnerGO is null then reference again the gameobject. And I think this is not efficient. Is there other way to solve this issue?

There are certainly other ways to accomplish this, but my official answer is "You're already doing it an acceptable way." You specifically said this:
"What I've done is check if shapeSpawnerGO is null then reference
again the gameobject. And I think this is not efficient. Is there
other way to solve this issue?"
You said the only time your code reinitializes the variables is whenever the scene reloads.That operation time doesn't even matter. You're literally talking about optimizing something completely irrelevant. Reinitializing scene data during a reload is what normal scene loading is all about.
The only exception to this would be if your idea of a scene reload is something you're doing every few seconds. If you're talking about the normal idea of a scene reload where you load the game scene once and then proceed to run the game for many minutes before a new scene reloads, then there's no reason to be worried about this code doing its normal initialization behavior.

Related

I am getting a NullReferenceException with unity collisions [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 9 months ago.
This is my code that is receiving the error on the playerScore increment line. The code is in a prefab that is generated many times, and if it hits the player, it is destroyed and the player's score is incremented. Currently the code will destroy on collision with anything that does not have the name "Player" due to the error once the if statement is executed.
private void OnCollisionEnter2D(Collision2D collisionInfo)
{
Debug.Log($#"Collision name = {collisionInfo.collider.name}");
if (collisionInfo.collider.name == "Player")
{
GetComponent<GameMechanics>().playerScore++;
}
GameObject e = Instantiate(explosion) as GameObject;
e.transform.position = transform.position;
Destroy(gameObject);
}
This is my code for the referenced variable, I just cannot tell what I am doing wrong here, I want the object destroyed in all cases, but want the score incremented when the player hits it.Is the object being destroyed preventing the changes to be made to the player score?
public class GameMechanics : MonoBehaviour
{
public int playerScore;
// Start is called before the first frame update
void Start()
{
playerScore = 0;
}
// Update is called once per frame
void Update()
{
Debug.Log($#"Player Score = {playerScore} ");
}
}
Thanks in advance!
This is because you are using GetComponent in a class that does not have it and should find GameMechanics in it, but the result is null.
GetComponent<GameMechanics>().playerScore++; // GameMechanics dosen't found..
One way is to give GameMechanic to the other object's is through the inspector:
public GameMechanics Mechanics;
private void OnCollisionEnter2D(Collision2D collisionInfo)
{
if (collisionInfo.collider.name == "Player") Mechanics.playerScore++;
}
However There are other ways to reference GameMechanic and Manager classes in other objects, the best of which is Singleton. here is a simple way to solve your problem easy and that is to use FindObjectOfType. Change the code below and it will probably be fixed.
if (collisionInfo.collider.name == "Player")
{
FindObjectOfType<GameMechanics>().playerScore++; // replace it with this way
}
NullReferenceExceptions occur when the code attempts to access a variable which has not been initialized properly. In your case, this could be because of several reasons.
Ensure the gameobject is set to active in the scene
Ensure the script object is enabled on the gameobject
If these do not help, you can try making your variable static, and setting it to an ititial value of 0.
Static variables are loaded before everything else, and accessible by most other classes, so this may be able to fix your issue.

Unity: Audio source gets destroyed after scene reloads

I just introduced a pause option to my little testing game. I have audio in background (that plays throughout the whole game, even when the scene changes), so I've decided to make the music stop while the game is paused. For some reason, it works just fine UNTIL the game reloads/changes scene.
Then an error pops up
"The object of type 'AudioSource' has been destroyed."
Can anybody help? Also.. I thought it's a component, not an object! Might be both, I'm not sure.
How I make my music continuous:
void Awake()
{
if (instance != null)
{
Destroy(gameObject);
}
else
{
instance = this;
GameObject.DontDestroyOnLoad(gameObject);
}
}
My pause menu:
public static bool GameIsPaused = false;
public GameObject pauseMenuUI;
public AudioSource song;
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (GameIsPaused)
{
Resume();
}
else
{
Pause();
}
}
}
void Resume()
{
pauseMenuUI.SetActive(false);
Time.timeScale = 1f;
GameIsPaused = false;
song.mute = false;
}
void Pause()
{
pauseMenuUI.SetActive(true);
Time.timeScale = 0f;
GameIsPaused = true;
song.mute = true;
}
Thank you!
Your problem could be with this line here
if (instance != null)
This is a singleton pattern and the purpose of this line is to prevent two instances of your singleton class from existing, which is a big no-no. This means if you try make a new object but one already exists, it will destroy the new object.
However, if Awake()is called again for any reason on the first singleton then it will see that the member instance is not null and promptly destroy itself.
A fix would be to change it to this:
if (instance != null && instance != this)
This would prevent your singleton from destroying itself.
Pretty sure it's your audio variable in the pause menu script. I think the pause menu dissociates itself from that AudioSrouce on a new scene load. To check this, go into game mode and load a new scene. When in the new scene, go over to the hierarchy and click the GameObject with the pause menu script on it. Now check to see if the public var AudioSource has anything associate with it.

Particle System not working on Play()

I have a particle effect that I would like to trigger to play, and then stop. I'm sure this is an easy fix that I am over looking.
The particle can instantiate and play, but this obviously leaves overhead and particles that are active in the hierarchy when they don't need to be.
public void EmitFX(ParticleSystem particle)
{
Instantiate(particle, particlePos, Qauternion.identity)
}
I would like to use methods within ParticleSystem but am running into some problems. I have been using the manual and am still running into a block. I've googled this up and down, based on problems others had I changed my code to the following. It still does not work and is now a monster based on hacks other people found useful :/
public void EmitFX(ParticleSystem particle)
{
particle = particle.GetComponent<ParticleSystem>();
particle.transform.position = ballPos;
var em = particle.emission;
em.enabled = true;
particle.Play();
}
Here is a s/c of a particle in the Inspector.
First of all, not sure what the particle = particle.GetComponent<ParticleSystem>(); line is supposed to do? The particle variable is the ParticleSystem provide to your EmitFX() method so no need to call for this.
My guess is you have some references problems in your script (some variables referring to your prefab, then what you instantiate overrides this reference, ...) so I wrote you a "cleaner" version of your code (by merging your two scripts):
#region Attributes
[SerializeField]
private ParticleSystem particle;
private ParticleSystem generatedParticle;
#endregion
#region MonoBehaviour
protected void Start()
{
generatedParticle = null;
}
protected void Update()
{
if(Input.GetKeyDown(KeyCode.Space))
{
EmitFX(particle);
}
}
#endregion
public void EmitFX(ParticleSystem a_Particle)
{
if(generatedParticle == null)
{
generatedParticle = Instantiate(particle, particlePos, Qauternion.identity);
}
generatedParticle.transform.position = ballPos;
generatedParticle.Play();
// You can set a fixed duration here if your particle system is looping
// (I assumed it was not so I used the duration of the particle system to detect the end of it)
StartCoroutine(StopFXAfterDelay(generatedParticle.main.duration));
}
private IEnumerator StopFXAfterDelay(float a_Delay)
{
yield return new WaitForSeconds(a_Delay);
generatedParticle.Stop();
}
What it does is it store the instantiated particle in a variable so it can access it later and remember it has been generated. Also I added a coroutine to deactivate it at the end of the effect.
Hope this helps,
I know this is an old question but I am going to place here what worked for me since the problem persists in the later versions of Unity.
Instead of just calling: particle.Play() first stop the particle system like the code below.
particles.Stop();
if (particles.isStopped)
{
particles.Play();
}
Where 'particles' is a component of type "ParticleSystem".

How would I keep a value between scenes?

I've been trying for a little bit here to set a muteSound boolean in my SoundManager and then to switch to a new scene and keep the value previously stored in muteSound but I'm unsuccessful.
I tried the DontDestroyOnLoad(this); in hopes that it'd bring it to the new scene but for some reason it isn't.
Would any of you know what my problem could be? Am I using the correct function?
Thanks,
Some would say use static. That would work but avoid doing that as you will run into other problems. What you need is the PlayerPrefs. Save the value on exit. Read the value when game starts. You can do that in your SoundManager script.
bool muteSound = false;
//Load the value when game starts (default is false)
void Start()
{
muteSound = intToBool(PlayerPrefs.GetInt("muteSound", 0));
}
int boolToInt(bool val)
{
if (val)
return 1;
else
return 0;
}
bool intToBool(int val)
{
if (val != 0)
return true;
else
return false;
}
//Save on Exit
void OnDisable()
{
PlayerPrefs.SetInt("muteSound", boolToInt(muteSound));
}
You can actually pass a game object from scene to scene and all the values for classes assigned to that game object are maintained between scenes.
You basically just create an empty game object that stores your manager scripts and then pass the game object from scene to scene when you load them. This should preserve the values for the scripts attached to the empty game object.
EDIT: Fixed some spelling/grammatical errors.
Instead of typing
DontDestroyOnLoad(this); type
void Awake() {
DontDestroyOnLoad(gameObject);
}
If this doesn't work I suggest you check out the PlayerPrefs.
When the level is finished usePlayerPrefs.SetFloat("pref name", variable); or .SetInt(); or .SetBool();
And when the next level loads usevalue = PlayerPrefs.GetFloat("pref name"); or .GetInt(); or .GetBool();
This player prefs can also be used as saves because they are stored inside the computers registry.
If you use this for loading things such as money and you are loading it for the first time, do this:
if(PlayerPrefs.HasKey("money")) {
money = PlayerPrefs.GetFloat("money");
} else {
PlayerPrefs.SetFloat("money", 0);
money = 0;
}
If you don't do this the values can get messy a lot. I had big problems when I didn't use PlayerPrefs.HasKey();
Here was my solution:
public class SoundManager : MonoBehaviour {
public static SoundManager instance = null;
protected virtual void Awake()
{
if (instance == null)
instance = this;
else if (instance != this)
Destroy(gameObject);
DontDestroyOnLoad(gameObject);
}
}
While I thought my problem was because of my changing the class to a singleton the actual problem was that there was another class that needed to be a singleton.
Since the rest of the code wouldn't be mine to share, here is a tutorial which helped me understand what I was doing much better if anyone else ever runs into this type of problem.

Enable object Parent in the hierarchy

How do I get an object in the hierarchy is enabled through scrip?
void OnCollisionEnter2D (Collision2D colisor)
{
if (colisor.gameObject.tag == "Floor") {
Destroy (gameObject, 0.6f);
} else {
if (colisor.gameObject.tag == "Bee") {
coinCollectSound.Play ();
heart = GameObject.FindGameObjectWithTag ("Hearts").GetComponent<Hearts> () as Hearts;
if (heart.TakeHeart ()) {
Destroy (gameObject);
} else {
//Here i want setActive(true) Object parent in hierarchy called "GameOver"
//And setActive(false) Object in hierarchy Called "Game"
}
}
}
}
I do not want to call a different scene for the Game Over, but now I just want to enable it.
You should access the GameOver using GameObject.Find("GameOver"), but if it's disabled you won't be able to do that. Instead, create a public GameObject variable on a enabled script that references the object you wanna SetActive. Then, find the object by type and access it's variable, enabling it.
Since this is a gameover object you could probably get away with simply calling
GameObject.Find("GameOver").SetActive(true);
GameObject.Find("Game").SetActive(false);
However this will have performance implications if there are many objects in your game.
To disable the parent of GameObject the script is attached:
transform.parent.gameObject.setActive(true);
To get specific GameObjects use a public variable and link them in the Inspector. To improve performance avoid using GameObject.Find.

Categories

Resources