So, I have a update running on a monobehaviour script(Let's call it Script A) that's in the scene. This update fires a event, so other non-monobehaviour scripts can listen to this when they need a update for something. However, at the start of the app it runs the update on Script A for a short time, but after a while it randomly stops calling the update on Script A.
So I started to place some debugs in standard monobehaviour functions like awake, start, disable, enable etc. Turns out the object is disabled from somewhere without me using or calling it anywhere. This problem only occurs on WebGL. In the editor the OnDisable event is never called and the update keeps running fine.
I tried various fixes, like all the debugs logs everywhere, I tried debugging the stacktrace to find out what is calling this OnDisable, saidly this doesn't show in the WebGL console. It just says: StackTrace:
public class UpdateManager : MonoBehaviour
{
private static UpdateManager _instance;
public static UpdateManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<UpdateManager>();
if (_instance == null)
{
GameObject updateManager = new GameObject("UpdateManager");
_instance = updateManager.AddComponent<UpdateManager>();
}
}
return _instance;
}
}
public delegate void OnUpdateEvent();
public event OnUpdateEvent OnUpdate;
private void Awake()
{
Debug.Log("UpdateManager: Awake!");
}
private void Start()
{
Debug.Log("UpdateManager: Start!");
}
private void OnEnable()
{
Debug.Log("UpdateManager: OnEnable!");
}
private void OnDisable()
{
Debug.Log("UpdateManager: OnDisable!, Stacktrace: " + UnityEngine.StackTraceUtility.ExtractStackTrace());
}
private void Update()
{
Debug.Log("UpdateManager: Update!");
OnUpdate?.Invoke();
}
I would like to know what is disabling this object since I have no clue. Good to note here is that it's only disabled once at the beginning, and after I enable it again it runs fine for the remaining time.
The only places I call this script is in a static class somewhere:
private static void initializeRefreshCycle(int expiresIn)
{
if (expiresIn > 0)
{
if (_refreshRoutine != null)
{
CoroutineManager.Instance.Stop(_refreshRoutine);
_refreshRoutine = null;
}
DateTime expirationDateTime = DateTime.Now;
_refreshTime = expirationDateTime.AddSeconds(expiresIn * 0.85f);
Debug.Log(string.Format("<< API TOKEN WILL BE AUTOMATICALLY REFRESHED IN {0} SECONDS >>", expiresIn * 0.85f));
UpdateManager.Instance.OnUpdate += OnUpdateEarly;
}
}
And then the function that actually listens to the call:
public static void OnUpdateEarly()
{
if (!HasAlmostExpired) return;
SSOCommunicator.RefreshToken(_refresh);
UpdateManager.Instance.OnUpdate -= OnUpdateEarly;
}
Looks like the first time it stops somewhere, and once it reached the subscribing again, it runs fine for the remaining time.
I found out it's a garbage collection issue. Since I would always call UpdateManager.Instance to call a function I never had a hard reference to that instance. So it was somehow destroyed at start only on WebGL. I solved it by keeping a hard reference in the awake of a script. So just having a simgple _updatamanager = Updatemanager.Instance solved it.
Related
I am building out a Zelda game and I have ran into an issue -
public void UpdateCoins()
{
Debug.Log(GameManager.instance.currCoins);
//coinsText.text = "Coins " + GameManager.instance.currCoins;
}
That works as expected -
But when I do this...
public void UpdateCoins()
{
Debug.Log(GameManager.instance.currCoins);
coinsText.text = "Coins " + GameManager.instance.currCoins;
}
I receive an Object Reference not set to an instance of the object error.
As you can see it is set. Here is my simple GameManager script
public static GameManager instance;
public int currCoins;
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public void GetCoins(int coinsToAdd)
{
currCoins += coinsToAdd;
UIManager.instance.UpdateCoins();
}
}
Any thoughts on why this isn't working the way it should?
Thanks!
Try add more logs
Either the coinsText or the coinsText.text is null, so what I would do is:
Debug.Log(coinsText);
if(coinsText != null) Debug.Log(coinsText.text);
I'm sure you'll make a great Zelda game but let's fix your issue here first , you are trying to access to an int GameManager.instance.currCoins
but returns null simply because the currCoins you're using is not even to been set to zero.
Also as a tip if you are going to use an int in a string text or something always use ToString() :
coinsText.text = "Coins " + GameManager.instance.currCoins.ToString();
Number 2 issue here , your use of singleton it should be like this
public static GameManager instance;
public void Awake()
{
if(instance == null){
instace = this;
}else
{
DontDestroyOnLoad(gameObject);
}
}
Last issue that may not be one now but in the future change the name of this method now while you still can public void GetCoins(int coinsToAdd)to something like AddCoins(int coinsToAdd)
I have the below audiomanager for my project. Whenever I run a game from the "Play" button in Unity - I can change the volume via slider and the settings are being saved and they load when I exit and play again via Unity's "Play" button.
However can you please explain to me why I cannot do this when I go to the PlayGame and then I go back to the main menu via "restart" button in my game? When I do that - the slider is 100% charged and it doesn't save/load new setings.
Any comments / feedback is much appreciated!
public static AudioManager AMInstance;
[SerializeField] Slider volumeSlider;
private void Awake()
{
if (AMInstance != null && AMInstance != this)
{
Destroy(this.gameObject);
return;
}
AMInstance = this;
DontDestroyOnLoad(this);
}
private void Start()
{
if (!PlayerPrefs.HasKey("musicVolume"))
{
PlayerPrefs.SetFloat("musicVolume", 1);
Load();
}
else
{
Load();
}
}
public void ChangeVolume()
{
AudioListener.volume = volumeSlider.value;
Save();
}
public void Save()
{
PlayerPrefs.SetFloat("musicVolume", volumeSlider.value);
}
public void Load()
{
volumeSlider.value = PlayerPrefs.GetFloat("musicVolume");
}
}
You will need to call PlayerPrefs.Save() before you navigate to another scene. It does get called by default OnApplicationQuit, but since you are just setting a the float, you might need to explicitly call the Save function.
Unity's documentation for PlayerPrefs.Save
I'm confused about finding reference GameObject but different Scene and set the onclick when difference scene, so I have GameManager who manage all but available only on the Main menu. So I decide to make Dontdestroyonload, the issue start at this, so when I play to the MainGame Scene, the field of GameManager at inspector will find, but I can't drag n drop different scene, right? That confuses me.
And if the GameManager at the MainMenu scene, the question is how to drag n drop at the onClick event, like I want pause button active or something else in the game.
]3
I tried with onLloadscene(scene s, Mode mode), but nothing happens, and here the scrip for the GameManager. :
public static GameManager gameManager;
[Header("Main Menu panels")]
public GameObject startPanel;
public GameObject settingPanel;
public GameObject levelPanel;
[Header("InGame Panels")]
#region Panel
public GameObject pausePanel;
public GameObject ObjectivePanel;
public GameObject shopPanel;
private int click = 0;
[Header("Int Tweaks")]
public int indexLevel;
public int onlevel;
public bool isPaused;
_levelSelect LevelSelect;
public static GameManager Instance { set; get; }
public int levelindexPlayerPrefs;
private void Awake()
{
if (gameManager != null)
{
Instance = this;
Destroy(gameObject);
}
else
{
DontDestroyOnLoad(gameObject);
}
}
void Start()
{
LevelSelect = FindObjectOfType<_levelSelect>();
OnStart();
onlevel = int.Parse(LevelSelect.levelIndex) + 1;
indexLevel = int.Parse(LevelSelect.levelIndex);
getPlayerData();
}
// Update is called once per frame
void Update()
{
ExitApp();
}
public void OnStart()
{
startPanel.SetActive(true);
settingPanel.SetActive(false);
levelPanel.SetActive(false);
}
#region Buttons
public void startbutton()
{
levelPanel.SetActive(true);
startPanel.SetActive(false);
settingPanel.SetActive(false);
}
public void backButtonMainMenu()
{
levelPanel.SetActive(false);
startPanel.SetActive(true);
settingPanel.SetActive(false);
}
public void settingbutton()
{
levelPanel.SetActive(false);
startPanel.SetActive(false);
settingPanel.SetActive(true);
}
public void PauseButton()
{
Time.timeScale = 0f;
pausePanel.SetActive(true);
ObjectivePanel.SetActive(false);
}
public void Resume()
{
Time.timeScale = 1f;
}
#endregion
public void ExitApp()
{
if (Input.GetKey(KeyCode.Escape))
{
click++;
StartCoroutine(ClickTime());
if (click>1)
{
print("Exit Game");
Application.Quit();
}
}
}
IEnumerator ClickTime()
{
yield return new WaitForSeconds(0.5f);
click = 0;
}
public void getPlayerData()
{
levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex", 0);
}
public void updateLevel(int Index)
{
if (levelindexPlayerPrefs < Index)
{
PlayerPrefs.SetInt("LevelIndex", Index);
levelindexPlayerPrefs = PlayerPrefs.GetInt("LevelIndex");
}
}
#region onloadedScenePickRefferences
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
pausePanel = GameObject.FindGameObjectWithTag("PausePanel");
ObjectivePanel = GameObject.FindGameObjectWithTag("ObjectivePanel");
}
#endregion
//public IEnumerator EndChapter()
//{
// updateLevel(indexLevel + 1);
// getPlayerData();
//}
Here is what I would probably do:
Have a static class for storing and sharing all your references. It doesn't have to be in any scene but simply "lives" in the assets:
public static class GlobalReferences
{
// as example just for one reference but you can implement the rest equally yourself
// here this class actually stores the reference
private static GameObject startPanel;
// A public property in order to add some logic
// other classes will always access and set the value through this property
public static GameObject StartPanel
{
get
{
// if the reference exists return it right away
if(startPanel) return startPanel;
// as a fallback try to find it
startPanel = GameObject.FindGameObjectWithTag("StartPanel");
// ofcourse it might still fail when you simply try to access it
// in a moment it doesn't exist yet
return startPanel;
}
set
{
startPanel = value;
// invoke an event to tell all listeners that the startPanel
// was just assigned
OnStartPanelReady?.Invoke();
}
}
// An event you will invoke after assigning a value making sure that
// other scripts only access this value after it has been set
// you can even directly pass the reference in
public static event Action<GameObject> OnStartPanelReady;
}
So now in your component(s) that is(are) in the new loaded scene you assign the value as early as possible (Awake). Here you can already store it via the Inspector since it is a scene reference:
public class ExampleSetter : MonoBehaviour
{
// already reference it via the Inspector
[SerializeField] private GameObject startPanel;
private void Awake()
{
// as a fallback
if(!startPanel) startPanel = GameObject.FindObjectWithTag("startPanel");
// assign it to the global class
GlobalReferences.StartPanel = startPanel;
}
}
And in other scenes that where already loaded before you add a listener so they do their stuff as soon as the other scene is ready:
public class ExampleConsumer : MonoBehaviour
{
[Header("Debug")]
[SerializeField] private GameObject startPanel;
private void Awake()
{
// Try to get the reference
startPanel = GlobalReferences.StartPanel;
// if this failed then wait until it is ready
if(!startPanel)
{
// it is save to remove callbacks even if not added yet
// makes sure a listener is always only added once
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
GlobalReferences.OnStartPanelReady += OnStartPanelReady;
}
// otherwise already do what you want
else
{
OnStartPanelReady(startPanel);
}
}
private void OnDestroy()
{
// always make sure to clean up callbacks when not needed anymore!
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
}
private void OnStartPanelReady(GameObject newStartPanel)
{
startPanel = newStartPanel;
// always make sure to clean up callbacks when not needed anymore!
GlobalReferences.OnStartPanelReady -= OnStartPanelReady;
// NOTE: It is possible that at this point it is null anyway if another
// class has set this actively to null ;)
if(startPanel)
{
// Now do something with the startPanel
}
}
}
The other way round when you need a reference in the new loaded Scene form the main scene ... it should already be set since the mainscene was loaded first and has already assigned its according references.
Now you can either go for this static class or simply implement the same logic for each reference that needs to be shared directly in an according component where you reference them via drag&drop .. it makes no difference since anyway you will use static fields and events that are not bound to any instance but the type(s) itself.
In my game I have LevelManager and LevelManager.cs and it's not a singleton.
I wanna make fade transition between scenes. I made animations for Fade_In and Fade_Out. And for the first it works fine but when I'd like to leave a scene, it (Fade_Out) doesn't work.
Some of LevelManager.cs:
private void Start() {
if (autoLoadNextLevelAfter != 0) {
Invoke("FadeOut", autoLoadNextLevelAfter);
}
}
public void LoadLevel(string name) {
SceneManager.LoadScene(name);
}
public void LoadNextLevel() {
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1);
}
private void FadeOut() {
animator.SetTrigger("FadeOut");
}
In the Start method I call Invoke of animation and at the end of Fade_Out I call LoadNextLevel method. In this case, Fade_Out works fine.
But If I wanna make transition with LoadLevel (it calls when I push the button) it breaks. I tried to make IEnumerator LoadLevel:
public void NewLoadLevel(string name) {
FadeOut();
StartCoroutine(CoroutLoadLevel(name));
}
private IEnumerator CoroutLoadLevel(string name) {
yield return new WaitForSeconds(1f);
SceneManager.LoadScene(name);
}
The console says: Coroutine couldn't be started because the the game object 'LevelManager' is inactive!. I guess it's because LevelManager is not a singleton.
Make sure the LevelManager is active and enabled. A coroutine needs an active object to run (because if it is not active update logic will not be called).
I'm making an event that does the failLevel stuff when it fires off. For that I have made a delegate
public delegate Coroutine FailGame(IEnumerator function);
public static event FailGame gameFailedEvent;
like so and I subscribed the appropriate function to it
void Start ()
{
gameFailedEvent += StartCoroutine;
}
It works when it is called from the same script like so:
gameFailedEvent(WaitThenFailLevel());
when this WaitThenFailLevel() looks like this:
IEnumerator WaitThenFailLevel()
{
CharacterController2D.playerDied = true;
if (CharacterController2D.animState != CharacterController2D.CharAnimStates.fall)
{
CharacterController2D.currentImageIndex = 0;
CharacterController2D.animState = CharacterController2D.CharAnimStates.fall;
}
CharacterController2D.movementDisabled = true;
yield return new WaitForSeconds(0.2f);
StartCoroutine(ScaleTime(1.0f, 0.0f, 1.2f));
}
It works fine here. Now, I have another object that can kill the player (dangerous times I know) and I don't want to copy paste everything again, I just want it to fire off the static event made in the script above.
I DID try making the WaitThenFailGame function
public static
and make static all my other ienumerators but I got an error named "An object reference is required for non-static field..."
Hence I tried the event stuff.
All well and fine, but I can't call the event from the other script because I can't pass it the function from the script mentioned.
What to do now?
Here is the example code:
EventContainor.cs
public class EventContainer : MonoBehaviour
{
public static event Action<string> OnGameFailedEvent;
void Update()
{
if (Input.GetKey(KeyCode.R))
{
// fire the game failed event when user press R.
if(OnGameFailedEvent = null)
OnGameFailedEvent("some value");
}
}
}
Listener.cs
public class Listener : MonoBehaviour
{
void Awake()
{
EventContainer.OnGameFailedEvent += EventContainer_OnGameFailedEvent;
}
void EventContainer_OnGameFailedEvent (string value)
{
StartCoroutine(MyCoroutine(value));
}
IEnumerator MyCoroutine(string someParam)
{
yield return new WaitForSeconds(5);
Debug.Log(someParam);
}
}
using UnityEngine;
public class ScriptA : MonoBehaviour
{
public ScriptB anyName;
void Update()
{
anyName.DoSomething();
}
}
using UnityEngine;
public class ScriptB : MonoBehaviour
{
public void DoSomething()
{
Debug.Log("Hi there");
}
}
This is linking functions between scripts , Copied from Here, maybe coroutines are the same, Then you need to start the coroutine in void Start() {} , You may find this useful as well.