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

Unity InputActions Hold交互切换角色后移动失效问题求助

Unity InputActions 角色切换时持续按键失效问题解决方案

问题描述

设置了带有Hold交互的1D Axis绑定MoveX动作,场景内有4个角色,同一时间仅激活一个且支持角色切换。按住A或D键的状态下切换角色,新激活的角色会停止移动,必须松开并重新按下按键才能恢复移动。尝试在Update方法中检测按键状态未解决问题;删除OnDisable方法中的controls.Disable()代码可解决问题,但控制台会出现大量错误,需寻求正确解决方案。

原代码

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

public class CharacterController : MonoBehaviour
{
    private StateManager stateManager;
    [SerializeField] private string name;

    private CharacterMovement movement;
    private CharacterJump jump;

    public Animator anim;
    public Rigidbody2D rb;
    private SpriteRenderer spriteRenderer;

    private InputActions controls;

    public float horizontal;

    public bool flipOnMove;

    public bool isGround;
   
    void Awake()
    {
        controls = new InputActions();

        controls.Main.MoveX.performed += ctx => 
        {
            horizontal = ctx.ReadValue<float>();
        };

        controls.Main.MoveX.canceled += ctx =>
        {
            horizontal = 0;
        };

        controls.Main.Jump.started += ctx => JumpStart();
        controls.Main.Jump.canceled += ctx => JumpEnd();

        stateManager = FindObjectOfType<StateManager>();

        anim = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();
        spriteRenderer = GetComponent<SpriteRenderer>();

        movement = GetComponent<CharacterMovement>();
        jump = GetComponent<CharacterJump>();
        
    }

    void Update()
    {
        if(flipOnMove)
        {
            switch(horizontal)
            {
                case -1:
                    spriteRenderer.flipX = true;
                    break;
                case 1:
                    spriteRenderer.flipX = false;
                    break;
            }
        }
    }

    void FixedUpdate()
    {
        Move(horizontal);
    }

    void OnEnable()
    {
        controls.Enable();

        stateManager.currentGameObject = gameObject;
        stateManager.currentStatesName = name;

        if(stateManager.lastGameObjectTs != null) // 保存位置
        {
            gameObject.transform.position = stateManager.lastGameObjectTs.position;
            if(name == "Human") // 如果是人类则生成在更高位置
                gameObject.transform.position += new Vector3(0f,0.4f,0f);
            gameObject.transform.rotation = stateManager.lastGameObjectTs.rotation;
        }

        if(stateManager.lastGameObjectRb != null) // 保存物理状态
        {
            Rigidbody2D rb = gameObject.GetComponent<Rigidbody2D>();
            rb.velocity = stateManager.lastGameObjectRb;
        }

        
    }

    void OnDisable()
    {
        controls.Disable();
    }

    public void Move(float direction)
    {
        if(movement != null)
        {
            movement.Move(direction);
        }
    }

    public void JumpStart()
    {
        if(jump != null)
        {
            jump.Jump();
        }
    }

    public void JumpEnd()
    {
        if(jump !=  null)
        {
            jump.StopJumpAnim();
        }
    }
}

问题根源

旧角色禁用时调用controls.Disable(),会触发Input系统的canceled事件,将horizontal设为0。而新角色激活时,由于按键处于持续按住状态,Hold交互没有新的输入变化,Input系统不会自动触发performed事件,导致新角色的horizontal保持0,进而停止移动。

解决方案

方案1:使用全局单例InputActions(推荐)

让所有角色共享同一个InputActions实例,避免切换时频繁启用/禁用输入实例,从根源解决问题。

  1. 创建全局Input管理类:
public class InputManager : MonoBehaviour
{
    public static InputActions Instance { get; private set; }

    void Awake()
    {
        if (Instance == null)
        {
            Instance = new InputActions();
            Instance.Enable();
        }
        else
        {
            Destroy(gameObject);
        }
        DontDestroyOnLoad(gameObject);
    }
}

将该脚本挂载到场景中的全局对象(如空物体)上,确保场景切换时不会被销毁。

  1. 修改CharacterController的Awake方法,引用全局实例:
void Awake()
{
    // 替换原有controls实例化代码
    controls = InputManager.Instance;

    // 将lambda表达式改为独立方法,方便后续解绑
    controls.Main.MoveX.performed += OnMoveXPerformed;
    controls.Main.MoveX.canceled += OnMoveXCanceled;
    controls.Main.Jump.started += OnJumpStarted;
    controls.Main.Jump.canceled += OnJumpCanceled;

    // 原有其他逻辑不变...
}

// 新增独立事件处理方法
private void OnMoveXPerformed(InputAction.CallbackContext ctx)
{
    horizontal = ctx.ReadValue<float>();
}

private void OnMoveXCanceled(InputAction.CallbackContext ctx)
{
    horizontal = 0;
}

private void OnJumpStarted(InputAction.CallbackContext ctx)
{
    JumpStart();
}

private void OnJumpCanceled(InputAction.CallbackContext ctx)
{
    JumpEnd();
}
  1. 修改OnDisable方法,仅解绑事件,不再禁用controls:
void OnDisable()
{
    controls.Main.MoveX.performed -= OnMoveXPerformed;
    controls.Main.MoveX.canceled -= OnMoveXCanceled;
    controls.Main.Jump.started -= OnJumpStarted;
    controls.Main.Jump.canceled -= OnJumpCanceled;
}

方案2:保留角色独立InputActions,手动同步输入状态

如果必须每个角色单独管理InputActions,可在新角色激活时手动读取当前输入值,同步到horizontal。

修改CharacterController的OnEnable方法:

void OnEnable()
{
    controls.Enable();
    // 手动读取当前MoveX的输入值,同步到horizontal
    horizontal = controls.Main.MoveX.ReadValue<float>();

    // 原有其他逻辑不变...
}

同时记得在OnDisable中解绑事件(同方案1的步骤3),避免内存泄漏和事件冲突。

总结

方案1通过全局单例统一管理输入,更简洁高效,能彻底避免切换角色时的输入中断问题;方案2适合需要角色独立输入配置的场景,通过手动同步输入状态解决问题。两种方案都需注意事件的解绑,防止内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:20:24