• 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
Question by sotirosn · Jun 13, 2013 at 11:17 PM · inspectorserializabledelegates

can you assign serializable delegates in the inspector?

Hopefully I am missing really simple but I am trying to do this.

 [Serializable]
 public delegate IEnumerator Routine();
 
 class Example : MonoBehaviour {
   public Routine start;
 
   public IEnumerator StartOne() {} 
   public IEnumerator StartTwo() {}
 
   IEnumerator Start() {
     yield return StartCoroutine(start());
   }
 }

And in the inspector I would like there to be a 'start' field with a drop down of ["StartOne", "StartTwo"]. I can do this if I make a custom editor for my class Example and use reflection to scoop up the matching methods, but I don't want a custom inspector for everyone that stores a Routine publicly. i.e:

 [CustomEditor(typeof(Example))]
 class ExampleEditor : Unity.Editor {
   override OnInpsectorGUI() {
       // collect method names = Example.GetMethods() :filter signatures
       // select index from popup(methods)
       // example.start = methods[selected index];

       // or
        // select example.index from popup(methods)
       // and just set the field on Awake from the index
   }
 }

actually I tried this and it works but it breaks as soon as I upgrade my delegate to a generic

 [Serializable]
 public delegate IEnumerator Routine<T>(T parameters); // compiles but stops the inspector form saving the below..
 
 public class Example {
     public Routine<int> start;
 
     IEnumerator Start() {
         yield return StartCoroutine(start(5));
     }
 
     public IEnumerator MaybeStartThis(int value) {
       ...
     }
 }

The editor finds the methods and assigns them but they are cleared as soon as I hit play? I don't know. I could save each method name as an index and assign it at runtime with reflection but this sounds cumbersome. Another problem is that I seem to have to make a custom editor for every singe class that has a delegate field!

If anyone can help me I would be much obliged.

Thx,

Comment
save
vexe
softrare
youblistermypaint
wsfflyn
quibit

People who like this

6 Show 2
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
avatar image Loius · Jun 13, 2013 at 11:42 PM 0
Share

http://docs.unity3d.com/Documentation/ScriptReference/PropertyDrawer.html

PropertyDrawers let you define what a type looks like in the inspector; if you create a wrapper "DelegateField" class and give it a custom drawer you won't have to write an editor for each class. (I don't have any words of wisdom on drawers other than I couldn't make them generic enough for me so i ended up not using them at all x))

avatar image sotirosn · Jun 14, 2013 at 12:01 AM 0
Share

There was a great in depth answer here a second ago.. Why is it gone?

3 Replies

· Add your reply
  • Sort: 
avatar image
Best Answer

Answer by sotirosn · Jun 16, 2013 at 11:46 AM

2 days of searching and this is my best solution so far. It has some kinks like having to make an editor file for each delegate signature (see Editor/TriggerActionDrawer.cs), and it does not automatically repopulate candidate matches after compiling (must manually refresh), and you have to manually initialize fields on Awake(), but other than that it works great.

alt text

Action.cs:

 [System.Serializable]
 public class Action<T> {
     // public settings
     public Object target;
     public string method;
     
     // inspector cache
     public string[] candidates = {};
     public int index;
     
     // invocation
     public System.Action<T> action;
     
     public void Awake() {
         action = System.Action<T>.CreateDelegate(typeof(System.Action<T>), target, target.GetType().GetMethod(method)) as System.Action<T>;
     }
 }
 
 public class ActionAttribute : PropertyAttribute {
     public System.Type returnType;
     public System.Type[] paramTypes;
     public ActionAttribute(System.Type returnType = null, params System.Type[] paramTypes) {
         this.returnType = returnType != null ? returnType : typeof(void);
         this.paramTypes = paramTypes != null ? paramTypes : new System.Type[0];
     }
     
     public System.Delegate method;
 }

Editor/ActionDrawer.cs:

 using UnityEngine;
 using UnityEditor;
 using System.Reflection;
 using System.Collections;
 using System.Collections.Generic;
 
 [CustomPropertyDrawer(typeof(Action))]
 public class ActionDrawer : PropertyDrawer {
     const float rows = 3;
     
     public override void OnGUI (Rect pos, SerializedProperty properties, GUIContent label) {
         //Debug.Log(((attribute as ActionAttribute).paramTypes.Length).ToString());
         
         SerializedProperty targetProperty = properties.FindPropertyRelative("target");
         SerializedProperty methodNameProperty = properties.FindPropertyRelative("method"); 
         SerializedProperty candidateNamesProperty = properties.FindPropertyRelative("candidates");
         SerializedProperty indexProperty = properties.FindPropertyRelative("index");
         
         // pass through label
         EditorGUIUtility.LookLikeControls();
         EditorGUI.LabelField(
             new Rect (pos.x, pos.y, pos.width/2, pos.height/rows),
             label
         );
         
         // target + method section
         EditorGUI.indentLevel++;
         EditorGUI.BeginChangeCheck(); // if target changes we need to repopulate the candidate method lists
         
         // select target
         EditorGUI.PropertyField(
             new Rect (pos.x, pos.y += pos.height/rows, pos.width, pos.height/rows),
             targetProperty
         );
         if(targetProperty.objectReferenceValue == null) {
             return; // null objects have no methods - don't continue
         } 
         
         // polulate method candidate names
         string[] methodCandidateNames;
         if(EditorGUI.EndChangeCheck()) {
             // lets do some reflection work -> search, filter, collect candidate methods..
             methodCandidateNames = RepopulateCandidateList(targetProperty, candidateNamesProperty, indexProperty);
         }
         else {
             methodCandidateNames = new string [candidateNamesProperty.arraySize]; 
             
             int i = 0;
             foreach(SerializedProperty element in candidateNamesProperty) {
                 methodCandidateNames[i++] = element.stringValue;
             }
         }
         
         // place holder when no candidates are available
         if(methodCandidateNames.Length == 0) {
             EditorGUI.LabelField (
                 new Rect (pos.x, pos.y += pos.height/rows, pos.width, pos.height/rows),
                 "Method",
                 "none"
             );    
             return; // no names no game
         }
         
         // select method from candidates
         indexProperty.intValue = EditorGUI.Popup (
             new Rect (pos.x, pos.y += pos.height/rows, pos.width, pos.height/rows),
             "Method (" + targetProperty.objectReferenceValue.GetType().ToString() + ")",
             indexProperty.intValue,
             methodCandidateNames
         );
         
         methodNameProperty.stringValue = methodCandidateNames[indexProperty.intValue];
         EditorGUI.indentLevel--;
     }    
 
     public string[] RepopulateCandidateList(
             SerializedProperty targetProperty, 
             SerializedProperty candidateNamesProperty,
             SerializedProperty indexProperty
     ) {
         System.Type type = targetProperty.objectReferenceValue.GetType();
         System.Type[] paramTypes = this.paramTypes;
         IList<MemberInfo> candidateList = new List<MemberInfo>();
         string[] candidateNames;
         int i = 0; 
         
         Debug.Log ("Candidate Criteria:");
         Debug.Log ("\treturn type:" + returnType.ToString());
         Debug.Log ("\tparam count:" + paramTypes.Length);    
         foreach(System.Type paramType in paramTypes)
             Debug.Log("\t\t" + paramType.ToString());
         
         type.FindMembers(
             MemberTypes.Method,
             BindingFlags.Instance | BindingFlags.Public,
             (member, criteria) => {
                 Debug.Log("matching " + member.Name);
                 MethodInfo method;
                 if((method = type.GetMethod(member.Name, paramTypes)) != null && method.ReturnType == returnType) {
                     candidateList.Add(method);
                     return true;
                 }
                 return false;
             },
             null
         );
         
         // clear/resize/initialize storage containers
         candidateNamesProperty.ClearArray();
         candidateNamesProperty.arraySize = candidateList.Count;
         candidateNames = new string[candidateList.Count];
         
         // assign storage containers
         i = 0;
         foreach(SerializedProperty element in candidateNamesProperty) {
             element.stringValue = candidateNames[i] = candidateList[i++].Name;
         }
     
         // reset popup index
         indexProperty.intValue = 0;
         
         return candidateNames;
     }
     
     public System.Type returnType {
         get { return attribute != null ? (attribute as ActionAttribute).returnType : typeof(void); }
     }
 
     public System.Type[] paramTypes {
         get { 
             return (attribute != null && (attribute as ActionAttribute).paramTypes != null) ? (attribute as ActionAttribute).paramTypes : new System.Type[0];
         }
     }
     
     public System.Delegate method {
         get { return attribute != null ? (attribute as ActionAttribute).method : null; }
         set { (attribute as ActionAttribute).method = value; }
     }
     
     public override float GetPropertyHeight (SerializedProperty property, GUIContent label) {
         return base.GetPropertyHeight (property, label) * rows;
     }
 }

Trigger.cs:

 // example consumer
 using UnityEngine;
 
 public class Trigger : MonoBehaviour {
     // must derive a new class because templates and inheritance don't seem to trigger the custom drawer
     [System.Serializable]
     public class Action : Action<Collider> { }
     
     [Action(typeof(void), typeof(Collider))]
     public Action enters, stays, exits;
     
     public void OnTriggerEnter(Collider collider) {
         Debug.Log ("here");
         enters.action(collider);
     }
     
     public void OnTriggerStays(Collider collider) {
         stays.action(collider);
     }
     
     public void OnTriggerExit(Collider collider) {
         exits.action(collider);
     }
     
     public void Awake() {
         enters.Awake();
         stays.Awake();
         exits.Awake();
     }
 }

Example.cs:

 // example provider
 using UnityEngine;
 
 public class Example : MonoBehaviour {
     public void Hello(Collider collider) {
         Debug.Log ("Hello " + collider + "!");
     }
     
     public void Goodbye(Collider collider) {
         Debug.Log ("bye " + collider);
     }
 }

Editor/TriggerActionDrawer.cs:

 using UnityEditor;
 
 [CustomPropertyDrawer(typeof(Trigger.Action))]
 public class TriggerActionDrawer : ActionDrawer { }



screen shot 2013-06-16 at 5.36.58 am.png (63.6 kB)
Comment
clunk47
rakkarage
hitmax87
wsfflyn
ThinhHB
samf1111

People who like this

6 Show 3 · 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
avatar image sotirosn · Jun 16, 2013 at 11:59 AM 0
Share

you can also set an action in script like this,

 GetComponent<Trigger>().enters.action =(collider)=> {
     Debug.Log ("enters " + collider);
 };
avatar image joanllobera · Dec 09, 2013 at 04:24 PM 0
Share

hi, i have tried to simply put your examples in a unity project and I get compilation errors. For example, in declaring

public System.Action action;

it complains that T is not defined, probably a directive is missing.

could you simply post the cs files? this would be very useful to understand better how to deal with delegates in custom editor. best, Joan

avatar image sotirosn · Dec 17, 2013 at 05:14 PM 0
Share

Sorry the template declaration '<T>' was being interpreted as markup or something, but its fixed now. Let me know if you still get compile errors.

avatar image

Answer by stepan-stulov · Aug 03, 2015 at 11:32 AM

Unity has serialisable delegates from the box.

Ok, Unity has (semi-) serialisable delegates from the box. It's not directly a serialisation of delegates, rather of observers, a notifier+observer pattern. Unity's UnityEvent, that's used for Inspector-driven UI event listening (when you hang a game object and its specific method to call on a button's Click event e.g.) is serialised multicast delegates. It's slow (there was an article somewhere comparing native C# delegates and Unity's delegates and it was magnitude 3 difference, but I can't seem to find it). But it works. You can also declare your own events/delegates as either public or [SerilizeField] private fields of type UnityEvent (or one of it's generic variations, up to 4 arguments, but in this case you'd need to extend the generic class, as Unity can't serialise generics) and they become inspector-injectable. These events have API for adding and removing listeners (and obviously invoking) from script. But script-added/removed listeners are non persistent. So happy mouse-programming:)

This feature enable developers to expose events for hooking up listeners to non-programmer folks. I'd say quiet a niche tool for event-driven visual level editing. I can imagine creating a set of actor game objects and a "protocol" of event cross-linking and vuala, a game designer can create little sandbox simulations with no coding help. Correct me if I'm wrong, this is how the GameMaker works in their visual scripting tool.

That having been said, I think you should keep programmer-for-programmer delegates in pure C# and unexposed for inspector.

The biggest problem of these serialised events is that they remain completely unadvertised outside of new UI system by Unity, although they're completely UI agnostic.

Here is a little video, where a guy uses these events (without the UI system):

https://www.youtube.com/watch?v=tZENtGYccGI

 public OrcWasCreatedEvent OrcWasCreated; // public class OrcWasCreatedEvent : UnityEvent<Orc>
 public UnityEvent AllOrcsWereDestroyed;
 
 // later
 
 OrcWasCreated.Invoke(myOrc);
 AllOrcsWereDestroyed.Invoke();


Comment
glasslabgames
eppz
softrare
gemfile
youblistermypaint
Wellmann
ConnectionFaild

People who like this

7 Show 1 · 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
avatar image gemfile · Jun 08, 2016 at 02:41 AM 0
Share

thank you man!

avatar image

Answer by vexe · May 19, 2014 at 09:19 AM

See this answer to see how you could basically serialize delegates. See uFAction for a full already-made solution for serializable and inspectable delegates.

Comment
clunk47
youblistermypaint

People who like this

2 Show 1 · 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
avatar image clunk47 · May 22, 2014 at 08:39 PM 0
Share

uFAction is amazing dude!

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.

Welcome to Unity Answers

If you’re new to Unity Answers, please check our User Guide to help you navigate through our website and refer to our FAQ for more information.

Before posting, make sure to check out our Knowledge Base for commonly asked Unity questions.

Check our Moderator Guidelines if you’re a new moderator and want to work together in an effort to improve Unity Answers and support our users.

Follow this Question

Answers Answers and Comments

21 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Class member shows as None inside inspector 1 Answer

How to get the subscribed functions of a multicast delegate? 1 Answer

Serializable Class values changed in inspector wont work on android 0 Answers

Serializable class with access to its Monobehaviour host 2 Answers

Custom Inspector for an array of a serialized class 0 Answers


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