Snippets

Bjarke Elias Odin Serialized CustomEvent struct

Created by Bjarke Elias last modified
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;

// Usage:
public class SomeScriptableObject : SerializedScriptableObject
{
    public BetterEventCollection Test;

    public void Update()
    {
        if (Input.GetKey(KeyCode.Space))
        {
            this.Test.Invoke();
        }
    }
}

// Data:
public struct BetterEventCollection
{
    [HideReferenceObjectPicker, ListDrawerSettings(CustomAddFunction = "GetDefaultBetterEvent", OnTitleBarGUI = "DrawInvokeButton")]
    public List<IBetterEvent> Events;

    public void Invoke()
    {
        if (this.Events == null) return;
        foreach (var e in this.Events)
        {
            if (e == null) continue;
            e.Invoke();
        }
    }

#if UNITY_EDITOR

    private IBetterEvent GetDefaultBetterEvent()
    {
        return new CustomEvent(null);
    }

    private void DrawInvokeButton()
    {
        if (SirenixEditorGUI.ToolbarButton("Invoke"))
        {
            this.Invoke();
        }
    }

#endif
}

public interface IBetterEvent
{
    Delegate Delegate { get; }
    void Invoke();
}

public class CustomEvent : IBetterEvent
{
    [SerializeField, HideInInspector]
    private Action action;

    public Delegate Delegate { get { return this.action; } }

    public CustomEvent(Action action)
    {
        this.action = action;
    }

    public void Invoke()
    {
        if (this.action != null)
        {
            this.action();
        }
    }
}

public class CustomEvent<T> : IBetterEvent
{
    [SerializeField, LabelText("$GetArg1LabelText")]
    private T arg1;

    [SerializeField, HideLabel, HideInInspector]
    private Action<T> action;

    public CustomEvent(Action<T> action)
    {
        this.action = action;
    }

    public Delegate Delegate { get { return this.action; } }

    public void Invoke()
    {
        if (this.action != null)
        {
            this.action(this.arg1);
        }
    }

    private string GetArg1LabelText()
    {
        if (this.action != null && this.action.Method != null)
        {
            return this.action.Method.GetParameters()[0].Name.SplitPascalCase();
        }

        return "Arg 1";
    }
}

public class CustomEvent<T1, T2> : IBetterEvent
{
    [SerializeField, LabelText("$GetArg1LabelText")]
    private T1 arg1;

    [SerializeField, LabelText("$GetArg2LabelText")]
    private T2 arg2;

    public CustomEvent(Action<T1, T2> action)
    {
        this.action = action;
    }

    public Delegate Delegate { get { return this.action; } }

    [SerializeField, HideLabel, HideInInspector]
    private Action<T1, T2> action;

    public void Invoke()
    {
        if (this.action != null)
        {
            this.action(this.arg1, this.arg2);
        }
    }

    private string GetArg1LabelText()
    {
        if (this.action != null && this.action.Method != null)
        {
            return this.action.Method.GetParameters()[0].Name.SplitPascalCase();
        }

        return "Arg 1";
    }

    private string GetArg2LabelText()
    {
        if (this.action != null && this.action.Method != null)
        {
            return this.action.Method.GetParameters()[1].Name.SplitPascalCase();
        }

        return "Arg 2";
    }
}

// Drawers:

// This drawer is completly optional but cleans up the inspector a lot. Try outcommenting it and see the difference.
// Instead of drawing the BetterEvent struct with its label and then a list named Events, this drawer will skip drawing the foldout, and draw the Events list with its own label.
public class BetterEventDrawer : OdinValueDrawer<BetterEventCollection>
{
    protected override void DrawPropertyLayout(GUIContent label)
    {
        this.Property.Children["Events"].Draw(label);
    }
}

public class IBetterEventAttributeDrawer<T> : OdinValueDrawer<T>
    where T : IBetterEvent
{
    // For while the user is modifying the delegate.
    private UnityEngine.Object tmpTarget;

    protected override void DrawPropertyLayout(GUIContent label)
    {
        SirenixEditorGUI.BeginBox();
        {
            SirenixEditorGUI.BeginToolbarBoxHeader();
            {
                var rect = GUILayoutUtility.GetRect(0, 19);
                var unityObjectFieldRect = rect.Padding(2).AlignLeft(rect.width / 2);
                var methodSelectorRect = rect.Padding(2).AlignRight(rect.width / 2 - 5);
                var dInfo = this.GetDelegateInfo();

                EditorGUI.BeginChangeCheck();
                var newTarget = SirenixEditorFields.UnityObjectField(unityObjectFieldRect, dInfo.Target, typeof(UnityEngine.Object), true);
                if (EditorGUI.EndChangeCheck())
                {
                    this.tmpTarget = newTarget;
                }

                if (dInfo.Target)
                {
                    EditorGUI.BeginChangeCheck();
                    var selectorText = dInfo.Method == null ? "Select a method" : dInfo.Method.GetNiceName();
                    var newMethod = MethodSelector.DrawSelectorDropdown(methodSelectorRect, selectorText, this.CreateSelector);
                    if (EditorGUI.EndChangeCheck())
                    {
                        this.CreateAndAssignNewDelegate(newMethod.FirstOrDefault());
                        this.tmpTarget = null;
                    }
                }
            }
            SirenixEditorGUI.EndToolbarBoxHeader();

            // Draws the rest of the ICustomEvent, and since we've drawn the label, we simply pass along null.
            this.CallNextDrawer(null);
        }
        SirenixEditorGUI.EndBox();
    }

    private void CreateAndAssignNewDelegate(DelegateInfo delInfo)
    {
        var method = delInfo.Method;
        var target = delInfo.Target;

        IBetterEvent result = null;
        var pTypes = method.GetParameters().Select(x => x.ParameterType).ToArray();
        var args = new object[1];

        if (pTypes.Length == 0)
        {
            var action = (Action)Delegate.CreateDelegate(typeof(Action), target, method);
            result = new CustomEvent(action);
        }
        else if (pTypes.Length == 1)
        {
            var delType = typeof(Action<>).MakeGenericType(pTypes);
            args[0] = Delegate.CreateDelegate(delType, target, method);
            result = (IBetterEvent)Activator.CreateInstance(typeof(CustomEvent<>).MakeGenericType(pTypes), args);
        }
        else if (pTypes.Length == 2)
        {
            var delType = typeof(Action<,>).MakeGenericType(pTypes);
            args[0] = Delegate.CreateDelegate(delType, target, method);
            result = (IBetterEvent)Activator.CreateInstance(typeof(CustomEvent<,>).MakeGenericType(pTypes), args);
        }
        else
        {
            Debug.LogError("TODO: Add more generic CustomEvent variants.");
        }

        this.Property.Tree.DelayActionUntilRepaint(() =>
        {
            this.ValueEntry.WeakSmartValue = result;
        });
    }

    private DelegateInfo GetDelegateInfo()
    {
        var value = this.ValueEntry.SmartValue;
        var del = value.Delegate;
        var methodInfo = del == null ? null : del.Method;

        UnityEngine.Object target = null;
        if (this.tmpTarget)
        {
            target = this.tmpTarget;
        }
        else if (del != null)
        {
            target = del.Target as UnityEngine.Object;
        }

        return new DelegateInfo() { Target = target, Method = methodInfo };
    }

    private OdinSelector<DelegateInfo> CreateSelector(Rect arg)
    {
        arg.xMin -= arg.width;
        var info = this.GetDelegateInfo();
        var sel = new MethodSelector(info.Target);
        sel.SetSelection(info);
        sel.ShowInPopup(arg);
        return sel;
    }

}

public struct DelegateInfo
{
    public UnityEngine.Object Target;
    public MethodInfo Method;
}

public class MethodSelector : OdinSelector<DelegateInfo>
{
    private BindingFlags bindingFlags = Flags.StaticPublic | Flags.InstancePublic | BindingFlags.FlattenHierarchy;
    private UnityEngine.Object currentObj = null;
    private HashSet<DelegateInfo> seenMethods = new HashSet<DelegateInfo>();

    public MethodSelector(UnityEngine.Object obj)
    {
        this.currentObj = obj;
    }

    protected override void BuildSelectionTree(OdinMenuTree tree)
    {
        tree.Config.DrawSearchToolbar = true;
        tree.Selection.SupportsMultiSelect = false;
        tree.Add("None", new DelegateInfo());
        tree.Config.UseCachedExpandedStates = false;

        var gameObject = this.currentObj as GameObject;
        var component = this.currentObj as Component;

        if (component)
        {
            gameObject = component.gameObject;
        }

        if (gameObject)
        {
            this.AddMethods(tree, gameObject);

            var components = gameObject.GetComponents(typeof(Component));
            foreach (var c in components)
            {
                this.AddMethods(tree, c);
            }
        }
        else if (this.currentObj)
        {
            this.AddMethods(tree, this.currentObj);
        }
    }

    public override bool IsValidSelection(IEnumerable<DelegateInfo> collection)
    {
        var info = collection.FirstOrDefault();
        return info.Method != null;
    }

    private void AddMethods(OdinMenuTree tree, UnityEngine.Object obj)
    {
        var methods = obj.GetType().GetMethods(this.bindingFlags);
        foreach (var mi in methods)
        {
            if (mi.ReturnType != typeof(void)) continue;
            if (mi.IsGenericMethod) continue;
            var target = mi.IsStatic ? null : obj;
            var info = new DelegateInfo() { Target = target, Method = mi };
            if (this.seenMethods.Add(info))
            {
                tree.Add(obj.GetType().GetNiceName() + "/" + mi.GetNiceName(), info);
            }
        }
    }
}

Comments (1)

  1. Linda Melson

    goodreads.com/user/show/177488397-editsiz-serverler twitch.tv/editsizserverler behance.net/editsizserverl instapaper.com/p/14184805 coub.com/metin2-pvpserverler myanimelist.net/profile/editsizserverler worldcosplay.net/member/1754620 onmogul.com/editsiz-serverler metin2pvpserverler.hashnode.dev/metin2-pvp-serverler gaiaonline.com/profiles/editsizserverler/46656672/ leetcode.com/editsizserverler/ coolors.co/u/editsiz_serverler unsplash.com/@editsizserverler metin2-pvp-serverler.jimdosite.com/ zazzle.com/mbr/238039878416461152 brownbook.net/business/52637466/metin2-pvp-serverler community.tubebuddy.com/index.php?members/205346/#about reedsy.com/discovery/user/editsizserverler hackerearth.com/@editsizserverlerorg wakelet.com/wake/7OIcdWsbjqXHh82vRa9ZZ peatix.com/user/21877725/view penzu.com/public/eef09aac2dcbfc71 experiment.com/users/eeditsizserverler pearltrees.com/editsizserverler wefunder.com/editsizserverler imageevent.com/editsizserverler ourclass.mn.co/members/23696284 friendtalk.mn.co/members/23696354 slides.com/editsizserverler roosterteeth.com/g/user/EditsizServerler/activity opencollective.com/editsiz-serverler pastelink.net/erd7vohi fairygodboss.com/users/profile/48WIpe-gxe/editsizserverler codingame.com/profile/e076eaf315403d3ed090624d8cdccc234708506 jigsawplanet.com/editsizserverler?viewas=3d85ff6a3ee9 jsfiddle.net/editsizserverler/x0sorwL5/6/ jsfiddle.net/editsizserverler/x0sorwL5/7/ jsfiddle.net/editsizserverler/x0sorwL5/8/ jsfiddle.net/editsizserverler/x0sorwL5/9/ jsfiddle.net/editsizserverler/x0sorwL5/10/ jsfiddle.net/editsizserverler/x0sorwL5/11/ jsfiddle.net/editsizserverler/x0sorwL5/12/ jsfiddle.net/editsizserverler/x0sorwL5/13/ jsfiddle.net/editsizserverler/x0sorwL5/14/ jsfiddle.net/editsizserverler/x0sorwL5/15/ jsfiddle.net/editsizserverler/x0sorwL5/16/ jsfiddle.net/editsizserverler/x0sorwL5/17/ jsfiddle.net/editsizserverler/x0sorwL5/18/ jsfiddle.net/editsizserverler/x0sorwL5/19/ jsfiddle.net/editsizserverler/x0sorwL5/20/ jsfiddle.net/editsizserverler/x0sorwL5/21/ jsfiddle.net/editsizserverler/x0sorwL5/22/ jsfiddle.net/editsizserverler/x0sorwL5/23/ jsfiddle.net/editsizserverler/x0sorwL5/24/ jsfiddle.net/editsizserverler/x0sorwL5/25/ jsfiddle.net/editsizserverler/x0sorwL5/26/ jsfiddle.net/editsizserverler/x0sorwL5/27/ jsfiddle.net/editsizserverler/x0sorwL5/28/ jsfiddle.net/editsizserverler/x0sorwL5/29/ jsfiddle.net/editsizserverler/x0sorwL5/30/ jsfiddle.net/editsizserverler/x0sorwL5/31/ jsfiddle.net/editsizserverler/x0sorwL5/32/ jsfiddle.net/editsizserverler/x0sorwL5/33/ jsfiddle.net/editsizserverler/x0sorwL5/34/ jsfiddle.net/editsizserverler/x0sorwL5/35/ jsfiddle.net/editsizserverler/x0sorwL5/36/ jsfiddle.net/editsizserverler/x0sorwL5/37/ jsfiddle.net/editsizserverler/x0sorwL5/38/ jsfiddle.net/editsizserverler/x0sorwL5/39/ jsfiddle.net/editsizserverler/x0sorwL5/40/ jsfiddle.net/editsizserverler/x0sorwL5/41/ jsfiddle.net/editsizserverler/x0sorwL5/42/ jsfiddle.net/editsizserverler/x0sorwL5/43/ jsfiddle.net/editsizserverler/x0sorwL5/44/ jsfiddle.net/editsizserverler/x0sorwL5/45/ jsfiddle.net/editsizserverler/x0sorwL5/46/ jsfiddle.net/editsizserverler/x0sorwL5/47/ jsfiddle.net/editsizserverler/x0sorwL5/48/ jsfiddle.net/editsizserverler/x0sorwL5/49/ jsfiddle.net/editsizserverler/x0sorwL5/50/ jsfiddle.net/editsizserverler/x0sorwL5/51/ jsfiddle.net/editsizserverler/x0sorwL5/52/ jsfiddle.net/editsizserverler/x0sorwL5/53/ jsfiddle.net/editsizserverler/x0sorwL5/54/ jsfiddle.net/editsizserverler/x0sorwL5/55/ jsfiddle.net/editsizserverler/x0sorwL5/56/ jsfiddle.net/editsizserverler/x0sorwL5/57/ jsfiddle.net/editsizserverler/x0sorwL5/58/ jsfiddle.net/editsizserverler/x0sorwL5/59/ jsfiddle.net/editsizserverler/x0sorwL5/60/ jsfiddle.net/editsizserverler/x0sorwL5/61/ jsfiddle.net/editsizserverler/x0sorwL5/62/ jsfiddle.net/editsizserverler/x0sorwL5/63/ jsfiddle.net/editsizserverler/x0sorwL5/64/ jsfiddle.net/editsizserverler/x0sorwL5/65/ jsfiddle.net/editsizserverler/x0sorwL5/66/ jsfiddle.net/editsizserverler/x0sorwL5/67/ jsfiddle.net/editsizserverler/x0sorwL5/68/ jsfiddle.net/editsizserverler/x0sorwL5/69/ jsfiddle.net/editsizserverler/x0sorwL5/70/ jsfiddle.net/editsizserverler/x0sorwL5/71/ jsfiddle.net/editsizserverler/x0sorwL5/72/ jsfiddle.net/editsizserverler/x0sorwL5/73/ jsfiddle.net/editsizserverler/x0sorwL5/74/ jsfiddle.net/editsizserverler/x0sorwL5/75/ jsfiddle.net/editsizserverler/x0sorwL5/76/ jsfiddle.net/editsizserverler/x0sorwL5/77/ jsfiddle.net/editsizserverler/x0sorwL5/78/ jsfiddle.net/editsizserverler/x0sorwL5/79/ jsfiddle.net/editsizserverler/x0sorwL5/80/ jsfiddle.net/editsizserverler/x0sorwL5/81/ jsfiddle.net/editsizserverler/x0sorwL5/82/ jsfiddle.net/editsizserverler/x0sorwL5/83/ jsfiddle.net/editsizserverler/x0sorwL5/84/ jsfiddle.net/editsizserverler/x0sorwL5/85/ jsfiddle.net/editsizserverler/x0sorwL5/86/ jsfiddle.net/editsizserverler/x0sorwL5/87/ jsfiddle.net/editsizserverler/x0sorwL5/88/ jsfiddle.net/editsizserverler/x0sorwL5/89/ jsfiddle.net/editsizserverler/x0sorwL5/90/ jsfiddle.net/editsizserverler/x0sorwL5/91/ jsfiddle.net/editsizserverler/x0sorwL5/92/ jsfiddle.net/editsizserverler/x0sorwL5/93/ jsfiddle.net/editsizserverler/x0sorwL5/94/ jsfiddle.net/editsizserverler/x0sorwL5/95/ jsfiddle.net/editsizserverler/x0sorwL5/96/ jsfiddle.net/editsizserverler/x0sorwL5/97/ jsfiddle.net/editsizserverler/x0sorwL5/98/ jsfiddle.net/editsizserverler/x0sorwL5/99/ jsfiddle.net/editsizserverler/x0sorwL5/100/ intensedebate.com/people/johnhenry2233 pxhere.com/en/photographer-me/4238660 longisland.com/profile/editsizserverler/ metin2-pvp-serverler.webflow.io/ anyflip.com/homepage/gwyra/preview pinshape.com/users/4109032-editsizserverlerorg allmyfaves.com/editsizserverler pexels.com/tr-tr/@editsiz-serverler-1225707393/ slideserve.com/editsizserverler archive.org/details/@editsizserverler divephotoguide.com/user/editsizserverler/ metal-archives.com/users/editsizserverler band.us/band/94702101 camp-fire.jp/profile/editsizserverler subscribe.ru/author/31420877 my.desktopnexus.com/blogamca/journal/metin2-pvp-serverler-49878/ replit.com/@editsizserverle fliphtml5.com/tr/homepage/pspuy/editsizserverlerorg/ free-ebooks.net/profile/1562629/editsiz-serverler qooh.me/editsizsrvl pubhtml5.com/homepage/exapj/ zzb.bz/Ib8s8 australian-school-holidays.mn.co/members/23780373 metin2pvpserverler.gallery.ru/ justpaste.it/eoa85 profile.hatena.ne.jp/editsizserverler/ indiegogo.com/individuals/37682987 taz.de/ list.ly/editsizserverlerorg/lists mypaper.pchome.com.tw/tomasvanek/post/1381781942 mypaper.pchome.com.tw/tomasvanek/post/1381781943 metin2pvpserverler.mystrikingly.com/ ted.com/profiles/46748800 play.eslgaming.com/player/20056929/ metin2pvpserverler.threadless.com/about knowyourmeme.com/users/editsiz-serverler active.popsugar.com/@editsizserverler/profile sitetanitimlari.seesaa.net/article/503120781.html sitetanitimlari.seesaa.net/article/502999078.html sitetanitimlari.seesaa.net/article/502585593.html sitetanitimlari.seesaa.net/article/502585551.html sitetanitimlari.seesaa.net/article/502585519.html sitetanitimlari.seesaa.net/article/502585492.html sitetanitimlari.seesaa.net/article/502585455.html sitetanitimlari.seesaa.net/article/498056830.html filmizle2018.blog.fc2.com/blog-entry-21.html filmizle2018.blog.fc2.com/blog-entry-26.html filmizle2018.blog.fc2.com/blog-entry-31.html ameblo.jp/sitetanitimlari/entry-12787859138.html connect.garmin.com/modern/profile/97fe48da-7177-4ae0-bf0e-34fbe1334538 reddit.com/user/uflee/ agario.buzzsprout.com/2066066/14949093-metin2 linkedin.com/posts/okeyoyna_metin2-ejderhalar-merhaba-metin2-oyununa-activity-7171861395326582784-UlrI/ linkedin.com/pulse/metin2-pvp-serverler-listeleri-okey-oyna-jyhpf/ blogger.com/profile/15166393869257970818 draft.blogger.com/profile/15166393869257970818 instagram.com/realokey/ blogger.com/profile/05227574979353865473 draft.blogger.com/profile/05227574979353865473 tumblr.com/onlineokey twitter.com/mt2org twitch.tv/okeyoynaa pinterest.com/a99io/ google.com/url?q=https://www.okeyoyna.com vimeo.com/846733433 wordpress.com/tr/forums/topic/metin2-pvp-tanirim-scpriti/ dailymotion.com/video/x8e47pq gravatar.com/realokey grepo.travelcarma.com/okeyoyna/okey-oyna beatstars.com/zaferozkel okeyoyunu.mystrikingly.com/ gamblingtherapy.org/user/okeyoyna public.tableau.com/app/profile/okey.oyna/vizzes okeyoyna.amebaownd.com/posts/53051499 wefunder.com/okey sovren.media/u/okeyoyna/ lazi.vn/user/okeyoyna gravatar.com/realokey soundcloud.com/okey-oyna okey-oyna.webflow.io/ guides.co/g/okey-oyna/372469 flickr.com/people/200607646@N08/ my.desktopnexus.com/realokey giantbomb.com/profile/okeyoyna/ giantbomb.com/profile/okeyoyna/blog/ encinitas.bubblelife.com/community/okey_oyna sites.bubblelife.com/users/okeyoynacom_a31336 fanart-central.net/user/okeyoyna/profile klse.i3investor.com/web/cube/blog/okeyoyna globalcatalog.com/okeyoyna.tr articlesjust4you.com/members/okeyoyna/ issuu.com/realokey audiomack.com/okeyoynacom/song/dj-okey-oyna-dii-kartal audiomack.com/okeyoynacom gitlab.nic.cz/okeyoyna ameblo.jp/okeyoyna/entry-12849563639.html ameblo.jp/okeyoyna/ profile.ameba.jp/ameba/okeyoyna nintendo-master.com/profil/okeyoyna band.us/band/94698085 pastelink.net/192agg8x pastelink.net/sxqkqqcx pastelink.net/do4ziud7 pastelink.net/9ebiqvd9 pastelink.net/urv9w3xn agario.buzzsprout.com/2066066/14949093-metin2 reverbnation.com/okeyoynacom disqus.com/by/efehanzkel/about/ hub.docker.com/u/okeyoyna tinhte.vn/members/okey-oyna.3017475/ openhumans.net/member/okeyoyna/ research.openhumans.org/member/okeyoyna/ openhumans.com/member/okeyoyna/ portfolium.com/okeyoyna anobii.com/en/0152c9fb8c9e13a07a/profile/activity gitlab.ifam.edu.br/okeyoyna peatix.com/group/16198815 peatix.com/user/21949084/view rapidapi.com/okeyoynacom/api/demo-project85460/details zillow.com/profile/okeyoynacom/ pinterest.com/a99io/ pinterest.ph/a99io/ pinterest.com/a99io/ pinterest.com.mx/a99io/ pinterest.it/a99io/ pinterest.fr/a99io/ pinterest.ca/a99io/ pinterest.jp/a99io/ pinterest.co.uk/a99io/ pinterest.de/a99io/ pinterest.es/a99io/ se.pinterest.com/a99io/ tr.pinterest.com/a99io/ ru.pinterest.com/a99io/ id.pinterest.com/a99io/ cs.pinterest.com/a99io/ es.pinterest.com/a99io/ pl.pinterest.com/a99io/ pt.pinterest.com/a99io/ br.pinterest.com/a99io/ co.pinterest.com/a99io/ nl.pinterest.com/a99io/ se.pinterest.com/a99io/ at.pinterest.com/a99io/ dk.pinterest.com/a99io/ in.pinterest.com/a99io/ ro.pinterest.com/a99io/ sk.pinterest.com/a99io/ fi.pinterest.com/a99io/ ar.pinterest.com/a99io/ freelance.habr.com/freelancers/okeyoyna 500px.com/p/okeyoyna?view=photos

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.