如何在不手动检查每一帧输入的情况下触发不频繁的交互事件?

How to trigger infrequent interaction events without manually checking for input every frame?

提问人:teeeeee 提问时间:6/2/2023 最后编辑:derHugoteeeeee 更新时间:6/6/2023 访问量:225

问:

我是Unity的新手,有一个非常基本的问题。

到目前为止,我一直在检查是否每一帧都按下一个键(仅在主“Update()”方法中),以处理玩家的移动。这很好,因为我希望无论如何几乎每一帧都会发生运动。

我的问题是关于如何处理不经常发生的事件——比如每 10 分钟发生一次。例如,暂停游戏。如果按下了暂停键,我不想检查每一帧,如下所示:

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

public class GameController : MonoBehaviour
{
    void Update()
    {
        if ( Input.GetKey(KeyCode.UpArrow) ) {
            // Move (this happens pretty much every frame)
        }
        
        if ( Input.GetKey(KeyCode.Space) ) {
            // Pause game (this hardly ever happens)
        }
        
        // Check for many more possible interactions ...
    }
}

可能有许多这样的不频繁事件(火灾、跳跃等)。我知道它不应该真的减慢游戏的速度,有很多“if”语句,但如果这可以通过回调风格的操作来完成,那就更好了。

我知道 c#/Unity 中的代表和侦听器,但这些实际上是一种处理事件被触发/发布后发生的事情的方法,所以这并不是我的问题。此外,我更想知道如何在不检查每一帧的情况下触发这样的事件。在Unity中可能吗?

C# unity-game-engine 回调

评论

0赞 Phillip Zoghbi 6/2/2023
你为什么不想检查每一帧?
2赞 shingo 6/2/2023
也许您正在寻找 Unity 的新输入系统。
0赞 teeeeee 6/2/2023
@PhillipZoghbi正如我所说,它不是很优雅,它们几乎都是多余的检查。如果 Unity 可以触发通知,那就更好了。
0赞 teeeeee 6/2/2023
@shingo 是的,我已经看到新的输入系统允许定义“动作”并将其绑定到按键。这就提出了两个问题:1)在新的输入管理器出现之前,这是不可能的吗?2)新的输入管理器在幕后做了什么(它只是检查每一帧)?
0赞 derHugo 6/2/2023
我声称 1) 不,至少不是开箱即用......2) 没有线索,但我怀疑您是否应该关注任何一种选择的效率......InputActions 为您提供了您正在寻找的回调样式 + 其他灵活性和功能,这些灵活性和功能是该类所没有的。这个内部如何工作,对你来说不应该太担心..很可能还有其他可以优化的地方,首先是;)甚至可能使用一些通常已经非常有效的本机部件Input

答:

0赞 Phillip Zoghbi 6/2/2023 #1

经过一些广泛的研究,我得出的结论是 InputSystem是您最好的选择。

它在后端的处理方式非常特定于平台,这就是 Unity 自己编写包的原因。

要实现与 C# 中的查询类似的功能,您必须使用命名空间和一个类,该类再次特定于平台,并且由于不兼容问题,该程序集将无法与 Unity 一起使用。不过,您可以在引擎之外使用 C# 执行此操作。如果您非常好奇,请查看此链接System.Windows.FormsControl.KeyPress Event

在所有其他情况下,请放心地依赖,因为这正是您所要求的。InputSystem

0赞 teeeeee 6/6/2023 #2

正如 Phillip Zoghbi 的评论和回答中所建议的那样,输入系统包确实提供了我正在寻找的行为。如果它对任何人有帮助,下面给出了这种方法的最低工作示例。

首先,确保从“包管理器”菜单安装了“输入系统”。

然后,右键单击项目 Assets 文件夹 --> “Create” --> “Input Action”。打开它并创建一个操作映射,以及移动键和暂停按钮的操作和绑定:

enter image description here

接下来,为此输入操作资产使用“生成 C# 类”文件,它会自动创建一个 C# 文件(对我来说称为 )。MyInputActions.cs

现在我还有两个脚本。第一个是处理所有游戏输入:MyGameInputs.cs

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

public class MyGameInputs : MonoBehaviour
{
    public event EventHandler OnPauseGameAction;
    private MyInputActions myInputActions;

    private void Awake(){
        myInputActions = new MyInputActions();
        myInputActions.MyActionMap.Enable(); 
        myInputActions.MyActionMap.PauseGame.performed += PauseGame_performed;
    }

    private void PauseGame_performed( UnityEngine.InputSystem.InputAction.CallbackContext obj){
        OnPauseGameAction?.Invoke(this, EventArgs.Empty);
    }

    public Vector3 getMoveDir(){
        Vector3 moveDirection = myInputActions.MyActionMap.Move.ReadValue<Vector3>();
        return moveDirection;
    }
}

第二个是播放器的控制器脚本,它对输入做出反应:MyPlayerController.cs

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

public class MyPlayerController : MonoBehaviour
{
    [SerializeField] private MyGameInputs myGameInputs;
    private Vector3 moveDirection;

    private void Start(){
        myGameInputs.OnPauseGameAction += myGameInputs_OnPauseGameAction;
    }

    private void myGameInputs_OnPauseGameAction(object sender, System.EventArgs e){
        Debug.Log("Pause Key Pressed");
        // Perform actions when pause key pressed
    }

    private void Update(){
        moveDirection = myGameInputs.getMoveDir();
        Debug.Log(moveDirection);
        // Act according to move input keys
    }
}

move 函数的行为方式与原始帖子中大致相同(从某种意义上说,它仍然在检查每一帧的移动输入),但现在使用新输入系统中的方法。ReadValue()

但是,现在“暂停游戏”函数是使用回调而不是轮询完成的。这是通过在脚本中使用操作的“已执行”阶段来完成的,其中函数会触发一个名为 的事件。MyGameInputs.csPauseGame_performed()OnPauseGameAction

最后,脚本正在侦听事件,并将在触发时运行该函数。MyPlayerController.csOnPauseGameActionmyGameInputs_OnPauseGameAction

这样,回调用于执行触发(而不是轮询),并且输入脚本和播放器脚本都是完全解耦的(通过事件通知完成通信)。

1赞 bguyl 6/6/2023 #3

简短的回答,不,你不能

检查每一帧的输入是必要的,并且仍然会使用基于事件的输入系统在引擎盖下完成,没有什么比这更优化的了。

使用“新输入系统”,您已经可以访问这些事件,使用“旧输入管理器”,您必须创建自己的事件

如果需要,您可以轻松地自己创建活动。

using UnityEngine;

public class InputEvents : MonoBehaviour
{
    public event Action OnPausePressed;

    [SerializedField] private KeyCode _pauseKey;
    [SerializedField] private string _pauseButton;

    private void Update()
    {
        if (Input.GetKeyDown(_pauseKey) || Input.GetButtonDown(_pauseButton)) {
            OnPausePressed?.Invoke();
        }
    }
}

public class MyOtherClass {
    public void MyMethodToRegister() {
        InputEvents.OnPausePressed -= PausePressed;
        InputEvents.OnPausePressed += PausePressed;
    }

    public void MyMethodToUnregister() {
        InputEvents.OnPausePressed -= PausePressed;
    }

    private void PausePressed() {
        Debug.Log("Pause the game here");
    }
}

评论

0赞 teeeeee 6/7/2023
谢谢你的回答。是什么让您认为使用回调方法(如下面答案中的示例所示)仍在检查每一帧?你有这方面的链接或参考资料吗?这可能是真的,但我一直认为回调的工作方式与轮询(在每种编程语言中,而不仅仅是 c# / Unity)有着根本的不同。如果不是,我真的误解了他们背后的想法......谢谢