How to fix Unity’s private VR/XR code so it becomes public

XR Toolkit / XR Interaction Toolkit is the 2019/2020 new 1st-party VR and AR framework from Unity that provides a unified code layer for all VR headsets. It’s not the first such cross-platform framework, but it benefits from being owned by the company that makes the game-engine, so that integration is tighter and implementation likely to be more stable.

But it’s not finished yet. Fortunately, Unity’s engineers decided to release a preview a year or so ahead of the public release, giving us lots of time to kick the tyres, add feature requests, find and report bugs for them to fix, etc. This is awesome! But sometimes … there are bugs that are particularly annoying to deal with and trivial to fix, but we have to fix them ourselves.

Private and Internal methods

Unity’s C# code for their engine has long been open source, enabling developers to more rapidly debug their own code, and to understand features where Unity’s docs may not be clear enough. However: in the initial preview for XRIT, the Unity team made a lot of methods “private” or “internal” (something they’ve said they plan to remove in future). We have the source, so you could search/replace and stick the word “public” everywhere, but for the class-default methods that are unlabelled you can’t do it with search/replace and have to do them manually, one by one.

Every time Unity releases a minor bugfix … you have to do this all over again. Every time you start a new project … you have to do this all over again. It’s a huge waste of time. And there’s an easier way…

Static class extensions to the rescue

C# has two great features here: “Reflection” and “Class Extensions”. You probably know Reflection already, it’s common to most/all OOP languages. But less common: you can also extend an existing class by adding methods to it without editing the class itself – you put the new methods in a new class file.

Two of the most widely used methods in XRIT – that are pretty much guaranteed not to change method signature, because they’re so core to how it works and are used heavily by the Unity classes – are SelectEnter and SelectExit. Here’s how we’d fix them without changing the Unity source…

Assuming you have some code in one of your classes where you want to call SelectEnter, like this:

// Unity's XRInteractionManager.cs class:
public void SelectExit(XRBaseInteractor interactor, XRBaseInteractable interactable)

// Your class:
public void MyClass ...
{
...
   public void DoVRStuff()
   {
    ...
    mgr.SelectEnter( interactor, interactable ); // won't compile: not public
    ...
   }
}

1. Create a new class to hold the Extension methods

The class has to be static, and can only contain static methods. That’s fine. I usually name these classes something that makes it obvious why I needed them – once I don’t need them any more (e.g. Unity updates XR to fix the method privacy), I can easily find them and delete them from my project.

public static class ExtendUnityInteractionManagerMakePublic
{
}

2. Add an Extension method for each

We need one method for each method we’re modifying:

public static void SelectEnter_public( this XRInteractionManager manager, XRBaseInteractor interactor, XRBaseInteractable interactable)
{
...
}
public static void SelectExit_public( this XRInteractionManager manager, XRBaseInteractor interactor, XRBaseInteractable interactable)
{
...
}

Note: there are two special features here:

  1. Each method has to be static even though we’ll be using them as if they were non-static (C# handles this behind the scenes)
  2. Each method has an extra parameter, with a special syntax: it’s preceded by the keyword “this”

3. Use C# Reflection to call the real method

One of the reasons I hate the “internal” keyword is that it’s purely optional anyway: it’s just extremely annoying and fiddly to have to workaround when it gets misused (which is most of the time). So we can Reflect it:

public static void SelectEnter_public( this XRInteractionManager manager, XRBaseInteractor interactor, XRBaseInteractable interactable)
{
  MethodInfo unity_SelectEnter = typeof(XRInteractionManager).GetMethod("SelectEnter",
 BindingFlags.NonPublic | BindingFlags.Instance );

  unity_SelectEnter.Invoke(manager, new object[] {interactor, interactable});
}

Depending on the method, you’ll need to modify the Reflection arguments – the example I’ve given here just aims for the widest, most common set of arguments that should find a private or internal method.

In particular: if the method you’re looking for has different overloads with more or fewer parameters, then you’ll need to change the “GetMethod” call and give C# the extra info it needs – google for tutorials on C# Reflection if you haven’t done this before.

4. Use the Extension methods seamlessly from our code

Using this method is now easy, going back to our original code, it becomes:

// Your class:
public void MyClass ...
{
...
   public void DoVRStuff()
   {
    ...
    mgr.SelectEnter_public( interactor, interactable ); // works!
    ...
   }
}

Additional Notes

The first thing to note: the Extension class can have any name you like (so long as there isn’t already a class with that name, obviously).

Secondly: the Extension methods can be named anything you like. I like to name them “[original method]_public” to make it extremely clear why I created the method.

Thirdly: for other common questions/answers/tips, please check out the Unofficial XR Input FAQ, where I’m collating everything I learn about XR from the Unity team and from other Unity developers.

Finally … if you’re building apps/games for VR in Unity, and have tech/features you need, I’d love to hear more about your project(s). I’ve been writing VR code in Unity since the first Oculus Development Kit came out, and in 2020 I’m publishing my VR-building toolset – but I’d love feedback on what features other people need and would want to use in their own games. Email me directly at: support @ snapandplug.com

Post Author: adam

Leave a Reply

Your email address will not be published. Required fields are marked *