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

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

Related

How can I store a gameobject in a variable, destroy it in the scene, but not in the variable?

I have an inventory that can store up to 5 items and every item in the scene is different, there are no duplicates. There's gonna be like one letter, one book, one gun, etc. in the scene.
The inventory is displayed on screen with 5 slots that I can iterate through with the mouse wheel.
So, instead of using a list or a linked list, I have an array of InventoryObjects and an InventoryObject contains a slot displayed on screen and the item displayed in the slot. It's basically like the equipped item bar in Minecraft. You have some slots with an item per slot and you can use whatever item you have currently selected. Only difference being I have 5 slots and Minecraft has 10.
using UnityEngine;
public class InventoryObject
{
public GameObject ItemObject;
public GameObject SlotObject;
// Only used for initialization
public InventoryObject(GameObject AddSlot)
{
SlotObject = AddSlot;
ItemObject = null;
}
}
The problem is that I want to pick up an item, add it to the inventory and destroy it, then instantiate it when selected. However, if I do that, the item will be added to the inventory and when I use Destroy() it also destroys the item in the inventory.
Here's the player script:
private void Update()
{
// _input.grab detects when I press the key to grab an item
if (_input.grab)
{
Grab();
_input.grab = false;
}
}
void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
Destroy(objectToGrab);
}
And here's the inventory script:
public class Inventory : MonoBehaviour
{
public static Inventory instance;
public int maxItems;
// Slots are added in editor
public GameObject[] InventorySlots;
public InventoryObject[] SlotList = new InventoryObject[5];
void Awake()
{
instance = this;
// Init list of InventoryObjects
for (int i = 0; i < 5; i++)
{
SlotList[i] = new InventoryObject(InventorySlots[i]);
}
}
public void Add(GameObject item)
{
if (item == null) return; // Guard clause
// Adding the item in the first available slot
for (int i = 0; i < maxItems; i++)
{
// Detecting an available slot
if (SlotList[i].ItemObject == null)
{
// Adding the item and getting out of the loop
SlotList[i].ItemObject = item;
i = maxItems;
}
}
}
}
I have found someone with a similar issue and the comments suggested object pooling. But I don't generate a bunch of objects quickly, I only generate one and it's a specific one, it's not like a bullet taken from a pool of a thousand bullets. So I don't think object pooling is suitable here and I'm wondering how I can solve my problem.
If I can't just copy an item, destroy the original and instantiate the copy, then how about disabling the item and enabling it when necessary? I'm just not sure how that would work because if the item is disabled, it's still in the scene, so could it conflict with other things or is it like if it wasn't there at all from the player's POV?
Disabling a GameObject will also disable any script attached to it, this should be the only drawback of using this solution.
If you want scripts and components attached to it to still be working while the item is hidden, you could simply disable the renderer instead.
Here's a small code snippet to illustrate my example, although I wouldn't recommend using the GetComponent function outside of Unity's Awake and Start event functions.
Disabling the item's renderer :
private void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
objectToGrab.GetComponent<Renderer>().enabled = false;
}
But as I think that disabling the Item's renderer shouldn't be delegated to a player function, here's a better implementation of this solution using a proper component for items that can be grabbed by the player.
IGrabItem interface :
public interface IGrabItem
{
public bool Enabled { get; set; }
}
Script to be attached to items that can be grabbed :
public class Item: MonoBehaviour, IGrabItem
{
[SerializeField] private Renderer itemRenderer;
private void Awake()
{
if (itemRenderer == null)
{
itemRenderer = GetComponent<Renderer>();
}
}
public bool Enabled
{
get => itemRenderer.enabled;
set => itemRenderer.enabled = value;
}
}
We add a new line in the Add function of Inventory :
public class Inventory : MonoBehaviour
{
public void Add(IGrabItem item)
{
item.Enabled = false;
// Do your thingy with the item here
}
}
Disabling the item is not the responsability of the Grab function anymore :
private void Grab()
{
if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed
Inventory.instance.Add(objectToGrab);
}

Unity3D: How to do object pooling without a spawner singleton

Usually, if you use object pooling, you make a singleton like in this video.
After seeing this video, I discovered how messy singleton can be. Is there any other way to do object pooling without using singletons? I wanna instead use Events.
You would need to hold the pool in a class which is not a singleton, and handle your gameobject pool according to your events.
Regarding to call them with events, "I want to use events" is not a very concrete question. You need to set your events to listen (method subscribe) and to call them in the code wherever they're supposed to occur, this is invoke the method. I suggest that if you are not clear about this, try to use the unity events (OnTriggerEnter, if(Input.GetMouseButtonDown(0)) in the Update etc) until you dig in the topic enough to understand them and make ones of you own with c# events or UnityEvents when needed.
Find two template scripts, a pool and and event handler to handle your objects in the scene. You can check those out in an empty scene with your respective two gameObject to attach, and the object you want in the pool, pressing 'space' and 'A' to create from pool and return to pool respectively.
Pool manager:
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
private Queue<GameObject> objPool;
private Queue<GameObject> activeObj;
private int poolSize = 10;
public GameObject objPrefab;
void Start()
{
//queues init
objPool = new Queue<GameObject>();
activeObj = new Queue<GameObject>();
//pool init
for (int i = 0; i < poolSize; i++)
{
GameObject newObj = Instantiate(objPrefab);
objPool.Enqueue(newObj);
newObj.SetActive(false);
}
}
public GameObject GetRandomActiveGO() {
GameObject lastActive = default;
if (activeObj.Count > 0)
lastActive = activeObj.Dequeue();
else {
Debug.LogError("Active object queue is empty");
}
return lastActive;
}
//get from pool
public GameObject GetObjFromPool(Vector3 newPosition, Quaternion newRotation)
{
GameObject newObject = objPool.Dequeue();
newObject.SetActive(true);
newObject.transform.SetPositionAndRotation(newPosition, newRotation);
//keep actives to be retrieved
activeObj.Enqueue(newObject);
return newObject;
}
//return to pool
public void ReturnObjToPool(GameObject go)
{
go.SetActive(false);
objPool.Enqueue(go);
}
}
Event handler:
using UnityEngine;
public class EventHandler : MonoBehaviour
{
public delegate GameObject OnSpacePressed(Vector3 newPosition, Quaternion newRotation);
public OnSpacePressed onSpacePressed;
public delegate void OnAKeyPressed(GameObject go);
public OnAKeyPressed onAKeyPressed;
public PoolManager poolManager;
void Start()
{
onSpacePressed = poolManager.GetObjFromPool;
onAKeyPressed = poolManager.ReturnObjToPool;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
onSpacePressed?.Invoke(new Vector3(0, 0, 0), Quaternion.identity);
}
//here I get a random active, however this would be called in the specific objects remove circumstances,
//so you should have a reference to that specific gameobje when rerunrning it to the pool.
if (Input.GetKeyDown(KeyCode.A))
{
GameObject go = poolManager.GetRandomActiveGO();
onAKeyPressed?.Invoke(go);
}
}
}
Edit: Singleton pattern
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T _instance;
public static T instance
{
get
{
if (_instance == null)
{
_instance = GameObject.FindObjectOfType<T>();
if (_instance == null)
{
_instance = new GameObject(typeof(T).Name).AddComponent<T>();
}
}
return _instance;
}
}
}

How to call coroutine from another script? UNITY

This is the script where the touch action, the main game is on it
var addingGoldPerSec = new GameObject();
var buttonInstance = addingGoldPerSec.AddComponent<ItemButton>();
StartCoroutine(buttonInstance.addGoldLoop());
And this is the one where I have the coroutine
public double goldPerSec
{
get
{
if (!PlayerPrefs.HasKey("_goldPerSec"))
{
return 0;
}
string tmpStartGoldPerSec = PlayerPrefs.GetString("_goldPerSec");
return double.Parse(tmpStartGoldPerSec);
}
set
{
PlayerPrefs.SetString("_goldPerSec", value.ToString());
}
}
public void updateUI()
{
priceDisplayer.text = LargeNumber.ToString(currentCostPerSec).ToString();
coinDisplayer.text = "PER SEC\n" + LargeNumber.ToString(goldPerSec).ToString();
levelDisplayer.text = "LEVEL\n" + level + "/150".ToString();
}
public IEnumerator addGoldLoop()
{
while (DataController.Instance.gold <= 1e36)
{
DataController.Instance.gold += goldPerSec;
if (Gameplay.Instance.booster == 1)
{
yield return new WaitForSeconds(0.25f);
}
else if (Gameplay.Instance.booster == 0)
{
yield return new WaitForSeconds(1.0f);
}
}
}
And this is a third Script where I manage the data stored into PlayerPrefs
public void loadItemButton(ItemButton itemButton)
{
itemButton.level = PlayerPrefs.GetInt("_level",1);
PlayerPrefs.GetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.GetString("_goldPerSec",itemButton.goldPerSec.ToString());
}
public void saveItemButton(ItemButton itemButton)
{
PlayerPrefs.SetInt("_level", itemButton.level);
PlayerPrefs.SetString("_costPerSec", itemButton.currentCostPerSec.ToString());
PlayerPrefs.SetString("_goldPerSec", itemButton.goldPerSec.ToString());
}
I have the second script which is the coroutine one attached into several gameobjects where exists an Upgrade button, per upgrade increases the coin by second you earn, the main problem here is that the coroutine just stops after I touch the screen, so I made another code into the main script so by that way the coroutine will keep working even after touched the screen, so I made a script where is the GameObject, but it just throws me NullReferenceException, tried a check with TryCatch and throws me that the problem is coming from the GameObject that I have created on the main script, but if is that way I need to attach to the main object where the main script exists, like more than 10 gameobjects where the coroutine exists, I think Singleton is not the way, it deletes me all the information above there by instantiating on Awake, I never thought about making static so I did as you told me and I need to change almost of my code, every text is attached into each gameobjects, to make a non-static member work with a static-member need to delete Monobehaviour but it just makes the game explode, thanks for helping me.
Create two scripts and attach them, for example, at Main Camera. The first script contains your timer with all variables:
using UnityEngine;
using System.Collections;
public class TimerToCall : MonoBehaviour {
//Create your variable
...
//Your function timer
public IEnumerator Counter() {
...
}
}
In the second script, you will call the timer:
public class callingTimer : MonoBehaviour {
void Start() {
//TimerToCall script linked to Main Camera, so
StartCoroutine(Camera.main.GetComponent<TimerToCall>().Counter());
}
}
if you want to have for example the second script not linked to any gameObjet you could use static property:
in first script linked:
void Start()
{
StartCoroutine(CallingTimer.ExampleCoroutine());
}
in second script not linked, you use the static property:
using System.Collections;
using UnityEngine;
public class CallingTimer
{
public static IEnumerator ExampleCoroutine()
{
//Print the time of when the function is first called.
Debug.Log("Started Coroutine at timestamp : " + Time.time);
//yield on a new YieldInstruction that waits for 5 seconds.
yield return new WaitForSeconds(5);
//After we have waited 5 seconds print the time again.
Debug.Log("Finished Coroutine at timestamp : " + Time.time);
}
}

Unity C# list being cleared when the method is complete - object pool

I am trying to make a simple object pool in c# for Unity.
So far I have used this class:
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class ObjectPool : MonoBehaviour
{
public static ObjectPool ShareInstance;
public List<GameObject> pooledObjects;
public GameObject objectToPool;
public int amountToPool;
// Use this for initialization
void Start()
{
pooledObjects = new List<GameObject>();
for (int i = 0; i < amountToPool; i++)
{
GameObject obj = (GameObject)Instantiate(objectToPool);
obj.SetActive(false);
pooledObjects.Add(obj);
}
}
void Awake()
{
ShareInstance = this;
}
public GameObject GetPooledObject()
{
for (int i = 0; i < pooledObjects.Count; i++)
{
if (!pooledObjects[i].activeInHierarchy)
{
return pooledObjects[i];
}
}
return null;
}
}
The start method makes the inactive instances of my prefab correctly. However I am finding an error when I use this code to call GetPooledObject:
GameObject optionTile = ObjecdtPooler.ShareInstance.GetPooledObject();
if (optionTile != null)
{
optionTile.SetActive(true);
}
The GetPooledObject method appears to think that the list (pooledObjects) is empty. This means that the for loop within this method is just skipped, and null is returned. I used debug.log and the list is correctly filled with the instances of my prefab. However when I call the GetPooledObject method, the list is said to be empty.
Any ideas?
Thanks
I expect that either amountToPool has a inital value of 0 or if it's greater than 0 all objects are in use.
So you should expand your pool to grow by a given value when all objects are in use.

Background music get louder each time a scene is loaded

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.

Categories