What’s new for UltEvents?

  • We’ve officially upgraded to UltEvents 3! What does this mean?
    • We can directly edit fields, private or public
    • Constructors have been fixed
    • New UI
  • UltEvent Holders now include an InvokeIf(bool condition)
  • Return value filtering has been changed and is not as strict anymore. You can now use any system object as a return value for any argument as well as anything your argument type inherits or is inherited by like normal. (For example, you can now plug in a Component for an argument that requires a Rigidbody)

With this update, Cam at SLZ added a whole bunch of new helpers directly to be used in UltEvents to allow things that were either extremely complicated and unperformant or just impossible.

New Components

Value Holders
  • Value holders allow us to store any value.
  • Value holders include:
    • Bool Holder
    • Float Holder
    • Int Holder
    • String Holder
    • Vector2 Holder
    • Vector3 Holder
    • Quaternion Holder
    • Object Holder (UnityEngine Object)
    • System Object Holder (Allows to store any value, not serialized)
List Holders
  • List holders allow us to interact with arrays and lists.
  • List holders can be set from and be converted to arrays.
  • List holders include a foreach event (Invoked with ForEachEvent()). The event includes two parameters, the current value and current index. The event will be triggered for every single item on the list.
  • They also include methods to directly set or get values using an index.

  • List holders include:
    • Bool List Holder
    • Float List Holder
    • Int List Holder
    • String List Holder
    • Vector2 List Holder
    • Vector3 List Holder
    • Quaternion List Holder
    • Object List Holder (UnityEngine Object)
    • System Object List Holder (Allows to store any value, not serialized). This holder also implements a special method to set from an Array.

Operators and Accessors

The classes mentioned above are two of the main three static helper classes added in this update. The third one will be covered on later on. See Reflectors.

Operators is a base UltEvents class, shipping with UltEvents 3. It was heavily modified to include things to help us modders. The class includes math operations between common types (including Unity ones such as Vector3) and equality checks.

Accessors, as the name suggests includes helper methods to access values we can’t normally access. For example, we are able to use get individual axes of off Vector2s, Vector3s and Quaternions. We can also get and set array elements as well as a few other useful methods (Go try them out!).

Reflectors

The methods in this class let us call any method on any object (even returned values!) - static or not, generic, using out parameters, or doing flips in the air while screaming hooray - as long as it isn’t malicious.

There are also methods to access fields on any object.

This update includes heavy security fixes, blocking dangerous types that could lead to exploits or enable malicious calls. One of those types happens to be System.Type.

How does this affect us?

Since System.Type is completely blocked, we cannot use Type.GetType() anymore. This method is crucial for higher-level UltEvents.

This is where we refer to the Reflectors class. It includes a safe implementation of GetType() that allows us to get non-blocked types. This is required for the method invoking mentioned above.

How do we call methods then?

We need to identify which method we want to invoke first. For this guide, we’ll use Rigidbody.AddForce(Vector3 force, ForceMode mode) as an example.

  1. Identify and get our parameters
    We need to identify the type of each parameter and get it. From now on, we’ll refer to the safe Reflectors.GetType(...) as GetType()

    The method we chose has two parameters:

    - UnityEngine.Vector3, UnityEngine.CoreModule
    - UnityEngine.ForceMode, UnityEngine.PhysicsModule
    

    We’ll simply do two GetType() calls. It should look like this:

  2. Setup our parameter array
    Reflectors has the following implementation:
    public static MethodInfo GetMethod(Type type, string methodName, Type[] parameterTypes)

    As you can see, we need to get an array populated with the parameter types we just got. We do this by getting the type of.. type! Confusing, but it’ll make sense in a moment.
    We have the following property: Reflectors.TypeOfType which returns typeof(Type)
    We’ll get it, and then call System.Array.CreateInstance(Type type, int length) with the type being TypeOfType, and the length being 2 (because we have two parameters).

  3. Filling the array
    We need to fill the array we just created with the types we got earlier. We have two ways to do this. We can either use the Accessors.SetArrayElement() or a SystemObjectListHolder (See New Components). For now we’ll use the former as it is inline and easy to use. Methods that have out parameters will be covered in a later section.

    We’ll create a SetArrayElement() call for each type, targeting the array we just created and setting the index according to the position of the parameter. Here’s our current setup:

  4. Getting the method
    Now we almost have everything ready to get the MethodInfo to later invoke it! We just need to get the declaring type of the method using GetType(). In our case, it is UnityEngine.Rigidbody, UnityEngine.PhysicsModule.
    Finally, we can call Reflectors.GetMethod(Type type, string methodName, Type[] parameterTypes) (For now we will not be dealing with generics, make sure to choose this specific overload). Link the type to the declaring type we just got and the parameterTypes to the array containing the parameter types. Also make sure to fill out the methodName. You now officially have the MethodInfo! Hard part’s over.

    It’s very important we cache our MethodInfos so that we are not burning performance every time we are trying to get it again, when instead we can just get it once, store it, and use it as many times as we’d like.

  5. Caching
    To cache our MethodInfo, we use a SystemObjectHolder (See New Components). You can either add it to the GameObject you’re working on right now or have separate objects so you don’t get confused later on.
    We just plug it into a new call, navigate to Base Types -> UltEvents.ValueHolder<object> and then choose HeldValue. All we have to do is set it to the MethodInfo we just got.

    Invoking the method also requires an array of object containing the actual values. Since we want to reduce allocation calls to the bare minimum, we’ll create the array now and cache it.
    We’ll go ahead and add another SystemObjectHolder, and then call GetType("System.Object, mscorlib") to get the type of system object.
    Then, we again use Array.CreateInstance() but this time using the object type. The length is the same as the previous array, and in our case it is 2.
    Exactly like before, navigate to Base Types -> UltEvents.ValueHolder<object>, choose HeldValue and set it to the newly created array. Make sure you choose the correct holder. Eventually, it should look like this:

  6. Invoking
    We can finally invoke our method. To do this, we create a separate UltEvent
    Now, get the HeldValue from the SystemObjectHolder containing the parameter array.
    We can again use Accessors.SetArrayElement() to populate the array with the values we want to pass into the method. Here’s what that looks like:

    You can either use returned values directly, or use Debug Mode to work with serialized values instead. Since Vector3s and Enums can be serialized, we’ll go with debug mode here.

    All we have to do now is get the stored MethodInfo from earlier, and then call Reflectors.Invoke()! Like we did before, we’ll choose the overload that does not contain methodGenerics. Plug in the returned parameter array and the returned MethodInfo.
    The target is well.. your target!


    (Debug Mode was used to set the target as a UnityEngine.Object. You wouldn’t normally use Reflectors.Invoke() on objects that exist in editor).

    That’s it! For static methods, you just leave the target as null.
    Invoking generic methods or methods that use out parameters will be covered later.

    The setup needs to be called once before invoking. You can use LifeCycleEvents, or add a check that calls the setup the first time you invoke the method.

We’ve updated UltUtils with an automatic method generator to make this process super quick and easy while making sure it is as performant as possible. Simply right click any UltEvent and then navigate to Ult Utils -> Generate Method (Wizard)

Using out parameters

An out parameter is essentially a by reference parameter similar to ref and in.

When using Reflectors.GetMethod(), you need to represent the parameter as a a by reference type. You can use Reflectors.MakeByRefType(Type type) to convert an actual ‘normal’ type to a by reference type.

After converting the type, you just populate the parameter type array exactly like you would normally but with the converted type instead.

Example with UnityEngine.Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo):

As for invoking, create the parameter array as you normally would, with a length equal to the total number of parameters, including out parameters.
Leave out entries null.
Simply firing invoke will populate it’s parameter entry which you can later get using Accessors.GetArrayElement(). That’s it!