Currently trying to make a door swing open/closed when you interact with it.
using UnityEngine;
using System.Collections;
public class DoorInteraction : MonoBehaviour, Interactable {
public float angleAccuracy = 5f;
public float slerpSpeed = 1f;
public float openAngle = 90f;
float startingY;
float endingY;
bool open = false;
bool swinging = false;
Quaternion temp;
public string mouseOverText(){
return "Press E to open door.";
}
public void Use(){
if (!swinging) {
swinging = true;
if (open) {
Debug.Log("closing");
temp.Set(0, startingY, 0, 0);
open = false;
} else {
Debug.Log("opening");
open = true;
temp.Set(0, endingY, 0, 0);
}
}
}
void Start(){
startingY = transform.eulerAngles.y;
endingY = transform.eulerAngles.y + openAngle;
temp.Set(0, transform.eulerAngles.y, 0, 0);
}
void FixedUpdate () {
Debug.Log( temp);
if (swinging) {
transform.rotation = Quaternion.Slerp( transform.rotation, temp, (Time.deltaTime * slerpSpeed/100) );
if(System.Math.Abs(transform.rotation.y - temp.y) < angleAccuracy){
swinging = false;
}
}
}
}
The open angle is set to 90 and when I run it, the quaternion is properly set to have an angle of 90 on the y axis. The door starts out with 0, but the door continues to rotate all the way to 180 degrees and it never sets swinging to false. It also swings EXTREMELY quickly if I don’t have /100 in the slerp speed.
Also! Is there a better way to handle this than in the FixedUpdate function? It seems kind of hard on the engine to have to call an update all the time for every single door, is there a way to disable it when it isn’t being opened/closed? Or something similar?
Create an animation using the Animation view…that will solve your problem. You use a triggered parameter and call it through the mechanim animation system
Transform.rotation is a 4D quaternion; don’t try to use x/y/z/w elements if you don’t understand quaternions. The point of lerp/slerp is that you advance the third parameter from 0.0 to 1.0, so there’s no need to check angles since the rotation is finished when you hit 1.0. Don’t use Time.deltaTime as the third parameter since it results in different results with different framerates, because of math (there are topics discussing this if you want to know more details). Typically coroutines are the easiest way to lerp stuff. Pseudocode:
t = 0.0
while (t < 1.0) {
t += Time.deltaTime * speed
slerp (rotationA, rotationB, t)
yield
}
Don’t use Slerp/Lerp if you expect a tween. Slerp/Lerp over a duration of time will result in a logarithmic tween.
First, difference between a slerp and a lerp is that slerp is just a lerp in a spherical coordinate system. You use it when you expect a rotational like interpolation.
If say your slerp/lerp value is 0.5, it means every time you call it you close the distance from current to target by half. Think of it like Zeno’s Paradox - Achilles and the tortoise.
Note how each step the distance traveled is shorter and shorter and shorter, but never actually reaches the target. (of course when dealing with finite bit depth, like that which the float is based on, the distance will become so small that it will get lost in float error… and it will technically “reach” the target)
This is also why it seems to go so fast unless you divide the step value by 100. Values larger than 1 will move you past the target, and values like 0.5 close HUGE gaps of distance in the first couple steps.
Always remember that Quaternion.Slerp will take the shortest path between start and end. So if the angle between your start and end is reflex (larger than 180deg) it will travel the opposite direction.
You compare the y value of your quaternion. Convert to euler first. The values in a quat are ‘complex’ (in the mathematical sense of the word), you’re not going to be able to easily compare them like that with out really understanding complex numbers and how they represent a rotation in 4D.
don’t use fixedupdate, it’s ALWAYS updating. Use Coroutines if you want to have a temporary update cycle. You can yield a ‘WaitForFixedUpdate’ object for every step of the animation until it’s done. Then exit the coroutine.
Animations, like SubZeroGaming suggested, are a valid option.
So are tween engines like iTween, HOTween, DOTween, or even mine SPTween.
It seems like a great way to do it that you, but I’ve never worked with coroutines before so I’m confused about why it isn’t working, this is my new code:
IEnumerator rotate(){
float t = 0.0f;
while (t < 1.0f) {
t += (float)(Time.deltaTime * slerpSpeed);
transform.rotation = Quaternion.Slerp (transform.rotation, temp, t);
yield return null;
}
}
public void Use(){
if (!swinging) {
swinging = true;
if (open) {
Debug.Log("closing");
temp.Set(0, startingY, 0, 0);
open = false;
} else {
Debug.Log("opening");
open = true;
temp.Set(0, endingY, 0, 0);
}
StartCoroutine("rotate");//rotate();
}
}
Edit: I now see that I was being dumb and forgot to make the transform.rotation = to the slerp. Fixed it in the code above.
But its still going extremely fast, even with t as my time, and it is still going 180 degrees
You’re not supposed to set the starting value of a (s)lerp to the current value (transform.rotation). It should always be the same starting value. Otherwise, the interpolation you’re trying to have is different every frame - you will interpolate between a smaller distance after every cycle, and your interpolation time is increasing. This results in an accelerated interpolation.
Also, you’re still setting the y element of a quaternion to an euler angle - that’s not how quaternions work. I would advise reading up on quaternions a little, it’ll shed some light on them. Instead, use Quaternion.Euler().
Okay I think I implemented this properly, but it is giving me a decelerating rotation.
This is my new code because I don’t know where it could be going wrong.
using UnityEngine;
using System.Collections;
public class DoorInteraction : MonoBehaviour, Interactable {
public float angleAccuracy = 5f;
public float slerpSpeed = 1f;
public float openAngle = 90f;
float startingY;
float endingY;
bool open = false;
bool swinging = false;
Quaternion temp;
Quaternion starting;
public string mouseOverText(){
return "Press E to open door.";
}
public void Use(){
if (!swinging) {
starting = new Quaternion(transform.rotation.x, transform.rotation.y, transform.rotation.z, transform.rotation.w);
swinging = true;
if (open) {
Debug.Log("closing");
temp.Set(0, startingY, 0, 0);
open = false;
} else {
Debug.Log("opening");
open = true;
temp.Set(0, endingY, 0, 0);
}
StartCoroutine("rotate");//rotate();
}
}
void Start(){
startingY = transform.rotation.y;
endingY = transform.rotation.y + openAngle;
temp.Set(0, startingY, 0, 0);
}
IEnumerator rotate(){
float t = 0.0f;
while (t < 1.0f) {
t += (float)(Time.deltaTime * slerpSpeed);
transform.rotation = Quaternion.Slerp (starting, temp, t);
Debug.Log("called coroutine " + t);
yield return null;
}
swinging = false;
}
}
It is still rotating 180 degrees, and doing it very quickly, then decellerating to extremely slowly and never getting there. I don’t understand what the 180 degrees could possibly be coming from.
It also doesn’t ever close.
Also, for some reason changing the openAngle is making it speed up/slow down
In Start(), you define endingY as startingY + openAngle (which is 90). However, that 90 is in degrees (euler angles), and the y element of a quaternion still has nothing to do with euler angles, as mentioned earlier. Use Quaternion.Euler() to define a quaternion with euler angles. It’s best not to touch Quaterion.Set() at all; remember to use .Euler() instead when you find yourself typing it.