ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity3D角色控制器:跳跃、二段跳与冲刺实现及手感调优

Unity3D角色控制器:跳跃、二段跳与冲刺实现及手感调优 1. 动手设计前先把“跳跃、二段跳、冲刺”拆开看这一条博文是我在Unity3D学习系列里的第二篇主题一句话就能讲完让一个角色能正常跳跃、能二段跳、能冲刺。听起来特别基础对吧但相信我把这三个功能做到“手感舒服”比塞一堆花哨的特效难多了。很多新手做完之后会发现跳是能跳了但要么飘得像太空漫步要么二段跳变成“无限跳”要么冲刺一按角色就穿墙。这些问题几乎全是设计和参数层面的病不是代码写不出来而是没把问题拆清楚。这篇文章适合两类人。第一刚接触Unity3D、想自己写一个角色控制器但不知道从哪下手的初学者第二已经复制过很多角色控制代码、但始终调不出理想手感想搞明白“为什么要这么写”的开发者。我会把正常跳跃、二段跳、冲刺三件事从物理原理、参数推导、代码实现到常见坑位全部走一遍最后还会提供一份可以直接抄作业的完整脚本。如果你跟着做完了应该能收获一个跑起来干净利落、手感扎实的基础角色控制器。在正式开始之前先把这三个能力放到游戏设计的语境里看。正常跳跃是“角色从地面起跳、上升、滞空、下落、落地”的完整循环二段跳是在空中失去地面支持后再打破一次下落趋势本质上是给玩家多一次操作窗口冲刺则是短时间内的爆发移动目的是快速位移、躲避攻击或跨过间隙。三者组合在一起构成了绝大多数横版动作游戏、平台跳跃游戏的核心移动体验。所以别看功能小它们的好坏直接决定玩家对一个游戏“手感”的第一印象。2. 框架选型与物理组件准备2.1 为什么选Rigidbody物理方案而不是CharacterController写角色移动时Unity3D社区里最常见的两条路是Rigidbody刚体方案和CharacterController方案。我在这个项目里选的是Rigidbody原因不是哪个更“高级”而是要看你的游戏需要什么手感。CharacterController自带的Move方法适合轴对齐的位移逻辑它不参与物理碰撞模拟没有速度、质量、惯性的概念。用它做跳跃时重力、落地检测、斜坡滑行全都要自己手算写起来其实也不轻松。而且一旦角色要被敌人的攻击弹飞、被爆炸冲击波推开或者要踩在另一个刚体物体上跟着移动CharacterController就会变得非常别扭因为它不会自然地跟物理世界产生力的交互。Rigidbody方案正好相反。角色拥有真实的质量、速度、碰撞反馈我只需要在FixedUpdate里设置linearVelocity旧版Unity写velocity物理引擎就会自动处理与外界的碰撞和摩擦。跳跃本质上是给刚体一个向上的瞬时速度冲刺本质上是短时间内把水平速度推高这些操作在刚体模型下写起来非常顺手。代价是刚体方案需要你理解“速度是状态不是瞬时命令”这个思维别在Update里直接改Transform.position否则物理抖动会把你折磨疯。这个后面详细说。2.2 搭建最小场景刚体、碰撞体、物理材质一个都不能少项目搭建很简单但几个关键点踩错了我保证你后面全是坑。首先是角色物体本身我建议用胶囊体或立方体起步不要一上来就挂一整套动画模型那样出了问题你根本分不清是代码问题还是模型坐标轴问题。给角色添加Rigidbody之后有四个参数必须处理干净。第一把Constraints里的Freeze Rotation X、Y、Z全部勾上否则角色跳起来撞到斜坡就翻滚物理引擎会给你表演一段原地旋转。第二Interpolate建议设为Interpolate这能让物理插值更平滑尤其是低帧率或Timestep不稳定的时候视觉上会顺滑很多。第三Collision Detection默认的Discrete在大速度冲刺时容易发生穿透建议至少设为Continuous如果你的冲刺速度非常夸张后续还要考虑调整物理时间步进。第四物理材质不要用默认的新建一个Physics Material把Friction设为0、Bounciness设为0。很多人忽略这个结果角色在地面上像踩了滚轮或者落地后不自觉地弹一下罪魁祸首全是它。地面的搭建就简单了一个Plane或者拉伸过的Cube挂上BoxCollider材质问题同上。我习惯把所有地板、平台放到一个单独的“Ground”图层里这样之后做射线检测时可以只检测地面不会出现角色踩在敌人头上也能跳起来的尴尬情况。2.3 先定坐标系与输入习惯别写到一半返工这是一个很容易被忽略的前置问题。你需要先确认这个项目是2D还是3D、视角是横版还是俯视因为输入和速度向量的写法完全不一样。我这个项目按3D场景来写水平移动用两个方向轴组合成三维向量跳跃只影响Y轴。横版游戏通常只需要X轴水平速度和Y轴跳跃速度俯视游戏则可能完全不需要跳跃。无论哪种情况我都建议先把输入和物理分离Update里只负责读取Input并记录状态FixedUpdate里真正去改刚体速度。原因很简单Unity3D的物理计算是固定时间步长而Update的调用频率不稳定如果你在Update里读取Input并立刻改velocity就会导致跳跃力度时大时小手感极其不稳定。我见过太多新手在这里翻车按一下跳跃键角色跳了两次排查半天发现是Update和FixedUpdate同时在处理同一个输入。3. 正常跳跃的实现与手感参数推导3.1 重力、跳跃高度、跳跃时间三者之间的数学关系你打开Unity3D的新场景时默认重力是-9.81米每秒平方这个数值方向是对的但直接拿来用你会发现角色跳得非常低、非常快完全没有动作游戏那种“飘起来再砸下去”的过程。原因很简单真实重力加速度是地球的而游戏角色需要的不是真实是“好看且可控”。做跳跃之前先明确两个设计目标你想让角色跳多高你希望他从起跳到最高点需要多少时间。这两个值定下来重力就可以反推。假设跳跃高度为h上升时间为t_up最高点速度为0那么初速度v0等于g乘以t_up而高度公式h等于0.5倍g乘以t_up的平方。整理一下g 2h / t_up²。举个例子我想要角色跳2米高上升时间0.3秒那么g 2 * 2 / 0.09算出来约等于44.4米每秒平方。这个数值比默认重力大很多听起来吓人但实际手感反而是对的——起跳速度足够快跳得足够高下落也干脆利落。如果你照搬默认重力9.81跳2米需要大约0.64秒的上升时间整个节奏会拖泥带水玩家会明显觉得角色“迟钝”。所以在代码里我不建议直接调Rigidbody的gravityScale这种隐藏参数来模拟手感而是在跳跃瞬间重新计算初速度。你只要在Inspector里开放jumpHeight和jumpTime两个参数物理上需要多少初速度由代码算出来。初速度公式是v0 Mathf.Sqrt(2 * g * jumpHeight)g在这里用当前物理重力的绝对值这样外部改重力也能联动。3.2 起跳瞬间给速度而不是持续给力很多Unity3D初学者做跳跃时会用AddForce然后发现角色跳得很“肉”怎么调都像在拉橡皮筋。原因在于AddForce是持续累积力物理引擎会在接下来的好几个物理帧里把这个力逐步转换成速度起跳过程被拉长了手感自然钝。正确做法是在起跳那一帧直接把Y轴速度设置成需要的初速度其他轴保持原来的水平速度不变。比如当前水平速度为每秒8米垂直速度为每秒6米起跳后就变成水平8、垂直6的组合这样角色既不会一边跳一边突然停住也不会因为AddForce把水平速度一起带跑偏。实现上就是一行代码rb.linearVelocity new Vector3(rb.linearVelocity.x, jumpVelocity, rb.linearVelocity.z)。注意这里一定要保留rb.linearVelocity.x和z而不是让它归零否则空中转向会变得非常僵硬。3.3 地面检测为什么不能省跳跃的核心前提是“站在地面上”所以地面检测是一切跳跃逻辑的基石。最常见的做法是用Raycast从角色底部向下发射一条短射线检测是否碰到地面。射线的长度很关键太短了角色落地后第一帧没被识别为着地导致跳跃延迟太长了角色离地还有半个身位就能跳手感会虚。我实测下来射线起点放在物体世界坐标向上偏移一点点长度设置为“物体中心到脚底的距离再加0.05米左右的缓冲”最稳定。如果碰撞体是Unity3D默认胶囊体物体原点一般在碰撞体中心假设胶囊体高度是1.8那从中心到底部的距离是0.9加上0.15的缓冲射线长度大约1.05。如果你的角色原点在脚底那射线起点直接从Transform.position开始长度给0.1就够了。这句代码里我建议加上Debug.DrawRay在Scene视图里就能直接看到检测线排查问题会方便非常多。3.4 用射线探测代替OnCollisionStay的原因有人会说我用OnCollisionStay判断角色是否着地不就行了吗确实可以但坑在于OnCollisionStay的触发时机跟物理步进绑定而且碰撞体之间只要还在接触就会持续触发这意味着你很难判断角色是“稳定站在地面上”还是“正在被斜坡边缘蹭着”。如果这个状态被误判成着地二段跳就会变得时灵时不灵。用一层薄薄的射线检测最靠谱因为我们要的并不是物理碰撞信息而是“角色底部附近有没有可供站立的地面”。一次Raycast就是一次瞬时探测结果干净、可控、不依赖碰撞回调顺序。唯一要注意的是射线检测默认会命中角色自己的碰撞体所以起点要稍微抬到碰撞体内部偏上的位置或者通过LayerMask过滤掉角色层否则射线一发射就被自己挡住了永远检测不到地面。4. 二段跳的完整实现与防Bug设计4.1 一个计数器管好所有跳跃次数二段跳的核心逻辑很简单以跳跃次数为判断依据在地面时重置次数在空中时消耗额外次数。最难的不是写这个逻辑而是防止各种角落Bug把好好的“二段跳”变成“无限跳”。我用一个int类型的jumpCount记录当前已经跳了几次。初始值为0角色落地时重置为0执行一次跳跃就自增1。每次尝试跳跃前先判断jumpCount是否已经大于等于maxJumpCount如果超过了就什么都不做。设maxJumpCount为2就正好是普通跳加二段跳以后想加三段跳、四段跳只需要把这个数改大逻辑完全不用动。这段代码真正容易出问题的地方在“落地重置”。我见过很多人在Update里直接写if(rb.linearVelocity.y 0) jumpCount0这几乎不可靠。因为角色从空中落地时速度从负值突变成地面约束后的0这个突变发生的时间点非常短Update根本捕捉不到。正确做法是依赖我们之前写的地面检测方法只在IsGrounded()返回true的那一帧重置jumpCount。4.2 二段跳的力度设计一段跳的“后续”而不是“重来”二段跳的初速度怎么取是个值得多说两句的细节。如果两次跳跃都用完全相同的初速度角色会跳得非常高因为二段跳本身是在一段跳上升或下落的基础上再次注入向上的速度叠加之后总高度会远超设计值。但如果二段跳力度设得太小玩家在空中按了等于没按挫败感很重。我在项目里给了secondJumpMultiplier这个可调参数默认值为0.9也就是说二段跳的初速度是一段跳的90%。这个值在全高度2米的设定下实测叠加跳大约能比一段跳多出50厘米左右的额外高度既足够跨过一个普通平台又不会让角色一个二段跳直接飞到屏幕顶。如果你希望二段跳更像一个“挣脱下落”的动作可以把这段力的方向调整为相对当前速度的增量而不是直接覆盖Y轴速度。两种方案各有适用场景前者适合平台跳跃游戏后者适合动作格斗游戏项目需要哪种建议在动手前先想清楚。4.3 上跃速度和下落速度怎么区分Implementing二段跳时会遇到一个很微妙的问题角色在空中上升的时候按了二段跳和正在下落的时候按了二段跳表现应该一样吗从物理感觉上说上升过程中再跳一次角色会获得“二次加速”视觉上有明显的推力感下落过程中跳则是一次救命的拉高。很多街机风格的动作游戏会故意把两种情况做成不同力度上升中二段跳给较少额外速度下落中二段跳给更多额外速度。但在基础控制器里统一覆盖Y轴初速度是最稳妥也最好调的做法因为你能用一个values就预测跳跃高度。如果你追求更细腻的手感可以加一个单独的extraUpForce字段在角色Y轴速度小于0时追加否则只是轻微修正。这个属于手感打磨阶段的内容先把基础版本跑通再说。4.4 空中状态下的输入保护即使代码逻辑正确也还有一个高频Bug等着你玩家在连续帧里狂按跳跃键系统可能在同一瞬间处理了好几次跳跃指令导致二段跳刚触发立刻又被第三段跳跃命令顶掉。而且这个问题在低帧率设备上更明显因为键盘扫描间隔大于物理更新频率Unity3D会把同一个按键按下事件在多个物理帧里读出来。解决办法是在跳跃执行成功后清空跳跃输入标记并且在下次落地前忽略掉新的跳跃输入。具体体现在代码里就是DoJump方法末尾把lastJumpPressTime设成一个负数让跳跃缓冲计时彻底失效。这样做还有一个附带的好处玩家如果想一次性按两下触发二段跳系统会准确识别出两次独立按击而不是把一次按下读成两个跳跃事件。5. 冲刺功能的三种实现方案与参数匹配5.1 冲刺的选型直接改速度还是用CoRoutine协程冲刺这个功能实现起来就三条路。第一条是直接用协程在冲刺时间窗口内每帧把角色的水平速度设成指定冲刺速度时间结束后恢复普通移动控制。第二条是用一个计时器变量在Update或FixedUpdate里累加时间超过冲刺时长后退出冲刺状态。第三条是物理力方案用AddForce推一把然后让摩擦自然减速。我强烈建议用协程原因只有一个代码可读性最高状态切换最清晰。计时器方案写起来其实也差不多但一旦后面要加冲刺无敌帧、冲刺后摇、地面冲刺和空中冲刺的区分用一堆if判断维护计时器会非常痛苦。协程则可以把“开始冲刺-冲刺中-冲刺结束”这个时序完整地凝固在一个方法里后续加特效、加声音都只是往这个流程里插入代码。5.2 冲刺参数三维度冲刺速度、冲刺时长、冷却时间冲刺的参数设计直接决定了手感。我在脚本里暴露了三个公开字段dashSpeed、dashDuration、dashCooldown。dashSpeed控制冲刺时的水平速度比普通移动速度高两倍以上才有明显的冲刺感普通移动8那冲刺至少16起步否则玩家根本感觉不出差别。dashDuration控制冲刺窗口时长0.15到0.2秒在多数游戏里都是甜蜜点太短像瞬移太长又会让角色像滑冰。dashCooldown则管冷却0.5秒左右适合连续冲刺感很强的游戏1秒以上则更强调“冲刺是个关键资源”。有三个参数配合起来需要注意的问题冲刺期间角色是否保留垂直方向速度我的答案是保留。因为空中冲刺是很多动作游戏保留玩法如果冲刺时把Y轴速度一并覆盖成0角色会在空中瞬间变成“冻结”状态落地节奏全乱。正确做法是每一帧只覆盖horizontal轴方向的速度Y轴保持rb.linearVelocity.y不变。这样地面冲刺和空中冲刺用同一套逻辑就能跑得很自然。5.3 冲刺方向移动输入、面朝方向、还是最后移动方向冲刺往哪个方向去看起来是小事但能直接毁掉手感。如果玩家在冲刺前随便按了一下方向键结果角色就朝那个方向冲出去了很容易误判。我的方案是三级优先级先读当前移动输入方向如果输入方向不为零朝输入方向冲刺如果输入方向为零朝角色当前的面朝方向冲刺如果前面两者都没有向右冲。原因是玩家按下冲刺键时天然希望角色冲向自己正在移动的方向而不是僵硬地冲向角色面向方向。这个逻辑和移动代码放在一起会有个很关键的问题移动方向向量必须先归一化否则斜向冲刺时速度会变快。你用new Vector3(h, 0, v)得到的方向当h和v同时为1时向量长度是sqrt(2)约1.41设置速度后角色斜向移动速度会比水平移动快41%。所以无论普通移动还是冲刺方向都要先调用normalized处理。6. 完整代码演示与逐行解读6.1 脚本结构总览下面这份代码我把前面所有方案全部整合在一起了。结构上分五个板块移动参数、跳跃参数、冲刺参数、手感调优参数以及核心逻辑。手感调优参数里包含了土狼时间、跳跃缓冲、跳跃切断三个进阶功能这三个概念在后面的调试部分会详细解释代码里先把接口预留好。代码的总体流程是Update只负责读取输入并记录按键时间FixedUpdate里做地面检测、水平移动和跳跃判断。跳跃执行时会根据当前jumpCount区分普通跳和二段跳冲刺用独立协程管理代码里直接加注释。Unity3D老版本用户注意新版Rigidbody把velocity改成了linearVelocity如果你用的Unity版interface是velocity把代码里所有linearVelocity替换回去就行。6.2 完整PlayerController.cs源码using System.Collections; using UnityEngine; public class PlayerController : MonoBehaviour { [Header(移动参数)] [SerializeField] private float moveSpeed 8f; [SerializeField] private float airControl 0.3f; [Header(跳跃参数)] [SerializeField] private float jumpHeight 2f; [SerializeField] private float jumpTime 0.3f; [SerializeField] private int maxJumpCount 2; [SerializeField] private float secondJumpMultiplier 0.9f; [Header(冲刺参数)] [SerializeField] private float dashSpeed 20f; [SerializeField] private float dashDuration 0.15f; [SerializeField] private float dashCooldown 0.5f; [Header(手感调优)] [SerializeField] private float coyoteTime 0.1f; [SerializeField] private float jumpBufferTime 0.1f; [SerializeField] private float jumpCutMultiplier 0.5f; private Rigidbody rb; private int jumpCount 0; private float lastGroundedTime -1f; private float lastJumpPressTime -1f; private float lastDashTime -100f; private bool isDashing false; private void Awake() { rb GetComponentRigidbody(); } private void Update() { // Update中只读取输入并记录时间不直接改物理参数 if (Input.GetButtonDown(Jump)) { lastJumpPressTime Time.time; } if (Input.GetKeyDown(KeyCode.LeftShift)) { TryDash(); } // 跳跃切断松开跳跃键时空中上升速度立即衰减 if (Input.GetButtonUp(Jump) rb.linearVelocity.y 0) { rb.linearVelocity new Vector3( rb.linearVelocity.x, rb.linearVelocity.y * jumpCutMultiplier, rb.linearVelocity.z ); } } private void FixedUpdate() { if (IsGrounded()) { lastGroundedTime Time.time; jumpCount 0; } float h Input.GetAxisRaw(Horizontal); float v Input.GetAxisRaw(Vertical); Vector3 inputDir new Vector3(h, 0f, v).normalized; if (!isDashing) { Move(inputDir); } if (Time.time - lastJumpPressTime jumpBufferTime) { TryJump(); } } private void Move(Vector3 dir) { Vector3 targetVel dir * moveSpeed; Vector3 currentVel rb.linearVelocity; if (IsGrounded()) { // 在地面时水平速度直接对准目标值 rb.linearVelocity new Vector3(targetVel.x, currentVel.y, targetVel.z); } else { // 空中控制力弱一些防止角色在空中像溜冰一样乱飘 rb.linearVelocity new Vector3( Mathf.Lerp(currentVel.x, targetVel.x, airControl), currentVel.y, Mathf.Lerp(currentVel.z, targetVel.z, airControl) ); } } private void TryJump() { if (jumpCount maxJumpCount) return; bool canFirstJump jumpCount 0 (IsGrounded() || Time.time - lastGroundedTime coyoteTime); bool canSecondJump jumpCount 1 !IsGrounded(); if (canFirstJump || canSecondJump) { DoJump(); } } private void DoJump() { jumpCount; float gravity Physics.gravity.magnitude; float jumpVelocity Mathf.Sqrt(2f * gravity * jumpHeight); if (jumpCount 2) { jumpVelocity * secondJumpMultiplier; } rb.linearVelocity new Vector3(rb.linearVelocity.x, jumpVelocity, rb.linearVelocity.z); // 清空跳跃缓冲防止一个按键被物理引擎重复读取 lastJumpPressTime -100f; } private bool IsGrounded() { float extraHeight 0.1f; Vector3 origin transform.position Vector3.up * extraHeight; float distance extraHeight 0.05f; bool grounded Physics.Raycast(origin, Vector3.down, distance); Debug.DrawRay(origin, Vector3.down * distance, grounded ? Color.green : Color.red); return grounded; } private void TryDash() { if (isDashing) return; if (Time.time - lastDashTime dashCooldown) return; Vector3 dir new Vector3(Input.GetAxisRaw(Horizontal), 0f, Input.GetAxisRaw(Vertical)).normalized; if (dir Vector3.zero) { dir transform.forward; } if (dir Vector3.zero) { dir Vector3.right; } StartCoroutine(DashRoutine(dir)); } private IEnumerator DashRoutine(Vector3 dir) { isDashing true; lastDashTime Time.time; float elapsed 0f; while (elapsed dashDuration) { rb.linearVelocity new Vector3( dir.x * dashSpeed, rb.linearVelocity.y, dir.z * dashSpeed ); elapsed Time.deltaTime; yield return null; } isDashing false; } }6.3 Inspector挂载参数与运行验证脚本准备好之后在Unity3D里操作就很快了。创建一个胶囊体当作角色添加Rigidbody和CapsuleCollider然后把PlayerController挂上去。Rigidbody的Constraints勾选冻结旋转Interpolate设为InterpolateCollision Detection设为Continuous。地面创建一个带BoxCollider的Plane角色放在地面上方按下Play键测试。如果你完全用默认参数跳起来应该已经比较跟手了。想验证二段跳就在空中再按一次跳跃键想验证冲刺按Shift键。我建议在Inspector里把jumpHeight改大到5再改小到1直观感受一下参数变化对跳跃弧线的影响这比读任何文档都更能建立直觉。还有一个小技巧把IsGrounded里的Debug.DrawRay打开Scene视图下就能实时看到绿色或红色的检测线遇到“跳不起来”的问题时先看这条线是不是一直红色就知道是地面检测失败还是跳跃逻辑出错了。7. 常见故障排查与手感调优实战7.1 故障排查速查表这几个问题是我在实际使用和帮别人调试时遇到次数最多的整理成表格方便你对着查。现象常见原因处理方案角色完全跳不起来地面检测失败射线一直没碰到地面检查射线起点是否在碰撞体内部调大distance用Debug.DrawRay观察按一下跳跃键跳了两次Update和FixedUpdate同时处理了输入或者跳跃缓冲被重复触发确保DoJump结束时清空lastJumpPressTime并且输入读取只放在一个地方二段跳变成了无限跳jumpCount没有在落地时正确重置IsGrounded误判检查地面检测的raycast是否检测到角色自身碰撞体尝试用LayerMask过滤角色跳跃后在空中太“漂”空中移动控制力过高或者重力太小降低airControl到0.1-0.3或者根据公式重新计算跳跃初速度冲刺时角色穿墙冲刺速度过高默认碰撞检测不够精准Rigidbody的Collision Detection改为Continuous或降低dashSpeed冲刺结束瞬间角色卡顿协程结束后速度从冲刺速度突变为普通速度在协程末尾加一个轻量插值把速度过渡到普通目标速度或接受这个风格化的突停角色在斜坡上跳不起来射线检测方向垂直向下斜坡上距离变远加大检测距离或改用小球OverlapSphere检测配合斜坡角度判断地面二段跳时按左右键完全无效空中控制力设为0给airControl一个0.2以上的值空中转向才有效果7.2 进阶手感三件套土狼时间、跳跃缓冲、跳跃切断基础功能跑通之后如果你想打磨手感几乎一定会用到这三个技巧。它们不是Unity3D特有概念而是横版动作游戏行业里沉淀出来的通用做法。土狼时间Coyote Time的意思是角色离开平台边缘后的极短时间内系统仍然允许他执行跳跃。这个机制看起来违反物理但它极大缓解了玩家“明明站在边缘跳了却没反应”的挫败感。实现方式就是我们代码里的lastGroundedTime每次地面检测成功就记录当前时间跳跃判断时允许“离开地面不超过0.1秒”的跳跃。跳跃缓冲Jump Buffer解决的是另一个问题玩家在落地前提前按了跳跃键但在那帧角色还没着地系统如果忽略这个输入玩家落地后还要再按一次手感上会觉得指令丢了。通过在Update里记录按键时间并且在FixedUpdate中允许0.1秒内的按键在之后触发跳跃玩家就会觉得角色“听话”多了。我给的jumpBufferTime默认值就是0.1秒你可以试到0.15再大就容易让玩家觉得系统自作主张。跳跃切断Jump Cut是很多动作游戏手感好坏的真正分水岭。它要求在玩家松开跳跃键时如果角色还在上升阶段立刻把Y轴速度乘一个小于1的系数比如0.5让角色快速落下。这样玩家可以通过“轻点跳跃键”跳出低矮的小跳通过“按住跳跃键”跳出完整的高跳。代码里已经实现了这个功能当你把jumpCutMultiplier调到0.5左右再去对比按住和轻点跳跃的差异你马上能体会到一个简单的乘法逻辑能带来多大的操作空间提升。7.3 物理与性能细节FixedUpdate、Timestep、插值的配合最后聊几个容易被忽视的物理细节。很多人在调跳跃手感时改的是Rigidbody的gravityScale、物理材质摩擦这些参数改了半天发现没效果其实问题经常出在项目配置里。Project Settings里的Fixed Timestep默认是0.02秒也就是物理更新频率50赫兹。这个数值是全局的直接影响跳跃和碰撞的稳定性。如果你发现角色高速运动时抖动明显可以尝试把Fixed Timestep降到0.0125甚至0.01代价是物理计算更频繁CPU消耗上升。在低端手机上这个取舍必须更谨慎。另外注意不要在Update里设计刚体的position、rotation或velocity因为物理引擎在固定时间步长里做插值你手动改会跟它的内部状态冲突导致画面抖动。所有物理参数的修改都要放在FixedUpdate或协程里。还有一个老生常谈但必须提到的每次跳跃或冲刺时如果角色形态变化很大建议在关键帧调用Rigidbody的ResetInterpolation或重新设置碰撞体位置否则高速移动下碰撞体可能卡在墙边沿。尤其是你后续给角色挂上动画模型时动画根骨骼和物理Playback的配合又是一个大坑这个系列后面我慢慢展开。我个人在实际操作中的习惯是先把基础跳跃调到“自己不停按都不会烦”的状态再去加二段跳和冲刺。你可以把一个空物体放在场景里当目标高度标记用前文的公式反推jumpHeight然后一边调一边跳。别小看这个笨办法它比任何数值表都直观。跳跃功能从来不是能不能跳的问题而是跳得舒不舒服的问题。把这段代码跑通之后你会发现后续加武器、加状态机、加敌人碰撞都是在这个基础上做加法地基打得稳上层才能盖高。
返回列表