简介:《Unity5实战:使用C#和Unity开发多平台游戏》源码,是一份面向初、中级Unity开发者的学习型资源,尤其适合想要系统掌握跨平台游戏开发流程的读者。资源以Unity5引擎和C#语言为核心,从组件系统、Transform与脚本协同,到MonoBehaviour的Awake、Start、Update等生命周期方法,再到类、继承、接口等面向对象设计,逐步串联起游戏逻辑的编写思路。源码包采用7z压缩格式,整体约130.09MB,内容涵盖场景文件、C#脚本、预设体、纹理音频及Shader/Material配置,场景文件定义关卡布局,脚本负责行为逻辑,预设体便于跨场景复用,目录结构清晰,适合按模块查阅。目前已有491人学习下载,可见其作为实战参考材料的热度。通过研究这份源码,读者既能学会在Unity5中搭建游戏场景、编写C#交互逻辑,也能了解不同平台(iOS、Android、Windows等)输入差异、性能优化与分辨率适配的处理方式,同时还为后续独立开发多平台游戏提供了可借鉴的工程组织与调试经验,对提升实战能力有明显帮助。
1. Unity5实战:用C#做多平台游戏,源码该怎么组织才不翻车
做Unity多平台游戏经常遇到这种开局:编辑器里手感很好,Build Settings勾上Android、iOS、Windows三个平台开始打包。Android包装上手机秒退,iOS证书没配置还打不了,Windows运行一会CPU居高不下,而你最初只改过一行分辨率代码。Unity5把跨平台构建管线统一了,真正的分水岭在C#源码的组织方式:平台判断、输入、存档路径若全部写在业务逻辑里,多平台发布就是连环翻车现场。下面要拆的不是教程demo,而是一套能迁移到真实项目的多平台C#基础结构——平台宏、输入抽象、存储路由和验证清单。适合已经写过C#基础、准备把项目导出到第二个平台的开发者照着改。
2. 平台宏与源码目录先定型:编译期分支决定后续三端兼容
2.1 用条件编译宏隔离平台差异,而不是运行时if
如果你把一个多平台游戏拆开看,真正会因为平台不同的代码其实只有几类:输入读取、存储位置、屏幕分辨率、部分渲染特性以及第三方SDK。这些差异如果不做隔离,项目做到后期,业务逻辑里到处是if (Application.platform == RuntimePlatform.Android),每次平台切换都会多跑三个分支。更麻烦的是,运行时判断没法删掉多余的代码,最终包体里带着所有平台的分支代码,热更新和AOT编译场景下还可能出现平台API误调用。
Unity从编译层面提供了一组宏定义,能把这部分差异提前到编译期处理:
using UnityEngine; public class PlatformRouter : MonoBehaviour { void Start() { #if UNITY_ANDROID && !UNITY_EDITOR // Android真机:关垂直同步,主动锁定帧率 QualitySettings.vSyncCount = 0; Application.targetFrameRate = 60; Debug.Log("Android targetFrameRate->60"); #elif UNITY_IOS && !UNITY_EDITOR QualitySettings.vSyncCount = 1; Debug.Log("iOS vSync->1"); #elif UNITY_STANDALONE QualitySettings.vSyncCount = 1; Debug.Log("Standalone vSync->1"); #else // 编辑器默认保持60帧即可 Application.targetFrameRate = 60; #endif } }这段代码的关键不在帧率数值,而在三个平台的分支互斥关系。UNITY_ANDROID、UNITY_IOS、UNITY_STANDALONE由Unity构建管线在预处理阶段写入,C#编译器只编译命中平台的那一段。所以这段代码到了真机上,实际上只包含一条初始化语句,不会有任何多余的平台分支。这里特别加了一句!UNITY_EDITOR,是为了防止你在编辑器里运行安卓模拟模式时,把编辑器误当成安卓真机去关掉垂直同步,导致预览画面撕裂。
参数上的两个习惯我建议直接沿用:Application.targetFrameRate在Unity5里是跨平台通用属性,安卓上设置60可以让游戏主动申请60Hz刷新,不和系统省电策略打架;QualitySettings.vSyncCount设为0时,渲染循环不再等待显示器垂直回扫,这时必须配合targetFrameRate设一个上限,否则GPU会满载空转。iOS端一般保留vSyncCount = 1,因为iOS系统自身有较严格的功耗管理,垂直同步反而让帧率更稳定。
如果你不只做移动和桌面,还要兼顾WebGL,宏名还得多记两个:UNITY_WEBGL和UNITY_EDITOR。WebGL平台在Unity5里不能使用System.IO的文件写接口,也不能同步加载资源,这些特性约束都适合用宏在编译期提前锁死。我的经验是:与其在运行时写一堆Application.platform判断,不如在最早初始化阶段就按宏给全局配置类赋值,之后业务逻辑只认配置,不再认平台。
2.2 源码目录和资源目录按平台分层:避免Plugins和Resources一锅烩
宏能把代码的差异提前,资源也类似。Unity里有些目录有平台语义,放对了省心,放错了直接埋雷。
Assets/ ├── Art/ │ ├── Models/ │ ├── Textures/ │ └── Audio/ ├── Code/ │ ├── Core/ │ ├── Gameplay/ │ └── UI/ ├── Data/ │ └── Config.json # 随包打进去的只读配置 ├── Editor/ │ └── MultiPlatformBuilder.cs ├── Plugins/ │ ├── Android/ │ ├── iOS/ │ └── WebGL/ ├── Resources/ │ ├── UIAtlas.prefab │ └── GameSettings.asset ├── Scenes/ │ └── Main.unity └── StreamingAssets/ └── LevelData/ # 运行时读取的关卡数据这个目录结构有四个固定项需要注意。第一,Editor文件夹是Unity的魔法目录,里面所有C#脚本只在编辑器环境编译,不会进入任何平台包体,适合放上一节说的BuildPipeline构建脚本和静态检查工具。第二,Plugins/Android、Plugins/iOS是Unity的另一个平台语义目录,对应的.so、.aar、.framework会在打包时只进入目标平台;如果这些文件放错到Assets根目录,打包时会报Architecture不匹配。第三,StreamingAssets里的文件会原封不动拷贝到各平台安装包,但不同平台的读取路径不一样,这个在第三章会专门讲。第四,Resources目录不要随意扩大,这个目录下的所有资源在Unity5里都会打进同一个包,而且运行时Resources.Load是全局字符串查找,目录过大时加载耗时和内存占用都存在隐患。
目录分层的核心目的是把平台专属内容和共享逻辑分开。比如安卓的通知权限插件、iOS的支付回调,都放在各自插件目录;游戏自身的玩法逻辑只放在Code里,通过公共同名类跨平台。这样当你要出第二个平台包时,排除某个插件目录或改名都很快,不需要在场景里逐个找引用挂接的资源。
有一个容易翻车的习惯是,把美术素材按平台名建文件夹,比如Art/Android和Art/iOS。这会让Prefab里的引用路径在跨平台时完全失效,重建prefab的成本很高。正确做法是:只有拿第三方SDK或平台API封装的资源,才按平台放Plugins;普通美术资源只按资源类型分目录,平台差异交给代码层去处理。
2.3 用BuildPipeline把Android、iOS、Windows一次打包
用Unity界面手动出包有个痛点:每次都要去Build Settings勾选场景、选平台、改Player Settings,一旦漏一步,出包后直接浪费时间。常见做法是把打包流程写成一个编辑器脚本,放到Assets/Editor目录下,通过菜单栏一键执行:
using UnityEditor; using UnityEngine; using System.IO; public static class MultiPlatformBuilder { [MenuItem("Tools/Build All Platforms")] public static void BuildAll() { // 打包时间戳目录,避免覆盖上一次的包 string stamp = System.DateTime.Now.ToString("yyyyMMdd_HHmm"); string root = "Builds/" + stamp; string[] scenes = { "Assets/Scenes/Main.unity" }; // Android:LZ4HC压缩,测试包加Development BuildPipeline.BuildPlayer( scenes, Path.Combine(root, "Android/game.apk"), BuildTarget.Android, BuildOptions.CompressWithLz4HC | BuildOptions.Development ); // iOS:不能直接生成ipa,这里导出Xcode工程 BuildPipeline.BuildPlayer( scenes, Path.Combine(root, "iOS"), BuildTarget.iOS, BuildOptions.None ); // Windows 64位桌面 BuildPipeline.BuildPlayer( scenes, Path.Combine(root, "Win64/game.exe"), BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4HC ); Debug.Log("构建完成,输出目录:" + root); } }这个脚本里值得说的参数有四个。BuildTarget.Android、BuildTarget.iOS、BuildTarget.StandaloneWindows64分别对应三端目标,Unity会按这个枚举值调用对应的构建管线。BuildOptions.Development会给包体附加调试信息和脚本调试器,方便用日志定位问题,但正式发布时不要勾,否则包体尺寸和运行速度都会受影响。BuildOptions.CompressWithLz4HC是对AssetBundle采用LZ4 HC压缩,这是构建时间换安装包体积的典型做法,发布包推荐用;如果只做本地测试,用CompressWithLz4速度更快。
iOS那一步的第二个参数传的是目录而不是文件,这点容易误解。BuildPipeline.BuildPlayer对iOS会生成一份Xcode工程,并不会直接在命令行帮你完成签名和ipa导出。所以你在自动打包脚本里,通常还会再调用xcodebuild或fastlane去完成签名归档。这也是为什么很多团队把iOS打包单独放到打包机上做,避免本机装全套Xcode。
当时我踩过的一个细节是BuildPipeline.BuildPlayer在打包Android时,如果Player Settings里的Package Name不对,脚本照样能跑,但装上后会和其他项目包名冲突,安装时直接提示签名不一致。这属于配置检查的层面,我会在第5章给一个自查清单。
3. 用C#抽象输入、存储与屏幕:跨平台必须改的三个子系统
3.1 输入抽象层:虚拟摇杆与手柄在共享代码里的共处方式
游戏逻辑里最容易被平台打乱的,是输入这一层。桌面端有键盘鼠标手柄,移动端只有触摸和虚拟按键,WebGL的输入延迟特征又不相同。如果让游戏逻辑去直接访问Input.GetAxisRaw("Horizontal"),你会在移动端看到角色无法控制,因为移动端根本没有键盘轴输入,也没有默认的轴映射。
我的方案是在逻辑层之上单独建一个GameInput静态类,所有玩法逻辑只跟它交互:
using UnityEngine; public static class GameInput { // 每帧调用,返回归一化移动方向 public static Vector2 moveDelta = Vector2.zero; public static Vector2 lookDelta = Vector2.zero; public static void Tick() { Vector2 move = Vector2.zero; #if UNITY_STANDALONE || UNITY_WEBGL // 桌面与网页端:优先键盘手柄,兼顾鼠标 move.x = Input.GetAxisRaw("Horizontal"); move.y = Input.GetAxisRaw("Vertical"); // 手柄右摇杆映射到视角旋转(第二组轴) lookDelta.x = Input.GetAxisRaw("JoystickAxis3"); lookDelta.y = Input.GetAxisRaw("JoystickAxis4"); #elif UNITY_ANDROID || UNITY_IOS // 移动端:只能读UI虚拟摇杆写入的状态 move.x = VirtualJoystick.horizontal; move.y = VirtualJoystick.vertical; #endif // 向量归一化,防止斜向移动速度超过直行 if (move.sqrMagnitude > 1f) move.Normalize(); moveDelta = move; } }这套抽象的关键有两处。第一,平台宏在编译期把输入源分流,桌面读轴,移动读虚拟摇杆变量,业务层代码不需要知道当前平台。第二,VirtualJoystick是一个简单的静态变量容器,由UI层在拖动虚拟摇杆时写入;Unity UGUI的EventTrigger或Slider都能实现写入,我这里只约定数据入口,避免UI控件和玩法代码强耦合。
这里有个常用但容易翻车的点:Input.GetAxisRaw返回的是不经过平滑的原始值,手感偏硬,很多教程推荐用Input.GetAxis带平滑包络。但在移动端如果读的是虚拟摇杆,平滑是UI层应该做的,逻辑层直接读原始值即可。如果逻辑层再做一次平滑,控制延迟会叠加。我一般会在抽象层里允许注入sensitivity和deadZone参数,把摇杆死区收敛到一块,而不是散落在各玩法脚本里。
对手柄的支持,就得说回Unity5的Input Manager。默认工程里只有Horizontal和Vertical两根轴,手柄右摇杆读取往往需要自定义四根轴。做法是在Edit > Project Settings > Input里新增JoystickAxis3到JoystickAxis6,分别对应右摇杆XY和LT/RT扳机,再在抽象层里按GetAxisRaw("JoystickAxis3")读取。很多主机移植项目的坑,都是默认轴读出来是0,加了几根轴立刻正常。
3.2 存档路径与读写策略:不同平台不能共用一套字符串
持久化存储是第二个跨平台重灾区。Windows上写C:/Users/xxx/AppData没问题,Android上外部存储没有写权限,iOS的沙盒目录每次启动位置不完全独立,而且Unity不同版本对persistentDataPath的映射还变过。如果代码里把存档路径写死,当前平台能存,换一个平台直接抛DirectoryNotFoundException。
using System.IO; using UnityEngine; public static class SaveSystem { public static string GetSavePath(string fileName) { string dir; #if UNITY_EDITOR // 编辑器里把存档放到工程外,避免污染Assets目录 dir = Path.Combine(Application.dataPath, "../SaveData"); #elif UNITY_STANDALONE // 通用AppData路径,C#的Directory API可正常操作 dir = Application.persistentDataPath; #elif UNITY_ANDROID // Android上位于 /storage/emulated/0/Android/data/包名/files dir = Application.persistentDataPath; #elif UNITY_IOS // iOS沙盒Documents,iCloud备份会包含此目录 dir = Path.Combine(Application.persistentDataPath, "Documents"); #else dir = Application.persistentDataPath; #endif // 目录不存在就创建,避免首次写档抛异常 if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); return Path.Combine(dir, fileName); } }这套代码在不同平台选择的是同一个语义——持久化可写目录。Unity的Application.persistentDataPath在安卓和iOS上都会映射到系统分配的应用专属目录,不依赖绝对路径,所以代码层面一共只有两处差异:编辑器下输出到工程外,iOS加了Documents子目录。为什么要加Documents?因为iOS的iCloud备份默认只针对Documents目录,玩家进度放这里能跟随iCloud恢复;而临时缓存文件会放到tmp,一旦升级系统或空间紧张,缓存清掉不影响存档。
存储的另一个坑是写频率。在移动端频繁File.WriteAllText会触发GC和IO中断,卡顿肉眼可见。常见做法是把存档对象序列化成一个POCO类,内存里维护引用,按明确时机(切场景、回主菜单、应用退到后台)才落盘一次。我习惯用OnApplicationPause回调,在安卓切后台瞬间写一次档,而不是每帧都写。
3.3 屏幕比例与UI安全区:Unity5时代没有官方safeArea怎么破
现代手机比例早就不是16:9,刘海屏、打孔屏、折叠屏把UI适配变成一场持久战。Unity在2017.2以后才提供Screen.safeArea,Unity5时期没有这个接口,典型的做法是手动维护一张安全距离表,按设备型号给UI边距做补偿。
using UnityEngine; using UnityEngine.UI; public class SafeAreaFitter : MonoBehaviour { [Tooltip("四项边距补偿值:上、下、左、右(像素)")] public int topInset; public int bottomInset; public int leftInset; public int rightInset; private RectTransform rectTrans; void Awake() { rectTrans = GetComponent<RectTransform>(); Apply(); } void Update() { // 屏幕旋转或分辨率变化时重新适配 if (Screen.orientation == ScreenOrientation.AutoRotation) Apply(); } public void Apply() { // 将补偿值换算成anchor偏移,按当前屏幕像素计算 float width = Screen.width; float height = Screen.height; Vector2 anchorMin = new Vector2( leftInset / width, bottomInset / height ); Vector2 anchorMax = new Vector2( (width - rightInset) / width, (height - topInset) / height ); rectTrans.anchorMin = anchorMin; rectTrans.anchorMax = anchorMax; rectTrans.offsetMin = Vector2.zero; rectTrans.offsetMax = Vector2.zero; } }这段代码解决的是UI四边避开刘海和圆角的需求。它的原理是绕过屏幕参考分辨率体系,直接按当前屏幕像素计算锚点,所以无论CanvasScaler怎么缩放,锚点比例都不会被缩放二次放大。手机横屏时刘海在左侧,竖屏时刘海在顶部,你只需要在进入场景前根据Screen.orientation给leftInset或topInset赋值。
这里有个关键细节:在Unity5时代,这套补偿值是机型相关的,而不是百分比。因为不同机型的刘海宽度和圆角半径不一样,纯按百分比算会在某些机型上留白过多、某些机型上还是被遮。常见做法是建立一个根据SystemInfo.deviceModel查表的工具类,内置主流机型的安全距离值,再留一个运行时手动调整接口供QA测试时修正。
配合这套补偿的逻辑,CanvasScaler的基准分辨率建议固定成1280x720横屏或720x1280竖屏,ScreenMatchMode用Shrink而不是Expand,避免UI在小屏上被等比缩小到看不清。如果你同时发横屏和竖屏两个版本,我的实践是两个Canvas分支各自套一组不同基准,不要试图用一套Canvas在旋转时无缝处理,那会导致预排版全部错位。
4. 多平台适配踩坑记录:5个从现象到解决的排查思路
这一节里的每条记录,都是我在实际项目中真金白银踩出来的。
4.1 Android包安装后秒退,编辑器正常运行
- 现象:Android APK在真机上点击图标,一两秒后直接退回桌面,没有异常弹窗;同一场景在编辑器里播放完全正常。
- 原因:最常见是Player Settings里Scripting Backend选了IL2CPP,但本机NDK版本和Unity5要求的版本不匹配。Unity5时期的IL2CPP需要特定NDK r10e或以上,装错NDK版本后C++层链接失败,运行时java调用不被解析,于是静默闪退。另一种常见原因是第三方SDK的AndroidManifest.xml里有Provider或Activity没注册。
- 解决:先用adb拉日志确认崩溃点:
adb logcat -s Unity | tail -50如果看到libil2cpp.so或JNI错误,去Unity安装目录下的AndroidSupport检查NDK版本,在Player Settings里重新指向匹配的NDK。同时检查Assets/Plugins/Android/AndroidManifest.xml,把缺失的<provider>和<activity>按SDK文档补齐。这条日志命令在Windows/Linux/macOS的终端都能跑,前提是手机开了USB调试。
4.2 粒子特效内存在移动端越用越多
- 现象:游戏在PC上连续运行30分钟内存稳定,打包到安卓后多次播放爆炸特效,内存占用逐渐攀升,最终触发系统杀进程。
- 原因:粒子系统在Unity5移动端有两个容易忽略的默认行为。第一,
ParticleSystem.Main里的Stop Action通常设为None,粒子播放完对象还挂在场景里,Stop后不会自动Disable,持续占住网格和材质引用;第二,玩法里用Instantiate/ Destroy反复创建特效对象,频繁触发的GC堆膨胀在移动端被放大。 - 解决:给粒子特效建一个对象池,播放完回收而不是销毁。参考实现:
using System.Collections.Generic; using UnityEngine; public class ParticlePool : MonoBehaviour { public GameObject particlePrefab; public int prewarmCount = 5; private Queue<ParticleSystem> _pool = new Queue<ParticleSystem>(); void Start() { for (int i = 0; i < prewarmCount; i++) { var ps = CreateNew(); ps.gameObject.SetActive(false); _pool.Enqueue(ps); } } public ParticleSystem Spawn() { ParticleSystem ps = _pool.Count > 0 ? _pool.Dequeue() : CreateNew(); ps.gameObject.SetActive(true); ps.Play(); return ps; } public void Recycle(ParticleSystem ps) { // 先Stop再Clear,避免残留粒子在下次Play时闪一下 ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); ps.gameObject.SetActive(false); _pool.Enqueue(ps); } private ParticleSystem CreateNew() { return Instantiate(particlePrefab, transform).GetComponent<ParticleSystem>(); } }对象池的关键参数是prewarmCount和回收时机。预创建数量按单屏同时出现的最大特效数来定,多了占内存,少了池命中率不够又回到Instantiate的循环。StopEmittingAndClear告诉粒子系统停止发射并把已生成粒子清空,否则下次Spawn时会看到一帧旧粒子残影。这个方案同样适用Unity5和后续版本,而且对3D特效和UI特效都有效。
4.3 Time.timeScale暂停引起物体速度计算错乱
- 现象:游戏里菜单暂停用
Time.timeScale = 0,恢复后在斜坡上的Rigidbody物体突然穿模,或者Rigidbody.velocity读到的物体速度比暂停前小一大截。 - 原因:Unity5的
timeScale影响所有基于Time.deltaTime的计时,包括物理模拟、Animator、粒子。物体速度用Rigidbody.velocity读取时,如果在暂停瞬间物理步进行到一半,恢复后速度会被插值重置,表现成失速后突然加速。还有一个隐蔽处:暂停后UI动画如果还用Time.deltaTime驱动,会完全不动。 - 解决:把UI动画和倒计时从
Time.deltaTime切到Time.unscaledDeltaTime,物理刚体在暂停时用Rigidbody.Sleep(),恢复时WakeUp();读速度时不要依赖单帧的velocity,多用两帧平滑:
float speed = rb.velocity.magnitude; float smoothSpeed = Mathf.Lerp(lastSpeed, speed, Time.unscaledDeltaTime * 10f);unscaledDeltaTime在Unity5.4以后可用,这就是针对游戏暂停和全屏卡顿场景的标准API。Mathf.Lerp的平滑系数按10Hz左右取值即可,太高会让读出的速度值抖动。要显示速度的UI,建议按真实速度保留一位小数,平滑值只用于指数条或特效,不要直接给玩家看,否则读出来和实际数值对不上。
4.4 手柄右摇杆失灵:Input Manager默认轴不够用
- 现象:同一套代码在Windows编辑器和安卓TV上表现不同,键鼠操控正常,手柄左摇杆正常,右摇杆完全没反应,或偶尔方向乱跳。
- 原因:Unity Input Manager默认只暴露
Horizontal和Vertical两组2D轴,左右摇杆共用一组,右摇杆通过第四轴读取,但默认工程没有定义对应轴名,导致GetAxisRaw("JoystickAxis3")返回恒0。 - 解决:在Player Settings的Input Manager里新增四根自定义轴,命名为
JoystickAxis3到JoystickAxis6,对应右摇杆和扳机;Negative Button和Positive Button留空,类型选Joystick Axis。如果项目同时要支持Xbox和PS手柄,轴顺序可能不同,稳妥做法是做一个手柄检测界面,运行时让玩家拨动摇杆识别并保存轴映射。
4.5 高宽比屏幕把UI裁出屏外
- 现象:iPhone X、三星折叠屏这类高宽比设备上,顶部按钮被刘海遮挡,底部按钮被Home条压住;切到分屏后一侧内容丢失。
- 原因:CanvasScaler的
ScaleWithScreenSize只保证设计的逻辑分辨率等比缩放,遇到刘海屏运算没有预留热区数据,内容锚点如果贴在屏幕顶部,就正好落在刘海区域。 - 解决:使用3.3节的SafeAreaFitter补偿顶部和底部边距,并在Canvas下加一套专用SafeArea面板存放所有关键操作。真机测试时专门准备几台异形屏设备,不要只在模拟器测试。这属于UI层验证的范畴,我在下一章会把它放进发布前的固定步骤。
5. 发布前三件必须坚持的事:真机矩阵、包体预算与构建留痕
到了这一步,源码层面的事情基本定型,最后一关在验证和习惯。我这些年做多平台发布,有三件事是雷打不动的。
第一,准备一个最小真机矩阵。桌面端留一台Win10、一台macOS;移动端留一台中端安卓、一台旗舰安卓、一台iPhone、一台iPad。不需要把市面上所有机型都买回来,但上面这个矩阵能覆盖90%的架构差异:x86_64、ARMv7、ARM64、iOS的Metal路径。每次发版前,在这个矩阵上把主流程手动走一遍,比自动化测试更早发现分辨率、输入延迟和功耗问题。
第二,给包体确定一个预算上限。Unity5时代的安装包如果能压到100MB以内,移动端很多渠道的审核和下载转化会友好很多。常用压缩顺序是:先用Build Report看各资源占比,纹理集成图集、音频压成Vorbis、剔除无用的场景依赖;再用LZ4HC把AssetBundle压到极致;最后把大配置从场景Prefab里剥出来,用ScriptableObject承载,避免整个场景因为一个配置改动整包重打。
第三,每次构建都留一份日志和产物清单。我习惯在BuildPipeline脚本里把构建时间、Unity版本、构建选项、输出包MD5写进一个build_manifest.txt:
var md5 = ComputeMd5(buildPath); File.WriteAllText( Path.Combine(root, "build_manifest.txt"), $"time={stamp}\nplatform={BuildTarget.Android}\nmd5={md5}" );这样三天后测试反馈某个包有bug,我能立刻知道这个包用的哪套代码、哪个平台、哪个构建选项,不用对着一个game_v13_最终版2.apk猜谜。这个习惯自从某次因为分不清两个包的代码差异,白白浪费了整晚后,就一直保持了下来。
给还在Unity5里挣扎的同行一个参考:别指望一套源码拿来就能跑。你拿到任何多平台源码,第一件事永远是按第二章的宏和目录把平台骨架重新过一遍,然后按第三章把输入和存档接回自己的项目,再按第四章的坑逐条排查,最后才谈得上优化和发布。把这套顺序固定成自己的构建习惯,多平台发布本身就会变成一件没那么玄学的事。希望帮到你。
本文还有配套的精品资源,点击获取