Background music get louder each time a scene is loaded - c#

I have a GameObject on level01 with an Audio Source and the script below.
When the game starts the script runs and music starts playing.
The problem I have is that each time I load a new level the sound gets louder. I don't understand why this is happening. Can someone explain why and give a solution or point me in the right direction?
using UnityEngine;
using System.Collections;
public class MusicManagerScript : MonoBehaviour
{
public AudioClip[] songs;
int currentSong = 0;
// Use this for initialization
void Start() {
DontDestroyOnLoad(gameObject);
}
// Update is called once per frame
void Update() {
if (audio.isPlaying == false) {
currentSong = currentSong % songs.Length;
audio.clip = songs[currentSong];
audio.Play();
currentSong++;
}
}
}

EDIT: I see that your answer was that your camera was simply closer to the 3D audio source, but I've put my answer here anyway as it is a common solution to a common problem.
You're instantiating your music manager every time you enter a scene with your music manager in it, but you're never destroying a music manager, which duplicates the sound. What you need is a singleton - a way of telling your code never to allow multiple instances. Try this:
public class MusicManagerScript : MonoBehaviour
{
private static MusicManagerScript instance = null;
public AudioClip[] songs;
int currentSong = 0;
void Awake()
{
if (instance != null)
{
Destroy(this);
return;
}
instance = this;
}
// Use this for initialization
void Start()
{
DontDestroyOnLoad(gameObject);
}
// Update is called once per frame
void Update()
{
if (audio.isPlaying == false)
{
currentSong = currentSong % songs.Length;
audio.clip = songs[currentSong];
audio.Play();
currentSong++;
}
}
void OnDestroy()
{
//If you destroy the singleton elsewhere, reset the instance to null,
//but don't reset it every time you destroy any instance of MusicManagerScript
//because then the singleton pattern won't work (because the Singleton code in
//Awake destroys it too)
if (instance == this)
{
instance = null;
}
}
}
Because instance is static, every music manager script will have access to it. If it's been set already, they destroy themselves when they're created.

Related

Background music destroyed when build index is 0

I've got this background sound playing throughout the game.
The problem is that I want it to stop when the scene index is 0.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BackgroundMusic : MonoBehaviour
{
void Start()
{
GameObject[] objs = GameObject.FindGameObjectsWithTag("music");
if (objs.Length > 1)
{
Destroy(this.gameObject);
}
if (SceneManager.GetActiveScene().buildIndex != 0)
{
DontDestroyOnLoad(this.gameObject);
}
}
I've been using this script but still doesn't work
Any suggestions?
If you use DontDestroyOnLoad it is "forever" you don't have to do it for each scene again.
And then note that after that the Start is not called for every scene load again only the first time!
Rather use SceneManager.sceneLoaded like e.g.
public class BackgroundMusic : MonoBehaviour
{
private static BackgroundMusic _instace;
private void Awake()
{
if (_instance && _instance != this)
{
Destroy(this.gameObject);
return;
}
DontDestroyOnLoaddd(this.gameObject);
_instance = this;
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDestroy ()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private void OnSceneLoaded (Scene scene, LoadSceneMode mode)
{
if(scene.buildIndex == 0)
{
// TODO disable música
// NOTE: I wouldn't Destroy this though but rather only stop the music
}
else
{
// TODO enable the music
}
}
}
The SIMPLEST way to do this with success :
Check the name of the music and if the name is different, you destroy the Game Object, else you keep it playing and don't destroy it ( exemple : if you die when you are playing, the music continue to play and do not start from the beggining when the level restart, but if you go to the menu or change the level, the music will change without any problem and the old music is destroyed safely. )
Here my own code to solve this problem :
private void Awake() {
GameObject[] audioSource = GameObject.FindGameObjectsWithTag("Music");
if (audioSource.Length>1)
{
if (audioSource[0].GetComponent<AudioSource>().clip.name != _audioSource.clip.name)
{
Destroy(audioSource[0].gameObject);
} else {
Destroy(this.gameObject);
}
}
DontDestroyOnLoad(this.gameObject);
}

How to prevent the reload of already loaded gameobjects in Unity?

I'm currently developing a game in Unity and I ran into a small problem. I'm working on a restart function that gets called automatically when the player dies and loads the first scene again. However for some reason when reloading the scene games objects are duplicated with the version of the gameobject that was active at the time of death being inactive and the version that gets loaded as should be loaded getting set to active and so on every time the player dies adding a new duplicate of the same gameobjects to the hierarchy. I tried to solve this problem in multiple ways. First by trying to check each the gameobjects that get duplicated already have an instance of themselves running by attaching a script that checks every time a change in scene occurs wether or not their already is an instance of the gameobjects present:
public static GameObject Instance;
void Awake()
{
if(Instance){
DestroyImmediate(gameObject);
}else
{
DontDestroyOnLoad(gameObject);
Instance = this;
}
}
This seemed to solve the problem at first but it became to teadious towards the end because the scripts made all of my other scene objects behave badly or not at all so I chose to look for another solution.
Secondly I tried to Destroy each individual gameobject before I start loading the first scene. This also seemed to work at first but now my object pooler just recreates new intances of the gameobjects that it adds too the hierarchy esentially displacing the same problem to other gameobjects.
Finally in order to solve this problem I tried to make my objectpooler run only once when the scene that requires it to be loaded gets called but this didn't seem to work either. Does anyone have any idea how I could solve this problem. This is part of the script responsible for loading the original scene upon player death:
void Restart()
{
GameObject[] allObjects = UnityEngine.Object.FindObjectsOfType<GameObject>();
foreach (GameObject gos in allObjects)
{
if (gos.activeInHierarchy)
{
if (gos != GameObject.Find("GameManager") && gos != GameObject.Find("ScreenBound"))
{
gos.SetActive(false);
}
}
}
MySceneManager.LoadScene(0, this);
}
How could I change this in order to be able to reload the original scene without having any previously loaded GameObject get duplicated and behave according to how it should in the scene in which it got loaded originally?
The class responsible for loading and deloading scenes:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public static class MySceneManager
{
private static int lastLoadedScene = 0;
public static void LoadScene(int index, MonoBehaviour caller)
{
ObjectPooler objP = new ObjectPooler();
objP.ReleaseAll();
caller.StartCoroutine(loadNextScene(index));
}
private static IEnumerator loadNextScene(int index)
{
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
yield return null;
}
_async.allowSceneActivation = true;
while (!_async.isDone)
{
yield return null;
}
var newScene = SceneManager.GetSceneByBuildIndex(index);
if (!newScene.IsValid()) yield break;
SceneManager.SetActiveScene(newScene);
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
lastLoadedScene = index;
}
}
This is my ObjectPooler:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectPooler : MonoBehaviour
{
[System.Serializable]
public class Pool
{
public string tag;
public GameObject prefab;
public int size;
}
#region Singleton
public static ObjectPooler Instance;
private void Awake()
{
if (Instance)
{
Destroy(this.gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
#endregion
public List<Pool> pools;
public Dictionary<string, Queue<GameObject>> poolDictionary;
private Dictionary<string, Pool> prefabPools;
void Start()
{
poolDictionary = new Dictionary<string, Queue<GameObject>>();
foreach (Pool pool in pools)
{
Queue<GameObject> objectPool = new Queue<GameObject>();
for (int i = 0; i < pool.size; i++)
{
GameObject obj = Instantiate(pool.prefab);
DontDestroyOnLoad(obj);
obj.SetActive(false);
objectPool.Enqueue(obj);
}
poolDictionary.Add(pool.tag, objectPool);
}
}
private List<GameObject> currentlySpawnedObjects = new List<GameObject>();
public void Release(GameObject obj)
{
currentlySpawnedObjects.Remove(obj);
obj.SetActive(false);
obj.transform.SetParent(transform);
poolDictionary[obj.tag].Enqueue(obj);
DontDestroyOnLoad(obj);
}
public void ReleaseAll()
{
foreach (var child in currentlySpawnedObjects)
{
Release(child);
}
}
public GameObject SpawnFromPool(string tag, Vector3 position, Quaternion rotation)
{
if (!poolDictionary.ContainsKey(tag))
{
Debug.LogWarning("Pool with tag" + tag + " doesn't exist.");
return null;
}
GameObject objectToSpawn = poolDictionary[tag].Dequeue();
objectToSpawn.SetActive(true);
objectToSpawn.transform.position = position;
objectToSpawn.transform.rotation = rotation;
IPooledObject pooledObj = objectToSpawn.GetComponent<IPooledObject>();
if (pooledObj != null)
{
pooledObj.OnObjectSpawn();
}
poolDictionary[tag].Enqueue(objectToSpawn);
return objectToSpawn;
currentlySpawnedObjects.Add(objectToSpawn);
return objectToSpawn;
}
}
Depends of your needs you can try next ways:
Use singleton pattern if you need save single instance of objects. This case relevant for saving managers (GameplayManager, SceneController, AssetBundleManager, etc.) in other cases will be better to use other ways. To read more about implementation you can see this article.
Destroy all old objects when loaded new scene. To do this you can use SceneManager.LoadScene method with LoadSceneMode.Single as parameter. It will keep DontDestoryOnLoad objects but will remove all others.
I'm not sure but the first possible issue to me already seems to be that it is running in Coroutine on an object in the scene you are going to unload.
It is cool that this is doable but have in mind that the Coroutine will stop working as soon as the caller object/component is destroyed or disabled.
To avoid that I would move your script to an object in the DontDestroyOnLoadScene using a Singleton pattern.
The next issue might be you going by SceneIndex ... both scenes, the one you want to unload and the one you want to load have index 0!
So maybe you get a conflict between the scene additively loading and the one you want to unload.
This also might happen again when you called
var newScene = SceneManager.GetSceneByIndex(lastLoadedScene);
To avoid this I would rather go by scene reference for the unloading
public class MySceneManager : MonoBehaviour
{
private static MySceneManager instance;
// Lazy initialization
// With this you wouldn't even need this object in the scene
public static MySceneManager Instance
{
if(instance) return instance;
instance = new GameObject ("MySceneManager").AddComponent<MySceneManager>();
DontDestroyOnLoad(instance);
}
// Usual instant initialization having this object in the scene
private void Awake ()
{
if(instance && instance != this)
{
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(this);
}
public void LoadScene(int index)
{
StartCoroutine(loadNextScene(index));
}
private IEnumerator loadNextScene(int index)
{
// I didn't completely go through your ObjectPooler but I guess you need to do this
ObjectPooler.Instance.ReleaseAll();
// Instead of the index get the actual current scene instance
var currentScene = SceneManager.GetActiveScene();
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
_async.allowSceneActivation = false;
yield return new WaitWhile(() => _async.progress < 0.9f);
_async.allowSceneActivation = true;
yield return new WaitUntil(() => _async.isDone);
// You have to do this before otherwise you might again
// get by index the previous scene
var unloadAsync = SceneManager.UnloadSceneAsync(currentScene);
yield return new WaitUntil(()=>unloadAsync.isDone);
var newScene = SceneManager.GetSceneByBuildIndex(index);
SceneManager.SetActiveScene(newScene);
}
}
Alternatively since anyway you do nothing special while loading/unloading the scenes:
why using Additive scene loading at all if you could also simply call
ObjectPooler.Instance.ReleaseAll();
SceneManager.LoadSceneAsync(index);
without making it additive so the current scene is simply removed automatically as soon as the new scene is fully loaded.
Note: Types on Smartphone so no warranty but I hope the idea gets clear

How to fade out and destroy audio on load

I have got a basic script that allows the music to continue playing from scene 2 until scene 6, but I have been trying most of the day to get it where the audio starts to fade out and then is destroyed on scene 6, before loading into scene 7.
I have searched on here, google , youtube and even the Unity forums, but nothing works as all i am getting in my search is how to make the music continue
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class Music : MonoBehaviour
{
// Start is called before the first frame update
public GameObject theManager;
public AudioSource music;
public string loadLevel;
public float fadeOutTime = 3f;
bool fading;
float fadePerSec;
void Awake()
{
DontDestroyOnLoad(theManager);
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void Update()
{
if (fading)
{
music.volume = Mathf.MoveTowards(
music.volume, 0, fadePerSec * Time.deltaTime);
}
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if ((scene.buildIndex != 2) && (scene.buildIndex != 3) && (scene.buildIndex != 4) && (scene.buildIndex != 5) && (scene.buildIndex != 6))
{
fading = true;
fadePerSec = music.volume / fadeOutTime;
Destroy(theManager, fadeOutTime);
}
}
}
Here are 2 screenshots of my project
Project layout
Inspector of ScriptManager
Any help appreciated and thanks in advance
Main Issue: The GameObject with this script attached is detroyed
The ScriptManager GameObject having this script attached itself is destroyed when you switch a Scene! Therefore OnSceneLoaded is probably never even called at all.
Then I would not use the field public GameObject theManager; ... you already have the field public AudioSource music; so I would rather use music.gameObject whenever a GameObject reference is required. Just to avoid mistakes.
Finally I would not use the Update method for checking a bool flag constantly just for a one-time event. I would recommend to rather use a Coroutine:
so something like
public class Music : MonoBehaviour
{
public AudioSource music;
public string loadLevel;
public float fadeOutTime = 3f;
private static Music _instance;
void Awake()
{
DontDestroyOnLoad(music.gameObject);
// If necessary use a singleton pattern to make sure this exists only once
if(_instance)
{
Destroy(gameObject);
return;
}
_instance = this;
// Also don't destroy yourself!
DontDestroyOnLoad(gameObject);
// Just to be sure before adding a callback you should always remove it
// This is valid even if it wasn't added yet
// but it makes sure it is only added exactly once
SceneManager.sceneLoaded -= OnSceneLoaded;
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
private IEnumerator FadeOutAndDestroy()
{
var fadePerSec = music.volume / fadeOutTime;
while(music.volume > 0)
{
music.volume = Mathf.MoveTowards(music.volume, 0, fadePerSec * Time.deltaTime);
// yield says: Interupt the routine here, render this frame
// and continue from here in the next frame
// In other words: Coroutines are like small temporary Update methods
yield return null;
}
// Now the volume is 0
Destroy(music.gameObject);
// and since no longer needed also destroy this object
Destroy(gameObject);
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (scene.buildIndex != 7) return;
StartCoroutine(FadeOutAndDestroy());
}
}

Getting print to only print once when countdown Deltatime reaches certain number (Unity)

I have a 10-second countdown script that runs on DeltaTime.
In my Update function, I'm trying to make it print, only once, "Hello" whenever it reaches second 8.
The problem is that deltaTime is repeatedly printing "Hello" while it hangs on the 8th second, rather than only printing once, and I don't know how to stop that behavior.
I kept trying to introduce triggers in the if-block that set to 0 as soon as the block is entered but it still keeps continuously printing "Hello" so long as the timer is on second 8.
Countdown Timer
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public int n = 0;
public int switcher = 0;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
}
// Update is called once per frame
void Update()
{
currentTime -= 1 * Time.deltaTime; //does it each frame
n = Convert.ToInt32(currentTime);
if (n == 8)
{
switcher = 1;
}
}
}
Update Method in different class
if (CountdownTimer.switcher == 1)
{
CountdownTimer.switcher = 0;
print("hey");
}
Any ideas on how to make print("hey") only happen once? It's important because later I would replace the print code with an important method and I need to make sure the method happens only once.
This is where you want to implement a system of subscriber with event/listener.
Add an event to the countdown, if countdown is meant to be unique, you can even make it static. Also, if the update is no longer needed after the setting of switcher to 1 then you can convert that to coroutine
public class CountdownTimer : MonoBehaviour
{
float currentTime = 0f;
float startingTime = 10f;
public static event Action RaiseReady;
// Start is called before the first frame update
void Start()
{
currentTime = startingTime;
StartCoroutine(UpdateCoroutine());
}
// Update is called once per frame
IEnumerator UpdateCoroutine()
{
while(true)
{
currentTime -= 1 * Time.deltaTime; //does it each frame
int n = Convert.ToInt32(currentTime);
if (n == 8)
{
RaiseReady?.Invoke();
RaiseReady = null; // clean the event
yield break; // Kills the coroutine
}
yield return null;
}
}
}
Any component that needs to know:
public class MyClass : MonoBehaviour
{
void Start()
{
CountdownTimer.RaiseReady += CountdownTimer_RaiseReady;
}
private void CountdownTimer_RaiseReady()
{
Debug.Log("Done");
// Remove listener though the other class is already clearing it
CountdownTimer.RaiseReady -= CountdownTimer_RaiseReady;
}
}
The solution #Everts provides is pretty good, but as you are using unity I would recommend a couple of tweaks to play nicer with the editor. Instead of a generic event I recommend using a UnityEvent from the UnityEngine.Events namespace. I would also advise against statics due to how unity goes about serializing them across scenes. There are some weird edge cases you can get into if you aren't familiar with how unity handles their serialization. If you just need to send a message to another object in the same scene I would actually recommend a game manager. You can safely do a GameObject.Find() in onvalidate() and link your variables to avoid a performance hit at runtime doing the find. If that data needs to carry across to a different scene for this message then use a ScriptableObject instead. It would look something like below.
Put this component on the scene's "Game Manager" GameObject
public class CountingPassthrough : MonoBehaviour
{
public CountdownTimer countdownTimer;
}
put this component on the scene's "Timer" GameObject
public class CountdownTimer : MonoBehaviour
{
public float startingTime = 10f;
public UnityEvent timedOut = new UnityEvent();
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
FindObjectOfType<CountingPassthrough>().countdownTimer = this;
}
private void Start()
{
StartCoroutine(TimerCoroutine());
}
// Coroutine is called once per frame
private IEnumerator TimerCoroutine()
{
float currentTime = 0f;
while (currentTime != 0)
{
currentTime = Mathf.Max(0, currentTime - Time.deltaTime);
yield return null;//wait for next frame
}
timedOut.Invoke();
}
}
Put this component on the GameObject you want to use the timer
public class user : MonoBehaviour
{
[SerializeField, HideInInspector]
private CountingPassthrough timerObject;
private void OnValidate()
{
if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene())
timerObject = FindObjectOfType<CountingPassthrough>();
}
private void OnEnable()
{
timerObject.countdownTimer.timedOut.AddListener(DoSomething);
}
private void OnDisable()
{
timerObject.countdownTimer.timedOut.RemoveListener(DoSomething);
}
private void DoSomething()
{
//do stuff here...
}
}
This workflow is friendly to prefabs too, because you can wrap the find() in onvalidate() with if(FindObjectOfType<CountingPassthrough>().gameObject.scene == gameObject.scene) to prevent grabbing the wrong asset from other loaded scenes. And again, if you need this to carry data across scenes then have CountingPassthrough inherit from ScriptableObject instead of MonoBehaviour, create the new ScriptableObject to your project folder somewhere, and ignore that extra if check to constrain scene matching. Then just make sure you use a function to find it that includes assets if you use the cross-scene ScriptableObject approach.
EDIT:Forgot nested prefabs edgecase in unity 2018+ versions. You need to add this to account for it: && FindObjectOfType<CountingPassthrough>() != new UnityEngine.SceneManagement.Scene() I've updated the code snippet above. Sorry about that.
Since Updtade() is called once per frame, then switcher is set to 1 once per frame during the 8th second (and there is a lot of frames in 1 sec).
An answer could be something like this to prevent it from printing again :
if (CountdownTimer.switcher == 1)
{
if (!AlreadyDisplayed)
{
print("hey");
AlreadyDisplayed = true;
}
}
Where AlreadyDisplayed is a Boolean set to false when declared.
This should do what you want to achieve. :)

animation.Play() doesn't work

I'm pretty sure that
animation.Play("DoorOpen");
Would play the animation "DoorOpen", but when i'm trying to put it in my code, it just giving me an error message:
The Animation attached to this GameObject (null if there is none attached).
using UnityEngine;
using System.Collections;
public class DoorPhysics : MonoBehaviour {
int Open = 0;
// Update is called once per frame
void Update() {
if (Open == 0) {
if (Input.GetKeyDown("e")) {
animation.Play("DoorOpen");
}
}
}
}
You need to show location of gameobjects in unity, they do not know eachother, you have to always use :
GameObject.GetComponent<T>()
GetComponentInParent<T>()
GetComponentInChildren<T>()
best practice is to get object references at Start()
also you should attach IMPORTANT!!! Animation component to the object this script it attached to
public class DoorPhysics : MonoBehaviour {
public Animation animation;
int Open = 0;
void Start()
{
animation=GameObject.GetComponent<Animation>(); //if your have derived type change Animation to good class DoorAnimation for example
}
void Update()
{
if (Open == 0) {
if (Input.GetKeyDown("e")) {
this.animation.Play("DoorOpen");
}
}
}
}
if that code wont work, you will need show me your GameObject hierarchy
And if your just start your trip with it learn MonoBehaviour call order
and life cycles of events

Categories