Now that our industrial robot is animated, I thought it a good time to head back on over and finish more of the Holograms 101 tutorial.
The next section of the tutorial focuses on adding a gaze cursor to the application: you have a torus mesh that tracks against the geometry the user is looking at. The instructions were reasonably straightforward – even when retrofitting the approach for a different project – but there were a few “gotchas” that are worth documenting:
- The simplest way to get the cursor – and its dependent bits and pieces – across into your project is to export the asset from the tutorial project and import it into your own. Perhaps obvious, but there it is.
- For your own geometry you’re going to need to add “collider” objects for Unity’s physics engine to perform raycasts into the model. It’ll still do it, without the colliders, but they won’t hit anything. :-)
- For our robot model there are lots of meshes, so I added MeshCollider components to each one. This worked a treat, although you should probably use cubes/spheres where you can, as presumably these perform better (not that I saw a particular issue there, I’m just saying).
Here’s the gaze cursor on our robot. To make it more visible than the red one in the tutorial, I changed its colour to green. It’s cool to see how nicely (and responsively) it hugs even complex geometry in our model.
Once you have a gaze cursor in place, the next step is to add gesture support, so that the user can perform actions on objects selected by the gaze cursor.
In our case I decided to use the standard air-tap gesture to pause or restart (in reverse) the particular part’s animation. Which is actually really cool: you can basically control the robot, placing its components exactly where you want them in 3D space, but turning the various animations on and off.
Once again it was pretty easy to implement: I added an “isStopped” property to the Rotate script – bypassing the transformation if this is set – and then set this property from the “command”. I also added an “isFast” property with the expectation that I might use it to make things faster, at some point (although accessing the speed parameter directly is also clearly an option). Aside from these minor changes, the approach I used is identical to the one shown in the tutorial.
Here’s the modified Rotate.cs file:
using UnityEngine;
using System.Collections;
public class Rotate : MonoBehaviour
{
// Parameters provided by Unity that will vary per object
public float speed = 50f; // Speed of the rotation
public Vector3 axis = Vector3.up; // Axis of rotation
public float maxRot = 170f; // Minimum angle of rotation (to contstrain movement)
public float minRot = -170f; // Maximim angle of rotation (if == min then unconstrained)
public bool isFast = false; // Flag to allow speed-up on selection
public bool isStopped = false; // Flag to allow stopping
// Internal variable to track overall rotation (if constrained)
private float rot = 0f;
void Update()
{
if (isStopped)
return;
// Calculate the rotation amount as speed x time
// (may get reduced to a smaller amount if near the angle limits)
var locRot = speed * Time.deltaTime * (isFast ? 2f : 1f);
// If we're constraining movement (via min & max angles)...
if (minRot != maxRot)
{
// Then track the overall rotation
if (locRot + rot < minRot)
{
// Don't go below the minimum angle
locRot = minRot - rot;
}
else if (locRot + rot > maxRot)
{
// Don't go above the maximum angle
locRot = maxRot - rot;
}
rot += locRot;
// And reverse the direction if we're at a limit
if (rot <= minRot || rot >= maxRot)
{
speed = -speed;
}
}
// Perform the rotation itself
transform.Rotate(axis, locRot);
}
}
Here’s the new PartCommands.cs:
using UnityEngine;
public class PartCommands : MonoBehaviour
{
// Called by GazeGestureManager when the user performs a Select gesture
void OnSelect()
{
var rot = this.gameObject.GetComponentInParent<Rotate>();
if (rot)
{
if (rot.isStopped)
{
rot.speed = -rot.speed;
}
rot.isStopped = !rot.isStopped;
}
}
}
The only slight nuance was that I had to place the command on the geometry – while the Rotate script was on the container, to make sure it affects the various descendants – but that just meant I needed to call GetComponentInParent<Rotate>() rather than GetComponent<Rotate>().
My next step was to add a few voice commands – mainly to start and stop individual parts and the whole robot – but I didn’t manage to get these working, for now. While trying to do so, I did notice that the “select” voice command is there by default – implemented at a low level in the system, to make it equivalent to an air-tap – so I actually didn’t really need them, after all.
Here’s selection working on the robot, both via air-taps and the built-in “select” voice command:
The next section in the tutorial shows how to add spatial sound, but this is once again Windows 10-specific. Boo. So I’ll look again at an OS upgrade (or just skipping this, until I can). I do want to place the robot on the ground – and perhaps make sure it won’t collide with anything – so the spatial mapping section is certainly going to get a closer look.