The problem is that when I hit escape to go baco to main menu then I have the dont destroy objects original on the main menu and also the same objects in the DontDestroyOnLoad scene.
I have in the main menu scene 3 objects Player, Game Manager, Scene Loader that each one have attached a script DontDestroy :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DontDestroy : MonoBehaviour
{
private void Awake()
{
if (GameManager.backToMainMenu == false)
{
DontDestroyOnLoad(transform);
}
}
}
In the Game Manager object also attached another script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public SceneLoader sceneLoader;
public PlayerController playerController;
public CamMouseLook camMouseLook;
public static bool backToMainMenu = false;
public static bool togglePauseGame;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
PauseGame();
}
if (Input.GetKeyDown(KeyCode.Escape))
{
BackToMainMenu();
}
}
public void PauseGame()
{
togglePauseGame = !togglePauseGame;
if (togglePauseGame == true)
{
playerController.enabled = false;
camMouseLook.enabled = false;
Time.timeScale = 0f;
}
else
{
playerController.enabled = true;
camMouseLook.enabled = true;
Time.timeScale = 1f;
}
}
private void BackToMainMenu()
{
sceneLoader.LoadScene(0);
playerController.enabled = false;
camMouseLook.enabled = false;
Cursor.lockState = CursorLockMode.None;
Time.timeScale = 0f;
backToMainMenu = true;
}
}
When I press the escape key it's switching scenes between 1 and 0 and loading scene 0 the main menu.
But then the result is this :
So I pressed escape and back to main menu but the DontDestroyOnLoad also still loaded not removed so I have this 3 objects Player, Game Manager, Scene Loader duplicated.
If I will click on new game again the main menu scene will be remove so there will be no duplication but when back to main menu the DontDestroyOnLoad is stay.
That's exactly what DontDestroyOnLoad it's supposed to do, preserve a GameObject across multiple scenes. Your GameManager script starts with backToMainMenu stetted to false, which will trigger the execution of DontDestroyOnLoad on the Awake function the first time, but not the second (since, when you go back to the main menu, backToMainMenu is setted to true).
Yup, the objects will persists across loading of scenes. That means if there are some already in a scene to begin with there will be multiple ones after loading / reloading a scene.
I deal with by making an Init class & object in every scene, which checks a static variable, and instantiates the objects that are supposed to persist. That way you can start the game from every scene.
class Init {
public static bool hasInstantiatedController = false;
public GameObject GameController;
void Awake() {
if (!hasInstantiatedController) {
hasInstantiatedController = true;
Instantiate (GameController, transform.position, transform.rotation);
}
}
}
Related
I can't get the Player Gameobject to reappear. The player deactivates the moment the Timeline starts when they touch the box collider, but the player never reactivates. I have tried using Player.SetActive(true) in the coroutine and even using an Invoke method with no luck. Any ideas on how to fix this? Please help.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Playables;
public class TimelineTrigger : MonoBehaviour
{
// calling items for Unity
public PlayableDirector timeline;
public GameObject Player;
public GameObject CutsceneCollider;
public GameObject CutsceneMC;
// Start is called before the first frame update
void Start()
{
// calls the playable director and turns off the MC for the scene
timeline = timeline.GetComponent<PlayableDirector>();
CutsceneMC.SetActive(false);
}
void Update()
{
Player = GameObject.FindGameObjectWithTag("Player");
}
private void EnableAfterTimeline()
{
Player = GameObject.FindGameObjectWithTag("Player");
Player.SetActive(true);
}
public void OnTriggerEnter2D (Collider2D col)
{
if (col.CompareTag("Player"))
{
// plays the cutscene and starts the timer
timeline.Play();
Player.SetActive(false);
Invoke("EnableAfterTimeline", 18);
CutsceneMC.SetActive(true);
StartCoroutine(FinishCut());
}
IEnumerator FinishCut()
{
// once the cutscene is over using the duration, turns off the collider and the MC.
yield return new WaitForSeconds(17);
CutsceneMC.SetActive(false);
CutsceneCollider.SetActive(false);
}
}
}
The issue here is that coroutines in Unity can't be run on inactive GameObjects, so FinishCut never gets executed.
This can be worked around by having a separate MonoBehaviour in the scene to which the responsibility of running a coroutine can be off-loaded. This even makes it possible to start static coroutines from static methods.
using System.Collections;
using UnityEngine;
[AddComponentMenu("")] // Hide in the Add Component menu to avoid cluttering it
public class CoroutineHandler : MonoBehaviour
{
private static MonoBehaviour monoBehaviour;
private static MonoBehaviour MonoBehaviour
{
get
{
var gameObject = new GameObject("CoroutineHandler");
gameObject.hideFlags = HideFlags.HideAndDontSave; // hide in the hierarchy
DontDestroyOnLoad(gameObject); // have the object persist from one scene to the next
monoBehaviour = gameObject.AddComponent<CoroutineHandler>();
return monoBehaviour;
}
}
public static new Coroutine StartCoroutine(IEnumerator coroutine)
{
return MonoBehaviour.StartCoroutine(coroutine);
}
}
Then you just need to tweak your code a little bit to use this CoroutineHandler to run the coroutine instead of your inactive GameObject.
public void OnTriggerEnter2D (Collider2D col)
{
if (col.CompareTag("Player"))
{
// plays the cutscene and starts the timer
timeline.Play();
Player.SetActive(false);
Invoke("EnableAfterTimeline", 18);
CutsceneMC.SetActive(true);
CoroutineHandler.StartCoroutine(FinishCut()); // <- Changed
}
IEnumerator FinishCut()
{
// once the cutscene is over using the duration, turns off the collider and the MC.
yield return new WaitForSeconds(17);
CutsceneMC.SetActive(false);
CutsceneCollider.SetActive(false);
}
}
If you have only one Player instance and it's accessible from the start, you'd better set it once in the Start method.
void Start()
{
// calls the playable director and turns off the MC for the scene
timeline = timeline.GetComponent<PlayableDirector>();
CutsceneMC.SetActive(false);
Player = GameObject.FindGameObjectWithTag("Player");
}
void Update()
{
}
private void EnableAfterTimeline()
{
Player.SetActive(true);
}
I'm wondering why my pause script doesn't work as expected. It should freeze the time and bring up the Pause menu, but instead it only freezes and nothing happens after that. And i can't resume the game too
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class PauseMenu : MonoBehaviour
{
// Start is called before the first frame update
public static bool GameIsPaused = false;
public GameObject pauseMenuUI;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
if (GameIsPaused)
{
Resume();
}
else
{
Pause();
}
}
}
void Resume()
{
pauseMenuUI.SetActive(false);
Time.timeScale = 1f;
GameIsPaused = false;
}
void Pause()
{
pauseMenuUI.SetActive(true);
Time.timeScale = 0f;
GameIsPaused = true;
}
}
SetActive(false) will disable the game object, i.e. it won't update anymore. So if your pauseMenuUI is the object you call that on (or a descent of it) then your script won't be called anymore.
The solution is to put your script on another object (e.g. a parent of pauseMenuUI`).
In the Hierarchy I have two scenes when running the game first time.
The scene that contains all the gameplay objects all the objects are disabled.
The main menu scene objects are enabled.
Now this script is attached to the main menu and the function PlayGame is being called from a Play button On Click event :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public static bool sceneLoaded = false;
public void PlayGame()
{
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
SceneManager.LoadScene(0, LoadSceneMode.Additive);
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
}
private void SceneManager_sceneLoaded(Scene arg0, LoadSceneMode arg1)
{
sceneLoaded = true;
}
public void QuitGame()
{
Application.Quit();
}
}
And this script is attached to one active gameobject on the gameplay scene and all it does is to check if I'm in the main menu or not and then to decide if to setactive or not all the gameplay objects :
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
public class ActiveScene : MonoBehaviour
{
public GameObject gameData;
// Update is called once per frame
void Update()
{
if(MainMenu.sceneLoaded == true)
{
gameData.SetActive(true);
MainMenu.sceneLoaded = false;
}
if(BackToMainMenu.loadedMainMenuScene == true)
{
gameData.SetActive(false);
BackToMainMenu.loadedMainMenuScene = false;
}
}
}
This is a screenshot of the Hierarchy :
You can see that the GameObject name Active Scene in the top scene is enabled gameobject while all the other gameobjects are disabled. On this Active Scene I attached the script ActiveScene.
This way when I click the Play button a new game start and when I hit the escape key it's getting back to the main menu.
The problem is when I hit the escape key and then click the Play button again it will keep load to the Hierarchy the main menu scene over and over again.
And this is after few times I hit the escape key and then started a new game over again by clicking the Play button :
This is the script to return to the main menu using the escape key. The script is attached to the Game Manager object in the game scene :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class BackToMainMenu : MonoBehaviour
{
public static bool loadedMainMenuScene = false;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
loadedMainMenuScene = true;
}
}
}
I want to keep both scenes in the Hierarchy all the time since it's easier then to reference between variables in both scenes.
When both scenes in the Hierarchy it's also easier to restart a new game by re loading the gameplay scene.
The problem is when restarting a new game I have to set the flag bool sceneLoaded to false but then next time since it's false it will load the main menu scene to the Hierarchy over and over again.
I have in the Hierarchy one scene and 3 parent objects. The 3 parent objects are :
Game Data (Where all the gameplay objects are childs of it)
Main Menu
Game Manager (That control on pausing/unpausing the game in some cases)
The idea the main goal is when the game start it's starting with the main menu then when clicking the start new game button it will start the game when hit the escape key it will be back to the main menu and again I can start a new game or resuming the game. I also did that while the game is running if you hit the P button it will pause/unpause the game.
First the script that is attached to the Game Manager :
Here I'm doing a reference for the Dialogue System, Depth Of Field, Main Menu And in the bottom I'm using find to find back the Depth Of Field and the Dialogue System since I'm destroying the Game Data object when starting a new game :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class GameController : MonoBehaviour
{
public GameObject dialogueSystem;
public DepthOfField depthOfField;
public static bool gamepaused = false;
public GameObject mainMenu;
private void Start()
{
Time.timeScale = 0f;
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.P))
{
gamepaused = !gamepaused;
if (gamepaused)
{
Time.timeScale = 0f;
}
else
{
Time.timeScale = 1f;
}
}
if (Input.GetKeyDown(KeyCode.Escape))
{
if(depthOfField == null || dialogueSystem == null)
{
depthOfField = GameObject.Find("Player Camera").GetComponent<DepthOfField>();
dialogueSystem = GameObject.Find("Dialogue System");
}
depthOfField.DepthOfFieldInit(3f);
Time.timeScale = 0f;
dialogueSystem.SetActive(false);
mainMenu.SetActive(true);
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
}
}
The problem is that the Dialogue System and Depth Of Field are in the Game Data and when I destroy the Game Data and instantiate the Game Data using a prefab over again to make a new game to start the variables in the GameController script lost reference and I need to use find. But using find so many times is a good idea ?
This script the next one is attached to the Main Menu :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MainMenu : MonoBehaviour
{
public GameObject gameDataPrefab;
public GameObject gameData;
public GameObject mainMenu;
public void StartNewGame()
{
var gdata = GameObject.Find("Game Data");
if (gdata != null)
{
Destroy(gdata);
}
GameObject go = Instantiate(gameDataPrefab);
go.name = "Game Data";
mainMenu.SetActive(false);
GameController.gamepaused = false;
Time.timeScale = 1f;
}
public void ResumeGame()
{
}
public void QuitGame()
{
Application.Quit();
}
}
Here when I click the UI button to start a new game in the function StartNewGame I destroy the Game Data and create a new one. This make the variables in the GameController script to lost references.
The last script is attached to the Game Data this script I'm calling the method DepthOfFieldInit to make effect when I hit the escape key back to the Main Menu.
The problem is that some variables are lost references since the Game Data is destroyed. For example I'm getting null on :
playerLockMode.PlayerLockState(true, true);
Since playerLockMode is also on the Game Data that have been destroyed.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.PostProcessing;
public class DepthOfField : MonoBehaviour
{
public UnityEngine.GameObject player;
public PostProcessingProfile postProcessingProfile;
public bool dephOfFieldFinished = false;
public LockSystem playerLockMode;
private Animator playerAnimator;
private float clipLength;
private Coroutine depthOfFieldRoutineRef;
// Start is called before the first frame update
void Start()
{
if (depthOfFieldRoutineRef != null)
{
StopCoroutine(depthOfFieldRoutineRef);
}
playerAnimator = player.GetComponent<Animator>();
AnimationClip[] clips = playerAnimator.runtimeAnimatorController.animationClips;
foreach (AnimationClip clip in clips)
{
clipLength = clip.length;
}
DepthOfFieldInit(clipLength);
// Don't forget to set depthOfFieldRoutineRef to null again at the end of routine!
}
public void DepthOfFieldInit(float duration)
{
var depthOfField = postProcessingProfile.depthOfField.settings;
depthOfField.focalLength = 300;
StartCoroutine(changeValueOverTime(depthOfField.focalLength, 1, duration));
postProcessingProfile.depthOfField.settings = depthOfField;
}
public IEnumerator changeValueOverTime(float fromVal, float toVal, float duration)
{
playerLockMode.PlayerLockState(true, true);
float counter = 0f;
while (counter < duration)
{
var dof = postProcessingProfile.depthOfField.settings;
counter += Time.deltaTime;
float val = Mathf.Lerp(fromVal, toVal, counter / duration);
dof.focalLength = val;
postProcessingProfile.depthOfField.settings = dof;
yield return null;
}
playerAnimator.enabled = false;
dephOfFieldFinished = true;
depthOfFieldRoutineRef = null;
}
}
But I don't understand if I instantiate right away back the Game Data when starting a new game why the variables lost references ? And is there a better way to use Find at any place many times after the Game Data have been destroyed ? And maybe the whole code in the StartNewGame is wrong ?
My main goal is when clicking the StartNewGame start over a new game. When hit the escape key pause the game and go back to the main menu.
Here is a screenshot of my Hierarchy :
I have a loader script that loads a scene additively. The issue that I am having, is that when the new scene loads, it checks to see if it is the main active scene on Awake, if it is not then it loads the scene with this script and that scene reloads the one with the Awake check thus creating an infinite loop of scene loads.
With this script, when I try to set the main scene it says it hasn't finished loading yet. If I comment out the if statement, I get a scene load loop as mentioned above.
So, how/what can I do to set the active scene before the Awake is called on the loading scene's gameobject components?
Note: LoadScene() is triggered from a UnityEvent
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.Events;
using System.Collections;
public class GSLoadComplete : MonoBehaviour {
public LoadSceneMode mode;
[Tooltip("Set the scene as the main scene (Only when mode is set to Addictive)")]
public bool addictiveSetAsMain;
public UnityEvent onAddictiveLoadCompleted;
public void LoadScene(string sceneName) {
if (mode == LoadSceneMode.Additive) {
StartCoroutine(LoadSceneAddictive(sceneName));
} else {
SceneManager.LoadScene(sceneName, mode);
}
}
IEnumerator LoadSceneAddictive(string sceneName) {
AsyncOperation asyncLoad = SceneManager.LoadSceneAsync(sceneName, mode);
asyncLoad.allowSceneActivation = false;
// Wait until the asynchronous scene fully loads
while (!asyncLoad.isDone) {
if (asyncLoad.progress >= 0.9f) {
// Commenting this out creates an infinite load loop
if (addictiveSetAsMain) {
SceneManager.SetActiveScene(SceneManager.GetSceneByName(sceneName));
}
asyncLoad.allowSceneActivation = true;
}
yield return null;
}
onAddictiveLoadCompleted.Invoke();
}
}
This is the script that gets loaded on Awake:
using UnityEngine;
using UnityEngine.SceneManagement;
public class LoadMain : MonoBehaviour {
public string scene;
void Awake() {
if (SceneManager.GetActiveScene().name != scene) {
SceneManager.LoadScene(scene, LoadSceneMode.Single);
}
}
}