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