Unity: LoadScene does not work when fired from timer - unity3d

in my game, when the player dies, a dying sound is played and once the sound is over, the scene is supposed to be reloaded when the user still has enough lives.
Before I had the sound, the play died instantly upon calling the death() function:
public static void Death()
{
AddCoinScript.coinCounter = 0;
LivesScript.livesCounter--;
if (LivesScript.livesCounter > -1)//to get 0 live
{
Debug.Log("TIMER");
var currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
}
else
{
//TO DO GameOver
}
}
This worked like a charm.
But now I added a death sound to it. Unfortunately, unity doesnt provide an event handler for when the sound is done playing (I want the scene to be reloaded not instantly anymore, but after the death sound is done playing), so I have decided to take it upon myself to just build a timer. The timer fires right after the death sound is over. This is what this function has become:
public static void Death()
{
AddCoinScript.coinCounter = 0;
LivesScript.livesCounter--;
PlayDeathSound();
System.Timers.Timer timer = new System.Timers.Timer();
timer.Interval = aSDeath.clip.length * 1000;
timer.Start();
timer.Elapsed += delegate
{
timer.Stop();
if (LivesScript.livesCounter > -1)//to get 0 live
{
Debug.Log("TIMER");
var currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
}
else
{
//TO DO GameOver
}
};
}
As you can see, to make sure the timer REALLY fires, I set up a "debug.Log("TIMER")" to see, if it really works. And guess what: it does. The debug now shows "TIMER" in its console. But you know what doesnt work anymore? The two lines of code right beneath that.
var currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
It's the same exact lines that worked just before - but when fired from the timer, they just get ignored? How is this even possible?
When I change it all back, it works again. Only when the timer fires the two lines, they get ignored.
This is totally odd or am I missing something? Thank you!

Okay I am not an expert on C# and delegate but apparently it creates a separate thread and you can only use SceneManager.GetActiveScene on main thread.
Since i am not so sure about delegate i will offer an easier solution. You can use a coroutine since you know how much you have to wait like this:
public void Death()
{
StartCoroutine(DeathCoroutine());
}
IEnumerator DeathCoroutine()
{
AddCoinScript.coinCounter = 0;
LivesScript.livesCounter--;
PlayDeathSound();
// wait for duration of the clip than continue executing rest of the code
yield return new WaitForSeconds(aSDeath.clip.length);
if (LivesScript.livesCounter > -1)//to get 0 live
{
Debug.Log("TIMER");
var currentScene = SceneManager.GetActiveScene();
SceneManager.LoadScene(currentScene.name);
}
else
{
//TO DO GameOver
}
}

What about using a coroutine ? You just start it when the player dies, and yield while your sound is still playing.

Related

After pause menu score doesn't work anymore

does anyone know how to fix this? In my unity game when I go to pause menu and then continue playing, my scoreboard stops updating. I have two scoreboards, one in game and one in pause menu. The one in pause menu works well and updates but the one in game freezes after once visited in pause menu.
Here is my pausecodes and codes to add money (score):
public void PauseGame()
{
Time.timeScale = 0;
}
public void UnPauseGame()
{
Time.timeScale = 1;
}
}
if (collision.gameObject.tag == "Respawn") // When player lifts fish up
{
Destroy(this.gameObject);
// TODO: Player gets money (points) when this happens
textManager.instance.AddMoney();
Debug.Log("Add money");
}
public class textManager : MonoBehaviour
{
public static textManager instance;
public Text moneyText;
public int money;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
Data data = SaveSystem.LoadData();
money = data.balance;
moneyText.text = "Balance: " + money.ToString() + " $";
}
public void AddMoney()
{
money = money + 10;
moneyText.text = "Balance: " + money.ToString() + " $";
SaveSystem.SaveData(this);
}
public int findMoney()
{
return money;
}
}
Please ask more info if needed.
I have tried to delete the one scoreboard in pause menu and after that the in-game pause menu started working right, but I would like to have still that other scoreboard too.
if (collision.gameObject.tag == "Respawn") // When player lifts fish up
{
// TODO: Player gets money (points) when this happens
textManager.instance.AddMoney();
Debug.Log("Add money");
Destroy(this.gameObject);
}
As I said this is the fix for what you posted as for the pause unpause problem you have to post the actual code where you do the pause unpause behavior so can people help you out my friend. As for what you posted what you've been doing is destroying the script just before excuting the call to textManager.instance.AddMoney(); and this will never run in the order you set in your code.
The scope of a static field is global, this means there can only be one.
Your textManager (side note: a classes first letter has to be upper case) is certainly attached to multiple GameObjects, once to the object displaying the score in the scene UI and once to a UI element in the pause menu.
As soon as you pause your game the first time, the TextManager attached to the object in the pause menu will run Awake() and the instance (please capitalise your properties as well) will be overriden and the reference to the ingame TextManager gets discarded.
The sloppy fix is re-initializing the ingame TextManager when you unpause the game, assigning it to be the Instance again. I'd not recommend doing that though.
The better solution is to implement an event on the player that gets triggered when the score changes and making the player instance a Singleton object since there will be only one player in all circumstances.
The UI elements displaying the score can then subscribe to this event in OnEnable() and unsubscribe in OnDisable() (do not forget to unsubscribe from events).
Addendum: You should not destroy your object before all code has been executed. Your code will still work because of how things are managed on the C++ layer of Unity, but it is definitely bad practice.

transporting a bool across scene's while changing it

For my game you need to complete a mini-game to unlock abilities. But I atually have no clue how to do it cause the value gets resetted to false whenever I load the main-level.
Code playerMovement:
static bool FistAttackEnabled;
void Update ()
{
if (FistAttackEnabled == true)
{
if (Input.GetMouseButtonDown(0))
{
Debug.Log("Attack");
PlayerMovement.SetFloat("Attacking", 1f);
HitArea.SetActive(true);
}
}
}
Code miniGame:
void Start()
{
FistAttackEnabled = Player.GetComponent<Player_Movement>().FistAttackEnabledPortable;
}
void Update()
{
if (SheepsAmountGuess == NeededAni)
{
FistAttackEnabled = true;
}
}
But this doesnt work. I tried making a portable bool (FistAttackEnabledStatic = FistAttackEnabled) Because you cant transport static bool value's across scripts, but this also didn't work. Does anyone have a clue how to do it?
PS: The code is bigger but it doesn't have anything to do with the attack.
Each time the scene is loaded the scripts are reloaded, so variables will go to their "default" state
You can avoid the destruction of the gameObject by usign DontDestroyOnLoad(this.gameObject);
Since the GameObject wont be deleted each time you reload the scene a new copy will be created to solve that you should use a singleton(look for it you will find information easyly)
Both things will solve the problem temporaly but once you close the game everything will go to the original state. You should use some method to save the progress, PlayerPrefs is a really easy way to do it.

How do I change GameObject properties in scripts Unity?

I'm trying to save/load my game. In the load method, everytime I change the properties of a GameObject, those changes are applied and then get reverted shortly after. Here is my code:
public void Load()
{
SceneManager.LoadScene(sceneID);
List<GameObject> rootObjects = new List<GameObject>();
Scene scene = SceneManager.GetActiveScene();
scene.GetRootGameObjects(rootObjects);
int ncube = 0, npick = 0;
for (int i = 0; i < rootObjects.Count; ++i)
{
GameObject obj = rootObjects[i];
if (obj.CompareTag("Player"))
{
obj.transform.position = player.position;
obj.transform.rotation = player.rotation;
obj.transform.localScale = player.localScale;
}
else if (obj.CompareTag("Cube"))
{
obj.transform.position = cube[ncube].position;
obj.transform.rotation = cube[ncube].rotation;
obj.transform.localScale = cube[ncube].localScale;
++ncube;
}
else if (obj.CompareTag("Pickup"))
obj.SetActive(pickup[npick++]);
else if (obj.CompareTag("Door"))
obj.SetActive(door);
else if (obj.CompareTag("GreenWall"))
obj.SetActive(greenWall);
}
}
Those changes are applied to the GameObject, however they get aborted right away. How can I resolve this?
The script contains these lines of code is not a component of the GameObject.
Edit 1: Complete code updated.
Problem is I think that
Scene scene = SceneManager.GetActiveScene();
scene.GetRootGameObjects(rootObjects);
gets the objects from the scene before the Scene is fully loaded so they are reset.
From the Docu
When using SceneManager.LoadScene, the loading does not happen immediately, it completes in the next frame. This semi-asynchronous behavior can cause frame stuttering and can be confusing because load does not complete immediately.
I guess you rather should use SceneManager.sceneLoaded and do your stuff there like
public void Load()
{
SceneManager.LoadScene(sceneID);
}
And maybe in an extra component within the scene:
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// your stuff here
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
though we don't know/see where player, cube[ncube] etc come from ...
for transparting values between Scenes you should get into using ScriptableObjects
The problem might be that SceneManager.LoadScene completes in the next frame. See the documentation: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.LoadScene.html
It says:
When using SceneManager.LoadScene, the loading does not happen
immediately, it completes in the next frame. This semi-asynchronous
behavior can cause frame stuttering and can be confusing because load
does not complete immediately.
You change the values within the same frame, thus they are overwritten when loading the scene finishes. Cou could use an event to prevent that behaviour: https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager-sceneLoaded.html

How to run multiple scene of unity just once during the launching of app

I want to know how to make something happen only once like a tutorial in a game which appears only when you first start your game and then when your game got saved to a further point it never appears again even when you close your game and start again.
Basically, I have seven scenes but I want to play scene 1-5 just one on the launching of the app and then next time when the user opens the app it directly jumps to the 6th scene
So I want to know how to make something happen only once in a game application.
I hope you get my point.
Well, this issue can be easily addressed using PlayerPrefs. You can call PlayerPrefs.SetInt() and PlayerPrefs.GetInt(). Like so:
public class SceneRouting : MonoBehaviour {
void Awake(){
// -1 means it is not the first time the player launches the game
// while 1 means it is the first time the player launches the game
int isFirstTime = PlayerPrefs.GetInt("isFirstTime", -1);
if(isFirstTime > 0){
// Here you can load any of these scenes 1, 2, 3, 4, 5
} else {
// Here you can load scene 6
}
}
}
You can set the isFirstTime variable anywhere in your game flow. Say, for example, when you call the Foo() method, you don't want the game to load scenes 1-5 next time it launches:
public void Foo(){
PlayerPrefs.SetInt("isFirstTime", -1);
}
Additionally, if you want your game to load scene 6 upon launch, you can set the isFirstTime value to 1
You could have a look at using PlayerPrefs. There are multiple ways of using them but this could be one lightweight solution for you.
An example of how to use them would be like this for you;
private void Awake()
{
int tutorialFinishedFlag = PlayerPrefs.GetInt("TutorialFinished", 0);
if(tutorialFinishedFlag == 0)
{
ShowScenesOneToFive();
}
else
{
ShowSceneSix();
}
}
And when you've finished showing them the first 5 scenes, you can set the PlayerPref like so;
PlayerPrefs.SetInt("TutorialFinished", 1);

When pausing game(using a bool and time.timescale) it doesn't pause the score counter

this will be my first question so go on easy on me please ;)
I'm building my first game in Unity using my limited knowledge, tutorials and troubleshooting on google but i can't seem to fix this issue
i have a script that counts score(with a GUIText) and another one to pause the game(using timescale) and you probably guessed it already but when i pause the game it doesn't pause the score.
Script for Pausing:
var isPaused : boolean = false;
function Update()
{
if(Input.GetKeyDown("p"))
{
Pause();
}
}
function Pause()
{
if (isPaused == true)
{
Time.timeScale = 1;
isPaused = false;
}
else
{
Time.timeScale = 0;
isPaused = true;
}
}
Script for Score:
var Counter : int = 100000;
var Substractor : int = 0;
var Score : int = Counter - Substractor;
function Update (
) {
Score--;
guiText.text = "Score: "+Score;
}
the score script is attached to a gameobject with a gui text and the script for pausing the game is attached to the player
another issue is that when i'm moving(using the arrow keys) then press pause and then unpause the player moves faster and unpredictable for a splitsecond but this is only when i press pause while pressing the arrow buttons when i only press pause there is no issue, this is a small bugg that i'l try to fix myself, just thought i'd add it here for those that have an easy answer to this issue aswell
I only know this problem from other game frameworks and I've got no experiance with unity. But it should work the same way for unity as it does in other frameworks. You have two options:
Update is called after every rendering frame! So your timescale doesn'T influence how often update is called. Instead you have to find some dt or deltaT (don't know how it is called in unity and how to get it, but I mean the time, since unities the last call to update, please tell me in the comments, so I can edit this answer).
Then don't calculate your score like:
Score--;
but use something like:
Score -= 1 * dt
or
Score -= 1 * dt * Time.timeScale
(depending how exactly unity works)
alternatively you can sourround your update-block with some if-statement:
in Score:
function Update (
) {
if (! isPaused()) { // not sure how this function / property is called in unity
Score--;
guiText.text = "Score: "+Score;
}
}