Creating custom conditions

Updated on

August 19, 2022

In this tutorial, you will learn how to create custom conditions for VR Builder. It is intended to be read by Unity / C# developers. If you are new to coding, you can still follow the guide but many concepts might not be clear for you directly. We also included some links for further readings that might help you.

In addition to following this step-by-step guide, you can download the final condition for reference.

Introduction

Conditions define under which circumstances transitions will trigger. These conditions can validate a user's interaction (e.g. the touching of an object), a constellation in the scene (e.g. two objects being close to each other), or even an external event (e.g. time running up or a button press from the observer).

Technical foundational concepts

In VR Builder, all entities form a tree-like structure. For instance, steps are children of chapters, behaviors and transitions are children of steps, and conditions belong to transitions. Parent entities have full control over their children, notably their life cycles.

When a step starts activating, it activates its behaviors. After all behaviors are active, it activates transitions. Transitions, in turn, activate their conditions. The step checks until all conditions of any transitions complete. Then it starts deactivating, and deactivates the behaviors.

If no condition is set for a transition, then the transition will automatically trigger after the current step's behaviors are completed, and the consecutive step will be activated.

By adding conditions to transitions, the VR designer controls how a step can be completed and which path to take in case the step has different transitions.

Our example use case - creating an "Object Is Upside Down" condition

As the example for this tutorial, we will create an "Object Is Upside Down" condition.

Consider a VR training application about postal package handling. The trainee has to move a box marked with a "This way up!" label. A VR designer would make a step with two transitions. One would trigger when the box is in the target area, and the other one would check for the alignment of the box. If the trainee flips the box while carrying it, the latter condition will complete first and the trainee will have to try again. If the trainee handles the box carefully, they will bring it to the target area and complete this task successfully.

Since we already have a default condition that checks if an object is inside an area, we only need to create a condition that will detect if an object is misaligned, i.e. if it's upside down.

Let's get started!

Condition Data

We need to define the condition's data. It will contain the target object and a threshold for the inclination.

Data

We need a C# class file which will contain the data class.

Just like in the tutorial on creating custom behaviors, create a new UpsideDownConditionData.cs file in the Assets folder and paste the following code:

----------------------------------------
using System.Runtime.Serialization;
using VRBuilder.Core;
using VRBuilder.Core.Attributes;
using VRBuilder.Core.Conditions;
using VRBuilder.Core.SceneObjects;

[DataContract(IsReference = true)]
[DisplayName("Is Object Upside Down?")]
public class UpsideDownConditionData : IConditionData
{
    // A reference to the target object that we will check.
    [DataMember]
    public SceneObjectReference Target { get; set; }

    // We will check how far the target is from being upside down in degrees.
    // If the difference is lower than the threshold, we must complete the condition.
    [DataMember]
    public float Threshold { get; set; }

    public Metadata Metadata { get; set; }
    public string Name { get; set; }
    public bool IsCompleted { get; set; }

    public UpsideDownConditionData()
    {
        Target = new SceneObjectReference("");
        Threshold = 135f;
    }
}
----------------------------------------

There are two significant differences from the behavior's data.

We implement the IConditionData interface instead of IBehaviorData. This interface has an additional property called IsCompleted. The condition's process has to set this property to true, then the containing step will access it.

Also, we have introduced a constructor. You can initialize some of the properties here instead of doing it in the condition itself. This change is optional; you can continue initializing data in entities, if you prefer to do so.

The StageProcess

Steps check for conditions in the Active stage. You need to implement only the Active stage process. It will check if the target is upside down in the Update() method. Depending on the result, it will either set IsCompleted to true and stop iterating, or wait for the next frame.

We will explain fast-forwarding of conditions in detail in the next subsection.

Create a new UpsideDownConditionActiveProcess.cs file in the Assets folder and add the following:

----------------------------------------
using System.Collections;
using UnityEngine;
using VRBuilder.Core;

public class UpsideDownConditionActiveProcess : StageProcess<UpsideDownConditionData>
{
    public override void Start()
    {
    }

    public override IEnumerator Update()
    {
        // Get the difference between vector pointing down,
        // And the vector that comes out of the "roof" of the target.
        // Then compare it with the threshold from data.
        while (Vector3.Angle(Vector3.down, Data.Target.Value.GameObject.transform.up) > Data.Threshold)
        {
            //If the angle is more than the threshold, wait for the next frame.
            yield return null;
        }

        // If the angle is less or equal to threshold, mark the condition as complete.
        Data.IsCompleted = true;
    }

    public override void End()
    {
    }

    // Nothing to fast-forward.
    // We will explain it soon.
    public override void FastForward()
    {
    }

    // Declare the constructor. It calls the base method to bind the data object with the process.
    public UpsideDownConditionActiveProcess(UpsideDownConditionData data) : base(data)
    {
    }
}
----------------------------------------

Fast-forwarding and automatic completion

As we have mentioned, the VR Builder's fast-forwarding system does not skip anything. Instead, it imitates the natural execution in a single frame. It is straightforward with behaviors: they always execute in the same way. With conditions, it is more complex.

Consider a step with two transitions. During an actual application run in VR, a VR user would either complete one set of conditions, or the other one. Since all conditions were active, they all have to deactivate, but only some of them were completed. If we want to imitate a natural execution, we should do the same: simulate circumstances in which certain conditions would complete, but fast-forward and deactivate all conditions.

For example, if we have a step where a VR user has to put a ball either into a left or a right basket, we would fast-forward both conditions, but our code would move the ball only into one of the baskets.

The VR Builder always calls the FastForward() method. In the majority of cases, you will leave this method empty for conditions. This is normal. To complete a condition in an artificial way, you have to create an autocompleter. It has only one method in which you have to set the IsCompleted flag and simulate the circumstances of the completion. Its class must inherit from VRBuilder.Core.IAutocompleter<UpsideDownConditionData>.

Create a new UpsideDownConditionAutocompleter.cs file within the Assets folder and copy the following:

----------------------------------------
using UnityEngine;
using VRBuilder.Core;

public class UpsideDownConditionAutocompleter : Autocompleter<UpsideDownConditionData>
{
    public UpsideDownConditionAutocompleter(UpsideDownConditionData data) : base(data)
    {
    }

    public override void Complete()
    {
        // Turn the target upside down, as it would normally happen.
        Data.Target.Value.GameObject.transform.rotation = Quaternion.Euler(0, 0, 180f);
    }
}
----------------------------------------

Assembling the condition

The code of the condition is very similar to the code of the behavior.

----------------------------------------
using System.Runtime.Serialization;
using VRBuilder.Core.Conditions;
using VRBuilder.Core;

[DataContract(IsReference = true)]
public class UpsideDownCondition : Condition<UpsideDownConditionData>
{
    public override IStageProcess GetActiveProcess()
    {
        // Always return a new instance.
        return new UpsideDownConditionActiveProcess(Data);
    }

    protected override IAutocompleter GetAutocompleter()
    {
        // Always return a new instance.
        return new UpsideDownConditionAutocompleter(Data);
    }

    public UpsideDownCondition()
    {
        Data.Name = "Upside Down";
    }
}
----------------------------------------

Creating menu items

If you click on an "Add Behavior" or "Add Condition" button in the Step Inspector, it will display a list of available options. If you select one of them, it will add a new behavior or condition to the step.

We have created a fully functional condition in the previous chapter, but we still miss it in the list. "Add Behavior" and "Add Condition" buttons do not display behaviors or conditions directly. Instead, they display menu items. A menu item defines the label to display and the way how it creates a new entity for a step. VR designers would be able to use our condition only after we create a menu item for it.

A menu item is an instance of a class that inherits from MenuItem inside the VRBuilder.Editor.UI.StepInspector.Menu namespace. A menu item class is an Editor script, so you must create a file for it under the Editor subfolder of the Assets folder.

The MenuItem class is generic. Use ICondition as the generic parameter. Once the class compiles, the Step Inspector will find it on its own.

The base class declares one property and one method that you have to implement.

The Step Inspector uses the DisplayedName property as a label. This property returns a string. If you use forward slashes ("/") in it, the Step Inspector will split it into submenus.

When a user selects one of the items, the Step Inspector calls the GetNewItem() method and adds the result to the list of entities.

Call the new file UpsideDownConditionMenuItem.cs and insert the following:

----------------------------------------
using VRBuilder.Core.Conditions;
using VRBuilder.Editor.UI.StepInspector.Menu;

public class UpsideDownConditionMenuItem : MenuItem<ICondition>
{
    public override string DisplayedName => "Tutorial/Object Upside Down";

    public override ICondition GetNewItem()
    {
        return new UpsideDownCondition();
    }
}
----------------------------------------

If you have named your condition in a different way or placed it under a namespace, adjust the code accordingly.

Open the Unity Editor, let the changes compile, and then click the "Add Condition" button in the Step Inspector. You will see the menu item in the list.

You can create multiple menu items for a single behavior. If you modify the condition's data in the GetNewItem() method, you will effectively create different presets of it.

Adding a help button

Each behavior or condition of our base template has a help button in their header. The button is linked to a webpage. If you would like to add your own link, add a [HelpLink] attribute above the condition class.

----------------------------------------
[DataContract(IsReference = true)]
[HelpLink("https://www.mindport.co/vr-builder-tutorials/creating-custom-conditions")]
public class UpsideDownConditionData : IConditionData
{
    //Your Condition code as shown above
}
----------------------------------------

Next Steps

What custom behaviors and conditions are you planning to implement? Join our community to discuss with other VR Builder developers on how to do it and share your extensions! Maybe someone built something similar already that you can pick up on.

Helpful links

Download Vr Builder