Unity新Input System结合PlayerInput实现角色平滑运动方案
实现方案
首先澄清核心认知误区:使用Player Input组件的Unity Events模式绑定输入,和在生命周期函数中做运动平滑完全不冲突。Input Event的设计定位仅为通知输入状态变更、传递输入值,从来没有要求所有输入响应逻辑必须全部写在事件回调中——恰恰相反,直接在输入事件回调中修改刚体参数、做插值计算是错误做法,因为输入事件的触发时机和Unity的帧更新、物理更新步长不统一,非常容易出现运动抖动、手感不一致的问题。
职责划分逻辑
你只需要把代码逻辑按职责拆分即可,原有Player Input的事件绑定完全不需要改动:
- Move、Jump事件绑定的回调方法:仅负责缓存最新的输入状态/输入值,不做任何刚体修改、插值计算
- 运动平滑、速度赋值、跳跃生效、角色翻转的逻辑:统一放在生命周期函数中执行(刚体相关操作优先放
FixedUpdate,避免帧率波动导致物理效果异常) Vector2.SmoothDamp需要逐帧调用才能实现平滑过渡,天然适合放在Update/FixedUpdate中执行
具体代码修改
- 新增平滑相关的配置字段和SmoothDamp所需的持久速度缓存变量
- 把原事件回调中的刚体操作逻辑移除,仅保留输入值缓存
- 在FixedUpdate中完成速度平滑、赋值、跳跃判定逻辑
- 保留Update中的角色翻转判定即可(翻转是纯Transform表现,和物理步长无关)
修改后的完整代码如下:
using UnityEngine; using UnityEngine.InputSystem; public class PlayerMovement : MonoBehaviour { [SerializeField] private Rigidbody2D rb; [SerializeField] private Transform groundCheck; [SerializeField] private LayerMask groundLayer; private const float Speed = 8f; private const float JumpingPower = 16f; private const float GroundedRadius = 0.2f; // 移动平滑时间,可自行调整手感,0.05-0.2f为常见区间 [SerializeField] private float moveSmoothTime = 0.1f; // 缓存原始输入值 private float _rawHorizontalInput; private bool _isFacingRight = true; // SmoothDamp所需的瞬时速度缓存,必须为成员变量,不能设为局部变量 private Vector2 _smoothingVelocity; // 缓存跳跃触发标记 private bool _jumpRequested; private bool _jumpCutRequested; void Update() { // 翻转逻辑为纯表现,放在Update即可 if (!_isFacingRight && _rawHorizontalInput > 0f || _isFacingRight && _rawHorizontalInput < 0f) { Flip(); } } private void FixedUpdate() { // 物理相关逻辑统一放在FixedUpdate,保证步长稳定 Vector2 targetVelocity = new Vector2(_rawHorizontalInput * Speed, rb.velocity.y); // 执行水平方向速度平滑 rb.velocity = Vector2.SmoothDamp(rb.velocity, targetVelocity, ref _smoothingVelocity, moveSmoothTime); // 处理跳跃触发 if (_jumpRequested && IsGrounded()) { rb.velocity = new Vector2(rb.velocity.x, JumpingPower); } // 处理松开跳跃键的短跳逻辑 if (_jumpCutRequested && rb.velocity.y > 0f) { rb.velocity = new Vector2(rb.velocity.x, rb.velocity.y * 0.5f); } // 重置请求标记 _jumpRequested = false; _jumpCutRequested = false; } // 输入事件回调仅做输入缓存,不处理运动逻辑 public void Move(InputAction.CallbackContext context) { _rawHorizontalInput = context.ReadValue<Vector2>().x; } public void Jump(InputAction.CallbackContext context) { if (context.performed) { _jumpRequested = true; } if (context.canceled) { _jumpCutRequested = true; } } private bool IsGrounded() { return Physics2D.OverlapCircle(groundCheck.position, GroundedRadius, groundLayer); } private void Flip() { _isFacingRight = !_isFacingRight; Vector3 localScale = transform.localScale; localScale.x *= -1f; transform.localScale = localScale; } }
配置说明
原有Player Input组件的事件绑定方式完全不需要调整,保持如下配置即可:
额外优化提示
- 如果你想要更"脆"的移动手感,可以把
moveSmoothTime调小;想要更飘、更滑的手感就适当调大数值 - 原代码中跳跃松开时的速度计算逻辑有冗余,修改后的短跳逻辑直接把当前y轴速度乘0.5,是平台跳跃游戏更常用的实现方式,手感更自然
- 不要直接对原始输入值做平滑,要对最终的目标速度做平滑,否则会出现输入延迟感
内容的提问来源于stack exchange,提问作者Matthew Harwood
相关产品推荐
相关产品推荐

