【问题标题】:How can I use Mathf.PingPong to scale and rotate an object between max and min?如何使用 Mathf.PingPong 在最大和最小之间缩放和旋转对象?
【发布时间】:2020-04-19 05:06:41
【问题描述】:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateAndScale : MonoBehaviour
{
    public Transform target; // Target to scale
    public Vector3 minScale; // Minimum scale value
    public Vector3 maxScale; // Maximum scale value
    public Vector3 maxRotate;
    public Vector3 minRotate;
    public float speed;

    private float t = 0.0f;

    void Update()
    {
        //if (Input.GetKey(KeyCode.C))
        //{
        t += speed * Time.deltaTime;
            target.localScale = Vector3.Lerp(target.localScale, maxScale, t);
            target.localRotation = Quaternion.Lerp(target.localRotation,
                Quaternion.Euler(maxRotate.x, maxRotate.y, maxRotate.z), t);
       // }
    }
}

这会将对象缩放并旋转到最大一次。但我希望它达到最大值,然后回到最小值,然后不间断地达到最大值和最小值。

然后我想使用一个键,例如 C,当按一次 C 时,它会达到最大,再按一次 C,它会回到最小值。

但首先我不知道如何用不间断的乒乓球来做,然后如何用钥匙做?

我添加了一个新的全局标志来中断协程,所以如果在中间按 C 时为真,它将实时直接更改为另一个方向 min 或 max,如果中断标志为假,它将使用 C 键和以前一样。但它不起作用,它仍然什么都不做,即使中断标志为真,我也需要等待它完成更改为最小值/最大值。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RotateAndScale : MonoBehaviour
{
    public Transform target; // Target to scale
    public Vector3 minScale; // Minimum scale value
    public Vector3 maxScale; // Maximum scale value
    public Vector3 maxRotate;
    public Vector3 minRotate;
    public float speed;
    public bool interruptCoroutine = false;

    // Flag for blocking input until one routine is done
    private bool isMoving;

    // Flag for deciding in which direction to go next
    // Via the Inspector set this to the direction it shall initially go towards
    [SerializeField] bool towardsMin;

    private float t = 0.0f;

    void Update()
    {


        if(interruptCoroutine == true)
        {
            if (Input.GetKeyDown(KeyCode.C))
            {
                StopAllCoroutines();
                StartCoroutine(DoMove());
            }
        }
        else
        {
            if (!isMoving && Input.GetKeyDown(KeyCode.C))
            {
                StartCoroutine(DoMove());
            }
        }
    }

    // Once started via a StartCoroutine call this will be executed
    // every frame until the next yield command
    IEnumerator DoMove()
    {
        if (interruptCoroutine == false)
        {
            // Just in case block concurrent routines
            if (isMoving) yield break;

            // Block input
            isMoving = true;
        }

        // Decide if going to Max or min
        var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
        var targetScale = towardsMin ? minScale : maxScale;

        // Store Start values
        var startRot = target.localRotation;
        var startScale = target.localScale;

        var duration = 1 / speed;
        var timePassed = 0f;
        while (timePassed < duration)
        {
            t = timePassed / duration;
            // Optional: add ease-in and ease-out
            //t = Mathf.SmoothStep(0, 1, t);

            target.localScale = Vector3.Lerp(startScale, targetScale, t);
            target.localRotation = Quaternion.Lerp(startRot, targetRot, t);

            // Increase the time passed since last frame
            timePassed += Time.deltaTime;

            // This tells Unity to "pause" the routine here,
            // render this frame and continue from here
            // in the next frame
            yield return null;
        }

        // Just to be sure to end with exact values apply them hard once
        target.localScale = targetScale;
        target.localRotation = targetRot;

        // Invert direction
        towardsMin = !towardsMin;

        if (interruptCoroutine == false)
        {
            // Done -> unlock input
            isMoving = false;
        }
    }
}

【问题讨论】:

    标签: c# unity3d


    【解决方案1】:

    首先是一般性说明:这不是您想要使用Lerp 的方式。

    目前,您总是开始一个具有增长因子的新插值,但从当前旋转/缩放到最大值。

    因此在下一帧中,当前值已经移动,因此下一个插值使用不同的起始值等。

    如何插值基本上有两种用例:

    • 使用常数因子根据目标插入当前值。这会导致平滑的运动,例如,当你有期待大量抖动的相机运动。这不是您想要做的,因为这会变得越来越慢,最终可能永远无法达到目标。
    • constant 起点和终点之间插值,因子 growth01。这导致在受控时间内的平稳运动。这更像你想要的。

    Mathf.PingPong 使用时间输入并在0 和给定的最大参数之间来回切换。

    在您的情况下,您希望使用速度值作为时间输入的乘数上升到 1

    这样

    void Update()
    {
        t = Mathf.PingPong(speed * Time.time, 1);
        target.localScale = Vector3.Lerp(target.localScale, maxScale, t);
        target.localRotation = Quaternion.Lerp(target.localRotation,
            Quaternion.Euler(maxRotate.x, maxRotate.y, maxRotate.z), t);
    }
    

    然后对于一次性按键,它会变得更复杂一些。我现在假设您总是希望等到按一次“动画”完成后再接受下一次按键返回。

    我会为此使用Coroutine。它们的工作方式类似于临时的小Update 例程,但更易于控制和维护:

    // Flag for blocking input until one routine is done
    bool isMoving;
    
    // Flag for deciding in which direction to go next
    // Via the Inspector set this to the direction it shall initially go towards
    [SerializeField] bool towardsMin;
    
    void Update()
    {
        if(!isMoving && Input.GetKeyDown(KeyCode.C))
        {
            StartCoroutine(DoMove());
        }
    }
    
    // Once started via a StartCoroutine call this will be executed
    // every frame until the next yield command
    IEnumerator DoMove()
    {
        // Just in case block concurrent routines
        if(isMoving) yield break;
    
        // Block input
        isMoving = true;
    
        // Decide if going to Max or min
        var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
        var targetScale = towardsMin ? minScale : maxScale;
    
        // Store Start values
        var startRot = target.localRotation;
        var startScale = target.localScale;
    
        var duration = 1 / speed;  
        var timePassed = 0f;
    
        while(timePassed < duration)
        {
            t = timePassed / duration;
            // Optional: add ease-in and ease-out
            //t = Mathf.SmoothStep(0, 1, t);
    
            target.localScale = Vector3.Lerp(startScale, targetScale, t);
            target.localRotation = Quaternion.Lerp(startRotation, targetRotation, t);
    
            // Increase the time passed since last frame
            timePassed += Time.deltaTime;
    
            // This tells Unity to "pause" the routine here,
            // render this frame and continue from here
            // in the next frame
            yield return null;
        }
    
        // Just to be sure to end with exact values apply them hard once
        target.localScale = targetScale;
        target.localRotation = targetRotation;
    
        // Invert direction
        towardsMin = !towardsMin;
    
        // Done -> unlock input
        isMoving = false;
    }
    

    如果您想打断当前的例行程序并立即返回,请使用

    // Flag for deciding in which direction to go next
    // Via the Inspector set this to the direction it shall initially go towards
    // Since the routine inverts this you have to set the initial value to exactly the opposite
    // of what you want to be the first direction!
    [SerializeField] bool towardsMin;
    
    void Update()
    {
        if(Input.GetKeyDown(KeyCode.C))
        {
            StopAllCoroutines(DoMove());
            StartCoroutine(DoMove());
        }
    }
    
    // Once started via a StartCoroutine call this will be executed
    // every frame until the next yield command
    IEnumerator DoMove()
    {
        // Invert direction for the next time
        towardsMin = !towardsMin;
    
        // Decide if going to Max or min
        var targetRot = Quaternion.Euler(towardsMin ? minRotate : maxRotate);
        var targetScale = towardsMin ? minScale : maxScale;
    
        // Store Start values
        var startRot = target.localRotation;
        var startScale = target.localScale;
    
        var duration = 1 / speed;  
        var timePassed = 0f;
        while(timePassed < duration)
        {
            t = timePassed / duration;
            // Optional: add ease-in and ease-out
            //t = Mathf.SmoothStep(0, 1, t);
    
            target.localScale = Vector3.Lerp(startScale, targetScale, t);
            target.localRotation = Quaternion.Lerp(startRotation, targetRotation, t);
    
            // Increase the time passed since last frame
            timePassed += Time.deltaTime;
    
            // This tells Unity to "pause" the routine here,
            // render this frame and continue from here
            // in the next frame
            yield return null;
        }
    
        // Just to be sure to end with exact values apply them hard once
        target.localScale = targetScale;
        target.localRotation = targetRotation;
    }
    

    简而言之:完全摆脱 isMoving 和所有出现的事件!

    然后,您需要在 while 循环之前 反转 towardsMin 标志!因此,您还需要反转 Inspector 中的标志,因此如果您希望它首先向最大值移动,请将其设置为 true => 由协程反转 => 第一次向最大值移动。


    在智能手机上输入,但我希望思路清晰

    【讨论】:

    • 这很好用。如果我想添加一个选项,如果我再次按下 C ,它会在中间添加一个选项,它会剪切动画并缩放和旋转到最小/最大?所以一旦标志 isMoving 将阻塞,但如果不使用 isMoving 我将能够在中间改变方向。
    • 刚刚添加到底部;)
    • 中断部分不起作用。我用我尝试的方法编辑了我的问题。我为此添加了一个新的全局标志,因此如果标志为真,则可以在它为假时中断,像之前一样使用它等待它完成。但是按下 C 时我仍然需要等待。
    • 我的错...移动towardsMin标志的反转之前while循环..否则如果例程中断,方向将永远不会改变!
    • 这几乎完美无缺。现在将我的代码与全局interruptCoroutine一起使用,并且在运行游戏时在while循环之前移动了朝向Min标志之后,我在它开始移动最大值或最小值之前按下了C键两次。出于某种原因,我需要在第一次运行游戏时按两次 C 才能开始工作。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-10-14
    • 2015-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多