6
votes

I'm adding a replaying feature to my game. Having this feature, I can capture user's input to the game and feed them back to Unity later on for replaying the game. But the design of VRStandardAssets.Utils.VRInput class prevents me to mimic user inputs.

This class does not provide any public method to make it possible to trigger its events (e.g. OnClick or OnDoubleClick events) programmatically. So I decided to create a derived class from it and write my own public methods to trigger the events. This strategy failed because VRInput's methods are private meaning that I cannot invoke them from a derived class.

It is recommended for this type of classes to provide a protected virtual void On[eventName](subclassOfEventArgs e) method to provide a way for a derived class to handle the event using an override but this class does not have it (why so restrictive?). I guess it's a poor design from Unity. This poor design also makes it hard to write unit/integration tests.

Am I missing something here? Can I still do something to trick other classes to think they are dealing with VRInput class while replaying the game?

2
Writing game apps is different than writing standard application. Many works flows are abandoned to make things easier or faster. They simply want you to subscribe to the events which is totally fine. The events are also public which means you can easily invoke them from your script. I don't think they want you to derive from VRInput because it create multiple instances of VRInput which is unnecessary since there is only one user input. Making it worth being derived from would have been fine if this was a UI control like a UI button event since there can be many of them in the scene. - Programmer
Can you please elaborate on "The events are also public which means you can easily invoke them from your script"? Events are commonly defined as public but that does not mean other classes can 'invoke' them. They can just subscribe to them. - Kamran Bigdely
Find VRInput GameObject: GameObject obj = GameObject.Find("VRInputObj"); . Now get the VRInput component from it: VRInput vrInput = obj.GetComponent<VRInput>(); then invoke any of its event: vrInput.OnUp != null) { vrInput.OnUp.Invoke(); } - Programmer
It's not made to me invoked manually. They didn't have that in mind. That API is just a basic minimum VR stuff required to just made a simple VR game in your scene without any plugin. If you want a complicated Input system, you should make your own or use a existing one like GoogleVr. - Programmer
No you don't have to. Give it the-same class name VRInput. Put it in the VRStandardAssets.Utils namespace then drop it in the location the old one is already at. That's it. you can even use this time to add the virtual method you've been talking about. - Programmer

2 Answers

2
votes

In fact you can trigger theses events (OnClick, OnDoubleClick or any other events) from another class and without using reflection using this clever hack (Inspired by this article):

C# is not really type safe so you can share the same memory location. First declare a class with two fields that share the same memory space:

[StructLayout(LayoutKind.Explicit)]
public class OverlapEvents
{
    [FieldOffset(0)]
    public VRInput Source;

    [FieldOffset(0)]
    public EventCapture Target;
}

Then you can declare a new class that will intercept and call the other event:

public class EventCapture
{
    public event Action OnClick;

    public void SimulateClick()
    {
        InvokeClicked();
    }

    // This method will call the event from VRInput!
    private void InvokeClicked()
    {
        var handler = OnClick;
        if (handler != null)
            handler();
    }
}

Then finally register it and call it:

public static void Main()
{
    input = GetComponent<VRInput>();

    // Overlap the event
    var o = new OverlapEvents { Source = input };

    // You can now call the event! (Note how Target should be null but is of type VRInput)
    o.Target.SimulateClick();
}

Here is a simple dotNetFiddle that show it working (at least outside of unity)

0
votes

It is recommended for this type of classes to provide a protected virtual void On[eventName](subclassOfEventArgs e) method to provide a way for a derived class to handle the event using an override but this class does not have it (why so restrictive?). I guess it's a poor design from Unity. This poor design also makes it hard to write unit/integration tests.

All code is good code. All code is also bad code. Depends on your evaluation criteria. Unity's developers probably didn't think about your use case. As another conflicting rule of thumb, software should also be as simple & rigid as possible, so anticipating subclassing without a known use case might be considered overengineering.

As for how you can work around this, see How do I raise an event via reflection in .NET/C#?