I’m an independent gameplay programmer who ended up falling in love with writing editor tools!
Allow me to present my first tool, uFAction! - It’s a Unity extension/package that gives you a full set of serializable/inspectable delegates (Actions Funcs) that could be easily integrated and made visible in the inspector for editing.
Features and contents:
You can target any UnityEngine.Object with UnityDelegate (Actions and Funcs - all with generic versions, up to 3 generic args, want more? create a wrapper class for your data instead)
You can target any non UnityEngine.Object with SysObjDelegate (Actions and Funcs - with generic versions too)
-No more dealing with Rects! with the convenient and pleasant-to-use awesome GUIWrapper that makes it possible to use GUILayout-like methods in GUILayout-restricted areas (PropertyDrawer.OnGUI for example) - This is what I used to draw the delegate editors! It can draw Buttons, Labels, Blocks (Horizontal/Vertical), all kinds of fields, (IntField, ColorField, ObjectField, etc), Popups, Foldouts, Boxes, DragDropAreas, GetLastRect, ColorBlock, ChangedBlock, EnabledBlock and many more!
Price? a [old $10 KFC lunch meal] $5 potatoes sack
Features to implement in coming updates:
The ability to select nested source properties.
Better serialization solution so that you don’t have to subclass when creating generic delegates.
The ability to add assemblies from which to fetch extension methods.
Better separation between the editor/runtime stuff in the Settings ScriptableObject.
Any user-requested feature(s)
Readme/Guide:
Here’s a readme/guide pdf file - explains things in depth (how I serialized things, the classes hierarchy, usage, different types of delegates, differences between editors, the settings etc) (Reading it directly from the uploading site might not be pleasant, consider downloading it - Zoom in if the pics were small, the quality becomes better)
Support:
If you have any questions, suggestions, features requests, etc feel free to post them in this thread.
If you ever encounter a bug, you could post it here providing a way of replicating it. If you needed an urgent fix and can’t wait till the next update, I will look into it and fix it as fast as I can and send you a private message with the fix.
If I don’t respond to you within 5 to 10 minutes, it could mean that: 1- I hit the sleep tank. 2- The lovely Unity forums thread notification is working perfectly. 3- My grandpa has taken the laptop to watch the news and enjoy a football match afterwards, or a 2-hours+ long women’s volleyball match.
Hope to hear your feedback! - Thanks.
uFAction 1.1 Change log:
FIXES: - Fixed a bug when dragging a gameObject to a gameObject field. Sometimes you get a “Couldn’t re-bind method xxx to target xxx”. - Fixed a bug when trying to use the value assigned to an object field when you set your handlers’ arguments. - Fixed a bug in GUIWrapper’s ChangeBlock if you draw a control in the changed block.
NEW: - Added a boolean flag to determine whether or not to keep the selection window active (focused) after making a selection (selecting a target, method or adding a new game object) - DEVBUS (Developer Extremely-Vriendly BetterUndo System): A much better undo system implemented via the command pattern for which you have full control on ‘what’ is it to be done, and ‘how’ it is to be undone! (Pic1, Pic2, Pic3. See TestEditor2 for the demo in the pics) - Now you have a full EditorGUIFramework with a BetterEditor, BetterPropertyDrawer, GUIWrapper and GLWrapper. With this framework, you could write GUI-independent drawers that well works in both custom editors and property drawers. (See the DrawMates in ShowEmAll) - With GLWrapper you could now draw your delegate from your custom editors so you’re not just constrained to using ShowDelegate! (See TestEditor1 for the example in the pic) - Found a way to keep editor data out of runtime with BetterPrefs and IUniquelyIdentifiedObject. See.
MODS: - SimpleSelectionMemorizer is now called SelectionMemorizer and it could memorize all your selections in a single editor session. It does this now by the aid of BetterUndo. Press Ctrl+Shift± to go back, Ctrl+Shift+= to go forward. - Moved all the helpers/utils static classes to DLLs. The point is not to hide the code but to keep everything clean, compact, portable and easily plug-able. (There’s really no practical meaning to hiding code in a DLL when it comes to .NET if you know what I mean…) - Much better undo support: You can now add/remove/set at will! There are some parts that don’t support undo yet, mainly the args (setting an arg directly/from a source) - See this pic: green = supports undo. Red = doesn’t. - Removed all the editor data (foldouts) from most the runtime classes with the help of BetterPrefs and IUniquelyIdentifiedObject. Now they’re editor-only. - GUIControlOption is now called GUIOption. (There’s now GUIOption and GLOption, both inheriting LayoutOption)
uFAction 1.2 Change log:
NEW:
Added an invocation performance scene to see some invocation benchmarks
Huge boost in Editor performance and invoking delegates with editor arguments thanks to memoization and Fasterflect!
Added new methods to EventManager: DynamicRaise, RaiseToAllExcept, RaiseToOnly
This is impressive indeed! I’ve seen a lot of delegate packages on the asset store, but nothing like this! They were all lacking. The editors look nice, the class hierarchy looks simple and robust. At first I was wondering why the seperation SysObjDelegate vs UnityDelegate, but the readme made it clear. Ahh, Unity and its serialization system. Can’t wait for this to be online, consider me your first customer
hey @hadiai: thanks for your positive feedback! - You are right, Serialization is the reason why I had to go with two different approaches/delegate branches. I will keep an eye out for better serialization solutions as promised so that maybe we won’t have that separation.
This looks awesome! I’ve had issues with Unity and delegates serialization before and this seems like a better solution than others available in the store. Good work!
How’s the performance on that GUIWrapper compared to the built-in GUILayout?
How about the run-time performance of these delegates versus normal delegates? Just wondering how using these will affect the run-time performance of my game…
As for your query, all the delegates (except KickassDelegate) use C# delegates as their back-end. Calling myUnityAction.Invoke(); will internally call a C# delegate (in this case it’s an Action) - And of course, performance of C# delegates is excellent! Almost as fast as invoking a method directly.
However, if you set your delegate’s arguments selectively from the editor, I will have to use reflection to be able to invoke your delegate via the arguments you set because high chances are, they’re gonna be different. (Cause normally, when you invoke a delegate with an argument, that same argument will be passed to all its subscribers - this is not the case here) And for that I use MethodInfo.Invoke - I don’t use DynamicInvoke. I have mentioned all this in the readme file. I also provided a benchmark link. - (Invoking by MethodInfos pretty much sits at the middle of the chart “Reflected after binding”).
This is what is used by KickassDelegate, since it could accept any method with no return regardless of its parameters’ signature. So if you’re using KickassDelegate or a UnityDelegate (with custom args set from the editor) you might want to avoid invoking the delegate frequently in high-performance-sensitive areas, it all depends on your game, what’s/how many things are being updated, etc. You certainly don’t have to worry about single calls.
Regarding GUIWrapper, it uses GUI as its back-end. I have not made any explicit benchmarks between it and GUILayout. But from my usage of it, it’s pretty fast! - However, just remember to do a gui.HeightHasChanged(); whenever your GUI changes height so that I re-layout everything otherwise you’d experience a single frame of lag (the change of height usually happens when you fold/unfold a foldout)
Hope that answers you, if you got more questions, please feel free to ask.
As for your query, not sure what you mean by ‘handle’ - but I assume you mean does uFAction support the adding/removal of anonymous methods to delegates. The answer is, no. The reason is that, let’s say you did something like:
int x;
myDel += () => x++;
One might ask, where is the target object in this case? - Well, it’s actually generated by the compiler. It’s a wrapper that most the times contains the name “DisplayClass” - (usually <>__DisplayClass or something but we don’t care about the name really) - Inside this wrapper, you’ll see your x variable defined! Since this class is compiler generated, none of my delegates are able to point to it because: 1- It’s not a UnityEngine.Object so all my UnityDelegates are useless. 2- It is a System.Object so you might think that my SysObjDelegate could target it, but no. currently SysObjDelegates could target System.Object classes that have the [Serializable] tag on them which in this case they don’t. Even if I used an annotation-free serializer, most annotation-free serializer will require you to let them know beforehand what types you’re gonna be serializing, in this case you don’t know the type cause it’s compiler-generated.
If all of that was not clear to you, see this video.
I might find some kind of work-around, but again, it’s sketchy business. Most the times you could just put your code in a legit method and call it instead.
Can’t stress enough out how great this tool actually is. Built in a comprehensible but also expandable way, saves so much time and vexe is such a nice contact. A pleasure to work with the asset!
@BTStone thanks so much for your kind words! Glad you’re satisfied. First happy customer
As for things to update/add, I’m actually thinking of making the method bindings available to each delegate on a per-instance basis. Of course, static one remains. But if you so choose you could go with the instance bindings instead. I will also include my GLWrapper that I’m working on so that you could now finally easily draw your delegate from an editor script that way you’re not constrained to just [ShowDelegate]. Also, give the ability to specify your own assemblies to fetch extensions from, so not just the first-pass assembly.
How did you get to show extension methods? I have a class right now with a simple extension method however when I reflect it (see its methods) I don’t get to see the extension method I wrote…
@hadiai: Good question! The first thing to understand is that extension methods will resolve to static methods. They are not an actual part of your object.
So this:
public static class TransformExtensions
{
public static void MyExt(this Transform t)
{
// ....
}
}
myTransform.MyExt();
Will turn into this:
public static class TransformExtensions
{
public static void MyExt(Transform t)
{
// ....
}
}
TransformExtensions.MyExt(myTransform);
With that being said, how can we reflect and get the extension methods of a certain type?
First, we need an assembly reference to look inside its types. Our TransformExtensions class is static which means it’s implicitly sealed. It’s also not nested nor generic (must check for these too) Also, the compiler generates a [ExtensionAttribute] on your extension method. We can use all of this information when reflecting.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices; // needed for ExtensionAttribute
namespace Test
{
public static class TestExtensions
{
public static void MyStringExt(this string input) { }
public static void MyOtherStringExt(this string input) { }
// etc
}
class Program
{
// you can put GetExtensionMethods(...) here...
static void Main(string[] args)
{
foreach (var m in GetExtensionMethods(typeof(Program).Assembly, typeof(string), typeof(void), BindingFlags.Public))
{
Console.WriteLine(m.Name);
}
}
}
}
This outputs the names of the two string extension methods. However, this gives you ‘exact’ type-match. So, if you have an extension method for UnityEngine.Object say, and you wanted all the extension methods for Transform, this method wouldn’t cut it, some extra inputs are needed. I wanted to keep it simple but if you still want to know I will happily share.
Thanks for your reply. You accessed the first parameter from GetParameters without checking to see if the length is greater than zero, shouldn’t we check first?
@hadiai: There’s no need for that, cause reaching the last where, all the methods at this stage have [ExtensionAttribute] on them so it’s guaranteed that they’ll have at least one argument/parameter (the ‘this’)