Rotate Player 90 degrees about its Y axis relative to the mouse being dragged between two angles
Hello, I am new to Unity and scripting in general so I am trying to learn as much as I can about different ways to manipulate the way my character moves and rotates. I was playing a game called Crossy Road and I wanted to know how I would rotate my character similarly via C# script.
Basically I want to write a simple script to attach to my game object that rotates my character (up, down, left, right), about the y-axis, when the mouse is dragged between two angles relative to where the mouseDown occurred. I provided a picture that hopefully clarifies what I mean.

I have considered using RaycastHit, Physics.Raycast and SLerp but I am still very confused on how to properly incorporate them together. I am also confused about how to make boundary angles. Does unity go by degrees or is it possible to use radians?
I am sure if I could make my game object at least follow the direction my mouse drag rotates, relative to the click, I could easily use an if statement to clamp the character to rotating 90 degrees once the drag passes a certain amount of degrees. The problem is I am unsure how to clearly obtain the values I need.
I have looked at many different examples and tutorials but nothing I have seen quite captures what I am looking for. I am doing my best to get better at scripting but this seriously has me stumped so of course Any feedback is greatly appreciated!
Answer by Statement · Oct 10, 2015 at 10:44 PM
You can use Mathf.Atan2 to get an angle from a direction vector - that would be the red line or X in your graphic. You can get the direction vector by subtracting the mouse position from the position where it was first clicked.
Mathf.Atan2 return a value between -PI and PI, so to get it to map to your graphic, a conversation has to be applied. From there it is just a matter of mapping the angle to predefined angles, something that can be called quantizing the value.
When you have your quantized angle, you can then build a quaternion from Quaternion.Euler for use with transforms. Quaternion.RotateTowards can be used to give a smooth transition from one rotation to another.
Today is my being bored day, so I wrote you a complete example and built a demo.
CrossyRoadInput.cs
 using UnityEngine;
 using UnityEngine.UI;
 
 public class CrossyRoadInput : MonoBehaviour
 {
     public float rotationSpeed = 360f;
     public float deadzone = 27f;
     public bool centerUnderMouse = true;
 
     public Transform dial;
     public Text indexText;
     public Text radianText;
     public Text degreeText;
 
     private Vector3 mouseCenter;
     private Vector2 mouseDrag;
     private Quaternion rotation;
 
     void Update()
     {
         if (Input.GetMouseButtonDown(0))
             CenterUnderMouse();
 
         if (Input.GetMouseButton(0))
             CalculateDragAndRotation();
 
         RotateDial();
         UpdateText();
     }
 
     void CalculateDragAndRotation()
     {
         mouseDrag = Input.mousePosition - mouseCenter;
         if (mouseDrag.magnitude > deadzone)
             rotation = CrossyRoadMath.QuantizedQuaternionZ(mouseDrag);
     }
 
     void CenterUnderMouse()
     {
         mouseCenter = Input.mousePosition;
 
         if (!centerUnderMouse)
             return;
 
         // Move the UI under mouse...
         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
         Plane plane = new Plane(-Vector3.forward, Vector3.zero);
         float rayDistance = 0f;
         if (plane.Raycast(ray, out rayDistance))
             transform.position = ray.GetPoint(rayDistance);
     }
 
     void RotateDial()
     {
         dial.rotation = Quaternion.RotateTowards(dial.rotation, rotation, 
                                                                 rotationSpeed * Time.deltaTime);
     }
 
     void UpdateText()
     {
         if (indexText)
             indexText.text = "Index: " + 
                 CrossyRoadMath.QuantizedIndex(mouseDrag);
         if (radianText)
             radianText.text = "Radians: " + 
                 CrossyRoadMath.QuantizedRadians(mouseDrag).ToString("F2");
         if (degreeText)
             degreeText.text = "Degrees: " + 
                 CrossyRoadMath.QuantizedDegrees(mouseDrag).ToString("F2");
     }
 }
CrossyRoadMath.cs
 using UnityEngine;
 
 public static class CrossyRoadMath
 {
     public static Quaternion QuantizedQuaternionX(Vector2 delta)
     {
         return Quaternion.Euler(QuantizedDegrees(delta), 0, 0);
     }
 
     public static Quaternion QuantizedQuaternionY(Vector2 delta)
     {
         return Quaternion.Euler(0, QuantizedDegrees(delta), 0);
     }
 
     public static Quaternion QuantizedQuaternionZ(Vector2 delta)
     {
         return Quaternion.Euler(0, 0, QuantizedDegrees(delta));
     }
 
     public static float QuantizedDegrees(Vector2 delta)
     {
         return QuantizedRadians(delta) * Mathf.Rad2Deg;
     }
 
     public static float QuantizedRadians(Vector2 delta)
     {
         return Quantize(RadiansFrom(delta));
     }
 
     public static float Quantize(float radians)
     {
         if (radians < Radians.Degree45) return Radians.Right;
         else if (radians < Radians.Degree135) return Radians.Up;
         else if (radians < Radians.Degree225) return Radians.Left;
         else if (radians < Radians.Degree315) return Radians.Down;
         else return Radians.Right;
     }
 
     // Utility if you need to get 0, 1, 2, or 3 for rotation index mapping.
     // Specialized helper that I don't know if you'll need.
     public static int QuantizedIndex(Vector2 delta)
     {
         return QuantizedIndex(RadiansFrom(delta));
     }
 
     // Utility if you need to get 0, 1, 2, or 3 for rotation index mapping.
     // Specialized helper that I don't know if you'll need.
     public static int QuantizedIndex(float radians)
     {
         if (radians < Radians.Degree45) return 0;
         else if (radians < Radians.Degree135) return 1;
         else if (radians < Radians.Degree225) return 2;
         else if (radians < Radians.Degree315) return 3;
         else return 0;
     }
 
     public static float RadiansFrom(Vector2 delta)
     {
         float radians = Mathf.Atan2(delta.y, delta.x);
 
         // At > 3.14 rad, Mathf.Atan2 returns -3.14 rad which shrinks 
         // back to -0 rad toward 360 deg. To make it easier to think 
         // in radians, I am converting that range [-PI, 0] so the output 
         // will be in the range [0, 2PI].
 
         if (radians < 0)
             radians = Mathf.PI * 2 + radians;
 
         return radians;
     }
 }
Radians.cs
 using UnityEngine;
 
 public static class Radians
 {
     public const float Right = 0;
     public const float Up = Mathf.PI / 2;
     public const float Left = Mathf.PI;
     public const float Down = 3 * Mathf.PI / 2;
 
     public const float Degree45 = 1 * Mathf.PI / 4f;
     public const float Degree135 = 3 * Mathf.PI / 4f;
     public const float Degree225 = 5 * Mathf.PI / 4f;
     public const float Degree315 = 7 * Mathf.PI / 4f;
 }
And for sake of completeness, I provide an alternative, slimmed down version of the script with less UI and functions to get to the core usage.
CrossyRoadInputSlimmed.cs
 using UnityEngine;
 
 public class CrossyRoadInputSlimmed : MonoBehaviour
 {
     public float rotationSpeed = 360f;
     public float deadzone = 27f;
     public Transform dial;
     private Vector3 mouseCenter;
     private Quaternion rotation;
 
     void Update()
     {
         if (Input.GetMouseButtonDown(0))
         {
             mouseCenter = Input.mousePosition;
             PositionSelfUnderMouse();
         }
 
         if (Input.GetMouseButton(0))
         {
             Vector2 mouseDrag = Input.mousePosition - mouseCenter;
             if (mouseDrag.magnitude > deadzone)
                 rotation = CrossyRoadMath.QuantizedQuaternionZ(mouseDrag);
         }
 
         dial.rotation = Quaternion.RotateTowards(dial.rotation, rotation,
                                                                 rotationSpeed * Time.deltaTime);
     }
 
     void PositionSelfUnderMouse()
     {
         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
         Plane plane = new Plane(-Vector3.forward, Vector3.zero);
         float rayDistance = 0f;
         if (plane.Raycast(ray, out rayDistance))
             transform.position = ray.GetPoint(rayDistance);
     }
 }
Oh I see this makes so much sense now! So the main idea is that you can get the Angle from the arc-tangent of the Input.mousePosition x and y components by taking the relative distance/location of the initial click to where the current drag is on screen. Then lock the rotation of the player to up,down,left,right if the angle falls within a specific region.
I was getting so confused on how I should start and how to properly use certain functions from the scripting API. I really like the idea of having many separate scripts working cleanly together like the way you pieced yours together. That made your answer very clear to understand. I am definitely going to play around with this a lot more and see if I can match it to my gameobject and even add a hopping ability to it!
This was incredibly helpful! Thank you so much @statement I really appreciate your insight.
Your answer
 
 
             Follow this Question
Related Questions
How can i run a script with a single key press? 1 Answer
My player character is jumping infinitely and it didn't use to do that... 0 Answers
Can't get my ball to move. 0 Answers
Interchangeable animations in a script? 0 Answers
Should I inherit from Monobehaviour if the use of my C# script is only to hold script references? 2 Answers
 koobas.hobune.stream
koobas.hobune.stream 
                       
                
                       
			     
			 
                