news 2026/9/8 14:20:55

Unity多平台游戏开发实战:基于C#的完整闯关Demo解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity多平台游戏开发实战:基于C#的完整闯关Demo解析

简介:《Unity5实战:使用C#和Unity开发多平台游戏》源码包,面向Unity5跨平台游戏开发初学者及有经验的C#程序员,提供一套可运行、可修改的工程参考。其中场景文件展示完整游戏环境与角色布局,C#脚本揭示MonoBehavior生命周期、面向对象设计与常见游戏逻辑;预设体便于复用角色或道具,纹理、音频、Shader和Material则呈现渲染与音画的配置方式。压缩包约130MB,7z格式,内容分模块整理,并附有开发过程笔记。工程覆盖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 Support
C# 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 = GetComponent<Rigidbody2D>(); anim = GetComponent<Animator>(); } 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 ScalerScale With Screen Size模式,再搭配Safe Area组件才能保证UI不被刘海遮住。这套源码里的UI全部采用相对布局和锚点,没有写死的绝对坐标。关于UI锚点,我提供给几个经验值:按钮类UI用中心锚点(0.5, 0.5),顶部标题用上方锚点(0.5, 1),左下角积分文字用左下锚点(0, 0)。

4.2 构建发布全流程:从Unity工程到手机安装包

这个部分我按实际操作的顺序,把关键步骤整理成清单:

  1. 打开Build Settings:File → Build Settings,选择目标平台(Android或iOS)。

  2. 配置Player Settings

    • Company Name:建议填你的真实信息或者一个团队名,商店发布时有影响
    • Product Name:显示在手机图标下方的名字
    • Package Name**:形如com.yourcompany.yourgame,这个一旦发布AppStore/Google Play就不能改,一定想清楚再填
    • Default Orientation:手机游戏一般选LandscapeLeftPortrait
  3. Android特别设置

    • Other Settings里勾选Internet Access,如果你的游戏要联网,不勾会闪退
    • Min API Level至少选到与Unity 5.6兼容的级别,选太高会导致老手机装不了,太低会失去新平台的特性
  4. 点击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格式后只有原来的三分之一左右。

对象池的使用。金币被玩家收集后会立即销毁,但频繁的InstantiateDestroy会产生大量内存碎片。我在源码里写了一个简单的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保持为Mono(IL2CPP一轮编译后部分版本也会出乱码问题)。更稳妥的做法是,中文字符串尽量写在场景里的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 Scenes
Android包运行闪退未配置正确的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#的理解绝对能上一个台阶。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 14:20:27

AI Agent 工具链实战:5个开源项目让开发更省心

最近被问得最多的一个问题是&#xff1a;AI agent 到底难在哪&#xff1f;我自己的答案是——难在杂事太多。调模型反而不是最耗时的事&#xff0c;真正磨人的是工具链&#xff1a;状态怎么管、多个角色怎么协作、视频素材怎么拉、下载失败怎么重试……这些问题在 GitHub 上其实…

作者头像 李华
网站建设 2026/9/8 14:17:06

C++游戏开发:GCC 7.3.0+SFML环境配置与常见报错全解析

简介&#xff1a;GCC 7.3.0 结合 SFML 的 Windows 开发环境资源包&#xff0c;面向希望在 DevC 中快速搭建 2D 游戏或多媒体应用的开发者。GCC 7.3.0 是 GNU 编译器套件的一个稳定版本&#xff0c;对 C17 标准支持更完善&#xff0c;编译速度也有优化&#xff1b;SFML 则提供简…

作者头像 李华
网站建设 2026/9/8 14:13:38

JSP酒店管理系统开发全攻略:从模块设计到部署优化

简介&#xff1a;jsp酒店管理系统是一份基于JSP与Struts2框架的酒店管理Web项目完整源码包&#xff0c;适合Java Web初学者、毕业设计者以及希望掌握MVC分层开发的读者。项目围绕房间、预订、入住退房、客户和账单等核心模块展开&#xff0c;演示了从JSP页面编写、Action控制器…

作者头像 李华
网站建设 2026/9/8 14:13:31

C#联合HALCON实现工业视觉模板匹配全流程解析

简介&#xff1a;一套基于C#与HALCON的模板匹配视觉框架&#xff0c;专为工业场景中的物品定位与匹配效率优化而设计&#xff0c;适合机器视觉工程师、上位机开发人员及HALCON初学者参考。工程以窗体程序展示完整交互流程&#xff1a;先选取搜索局域与模板局域&#xff0c;若物…

作者头像 李华
网站建设 2026/9/8 14:10:52

Python切片详解:从索引边界到NumPy多维数组的进阶指南

切片这个词&#xff0c;是我刚学Python时觉得最“爽”的一个功能。别人还在用循环一个个取列表元素&#xff0c;你一行代码搞定&#xff0c;不仅看着舒服&#xff0c;运行效率还高。也许有人会觉得它很简单&#xff0c;就是list[start:stop]嘛&#xff0c;有什么好写的&#xf…

作者头像 李华
网站建设 2026/9/8 14:10:42

Linux入门核心:理解shell机制,掌握常用指令与脚本基础

如果你刚接触 Linux&#xff0c;多半会经历这样一个阶段&#xff1a;收藏了一堆“linux 常用命令大全”&#xff0c;以为背下几十条指令就能玩转服务器&#xff0c;结果真坐到了终端前&#xff0c;却只敢敲cd和ls。遇到一个权限错误就把命令抄到搜索框里查半天。这不是你的问题…

作者头像 李华