资讯动态

Unity 3D射击小游戏开发实战:从射线检测到性能优化

发布时间:2026/9/8 6:47:50 来源:尧图企业网站定制
简介这份 Unity 3D 射击小游戏资源包基于 Unity 2019.1.9f1 开发包含完整工程源码和可直接运行的 exe 文件适合 Unity 初学者上手学习 3D 场景搭建、角色控制、射击交互逻辑与项目打包流程。资源共 4655 个文件压缩包约 93.53MB主要文件类型包括 C# 脚本、Prefab 预制体、FBX 模型、材质与贴图、Shader 着色器、DLL 依赖库以及打包好的 exe 执行程序从源码到成品一应俱全。目前已有 2162 人学习或浏览对想要快速体验或参考 Unity 射击游戏实现的开发者有一定参考价值。通过完整工程可直观了解场景、脚本、资源的分目录组织方式并可直接基于现有代码修改功能、替换角色模型或扩展关卡节省从零搭建项目的时间。 做射击小游戏这个需求我接到的频率比想象中高。不管是最开始学Unity的新手还是想快速验证玩法的独立开发者几乎都会把3D射击当成第一个练手项目。但真正动手之后才发现从能动的角色到打得爽的射击手感中间隔着摄像机、射线检测、敌人AI、UI反馈、性能优化好几道坎。这篇文章就围绕我近期完成的一个unity 3D射击小游戏项目把从场景搭建到打包发布的关键环节、踩过的坑和值得抄作业的方案整理出来给准备入坑或正在调手感的同学一个可参考的路线。先说结论这个项目最终用Unity 2021.3 LTS做渲染管线走URP目标平台先按安卓和微信小游戏考虑从零开始到能顺畅打完几波敌人大概花了一周左右的业余时间。下面每一块我都会讲清楚我当时的选择逻辑以及只有真跑起来才会发现的问题。1. 为什么最终选了Unity一个小项目背后的引擎选型账1.1 引擎与渲染管线的实际考量要做3D射击玩法绕不开引擎选型。Unreal的渲染效果确实顶级但蓝图加C的组合对小体量项目来说是过重的负担随便一个空模板工程就好几个GB迭代速度也偏慢。Godot这两年势头很猛可是3D物理、NavMesh寻路、移动端适配这几个射击游戏刚需模块成熟度和踩坑资料都还不够厚。Unity赢在三点C#开发效率高、视频教程和Asset Store资源积累多、打包安卓和微信小游戏的管线完整。对小游戏来说这些比画面上限更重要。版本选择上我建议直接上Unity 2021 LTS以上的版本渲染方案用URP。URP对后处理的支持很全瞄准景深、受击模糊、色调映射都能在Volume组件里调而且性能开销可控移动端友好。有人习惯继续用内置渲染管线不是说不行只是URP里灯光的混光方式、阴影配置和SRP Batcher都是当前主流项目的标准做法现在转型的成本最低。项目里记得把Lighting Mode设置成Baked Indirect配合静态场景烘焙之后在真机上的帧率会好看很多。1.2 场景组织与资源命名的习惯这个项目规模不大但目录规划我还是按正式项目的习惯来。Assets下面拆成Art、Audio、Prefabs、Scripts、Scenes五个大目录Prefabs里再按Player、Enemy、Weapon、VFX细分。资源命名统一采用类型_用途_序号的规则比如Enemy_Soldier_01、Weapon_Pistol_Muzzle虽然短项目看不出太大区别等后期加敌人种类和武器数量时你就知道规范命名能省多少找资源的时间。我见过太多小游戏项目做到一半开始卡不是因为功能复杂而是所有模型、贴图、材质堆在一起Prefab互相引用又找不到谁是谁。所以在项目第一天就确定目录规范和命名规则这个投入几乎是稳赚不赔的。2. 第一人称角色与摄像机先把眼睛装对2.1 Character Controller还是Rigidbody第一人称射击里角色移动部分很多人会纠结用Character Controller还是Rigidbody。我的建议是没有特殊物理需求就直接用Character Controller。它本质是一个专门为人形角色设计的胶囊碰撞体加移动组件自带防穿墙、防滑步、斜坡处理的能力省去了一大堆自己调物理参数的时间。Rigidbody方案的优势是可以被爆炸、子弹冲击力推动但要处理弹跳、摩擦、角速度等一堆麻烦事对射击小游戏来说性价比不高。移动逻辑用最基本的Input.GetAxis读取输入方向向量不需要做复杂的相机相对旋转直接取摄像机forward和right投影到XZ平面再归一化就行。这里新手很容易忽略一个点移动向量要乘以速度后再交给CharacterController.Move不要在Update里直接改Transform.position否则碰撞检测就形同虚设角色穿墙和抖动会把你折磨到怀疑人生。2.2 CameraPivot的双层旋转结构视角控制是这个项目里第一个手感分水岭。我用了三层结构根节点PlayerRig挂CharacterController和胶囊碰撞体下面放一个CameraPivot空物体摄像机再挂在CameraPivot下。这样角色朝向、视角俯仰、摄像机抖动各管各的互不干扰。鼠标X轴控制PlayerRig的Y轴旋转也就是左右转体鼠标Y轴控制CameraPivot的X轴旋转也就是抬头低头。抬头低头必须有角度限制我夹在-85度到85度之间。很多教程会写直接对Transform.localRotation做欧拉角累加但欧拉角超过90度方向会反转最好用Quaternion.Euler每个帧重新赋值或者维护一个pitch变量在限定范围内累加。跳跃的逻辑也不要自己写重力公式Character Controller的isGrounded布尔值足够判断是否在地面给一个向下的速度值模拟重力即可手感虽然不惊艳但稳定可靠。3. 射击核心链路从射线检测到打中的完整闭环3.1 Raycast检测与IDamageable接口射击玩法最核心的就是那一发子弹的判定。我在枪口位置生成一条射线用Physics.Raycast检测命中的第一个碰撞体。这里有几个必须注意的点第一被射击的对象一定要有Collider且最好是专用碰撞体而不是整个模型合并的大碰撞体否则命中位置会非常怪异。第二命中之后不要直接对collider.gameObject取组件要向上遍历拿根节点因为敌人如果是多部件模型射线可能打到手臂、腿这些子物体。可以设计一个IDamageable接口让玩家、敌人、可破坏箱子都实现同一个受伤方法后面加新敌人或者爆炸桶只需要挂上对应的实现脚本就行。核心代码也就几十行public interface IDamageable { void TakeDamage(float amount); } public class WeaponController : MonoBehaviour { public float damage 20f; public float range 100f; public float fireRate 8f; public float spreadAngle 1.5f; public Transform muzzlePoint; private float nextFireTime; void Update() { if (Input.GetButton(Fire1) Time.time nextFireTime) { nextFireTime Time.time 1f / fireRate; Fire(); } } void Fire() { Vector3 dir muzzlePoint.forward; float randomX Random.Range(-spreadAngle, spreadAngle) * Mathf.Deg2Rad; float randomY Random.Range(-spreadAngle, spreadAngle) * Mathf.Deg2Rad; dir Quaternion.Euler(randomX, randomY, 0) * dir; Ray ray new Ray(muzzlePoint.position, dir); if (Physics.Raycast(ray, out RaycastHit hit, range)) { var target hit.collider.GetComponentInParentIDamageable(); target?.TakeDamage(damage); } } }扩散随机偏移我控制在1.5度左右太大会让子弹乱飘太小又会让连射过于精准。这个参数后期一定要在真机上试不同屏幕尺寸和帧率下玩家对散布的感知是完全不一样的。3.2 后坐力、弹孔、枪口火光与弹壳的配合射击质感不是单纯能打中就够的。后坐力我建议用视觉欺骗法开火瞬间给CameraPivot的rotation目标加一个向上的小角度偏移然后让视角在几帧内用Lerp回落。不要真的去旋转摄像机本身会产生眩晕感。连续开火时这个偏移可以按射速累积模拟枪口上跳。弹孔、枪口火光和弹壳掉落是开枪反馈的质感三件套看着是细节合起来就是这枪到底有没有劲。命中墙面时在命中点实例化弹孔Prefab用SpawnPosition对齐表面法线防止弹孔歪斜或悬浮。枪口火光用Particle System材质选Additive模式配一个很小很亮的光晕贴图播放时间控制在0.05秒。弹壳最简单用一个自带Rigidbody的小胶囊体开火时往侧后方抛一个力再写个延迟几秒自动销毁的脚本就行。4. 敌人AI不做花活状态机加NavMesh就够用4.1 状态机的最小实现小游戏里的敌人AI不需要有多聪明真正决定玩法体验的是状态切换的果断性和可预测性。我实现了一个极简状态机巡逻、追击、攻击、受击、死亡。默认状态在几个巡逻点之间走动一旦玩家进入索敌范围就切入追击到达攻击距离后停下开火。受伤事件会被立即响应播放受击动画同时把仇人锁定为当前玩家这个交互细节非常重要。状态机的实现不需要引入复杂框架一个枚举加Update里的switch就够了。但我强烈建议把索敌范围和攻击距离做成可配置参数而不是写死在代码里因为后续要调游戏难度时你不可能为了改个距离重编译一次项目。用[SerializeField]把参数暴露在Inspector面板数值不平衡的时候拖一拖滑块就能找到合适手感。4.2 NavMesh烘焙和双段索敌寻路我用Unity自带的NavMesh。烘焙前必须把地面和静态障碍物标记为Navigation Static这是90%的人第一次烘焙失败的原因。烘焙面板里Agent Radius、Step Height、Max Slope这三个参数要按实际模型的尺寸调整我项目里的角色胶囊体半径是0.3所以Agent Radius设成0.4比较安全太小会导致AI贴着墙走太大会出现绕远路。索敌逻辑我建议拆成视觉和听觉两段。视觉用Physics.OverlapSphere检测范围内是否有人再加一个朝向夹角判断防止敌人后脑勺长眼睛看到背后玩家。听觉就比较作弊了玩家每次开火触发一个全局事件敌人监听到之后根据距离判断是否进入警戒状态。这两段配合下来敌人既不会隔墙透视又给潜行玩法留了操作空间玩家会觉得敌人有脑子而不是傻站在那等被打。敌人死亡的处理也有讲究不要马上Destroy对象先禁用Collider和NavMeshAgent播放死亡动画延迟大约1秒再销毁。直接销毁会造成两个问题一是子弹和碰撞检测可能会在销毁瞬间报空引用二是死亡动画被截断会让击杀反馈非常廉价。5. 别小看手感工程声音、屏幕反馈与UI的隐藏坑5.1 声音分层与优先级射击手感的构成里声音占比可能超出很多人预期。开枪声、命中声、敌人倒下声这三类音效必须分开设计。命中声我还做了两个变体命中敌人和命中墙壁用不同的混音玩家能通过声音立刻判断这枪到底打没打中这种即时反馈是手感的重要来源。AudioSource不要全部挂在主摄像机上最好每个敌人身上挂一个巡逻用的AudioSource死亡时播放独立音效这样受击声音有空间感也更真实。声音优先级也要调。Unity默认的AudioSource优先级是一样的多个音效同时触发时会互相压缩听起来像一团糊。把开枪的优先级设成最高数值最小命中次之环境音最低。这样即使同屏有大量音效玩家最先听到的永远是和自己操作直接相关的反馈。5.2 UI更新、TextMeshPro和LayoutGroup的坑UI这块我踩过两个典型的坑。第一个是频繁变动的Text默认用旧版Text组件每次赋值都会触发重建子弹数量、生命值这种每帧都在变的内容多了就会卡顿。解决方案很简单全部换成TextMeshPro它内部做了网格缓存更新开销小得多。或者把整个界面拆成静态背景和动态数值两层动态层尽量少。第二个坑是关于Vertical Layout Group的刷新问题。很多人往一个垂直布局里动态添加Item发现UI没有按预期重新排列折腾半天代码发现没刷新。这是Unity布局组的一个经典问题布局计算有延迟需要手动强制重建。简单粗暴的办法是在添加Item之后调用LayoutRebuilder.ForceRebuildLayoutImmediate加Canvas的ARPG接口或者调用ContentSizeFitter对应的接口。我项目里的击杀提示列表就因为这个坑卡了半个晚上因为列表项是动态生成的不强制刷新就永远错位。如果你遇到类似问题建议先检查布局组件有没有被禁用或放在不活动的节点上再排查是否缺少强制刷新的调用。6. 从编辑器到打包阴影、合批与平台适配的踩坑记录6.1 移动端性能的第一刀阴影与合批项目能玩了之后真正闹心的是优化。移动端第一个要动刀的就是阴影把Shadow Distance砍到40米以内很多时候玩家根本注意不到远处缺阴影但帧率能提升一截。条件允许就选用Shadowmask模式静态场景的阴影预烘焙到Lightmap里动态角色只保留一张实时阴影贴图性能消耗会小很多。URP里有两个默认可能没打开的开关值得检查SRP Batcher和GPU Instancing。这两个开关能显著降低DrawCall。这个项目里重复模型很多比如掩体、木箱、墙壁碎片我专门检查了它们是否共享同一个材质贴图也尽量合到一张图集里配合GPU Instancing之后DrawCall降了将近三分之一。后处理方面Bloom我最终关掉了中低端机能一开Bloom就容易发热降频这是比掉帧更影响体验的问题。我宁可把整体亮度调高用色调映射做出通透感也不用Bloom硬撑高级感。6.2 微信小游戏与Android打包的适配差异这个项目一开始就计划发微信小游戏和安卓两个平台适配差异比想象中大。微信小游戏走Unity官方的小程序转换工具链这一步会校验不少API兼容性。比如某些底层线程库、反射调用在小程序环境里会受限如果项目里用了第三方插件务必先去查插件支持列表免得打包到最后一步才报错。我的做法是提前建了一个真机调试专用场景把需要验证的功能尽可能全部摆进去每次打包后先用它在开发者工具里跑一遍能节省大量来回改包的时间。Android端相对顺畅但Player Settings里Minimum API Level这个选项要主动改别用默认值。最近有项目把API Level提高到35主要是因为部分广告SDK和统计SDK要求高版本系统接口如果在gradle里发现构建报各种依赖找不到先检查这里。API Level设太低会出现第三方插件初始化失败保活和权限弹窗逻辑也会异常这类问题在测试机上很难复现用户那边却一堆反馈排查起来非常痛苦。还有一个很多人忽略的包体问题URP的Shader一定要通过Preloaded Shaders提前加载否则首次进入场景会卡顿。微信小游戏环境对包体更敏感美术资源能压缩尽量压缩音频转Vorbis格式贴图开压缩格式但别压太狠避免近看糊成一片。发布前我一定会做一轮弱网中低端机测试很多性能问题只有在真实环境下才暴露得出来。最后分享一个我自己的习惯每调完一轮手感我都会录一段游戏视频然后隔一天再看。当天反复试射的时候很容易被自己的操作麻痹很多问题察觉不到隔一天再回看哪里开枪衔接不顺、哪里命中反馈不够明显判断会客观很多。这个习惯帮我砍掉了不少自欺欺人的手感问题。后续如果要扩展这个项目我会优先做武器管理和更多敌人种类但当前这套角色控制、射击判定、敌人AI和发布流程的框架已经足够撑起一个完整可玩的unity 3D射击小游戏了。本文还有配套的精品资源点击获取

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

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

免费获取报价