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

Unity新Input System结合PlayerInput实现角色平滑运动方案

实现方案

首先澄清核心认知误区:使用Player Input组件的Unity Events模式绑定输入,和在生命周期函数中做运动平滑完全不冲突。Input Event的设计定位仅为通知输入状态变更、传递输入值,从来没有要求所有输入响应逻辑必须全部写在事件回调中——恰恰相反,直接在输入事件回调中修改刚体参数、做插值计算是错误做法,因为输入事件的触发时机和Unity的帧更新、物理更新步长不统一,非常容易出现运动抖动、手感不一致的问题。

职责划分逻辑

你只需要把代码逻辑按职责拆分即可,原有Player Input的事件绑定完全不需要改动:

  • Move、Jump事件绑定的回调方法:仅负责缓存最新的输入状态/输入值,不做任何刚体修改、插值计算
  • 运动平滑、速度赋值、跳跃生效、角色翻转的逻辑:统一放在生命周期函数中执行(刚体相关操作优先放FixedUpdate,避免帧率波动导致物理效果异常)
  • Vector2.SmoothDamp需要逐帧调用才能实现平滑过渡,天然适合放在Update/FixedUpdate中执行

具体代码修改

  1. 新增平滑相关的配置字段和SmoothDamp所需的持久速度缓存变量
  2. 把原事件回调中的刚体操作逻辑移除,仅保留输入值缓存
  3. 在FixedUpdate中完成速度平滑、赋值、跳跃判定逻辑
  4. 保留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组件的事件绑定方式完全不需要调整,保持如下配置即可:
Player Input组件事件绑定界面

额外优化提示

  • 如果你想要更"脆"的移动手感,可以把moveSmoothTime调小;想要更飘、更滑的手感就适当调大数值
  • 原代码中跳跃松开时的速度计算逻辑有冗余,修改后的短跳逻辑直接把当前y轴速度乘0.5,是平台跳跃游戏更常用的实现方式,手感更自然
  • 不要直接对原始输入值做平滑,要对最终的目标速度做平滑,否则会出现输入延迟感

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:31:08