You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity新输入系统射击动作多次触发问题求助

Unity新输入系统Shoot动作多次触发的解决办法

问题说明

我是Unity游戏开发新手,正在学习新输入系统,已经实现3D第一人称移动功能,但添加的Shoot动作会多次触发(有时多达10次),控制台能观测到这个现象。尝试将输入事件从performed改为started后仍未解决问题。

相关代码

PlayerInputHandler.cs

[Header("Input Action Asset")]
[SerializeField] private InputActionAsset playerControls;

[Header("Action Map Name References")]
[SerializeField] private string actionMapName = "Gameplay";

private InputAction moveAction;
private InputAction lookAction;
private InputAction jumpAction;
private InputAction sprintAction;
private InputAction interactAction;
private InputAction shootAction;

public Vector2 MoveInput {  get; private set; }
public Vector2 LookInput { get; private set; }
public bool JumpTriggered { get; private set; }
public float SprintValue { get; private set; }
public bool InteractTriggered { get; private set; }
public bool ShootTriggered { get; private set; }

public static PlayerInputHandler instance { get; private set; }

private void Awake()
{
    if(instance == null)
    {
        instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        Destroy(gameObject);
    }

    moveAction = playerControls.FindActionMap(actionMapName).FindAction("Move");
    lookAction = playerControls.FindActionMap(actionMapName).FindAction("Look");
    jumpAction = playerControls.FindActionMap(actionMapName).FindAction("Jump");
    sprintAction = playerControls.FindActionMap(actionMapName).FindAction("Sprint");
    interactAction = 
    playerControls.FindActionMap(actionMapName).FindAction("Interact");
    shootAction = playerControls.FindActionMap(actionMapName).FindAction("Shoot");
    RegisterInputActions();
}

private void RegisterInputActions()
{
    moveAction.performed += context => MoveInput = context.ReadValue<Vector2>();
    moveAction.canceled += context => MoveInput = Vector2.zero;

    lookAction.performed += context => LookInput = context.ReadValue<Vector2>();
    lookAction.canceled += context => LookInput = Vector2.zero;

    jumpAction.performed += context => JumpTriggered = true;
    jumpAction.canceled += context => JumpTriggered = false;

    sprintAction.performed += context => SprintValue = context.ReadValue<float>();
    sprintAction.canceled += context => SprintValue = 0f;

    interactAction.performed += context => InteractTriggered = true;
    interactAction.canceled += context => InteractTriggered = false;

    shootAction.performed += context => ShootTriggered = true;
    shootAction.canceled += context => ShootTriggered = false;
}

private void OnEnable()
{
    moveAction.Enable();
    lookAction.Enable();
    jumpAction.Enable();
    sprintAction.Enable();
    interactAction.Enable();
    shootAction.Enable();
}
private void OnDisable()
{
    moveAction.Disable();
    lookAction.Disable(); 
    jumpAction.Disable();
    sprintAction.Disable();
    interactAction.Disable();
    shootAction.Disable();
}

枪械逻辑代码

[SerializeField] private float damage = 10f;
[SerializeField] private float range = 100f;

private PlayerInputHandler inputHandler;
private Camera cam;

void Start()
{
    cam = Camera.main;
    inputHandler = PlayerInputHandler.instance;
}

void Update()
{
    if(inputHandler.ShootTriggered)
    {
        shoot();
    }
}

private void shoot()
{
    RaycastHit hit;
    if(Physics.Raycast(cam.transform.position, cam.transform.forward, out hit, range))
    {
        Debug.Log(hit.transform.name);
        Debug.DrawRay(cam.transform.position, cam.transform.forward * range);
        Target target = hit.transform.GetComponent<Target>();
        if(target != null) 
        {
            target.TakeDamage(damage);
        }
    }
}

问题根源

当前代码中,ShootTriggered在shootAction.performed时被设为true,直到canceled事件触发才会变回false。而枪械逻辑在Update中每帧检查这个布尔值,意味着只要按住射击按键(或鼠标键),每帧都会调用一次shoot()方法;即使是单次点击,也可能因为输入事件与帧循环的同步问题,导致连续几帧ShootTriggered保持true,从而多次触发射击。

解决方案

这里提供两种可靠的解决方式:

方式一:单次触发后立即重置标记

修改PlayerInputHandler,添加重置ShootTriggered的方法,然后在枪械逻辑调用射击后立刻重置标记:

  1. 在PlayerInputHandler.cs中新增方法:
public void ResetShootTrigger()
{
    ShootTriggered = false;
}
  1. 修改枪械代码的Update方法:
void Update()
{
    if(inputHandler.ShootTriggered)
    {
        shoot();
        inputHandler.ResetShootTrigger(); // 触发后立刻重置标记
    }
}

方式二:使用事件驱动(更推荐)

放弃在Update中轮询标记的方式,改用输入事件直接绑定射击逻辑,这样每次输入只会触发一次射击:

  1. 在PlayerInputHandler.cs中添加射击事件,并修改shootAction的绑定:
// 新增射击事件
public event System.Action OnShoot;

private void RegisterInputActions()
{
    // ... 保留其他输入绑定代码 ...

    // 替换原有的shootAction绑定逻辑
    shootAction.performed += context => OnShoot?.Invoke();
    // 不需要再设置ShootTriggered,也不需要canceled绑定
}
  1. 修改枪械代码,订阅事件并移除Update中的轮询:
void Start()
{
    cam = Camera.main;
    inputHandler = PlayerInputHandler.instance;
    // 订阅射击事件
    inputHandler.OnShoot += shoot;
}

// 记得取消订阅,避免内存泄漏
void OnDisable()
{
    if(inputHandler != null)
    {
        inputHandler.OnShoot -= shoot;
    }
}

// 移除原有的Update方法中的检查逻辑
// void Update()
// {
//     if(inputHandler.ShootTriggered)
//     {
//         shoot();
//     }
// }

额外注意事项

  • 如果你需要支持按住按键连续射击,可以在事件驱动的基础上,结合started和canceled事件配合协程实现,比如started时启动协程每隔固定时间调用射击,canceled时停止协程。
  • 确保输入系统中Shoot动作的交互类型设置正确:如果是鼠标左键,建议设为Button类型,触发方式为Press,避免重复触发。

内容的提问来源于stack exchange,提问作者Fadzii

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 16:53:18