Part 4: Gaze Input

Christiane Snyder
8/8/2016

Now that we finished some basic aspects of the game, let’s incorporate the VR aspect of this tutorial.

The input of our version of tetris will come from where the player’s gaze hits the backdrop/field of play of the game. If it’s left of the currently active (falling) block, it will move left and the same logic applies for moving right. Since our game is meant for Cardboard, when the trigger button on the player’s mobile device, the active game piece will rotate.

To make the Backdrop interactive with gaze input, the “PuzzleSystem” class must inherit from the  IGvrGazeResponder interface. We will only be utilizing the OnGazeTrigger() function.

public class PuzzleSystem : MonoBehaviour, IGvrGazeResponder {

And add implementations of its functions:

   #region IGvrGazeResponder implementation

 

   /// Called when the user is looking on a GameObject with this script,

   /// as long as it is set to an appropriate layer (see GvrGaze).

   public void OnGazeEnter() {

   }

 

   /// Called when the user stops looking on the GameObject, after OnGazeEnter

   /// was already called.

   public void OnGazeExit() {

   }

 

   /// Called when the viewer's trigger is used, between OnGazeEnter and OnGazeExit.

   /// If the Cardboard viewer is triggered while looking at the backdrop, rotate the active/falling block

   public void OnGazeTrigger() {

   }


   #endregion

Now we’ll need to add some public functions inside of  Block.cs that can move the blocks on the board. PuzzleSystem will call these functions on the CurrentBlock.

public void MoveLeft(){

    transform.position = new Vector3 (Mathf.Round (transform.position.x - 1.0f), transform.position.y, 6.0f);

}

 

public void MoveRight(){

    transform.position = new Vector3 (Mathf.Round (transform.position.x + 1.0f), transform.position.y, 6.0f);

}

 

public void MoveFaster(){

    Movement = new Vector3 (0f, 0.2f, 0f);

}

 

public void Rotate(){

transform.rotation = transform.rotation * Quaternion.Euler (0f, 0f, 90f);

transform.position = new Vector3 (Mathf.Round (transform.position.x), transform.position.y, 6.0f);

}

We don’t want to continuously call these functions in  PuzzleSystem.cs when gaze input is left or right of the falling block because that will just cause it to shoot straight off the board never to be seen again, so we need to add the float values “NextTime” and “UpdateRate” to control when changes can be made to the CurrentBlock. There should also be a reference to the main camera called “Reticle”(not actually the Reticle) and a Plane to represent the backdrop in a few of the calculations that will run in the Update() function.


Make the following updates to PuzzleSystem.cs to begin working with our gaze input system:

        private GameObject Reticle;

   List<GameObject> Blocks;

   public GameObject LBlock, LBlock2, SquareBlock, LineBlock, SBlock1, SBlock2, TBlock;

   private Plane plane;

   private float NextTime, UpdateRate;

   private GameObject CurrentBlock;

   // Use this for initialization

   void Start () {

       Reticle = GameObject.Find("Main Camera");

       Blocks = new List<GameObject>();

       //Plane representing the backdrop for interpreting player gazing

       plane = new Plane(Vector3.forward, transform.position);

       UpdateRate = 0.35f;

       NextTime = Time.time + UpdateRate;

       CurrentBlock = null;

       //Start coroutine to continuously spawn new blocks

       StartCoroutine ("StartSpawning");


 

   }


 

   void Update () {

       if (CurrentBlock != null) {

           //Check player gaze location and process associated input

           //Get the point of the players gaze location by checking its hit point with the plane

           //        representing the backdrop.

           float distance;

           Ray ray = new Ray (Reticle.transform.position, Reticle.transform.forward);

           plane.Raycast (ray, out distance);

           Vector3 point = ray.GetPoint (distance);

           //Round the x value so that it will always align with the grid and clamp it so that it never goes off the grid

           //        or interesects with the bounding box

           float x = Mathf.Round (point.x);

           x = Mathf.Clamp (x, -2.0f, 12.0f);

 

           //Only move a block left or right if it's in the right time step

           if (Time.time > NextTime) {

               if (x < CurrentBlock.transform.position.x) {

                   CurrentBlock.GetComponent<Block> ().MoveLeft();

               } else if (x > CurrentBlock.transform.position.x) {

                   CurrentBlock.GetComponent<Block> ().MoveRight();

               }

 

               //If player is gazing at the bottom of the playing board, speed up the current block

               if (point.y > 0f && point.y <= 2f) {

                   CurrentBlock.GetComponent<Block> ().MoveFaster();

               }

               NextTime = Time.time + UpdateRate;

           }

       }

   }

 

   public void OnGazeTrigger() {

       if (CurrentBlock != null) {

           CurrentBlock.GetComponent<Block> ().Rotate ();

       }

   }

This code will work on a basic level, but you’ll notice some issues when it comes to calculating if the center of an object is left or right of the gaze location because the center of the blocks’ transform is simply the center of it’s child cube that was created first, not actually the center of the combination of its child GameObjects. To fix this, we’ll calculate a XCenter variable in Block.cs  and update it every time the x value of the block changes. (We won’t need a center for the other two coordinate values because when checking the gaze location, we only need to compare the X values.)

public float XCenter;

private bool Downward;

private Vector3 Movement;

 

void Start () {

   XCenter = 0f;

   foreach (Transform child in transform){

       XCenter += child.position.x;

   }

   XCenter = Mathf.Round(XCenter / transform.childCount);

   Movement = new Vector3 (0f, 0.1f, 0f);

   Downward = true;

}
 

public void MoveLeft(){

  transform.position = new Vector3 (Mathf.Round (transform.position.x - 1.0f), transform.position.y, 6.0f);

  UpdateCenter();

}

 

public void MoveRight(){

  transform.position = new Vector3 (Mathf.Round (transform.position.x + 1.0f), transform.position.y, 6.0f);

  UpdateCenter();

}

 

public void MoveFaster(){

   Movement = new Vector3 (0f, 0.2f, 0f);

}

 

public void Rotate(){

   transform.rotation = transform.rotation * Quaternion.Euler (0f, 0f, 90f);

   transform.position = new Vector3 (Mathf.Round (transform.position.x), transform.position.y, 6.0f);

   UpdateCenter();

}

 

void UpdateCenter(){

   XCenter = 0;

   foreach (Transform child in transform) {

       XCenter += child.position.x;

   }

   XCenter = Mathf.Round(XCenter / transform.childCount);

}

We’ll incorporate this improvement into PuzzleSystem.cs by changing lines 52 and 54 to what is shown below:

void Update () {

   if (CurrentBlock != null) {

      //Check player gaze location and process associated input

      //Get the point of the players gaze location by checking its hit point with the plane

      //        representing the backdrop.

      float distance;

      Ray ray = new Ray (Reticle.transform.position, Reticle.transform.forward);

      plane.Raycast (ray, out distance);

      Vector3 point = ray.GetPoint (distance);

      //Round the x value so that it will always align with the grid and clamp it so that it never goes off the grid

      //        or interesects with the bounding box

      float x = Mathf.Round (point.x);

      x = Mathf.Clamp (x, -2.0f, 12.0f);

 

      //Only move a block left or right if it's in the right time step

      if (Time.time > NextTime) {

         if (x < CurrentBlock.GetComponent<Block> ().XCenter) {

             CurrentBlock.GetComponent<Block> ().MoveLeft();

         } else if (x > CurrentBlock.GetComponent<Block>().XCenter) {

             CurrentBlock.GetComponent<Block> ().MoveRight();

         }

 

          //If player is gazing at the bottom of the playing board, speed up the current block

          if (point.y > 0f && point.y <= 2f) {

              CurrentBlock.GetComponent<Block> ().MoveFaster();

          }

              NextTime = Time.time + UpdateRate;

       }

    }

 }