ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Unity飞行动作游戏开发:从Rigidbody控制到战斗反馈与工程化

2026/9/15 14:58:23 拓冰建站 浏览量
Unity飞行动作游戏开发:从Rigidbody控制到战斗反馈与工程化 简介超级英雄飞行题材的3D动作游戏Unity项目源码基于C#开发支持Unity 2022.1.6f1及以上版本。项目围绕开放城市环境中的飞行、跳跃、拳击、踢击与射击等操作展开玩家需完成不同层级的救援与治安任务适合希望学习Unity角色控制、动作状态机、任务系统及移动端游戏架构的中高级开发者参考。压缩包共2000个文件包括600余个asset资源、30余个C#脚本、14个prefab预制体、9个shader着色器、27个wav音效以及多张png贴图另有aar/jar等安卓依赖库用于广告与基础服务接入整体约60.28MB。资源附带AppLovin等广告SDK相关配置可直接观察商业化Unity项目的工程组织方式也便于在此基础上扩展玩法或更换表现层。目前已有124人学习下载适合作为飞行模拟与超级英雄题材的完整项目范本。1. Superpower Flying Hero 在解决3D飞行动作的什么问题Superpower Flying Hero 是 Unity 上一套典型的“超级英雄飞行 3D 动作”源码工程C# 脚本贯穿了角色控制、战斗逻辑和表现层的主链路。把标题拆开看superpower 对应技能系统和数值设计flying 决定俯仰、滚转和六自由度移动hero 则落在第三人称视角、打击感和受击反馈上。三者缺一个飞行就只是“会跳的普通动作游戏”。先明确一个前提这里不预设你拿到了某个具体版本号的完整工程只顺着这条技术主线讲一套可复现的落地方案——飞行动作游戏和地面动作游戏在物理选型、命中判定、相机平滑和构建发布上各自差在哪里参数怎么设、C# 代码怎么写、坑在哪里。适合正在做 Unity 飞行原型或准备把地面 Demo 改成空战玩法的开发者直接参照。2. Superpower Flying Hero 的飞行控制实现Rigidbody、推力与相机姿态2.1 从 CharacterController 到 Rigidbody 的选型差异飞行角色第一件事是放弃地面动作游戏常用的 CharacterController。CharacterController.Move 按帧驱动控制器滑动自带碰撞响应但本质是一个没有速度积分、没有外力堆叠的胶囊体。空中英雄需要的是持续受推力影响、能被打飞硬直、能在惯性中保留速度方向同时又可以快速转向改变运动趋势这些恰好都在 Rigidbody 的能力边界内。常见做法是给英雄挂 Rigidbody把 useGravity 设为 false然后在 FixedUpdate 里直接改写 velocity 或按需 AddForce。这里有一个明确的分水岭追求物理可信度用 AddForce Drag追求操作响应直接写 velocity。飞行动作游戏几乎都选后者原因是手柄摇杆推多少就要有多少响应物理力累计在帧间有可见滞后玩家会感觉“摇杆灌了水”。“Superpower Flying Hero 的手感立体感很大程度就看这句 velocity 写入是否干净。Rigidbody 上还要处理约束把 rotation 的 X 轴和 Z 轴冻结掉只允许绕 Y 轴旋转防止飞行中被击中时角色在空中无规则翻转。飞行时的俯仰和侧倾要用代码叠加在旋转上而不是依赖物理碰撞产生否则一次撞到敌人就会导致视角天旋地转。2.2 最小可用的飞行控制 C# 实现下面这段 C# 脚本是飞行动作游戏最常见的控制骨架包含输入采样、加速度插值、垂直升降、侧倾姿态和高速冲刺可以直接放到 Unity 里跑起来再逐项调参数using UnityEngine; [RequireComponent(typeof(Rigidbody))] public class FlyingHeroController : MonoBehaviour { [Header(飞行参数)] public float maxSpeed 18f; // 水平最大飞行速度 public float acceleration 12f; // 水平加速度 public float verticalSpeed 9f; // 垂直起降速度 public float boostMultiplier 2f; // 冲刺倍率 public float bankAngle 25f; // 滚转倾斜角 [Header(相机)] public Transform cameraPivot; private Rigidbody rb; private Vector3 currentVel; void Awake() { rb GetComponentRigidbody(); rb.useGravity false; rb.interpolation RigidbodyInterpolation.Interpolate; rb.collisionDetectionMode CollisionDetectionMode.Continuous; } void FixedUpdate() { // 输入采样左手柄摇杆与跳跃键垂直升降 float h Input.GetAxis(Horizontal); float v Input.GetAxis(Vertical); float up Input.GetAxis(Jump); Vector3 camForward cameraPivot.forward; Vector3 camRight cameraPivot.right; camForward.y 0f; camRight.y 0f; camForward.Normalize(); camRight.Normalize(); // 期望速度方向以相机为基准大小来自摇杆 float speedMul Input.GetKey(KeyCode.LeftShift) ? boostMultiplier : 1f; Vector3 desiredVel (camForward * v camRight * h) * maxSpeed * speedMul; desiredVel.y up * verticalSpeed; // 水平和垂直分别按加速度向目标收敛 currentVel.x Mathf.Lerp(currentVel.x, desiredVel.x, Time.fixedDeltaTime * acceleration); currentVel.z Mathf.Lerp(currentVel.z, desiredVel.z, Time.fixedDeltaTime * acceleration); currentVel.y Mathf.Lerp(currentVel.y, desiredVel.y, Time.fixedDeltaTime * verticalSpeed); rb.velocity currentVel; // 姿态朝速度方向看再叠加左右侧倾 float bankTarget -h * bankAngle; Quaternion targetRot Quaternion.LookRotation(rb.velocity.normalized, Vector3.up); targetRot * Quaternion.Euler(0f, 0f, bankTarget); rb.rotation Quaternion.Slerp(rb.rotation, targetRot, Time.fixedDeltaTime * 5f); } }代码逻辑说明水平方向用 Mathf.Lerp 向期望速度收敛而不是直接赋值目的是让角色从静止到满速有加速过程Lerp 的系数用 Time.fixedDeltaTime 乘以 acceleration保证 50Hz 物理帧率下行为一致。垂直方向把 Jump 轴映射到 y 速度按住上升、松开由外部重力或下压逻辑接管。姿态部分用速度方向做 LookRotation再乘一个绕本地 Z 轴的侧倾角转弯时角色会像飞机一样压坡视觉反馈比平移感强得多。2.3 关键参数的可调窗口与相机跟随C# 数字和手感是直接绑定的。maxSpeed 决定“快不快”acceleration 决定“跟不跟手”。实际工程里的合理范围大致如下参数建议范围表现特征maxSpeed12–20低于 10 像跑步高于 25 碰撞检测压力陡增acceleration8–15低于 6 漂移感明显高于 20 几乎无惯性verticalSpeed0.5–0.7 倍 maxSpeed垂直过快容易丢失地面参照物bankAngle20–35 度超过 40 度侧倾影响瞄准视野相机是飞行手感的另一半。地面动作游戏相机只绕 yaw 和 pitch 旋转飞行是六自由度相机不处理平滑急转画面会剧烈抖动。推荐“快速跟随 慢速回正”策略用 SmoothDamp 控制跟随目标void LateUpdate() { Vector3 lookTarget target.position target.velocity.normalized * 3f; Vector3 camForward Vector3.SmoothDamp( transform.forward, (lookTarget - transform.position).normalized, ref velRef, 0.08f); transform.rotation Quaternion.LookRotation(camForward, Vector3.up); }smoothTime 取 0.08 到 0.12 之间比较合适低于 0.05 相机像焊死在角色身后急转会直接甩画面高于 0.15 视角回摆明显玩家会感觉角色天旋地转但画面没跟上。提示飞行速度增加时玩家需要更宽视野来判断方向常见做法是做一个 speedToFov 曲线0 到 40m/s 把 FOV 从 60 线性推到 85。Unity 里动态改 camera.fieldOfView 几乎没有性能成本但手感提升非常明显。3. Flying Hero 的3D战斗模块状态机、命中判定与打击反馈3.1 输入事件到技能状态的战斗数据流飞行动作战斗和地面最大的区别是每个攻击动作都要能在任意姿态使用并且产生位移判定。传统地面动作游戏用严格状态机锁住移动到空中就行不通因为角色不会“站在地面上”等玩家按完一套连招。常见做法是用一个轻量的技能状态枚举配 Animator 分层上半身跑攻击动画下半身继续飞。攻击输入只改状态枚举和动画参数不直接控制位移战斗表现层与飞行层互不阻塞。public enum HeroAction { IdleFly, // 自由飞行 MeleeA, // 普攻第一段 MeleeB, // 普攻第二段 DashAttack, // 空中急袭位移 伤害 SkillQ // 远程能量弹 }状态切换逻辑集中在 InputHandlervoid Update() { if (Input.GetButtonDown(Fire1) action HeroAction.IdleFly) { action HeroAction.MeleeA; animator.SetTrigger(MeleeA); } else if (Input.GetButtonDown(Fire1) action HeroAction.MeleeA animator.GetCurrentAnimatorStateInfo(0).normalizedTime 0.3f) { action HeroAction.MeleeB; animator.SetTrigger(MeleeB); } }normalizedTime 大于 0.3 才允许接下一段这是动作游戏里常用的“可取消窗口”。窗口太小连招按不出来窗口太大玩家会在第二段输入时反而丢帧。0.3 到 0.45 是近战手感的常见甜点区。3.2 飞行中的命中判定与攻击判定体积命中检测绕不开的问题是帧率不稳时武器扫过的动作会“跳帧穿怪”。地面动作游戏普遍用 OverlapSphere 解决在攻击激活帧做球形检测命中即结算。但飞行状态角色会把整个碰撞体带离地面攻击起点和终点都在运动单一球体做不到有效覆盖。更可靠的是 OverlapCapsule——起点为角色胸口终点为武器挥击方向前端的点radius 为攻击半径一次性覆盖一套挥拳轨迹bool TryHitInArc(float range, float radius, out Collider[] hits) { Vector3 origin transform.position Vector3.up * 1.2f; Vector3 end origin transform.forward * range; hits Physics.OverlapCapsule(origin, end, radius, targetLayer); return hits.Length 0; }使用时按攻击段位不同填 range 和 radius。飞行动作游戏里还有一个地面不会遇到的高发坑命中判定发生了但敌人没有受击表现表现为“打中了不结算”。原因通常是敌人 Animator 的受击 Layer 被移动或飞行 Layer 覆盖了需要在敌人 Animator 上单独建一层 HitReact并把层权重设成“不受其他层遮挡”。攻击类型RangeRadius帧数建议普攻近战2.0–2.50.8–1.0第 4 帧激活第 8 帧结束空中急袭3.5–4.51.0–1.2第 3 帧激活伴随位移远程能量弹判定走子弹0.4生成后逐帧位移提示激活帧在动画事件里打标记比在 Update 里硬算更稳妥Animator 编辑器中把攻击激活帧标到动画对应位置逻辑和表现同步成本最低。3.3 打击反馈镜头震动、短时慢动作与特效触发器打击反馈决定“超级英雄挥拳”是否成立。纯动画无法传递冲击力工程里普遍是“三件套”镜头震动、短时慢动作、命中粒子。这三者在 Unity 里都不需要插件。镜头震动可以直接在 LateUpdate 里叠加 Perlin 噪声向量并随时间衰减float shakeTimer 0f; Vector3 shakeOffset; void LateUpdate() { if (shakeTimer 0f) { shakeTimer - Time.deltaTime; float p shakeTimer / shakeDuration; shakeOffset Random.insideUnitSphere * shakePower * p; transform.localPosition shakeOffset; } }震动时长推荐 0.1–0.15 秒幅度不超过 0.5否则镜头会从“冲击感”变成“画面失误”。慢动作是另一个层面的事不要动 Time.timeScale飞行动画、输入和相机都在同一时间尺度下降速到 0.3 倍 0.15 秒后立刻回 1.0线性插值回去实际手感比直接跳变好非常多。特效与逻辑必须解耦攻击命中是一个数据事件特效、音效、震动都是这个事件的监听者。C# 的 event 或 UnityEvent 都能承担这件事。别把特效播放写在同一方法里——一旦后续想换技能特效或加命中音效改动会顺着调用链上下翻。4. 把 Unity 项目源码工程化C# 分层、场景参数与构建链路4.1 飞行动作游戏的目录设计与 ScriptableObject 数值表拿到 Unity 源码工程第一件事不是打开主场景而是看目录分层。飞行动作游戏涉及玩家、战斗、AI、UI、特效多个子系统脚本互相引用无法避免但至少在 C# 层面按职责分层Assets/ Scripts/ Core/ # 单例、事件系统、全局管理器 Player/ # 飞行控制、相机、技能输入 Combat/ # 命中检测、伤害结算、状态机 Enemy/ # 敌对 AI、追踪与攻击 Effects/ # 特效触发器、震屏、音频 Prefabs/ Scenes/ Configs/ # SkillConfig、AttackData这套结构的核心思想是表现层特效音频和逻辑层伤害与状态分开美术资源不进 C# 脚本目录数值配置全部走 ScriptableObject。飞行动作游戏技能多、参数杂硬编码魔法数字后期改一个数值要重构一整个类不值得。[CreateAssetMenu(fileName Skill_, menuName HeroDemo/SkillConfig)] public class SkillConfig : ScriptableObject { public string skillName; public float damage; public float cooldown; public float flyAttackRadius; public GameObject hitFxPrefab; }这种 Unity 内建的资源扩展机制可以直接在 Project 面板右键创建技能配置策划改伤害、冷却、特效引用不用碰代码。4.2 飞行场景必须调的物理、渲染与阴影参数飞行动作游戏是开放空旷场景相比室内环境阴影、雾效和天空盒的差异会直接冲进视野。下面这张表是工程里最常见的必调清单参数建议值原因Fixed Timestep0.0250Hz 物理更新飞行手感最稳Rigidbody Collision DetectionContinuous高速飞行防止穿墙穿怪阴影距离30–60m超过 60 阴影闪烁且无收益Shadow Cascades2 级级联太多在开放场景 GPU 压力大Anti Aliasing4x MSAA移动端降为 2x天空盒反射关掉实时反射飞行场景天空占画面比例大频繁出现的“unity阴影问题”在飞行游戏里通常是阴影距离没调主角飞到高空后地面阴影直接消失画面像漂在虚空中把阴影距离拉到 60–80 并适度降低阴影分辨率能保留视觉深度又不吃掉移动端带宽。4.3 构建链路从编辑器到 WebGL 与移动端的典型坑构建飞行游戏最常见的坑存在于是“运行好好的构建一跑就崩”。IL2CPP 构建时C# 里通过反射或字符串调用的类会被裁剪掉编辑器走 Mono 不触发但构建到 WebGL 或移动端后在特定路径上报 MissingMethodException。解法是给运行时反射类打上[Preserve]特性或者在 Player Settings 里关闭过激托管代码裁剪。真机出问题时先切 Mono 后端跑一遍能快速定位是否裁剪导致。WebGL 构建还有一个高频问题是普通平台的存档方案不适用浏览器里 Application.persistentDataPath 写入背后是 IndexedDB构建模板若未正确挂载 IDBFS运行时会报写入失败。处理策略是降低存档频率、分批写入不要在 Update 里每帧写 PlayerPrefs。C# 层还有一个容易被忽视的性能点循环里做字符串拼接或每帧 Debug.Log 输出调试数据会在手机上卡到掉帧。调试日志先写到内存队列里攒批落盘比每帧调用 Debug.Log 稳得多。飞行动作游戏本身每帧要同步飞行速度、技能冷却、敌人状态UI 刷新也要做更新缓存而不是每帧重新 setActive。命令行构建可以放进静态方法方便 CI 或本机批量出包public static class BuildScript { public static void BuildWebGL() { var opt new BuildPlayerOptions { scenes new[] { Assets/Scenes/Main.unity }, locationPathName Build/WebGL, target BuildTarget.WebGL, options BuildOptions.None }; BuildPipeline.BuildPlayer(opt); } }调用方式Unity.exe -batchmode -executeMethod BuildScript.BuildWebGL把这行放进打包脚本每次出包的场景列表和参数都固定省掉手动勾选场景的遗漏风险。5. 用速度数据曲线验证 Flying Hero 的飞行手感最后这一节给一个能直接落地的手感调优闭环。飞行动作游戏“手感好”本质上就是速度曲线的收敛形状符合直觉。设 maxSpeed 为 18、acceleration 为 12那么从零速到 90% 最大速度的时间约 0.55 秒曲线是前陡后缓的指数式收敛而不是线性直线。线性收敛会让玩家觉得“加速没有力量感”指数式收敛才有空气被推开的体验。把实际角色速度按时间采样后拉出来和参考曲线对比比反复进 Play Mode 试玩可靠得多。写一个小工具在 FixedUpdate 里把速度和耗时写入 CSVusing System.IO; private StreamWriter sw; void Start() { // 编辑器模式可直接写 DataPath构建到端侧后建议换到 persistentDataPath sw new StreamWriter(Application.persistentDataPath /vel_log.csv); } void FixedUpdate() { sw.WriteLine(Time.time , rb.velocity.magnitude); }用 Excel 或 Python 把 CSV 画出来和参考指数曲线v(t) 18 * (1 - e^(-t/0.6))叠在一起看。实际曲线腰线比参考明显高说明 acceleration 太大速度瞬间到顶玩家会感觉角色“没有重量”曲线一直达不到参考说明有隐藏阻力在拖拽常见来源是 Rigidbody 默认 drag 没清零或者代码里还有别的 Transform 层级在同步位移。把 maxSpeed、acceleration、verticalSpeed 暴露成 Slider 就能在编辑器里实时拖参数Unity UI 的 Slider.onValueChanged 绑到脚本字段进 Play Mode 后边飞边拉直到速度曲线落在参考曲线 ±15% 带宽内。飞行、连招、镜头三者的手感问题绝大多数都能从这张速度曲线图上定位出来。本文还有配套的精品资源点击获取