Touch Input in 3D Environments

Hello Everybody,

This article is pulled from my website: www.IndieDevSpot.com and can be viewed here as well: https://indiedevspot.azurewebsites.net/2014/06/19/touch-input-moving-a-character-in-3d-environments-using-rays/

There are literally hundreds of millions of windows touch tablets out there running windows 8 these days, so it is almost mandatory these days to support touch input in some manner.  I am going to just provide some sample code below that has been commented.  Please note you can use these same techniques to select or unselect objects.  The sample below is a mouse and touch input that allows you to point and click, or touch a point in the 3D environment and the character will move to that point.

touchInput3D

 

If you ran through my Top Down tutorial series, some of this should look familiar, as we are still controlling in a 2D space really, just with 3D objects and letting gravity handle the y axis for us.

First, get the variables that you need.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //adjust the facing angle of this character to match world co-ordinates     public float facingAngleAdjustment = 90f;       //default maxSpeed     public float maxSpeed = 10.0f;       //our rigid body to cache     private Rigidbody cachedRigidBody = null;       //our animator     private Animator cachedAnim = null;       //destination to reach     private Vector3 touchPoint;       //which direction should we go?     private Vector3 heading;       //should we move somewhere?     private bool dontMove = true;

Then, in update, check for touches or Mouse input.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 /// <summary>     /// used for checking for information as fast as possible that     /// does not necessarily make impact on game state.  Just check     /// to see if we should, and then make those changes in fixed update.     /// </summary>     private void Update ()     {         //We are touch first, so check for that.         if (Input.touchCount > 0)         {             //Handle Point Movement, send the touch position on the screen             HandlePointMovement(Input.GetTouch(0).position);         }         else if(Input.GetButtonDown("Fire1"))//if no touch, fall back to see if a mouse click happened.         {             //Handle Point Movement, send the mouse position associated with this click.             HandlePointMovement(Input.mousePosition);         }       }

Ok, but how are we actually handling the point movement?  Glad you asked, here it is…

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 /// <summary>     /// Checks to see if there is a hit on a collider     /// If there is, will set the new heading and save     /// the destination point.  This will then allow     /// movement to happen in fixed update.     /// </summary>     /// <param name="position"></param>     private void HandlePointMovement(Vector3 position)     {         //the hit object, contains information about a hit.         RaycastHit hit;         //ray from where you touched or clicked projected in the orientation of the camera into the world         Ray collisionRay = Camera.main.ScreenPointToRay(position);         //returns a bool for if a hit happened or not, and resets hit (out) with correct information         //100 is the layer to check against.  In this sample we check against all layers,         //to optimize this check, we may want to check only for ground touches.         if (Physics.Raycast(collisionRay, out hit, 100))         {             //touch point is used to determine if the character has reached the destination             //therefor we set it to the destination             this.touchPoint = new Vector3(hit.point.x, hit.point.y, hit.point.z);             //vector mathematics, determine the direction the character needs to move.             this.heading = this.touchPoint - this.transform.position;             //don't move is the check for if the character should move or not.             //since we want the character to move now, we set this to false.             this.dontMove = false;         }     }

OK, so we have our variables, we have checked for input, we have done something with that input.
Now we actually make changes to our visual and physical properties.  We always do this in FixedUpdate()

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 /// <summary>     /// update used for ensuring networked machines are kept in sync as well as     /// for visual state updates.     /// </summary>     private void FixedUpdate()     {         //Distance formula for the x,z plane         //sqrt (a^2 + b^2) = c (Pythagorean Theorum)         //calc a^2         float aSquared = this.transform.position.x - this.touchPoint.x;         aSquared *= aSquared;           //calc b^2         float zSquared = this.transform.position.z - this.touchPoint.z;         zSquared *= zSquared;           //take sqrt for distance         float distance = Mathf.Sqrt(aSquared + zSquared);           //have we reached our threshold of our destination?         if(distance < .5f)         {             //stop moving.             this.dontMove = true;         }           //should we move?         if (!dontMove)         {             //Ok, move in the heading set by our handlePointMovement function.             this.Move(this.heading.x, this.heading.z);         }         else         {             //set heading to zero just in case             this.heading = Vector3.zero;             //remove all velocity from our character             //except in the y direction as we don't control that             //gravity does.             this.cachedRigidBody.velocity = new Vector3(0.0f, this.cachedRigidBody.velocity.y, 0.0f);             //zero out the animator, as we are no longer entering the move function             this.cachedAnim.SetFloat("Speed", 0.0f);         }     }

OK, so what does the move function look like?  This is almost exactly like our 2D top down game.  In fact, I’m not really sure what the difference is off hand at the moment, except maybe name changes.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /// <summary>     /// Move the character on the x,z plane as we use y for vertical in this game.     /// Colliders will take care of y and our character doesn't fly.     /// </summary>     /// <param name="xMove"></param>     /// <param name="zMove"></param>     public void Move(float xMove, float zMove)     {         //Where should we move?         Vector3 move = new Vector3(xMove, this.cachedRigidBody.velocity.y, zMove);                   //normalize this, so it has a magnitude of 1 such         //that we are accurately portraying or max speed         move.Normalize();           //multiply by max speed         //such that we don't move super slow.         this.cachedRigidBody.velocity = move * this.maxSpeed;           //calculate our total speed for the animator         float speed = Mathf.Abs(this.cachedRigidBody.velocity.x) + Mathf.Abs(this.cachedRigidBody.velocity.z);           //set that speed so the animation changes.         this.cachedAnim.SetFloat("Speed", speed);           //convert the vector into a radian angle,         //convert to degrees and then adjust for the         //object's starting orientation         float angle = Mathf.Atan2(this.cachedRigidBody.velocity.z * -1, this.cachedRigidBody.velocity.x) * Mathf.Rad2Deg + facingAngleAdjustment;           //don't rotate if we don't need to.         if (speed > 0.0f)         {             //rotate by angle around the z axis.             this.transform.rotation = Quaternion.AngleAxis(angle, new Vector3(0, 1, 0));         }     }

And just for reference, I’m pasting the whole class below for your reference and use :)
Please note that you will need a character with an animator that has a speed parameter and also a rigidbody attached to it for this to work.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 using UnityEngine; using System.Collections;   public class CharacterController : MonoBehaviour {       //adjust the facing angle of this character to match world co-ordinates     public float facingAngleAdjustment = 90f;       //default maxSpeed     public float maxSpeed = 10.0f;       //our rigid body to cache     private Rigidbody cachedRigidBody = null;       //our animator     private Animator cachedAnim = null;       //destination to reach     private Vector3 touchPoint;       //which direction should we go?     private Vector3 heading;       //should we move somewhere?     private bool dontMove = true;       /// <summary>     /// second initialization phase     /// </summary>     void Start () {       }       /// <summary>     /// first initialization phase     /// </summary>     private void Awake()     {         this.cachedRigidBody = this.GetComponent<Rigidbody>();         this.cachedAnim = this.GetComponent<Animator>();     }       /// <summary>     /// update used for ensuring networked machines are kept in sync as well as     /// for visual state updates.     /// </summary>     private void FixedUpdate()     {         //Distance formula for the x,z plane         //sqrt (a^2 + b^2) = c (Pythagorean Theorum)         //calc a^2         float aSquared = this.transform.position.x - this.touchPoint.x;         aSquared *= aSquared;           //calc b^2         float zSquared = this.transform.position.z - this.touchPoint.z;         zSquared *= zSquared;           //take sqrt for distance         float distance = Mathf.Sqrt(aSquared + zSquared);           //have we reached our threshold of our destination?         if(distance < .5f)         {             //stop moving.             this.dontMove = true;         }           //should we move?         if (!dontMove)         {             //Ok, move in the heading set by our handlePointMovement function.             this.Move(this.heading.x, this.heading.z);         }         else         {             //set heading to zero just in case             this.heading = Vector3.zero;             //remove all velocity from our character             //except in the y direction as we don't control that             //gravity does.             this.cachedRigidBody.velocity = new Vector3(0.0f, this.cachedRigidBody.velocity.y, 0.0f);             //zero out the animator, as we are no longer entering the move function             this.cachedAnim.SetFloat("Speed", 0.0f);         }     }       /// <summary>     /// Move the character on the x,z plane as we use y for vertical in this game.     /// Colliders will take care of y and our character doesn't fly.     /// </summary>     /// <param name="xMove"></param>     /// <param name="zMove"></param>     public void Move(float xMove, float zMove)     {         //Where should we move?         Vector3 move = new Vector3(xMove, this.cachedRigidBody.velocity.y, zMove);                   //normalize this, so it has a magnitude of 1 such         //that we are accurately portraying or max speed         move.Normalize();           //multiply by max speed         //such that we don't move super slow.         this.cachedRigidBody.velocity = move * this.maxSpeed;           //calculate our total speed for the animator         float speed = Mathf.Abs(this.cachedRigidBody.velocity.x) + Mathf.Abs(this.cachedRigidBody.velocity.z);           //set that speed so the animation changes.         this.cachedAnim.SetFloat("Speed", speed);           //convert the vector into a radian angle,         //convert to degrees and then adjust for the         //object's starting orientation         float angle = Mathf.Atan2(this.cachedRigidBody.velocity.z * -1, this.cachedRigidBody.velocity.x) * Mathf.Rad2Deg + facingAngleAdjustment;           //don't rotate if we don't need to.         if (speed > 0.0f)         {             //rotate by angle around the z axis.             this.transform.rotation = Quaternion.AngleAxis(angle, new Vector3(0, 1, 0));         }     }           /// <summary>     /// used for checking for information as fast as possible that     /// does not necessarily make impact on game state.  Just check     /// to see if we should, and then make those changes in fixed update.     /// </summary>     private void Update ()     {             //We are touch first, so check for that.         if (Input.touchCount > 0)         {             //Handle Point Movement, send the touch position on the screen             HandlePointMovement(Input.GetTouch(0).position);         }         else if(Input.GetButtonDown("Fire1"))//if no touch, fall back to see if a mouse click happened.         {             //Handle Point Movement, send the mouse position associated with this click.             HandlePointMovement(Input.mousePosition);         }       }       /// <summary>     /// Checks to see if there is a hit on a collider     /// If there is, will set the new heading and save     /// the destination point.  This will then allow     /// movement to happen in fixed update.     /// </summary>     /// <param name="position"></param>     private void HandlePointMovement(Vector3 position)     {         //the hit object, contains information about a hit.         RaycastHit hit;         //ray from where you touched or clicked projected in the orientation of the camera into the world         Ray collisionRay = Camera.main.ScreenPointToRay(position);         //returns a bool for if a hit happened or not, and resets hit (out) with correct information         //100 is the layer to check against.  In this sample we check against all layers,         //to optimize this check, we may want to check only for ground touches.         if (Physics.Raycast(collisionRay, out hit, 100))         {             //touch point is used to determine if the character has reached the destination             //therefor we set it to the destination             this.touchPoint = new Vector3(hit.point.x, hit.point.y, hit.point.z);             //vector mathematics, determine the direction the character needs to move.             this.heading = this.touchPoint - this.transform.position;             //don't move is the check for if the character should move or not.             //since we want the character to move now, we set this to false.             this.dontMove = false;         }     }   }