Singleton reference suddenly becomes null, why?
This used to work consistently for a long time until it randomly started getting a NullReferenceExeption
for my singleton class, i have been trying to understand why but it makes no sense to me, it looks like it's triggering an OnApplicationPause
method before Awake
methods but i don't get it...
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BreakInfinity;
public class MineSpawner : MonoBehaviour
{
[SerializeField] private int maxMinesAtOnce;
[SerializeField] private float mineSpawnInterval;
[SerializeField] private float spawnHeight;
[SerializeField] private float[] chance;
[SerializeField] private GameObject[] mineTypes;
[SerializeField] private Transform mineSlotsParent;
private DataController dataC;
private void Start()
{
dataC = DataController.Instance;
SpawnSavedMines();
InvokeRepeating("SpawnMine", 0f, mineSpawnInterval);
}
void SpawnSavedMines()
{
for (int i = 0; i < dataC.minesParent.Count; i++)
{
GameObject newMine = Instantiate(mineTypes[dataC.minesType[i]], mineSlotsParent.GetChild(dataC.minesParent[i]));
newMine.gameObject.GetComponent<MineLogic>().maxHitPoints = dataC.minesMaxHitPoints[i];
newMine.gameObject.GetComponent<MineLogic>().mineType = dataC.minesType[i];
}
CheckCurrentMinesAmount();
}
void SpawnMine()
{
CheckCurrentMinesAmount();
if (PlayerPrefs.GetInt("CurrentMines") < maxMinesAtOnce)
{
float percentageCap = 0f;
for (int i = 0; i < chance.Length; i++) // gets the sum of total percentages
{
percentageCap += chance[i];
}
float tempChance = 0f; // initializes percentage checking
float roll = Random.Range(0f, percentageCap); // random number roll for percentage
for (int i = 0; i < mineTypes.Length; i++)
{
if (roll >= tempChance && roll < tempChance + chance[i])
{
GameObject newMine = Instantiate(mineTypes[i], GetRandomFreeChild());
newMine.transform.position += new Vector3(0, spawnHeight, 0);
newMine.transform.eulerAngles = new Vector3(0,Random.Range(0,360),0);
switch (newMine.tag)
{
case "Copper":
newMine.GetComponent<MineLogic>().maxHitPoints = BigDouble.Round(dataC.copperOreHealth +
dataC.copperOreHealth * Random.Range(0,0.1f));
break;
case "Iron":
newMine.GetComponent<MineLogic>().maxHitPoints = BigDouble.Round(dataC.ironOreHealth +
dataC.ironOreHealth * Random.Range(0, 0.1f));
break;
}
newMine.GetComponent<MineLogic>().mineType = i;
}
tempChance += chance[i]; // add new percentage to check before looping again
}
}
}
Transform GetRandomFreeChild()
{
int randomParentNum;
do
{
randomParentNum = Random.Range(0, mineSlotsParent.childCount);
} while (mineSlotsParent.GetChild(randomParentNum).childCount != 0);
return mineSlotsParent.GetChild(randomParentNum);
}
void CheckCurrentMinesAmount()
{
int count = 0;
foreach (Transform childs in mineSlotsParent)
{
if (childs.childCount != 0)
{
count++;
}
}
PlayerPrefs.SetInt("CurrentMines", count);
}
private void SaveMines()
{
if (dataC == null) Debug.Log("dataC is null"); //debug
dataC.minesParent.Clear();
dataC.minesMaxHitPoints.Clear();
dataC.minesType.Clear();
for (int i = 0; i < mineSlotsParent.childCount; i++)
{
if (mineSlotsParent.GetChild(i).childCount != 0)
{
dataC.minesParent.Add(i);
dataC.minesMaxHitPoints.Add(mineSlotsParent.GetChild(i).GetChild(0).GetComponent<MineLogic>().maxHitPoints);
dataC.minesType.Add(mineSlotsParent.GetChild(i).GetChild(0).GetComponent<MineLogic>().mineType);
}
}
CheckCurrentMinesAmount();
dataC.SaveMineObjects();
}
private void OnApplicationQuit()
{
SaveMines();
Debug.Log("MineSpawner OAQ triggered");
}
private void OnApplicationPause(bool pause)
{
SaveMines();
Debug.Log("MineSpawner OAP triggered");
}
}
The singleton DataController class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BreakInfinity;
using TMPro;
public class DataController : MonoBehaviour
{
public float basePrice, baseProd, baseSpeed, priceMult, prodMult, speedMult; //for prototype
[SerializeField] private float saveCycleSeconds;
[SerializeField] private string saveFileName;
[SerializeField] private TMP_Text smallInvCopper;
[SerializeField] private TMP_Text smallInvIron;
Data data;
MineData mineData;
UpgradeData upgradeData;
MineObjects mineObjects;
Init init;
public BigDouble coins;
public BigDouble copper;
public BigDouble iron;
public BigDouble damage;
public BigDouble copperOreHealth;
public BigDouble ironOreHealth;
public BigDouble copperPerSecond;
public BigDouble ironPerSecond;
///////
public int copperMineLevel;
public int ironMineLevel;
public BigDouble copperMineProd;
public BigDouble ironMineProd;
public float copperMineRate;
public float ironMineRate;
public BigDouble copperUpgradePrice;
public BigDouble ironUpgradePrice;
////////
public float copperPriceMultiplier;
public float ironPriceMultiplier;
public byte copperPriceULevel;
public byte ironPriceULevel;
private float copperTimer;
private float ironTimer;
////////
public List<int> minesParent;
public List<BigDouble> minesMaxHitPoints;
public List<int> minesType;
///////
#region singleton stuff
public static DataController Instance
{
get
{
if (_instance == null)
{
Debug.LogWarning("DataController is null");
_instance = FindObjectOfType(typeof(DataController)) as DataController;
if (_instance == null) Debug.LogError("DataController still null after fixing attempt :(");
Debug.Log("DataController now fixed");
}
return _instance;
}
set
{
_instance = value;
}
}
private static DataController _instance;
#endregion
private void Awake()
{
_instance = this;
data = SavingSystem.SaveExists(saveFileName) ? SavingSystem.LoadData<Data>(saveFileName) : new Data();
mineData = SavingSystem.SaveExists(saveFileName + "MineData") ? SavingSystem.LoadData<MineData>(saveFileName + "MineData") : new MineData();
upgradeData = SavingSystem.SaveExists(saveFileName + "UpgradeData") ? SavingSystem.LoadData<UpgradeData>(saveFileName + "UpgradeData") : new UpgradeData();
mineObjects = SavingSystem.SaveExists(saveFileName + "MineObjects") ? SavingSystem.LoadData<MineObjects>(saveFileName + "MineObjects") : new MineObjects();
init = SavingSystem.SaveExists(saveFileName + "Init") ? SavingSystem.LoadData<Init>(saveFileName + "Init") : new Init();
Load();
UpdateOPS();
}
private void Start()
{
InvokeRepeating("AutoSaveCycle", saveCycleSeconds, saveCycleSeconds);
copperTimer = Time.time;
ironTimer = Time.time;
}
private void Update()
{
//copper += copperPerSecond * Time.deltaTime;
//iron += ironPerSecond * Time.deltaTime;
UpdateSmallInv();
if (Time.time > copperTimer)
{
copper += copperMineProd;
smallInvCopper.text = Utils.IdleGameNumberBigDouble(copper);
copperTimer += copperMineRate;
}
if (Time.time > ironTimer)
{
iron += ironMineProd;
smallInvIron.text = Utils.IdleGameNumberBigDouble(iron);
ironTimer += ironMineRate;
}
}
public void UpdateOPS() //
{
copperUpgradePrice = basePrice * BigDouble.Pow(priceMult, copperMineLevel);
copperMineProd = baseProd * BigDouble.Pow(prodMult, copperMineLevel);
copperMineRate = baseSpeed / Mathf.Pow(speedMult, copperMineLevel);
if (copperMineRate > 0) copperPerSecond = copperMineProd / copperMineRate;
if (ironMineRate > 0) ironPerSecond = ironMineProd / ironMineRate;
SaveMineData();
}
public void UpdateSmallInv()
{
smallInvCopper.text = Utils.IdleGameNumberBigDouble(copper);
smallInvIron.text = Utils.IdleGameNumberBigDouble(iron);
}
void AutoSaveCycle()
{
InternalSave();
}
void OnApplicationPause()
{
InternalSave();
SaveMineData();
SaveUpgradeData();
//SaveMineObjects();
Debug.Log("OnApplicationPause Triggered!", gameObject);
}
void OnApplicationQuit()
{
InternalSave();
SaveMineData();
SaveUpgradeData();
//SaveMineObjects();
Debug.Log("OnApplicationQuit Triggered!", gameObject);
}
void Load()
{
coins = data.coins;
copper = data.copper;
iron = data.iron;
damage = data.damage;
copperOreHealth = data.copperOreHealth;
ironOreHealth = data.ironOreHealth;
copperPerSecond = data.copperPerSecond;
ironPerSecond = data.ironPerSecond;
////////////////////////////
copperMineLevel = mineData.copperMineLevel;
ironMineLevel = mineData.ironMineLevel;
copperMineProd = mineData.copperMineProd;
ironMineProd = mineData.ironMineProd;
copperMineRate = mineData.copperMineRate;
ironMineRate = mineData.ironMineRate;
copperUpgradePrice = mineData.copperUpgradePrice;
ironUpgradePrice = mineData.ironUpgradePrice;
////////////////////////////
copperPriceMultiplier = upgradeData.copperPriceMultiplier;
ironPriceMultiplier = upgradeData.ironPriceMultiplier;
copperPriceULevel = upgradeData.copperPriceULevel;
ironPriceULevel = upgradeData.ironPriceULevel;
///////////////////////////
minesParent = mineObjects.minesParent;
minesMaxHitPoints = mineObjects.minesMaxHitPoints;
minesType = mineObjects.minesType;
}
public void InternalSave()
{
data.coins = coins;
data.copper = copper;
data.iron = iron;
data.damage = damage;
data.copperOreHealth = copperOreHealth;
data.ironOreHealth = ironOreHealth;
data.copperPerSecond = copperPerSecond;
data.ironPerSecond = ironPerSecond;
SavingSystem.SaveData(data, saveFileName);
}
public void SaveMineData()
{
mineData.copperMineLevel = copperMineLevel;
mineData.ironMineLevel = ironMineLevel;
mineData.copperMineProd = copperMineProd;
mineData.ironMineProd = ironMineProd;
mineData.copperMineRate = copperMineRate;
mineData.ironMineRate = ironMineRate;
mineData.copperUpgradePrice = copperUpgradePrice;
mineData.ironUpgradePrice = ironUpgradePrice;
SavingSystem.SaveData(mineData, saveFileName + "MineData");
}
public void SaveUpgradeData()
{
upgradeData.copperPriceMultiplier = copperPriceMultiplier;
upgradeData.ironPriceMultiplier = ironPriceMultiplier;
upgradeData.copperPriceULevel = copperPriceULevel;
upgradeData.ironPriceULevel = ironPriceULevel;
SavingSystem.SaveData(upgradeData, saveFileName + "UpgradeData");
}
public void SaveMineObjects()
{
mineObjects.minesParent = minesParent;
mineObjects.minesMaxHitPoints = minesMaxHitPoints;
mineObjects.minesType = minesType;
SavingSystem.SaveData(mineObjects, saveFileName + "MineObjects");
Debug.Log("mine objects saved!");
}
}
Answer by rh_galaxy · Jun 01 at 03:41 AM
Try adding
DontDestroyOnLoad(gameObject);
To the Awake() of DataController.
Also you can try DataController.Instance instead of dataC in SaveMines() just to get some more info about this (does the DataController Instance exist at all).
Thanks for the insight, i tried and DataController.instance existed, also added the DontDestroyOnLoad to the class, to fix the problem I ended up adding if (dataC == null) return; at the beginning of the SaveMines function so the code doesn't get executed before assigning the instance to dataC. Turns out I didn't really know how OnApplicationPause works, i was supposed to add if (pause == true) { // code } because otherwise it gets executed before awake methods.
Your answer
![](https://koobas.hobune.stream/wayback/20220613061202im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Elements of Listare null after loading scene 1 Answer
Start is not called after reloading the level 3 Answers
Field value changes after SceneManager.LoadScene been called 0 Answers
Coroutine Issue 1 Answer