有没有办法在不统一使用 Update() 函数的情况下从按钮单击启动方法

Is there a way to start a method from button click without using Update() function in unity

下面是我的 C# 脚本。我使用 On Click 事件向我的项目添加了一个按钮,并调用了 Rotate() 方法。但由于某种原因它不起作用

using System.Threading;
using UnityEngine;

public class Orbit : MonoBehaviour {

    public GameObject sun;
    public float speed;

    // Use this for initialization
    void Start () {

    }

    public void Update()
    {
        Rotate();
    }

    public void Rotate()
    {
        transform.RotateAround(sun.transform.position, Vector3.up, speed * 
        Time.deltaTime);
    }
}

我在调用Rotate()方法时注释了Update()方法。我还为脚本创建了一个游戏对象。

目前只能在Update中使用的原因是

public void Rotate()
{
    transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}

需要反复调用。否则它只会旋转一帧并且只导致 Time.deltaTime 非常小。但是 Button 组件的 onClick 事件只触发一次。它类似于例如Input.GetKeyDown 仅在按键按下时调用一次。 Button 组件本身没有实现来处理持续的按钮按下。


据我所知,您想要的是在单击按钮后旋转对象

  • 开始永远旋转
  • 一段时间
  • 直到您再次按下按钮
  • 直到它被释放(-> 实现一个连续发射按钮,见下文)

单独的Button组件只能做前三个:

永远旋转

要么使用 Coroutine

private bool isRotating;

public void Rotate()
{
    // if aready rotating do nothing
    if(isRotating) return;

    // start the rotation
    StartCoroutine(RotateRoutine());

    isRotating = true;
}

private IEnumerator RotateRoutine()
{
    // whuut?!
    // Don't worry coroutines work a bit different
    // the yield return handles that .. never forget it though ;)
    while(true)
    {
         // rotate a bit
         transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);

        // leave here, render the frame and continue in the next frame
        yield return null;
    }
}

或仍在 Update

private bool isRotating = false;

private void Update()
{
    // if not rotating do nothing
    if(!isRotating) return;

    // rotate a bit
    transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}

public void Rotate()
{
    // enable the rotation
    isRotating = true;
}

请注意,Update 解决方案仅供您了解正在发生的情况。它不应该那样使用,因为它不是那么有效,因为 Update 被连续调用并检查 bool 如果还没有旋转。这会产生不必要的开销。这同样适用于 all 以下示例:比 Update 更喜欢使用协程(在这种情况下! 在其他情况下它是实际的使用一个 Update 方法而不是多个并发协程更好更有效..但那是另一回事了。)

旋转一段时间

作为协程

// adjust in the inspector
// how long should rotation carry on (in seconds)?
public float duration = 1;

private bool isAlreadyRotating;

public void Rotate()
{
    // if aready rotating do nothing
    if(isAlreadyRotating) return;

    // start a rottaion
    StartCoroutine(RotateRoutine());
}

private IEnumerator RotateRoutine()
{
    // set the flag to prevent multiple callse
    isAlreadyRotating = true;

    float timePassed = 0.0f;
    while(timePassed < duration)
    {
         // rotate a small amount
         transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);

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

         // leave here,  render the frame and continue in the next frame
         yield return null;
    }

    // reset the flag so another rotation might be started again
    isAlreadyRotating = false;
}

Update

public float duration;

private bool isRotating;
private float timer;

private void Update()
{
    // if not rotating do nothing
    if(!isRotating) return;

    // reduce the timer by passed time since last frame
    timer -= Time.deltaTime;

    // rotate a small amount
    transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);

    // if the timer is not 0 return
    if(timer > 0) return;

    // stop rottaing
    isRotating = false;
}

public void Rotate()
{
    // if already rotating do nothing
    if(isRotating) return;

    // start rotating
    isRotating = true;

    // enable timer
    timer = duration;
}

切换旋转

这与之前的非常相似,但这次您不再使用计时器,而是通过再次单击来停止旋转。 (你甚至可以将两者结合起来,但要小心正确地重置 isRotating 标志;))

作为协程

private bool isRotating;

public void ToggleRotation()
{
    // if rotating stop the routine otherwise start one
    if(isRotating)
    {
        StopCoroutine(RotateRoutine());
        isRotating = false;
    }
    else
    {
        StartCoroutine(RotateRoutine());
        isRotating = true;
    }
}

private IEnumerator RotateRoutine()
{
    // whuut?!
    // Don't worry coroutines work a bit different
    // the yield return handles that .. never forget it though ;)
    while(true)
    {
        // rotate a bit
        transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);

        // leave here, render the frame and continue in the next frame
        yield return null;
    }
}

Update

private bool isRotating;

private void Update()
{
    // if not rotating do nothing
    if(!isRottaing) return;

    // rotate a bit
    transform.RotateAround(sun.transform.position, Vector3.up, speed * Time.deltaTime);
}

public void ToggleRotation()
{
    // toggle the flag
    isRotating = !isRotating;
}

旋转直到释放

这是最"complicated"的部分,因为单靠Button无法做到这一点(没有"on Release")。但是您可以使用 IPointerXHandler 接口来实现它。

好消息:您可以保留现有的原始脚本

public void Rotate()
{
    transform.RotateAround(sun.transform.position, Vector3.up, speed * 
    Time.deltaTime);
}

现在您需要按钮的扩展。它会像 Update 一样每帧重复调用 whilePressed 事件,所以你只需要在 whilePressed 中引用你的 Rotate 方法而不是 onClick.

同样有两种选择将其实现为协程:

[RequireComponent(typeof(Button))]
public class HoldableButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
    // reference the same way as in onClick
    public UnityEvent whilePressed;       

    private Button button;
    private bool isPressed;

    private void Awake()
    {
        button = GetComponent<Button>();

        if(!button)
        {
            Debug.LogError("Oh no no Button component on this object :O",this);
        }
    }

    // Handle pointer down
    public void OnPointerDown()
    {
        // skip if the button is not interactable
        if(!button.enabled || !button.interactable) return;

        // skip if already rotating
        if(isPressed) return;

        StartCoroutine(PressedRoutine());
        isPressed= true;

    }

    // Handle pointer up
    public void OnPointerUp()
    {
        isPressed= false;
    }

    // Handle pointer exit
    public void OnPointerExit()
    {
        isPressed= false;
    }

    private IEnumerator RotateRoutine()
    {
        // repeatedly call whilePressed until button isPressed turns false
        while(isPressed)
        {
            // break the routine if button was disabled meanwhile
            if(!button.enabled || !button.interactable)
            {
                isPressed = false;
                yield break;
            }

            // call whatever is referenced in whilePressed;
            whilePressed.Invoke();

            // leave here, render the frame and continue in the next frame
            yield return null;
        }
    }
}

或者您也可以在 Update 中再次执行相同的操作

[RequireComponent(typeof(Button))]
public class HoldableButton : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IPointerExitHandler
{
    public UnityEvent whilePressed;

    private bool isPressed;
    private Button button;

    private void Awake()
    {
        button = GetComponent<Button>();

        if(!button)
        {
            Debug.LogError("Oh no no Button component on this object :O",this);
        }
    }


    private void Update()
    {
        // if button is not interactable do nothing
        if(!button.enabled || !button.interactable) return;

        // if not rotating do nothing
        if(!isPressed) return;

        // call whatever is referenced in whilePressed;
        whilePressed.Invoke();
    }

    // Handle pointer down
    public void OnPointerDown()
    {
        // enable pressed
        isPressed= true;
    }

    // Handle pointer up
    public void OnPointerUp()
    {
        // disable pressed
        isPressed= false;
    }

    // Handle pointer exit
    public void OnPointerExit()
    {
        // disable pressed
        isPressed= false;
    }
}

将此组件放在 Button 组件旁边。您不必在 onClick 中引用任何内容,只需将其留空即可。而是引用 onPressed 中的内容。保留 Button 组件,因为它也为我们处理 UI 样式(例如悬停更改 color/sprite 等)


再次强调:Update 解决方案目前可能看起来 cleaner/simplier 但不如协程解决方案高效(在此用例中)和易于控制(这可能基于意见)。

请搜索有关按键功能的文章。这将对您找到答案有很大帮助。如果我们需要在我们的项目中连续做某事,则使用更新,因为当我们做了一次时使用按下的键

此示例也用于解决您的问题并在按下特定按钮时使用此脚本