资讯动态

Unity角色移动全解析:Transform、Rigidbody、CharacterController对比与实战

发布时间:2026/10/5 3:02:19 来源:尧图企业网站定制
1. 动手前先把这三个底层概念理清楚很多刚接触Unity的初学者第一反应是找个角色模型拖进场景然后就开始写移动脚本。这个流程我太熟悉了因为我自己也是从这一步过来的。但问题在于网上的教程五花八门有人用Transform移动有人用Rigidbody还有人用CharacterController每个看上去都能跑你抄下来之后发现自己的角色要么穿墙、要么抖成帕金森、要么原地打转。为什么因为不同的移动方式背后是完全不同的运行机制搞不清楚这些你抄来的代码就只是个没有灵魂的壳。这篇文章要讲的就是在C#脚本里实现角色移动的几种主流方式以及我在真实项目里摸爬滚打得到的经验。我会把每种方案的底层原理说透再配上可以直接抄走的代码最后告诉你什么场景该用哪一种。无论你是做2D平台跳跃、3D俯视角射击还是第三人称RPG这篇文章都能让你少走很多弯路。1.1 坐标系统先搞懂Unity让角色动起来的本质在Unity里任何物体的移动说白了就是修改它的Transform组件的数值。Transform是每个游戏物体GameObject都有的组件它记录了物体在世界中的位置Position、旋转Rotation和缩放Scale。你可以把Unity的场景想象成一个巨大的三维坐标系X轴左右、Y轴上下、Z轴前后。角色要移动本质上就是要改变它在世界坐标World Space或者局部坐标Local Space中的值。世界坐标是以原点为基准的绝对坐标而局部坐标是以父物体为基准的相对坐标。通俗点说你在房间里走了一步世界坐标记录的是你在这个城市中的绝对位置变化而局部坐标记录的是你相对于书桌的位置变化。对于移动逻辑来说我们关心的是两个系统一个是不经过物理引擎的纯坐标变换一个是经过物理引擎的刚体运动。前者直来直去简单可控后者会自动处理碰撞、摩擦和受力真实感更强。理解了这两套系统的差别你才能明白为什么有的移动脚本要在Update里写、有的要写在FixedUpdate里。1.2 物理系统Rigidbody、Collider 和 Update 方法的三角关系Unity的物理模拟是基于Rigidbody刚体来完成的。当一个物体挂上Rigidbody组件后它就纳入了物理引擎的管理范围会受到重力、摩擦力、碰撞力的影响。与此同时你还需要一个Collider碰撞器来定义物体的物理边界比如BoxCollider盒形、SphereCollider球形或CapsuleCollider胶囊形。这里有个初学者经常会掉进去的陷阱如果在Update里直接修改挂有Rigidbody的物体的Transform.position物理引擎下一帧计算碰撞时就会懵掉——它以为物体还在按照自己的模拟规则运动结果你突然篡改了位置最后就会出现穿模、抖动、甚至物体直接飞出去的诡异现象。正确的做法很简单如果你在用物理方式移动角色相关代码请写在FixedUpdate里。这个方法的执行频率是固定的默认每秒50次专门配合物理引擎的计算节奏。而如果你用纯Transform方式移动那就写在Update里因为Update的调用频率和帧率一致画面表现会更顺滑。简单记住一个原则凡涉及刚体一律FixedUpdate只是改位置用Update。1.3 输入系统旧Input与新Input的取舍在C#脚本中移动逻辑的第一步是获取玩家输入。Unity有两种输入系统老牌的是Input.GetAxis和Input.GetKey新的是可编程的输入系统包Input System Package。新手入门阶段用旧Input完全没问题API简单类型提示明确几行代码就能拿到方向值。我自己的建议是先别纠结教程里给什么用什么把核心移动逻辑跑通再说。但你要有心理准备Input System是未来趋势它支持手柄、触屏、多设备映射、UI事件绑定在复杂项目里优势明显。后面我会专门写一节关于输入封装的建议那是真正能提升开发效率和工作幸福感的东西但那是后话。现在先把移动方案的原理讲透这比什么都重要。2. 方案一Transform直接修改位置的移动方案这是最直观、最容易理解的做法也是新手第一次让物体“动起来”时最常用的方式。它的核心就是一个概念在每一帧我给物体的位置加上一个增量时间长了位置自然就变了。2.1 最基础的Transform.Translate写法看下面这段代码它是我见过最精简的移动脚本之一using UnityEngine; public class TransformMove : MonoBehaviour { public float moveSpeed 5f; void Update() { float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 direction new Vector3(horizontal, 0f, vertical).normalized; transform.Translate(direction * moveSpeed * Time.deltaTime); } }这段代码的逻辑很直白Input.GetAxis(Horizontal)读取键盘A/D和方向键的左右输入返回-1到1之间的值GetAxis(Vertical)读取W/S和上下方向键。把两个值拼成三维向量后调用normalized归一化防止斜着走时速度变成根号2倍。最后用Translate把方向乘以速度再乘以Time.deltaTime这样移动速度就变成每秒多少单位而不是每帧多少单位。这里我特别强调一下Time.deltaTime的作用。它表示从上帧到本帧所花费的秒数帧率高时这个值小帧率低时这个值大。如果不乘它在帧率60的环境下角色飞得跟跑车一样帧率20的环境下角色慢得像蜗牛。乘了它之后同样一个速度值在任何帧率下每秒移动的距离都是一样的。凡是持续性的数值变化都要乘Time.deltaTime这是所有C#脚本里最值得养成的习惯。2.2 平滑移动的进阶姿势MoveTowards和Lerp上面的写法在没障碍物的场景里没问题但有个小毛病角色速度是恒定的起停都很生硬。如果你想让角色像现实生活中的人一样起步有加速、停下有减速可以改用Vector3.MoveTowardsusing UnityEngine; public class SmoothTransformMove : MonoBehaviour { public Transform target; public float moveSpeed 3f; void Update() { // 每帧朝目标位置靠近速度固定为 moveSpeed * deltaTime transform.position Vector3.MoveTowards( transform.position, target.position, moveSpeed * Time.deltaTime ); } }MoveTowards的特点是它会以你指定的最大速度向目标点移动到达后自动停在目标位置不会超出。这种特性特别适合寻路、小兵追击、自动门等场合。它的表现更像AI控制而不像玩家手动操作的角色。另一种是Vector3.Lerp线性插值它的写法是这样的Vector3 newPosition Vector3.Lerp(transform.position, targetPosition, 0.1f);Lerp的作用是在两个点之间按比例取一个中间点。第三个参数是0到1之间的数0.1表示“每帧向目标靠近10%”。注意这是个非常迷惑人的陷阱——它不是匀速运动而是前快后慢的指数衰减运动。如果你是第一次用很容易觉得这没毛病啊但真做竞技类游戏时就会发现手感怪怪的。很多新手以为Lerp是匀速其实只有MoveTowards才是匀速。2.3 这套方案适合用在什么场景说了这么多你肯定想问直接改Transform位置的方法到底能不能用于正式项目我的回答是能用但要看场景。如果你在做一款俯视角的休闲游戏、机关门、开关面板、移动平台或者角色本身只是场景中的一颗棋子比如炉石传说的棋子直接用Transform移动是最高效的选择代码简单、易于控制、不需要关心物理系统带来的各种奇怪交互。另外在制作UI动画、技能特效位移、机关触发方面它同样表现优秀。但如果是ARPG、MOBA、动作游戏里玩家真正操控的角色我强烈不建议你用纯Transform移动因为它天生没有碰撞响应。你虽然可能给角色加了Collider但在Update里强行改位置会让碰撞系统没办法准确反应最后结果是角色要么压过障碍物、要么被卡得住。所以当你需要和世界中的其他物体交互时请跳到下一节用刚体方案。3. 方案二Rigidbody物理驱动的移动方案物理驱动的核心是让Unity物理引擎来管理角色的运动过程。你不直接设置位置而是给刚体施加速度或力引擎会处理碰撞、反弹、摩擦等一切细节。这是做3D动作游戏最常用的方案也是我实际项目里用得最多的方式。3.1 用velocity控制速度手感像开遥控车最简单的刚体移动写法是直接改Rigidbody.velocity。这个属性表示刚体的线速度单位是米每秒。你赋值给它的值会覆盖物理引擎原本计算出的速度。看代码using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class RigidbodyMove : MonoBehaviour { public float moveSpeed 8f; private Rigidbody rb; void Start() { rb GetComponentRigidbody(); } void FixedUpdate() { float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 direction new Vector3(horizontal, 0f, vertical).normalized; // 把方向乘以速度得到目标速度向量 Vector3 targetVelocity direction * moveSpeed; // 保留原有y轴速度重力影响只替换水平方向的速度 targetVelocity.y rb.velocity.y; rb.velocity targetVelocity; } }这段代码看着简单但里面藏着一个小技巧赋值速度之前先把原来的y轴速度保存下来再放回去。为什么因为重力每帧都在修改刚体的y轴速度如果你直接把整个velocity覆盖成一个只有水平方向的向量重力效果就消失了角色的跳跃和下坠都会变得极其诡异。保持y轴速度不动只在水平面上动手这是新手最容易漏掉的一行代码。把速度设成每秒8米在默认物理设置下角色的移动非常跟手。但要注意直接改velocity意味着你剥夺了物理引擎对水平速度的累积计算角色会有一种开遥控车的手感上半身没有惯性按下按键瞬间速度拉满松开按键瞬间速度归零。这在很多快节奏游戏里是优点但在模拟类项目里会显得假。3.2 用AddForce推角色手感像推一块冰和直接改速度不同AddForce是给刚体施加力物理引擎会按照牛顿第二定律去计算加速度进而改变速度。这意味着角色有惯性起步慢、加速渐显、松开后还会滑行一段距离。看示例using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class RigidbodyForceMove : MonoBehaviour { public float forceMagnitude 12f; public float damping 5f; // 速度衰减系数 private Rigidbody rb; void Start() { rb GetComponentRigidbody(); rb.drag damping; // 让刚体自带阻力模拟地面的摩擦力 } void FixedUpdate() { float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 direction new Vector3(horizontal, 0f, vertical).normalized; // 沿输入方向施加力 rb.AddForce(direction * forceMagnitude, ForceMode.Acceleration); } }ForceMode.Acceleration表示这个力忽略物体的质量直接产生加速度。如果你用ForceMode.Force那么就取决于物体本身的质量质量大的加速慢、质量小的加速快。实际手感上AddForce方案会有明显的滑动感起步那一瞬间人物像站在冰面上。为了保证可控性我会在Rigidbody的Drag阻力属性上加上一个值来模拟地面摩擦带来的减速效果。Drag值调大一些手感和速度控制的折中才合理。3.3 搭配Collider才能真正和场景碰撞用Rigidbody移动时碰撞器的作用不可忽视。你在Rigidbody方案里使用物理引擎来处理碰撞场景里的墙壁、障碍物只要有Collider角色就会自动被挡住。这也是Rigidbody方案和Transform方案最本质的区别物理引擎会在FixedUpdate里精确模拟碰撞把你的角色从穿透状态中推挤回来。我会在角色身上挂一个CapsuleCollider胶囊碰撞器因为它最接近人体轮廓足够圆润、不容易卡墙角。同时也给场景里的墙壁挂BoxCollider。这样角色撞墙时会被引擎推回来不会穿模。这是整个物理移动方案能成立的关键所在。3.4 刚体方案的两大坑抖抖病和旋转大战刚体方案用久了你会遇到两个比较典型的毛病。第一个是抖动。如果场景里有移动平台角色站在上面或者地面物体发生了形变刚体在每一帧尝试保持静止和引擎碰撞推挤的作用会反复冲突角色就会肉眼可见地“抖动”。这个问题的倍数方案是让角色对移动平台使用Transform.SetParent或者干脆用Rigidbody的关节组件。第二个是旋转冲突。当你想让角色朝移动方向转向时如果你直接修改transform.rotation而这个物体又受物理引擎的其他作用力影响就会产生旋转争夺。解决办法是在使用旋转时使用rb.MoveRotation或者锁死刚体的FreezeRotation选项让它只跟着代码走不参与物理旋转模拟。4. 方案三CharacterController专门给人形角色设计的移动方案CharacterController 是Unity内置的一个专用组件它本质上不是刚体也能被它巨大的性能优势惊艳到。官方对它的定位是用于“人形角色”在复杂地形上的移动控制比如斜坡、楼梯、走廊转角。4.1 CharacterController 到底帮你解决了什么问题用CharacterController写移动脚本最直观的感受是角色有“脚”去踩地面了。它会自动处理重力带来的下压、自动限制不能进入斜坡的大角度倾斜面、自动在台阶高度允许的情况下跨过小台阶。这些能力是Rigidbody方案默认不具备的用刚体你需要自己写一大坨地面检测逻辑而CharacterController内置帮你处理好了。它的实现原理说穿了也不复杂它内部是一个胶囊形的物理检测体每一帧通过Move或SimpleMove方法来检测路径上的碰撞然后贴碰撞面滑动。所以它不会穿透墙体同时又能平滑地爬坡。这天生适合“人形角色”的需求所以它在FPS、TPS、俯视角ARPG里被广泛使用。4.2 写出带重力和地面检测的Controller移动脚本下面这段脚本是我个人最常用的CharacterController移动模板添加了重力、基础的转向逻辑并兼顾了斜坡倾斜方向的动态调整using UnityEngine; [RequireComponent(typeof(CharacterController))] public class CharacterControllerMove : MonoBehaviour { public float moveSpeed 6f; public float gravity -9.81f; public float jumpHeight 1.2f; private CharacterController controller; private Vector3 velocity; private bool isGrounded; void Start() { controller GetComponentCharacterController(); } void Update() { // 地面检测 isGrounded controller.isGrounded; if (isGrounded velocity.y 0f) { velocity.y -2f; // 让角色稳定贴地 } // 获取输入 float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 move transform.right * horizontal transform.forward * vertical; // 这里移动使用Move方法传入方向与速度 controller.Move(move * moveSpeed * Time.deltaTime); // 跳跃判断 if (Input.GetButtonDown(Jump) isGrounded) { velocity.y Mathf.Sqrt(jumpHeight * -2f * gravity); } // 重力累积 velocity.y gravity * Time.deltaTime; controller.Move(velocity * Time.deltaTime); } }细心的读者会发现我在角度朝向里利用了transform.right和transform.forward这意味着角色的移动方向会相对于它当前的朝向这种写法非常适合第三人称操作。如果希望像第一人称那样输入方向始终是世界方向直接改用Vector3控制即可。controller.isGrounded是CharacterController提供的真实地面检测它会监测胶囊底部是否接触可站立层避免你写一堆射线检测代码。跳跃初速度我直接用了Mathf.Sqrt(jumpHeight * -2f * gravity)这是公式推导出来的当你设置跳起高度为1.2米时这个初速度恰好能让角色在重力作用下达到那个最高点。4.3 CharacterController和Rigidbody的本质区别很多人第一次接触CharacterController时会疑惑它不是刚体那它能碰撞吗答案是能但它只做碰撞检测不模拟物理响应。也就是说你的角色推不动场景里的箱子受力不会倒地、不会弹飞表现更靠近“幽灵”而不是“物理实体”。所以凡是需要角色和场景进行物理交互比如踢飞一个花瓶、被爆炸击退的游戏要么混合方案、要么干脆用刚体。CharacterController的好处是稳定、可控、不会瞎崩缺点是“它只会走不会撞”。我实际做项目时的经验是在RPG里用CharacterController加Rigidbody做混合方案角色主体跑在Controller上受击击退、爆炸震开则直接操作刚体让物理引擎接管一小段运动。这样一来平时移动手感稳定特殊事件又有物理反馈两全其美。5. 三种方案横向对比与选型建议学完了三种写法选型就成了下一个绕不开的问题。每种方案没有绝对的好坏只有合不合适的区别。我做一个比较直观的表格方便你参考对比项Transform移动Rigidbody移动CharacterController移动核心组件无Rigidbody ColliderCharacterController是否参与物理模拟否是否碰撞检测能力弱靠碰撞器但易穿模强物理引擎准确处理强胶囊体滑动式碰撞斜坡与台阶处理无需自行实现重力与斜坡逻辑内置支持效果好常见代码位置UpdateFixedUpdateUpdate内部检测可控性极高中等受物理影响高稳定可控适用场景机关移动、UI动画、平台移动ARPG受击、物理交互物、追求真实感的项目FPS/TPS移动、人形角色、稳定碰撞缺点不真实、撞墙易穿透调参成本高、可能抖动无法主动推箱子和物理交互从这张表能清晰看出如果你的项目只需要一个“会移动的方块”用Transform如果你需要“被物理世界影响的有质量物体”用Rigidbody如果你需要“一个像人一样走路和碰撞的角色”首选CharacterController。5.1 我的一般选型逻辑先问需求再选方案每次动手写移动脚本之前我会先问自己三个问题。第一角色会撞到东西吗第二东西会撞角色吗第三移动手感偏“人”还是偏“物体”第三个问题尤其关键。举个实际例子做恐怖游戏里的怪物追踪怪物需要绕过障碍物追玩家但它不需要推动任何箱子也不需要被子弹击退没有受击系统选CharacterController就很合适。但如果做开放世界沙盒游戏角色可以推箱子、捡起物品、被爆炸掀飞那就必须用Rigidbody来保证交互效果。5.2 前期用最简单的方式把核心玩法跑通对于纯新手我的建议是第一版用Transform移动也行因为你好调试、好理解。核心玩法验证通过后再按需要切换方案。我遇到很多同行把大量时间花在纠结移动方案上结果原型跑不起来。先把能动的版本做出来后面再优化这个思路适合所有Unity项目。6. 实战实录一个融合重力、转向、物理交互的通用移动控制器方案各有优劣但在真实项目里我们经常需要组合使用。下面这个例子是我在开发一个俯视角ARPG原型时实际用过的方案它融合了CharacterController、重力、跳跃、鼠标控制转向和受击击退效果。作为最终演示我会把它拆解到可以直接抄走的颗粒度。6.1 需求清单与脚本结构设计在做任何代码之前先列需求。我的需求很简单玩家用WASD控制角色前后左右移动鼠标控制角色面向空格跳跃当角色被怪物攻击时会弹出一小段受击位移。由于涉及受击物理反馈我选择CharacterController刚体混合方案。整个脚本的划分是一个角色移动控制脚本PlayerMotor负责处理平时的移动、跳跃、转向另一个受击反馈脚本PlayerImpactReceiver负责处理瞬时的物理震动。两者通过公共方法或事件通信。下面给出主控制器代码using UnityEngine; [RequireComponent(typeof(CharacterController))] public class PlayerMotor : MonoBehaviour { [Header(移动参数)] public float moveSpeed 5f; public float turnSpeed 540f; // 每秒旋转角度 public float jumpHeight 1.2f; public float gravity -9.81f; [Header(组件引用)] public Camera cam; private CharacterController controller; private Vector3 verticalVelocity; private bool isGrounded; void Start() { controller GetComponentCharacterController(); if (cam null) { cam Camera.main; } } void Update() { HandleGroundCheck(); HandleMove(); HandleJump(); HandleRotation(); } void HandleGroundCheck() { isGrounded controller.isGrounded; if (isGrounded verticalVelocity.y 0f) { verticalVelocity.y -2f; } } void HandleMove() { float horizontal Input.GetAxis(Horizontal); float vertical Input.GetAxis(Vertical); Vector3 moveCamForward cam.transform.forward; Vector3 moveCamRight cam.transform.right; moveCamForward.y 0f; moveCamRight.y 0f; moveCamForward.Normalize(); moveCamRight.Normalize(); Vector3 direction (moveCamForward * vertical moveCamRight * horizontal).normalized; controller.Move(direction * moveSpeed * Time.deltaTime); } void HandleJump() { if (Input.GetButtonDown(Jump) isGrounded) { verticalVelocity.y Mathf.Sqrt(jumpHeight * -2f * gravity); } verticalVelocity.y gravity * Time.deltaTime; controller.Move(verticalVelocity * Time.deltaTime); } void HandleRotation() { Ray ray cam.ScreenPointToRay(Input.mousePosition); Plane groundPlane new Plane(Vector3.up, Vector3.zero); if (groundPlane.Raycast(ray, out float enter)) { Vector3 hitPoint ray.GetPoint(enter); Vector3 direction hitPoint - transform.position; direction.y 0f; if (direction.sqrMagnitude 0.001f) { Quaternion targetRotation Quaternion.LookRotation(direction); transform.rotation Quaternion.RotateTowards( transform.rotation, targetRotation, turnSpeed * Time.deltaTime ); } } } public void ApplyImpact(Vector3 impactForce, float duration) { // 通过协程或者给Rigidbody加力模拟冲击 // 实际项目中可以调用刚体组件实现 } }6.2 代码中的几个关键细节解读这段代码至少隐藏了三个值得学习的细节。第一移动方向不是直接取键盘输入的世界向量而是用相机的前向和右向作为基准这样无论相机朝向哪里按W总是“往屏幕深处走”。这是第三人称相机的标准做法。第二旋转用了Quaternion.RotateTowards而不是瞬间Lerp它让转身速度更接近人的自然动作尤其适合做需要精细操控的动作游戏。第三通过射线与地面平面的交点计算目标朝向这样鼠标点哪里角色就转到哪适配俯视角的瞄准需求。6.3 参数调试的方法论很多新手看到一推public浮点参数就头晕不知道该怎么调。我的调试思路是这样的先把moveSpeed调到8左右感受一下基础速度然后调turnSpeed先设360度每秒再上下浮动看手感接着调jumpHeight从0.8开始每加0.2看一次跳起来的幅度是否合适最后调gravity如果觉得跳跃滞空感太假就把重力调大些。Unity的Inspector面板支持运行时实时修改所以你可以一边运行一边拖参数看到效果就记录下数值。不要靠脑补直接跑起来调才是最高效的。7. 新手常踩的坑与排查技巧写移动脚本最容易出问题的地方往往不是脚本本身而是组件配置和Unity的工作模式。我整理了四个出现频率极高的坑附上排查思路。7.1 角色一按方向键就穿墙是代码问题吗很多人第一个反应是脚本写错了但绝大多数穿墙案例其实是碰撞器配置问题。排查步骤应该是第一确认角色身上有Collider第二确认墙壁有Collider第三确认其中一个物体通常是角色身上有Rigidbody或者你用CharacterController它自带碰撞逻辑第四检查两者是否在同一Layer并且启用了碰撞检测。另外值得一提的是如果角色确实挂了刚体和碰撞器但在Update里直接改transform.position依然可能穿墙原因就是我开头讲到的物理模拟顺序问题——请改用rb.MovePosition或者把代码挪到FixedUpdate。7.2 角色上下楼梯过快导致飞起来CharacterController能自动跨越台阶但遇到太高的台阶或陡坡也会弹飞。这时候可以检查Step Offset台阶偏移值和Slope Limit斜坡限制角这两个参数。Step Offset默认是0.3米如果你的台阶高度接近或略大于这个值角色就会“爬”不上去表现为在原地抖动。解决方法是把Step Offset设到合理范围——不能无限大过大角色容易直接从矮墙顶上踩过去。7.3 角色跑起来身体乱晃、卡在墙角这个情况最常见的原因是多个Collider互相挤压。当你的角色碰撞器和场景里的碰撞器边界出现长时间的接触时Unity会不断尝试把角色推回正确位置结果就是疯狂抖动。我的排查经验是先把角色的CapsuleCollider半径调小一点再把中心的Y轴位置抬高让胶囊体的下端刚好贴合脚底。另外在墙体转角处引入小的“倒角”或使用半圆滑的墙角模型也能有效减少卡墙角的问题。7.4 键盘输入时灵时不灵断触感极强这种情况大概率不是键盘坏了而是输入响应逻辑写到了某些会被跳过的生命周期函数里。比如把移动代码写在OnTriggerStay里触发器不持续触发时就拿不到输入。还有一种是你在Update里拿输入再去操作Rigidbody因为物理世界和逻辑帧不同步角色的响应会有半帧延迟手感就很“肉”。正确的排查顺序是检查输入逻辑是不是在Update里检查物理操作是不是在FixedUpdate里检查两者的数据交换是不是用了临时变量而非直接读输入值。8. 写在最后的一些工程级建议代码能跑起来和能在项目里稳定工作是两码事。下面这些经验是我在做了多个完整Unity项目后才总结出来的分享给走到这一步的你。第一尽量封装你的输入层不要直接在移动逻辑里散落一堆Input.GetAxis。哪怕你现在只做PC端以后要加手柄、要加移动端虚拟摇杆如果输入代码和移动逻辑混在一起改动成本会直线上升。合理做法是定义自己的MoveInput数据结构把所有输入转换成一个统一的向量供移动逻辑调用。第二注意帧率对移动手感的影响。虽然Time.deltaTime消除了大部分帧率差异但角色转动、相机跟随这类持续性操作在帧率剧烈波动时依旧会让人觉得不流畅。可以给相机Follow和角色转向加入平滑阻尼比如Quaternion.Slerp配合固定速度避免帧率抖动带来的劣质感。第三永远不要迷信教程代码。任何移动方案都有其隐含的前提条件——有些教程默认角色朝向等于相机朝向有些默认物理步长恒定这些前提在你的项目里不一定成立。把代码抄过来之后先用最简单的空场景验证基础行为再逐步加入自己的实际逻辑这样出了错也容易定位。最后说一句体己话角色移动是游戏的手感根源也是编程逻辑和游戏设计碰撞最密集的地方。一开始写得难看没关系代码是迭代出来的。每换一次方案你对Unity运行机制的理解就更深一层。希望这篇文章能让你少踩几个坑多享受几次“跑通那一刻”的爽感。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑