Laser Lights and Movement Fights

Gerald McAlister | January 20, 2017 | Dev Diary


This week’s post will cover the other half of code from out GitHub repository we discussed last week, involving our teleportation mechanic! We’ve spent quite a bit of time debating how movement in virtual reality works, and want to discuss briefly some of the ideas that we can (for right now), as well as why we think teleportation is here to stay, at least for the time being. With that said, let’s go ahead and dive right in to our thoughts on locomotion in virtual reality for today!

The Current Generation

Virtual reality currently is in its infancy. While the industry as a whole has made great strides over the past couple of decades, locomotion in virtual reality still suffers from a lot of simulator sickness issues. This is not an easy problem to solve, as its core issue stems from the disconnect from what your brain interprets as reality, and what is actually happening to your body. For example, if you are moving in virtual reality but not in real life, your brain is aware of this, and begins to make your body sick in order to “protect” itself. This is very similar to how car sickness works, though mostly in reverse since you are actually moving in a car. Even in the real world, many people still have instances where motion sickness occurs and cannot be easily helped.

Because motion sickness occurs in the real world, we believe that trying to combat all forms of motion sickness is just not realistic. That said, there are still many things that we can do to help make our experiences more comfortable for everyone, not just a few. At the same time, we also want to ensure that we do not detract from the experience, and create as immersive of an experience as possible. Thus, we have explored several options for locomotion in virtual reality. We will talk about a few of these today, but some of our other methods we are not quite ready to reveal to the public, so we may follow up to these at a later date.

First-Person Shooter Movement

This is a traditional form of locomotion in games that we explored originally. While we are looking into using this in some of our titles, it is not our preferred form of locomotion because of how easily it causes simulator sickness in players. That said, we think it has some value for more adventurous players. The basic idea of this form of movement is that you move in the way you would any game currently (forward/backward and side to side just using a joystick), allowing for fast instances of gameplay. It’s a very easy implementation on our end, and allows us to adapt many types of games to virtual reality, but we think that this form of movement isn’t ideal because the vast majority of players get simulator sickness from it.

Physically Moving

Physically moving around is the next type of locomotion method that we want to discuss, as it’s one that we think is often overlooked. Most of the time, when we think about games, we think in very large scale instances, where a player will be traversing the entire world. This isn’t always the case however, and incredible experiences can be created in small spaces as well. Owlchemy Labs (who we are huge fans of!) has done an incredible job with this in Job Simulator, and we think that allowing players to just move in the space they have can still allow for some incredible games with tons of exploration into their areas, and is something that we are also looking into using in our games.

Teleportation

Teleportation is the final form of movement seen in many games, and is how we are currently looking to implement our “large world” games. While this method of locomotion may be less immersive, it has many substantial benefits to the ones mentioned above. The first and most obvious being that it allows for players to still explore a game world to its fullest. The less obvious being that it prevents simulator sickness much better than standard FPS controls. As a result, we see this being the main form of movement for some time, due to both its simplicity and its lack of simulator sickness. We want to share with you how our implementation of this works currently, as it requires a bit more work than the above methods, and we think that this is useful to know. Currently, this will focus mainly on a Vive implementation, but can of course be adapted to the Oculus Rift fairly easily too!

Our Teleportation Implementation

Continuing off of our last post, this time we want to go over the other half of code that was present in that project, which is our teleportation method. You can find this GitHub project here, and if you want to just dive in right away. In our approach, each hand is given a “laser” sight to determine where you will be teleporting to. It has a starting color, and a using color that indicates you are about to teleport. This is a neat effect in multiplayer, as it allows for strategizing about both where players could teleport to and where they will, since you can see the difference. However, only certain surfaces can be teleported to, which is again defined in Unity’s layering system for our implementation.

To start out, we need to once again create our initial project based on our previous tutorial, with an empty project. To make this easier, we recommend following that tutorial, and getting your basic project setup to where you can pick up and throw objects. We will then begin adding additional code to our Hand script from that project. Begin by adding the following section to the top of your Hand script:

    public float LaserThickness = 0.002f;
    public Color TeleportStartLaserColor = Color.blue;
    public Color TeleportLaserColor = Color.red;
    public float MaxTeleportDistance = 100.0f;
    private GameObject mLaser;

These are several global objects that we will need throughout our script, in order to create our laser object itself. The first is the actual laser thickness, allowing us to quickly adjust how thick the laser pointer becomes. The next two variables define the color of the laser when we shine it, and the color when we are actually getting ready to teleport. Next, we have the maximum distance the player can teleport, and then finally the laser object itself that will be shown to the player.

Continuing on, we will once again modify our Start() method from before. We will add the following lines of code to the method:

    mLaser = GameObject.CreatePrimitive(PrimitiveType.Cube);
    mLaser.transform.parent = this.transform;
    mLaser.transform.localScale = Vector3.zero;
    mLaser.transform.localPosition = Vector3.zero;
    mLaser.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Unlit/Color"));
    mLaser.GetComponent<MeshRenderer>().material.SetColor("_Color", TeleportStartLaserColor);
    mLaser.GetComponent<BoxCollider>().enabled = false;

Not too much was added on here, but let’s cover this all just to be sure. First up, we create a new cube primitive to represent our laser. Next, we set the parent transform of the laser to the hand’s transform. This ensures that the laser is always pointing in the direction of the hand. Next, we set the local scaling and local position to 0. The position is 0 because it needs to be centered at the hand, but the scaling is 0 to hide it from the player’s view, since we do not know where the controller’s positions are yet. Next, we need to create a material for the laser so we can change its color. The material will use an unlit color shader so that it is always visible (even in the dark), though you can change this if you decide to. We then set that material’s color to the laser’s starting color. Finally, we disable collisions for the laser pointer, as it shouldn’t be able to affect the environment.

Now we need to update our FixedUpdate() method again. This will require quite a bit of code, but stay with us here:

    if (mControllerDevice != null) {
        Ray raycast = new Ray(transform.position, transform.forward);
        RaycastHit hitInfo;
        bool hit = Physics.Raycast(raycast, out hitInfo, MaxTeleportDistance);
        float distance = hit ? hitInfo.distance : MaxTeleportDistance;
        if (mControllerDevice.GetTouch(SteamVR_Controller.ButtonMask.Touchpad)) {
            mLaser.transform.localScale = new Vector3(LaserThickness, LaserThickness, distance);
            mLaser.transform.localPosition = new Vector3(0f, 0f, distance / 2f);
        } else {
            mLaser.transform.localScale = new Vector3(0f, 0f, 0f);
            mLaser.transform.localPosition = new Vector3(0f, 0f, 0f);
        }
        if (mControllerDevice.GetPress(SteamVR_Controller.ButtonMask.Touchpad)) {
            if (hit && hitInfo.collider.gameObject.layer == LayerMask.NameToLayer("teleportable")) {
                mLaser.GetComponent().material.SetColor("_Color", TeleportLaserColor);
            } else {
                mLaser.GetComponent().material.SetColor("_Color", TeleportStartLaserColor);
            }
        }
        if (mControllerDevice.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad)) {
            if (hit && hitInfo.collider.gameObject.layer == LayerMask.NameToLayer("teleportable")) {
                float originalY = transform.parent.position.y;
                transform.parent.position = transform.parent.position + raycast.direction * distance;
                transform.parent.position = new Vector3(transform.parent.position.x, originalY, transform.parent.position.z);
            }
            mLaser.GetComponent().material.SetColor("_Color", TeleportStartLaserColor);
        }
    }

Okay, let’s start from the top: First off, we need to check that we have a controller device, so we simply do a check on whether the controller device is null. If it is not, then we need to create a new Ray to raycast with. We use the position of the hand and its forward direction to create the ray. Next, we create the RaycastHit variable, which stores where we actually saw our laser hit something. The purpose of all of this is to check whether our laser is hitting something (IE: The ground) and if so, stop shining the laser pointer so that it doesn’t shine through the object, and then determine if we can teleport onto that surface. To check if we actually got our hit, we use the Physics.Raycast method, supplying our raycast variable, outputting to our hitInfo variable, and setting the maximum distance we want to check with (Aka: The maximum distance the player can teleport). We then set what our final distance was based on whether we got a hit. At this point, we go ahead and see if the player is touching the Vive wand’s trackpad. If they are, we update the scaling to be our laser’s thickness, setting it to be as long as the distance that we got from our raycast previously. We then update the position to be properly offset for the distance (IE: The distance / 2). If the player is not touching the trackpad, we need to set the scaling and position to 0 again to hide the laser pointer.

After we’ve determined whether to show the laser, we then need to determine if the player is actually teleporting. If they are actually pressing the trackpad’s button (rather than just touching it), we check if the surface they are pointing to is on the teleportable layer. If so, we change the color to our teleport color, otherwise we keep it at the starting color. If they release the trackpad’s button, we check again that the surface they are pointing to is on the teleportable layer, and if so, begin teleporting the player. To do this, we need to store the current Y position of the player, and then move the player by however far away the laser pointer is (the raycast’s direction multiplied by the distance). We can then set the player’s Y position back to what it originally was, making sure that they do not get moved up. One thing to note about this method is that it does not work with ramps, however you can adjust this by not reseting to the original Y position. We found however that it is best to keep the player on the same Y plane, in order to further prevent simulator sickness. Once the player’s position has moved, we again set the laser’s color back to the starting color.

These are the final properties available for our Hand script.

At this point, everything is all set. You now need to create another layer in Unity called “teleportable” and place objects that you want the player to be able to teleport onto on this layer. Now, when you touch the trackpad on the Vive wands, you should see a blue laser appear by default. When you press the button, you should see it turn red when you point to surfaces you can teleport to, but blue if you can’t. When you release the button, you should end up teleporting to where you pointed. All of the public variables we created at the beginning can be adjusted in the Unity editor too, so you can change the colors and max distances there, as well as the laser’s thickness.

Conclusion

Overall, our teleport script is very simplistic, but works in a variety of situations. You can alter the material adapting it to a variety of game genres, and can of course eliminate the laser itself if you decide to as well. As mentioned above, our code is all available over on our GitHub page to use, so please check it out. Once again, we’d really like feedback on our developer diaries, so please like, share, and comment below! We know these first two developer diaries have been pretty packed, so expect our next few to be a bit lighter on their content to make up for it! We’ll see you next week!