Unity多平台游戏开发实战:基于C#的完整闯关Demo解析
简介《Unity5实战使用C#和Unity开发多平台游戏》源码包面向Unity5跨平台游戏开发初学者及有经验的C#程序员提供一套可运行、可修改的工程参考。其中场景文件展示完整游戏环境与角色布局C#脚本揭示MonoBehavior生命周期、面向对象设计与常见游戏逻辑预设体便于复用角色或道具纹理、音频、Shader和Material则呈现渲染与音画的配置方式。压缩包约130MB7z格式内容分模块整理并附有开发过程笔记。工程覆盖iOS、Android、Windows、Mac、Linux等多平台发布要点对理解跨平台输入适配与性能优化有直接帮助。已有490人浏览学习既能支撑新手边改边学也可为资深开发者提供设计思路。1. 项目概述与整体设计1.1 这个项目要解决什么问题先说结论这是一个基于Unity 5.6.7f1开发、使用C#编写核心逻辑、同时面向PC端、Android和iOS三平台发布的小型游戏Demo完整源码在文末给出。它不是一个商业级大作而是一个帮你把“Unity多平台开发”这条路完整走一遍的实战模板。Unity 5这个版本放在今天看虽然有点“老”但它有一个好处API稳定、资料齐全、对硬件要求低。大部分在Unity 5里学到的核心概念——MonoBehaviour生命周期、物理系统、AssetBundle加载、多平台Build流程——在Unity 2018、2020、2022甚至Unity 6里依然适用。所以用Unity 5入门完全不会被时代淘汰反而是打基础的一个好选择。这套源码里包含了一个完整的闯关小游戏玩家控制角色移动、跳跃、收集金币、躲避障碍、到达终点后加载下一关。听起来简单但里面涉及了C#脚本架构、物理碰撞、UI管理、场景切换、跨平台输入适配、性能优化这几大块。做完这个项目你对“Unity做游戏到底是怎么一回事”会有一个很完整的认识。适合的人群我直接说清楚会用电脑、能装软件但没写过Unity脚本的新手朋友刚学C#语法、想找个真实项目练手的同学已经能用Unity拖拽做原型、但从来没有发布过手机游戏的开发者。这三类人从这套源码里能拿到的收获是完全不一样的。1.2 技术选型为什么是Unity 5加C#先聊个很多人纠结的问题做游戏为什么非要选Unity市面上的引擎不少Unreal、Godot、Cocos都有各自的拥趸。但Unity有一个别人比不了的优势——它是“多平台发布”这件事做得最无痛的引擎。我举个例子你就明白了同样一个游戏你要发布到Windows、Android、iOS三个平台。如果用原生开发你得维护三套代码、三套UI、三套打包流程。而Unity里你只需要点几下按钮切换Build Target然后分别打包就行。游戏逻辑的C#代码一套代码全平台通用。这就是我选Unity最核心的理由。至于为什么用C#而不是其他语言更简单Unity的官方脚本运行时就是Mono/.NET框架对C#的支持最成熟。C#是一个强类型语言类型安全意味着很多低级错误能在编译期就被发现而不是等你跑起来才崩溃。这对于新手来说其实是保护编译器帮你挡掉了很多坑。另外一个容易被忽视的点是C#的语法设计非常“友好”。它不像C那样要手动管理内存也不像Python那样写起来随意但跑起来性能没底。C#处在中间位置——写起来像高级语言一样舒服跑起来因为有JIT和AOT编译性能也够用。如果你以后想从游戏开发转到后端开发、上位机开发C#这套技能也不会白学它就是C#生态里的通用语言。我认识不少朋友从Unity转去做其他方向C#的功底都派上了大用场。这套项目的源码在设计上还特意保持了一个原则能用官方API解决的事绝不自己造轮子。这样做的好处是你学的每一个知识点放到未来的项目里都还能复用不会因为用了某个第三方插件而废掉。2. 环境搭建与项目初始化2.1 开发环境准备该装的东西一个都别少先列出我实际开发时使用的环境供你参考项目推荐配置备注Unity版本Unity 5.6.7f1 (64位)安装时勾选Android Build Support和iOS Build SupportC# IDEVisual Studio 2019 Community也可以选VS Code加C#插件Android SDKAPI Level 23以上Unity 5.6要求Android SDK版本不能太低JDKJDK 1.8太高版本会踩坑测试手机任意Android 6.0以上真机模拟器只能测逻辑测不了真实性能这里我得插句嘴很多人装Unity的时候嫌麻烦不勾选Android Build Support等到要出手机包的时候才发现缺模块从Unity Hub单独补装有时候还会出幺蛾子。听我的安装时把“Android Build Support”和对应的SDK、NDK、JDK模块一次性勾上省得后面折腾。iOS这边要单独说一下如果你用的是Windows电脑你仍然可以用Unity导出Xcode工程但最终打包成iOS应用必须在Mac电脑上操作。这是苹果的规则绕不开的。没有Mac的同学也不用急你可以先跑通Android和PC端的发布流程iOS的工程导出也可以正常演示只是ipa那一步要等有Mac环境再做。2.2 项目结构设计源码不是乱放的打开源码先别急着跑先花五分钟看一下目录结构。我设计这套源码时特意按照Unity社区通行规范做了分层Assets/ ├── Scripts/ │ ├── Player/ # 玩家控制相关脚本 │ │ ├── PlayerController.cs │ │ └── PlayerAnimator.cs │ ├── GameManager/ # 游戏流程管理 │ │ ├── GameManager.cs │ │ └── LevelLoader.cs │ ├── UI/ # UI控制脚本 │ │ └── UIManager.cs │ └── Enemies/ # 敌人AI脚本 │ └── EnemyPatrol.cs ├── Scenes/ # 游戏场景 │ ├── MainMenu.unity │ └── Level_01.unity ├── Prefabs/ # 预制体 │ ├── Player.prefab │ ├── Coin.prefab │ └── Enemy.prefab └── Resources/ # 动态加载资源 └── GameData.json这套结构的核心思路是“按功能拆、不按类型堆”。很多新手容易犯的毛病是把所有脚本和场景全扔在Assets根目录下一着急找东西要翻半天。按功能拆完之后哪个模块出问题直接去对应文件夹里找就行效率高得多。另外Resources文件夹是保底手段只放JSON配置文件这类需要动态加载但又不太多的数据。千万不要把场景、模型、贴图全扔进Resources里那样会让打包体积暴涨启动加载变慢。后面我会专门讲这个坑。3. 核心玩法实现详解3.1 角色控制C#脚本与物理系统的第一次握手这套源码里玩家控制的核心是PlayerController.cs。它的主要逻辑很典型读取输入、计算速度、施加物理、播放动画。我直接贴核心代码并注解using UnityEngine; public class PlayerController : MonoBehaviour { [Header(移动参数)] public float moveSpeed 5f; public float jumpForce 10f; [Header(组件引用)] private Rigidbody2D rb; private Animator anim; private bool isGrounded; private void Awake() { // Awake里获取组件比在Inspector里手动拖更稳定 rb GetComponentRigidbody2D(); anim GetComponentAnimator(); } private void Update() { // Update里处理输入因为输入检测每帧都需要 float horizontal Input.GetAxisRaw(Horizontal); rb.velocity new Vector2(horizontal * moveSpeed, rb.velocity.y); if (Input.GetButtonDown(Jump) isGrounded) { rb.velocity new Vector2(rb.velocity.x, jumpForce); isGrounded false; } // 动画状态控制 if (anim ! null) { anim.SetFloat(Speed, Mathf.Abs(horizontal)); anim.SetBool(isGrounded, isGrounded); } } private void OnCollisionEnter2D(Collision2D collision) { if (collision.gameObject.CompareTag(Ground)) { isGrounded true; } } }注意这里一个关键设计Update和FixedUpdate分工明确。Update每帧调用一次帧率越高调用越频繁所以适合处理输入检测这种“实时响应”的逻辑。而Rigidbody2D的物理模拟发生在FixedUpdate里固定每0.02秒一次。直接把rb.velocity赋值写在Update里虽然看起来流畅但物理计算会和渲染帧脱节某些极端帧率下会出现抖动。那为什么还有很多人把移动写在Update里因为对小体量项目来说Update里直接改速度完全够用代码还直观。我这里就是这么处理的——简洁优先。但如果你做的是竞速类游戏对物理精度要求高最好把移动逻辑挪到FixedUpdate里。另一个细节是用Input.GetAxisRaw而不是Input.GetAxis。差别在于GetAxis带平滑过渡会有加速减速的惯性感适合赛车类游戏模拟手感。而平台跳跃游戏需要“按下去立刻走、松开立刻停”GetAxisRaw没有平滑响应最直接。这种手感设计上的选择往往比脚本本身更能拉开作品之间的差异。3.2 碰撞检测与物理交互踩坑预警碰撞检测是2D平台游戏的核心很多新手在这里出问题。我的源码里有三处碰撞逻辑玩家踩地面判断、玩家收集金币触发、玩家碰到敌人受伤。先说金币收集的逻辑private void OnTriggerEnter2D(Collider2D other) { if (other.CompareTag(Coin)) { // 金币加一播放音效销毁金币对象 GameManager.Instance.AddScore(1); Destroy(other.gameObject); } }金币的Collider2D需要勾选Is Trigger而不是用物理碰撞。Trigger和Collision是Unity物理里的两种模式前者是“穿过且触发事件”后者是“挡住且弹开”。判断的条件不一样OnTriggerEnter2D接收的是Collider2D而OnCollisionEnter2D接收的是Collision2D参数类型都不同别用混了。踩到敌人是大多数平台游戏的经典设计从上方踩到敌人敌人被消灭从侧面碰到敌人玩家受伤。实现思路是在玩家脚下放一个子物体给它一个单独的Trigger叫FeetChecker。判断时如果FeetChecker触发到了敌人就执行敌人死亡逻辑如果玩家本体触碰到敌人则执行玩家受伤逻辑。这样用一个子物体就能区分“从哪个方向碰到”简单可靠。关于物理材质我再多说一句。很多人做跳跃游戏时发现角色粘墙或者滑得太快大概率是物理材质Physics Material 2D设置有问题。摩擦系数设为0可以防止角色贴在墙上不动弹性系数保持0可以避免角色撞到东西时被弹飞。这套源码里的玩家、地面、障碍物都设置了独立的物理材质。3.3 敌人AI一个巡逻怪物的最小实现源码里的敌人脚本EnemyPatrol.cs逻辑是在两个点之间来回巡逻碰到墙壁自动掉头。核心是状态控制public class EnemyPatrol : MonoBehaviour { public Transform pointA; public Transform pointB; public float speed 2f; private Transform target; private void Start() { target pointA; } private void Update() { // 向量移动 transform.position Vector3.MoveTowards( transform.position, target.position, speed * Time.deltaTime); // 到达目标点后切换目标 if (Vector3.Distance(transform.position, target.position) 0.05f) { target target pointA ? pointB : pointA; } } }这套写法特别适合初学者理解。Vector3.MoveTowards实现匀速直线移动Time.deltaTime保证移动速度与帧率无关。这个脚本在移动逻辑上没有用物理系统所以放在Update里是合理的——纯运动的物体不参与碰撞计算时这样做性能更好。我特意没在敌人身上用复杂的NavMesh寻路原因很简单功能越复杂新手越难掌控。你先学会用一个点做状态切换将来做更复杂的AI时你会自然地演化出“状态机”的思路。地基打牢后面的高楼才稳。4. 多平台适配与构建发布实操4.1 平台差异处理一次编写到处运行的真相“一次编写到处运行”这话在Unity里是真的但有个前提——你得处理好几个客观存在的平台差异。第一个差异是输入适配。PC上我们用键盘的WASD和空格手机上可没有键盘你得用虚拟摇杆。Unity 5自带的CrossPlatformInputManager就是干这个用的。在Player Settings里输入轴配置中有针对Mobile的模式运行时Unity会自动检测当前平台加载对应的输入方案。代码层面你要做的其实不多继续用Input.GetAxisRaw(Horizontal)底层Unity已经帮你把虚拟摇杆的输入映射到了同一个键位上。我在源码的MainMenu场景里放了一个Canvas虚拟摇杆的预制体手机上运行时自动激活PC上运行时自动隐藏。判定逻辑就一句话#if UNITY_ANDROID || UNITY_IOS mobileControls.SetActive(true); #else mobileControls.SetActive(false); #endif这是Unity的条件编译语法。#if和#endif之间的代码只在指定的平台上被编译。这比if(Application.isMobilePlatform)判断更彻底——后者运行时才判断前者能直接让不需要的代码从包体里消失。第二个差异是屏幕适配。手机屏幕有全面屏、刘海屏等不同规格UI设计得用Canvas Scaler的Scale With Screen Size模式再搭配Safe Area组件才能保证UI不被刘海遮住。这套源码里的UI全部采用相对布局和锚点没有写死的绝对坐标。关于UI锚点我提供给几个经验值按钮类UI用中心锚点0.5, 0.5顶部标题用上方锚点0.5, 1左下角积分文字用左下锚点0, 0。4.2 构建发布全流程从Unity工程到手机安装包这个部分我按实际操作的顺序把关键步骤整理成清单打开Build SettingsFile → Build Settings选择目标平台Android或iOS。配置Player SettingsCompany Name建议填你的真实信息或者一个团队名商店发布时有影响Product Name显示在手机图标下方的名字Package Name**形如com.yourcompany.yourgame这个一旦发布AppStore/Google Play就不能改一定想清楚再填Default Orientation手机游戏一般选LandscapeLeft或PortraitAndroid特别设置在Other Settings里勾选Internet Access如果你的游戏要联网不勾会闪退Min API Level至少选到与Unity 5.6兼容的级别选太高会导致老手机装不了太低会失去新平台的特性点击Build选择输出路径Unity开始自动打包。第一次打包Android会比较慢因为Unity要编译所有代码和资源。耐心等别中途关掉。我遇到过很多人卡在这一步明明操作都对但Build按钮一直是灰色的。灰掉的原因是当前场景没加进Build列表。你要在Build Settings里点Add Open Scenes按钮把当前打开的Level_01场景加进去Build按钮才会变亮。4.3 性能优化多平台游戏必须迈过的坎手机性能和PC完全不是一个量级同一款游戏在PC上跑60帧到了手机上可能掉到20帧。我在源码里做了几处有针对性的优化纹理压缩格式的调整。把角色贴图、背景图的Max Size从纹理原始尺寸限制为是2048或1024并启用了Generate Mip Maps。这样做的好处是内存占用大幅下降。一张2048×2048的RGBA32位贴图裸占16MB内存但压成ASTC格式后只有原来的三分之一左右。对象池的使用。金币被玩家收集后会立即销毁但频繁的Instantiate和Destroy会产生大量内存碎片。我在源码里写了一个简单的PoolManager金币从对象池里借用完了还回去。对象池这个概念看着高大上核心其实就三步池子里有就激活一个没有就新建一个用完放回池子。一个最容易被人忽略的优化是减少GetComponent的调用次数。GetComponent这个API在每次调用时都有性能开销尤其在Update里调用会拖慢帧率。正确做法是在Awake或Start里提前获取并缓存组件引用Update里直接使用缓存的变量。这套源码里的所有脚本都严格遵守了这个规则。Android屏幕方向的切换也是功耗大户。如果你在Player Settings里勾选了Auto Rotation意味着重力感应器会持续监听用户的手持姿态这会持续占用传感器功耗明显上升。只在你确定的游戏方向上勾选一个方向省电效果立竿见影。5. 常见问题与排查技巧实录5.1 我踩过的坑希望你别再走一遍第一个坑是Android导出后发现中文乱码。这个问题的根源是Unity 5.6默认的脚本序列化格式是Force Text而C#脚本文件默认编码在Windows上是GBK或者带BOM的UTF-8。Unity编译时对中文字符串支持不好导致打包后中文注释和字符串全部变成乱码。解决方案是在Visual Studio里把脚本文件另存为UTF-8 with BOM编码格式并且把Player Settings中的Scripting Backend保持为MonoIL2CPP一轮编译后部分版本也会出乱码问题。更稳妥的做法是中文字符串尽量写在场景里的Text组件上或JSON资源文件中而不是硬编码在C#代码里。第二个坑是场景切换后持有的引用全部丢失。Unity里有一个典型的生命周期陷阱DontDestroyOnLoad的对象只能保留一个如果你在场景A里创建了GameManager设置了DontDestroyOnLoad然后在场景B里又创建了一个GameManager场景A的数据会被场景B的覆盖掉。我在源码里用的是单例模式初始化时会检查是否已有实例有就直接销毁新创建的public class GameManager : MonoBehaviour { public static GameManager Instance; private void Awake() { if (Instance ! null) { Destroy(gameObject); return; } Instance this; DontDestroyOnLoad(gameObject); } }这个写法你以后在任何一个Unity项目里都用得上。但注意单例千万别用太多全局状态一旦多起来你的项目会变得没法调试。第三个坑是新手机APK安装时提示“应用未安装”。这个九成是因为你的APK签名和手机上已安装的旧版本签名不一致。Android系统强制要求同一个包名的应用必须用同一个密钥签名。解决方案是在Player Settings里勾选Create New Keystore设置一个密码保存好这个密钥文件以后每次发布都用这一个密钥签名。这个密钥文件千万要备份丢了就意味着你的应用永远无法更新。5.2 常见问题速查表问题现象可能原因排查与解决Build按钮置灰场景未加入Build列表Build Settings → Add Open ScenesAndroid包运行闪退未配置正确的IL2CPP或缺少SDK组件检查Android SDK路径和Player Settings的Scripting Backend手机屏幕UI跑偏或被刘海遮挡未适配Safe Area使用Canvas Scaler的Scale With Screen Size并挂载Safe Area脚本角色在真机上移动卡顿Update中频繁GetComponent或频繁实例化改为缓存组件引用使用对象池中文注释打包后乱码脚本文件编码不对将脚本另存为UTF-8 with BOM编码5.3 独家调试技巧分享调试手机上的游戏很多人还是靠Debug.Log打日志然后adb logcat看输出。这个方法太累了。我推荐你用Unity的Remote功能在Game视图下点击Remote下拉按钮选择你的真机Unity会把编辑器里的实时状态同步到手机上显示。这样你能直接看到手机上运行时的场景、层级、帧率和内存曲线。另外手机上性能卡顿别光靠感觉要拿数据说话。Unity自带的Profiler窗口对Android设备支持很好打开Profiler连接手机设备你就能看到每个函数占用的CPU耗时和内存分配情况。我遇到过一个诡异的手机发热问题用Profiler一查发现是UI上一张没压缩的2048贴图导致的GPU渲染时间爆炸压缩后问题立刻消失。这套源码下载后建议你按三步走先原样跑通一遍看懂每段代码的逻辑然后改一些参数速度、跳跃力、敌人巡逻距离感受参数对游戏手感的影响最后选一个功能点自己动手重写一遍再对比我写的版本看看差异在哪里。这个过程走完你对Unity和C#的理解绝对能上一个台阶。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →