- Home /
Using a custom Start()/Awake() in non monobehaviour classes.
After much toil, I've created a decent 'serializable' dictionary. I originally followed the ISerializationCallbackReciever method, shown here, but this proved to be ridiculously error-prone with in-editor functionality, a fragile hack, and a complete waste of time (it seems many Unity users warn that serialization in Unity is a mess anyway). Instead, I've simply created a dictionary that stores as two lists, and only forms a dictionary on Start().
These dictionaries are not Monobehaviours, so I can't simply say
// class SDictionary // OnStart() { // Make yourself into a dictionary }
Instead. I need a way to hook the lists-to-dictionary code up to the Start or Awake event. As you can imagine, I can easily do this by calling the function from the Dictionary's parent, which will be a monobehaviour. This works fine, but it means I have to both declare an SDictionary, AND call its builder function. It's just a bit of extra hassle, plus some error-prone code.
I've tried creating my own custom ObjectStart() callback event, using delegates, similair to the second part of this tutorial. But this has a problem. See, I'm using a singleton Monobehaviour called 'EventManager' to use its Start() to call the ObjectStart(). It's a singleton, so all of the SDictionaries can access it on construction. However, accessing a singleton on the construction of these dictionaries, it seems, calls before runtime. Because the singleton tries to find its instance reference using FindObjectOfType(), this causes an error, since that function apparently can't run before runtime, UNLESS I use [ExecuteInEditMode] on the EventManager. But then that will mess up when Monobehaviour calls Start(). Plus, it overall feels like a bit of a hack.
I was wondering if there was a better way of doing this. That is: using a singleton that can be accessed before runtime so that my custom dictionaries can subscribe their ObjectStart() callbacks to the singleton's Start().
Code:
using UnityEngine;
using System.Collections;
// Singleton EventManager to handle custom events.
public class EventManager : MonoBehaviour
{
// Singleton instance reference.
static EventManager instance = null;
// The objectStart multidelegate, for non-monobehaviour objects that need a Start() event to subscribe to.
public delegate void MultiDelegate();
public MultiDelegate objectStart;
void Start()
{
objectStart();
}
// Getter for the EventManager singleton.
public static EventManager Instance
{
get
{
if (instance == null)
{
instance = FindObjectOfType<EventManager>();
}
return instance;
}
}
}
// My Market class, which needs dictionaries that can be serialized.
public class Market : MonoBehaviour
{
// A dictionary of values for each resource type.
[SerializeField]
public Supplies supplies = new Supplies();
// The custom SDictionary needs to be extended into a type-specific class to work properly.
// This one uses Goods (trade resources), and values for each of them.
[Serializable]
public class Supplies : SDictionary<Good, float>
{
public Supplies() : base(SGoods.goods.goods)
{
}
}
}
namespace DataUtility
{
// Interface for labels.
// Labels are implemented on objects that can be used by the SDictionary as keys.
// This is because the SDictionary turns object keys (references) into string labels (values) on serialization,
// and turns them back on deserialization. Objects that need to be used as keys in an SDictionary should
// implement ILabel, and typically just set the label to their name (e.g. the silk object is labelled "Silk").
public interface ILabel
{
string label
{
get;
set;
}
}
// The custom dictionary class, that can be serialized as two lists, and builds a dictionary on runtime.
// Note that it requires the keys to use the ILabel interface.
[Serializable]
public class SDictionary<TK, TV> : Dictionary<TK, TV> where TK : ILabel
{
// Serialized keys (uses key labels as strings).
[SerializeField]
public List<string> keys = new List<string>();
// Serialized values.
[SerializeField]
public List<TV> values = new List<TV>();
// The key stock is the reference used to convert strings back to the proper types on creating the dictionary.
protected List<TK> keyStock = new List<TK>();
// Constructor, which requires a specified keyStock.
public SDictionary(List<TK> _keyStock)
{
EventManager.Instance.objectStart += CreateDictionary;
keyStock = _keyStock;
}
// Build the dictionary from the lists. Should be called on Start().
public void CreateDictionary()
{
Clear();
for (int k = 0; k < keys.Count; k++)
{
Add(LabelToKey(keys[k]), values[k]);
}
}
// Using the keyStock, find the key object which the stored string is supposed to represent.
private TK LabelToKey(string key)
{
TK stockKey = keyStock.Find(k => k.label == key);
if (stockKey == null)
{
throw new Exception("The label: " + key + " could not be found in the stock list: " + keyStock);
}
else
{
return stockKey;
}
}
}
}
For those interested/looking for help on serializable dictionaries, this dictionary works by turning keys into strings. On play, the strings are turned back into keys by searching through the static 'stock list' of types, able to take the string "Silk" and return the actual object for Silk. Any classes used as keys need to implement an ILabel interface, so that the abstract Dictionary class knows its keys have a name. See the SDictionary class for how I worked around it.
Answer by Thomas-Slade · Oct 21, 2016 at 04:15 PM
I have a solution, for now.
I've made EventManager a static class. This means its multidelegate - objectStart - must also be static. I thought this would make it impossible to add the SDictionary functions to, since I've been told that static objects cannot be changed, but I'm starting to realize that static objects CAN be changed on compile (?) which is precisely when the SDictionary objects are declared (incidentally, I assume this means I won't be able to create SDictionaries during runtime, or rather, I'll need to call their builder function myself).
Meanwhile, EventManager's static objectStart delegate is invoked from a regular old game object's Start() function. It's a 3-part system; Regular Game Object -> Static EventManager -> SDictionary. Not perfect, but it works for now.
Your answer
Follow this Question
Related Questions
Why not using Start exlusively in NetworkBehaviours? 0 Answers
Why does AddComponent not exist in the current context? 2 Answers
Can't access singleton's dictionary from another singleton 0 Answers
Dictionary within a dictionary? 1 Answer
Crash when using "Base" as member... is it a reserved word ? 1 Answer