Hello!
I’m creating a 2D topdown shooter where I want to rotate the player character to where the player touches the screen. Later on I want the player to shoot after he’s rotated fully, so I thought that maybe a corountine would work here?
if (Input.GetTouch(0).phase == TouchPhase.Began)
{
Vector3 upAxis = new Vector3(0, 0, -1);
Vector3 mouseScreenPosition = Input.GetTouch(0).position;
//set mouses z to your targets
mouseScreenPosition.z = transform.position.z;
Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
transform.LookAt(mouseWorldSpace, upAxis);
}
While this works, it (obviously) snaps directly to the point. I want to make it smoothly rotate (slerp/lerp?).
Could anyone help me out or give me some tips?
First parameter is your start rotation, second is your desired rotation, 3rd is the rotation speed (and rotationSpeed is just a float… experiment with that number to get the desired results, but it will be quite low).
It probably because you are slerping a fixed amount from a smaller and smaller range.
Slerp is like a lerp but for rotations/quaternions.
You could just remember your start and end rotations. Then increment the 3rd parameter between 0 - 1 with the speed and time.deltatime.
The gif problem looks like because you are calculating your look at wrong. Cstooch example is just using the world position which is from world 0. You want the direction from your character to the mouse position.
Or go back to the transform.lookat you had at first.
Vector3 upAxis = new Vector3(0, 0, -1);
if (Input.GetMouseButtonDown(0))
{
Vector3 mouseScreenPosition = Input.mousePosition;
//set mouses z to your targets
mouseScreenPosition.z = transform.position.z;
Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
LookTarget = mouseWorldSpace;
// transform.LookAt(mouseWorldSpace, upAxis);
}
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(LookTarget, upAxis), Time.deltaTime * 4.0f);
This was the code that produced the result from the gif. The LookTarget is a vector that’s stored. @BlackPete Could you come with an example in this case using the RotateTowards, since I need to convert the mouse position to a quaternion it looks.
Sorry, as noted above, I think the problem is how I suggested the 2nd parameter of Slerp… I was taking a guess there, as I’m not sure how you convert the screen point to an actual rotation (there’s tuts out there like this 2D Mouse Aiming - Questions & Answers - Unity Discussions on how to do that properly though, of course). When I used slerp method, it worked quite well, but I was using joystick input, and I had already figured out my desired look rotation Quaternion, so I just simply dropped that in as 2nd param.
This works almost perfect, except for when I move around the calculations gets wierd. I thought that the endRot did check for this?
I modified it slightly to test with the mouse, setting Vector3 mouseScreenPosition = Input.mousePosition, instead of the touch position. Does that matter?
Alright thank you guys all so much! There is one little issue left, I think it has to do with normalization.
What’s coursing the rotation.y to behave like this?
GIF: Screen recording 2017-07-10...
Vector3 upAxis = new Vector3(0, 0, -1);
if (Input.GetMouseButtonDown(0))
{
Vector3 mouseScreenPosition = Input.mousePosition;
//set mouses z to your targets
mouseScreenPosition.z = transform.position.z;
Vector3 mouseWorldSpace = Camera.main.ScreenToWorldPoint(mouseScreenPosition);
StartCoroutine(SmoothLookAt(mouseWorldSpace, upAxis, 0.5f)); //
}
SmoothLookAt(StarManta)
IEnumerator SmoothLookAt(Vector3 worldPoint, Vector3 upAxis, float duration)
{
Quaternion startRot = transform.rotation;
Quaternion endRot = Quaternion.LookRotation(worldPoint - transform.position, upAxis);
for (float t = 0f; t < duration; t += Time.deltaTime)
{
transform.rotation = Quaternion.Slerp(startRot, endRot, t / duration);
yield return null;
}
transform.rotation = endRot;
}
I have a suspicion that the axis are wrong, or that the world points aren’t coplanar to the player, hence the sprite flipping. you want the sprite to only spin on one axis so you’ll need to ensure that the world points that are fed into look rotation are coplanar (with a 0 on the z component for 2D, y component for 3D).
can you show another gif but with the sprite selected so that we can see the axes? or with 4 Debug.DrawRays (3 to show each sprite’s axis in update, 1 to show the target look direction while rotating)
EDIT:
Also you should be aware of the behavior behind your smooth rotation code. By using Slerp and duration the rotation speed will be linear, but not consistent. so if you have the duration set to 2 seconds, then it will always take him 2 seconds to fully rotate whether thats 2 degrees or 180 degrees. RotateTowards will always have a linear speed but also consistent.
plus, please set your StartCoroutine to a Coroutine variable and use StopCoroutine when starting a new one. the way you have it now if you click multiple times in opposite sides of the screen the rotations will “freak out” because you’ll have multiple coroutines fighting each other to rotate the character.
Edit: And also look how the y is getting modifed differently the further away from the player I press
Edit: I’ve tried that @BlackPete but that only reversed the whole thing, so he looks in the 180 opposite direction of where I’m clicking. The flip in the start still appears.
For what it’s worth, you could use transform.up (assuming up is along the z axis, otherwise use transform.forward or negative forward) instead of hardcoding a Vector3(0,0,-1).
Noted about the linear, thank you. I will stop the coroutine when I start it. Did you see I’ve posted the new gif? I don’t quite know why it does that.
You’ll want to use Vector.forward as the up axis cause you want to spin around the world z axis i.e. the Vector.foward.
and cache the lookDirection vector and clear the z value from it before passing it to Quaternion.LookRotation. You’ll also need a rotation offset cause LookRotation tries to point the sprites z-axis at the look direction (which you want looking at the camera), while pointing the y axis to where you’ve defined the upaxis to be (LookRotation will make it point to the camera, when you’ll want it pointing in the opposite direction that you’ve clicked.
Below is the same coroutine reworked with a couple fixes
IEnumerator LookToPoint2D(Vector3 worldPoint, Vector3 upAxis, float degreesPerSecond)
{
Quaternion 2DLookCorrection = Quaternion.LookRotation(Vector3.up,-Vector3.back);
Vector3 targetDirection = worldPoint - transform.position;
targetDirection.z = 0; // prevents the
// this check prevents lookRotation is zero log if you try to feed Vector3.zero to look rotation
if(targetDirection.sqrMagnitude >= float.Epsilon)
{
Quaternion targetRot = Quaternion.LookRotation(targetDirection, upAxis) * 2DLookCorrection;
while(Quaternion.Dot(transform.rotation,targetRot) < 0.999)
{
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRot,
degreesPerSecond * Time.deltaTime);
yield return null;
// recalculate on the next frame in case the character is also moving
targetDirection = worldPoint - transform.position;
targetDirection.z = 0;
if(targetDirection.sqrMagnitude < float.Epsilon) break;
targetRot = Quaternion.LookRotation(targetDirection, upAxis) * 2DLookCorrection;
}
if(targetDirection.sqrMagnitude >= float.Epsilon)
transform.rotation = targetRot;
}
//Done rotating, shoot now
}
Edit: another way of writing the LookRotation without the need of LookCorrection is to swap the fields. make the 1st parameter point to where you want the z to point to (World positive Z) while the second parameter describes where you want the y axis to point (opposite from the target direction)