Hello guys, i have a problem with a constructor. The class of this constructor is not instantiated at all, but when i press play, an error occurs from the constructor that can’t find a singleton instance. I searched all the references to this class, and i set the variables of this type to Null, i don’t know if this makes any sense. Another weird thing is that whenever i compile this class, the same errors occurs while in edit mode! Is the first time i use constructors, can you help me?
Other info: The class has the modifier (?) [System.serializable] and it does’t inherits monobehaviour.
Thanks in advance!
If it is System.Serializable, you probably have to provide a default no-argument constructor. Did you provide this?
Code? Exact error message?
Yes, i provided it
I didn’t think it is necessary but here it is
[System.Serializable]
public class PCData
{
public byte level { get; private set; }
public short maxHp { get; private set; }
public short maxMana { get; private set; }
public string name { get; private set; }
public void SetName(string value)
{
name = value;
}
public PCData()
{
GeneralGameData data = GeneralGameData.singleton;
if (data != null)
{
name = data.GetRandomName();
level = data.startingLevel;
maxHp = data.startingHp;
maxMana = data.startingMp;
uniqueId = Saver.ObtainUniqueId();
}
else
{
UnityEngine.Debug.LogError("error"); //THIS IS THE ERROR DISPLAYED IN CONSOLE
}
}
In the data class
static public GeneralGameData singleton;
private void Awake()
{
if (singleton == null)
{
singleton = this;
}
else
{
Destroy(this);
}
}
Exactly this is the reason I asked for it. So, what is your plan exactly? Why are you making a general, serializable class when you have a serialized MonoBehaviour with the very same data? What’s the reasoning?
I’m assuming this
is the GeneralGameData class. Right? (You left out the most important parts from your code you shared… it’s hard to help when you aren’t clear with your intentions) If that’s the case, I think it is a simple race condition. The creation of the PCData class comes first, the Awake of the GeneralGameData didn’t run yet, so the singleton is null.
(BTW, comparing unity objects, including MonoBehaviours to null is very costly, should avoid whenever possible, but this is a problem for another day)
I made that serializable class to store the player data and make it persistent between play sessions. The problem is that this constructor should be called only when i need it, and i need it when i create a new character. In the constructor it takes the starting values from the data, and i did it this way because the values can be changed from the inspector. The problem is that the constructor is called whenever i press the play button, and i don’t understand why, where this call is from. I’m sorry if this is unclear, and thanks for the help.
About this: “comparing unity objects, including MonoBehaviours to null is very costly”, is there a “cheap” way to do this?
I’m guessing you have this class serialized somewhere. During deserialization the parameterless constructor gets called. So probably what happens is your game gets loaded and this class gets deserialized somewhere.
What I do is this:
Optional.cs
using System;
using UnityEngine;
[Serializable]
public class Optional<T>
{
[SerializeField]
private T value;
[SerializeField]
private bool isEmpty = true;
private Optional() {}
private Optional(T value) => Value = value;
public T Value
{
get => value;
set
{
this.value = value;
isEmpty = value == null;
}
}
public bool IsEmpty
{
get => isEmpty;
set => isEmpty = value;
}
public static Optional<T> Create(T value) => new(value);
public static Optional<T> Empty() => new();
}
SingletonBehaviour.cs
using UnityEngine;
public abstract class SingletonBehaviour<T> : MonoBehaviour where T : Component
{
private static readonly Optional<T> _instance = Optional<T>.Empty();
public static T Instance => _instance.Value;
protected void Init(T instance)
{
if(!_instance.IsEmpty)
{
Destroy(instance.gameObject);
return;
}
_instance.Value = instance;
}
}
Then you can inherit from the SingletonBehaviour
if you want a singleon and call the Init(this);
from Awake()
. Obviously there are simpler ways to achieve this, but I have a framework where I make use all of these on different ways.