Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Generate a method/property list like the one in a UnityEvent but have some control

Discussion in 'Scripting' started by davidosullivan, Nov 24, 2015.

  1. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    I am trying to create something a bit like a UnityEvent action list, but I need a bit more control over what it allows in the list and what it doesn't. Below is what I am talking about.
    unityEvent-methodSelect-Transform.jpg

    For example if you add a gameobject to a Unity Event and look at its method/property list for 'Transform' you dont get any options for things like 'localscale' or 'localrotation', in the MeshRenderer you get an option for 'Material material' but what if the MeshRenderer has an array of materials? Examples below.
    unityEvent-methodSelect-MeshRenderer.jpg unityEvent-MeshRenderer-hasArrayOfMaterials.jpg

    I appreciate that making extra methods to deal with these things will require work, but I'd like the ability to define what gets excluded from the list. So what I'd like to know is how I go about 're' generating a list like that for myself.
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    You'll need reflection.

    I have my own UnityEvent system I wrote a while before UnityEvent was released. I'll show you the source code for that to see how I pulled it off:
    trigger_inspector.png

    It's a class called 'Trigger', that you can place in any script (like UnityEvent) that lets you trigger an event.
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Scenario/Trigger.cs

    The actual place where I store the targets is a list of a class called TriggerTarget in Trigger:
    https://github.com/lordofduct/space...ster/SpacepuppyBase/Scenario/TriggerTarget.cs

    It runs in 4 modes, 'Trigger All' which works with a special ITriggerableMechanism interface I have defined, Trigger specific, SendMessage... and the part you're interested in (and how UnityEvent kind of works) 'Call Method'.

    The inspector for it is:
    https://github.com/lordofduct/space...ditor/Scenario/TriggerTargetPropertyDrawer.cs

    Look under the method 'DrawAdvanced_CallMethodOnSelected' how I draw it in that mode.




    And lastly, the part you're probably most interested in. How to reflect out the members. This is the function I use:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/Dynamic/IDynamic.cs
    Code (csharp):
    1.  
    2.         public static IEnumerable<System.Reflection.MemberInfo> GetEasilySerializedMembers(object obj, MemberTypes mask = MemberTypes.All, DynamicMemberAccess access = DynamicMemberAccess.ReadWrite)
    3.         {
    4.             if (obj == null) yield break;
    5.  
    6.             bool bRead = access.HasFlag(DynamicMemberAccess.Read);
    7.             bool bWrite = access.HasFlag(DynamicMemberAccess.Write);
    8.             var members = com.spacepuppy.Dynamic.DynamicUtil.GetMembers(obj, false);
    9.             foreach (var mi in members)
    10.             {
    11.                 if ((mi.MemberType & mask) == 0) continue;
    12.  
    13.                 if (mi.DeclaringType.IsAssignableFrom(typeof(UnityEngine.MonoBehaviour)) ||
    14.                     mi.DeclaringType.IsAssignableFrom(typeof(SPComponent)) ||
    15.                     mi.DeclaringType.IsAssignableFrom(typeof(SPNotifyingComponent))) continue;
    16.  
    17.                 switch (mi.MemberType)
    18.                 {
    19.                     case System.Reflection.MemberTypes.Method:
    20.                         {
    21.                             var m = mi as System.Reflection.MethodInfo;
    22.                             if (m.IsSpecialName) continue;
    23.                             if (m.IsGenericMethod) continue;
    24.  
    25.                             var parr = m.GetParameters();
    26.                             if (parr.Length == 0)
    27.                             {
    28.                                 yield return m;
    29.                             }
    30.                             else
    31.                             {
    32.                                 bool pass = true;
    33.                                 foreach (var p in parr)
    34.                                 {
    35.                                     if (!(VariantReference.AcceptableType(p.ParameterType) || p.ParameterType == typeof(object)))
    36.                                     {
    37.                                         pass = false;
    38.                                         break;
    39.                                     }
    40.                                 }
    41.                                 if (pass) yield return m;
    42.                             }
    43.                         }
    44.                         break;
    45.                     case System.Reflection.MemberTypes.Field:
    46.                         {
    47.                             var f = mi as System.Reflection.FieldInfo;
    48.                             if (f.IsSpecialName) continue;
    49.  
    50.                             if (VariantReference.AcceptableType(f.FieldType)) yield return f;
    51.                         }
    52.                         break;
    53.                     case System.Reflection.MemberTypes.Property:
    54.                         {
    55.                             var p = mi as System.Reflection.PropertyInfo;
    56.                             if (p.IsSpecialName) continue;
    57.                             if (!p.CanRead && bRead) continue;
    58.                             if (!p.CanWrite && bWrite) continue;
    59.                             if (p.GetIndexParameters().Length > 0) continue; //indexed properties are not allowed
    60.  
    61.                             if (VariantReference.AcceptableType(p.PropertyType)) yield return p;
    62.                         }
    63.                         break;
    64.                 }
    65.  
    66.             }
    67.         }
    68.  
    I loop over and return only members whose types can be easily serialized in my VariantReference, which is how I store the value that will be passed as the argument of the function (or multiple arguments, if the function accepts more than 1).

    This is VariantReference:
    https://github.com/lordofduct/space...lob/master/SpacepuppyBase/VariantReference.cs
     
  3. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    Lol, you obviously didn't recognize me... I was trying to see if there was a way of doing it without SpacePuppy...
     
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    I just noticed this part of your post:

    I don't think you can modify what is shown in the list for UnityEvent itself. That's pretty specific to UnityEvent, it has its own rules. If it could deal with properties like 'localPosition', it would have included them.

    I demonstrated how to roll your own.
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    Awww, sorry, yeah. I didn't notice it was you.

    As I said, you won't be able to just modify UnityEvent's list. You don't necessarily have to use Spacepuppy either. It's why I have the source for free... it's more a way for people to see how I accomplished the things I do (it's seldom stable as I'm actively working with it all the time), and can adapt from there, rip out sections. Like the function I just posted showing how I pull the appropriate members.

    As you can see, it's not trivial to do what you're asking.
     
  6. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    I've done it using SpacePuppy already tho, if you look at the last post in our 'conversation' I've even attached the files... I was just waiting to hear from you is all...
     
  7. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    Let me take a look.
     
  8. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    @everyone SpacePuppy is awesome btw ;)
     
  9. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
  10. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
  11. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    Ok so I decided to stick with SpacePuppy. So SP will get me all the data I need. no problem...

    However I'd now like to know how to format the resulting dropdown (Popup) a bit more nicely... I can generate a single level popup, just fine but in the Unity Events dropdown (Popup) I gave as an example it is organised into 'Sub-Menus' so 'GameObject >', 'Transform >', 'MeshFilter >' etc

    So I have an array of 'Fields', 'Properties' and 'Methods' and I'd like those names to be in the top level and show the contents of those arrays when you hover over the top level...

    What do I need to do in my CustomEditor to make it display like that? I tried a multidimensional array but unity didn't like that...
     
  12. davidosullivan

    davidosullivan

    Joined:
    Jun 9, 2015
    Posts:
    387
    I sussed this out actually, what you need to do is make sure that every entry that you want in a submenu starts like this for example:-
    "MySubMenu/"+item.name
    its the bit that ends in the forward slash that does the magic, every item that starts with "MySubMenu/" will show in the popup as:
    My Sub Menu > Item 1
    Item 2
    Item 3 (etc)
    ... and the great thing is that the items retain their order in the list even though its drawn differently, so those items are still item[0], item[1], item[2], they have not been changed into item[0][0], item [0][1] item[0][2] as you might expect...