Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
0
Question by Thomas-Slade · Oct 21, 2016 at 01:51 PM · startdictionarysingletoncallbackserializable

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.

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

1 Reply

· Add your reply
  • Sort: 
avatar image
0

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.

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

3 People are following this question.

avatar image avatar image avatar image

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


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges