Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Interactable event via script #5013

Closed
1 task done
fcagnetta opened this issue Jun 21, 2019 · 8 comments
Closed
1 task done

Add Interactable event via script #5013

fcagnetta opened this issue Jun 21, 2019 · 8 comments
Assignees
Labels
Feature Request Feature request from the community MRTK Usability An issue that impacts usability of MRTK APIs, prefabs, scripts, or assets. UX Controls - Interactable

Comments

@fcagnetta
Copy link

fcagnetta commented Jun 21, 2019

Describe the problem

It would be useful to add events to interactables via script. Imagine cases in which we add the script at runtime.

  • Add OnClick from dynamically instantiated Interactable

Describe the solution you'd like

It would be great a simple way to do that (still I can't find out how). Ok, maybe not same as OnClick event that is completely exposed, but something easy to implement.

Describe alternatives you've considered

Use a global listener instead of interactable script

Additional context

Same for interactable receiver script, and so on.

@fcagnetta fcagnetta added the Feature Request Feature request from the community label Jun 21, 2019
@Troy-Ferrell
Copy link
Contributor

Troy-Ferrell commented Jun 22, 2019

@fcagnetta , hello! Can you elaborate on what you mean by "add events"? Do you mean just subscribe to the OnClick Unity event inside Interactable.cs and thus be able to assign actions at runtime when a user selects the Interactable? If so, this is possible.

var interactable = myGameObject.AddComponent<Interactable>();
interactable .OnClick.AddListener(YourFunction);

@fcagnetta
Copy link
Author

Hi Troy, sorry I try to explain better:

If you programmatically add Interactable component to an object, and then you want to add a listener for OnClick event and for OnFocus (on and off) event, and X other event you may need, how can you do that? OnClick event is automatically provided so you can just add a listener to it, but what about the others?

In the inspector you can just click add event and choose among a list of events, then you drag the script on which the method you want to call resides onto.

How to script this?
Thanks

@Troy-Ferrell
Copy link
Contributor

Ah I see now. I know in Unity inspector it says "Add event", but to be all on the same page, you mean add InteractableEvent objects dynamically. The "Add event" in the inspector I am pretty sure just represent InteractableEvent objects. The property we want is Interactable.Events and I believe if added correctly this could work. But the InteractableEvent creation isn't super obvious. For example, protected SetupEvent() in Interactable gets called on Awake so you would have to link in those pieces yourself?

@killerantz can you help with better guidance?

@fcagnetta
Copy link
Author

Meanwhile I would like to add a bit of code of a use case for the OnClick() event registration:

Interactable interactable = gameObject.AddComponent<Interactable>();
interactable.IsGlobal = true;
interactable.OnClick.AddListener(() => SomeGuy.Instance.HelloWorld());

I get a null exception when Interactable.cs runs SetupStates() (line 272)
StateManager = States.SetupLogic(); - States is null. Maybe a call to GetStates() is needed instead of using States as is.

@killerantz
Copy link
Member

A good way to do this is implement the IInteractableHandler interface and call the Interactable.AddHandler(IInteractableHandler handler) method. This will call two events methods and track all the state changes currently being set in Interactable.

        public virtual void OnStateChange(InteractableStates state, Interactable source)
        {
            print(source.HasFocus);
            // the state has changed, do something new
            /*
            bool hasDown = state.GetState(InteractableStates.InteractableStateEnum.Pressed).Value > 0;

            bool focused = state.GetState(InteractableStates.InteractableStateEnum.Focus).Value > 0;

            bool isDisabled = state.GetState(InteractableStates.InteractableStateEnum.Disabled).Value > 0;

            bool hasInteractive = state.GetState(InteractableStates.InteractableStateEnum.Interactive).Value > 0;

            bool hasObservation = state.GetState(InteractableStates.InteractableStateEnum.Observation).Value > 0;

            bool hasObservationTargeted = state.GetState(InteractableStates.InteractableStateEnum.ObservationTargeted).Value > 0;

            bool isTargeted = state.GetState(InteractableStates.InteractableStateEnum.Targeted).Value > 0;

            bool isToggled = state.GetState(InteractableStates.InteractableStateEnum.Toggled).Value > 0;

            bool isVisited = state.GetState(InteractableStates.InteractableStateEnum.Visited).Value > 0;

            bool isDefault = state.GetState(InteractableStates.InteractableStateEnum.Default).Value > 0;

            bool hasGesture = state.GetState(InteractableStates.InteractableStateEnum.Gesture).Value > 0;

            bool hasGestureMax = state.GetState(InteractableStates.InteractableStateEnum.GestureMax).Value > 0;

            bool hasCollistion = state.GetState(InteractableStates.InteractableStateEnum.Collision).Value > 0;

            bool hasCollistion = state.GetState(InteractableStates.InteractableStateEnum.VoiceCommand).Value > 0;

            bool hasCustom = state.GetState(InteractableStates.InteractableStateEnum.Custom).Value > 0;
            */
        }

        /// <inheritdoc />
        public virtual void OnVoiceCommand(InteractableStates state, Interactable source, string command, int index = 0, int length = 1)
        {
            // Voice Command Happened
        }

        /// <inheritdoc />
        public virtual void OnClick(InteractableStates state, Interactable source, IMixedRealityPointer pointer = null)
        {
            // Click Happened
        }

Another option is to have a component that monitors Interactable.HasFocus or Interactable.HasPress on update. Since we could be trying to tack any combination of states to detect the pattern we care about, we didn't set up all the state changes as events, but an OnStateChanged event may be helpful.

@fcagnetta
Copy link
Author

At the moment, I workarounded this with a script which implement a couple of interfaces, including IMixedRealityInputHandler and its "sisters", and programmatically attached to the gameobjects I need to interact with.

@wiwei wiwei added 0 - Backlog MRTK Usability An issue that impacts usability of MRTK APIs, prefabs, scripts, or assets. and removed Consider for Next Iteration labels Jul 24, 2019
@julenka julenka assigned Troy-Ferrell and unassigned killerantz Aug 21, 2019
@julenka julenka added the Urgency-Soon High priority issues to be worked on after Urgency-Now issues label Aug 28, 2019
@julenka julenka self-assigned this Aug 28, 2019
@julenka julenka removed the Urgency-Soon High priority issues to be worked on after Urgency-Now issues label Aug 30, 2019
@julenka
Copy link
Contributor

julenka commented Aug 30, 2019

Meanwhile I would like to add a bit of code of a use case for the OnClick() event registration:

Interactable interactable = gameObject.AddComponent<Interactable>();
interactable.IsGlobal = true;
interactable.OnClick.AddListener(() => SomeGuy.Instance.HelloWorld());

I get a null exception when Interactable.cs runs SetupStates() (line 272)
StateManager = States.SetupLogic(); - States is null. Maybe a call to GetStates() is needed instead of using States as is.

I've just verified that adding OnClick event handler to dynamically instantiated interactable actually works in the GA release, added a test for this. Yay!

julenka pushed a commit that referenced this issue Sep 4, 2019
## Overview
Most UI frameworks allow for event listeners to be added not only from a GUI editor, but also from code. MRTK currently makes it easy for event listeners on Interactable (except OnClick) to be configured in the editor, but configuring at run-time from script is not possible.

This PR does the following:
1. Fixes bugs preventing adding events at run-time
1. Adds utility scripts to Interactable  to simplify adding Interactable event listeners 
1. Adds tests to verify all event listeners available in interactable by default
1. Adds examples to documentation

### Demo: Old vs. new way to add event listeners
Here's the old way to listen to focus enter/exit events:

```csharp
public static void AddFocusEvents(Interactable interactable)
{
    var ie = new InteractableEvent();
    var fr = new InteractableOnFocusReceiver(ie.Event);
    ie.Receiver = fr;
    interactable.Events.Add(ie);
    ie.Event.AddListener(() => Debug.Log("Focus on"));
    ie.OnFocusOff.AddListener(() => Debug.Log("Focus off"));
}
```

And the new way to add focus enter/exit events:

```csharp
public static void AddFocusEvents(Interactable interactable)
{
    var onFocusReceiver = interactable.AddReceiver<InteractableOnFocusReceiver>();
    onFocusReceiver.OnFocusOn.AddListener(() => Debug.Log("Focus on"));
    onFocusReceiver.OnFocusOff.AddListener(() => Debug.Log("Focus off"));
}
```

- Fixes: #5013

## Verification
Verify that new tests pass, and that they did not pass before.
@julenka
Copy link
Contributor

julenka commented Sep 4, 2019

Fixed

@julenka julenka closed this as completed Sep 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature Request Feature request from the community MRTK Usability An issue that impacts usability of MRTK APIs, prefabs, scripts, or assets. UX Controls - Interactable
Projects
None yet
Development

No branches or pull requests

6 participants