Skip to content

Custom Logic

Henry de Jongh edited this page Mar 10, 2023 · 9 revisions

IReactive Interface

You can make any existing MonoBehaviour in your project reactive by implementing the IReactive interface:

public class Button : MonoBehaviour, IReactive
{
   ...
}

IReactive Implementation

The IReactive interface and system require a common implementation, you should copy this into your class:

#region Required IReactive Implementation

[SerializeField]
private ReactiveData _reactiveData;
public ReactiveData reactiveData => _reactiveData;
public ReactiveMetadata reactiveMetadata => _reactiveMeta;

private void OnEnable() => this.OnReactiveEnable();

private void OnDisable() => this.OnReactiveDisable();

#endregion Required IReactive Implementation

private static ReactiveMetadata _reactiveMeta = new ReactiveMetadata();

Only the last line should be modified to provide metadata.

IReactive Metadata

This contains a list of inputs, outputs, parameters and descriptions among other things.

This is primarily used to support the game developer within Unity Editor and is not a requirement for function.

The last line in the code above could for example be:

private static ReactiveMetadata _reactiveMeta = new ReactiveMetadata(
    new MetaInterface(MetaInterfaceType.Input, "Use", "When a player presses the button."),
    new MetaInterface(MetaInterfaceType.Output, "Pressed", "Invoked when the button has been pressed."),
    new MetaInterface(MetaInterfaceType.Output, "Reset", "Invoked when the button is reset after being pressed.")
);

This declares an input "Use" with a description. The player could for example press the E-Key and invoke the "Use" input on the button. Then the output "Pressed" will be invoked when the button is down and "Reset" will be invoked when the button is up. These inputs and outputs will appear in the Unity Editor to help make clever suggestions and prevent typos.

You will find more MetaInterface-constructor overloads to provide details for a parameter name, type and description.

For example:

new MetaInterface(MetaInterfaceType.Input, "Message", "Logs a message to the Unity Console.",
                  "message", MetaParameterType.String, "The message to be logged in the console.")

Handling Inputs

The IReactive interface requires a function to be implemented that handles inputs:

public void OnReactiveInput(ReactiveInput input)
{
    switch (input.name)
    {
        case "Use":
            
            ...
            
            break;
    }
}

Here you can write custom logic for the input name that was invoked. The input class contains useful details such as the activator, caller and parameter passed to the input.

Handling Outputs

The IReactive interface provides extension methods on your current MonoBehaviour. You can find them by prefixing the call with this.

this.OnReactiveOutput(input, "Pressed");

This invokes all user-configured output handlers on this component matching the specified output name, "Pressed" in this case.

If you wish to pass along a parameter you can do so with this variant of the function:

this.OnReactiveOutput(input, "Pressed", parameter);

Where parameter is an object that can be of various parameter types.

If the level designer writes a custom parameter in an output, that custom parameter takes priority over the parameter provided here. It can be considered a default value used when the level designer leaves the parameter field blank.

Creating a chain of events

By invoking an output upon receiving an input, you create a chain of events:

public void OnReactiveInput(ReactiveInput input)
{
    switch (input.name)
    {
        case "Use":
            this.OnReactiveOutput(input, "Pressed");
            break;
    }
}

In this case, invoking "Use" on the button causes an output "Pressed". An output handler for "Pressed" may then turn on a light by invoking an input "Enable" on the light. The light starts burning and breaks after 10 seconds causing a "Broken" output. An output handler for "Broken" may then play a sound by invoking an input on a sound component and so on.

Execution Delay

It is important to note that the invocation of inputs and outputs are not immediate. They will require at least one FixedUpdate-step in the game engine and may be delayed further (possibly several seconds if not minutes) by the user.

Example Class

Our example button class could look something like this in practice:

public class Button : MonoBehaviour, IReactive
{
    [Required IReactive Implementation] // the closed region.

    private static ReactiveMetadata _reactiveMeta = new ReactiveMetadata(
        new MetaInterface(MetaInterfaceType.Input, "Use", "When a player presses the button."),
        new MetaInterface(MetaInterfaceType.Output, "Pressed", "Invoked when the button has been pressed."),
        new MetaInterface(MetaInterfaceType.Output, "Reset", "Invoked when the button is reset after being pressed.")
    );
    
    private bool busy = false;

    public void OnReactiveInput(ReactiveInput input)
    {
        if (busy) return;

        if (input.name == "Use")
        {
            StartCoroutine(CoPressed(input));
        }
    }
    
    private IEnumerator CoPressed(ReactiveInput input)
    {
        busy = true;
        
        // animation button getting pressed.
        yield return new WaitForSeconds(0.1f);
        
        this.OnReactiveOutput(input, "Pressed");
        
        // animation button getting released.
        yield return new WaitForSeconds(0.1f);
        
        this.OnReactiveOutput(input, "Reset");
        
        busy = false;
    }
}

It's then easy to add outputs for "Pressed" and invoke things in your scene:

Showcasing Dynamic Lighting and Reactive Logic by switching some light sources

For an example on how to use a keyboard key to invoke the "Use" input see Use Key.

Clone this wiki locally