Start Game from the Last Scene Unity 3D - c#

After closing the game and reopening it, I want to load the last scene, so the player can continue from the level he reached. I've tried using PlayerPrefs as you see in the code bellow, but the game crash on startup.
GameManager Script:
bool GameHasEnded = false;
public float RestartDelay = 2f;
public float NextLevelDelay = 5f;
int level_index;
private void Start()
{
level_index = PlayerPrefs.GetInt("Last_Level");
SceneManager.LoadScene(level_index);
}
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
level_index = level_index++;
PlayerPrefs.SetInt("Last_Level", level_index);
}
public void EndGame()
{
if (GameHasEnded == false)
{
GameHasEnded = true;
Invoke("Restart", RestartDelay);
}
}
void NextLevel()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex +1);
}
void Restart()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().path);
}
}
All scenes are linked with GameManager, they all have the same code that load the next scene:
FindObjectOfType<GameManager>().CompleteLevel();

You need to specify a defaultValue for GetInt so that the first time it starts up, it can pick the appropriate scene to start at.
Also, you need to track whether or not you have already loaded at scene start, and only do this load if it hasn't yet happened. GameManager seems like it should be made into a singleton but since it's not, you can just make this loaded flag static, so that is common among all GameManager instances.
Altogether, this might look like this:
public readonly int defaultLastLevel = 1; // Set as appropriate
private static bool loaded = false;
void Start()
{
if (!loaded) {
loaded = true;
level_index = PlayerPrefs.GetInt("Last_Level", defaultLastLevel);
SceneManager.LoadScene(level_index);
}
}
Also, your CompleteLevel has some very strange code where you post-increment and assign in the same line:
level_index = level_index++;
This is extremely hard to read and actually doesn't do what you want it to do. You can do level_index = SceneManager.GetActiveScene().buildIndex + 1; instead.
Also, you need to Save changes you make to PlayerPrefs with PlayerPrefs.Save();
Altogether, this makes your CompleteLevel look like this:
public void CompleteLevel()
{
Invoke("NextLevel", NextLevelDelay);
level_index = SceneManager.GetActiveScene().buildIndex + 1; // use this instead
PlayerPrefs.SetInt("Last_Level", level_index);
PlayerPrefs.Save();
}

Related

How Can I Make This Debounce Script Work? Unity3D

This is my script, when I click the debounce works at first, but after the wait you can just spam click and shoot many bullets at once. How can I fix this? I am a beginner so any help will be nice :) I had to get rid of some because stack overflow wasn't happy
using Photon.Pun;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SingleShotGun : Gun
{
[SerializeField] Camera cam;
PhotonView PV;
public bool debounce = false;
public float debounce2;
public AudioSource audioSource;
public AudioClip audioClip;
private float timeStarted;
private float audioTime;
private void Start()
{
if (PV.IsMine)
{
audioSource.clip = audioClip;
float timeStarted = (float)PhotonNetwork.Time;
audioTime = 0;
}
}
private void Update()
{
if (PV.IsMine)
{
float audioTime = (float)PhotonNetwork.Time - timeStarted;
}
else
{
audioSource.time = audioTime;
}
}
void Awake()
{
PV = GetComponent<PhotonView>();
}
public override void Use()
{
if (debounce)
{
StartCoroutine(Deb());
return;
}
if (PV.IsMine)
{
audioSource.clip = audioClip;
float timeStarted = (float)PhotonNetwork.Time;
}
debounce = true;
Shoot();
}
private IEnumerator Deb()
{
Debug.Log("Debouncing");
yield return new WaitForSeconds(debounce2);
debounce = false;
}
}
I Tried to make a debounce script for unity3d, but it didn't work?
As a first note: in
float audioTime = (float)PhotonNetwork.Time - timeStarted;
you are creating a new local variable => the class field audioTime is not assigned in that case
same also for all occurrences of
float timeStarted = (float)PhotonNetwork.Time;
you want to remove the float in all three places in order to rather assign your class fields instead of over shadowing them with same named local variables, otherwise they will always have the default value 0.
Then within Use you are starting multiple concurrent routines that will all finish eventually and reset your debounce in unexpected moments. You probably rather want to wrap it in order to start only a single routine like
if(!debounce)
{
debounce = true;
Shoot();
StartCoroutine(Deb()):
}
Also again in general I would expect the entire Use should be wrapped in / start with
if (!PV.IsMine) return;
as it looks and sounds like none of those things should happen at all if this is not your gun ;)
And after setting
audioSource.clip = audioClip;
you also need to
audioSource.Play();

Trying to Re-Instantiate the Zombie(main character) again when replay button is clicked, but unable to

I am making Replay logic for my game, where when I click replay I got to the Main Page. The problem I am facing is that after clicking Play on the game after coming from Replay, the Zombie character in my game is not showing up. The game is running without the player. I am posting the script, check the Replay function which is attached to Replay button in the game.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Assertions;
public class GameManager : MonoBehaviour
{
public static GameManager instance = null;
private bool playerActive = false;
private bool gameOver = false;
private bool gameStarted = false;
private GameObject newZombie;
[SerializeField] private GameObject mainMenu; //contains main menu content
[SerializeField] private GameObject endGame; //contains game over content
[SerializeField] private GameObject zombie;
public bool PlayerActive{
get{
return playerActive;
}
}
public bool GameOver{
get{
return gameOver;
}
}
public bool GameStarted{
get{
return gameStarted;
}
}
void Awake()
{
if(instance == null){
instance = this;
}else if(instance != this){
Destroy(gameObject);
}
Assert.IsNotNull(mainMenu);
Assert.IsNotNull(endGame);
DontDestroyOnLoad(gameObject);
}
// Start is called before the first frame update
void Start()
{
endGame.SetActive(false);
mainMenu.SetActive(true);
}
// Update is called once per frame
void Update()
{
}
public void PlayerCollided()
{
gameOver = true;
endGame.SetActive(true);
mainMenu.SetActive(false);
DontDestroyOnLoad(gameObject);
}
public void PlayerStartedGame()
{
playerActive = true;
}
public void EnterGame()
{
endGame.SetActive(false);
mainMenu.SetActive(false);
gameStarted = true;
}
public void Replay()
{
endGame.SetActive(false);
mainMenu.SetActive(true);
gameOver = false;
newZombie = Instantiate(zombie) as GameObject;
}
There are a lot of assumptions we have to make based on the information you gave.
Try instantiating the zombie on a specific location. You're using Instantiate(gameObject), but there's a different variant to the Instantiate method which also takes a Vector3 position to spawn the object as a second argument.
If that doesn't work, please reply with answers to the following questions:
Does the zombie spawn at all (is it in the hierarchy)
Which methods exactly do the buttons invoke, for example:
[1]: https://i.stack.imgur.com/9xbgy.png
You're using a singleton pattern but you don't change scenes in this class. Are you changing scenes in any of your scripts? If yes, you will have to consider looking into them because every script that is persisting between scenes with the DontDestroyOnLoad() method could potentially interfere with your player.
Vlad

Unity is throwing an UnassignedReferenceException during runtime even after reference has been set

I'm currently working on a script for a basic pause menu. The script is passed two GameObjects through the inspector, each being a different menu, and alternates between setting each object as enabled while setting the other as disabled. The code had worked fine before I moved it to a different script while restructuring my codebase. The script still executes properly, but each time I perform the operation Unity throws an UnassignedReferenceException, telling me to set the reference to the HUD in the inspector. The reference is clearly set and the operation is being performed, but the error is still thrown. I have checked for other instances of the script, and none are present.
Here is the full script in question:
public class GameMenuHandler : MonoBehaviour
{
private bool paused;
[SerializeField] private GameObject HUD;
[SerializeField] private GameObject pauseMenu;
void Start()
{
SubscribeMethodsToEvents();
}
public void RestartLevel()
{
String scene_name = SceneManager.GetActiveScene().name;
SceneManager.LoadScene(scene_name);
}
public void ExitToMainMenu()
{
SceneManager.LoadScene("MainMenu");
}
public void TogglePauseMenu()
{
paused = !paused;
if (paused)
{
HUD.SetActive(false);
pauseMenu.SetActive(true);
Time.timeScale = 0f;
}
else
{
HUD.SetActive(true);
pauseMenu.SetActive(false);
Time.timeScale = 1f;
}
}
void SubscribeMethodsToEvents()
{
GameEvents.current.onEscapeKeyPress += TogglePauseMenu;
}
void UnsubscribeMethodsFromEvents()
{
GameEvents.current.onEscapeKeyPress -= TogglePauseMenu;
}
void OnDestroy()
{
UnsubscribeMethodsFromEvents();
}
}
Here is the inspector view of the script:
Here is the console after the operation had been performed (successfully) numerous times:
One of the best ways to avoid this error is to set the condition != Null. Try this:
if (HUD) HUD.SetActive(false);
if (HUD) HUD.SetActive(true);

can't reset specfic playerprefs in unity3d

I have made 2 codes to manage my interstitial ads by making the ads show every 5 mins when the player losses but the problem is that I tried to reset them when the player passes the 5 mins and press the button when he losses but it didn't work so how to reset the timer when the player presses the button?
this is the 1st code :
public int LastShownIntTime = 300;
void Start()
{
#if UNITY_ANDROID
Advertisement.Initialize(androidID);
#endif
}
public void Update()
{
LastShownIntTime = PlayerPrefs.GetInt("LastShownIntTime");
}
public void showInterstitial()
{
if (LastShownIntTime <=0)
{
showInterstitialwith5mint();
}
}
public void showInterstitialwith5mint()
{
Advertisement.Show("video");
PlayerPrefs.SetInt("LastShownIntTime", 300);
}
and the 2nd one :
public float LastShownIntTimefloat;
public int LastShownIntTime = 300;
void Start()
{
LastShownIntTime = PlayerPrefs.GetInt("LastShownIntTime");
LastShownIntTimefloat = LastShownIntTime;
}
public void Update()
{
LastShownIntTimefloat -= Time.deltaTime;
LastShownIntTime = (int)LastShownIntTimefloat;
PlayerPrefs.SetInt("LastShownIntTime", LastShownIntTime);
}
}
The main issue here:
You would have to reset the LastShownIntTimefloat in your script2!
Otherwise you simply continue overwriting it with new values reducing the value more and write it back to PlayerPrefs
→ the next time your script1 polls the value it is not reset but already overwritten by script2!
In general: You should not use PlayerPrefs in order to make two components communicate!
In your case here I wouldn't even separate the logic and bother with implementing the communication between them but rather merge them into one single component.
Then it is not necessary to read and write PlayerPrefs every frame but rather only on certain checkpoints like
Read once in Start
Write once in OnApplicationQuit
Write once in OnDestroy (This is for the case you e.g. switch Scene but don't quit the app)
Write once ever time your user loses (showInterstitial is called)
Write once when resetting the value after showing the advertisement
I would also simply directly use a float and GetFloat and SetFloat instead of converting it from and to an int.
public class MergedClass : MonoBehaviour
{
// Rather sue a FLOAT for time!
public float LastShownTime = 300;
void Start()
{
#if UNITY_ANDROID
Advertisement.Initialize(androidID);
#endif
// use 300 as default value if no PlayerPrefs found
LastShownTime = PlayerPrefs.GetFloat("LastShownTime", 300f);
}
public void Update()
{
if(LastShownTime > 0f) LastShownTime -= Time.deltaTime;
}
public void showInterstitial()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
if (LastShownTime <= 0f)
{
showInterstitialwith5mint();
}
}
public void showInterstitialwith5mint()
{
#if UNITY_ANDROID
Advertisement.Show("video");
#else
LastShownTime = 300f;
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
private void OnApplicationQuit()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
private void OnDestroy()
{
PlayerPrefs.SetFloat("LastShownTime", LastShownTime);
PlayerPrefs.Save();
}
}

DontDestroyOnLoad through scenes for Audio

I can't change the volume of the music more than 1 time. The volume control is on a "Settings" scene, so when I move to this scene I can change the volume of the game sound, but when i go out of the scene and come back I can't. I guess that happens cause of my bad DontDestroyOnLoad usage.
Once I change the volume the DontDestroyOnLoad comes in so the volume doesn't change once you go out of the scene, but when you come back to Settings to change it back it doesn't work cause of the DontDestroyOnLoad.
How can I solve this so I can change it wherever I want?
public class soundCont : MonoBehaviour {
private static float musicVolume = 1f;
private static float SetVolumeFX = 1f;
private void Start()
{
musicVolume = Musicafondo.instance.musicSource.volume;
}
void Update ()
{
MusicBack.instance.musicSource.volume = musicVolume;
MusicBack.instance.efxSource.volume = SetVolumeFX;
MusicBack.instance.efxSourceEnemy.volume = SetVolumeFX;
DontDestroyOnLoad(this.gameObject);
}
public void SetVolume(float vol)
{
musicVolume = vol;
}
public void SetVolumeFx(float vol2)
{
SetVolumeFX = vol2;
}
}
Both the instance you're using for the volumes are don't destroy on load, so you do not need to store the last values of the volume on a field.
This code solve your issue:
private void Start()
{
DontDestroyOnLoad(this.gameObject);
MusicBack.instance.musicSource.volume = 1f;
MusicBack.instance.efxSource.volume = MusicBack.instance.efxSourceEnemy.volume = 1f;
}
public void SetVolume(float vol) { MusicBack.instance.musicSource.volume = 1f; }
public void SetVolumeFx(float vol2)
{
MusicBack.instance.efxSource.volume = MusicBack.instance.efxSourceEnemy.volume = vol2;
}
SetVolume will change the music volume and SetVolumeFx will change the volume of the sound effects.
Make also sure SetVolume and SetVolumeFx are called properly.

Categories

Resources