Unity新Input System实现2D玩家水平移动速度归零问题
Unity新Input System实现2D玩家水平速度控制问题解决
问题背景
用Unity新Input System编写2D玩家水平移动脚本时,需要通过Rigidbody2D.velocity实现平滑移动,而非直接修改position,但遇到了核心问题:
- 仅监听
performed输入事件时,松开按键/摇杆回中后,系统不会主动触发输入归零逻辑,导致玩家速度保持不变,无法减速到0 - 若手动强制归零输入值,又会出现速度刚提升就被重置的异常情况
- 本质是复合输入(键盘/摇杆)在无输入状态下不会主动返回
Vector2.zero,缺少旧Input Manager中OnButtonUp()的触发逻辑
解决方案
方法1:监听performed+canceled双事件
Input System的canceled事件会在输入结束时触发(比如按键松开、摇杆回到中立位),通过监听该事件可在输入停止时将速度目标设为0,完美匹配需求。
修改后的完整代码:
using UnityEngine; using UnityEngine.InputSystem; public class PlayerHorizontalMovement : MonoBehaviour { [Tooltip("[units/s]")] [SerializeField] private float horizontalSpeed = 3f; [Tooltip("[units/s^2]")] [SerializeField] private float horizontalAccel = 60f; private PlayerControls controls; private Rigidbody2D myRigidbody; private float velocityGoal; private void Awake() { controls = new PlayerControls(); // 输入触发时,将输入值乘以最大速度作为目标速度 controls.Player.Move.performed += context => velocityGoal = context.ReadValue<Vector2>().x * horizontalSpeed; // 输入取消时,直接将目标速度归零 controls.Player.Move.canceled += _ => velocityGoal = 0f; } private void Start() { myRigidbody = GetComponent<Rigidbody2D>(); velocityGoal = 0f; } private void FixedUpdate() { Vector2 newVelocity = myRigidbody.velocity; // 平滑向目标速度过渡,实现加速度控制 newVelocity.x = Mathf.MoveTowards(newVelocity.x, velocityGoal, horizontalAccel * Time.fixedDeltaTime); myRigidbody.velocity = newVelocity; } private void OnEnable() => controls.Player.Enable(); private void OnDisable() => controls.Player.Disable(); private void OnDestroy() => controls.Dispose(); // 释放Input资源,避免内存泄漏 }
方法2:FixedUpdate周期主动读取输入值
如果不想依赖事件驱动,可在物理更新周期FixedUpdate中主动读取当前输入状态,无论有无输入,系统都会返回正确的数值(无输入时为0)。
修改后的完整代码:
using UnityEngine; using UnityEngine.InputSystem; public class PlayerHorizontalMovement : MonoBehaviour { [Tooltip("[units/s]")] [SerializeField] private float horizontalSpeed = 3f; [Tooltip("[units/s^2]")] [SerializeField] private float horizontalAccel = 60f; private PlayerControls controls; private Rigidbody2D myRigidbody; private InputAction moveAction; private void Awake() { controls = new PlayerControls(); moveAction = controls.Player.Move; } private void Start() { myRigidbody = GetComponent<Rigidbody2D>(); } private void FixedUpdate() { // 主动读取当前输入值,无输入时自动返回0 float inputX = moveAction.ReadValue<Vector2>().x; float velocityGoal = inputX * horizontalSpeed; Vector2 newVelocity = myRigidbody.velocity; newVelocity.x = Mathf.MoveTowards(newVelocity.x, velocityGoal, horizontalAccel * Time.fixedDeltaTime); myRigidbody.velocity = newVelocity; } private void OnEnable() => controls.Player.Enable(); private void OnDisable() => controls.Player.Disable(); private void OnDestroy() => controls.Dispose(); }
关键说明
- 方法1适合需要在输入开始/结束时执行额外逻辑的场景(比如播放移动音效),事件驱动更高效
- 方法2逻辑更直观,接近旧Input Manager的写法,适合简单移动控制需求
- 必须将读取到的输入值(范围[-1,1])乘以
horizontalSpeed,否则移动速度会远低于预期 - 务必在
OnDestroy中调用controls.Dispose(),释放Input System资源避免内存泄漏
内容的提问来源于stack exchange,提问作者Lurosset
相关产品推荐
相关产品推荐

