- Home /
Slerp / lerp not creating a smooth transition
Hi all,
So I have an almost fully functional script utilising slerp to attempt to create a smooth veering movement for a shmup. But slerp and lerp seem to have no affect on the rotation and it will go from say 45 degrees straight back to 0 (leveled out).
I cannot see where I am going wrong. The entire script is below, if it helps to test it out, just stick it on a model in a scene and use the mouse and left click to move around.
Also, the first Slerp is controlling the ships veering motion from leveled out to the max rotation during movement, the second one at the bottom is from the max rotation back to leveling out. Neither are currently smooth.
using UnityEngine;
using System.Collections;
public class PlayerController : MonoBehaviour
{
public float moveSpeed;
public float rotSpeed;
public float maxTilt;
private Transform myTransform;
private Vector3 destinationPoint;
private float destinationDistance;
public Vector3 defaultRotation;
private GameObject Player;
public bool hard = true;
void Start()
{
myTransform = transform;
destinationPoint = myTransform.position;
Player = GameObject.FindWithTag("Player");
}
void FixedUpdate()
{
destinationDistance = Vector3.Distance(destinationPoint, myTransform.position);
if (Input.GetMouseButton(0))
{
Plane playerPlane = new Plane(Vector3.up, myTransform.position);
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float hitdist = 0.0f;
if (playerPlane.Raycast(ray, out hitdist))
{
Vector3 lastPosition = myTransform.position;
Vector3 targetPoint = ray.GetPoint(hitdist);
destinationPoint = ray.GetPoint(hitdist);
Vector3 D = targetPoint - transform.position;
Quaternion targetRotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(D), rotSpeed * Time.deltaTime);
myTransform.rotation = targetRotation;
if (targetPoint.x <= lastPosition.x)
{
transform.localEulerAngles = new Vector3(0, 0, transform.localEulerAngles.y + maxTilt);
}
else if (targetPoint.x >= lastPosition.x)
{
transform.localEulerAngles = new Vector3(0, 0, transform.localEulerAngles.y - maxTilt);
}
}
}
if (destinationDistance > 0)
{
myTransform.position = Vector3.MoveTowards(myTransform.position, destinationPoint, moveSpeed * Time.deltaTime);
}
if (destinationDistance < 5f)
{
Quaternion playerRotation = myTransform.rotation;
float rotationz = playerRotation.z;
//Debug.Log("Rotationz = " + rotationz);
transform.eulerAngles = Vector3.Slerp(myTransform.eulerAngles = new Vector3(0, 0, rotationz), defaultRotation, rotSpeed * Time.deltaTime);
}
}
}
Answer by DanSuperGP · Feb 09, 2015 at 10:50 PM
Your problem is here...
rotSpeed * Time.deltaTime;
That's not how lerp / slerp works.
Lerp takes 3 elements. A starting position, an ending position, and T.
T is a number between 0 and 1
At T = 0 the Lerp will return the starting position, At T = 0.25 the Lerp will return the position 25% of the way along the line between the starting and ending position. At T = 0.5 the Lerp will return the position 50% of the way along the line between the starting and ending position. At T = 0.75 the Lerp will return the position 75% of the way along the line between the starting and ending position. At T = 1.0 the Lerp will return the ending position.
The reason it's popping is you're just setting T to rotSpeed * Time.deltaTime; Which is whatever rotSpeed is times something very small like 0.03... which is very close to zero, which looks like no rotation at all.
So what you want to do to get a smooth movement (or rotation) is to increase the value of T from zero to one smoothly over the amount of time that you want the movement to take.
I usually do it something like this
IEnumerator lerpPosition( Vector3 StartPos, Vector3 EndPos, float LerpTime)
{
float StartTime = Time.time;
float EndTime = StartTime + LerpTime;
while(Time.time < EndTime)
{
float timeProgressed = (Time.time - StartTime) / LerpTime; // this will be 0 at the beginning and 1 at the end.
transform.position = Mathf.Lerp(StartPos, EndPos, timeProgressed);
yield return new WaitForFixedUpdate();
}
}
Thanks that makes sense, I've done something like that in the past on key movement, but I think I've just confused myself trying to get it working with just the mouse. I also didn't fully understand how Lerp works I guess. I'll try that approach when I get back from work.
It's actually very common to see Lerp used to change a transform in exactly this way (including, for example, in the official Unity tutorials). It's "incorrect" in the sense that you don't end up with a linear movement (which is what the "L" of Lerp stands for...), because you're changing the start position of the Lerp in each frame and keeping the same delta, ins$$anonymous$$d of keeping the same start and changing the delta. There's a good explanation of why this is wrong at http://www.blueraja.com/blog/404/how-to-use-unity-3ds-linear-interpolation-vector3-lerp-correctly.
However, while incorrect, it's not the cause of the "popping" described in this case - that's due to other problems with the code (although you should fix this too!)
Omg, no wonder I'm seeing this crazy way of using lerp popping up all over answers. I thought I was going crazy.
Ok DanSuperGP, I went with your approach as on paper so speak, it makes more sense. Tanoshimi, the way in the link you provided and the information was very helpful to get me understanding lerping/slerping correctly though. Thank you.
Now I have the problem that it will lerp perfectly when going left on the screen. However when traveling right, rather than going from a rotation of say 320 degrees up to 0. it goes the full 320 degree turn back down to zero, so an aerial role is carried out. I have corrected what Tanoshimi pointed out about the rotation.z error.
Also I realise a lot of the rotation at the top of the script is still incorrect, I am focusing on the bottom section for now to keep it simple, when that is fixed I think I will start from scratch.
if (destinationDistance < 5f)
{
StartCoroutine(lerpPos(transform.eulerAngles, transform.eulerAngles.normalized, rotSpeed));
}
}
IEnumerator lerpPos(Vector3 startPos, Vector3 endPos, float lerpTime)
{
float startTime = Time.time;
float endTime = startTime + lerpTime;
while (Time.time < endTime)
{
float timeProgressed = (Time.time - startTime) / lerpTime;
transform.eulerAngles = Vector3.Slerp(startPos, endPos, timeProgressed);
yield return new WaitForFixedUpdate();
}
}
}
I've added the code again (just the bits which have changed) for comparison.The only thing I can think is that transform.eulerAngles = Vector.Slerp... is not he correct approach???
Answer by tanoshimi · Feb 09, 2015 at 10:47 PM
You've got some very confused code here, but the biggest problem is that you're treating rotationz
as if it were the rotation around the z axis. It's not, it's the z component of a quaternion:
Quaternion playerRotation = myTransform.rotation;
float rotationz = playerRotation.z;
So you can't use it as the z component of a Vector3:
transform.eulerAngles = Vector3.Slerp(myTransform.eulerAngles = new Vector3(0, 0, rotationz), defaultRotation, rotSpeed * Time.deltaTime);
(There's other problems with that line, but I think this is the problem at hand)
Yes. This is another problem.
Also, he's using lerp incorrectly.
Thanks for the quick reply, when I get back from work I will try to tidy the code a little.