I have an object that I want to rotate back and forth on one axis (y) over a 120-degree arc. A simple swivel, right? Like a hinged gate - it can open, and then close. So far, using Slerp or Lerp (I don’t really care which) has worked fine for getting it to go one way… but when it reaches the end of the arc it “snaps” back to the starting position instantaneously rather than swiveling back at the same speed.
My code:
#pragma strict
var to : Transform;
var from : Transform;
var speed : float = 1.0;
var startPosition = false;
var endPosition = false;
function Start () {
}
function BackRotation () {
if (startPosition) {
transform.rotation = Quaternion.Slerp (from.rotation, to.rotation, Time.time * speed);
}
if (transform.rotation == to.transform.rotation) {
endPosition = true;
startPosition = false;
StopCoroutine ("BackRotation");
}
}
function ForeRotation () {
if (endPosition) {
transform.rotation = Quaternion.Slerp (to.rotation, from.rotation, Time.time * speed);
}
if (transform.rotation == from.transform.rotation) {
endPosition = false;
startPosition = true;
StopCoroutine ("ForeRotation");
}
}
function Update () {
if (startPosition) {
StartCoroutine ("BackRotation");
}
if (endPosition) {
StartCoroutine ("ForeRotation");
}
}
I suspect the problem is this: transform.rotation== to.transform.rotation . Those will never truly be exact. What you probably want to do is check if their difference is below some epsilon:
if(Mathf.Abs(distance) < 0.05) then close_enough
[edit] Once it’s close enough, to make it exact you can set the transform.rotation = to.transform.rotation. Otherwise small errors might accumulate over time.
Also, are you sure “Time.time * speed” is really what you want to provide for the “t” parameter? I know the example in the document also has this, but it doesn’t really make sense. Instead, the “t” parameter to all lerp functions can usually be thought of as a percentage value from 0 to 1.
This script tweens what it is attatched to between from and to over a duration that is based on the distance between from and to (beginning at each loop).
#pragma strict
var to : Transform;
var from : Transform;
var speed : float = 1.0;
private function Start() : void {
StartCoroutine(MoveAndRotate(from, to));
}
private function MoveAndRotate(from : Transform, to : Transform) : IEnumerator {
var curFrom : Transform = from;
var curTo : Transform = to;
while (true) {
var distance = Vector3.Distance(curFrom.position, curTo.position); // Calculate distance.
var duration = distance / speed; // Calculate duration based on speed and distance. (Will not change duration while tweening)
var currentTime : float = 0f; // Current time variable.
while (currentTime < duration) { // While we haven't reached our end time.
currentTime += Time.deltaTime; // Progress time.
var t : float = currentTime / duration; // Calculate t value.
transform.position = Vector3.Lerp(curFrom.position, curTo.position, t); // Lerp positions.
transform.rotation = Quaternion.Slerp(curFrom.rotation, curTo.rotation, t); // Slerp rotations.
yield null; // Yield to next frame.
}
transform.position = curTo.position; // Pop into place when done.
transform.rotation = curTo.rotation; // Pop into rotation when done.
var newTo : Transform = curFrom; // Switch curFrom and curTo
curFrom = curTo;
curTo = newTo;
}
}
This script moves what it is attatched to between two other transforms with a constant speed regardless if those transforms move.
#pragma strict
var to : Transform;
var from : Transform;
var moveSpeed : float = 1.0f;
var rotationSpeed : float = 100f;
private function Start() : void {
StartCoroutine(MoveAndRotate(from, to));
}
private function MoveAndRotate(from : Transform, to : Transform) : IEnumerator {
var curFrom : Transform = from;
var curTo : Transform = to;
while (true) {
while (Vector3.Distance(transform.position, curTo.position) > Time.deltaTime * moveSpeed) { // loop while distance is further than the distance we can cover this frame.
transform.position = Vector3.MoveTowards(transform.position, curTo.position, moveSpeed * Time.deltaTime); // Move towards target poition.
transform.rotation = Quaternion.RotateTowards(transform.rotation, curTo.rotation, rotationSpeed * Time.deltaTime); // Rotate towards target rotation.
yield null; // Yield to next frame.
}
transform.position = curTo.position; // Pop into place when done.
transform.rotation = curTo.rotation; // Pop into rotation when done.
var newTo : Transform = curFrom; // Switch curFrom and curTo
curFrom = curTo;
curTo = newTo;
}
}
If anything I’m even more confused now than I was… Is there not a simple way to get it to reverse the arc? Or, failing that, repeat it once it jumps back to it’s original position?
Time.time keeps increasing. So on the way up, (Time.time * speed) goes from 0 to 1. When you start rotating the object back, though, (Time.time * speed) is already 1, and it hits the last value of the lerp or slerp instantly.
Store the starting value of Time.time in a variable, and use that to set the lerp value:
For the record I feel zero need to use coroutines, that’s just coming from an answer I found to solve the first half of the rotation. It’s not necessary (unless it’s actually necessary).
Agreed, using coroutines for something like this is unnecessary, and confusing to newbies.
But @wilhelmscream , I will also advise you (just this once) to make the move to C#. The industry has settled on it; almost nobody uses JS anymore, for a variety of reasons. Join us, and together we will rule the galaxy.
Finally, even using Lerp for this is working harder than you need to. Since you want to move at a constant speed, just use MoveTowards, or in this case, MoveTowardsAngle. Easy peesy. Here’s an example — untested, and in C#, but it should show you how to do it.
using UnityEngine;
public class Rotator : MonoBehaviour {
public float targetAngle = 120; // target angle (duh)
public float speed = 10; // movement degrees/sec
void Update() {
float ang = transform.rotation.eulerAngles.y;
float maxMove = speed * Time.deltaTime;
ang = Mathf.MoveTowardsAngle(ang, targetAngle, maxMove);
transform.rotation = Quaternion.Euler(0, ang, 0);
}
}
Just attach this script to whatever object you want to rotate, and whenever you like, set the targetAngle to whatever you want. It will smoothly rotate (around Y) to that angle at a constant speed. Easy peasy, right?
It also looks like you tried to translate and shoehorn my proposed solution into your own thing - so I’m not really sure what’s going on in there. Percent keeps track of whether you’re going forward or backward so your other checks are redundant. There was a point to the nested while loops.
True — I’d rather have a script that can go to any target, and then change (perhaps via some other component) the target to make it ping-pong, as it strikes me as more general.
Mathf.PingPong is a neat suggestion, and you’re right, it would work nicely with Lerp in this case, if you’re sure you don’t want any delay at either end, events or other processing when it reverses direction, etc. (Which may very well be the case.)
But, for the sake of argument, here’s a version using MoveTowards, that also kicks off a UnityEvent at either end so you can trigger a sound or some other script or whatever.
using UnityEngine;
using UnityEvents;
public class Sweeper : MonoBehaviour {
public float targetAngle0 = 0;
public float targetAngle1 = 120;
public float speed = 10; // movement degrees/sec
public UnityEvent hitEnd0;
public UnityEvent hitEnd1;
bool moveTowards1 = true;
void Update() {
float ang = transform.rotation.eulerAngles.y;
float maxMove = speed * Time.deltaTime;
if (moveTowards1) {
ang = Mathf.MoveTowardsAngle(ang, targetAngle1, maxMove);
if (ang == targetAngle1) {
hitEnd1.Invoke();
moveTowards1 = false;
}
} else {
ang = Mathf.MoveTowardsAngle(ang, targetAngle0, maxMove);
if (ang == targetAngle0) {
hitEnd0.Invoke();
moveTowards1 = true;
}
}
transform.rotation = Quaternion.Euler(0, ang, 0);
}
}
You need to make sure that the from and the to transforms in your method are cached and have nothing to do with the current gameObject’s transform.position