In the Hierarchy I have 3 objects I want to keep and to not destroy when starting a new game.
But I want to destroy them when switching back to the main menu.
The objects are : Player , Game Manager , Scene Loader
On the 3 objects Player , Game Manager , Scene Loader I added to each one a script name PersistentManager:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PersistentManager : MonoBehaviour
{
public static PersistentManager Instance { get; private set; }
private void Awake()
{
if(Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
The Scene Loader script that attached to Scene Loader :
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class SceneLoader : MonoBehaviour
{
private bool loadScene = false;
[SerializeField]
private int scene;
[SerializeField]
private Text loadingText;
// Updates once per frame
void Update()
{
// If the player has pressed the space bar and a new scene is not loading yet...
if (Input.GetKeyUp(KeyCode.Space) && loadScene == false)
{
LoadScene(scene);
}
// If the new scene has started loading...
if (loadScene == true)
{
// ...then pulse the transparency of the loading text to let the player know that the computer is still working.
loadingText.color = new Color(loadingText.color.r, loadingText.color.g, loadingText.color.b, Mathf.PingPong(Time.time, 1));
}
}
// The coroutine runs on its own at the same time as Update() and takes an integer indicating which scene to load.
IEnumerator LoadNewScene()
{
// This line waits for 3 seconds before executing the next line in the coroutine.
// This line is only necessary for this demo. The scenes are so simple that they load too fast to read the "Loading..." text.
//yield return new WaitForSeconds(3);
// Start an asynchronous operation to load the scene that was passed to the LoadNewScene coroutine.
AsyncOperation async = SceneManager.LoadSceneAsync(scene);
// While the asynchronous operation to load the new scene is not yet complete, continue waiting until it's done.
while (!async.isDone)
{
yield return null;
}
}
public void LoadScene(int scene)
{
// ...set the loadScene boolean to true to prevent loading a new scene more than once...
loadScene = true;
// ...change the instruction text to read "Loading..."
loadingText.text = "Loading...";
this.scene = scene;
// ...and start a coroutine that will load the desired scene.
StartCoroutine(LoadNewScene());
}
}
And the Game Manager script that attached to Game Manager :
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;
}
}
Now when I'm running the game and then pressing the space bar for starting a new game the Main Menu scene is removed not sure why.
Before using the PersistentManager script I used on each of the 3 objects another script :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DontDestroy : MonoBehaviour
{
private void Awake()
{
if (GameManager.backToMainMenu == false)
{
DontDestroyOnLoad(transform);
}
}
}
That keep the 3 objects alive when starting a new game and if switching back to the main menu the problem was that the 3 objects was also in the main menu when switching back to the main menu.
So I had 6 copies of the objects. 3 in the main menu scene and 3 in the DontDestroyOnLoad scene.
That's why I'm trying to use singleton.
I want that when I'm switching back to the main menu the 3 object will not be in the main menu scene in the hierarchy only on the DontDestroyOnLoad.
The problem is that the class you are inheriting from has it's instance in the parent
public static PersistentManager Instance { get; private set; }
this means that the first object will go
Is instance null? yes
Set me as instance
Set a don't destroy on load
the other two objects will check the instance, see that it's not null because the first object is referenced, and they will destroy themselves.
In general I would advise against using singletons as much as possible, as some might consider them an anti design pattern.
However, if you still want to use one; I'm attaching here a script I used in games that are in production for multiple years with millions of users, so it's already set up against most of the edge cases we encountered.
#region Using
using System.Linq;
using UnityEngine;
using UnityEngine.SceneManagement;
#endregion
/// <summary>
/// Generic singleton Class. Extend this class to make singleton component.
/// Example:
/// <code>
/// public class Foo : GenericSingleton<Foo>
/// </code>
/// . To get the instance of Foo class, use <code>Foo.instance</code>
/// Override <code>Init()</code> method instead of using <code>Awake()</code>
/// from this class.
/// </summary>
public abstract class GenericSingleton<T> : MonoBehaviour where T : GenericSingleton<T>
{
private static T _instance;
[SerializeField, Tooltip("If set to true, the gameobject will deactive on Awake")] private bool _deactivateOnLoad;
[SerializeField, Tooltip("If set to true, the singleton will be marked as \"don't destroy on load\"")] private bool _dontDestroyOnLoad;
private bool _isInitialized;
public static T instance
{
get
{
// Instance required for the first time, we look for it
if (_instance != null)
{
return _instance;
}
var instances = Resources.FindObjectsOfTypeAll<T>();
if (instances == null || instances.Length == 0)
{
return null;
}
_instance = instances.FirstOrDefault(i => i.gameObject.scene.buildIndex != -1);
if (Application.isPlaying)
{
_instance?.Init();
}
return _instance;
}
}
// If no other monobehaviour request the instance in an awake function
// executing before this one, no need to search the object.
protected virtual void Awake()
{
if (_instance == null || !_instance || !_instance.gameObject)
{
_instance = (T)this;
}
else if (_instance != this)
{
Debug.LogError($"Another instance of {GetType()} already exist! Destroying self...");
Destroy(this);
return;
}
_instance.Init();
}
/// <summary>
/// This function is called when the instance is used the first time
/// Put all the initializations you need here, as you would do in Awake
/// </summary>
public void Init()
{
if (_isInitialized)
{
return;
}
if (_dontDestroyOnLoad)
{
DontDestroyOnLoad(gameObject);
}
if (_deactivateOnLoad)
{
gameObject.SetActive(false);
}
SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
InternalInit();
_isInitialized = true;
}
private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
{
// Sanity
if (!instance || gameObject == null)
{
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
_instance = null;
return;
}
if (_dontDestroyOnLoad)
{
return;
}
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
_instance = null;
}
protected abstract void InternalInit();
/// Make sure the instance isn't referenced anymore when the user quit, just in case.
private void OnApplicationQuit()
{
_instance = null;
}
void OnDestroy()
{
// Clear static listener OnDestroy
SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
StopAllCoroutines();
InternalOnDestroy();
if (_instance != this)
{
return;
}
_instance = null;
_isInitialized = false;
}
protected abstract void InternalOnDestroy();
}
Related
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
Here is my script code:
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ControlSpike : MonoBehaviour
{
private static ControlSpike _instance = null;
public static ControlSpike Instance { get { return _instance; } }
public Rigidbody2D spikeBody;
public void Awake()
{
_instance = null;
if (_instance != null && _instance != this)
{
}
else
{
_instance = this;
}
spikeBody.bodyType = RigidbodyType2D.Static;
}
// Start is called before the first frame update
void Start()
{
spikeBody.bodyType = RigidbodyType2D.Static;
}
// Update is called once per frame
void Update()
{
}
public void DropSpikes()
{
if (DrawingManager.Instance.paths.Contains(DrawingManager.Instance.clone) && VehicleSimpleControl._instance.RacePress)
{
spikeBody.bodyType = RigidbodyType2D.Dynamic;
spikeBody.gravityScale = 10f;
}
}
}
The rigidbody.body type changes bodytype of one gameobject to dynamic but doesnt change the bodytype of the second gameobject to dynamic. Both get changed to static though. What could be wrong?
Thanks in Advance!
So, I created a manager for this in which I created two rigidbodies references. I passed the references of my objects in this manager and it worked. Basically, the code is same I just removed the script from the gameobjects and created a manager for them instead.
I've been stuck at this for a while. What I want is for my outline object to be instantiated at the location of my bronze Base game object and for it to destroy when the bronze base is no longer the closest to the player.
I'm willing to completely restart my bronze script if it means I can make this easier.
Thanks in advance!
Find Closest Bronze Script
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using UnityEngine;
public class FindBronze : MonoBehaviour
{
void Update()
{
FindClosestBronze();
}
void FindClosestBronze()
{
float distanceToClosestBronze = Mathf.Infinity;
Bronze closestBronze = null;
Bronze[] allBronze = GameObject.FindObjectsOfType<Bronze>();
foreach (Bronze currentBronze in allBronze)
{
float distanceToBronze = (currentBronze.transform.position - this.transform.position).sqrMagnitude;
if (distanceToBronze < distanceToClosestBronze)
{
distanceToClosestBronze = distanceToBronze;
closestBronze = currentBronze;
}
if (distanceToBronze > distanceToClosestBronze)
{
closestBronze.GetComponent<Bronze>().notSelected();
}
closestBronze.GetComponent<Bronze>().Selected();
}
}
}
Bronze (includes outline) script
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bronze : MonoBehaviour
{
public bool isSelected = false;
public Animator anim;
[SerializeField]
public GameObject selectedBox;
public GameObject bronzeBase;
private GameObject clone;
// Update is called once per frame
void Awake()
{
clone = (GameObject)Instantiate(selectedBox, bronzeBase.transform);
}
public void Selected()
{
if (!isSelected)
{
clone = (GameObject)Instantiate(selectedBox, bronzeBase.transform);
isSelected = true;
}
else
{
Destroy(clone);
isSelected = false;
}
}
public void notSelected()
{
Destroy(selectedBox);
}
}
In the Bronze in notSelected you are destroying the prefab selectBox!
You probably rather wanted to destroy the clone instance.
Anyway I would suggest a few things that I would do different
Instead of Instantiate and Destroy all the time rather only use SetActive
Instead of using FindObjectOfType in Update store them in a HashSet event driven: Each Bronze instance registers and unregisters itself
Depends on personal taste but I would use Linq to find the closest instance
This could look somewhat like
public class Bronze : MonoBehaviour
{
// Every instance of this component registers and unregisters itself here
public static readonly HashSet<Bronze> Instances = new HashSet<Bronze>();
[Header("References")]
public Animator anim;
[SerializeField] private GameObject selectedBox;
[SerializeField] private GameObject bronzeBase;
[Header("Debugging")]
[SerializeField] bool _isSelected = false;
private GameObject clone;
// Have a property for the selection
public bool IsSelected
{
// when something reads this property return _isSelected
get => _isSelected;
// This is executed everytime someone changes the value of IsSelected
set
{
if(_isSelected == value) return;
_isSelected = value;
clone.SetActive(_isSelected);
}
}
// Update is called once per frame
void Awake()
{
// Instantiate already returns the type of the given prefab
clone = Instantiate(selectedBox, bronzeBase.transform);
// Register yourself to the alive instances
Instances.Add(this);
}
private void OnDestroy ()
{
// Remove yourself from the Instances
Instances.Remove(this);
}
}
And then use it
using System.Linq;
public class FindBronze : MonoBehaviour
{
private Bronze currentSelected;
private void Update()
{
UpdateClosestBronze();
}
void UpdateClosestBronze()
{
if(Bronze.Instances.Count ==0) return;
// This takes the instances
// orders them by distance ascending using the sqrMagnitude
// of the vector between the Instance and you
// sqrMagnitude is more efficient than Vector3.Distance when you only need to compare instead of the actual distance
// then finally it takes the first item
var newClosest = Bronze.Instances.OrderBy(b => (b.transform.position - transform.position).sqrMagnitude).First();
// skip if the result is the same as last time
if(newClosest == currentSelected) return;
// otherwise first deselect the current selection if there is one
if(currentSelected)
{
currentSelected.IsSelected = false;
}
// Then set the new selection
currentSelected = newSelected;
currentSelected.IsSelected = true;
}
}
I've trying to make an adventure game which saves different bools in order to get to the next scene. The problem is that one bool is in another scene with makes it null. Is there any advice?
public class Farmer : MonoBehaviour
{
public bool NextLevel = false;
public Cow1 cow1;
public Cow2 cow2;
public Cow3 cow3;
public Dialogue dialogue;
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
if (GetComponent<Cow1>().getIsDisplayed() && GetComponent<Cow2>().getIsDisplayed() && GetComponent<Cow3>().getIsDisplayed())
{
NextLevel = true;
}
if (NextLevel == true)
{
FindObjectOfType<DialogueManager>().startDialogue(dialogue);
}
}
}
Yes, there is quite an easy way to do this, simply give a game object the DontDestroyOnLoad property
{
public bool NextLevel = false;
public Cow1 cow1;
public Cow2 cow2;
public Cow3 cow3;
public Dialogue dialogue;
// Start is called before the first frame update
void Start()
{
DontDestroyOnLoad(this.gameObject);
}
// Update is called once per frame
void Update()
{
if (GetComponent<Cow1>().getIsDisplayed() && GetComponent<Cow2>().getIsDisplayed() && GetComponent<Cow3>().getIsDisplayed())
{
NextLevel = true;
}
if (NextLevel == true)
{
FindObjectOfType<DialogueManager>().startDialogue(dialogue);
}
}
}
This will make your game object persistent through scene changes
You can make use of multi-scene editing so you have some values and variables carry on between your scenes. You can learn more about this here.
I am almost done with my Object Pooling System for my PickAxe GameObject, but I have this one problem I need help with.
What I am trying to figure out is why all of my spawning restarts whenever the first PickAxe hits the wall? How do I make to where the PickAxe just goes back into the "pooler" whenever it hits the wall?
I took two screenshots and I'll post my code below too. The first one is just before the first Pickaxe that was spawned hits the wall, and the second screenshot is right after that same PickAxe hit the wall.
Some of my code below:
This script spawns my PickAxes. I am 99% sure my problem is where I am calling my Event function in my CoRoutine. Am I right?
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Obstacle4 // Pick Axe Obstacle
{
public float SpawnWait; // Time in seconds between next wave of obstacle 4.
public float StartGameWait; // Time in seconds between when the game starts and when the fourth obstacle start spawning.
public float WaveSpawnWait; // Time in seconds between waves when the next wave of obstacle 4 will spawn.
}
public class SpawnPickAxe : MonoBehaviour
{
public GameObject pickAxePrefab;
public Obstacle4 obstacle4_;
void Start ()
{
PickAxePoolManager.instance.CreatePool (pickAxePrefab, 15); //CreatePool is a method in PickAxePoolManager
StartCoroutine (PickAxeSpawner ());
}
IEnumerator PickAxeSpawner ()
{
yield return new WaitForSeconds (obstacle4_.StartGameWait);
while (true)
{
for (int i = 0; i < 5; i++)
{
Vector3 newSpawnPosition = new Vector3 (Random.Range(-10.0f, 10.0f), 1.2f, 30.0f);
Quaternion newSpawnRotation = Quaternion.Euler (0.0f, -90.0f, 0.0f);
PickAxePoolManager.instance.ReuseObject (pickAxePrefab, newSpawnPosition, newSpawnRotation); //ReuseObject is also a method in PickAxePoolManager
//Instantiate (obstacle4.pickAxe, spawnPosition, spawnRotation);
yield return new WaitForSeconds (obstacle4_.SpawnWait);
ResetByWall.instance.onTriggerEntered += delegate(GameObject obj)
{
PickAxePoolManager.instance.ReuseObject(pickAxePrefab, newSpawnPosition, newSpawnRotation);
};
}
yield return new WaitForSeconds (obstacle4_.WaveSpawnWait);
}
}
}
Below is my "ResetByWall" script. This script is also important because it contains the public Event that allows me to detect a collision with whatever collides with my wall, and my wall is the trigger.
using System;
using UnityEngine;
using System.Collections;
public class ResetByWall : MonoBehaviour
{
//public bool collided;
//public GameObject pickAxePrefab;
//NOTE:
//Singleton Pattern from lines 12 to 25 //
// ************************************
static ResetByWall _instance; // Reference to the Reset By Wall script
public static ResetByWall instance // This is the accessor
{
get
{
if(_instance == null) // Check to see if _instance is null
{
_instance = FindObjectOfType<ResetByWall>(); //Find the instance in the Reset By Wall script in the currently active scene
}
return _instance;
}
}
public event Action <GameObject> onTriggerEntered;
void OnTriggerEnter(Collider other)
{
if (onTriggerEntered != null) {
onTriggerEntered (other.gameObject);
}
}
}
And below is my PoolManager Script. You might not even need to look at this script but I'll post it anyways just in case.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class PickAxePoolManager : MonoBehaviour {
Dictionary<int,Queue<GameObject>> poolDictionary = new Dictionary<int,Queue<GameObject>>();
//NOTE:
//Singleton Pattern used from lines 12 to 25
static PickAxePoolManager _instance; // Reference to the Pool Manager script
public static PickAxePoolManager instance // This is the accessor
{
get
{
if(_instance == null) // Check to see if _instance is null
{
_instance = FindObjectOfType<PickAxePoolManager>(); //Find the instance in the Pool Manager script in the currently active scene
}
return _instance;
}
}
/// <summary>
/// Creates the pool.
/// </summary>
/// <param name="prefab">Prefab.</param>
/// <param name="poolSize">Pool size.</param>
public void CreatePool(GameObject prefab, int poolSize)
{
int poolKey = prefab.GetInstanceID (); // Unique integer for every GameObject
if (!poolDictionary.ContainsKey (poolKey)) //Make sure poolKey is not already in the Dictionary,
//if it's not then we can create the pool
{
poolDictionary.Add(poolKey, new Queue<GameObject>());
for (int i = 0; i < poolSize; i++) //Instantiate the prefabs as dictated by the "poolSize" integer
{
GameObject newObject = Instantiate (prefab) as GameObject; //Instantiate as a GameObject
newObject.SetActive(false); // Don't want it to be visible in the scene yet
poolDictionary [poolKey].Enqueue(newObject); // Add it to our Pool
}
}
}
I solved the problem. I am just going to use this in my ResetByWall script:
void OnTriggerEnter (Collider obstacle)
{
if (obstacle.gameObject.tag == "Pick Axe")
{
obstacle.gameObject.SetActive (false);
}
}
Thanks! :)