Unity: Audio source gets destroyed after scene reloads - c#

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.

Related

How to do if something happened make number - 1

I am trying to do when i destroy all boxes something happen.
My code is;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class destroy : MonoBehaviour
{
private string BALL_TAG = "ball";
public AudioClip coin;
public AudioSource src;
public float numBox = 120f;
public bool isDestroyed;
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag(BALL_TAG))
{
src.clip = coin;
src.Play();
Destroy(gameObject);
isDestroyed = true;
}
}
private void Update()
{
boxes();
}
public void boxes()
{
if(isDestroyed == true)
numBox -= 1f;
if(numBox == 119)
SceneManager.LoadScene("mainManu");
}
private IEnumerator Two()
{
yield return new WaitForSeconds(1f);
Destroy(gameObject);
}
}
But it doesn't work.
It is suppose to do when I broke 1 box it sends me to menu.
I think its problem in "numBox -= 1f;" because I don't know hot to make this.
I don't understand your code completely. So, I need to make some assumptions.
I think the Script is attached to the box and every box has this Script. I also think, that your player Shoots Ball. Those Balls have a collider with an ball tag.
There are multiple problems with your code.
The first one is, that your count variable, numBox, is saved in your destroy Script, which is placed on each box.
this means, that every Box is counting for itself.
You have to centralize this. There are multiple ways for doing this.
One way is to declare this variable as static(https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/static)
This is not best practice, but works.
A Better way is to have a Script on Your Player, which holds this number and every Box searches for this Script and change this number if it is destroyed.
The second big Problem is, that your are doing some really weird thing in your Update and the collision handling
First of all, you are setting isDestroyed to true. Then in your boxes method, which is called in every Frame, you are decrementing your numBox variable by one, if this is Destroyed is true.
So if your Box gets hit, you are decrementing every frame.
After that you are checking every frame if your numBox is 119
If so, you change the Scene.
This is the reason, why you are getting to your MainMenu after only one boy
This behaviour is very weird, because it is totally unnecessary. You can reduce your variable directly in in your OnCollisionEnter2D Method.
There are some little things, which can be improved.
When you are trying to play a Sound, you don't have to specify the AudioClip in code. You can assign this directly in Unity on the AudioSource Component via drag and drop. This makes your code simpler.
You are not calling the Two Coroutine. You've specified this Coroutine but don't call it.
//Script on Player
public class PlayerBoxDestroyManager:MonoBehaviour
{
public int StartBoxes = 120;
private int Boxes;
private void Start()
{
Boxes = StartBoxes;
}
public void DestroyBox()
{
//Reduce our Boxes count
//This is equal to Boxes -= 1
// Boxes = Boxes -1
Boxes--;
// If we have less or zero Boxes left, we End call our EndGame methode
if(Boxes <= 0)
{
EndGame();
}
}
private void EndGame()
{
// We change the Scene to the mainMenu
SceneManager.LoadScene("mainManu");
}
}
```
//Script on all Boxes
public class Box : MonoBehaviour
{
public string Balltag = "ball";
//Audio Source the Audio Clip has to be assigned in the Unity editor
public AudioSource Coin;
private void OnCollisionEnter2D(Collision2D collision)
{
//Check it colliding Object has the right Tag
if(collision.transform.tag == Balltag)
{
//Get the reference to the Player Script
PlayerBoxDestroyManager PBDM = FindObjectOfType<PlayerBoxDestroyManager>();
//We can now access the Destroy Box Methode
PBDM.DestroyBox();
//Play the sound
Coin.Play();
//If we destroy our Object now, the Sound would also be deletet.
//We want to hear the sound, so we have to wait, till the sound is finished.
StartCoroutine(WaitTillAudioIsFinished());
}
}
IEnumerator WaitTillAudioIsFinished()
{
//we wait till the sound is finished
while (Coin.isPlaying)
{
yield return null;
}
//if finished, we destroy the Gameobject
Destroy(gameObject);
}
}
I hope I helped you. If you have questions, feel free to ask.
And sorry for my English:)

Unity New Input Manager and Photon issue

I'm working on a multiplayer game and I'm encountering a issue when 2 players are loading the same level using PhotonNetwork.LoadLevel().
When I start the game alone, I can control my player and everything is fine. But when we are 2 players, Player A is controlling Player B and vice-versa.
I check a lot of links on the internet these past few days, and I learned the concept of PhotonNetwork.IsMine which I thought would solve all of my problem but it seems to not working with me. Also, I'm using the new input system of Unity but I don't think the issue come from here.
Basically, what I'm doing is:
Instantiate a player (this happened twice since I have 2 players) which have a PlayerManager
Player Manager Get Instance of the local player and synchronize camera with the local player only if isMine = true
CameraManager creates input manager if the script is linked to the local player by using isMine
Link the main camera to this script when the gamemanager request it
Update camera rotation only when isMine is true (second protection)
Here is a piece of my code:
GameManager.cs (Holding by a Scene Object, so it is initially instantiated for everyone with the scene)
void Start()
{
if(PlayerManager.LocalPlayerInstance == null)
{
//Get player's team
string team = (string)PhotonNetwork.LocalPlayer.CustomProperties["Team"];
int indexPlayer = GetSpawnPosition();
//Spawn player depending on its team and its index in the players pool
if (team.Equals("Spy"))
{
PhotonNetwork.Instantiate(this.spyPrefab.name, SpySpawns[indexPlayer].position, SpySpawns[indexPlayer].rotation);
}
else if (team.Equals("Defender"))
{
PhotonNetwork.Instantiate(this.defenderPrefab.name, SpySpawns[indexPlayer].position, SpySpawns[indexPlayer].rotation);
}
}
}
PlayerManager.cs (Holding by the player, so not initially instantiated with the scene)
void Awake()
{
//Keep track of the localPlayer to prevent instanciation when levels are synchronized
if (photonView.IsMine)
{
LocalPlayerInstance = gameObject;
}
//Don't destroy this gameobject so it can survives level synchronization
DontDestroyOnLoad(gameObject);
}
private void Start()
{
//Get Manager of the camera of the player and attach to the local player
CameraLookFPS cameraFPSManager = gameObject.GetComponent<CameraLookFPS>();
if (cameraFPSManager != null)
{
//Ensure that we the local player is controlling its own camera
if(photonView.IsMine == true)
{
cameraFPSManager.SynchronizeWithLocalPlayer();
}
}
else
Debug.Log("This player is missing the CameraLookFPS component");
}
CameraManager.cs (Holding by the player, so not initially instantiated with the scene)
private void Start()
{
//Synchronize camera with local player on start for debug
if(SynchronizeOnStart)
{
SynchronizeWithLocalPlayer();
}
//Bind input for controlling the camera
BindingInput();
}
private void BindingInput()
{
// Prevent control is connected to Photon and represent the localPlayer
if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
{
return;
}
else
{
//Get Components
Input_Master = new InputMaster();
//Enable
Input_Master.Enable();
//Input binding
Input_Master.Player.Look.performed += context => MoveCamera(context);
Input_Master.Player.Look.Enable();
}
}
public void SynchronizeWithLocalPlayer()
{
if (photonView.IsMine == false && PhotonNetwork.IsConnected == true)
{
return;
}
Player_Camera = Camera.main.transform;
isSynchronized = true;
}
I tried to be clear enough, tell me if something is bad explained. I'll continue my research on my side and I'll keep you in touch if I find something.
Thanks in advance for your help!
Adrien
I finally found my solution. It appears that I already read about it but I have failed when trying to solve it.
So, like a lot of people, it is a Camera issue.
What I did is:
Create a game object holding the camera and put it as a child of the player gameobject
Deactivate the game object holding the camera
When instantiating the player, check if the player is the local player, using PhotonNetwork.IsMine. If yes, activate the gameobject holding the camera through script
If you have questions, send me a message!
Adrien

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

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.

How to activated object again in unity

I created a dot following my mouse around in 2D and I created a cube object changing position on x and y. Now when I point my mouse to cube, it deactivates I set that, and now I want to activate it again. I try on trigger exit, but it didn't work.
public GameObject tapObject;
private float respawnTime = 1f;
public float xMin;
public float xMax;
public float yMin;
public float yMax;
void Start()
{
StartCoroutine(spawnEnemyTime());
}
private void RandomSpawnObject()
{
tapObject.transform.position = new Vector2(Random.Range(xMin, xMax), Random.Range(yMin, yMax));
}
private void OnTriggerEnter2D(Collider2D collision)
{
tapObject.SetActive(false);
}
IEnumerator spawnEnemyTime()
{
while (true)
{
yield return new WaitForSeconds(respawnTime);
RandomSpawnObject();
}
}
Once inactive the scripts on that object are not executed anymore => messages like OnTriggerExit are not called/executed.
One solution is to simply wrap the target object in a parent object and attach your script to the parent instead but make it (de)activate the child.
So the parent stays active and receives the message.
I am just going to repeat what everyone else here said:
A inactive object in Unity is truly inactive, meaning it does not receive any updates, can't collide with stuff and all the MonoBehaviour stuff that usually calls your code does not work either. You have to manually re-activate the object using a reference that you cached somewhere.
But, instead of just flat out disabling the whole object you could disable the components that you don't want to be active.
Example:
private void OnTriggerEnter2D(Collider2D collision)
{
tapObject.GetComponent<Renderer>().enabled = false;
}
private void OnTriggerExit2D(Collider2D collision)
{
tapObject.GetComponent<Renderer>().enabled = true;
}
This only deactivates your renderer component but leaves everything else as it is. So your object can still collide and it's still registered via e.g. OnTriggerExit.
Keep in mind that GetComponent<T>() is a pretty expensive operation so caching your component references is a good idea. The best solution would be to start out with a reference by creating a variable for it and assign it in the inspector.
Example:
//Set in inspector
public Renderer renderer
private void OnTriggerEnter2D(Collider2D collision)
{
renderer.enabled = false;
}
private void OnTriggerExit2D(Collider2D collision)
{
renderer.enabled = true;
}
When a GameObject is not active in Unity , you can't click it(no rendering,no colliding , nothing )
But ,You can create a hotkey (new script or in other script) , that can set it back to active , if it is not active.
public GameObject GO;
Use GO.setactive(true);
whereas gameobject is the object use to define the specific thing or object which needs to be active and the whole code needs to written in the method "spawnEnemyTime" so that it could be get active after the specific time period
You can just use an empty GameObject and get a reference the object that you want to enable/disable. If you get the reference before you disable it you will be able to activate it again.
the alternative is to do what TehMightyPotato said. Disable components it's actually the best way to solve this problem, but if you have lot's of components/subcomponents disable the gameobjects is faster.

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.

Categories

Resources