问题
I'm programming a game where a cube has to rotate smoothly around itself 90 degrees each time a metronome is beating.
Each time the metronome is beating, my method call a coroutine :
IEnumerator moveCoroutine()
{
if (!isCollided && canRotate) {
for (float i = 0; i < 10; i++) {
transform.RotateAround(pivot, direction, 90 / 10);
yield return null;
}
}
}
and the cube rotates rather smoothly, but the rotation is done long time before the next tick of the metronome, and I want it to rotate from the beginning to the end, in a smooth move.
I imagine that I've to use the time between two metronome ticks (which is the var 'Metronome.manager.delay') but I'm not finding how.
Thanks for your help
回答1:
So your unity c# script will usually run at each frame render. For your animation to be smooth, you need to multiply your changing field/variable with Time.DeltaTime
as per the documentation:
http://docs.unity3d.com/ScriptReference/Time-deltaTime.html
Time.DeltaTime
is calculated by the unity game engine and is the elapsed time from the last rendered frame.
The example from the documentation:
public class ExampleClass : MonoBehaviour {
void Update() {
float translation = Time.deltaTime * 10;
transform.Translate(0, 0, translation);
}
}
In the example, the transform is being translated on the z-axis by 10 units which is correctly animated due to the Time.deltaTime
[correction factor].
If you add or subtract to a value every frame chances are you should multiply with Time.deltaTime. When you multiply with Time.deltaTime you essentially express: I want to move this object 10 meters per second instead of 10 meters per frame.
Another example that uses a coroutine:
using UnityEngine;
using System.Collections;
public class CoroutinesExample : MonoBehaviour
{
public float smoothing = 1f;
public Transform target;
void Start ()
{
StartCoroutine(MyCoroutine(target));
}
IEnumerator MyCoroutine (Transform target)
{
while(Vector3.Distance(transform.position, target.position) > 0.05f)
{
transform.position = Vector3.Lerp(transform.position, target.position, smoothing * Time.deltaTime);
yield return null;
}
print("Reached the target.");
yield return new WaitForSeconds(3f);
print("MyCoroutine is now finished.");
}
}
What you want is probably something like this:
IEnumerator moveCoroutine()
{
if (!isCollided && canRotate) {
for (float i = 0; i < 10; i++) {
transform.RotateAround(pivot, direction, 9.0f * Time.deltaTime);
yield return null;
}
}
}
This fixes the smoothness of the animation and makes it independent of each computer's capacity to render frames (FPS). -> issue you will only notice when you playtest on other devices than your own.
Now to fix the issue of the length of the animation:
IEnumerator moveCoroutine()
{
int maxIterations = 10;
if (!isCollided && canRotate) {
for (float i = 0; i < maxIterations; i++) {
transform.RotateAround(pivot, direction, 9.0f * Time.deltaTime);
yield return new WaitForSeconds(Metronome.manager.delay / maxIterations);
}
}
}
The WaitForSeconds(Metronome.manager.delay / maxIterations);
will ensure your animation plays out with the timing you desire. (note that Metronome.manager.delay
will need to be converted to seconds if it is not already.
回答2:
You can use the SmoothStep function in Mathf : http://docs.unity3d.com/ScriptReference/Mathf.SmoothStep.html
minimum is the current angle of the cube, maximum is current angle + 90 and t is something like (currentTime - previousTick) / (nextTick - previousTick)
(to modify according to your data structure)
回答3:
This is a coroutine you just need to specify a value for waitforseconds. Monitor the game and set the value whatever you want.
IEnumerator moveCoroutine()
{
float time=0.5f;
if (!isCollided && canRotate) {
for (float i = 0; i < 10; i++) {
transform.RotateAround(pivot, direction, 9);
yield return new WaitForSeconds(time);
}
}
}
回答4:
Other answers might work .. but I use this setup for smooth Coroutines in Unity not depending on the framerate but the actual time (necessary changes for movement should be trivial):
public IEnumerator RotateCoroutine(Vector3 rotationEulerAngles, float rotationDuration)
{
/* Set the current goal Rotation to animate between them */
Quaternion currentRotation = transform.localRotation;
Quaternion goalRotation = currentRotation * Quaternion.Euler(rotationEulerAngles);
/* Set control variables */
float timePassed = 0.0f;
float fracTime = 0.0f;
/* Begin the animation, end after rotationDuration seconds */
while (fracTime < 1)
{
/* Set Rotation between current and goal depending on fracTime */
transform.transform.localRotation = Quaternion.Lerp(currentRotation, goalRotation, fracTime);
/* Update timePassed and fracTime */
timePassed += Time.deltaTime;
/* On this way fracTime will always be a value between
* 0 - start of animation and
* 1 - end of animation after rotationDuration seconds */
fracTime = timePassed / rotationDuration;
/* yield return so the actual state gets printed */
yield return null;
}
/* Just to be sure after the animation set yourGameObject
* to exactly the tranformation value(s) you want */
transform.localRotation = goalRotation;
}
With this you can way esier control how long an animation takes and setting exact values for your rotation makes sure nothing goes strange while rotating.
you can call this Coroutine than by e.g.
StartCoroutine(RotateCoroutine(new Vector3(0,90,0), 2.0f));
for rotating the Object in 2 seconds about 90° in its local Y-Axis
来源:https://stackoverflow.com/questions/33975164/unity-3d-rotate-object-smoothly-during-time