At Leap Motion, we’re making VR/AR development easier with Widgets: fundamental UI building blocks for Unity. This is the final instalment of our Planetarium series.

Hi, I’m Wilbur Yu! You might remember me from such webcasts as Let’s Play! Soon You Will Fly and Getting Started with VR. In this post, we’ll look at how we structured Widgets to be as accessible and comprehensive as possible. For the purposes of the blog, we’ll look at the Button Widget in particular, but all of the Widgets follow a similar pattern.

Before we get started, here are the key points to keep in mind:

  • Readability is essential. Write understandable, simple functions.
  • Create base classes that contain physics and important abstract functions. Only one class or gameObject should be responsible for physics.
  • Minimize the number of colliders required for physics interaction

Prefab Structure

All Widgets are created using Unity’s Prefab feature. Here’s the prefab structure for buttons as an example:

widgets1

Base Component: ButtonDemoToggle. The Widget parent contains no scripts and no components. This gameObject’s responsibility is to determine the world position and scale of the Widget.

Physics Component: Button. This gameObject contains a trigger collider used to determine if a hand is interacting with it and respond accordingly. When a hand is no longer interacting with it, the physics component will take over.

The physics component is designed such that all the important properties can be changed in the inspector. The script is only responsible for responding to the physical changes because it inherits from a base physics script that handles all the physical movements.

Graphics Component: OnGraphics, OffGraphics, MidGraphics, and BotGraphics. These components are optional. In this example, these components only contain scripts with graphical changes, linked by the Physics Component. This signals the Graphics Components when to change their states based on the Physics states.

Physics Structure

widgets2

This is an inspector view of the Button physics structure. From here, you can specify the spring constant, trigger distance, and cushion thickness (used for hysteresis). The script references the graphics components because it will call their functions based on the state in the physics component.

Here’s a snippet of how it’s being done currently in ButtonBase.cs:

    protected virtual void ApplyConstraints ()
    {
      Vector3 localPosition = transform.localPosition;
      localPosition.x = 0.0f;
      localPosition.y = 0.0f;
      localPosition.z = Mathf.Clamp (localPosition.z, min_distance_, max_distance_);
      transform.localPosition = localPosition;
    }

    protected void ApplySpring ()
    {
      rigidbody.AddRelativeForce(new Vector3(0.0f, 0.0f, -scaled_spring_ * (transform.localPosition.z)));
    }

    protected virtual void FixedUpdate ()
    {
      ApplySpring ();
      ApplyConstraints ();
    }

During each physics update (FixedUpdate), the spring is applied first, then constraint is applied afterwards to constrain its x-axis and y-axis movement. I chose to use our own formula for spring physics because Unity’s spring hinge doesn’t work well when the displacement of the object is less than 1 (as Unity distances equal meters). Since we’re always working with a space less than a meter from the camera, this became a problem, so we had to implement our own spring physics.

These base classes also have abstract functions, such as:

    protected virtual void Update()
    {
      CheckTrigger();
    }
    protected void CheckTrigger()
    {
      float spring_location = transform.localPosition.z;
      if (is_pressed_ == false)
      {
        if (spring_location > scaled_trigger_distance_)
        {
          is_pressed_ = true;
          ButtonPressed();
        }
      }
      else if (is_pressed_ == true)
      {
        if (spring_location < (scaled_trigger_distance_- scaled_cushion_thickness_))
        {
          is_pressed_ = false;
          ButtonReleased();
        }
      }
    }

    public abstract void ButtonReleased();
    public abstract void ButtonPressed();

ButtonToggleBase.cs (inherits ButtonBase):

    public override void ButtonReleased() { }
    public override void ButtonPressed()
    {
      if (toggle_state_ == false)
        ButtonTurnsOn();
      else
        ButtonTurnsOff();
      toggle_state_ = !toggle_state_;
    }
    public abstract void ButtonTurnsOn();
    public abstract void ButtonTurnsOff();

ButtonDemoToggle.cs (inherits ButtonToggleBase):

  public override void ButtonTurnsOn()
  {
    TurnsOnGraphics();
  }

  public override void ButtonTurnsOff()
  {
    TurnsOffGraphics();
  }

This example shows that the ButtonBase calls two abstract functions – ButtonPressed and ButtonReleased – when the button passes or retracts from a certain point. ButtonToggleBase overrides the previous two abstract functions, and whenever the button is pressed it also calls two other abstract functions: ButtonTurnsOn and ButtonTurnsOff. Finally, ButtonDemoToggle overrides the previous two abstract functions, and handles the graphics components during these events. As mentioned earlier, other Widgets follow a similar pattern.

Solving the Rigidbody Problem

The biggest problem we came across while using Widgets in Planetarium is that, when the Widgets are approaching astronomical speeds (e.g. flying around the Earth), the rigidbody inertia causes unexpected collisions. In turn, this causes unintentional event triggers. We decided that a major physics refactor with our own non-rigidbody implementation was necessary.

A sample code snippet would look something like:

LeapPhysicsBase:

    protected virtual void FixedUpdate()
    {
      switch (m_state)
      {
        case LeapPhysicsState.Interacting:
          ApplyInteraction();
          break;
        case LeapPhysicsState.Reflecting:
          ApplyPhysics();
          break;
        default:
          break;
      }
      ApplyConstraints();
    }
    protected abstract void ApplyPhysics();
    protected abstract void ApplyConstraints();
    private void ApplyInteraction()
    {
      transform.localPosition = transform.InverseTransformPoint(m_target.transform.position) - m_targetPivot + m_pivot;
    }

LeapPhysicsSpring (inherits from LeapPhysicsBase):

    protected override void ApplyPhysics()
    {
      float scale = transform.lossyScale.z;
      float localSpringConstant = springConstant * scale;

      m_springVelocity.z += -localSpringConstant * Time.deltaTime;
      transform.position += transform.TransformDirection(m_springVelocity) * Time.deltaTime;
    }

    protected override void ApplyConstraints()
    {
      transform.localPosition.Scale(new Vector3(0.0f, 0.0f, 1.0f));
    }

ButtonBase (inherits from LeapPhysicsSpring, but will no longer have ApplySpring, etc.)

—Wilbur Yu, Unity Engineering Lead

Thanks for following us on our journey through the stars with Planetarium! This demo release is just a taste of what we have in store for the future. Right now, the team is working on getting Planetarium’s source code ready for open license, and putting the finishing touches on the next major round of Widgets releases. We’re also working on adding new Widgets features, including easy-to-use graphics modification functions, event handling, and physics components.

Whether it’s gazing at the stars or soaring through space, we’d love to know what inspires you about VR. What kind of experience would you like to see (or build!) with Widgets?

Wilbur is a software engineer and master of all things Unity.

Flickr