1. 第一次看到 Unity 的 Windows 构建产物时,别急着懵——先搞懂那一堆文件是什么
很多 Unity 开发者的 Windows 构建初体验是这样的:点击 Build,等进度条跑完,打开输出目录,瞬间傻眼。一个 exe 后面跟着一个巨大的_Data文件夹,旁边还躺着UnityCrashHandler64.exe、UnityPlayer.dll、MonoBleedingEdge目录,以及一堆不知道干嘛的 dll。第一反应往往是“这玩意儿怎么发?直接把整个文件夹拖进微信吗?”
先别急,你得先明白一件事:Unity 的 Windows 构建从来不是给你一个单文件 exe,而是一个完整的运行时生态目录。那个 exe 只是个启动器,真正的游戏本体全部在_Data文件夹里。
1.1 构建输出里的主角都有谁
以 Unity 2022 LTS 默认的 Mono 构建为例,一个典型输出目录大概长这样:
MyGame.exe MyGame_Data/ - boot.config - globalgamemanagers - level0 - level1 - resources.assets - sharedassets0.assets - streamingassets/ - Managed/ - Assembly-CSharp.dll - UnityEngine.CoreModule.dll - System.dll - Plugins/ - x86_64/ - Frameworks/ - Mono/ - lib/ - etc/ MonoBleedingEdge/ - embedruntime/ - etc/ UnityCrashHandler64.exe UnityPlayer.dll这里面的关键角色:
| 文件/目录 | 作用 | 能不能删 |
|---|---|---|
MyGame.exe | 启动入口,负责初始化 Unity 运行时,加载 Data 目录 | 不能 |
MyGame_Data | 游戏核心资产、脚本编译产物、场景数据全在这 | 完全不能 |
UnityPlayer.dll | Unity 引擎本体,所有渲染、物理、音频逻辑都在里面 | 不能 |
MonoBleedingEdge | Mono 运行时环境,托管代码的“虚拟机” | 不能,删了直接起不来 |
UnityCrashHandler64.exe | 崩溃上报工具,游戏崩了它会弹窗收集信息 | 可以删,但建议留 |
这里最反直觉的地方在于:你以为的“主程序”exe,在 Unity 里其实只是个壳。真正干活的是UnityPlayer.dll,exe 只负责把UnityPlayer.dll拉起来,然后由引擎去_Data里加载资源。所以你把 exe 单独拷到别的目录想跑起来,得到的只会是“应用程序无法正常启动”之类的报错。
曾经有人在客户现场把
MyGame.exe单独拷到桌面,双击后报0xc000007b,然后打电话问我是不是程序写崩了。其实就是把 exe 和_Data分家了。
1.2 为什么 Unity 死活不给你一个“单个 exe”
这个问题几乎每个 Unity 开发者都问过。你去看某些独立游戏,一个 exe 走天下,那是因为很多开发框架(尤其是一些最终用户很熟悉的国产 GameMaker 类工具或纯 C++ 写的程序),它们可以把所有资源全部内嵌进二进制文件里。Unity 的架构决定了它的资源加载方式是“按需加载”,AssetBundle、Resources、StreamingAssets这些机制都依赖于外部文件系统,如果全塞进 exe,后果是:
- 内存映射失效:Unity 的资源加载不全是读磁盘,有些是直接做内存映射的。把资源塞进 exe 就破坏了这个机制,加载速度会明显变慢。
- 增量更新没法做:游戏发了一个补丁,只有 10MB,如果所有东西都在一个 exe 里,玩家就得重新下载整个 exe。分开存放,补丁只替换对应资源文件就可以。
- Unity 官方技术上根本不支持:Build Settings 里没有任何一个选项能把 Windows 构建打成单文件。
所以别纠结了,这不是你做错了什么,是架构本来就如此。认清这一点,你就该把精力放在“怎么把这一堆文件体面地交给客户”上。
1.3 别删任何文件:那些看似冗余的 dll 和资源到底在干嘛
有人为了“精简”会手动删掉UnityCrashHandler64.exe、MonoBleedingEdge里的某些文件、_Data/Managed里用不到的 dll——然后程序就崩了。这里我梳理一下哪些能碰、哪些不能碰:
_Data/Managed里的 dll,不能碰。即使你的代码没用到某个系统程序集,运行时也可能通过反射加载它。手动删了某个“看起来没用”的 dll,可能只会在特定功能触发时炸。UnityCrashHandler64.exe,可以删,删了只是崩溃时没有漂亮的弹窗报告。但我不建议删,因为当客户说“游戏打不开”时,这个 crash handler 能帮你抓崩溃日志。MonoBleedingEdge里的文件,千万别动。libmono-2.0-bdwgc.dll这类文件是 Mono 虚拟机的核心。见过有人为了缩减体积把它删了,结果双击 exe 报“缺少 DLL”,压根没进游戏。
我见过最惨烈的案例是有人把_Data/Plugins/x86_64里自己接入的 C++ 插件 dll 当垃圾删了,结果游戏跑起来一切正常,直到调用某个功能时直接内存报错。所以记住:构建产物的每一部分都是运行系统的一部分,不是“可以清理的垃圾”。
2. 发给客户前的第一课:压缩打包的姿势与坑
搞清楚产物结构后,你面临的实际问题还是那个:怎么把这一堆东西交给客户?最简单粗暴的方式当然是压缩成 zip 发过去。但这里面有几个坑,不提前安排好,客户体验会很差。
2.1 直接右键“压缩到 zip”真的够用吗
如果你是做个 Demo 发给朋友看一下,或者甲方那边有基本的文件操作能力,zip 完全够用。但你需要注意几个点:
- 别用 Windows 自带的“发送到压缩文件夹”在压缩时选择“存储”模式——默认压缩模式没问题,但某些杀软实时扫描大压缩包时会拖慢速度。更推荐 7-Zip 或 Bandizip。
- 压缩前检查 Total 大小:Unity 的 Windows 构建普遍在 200MB 起步,如果你的项目有大量高清贴图、音频,几个 GB 也正常。超过 2GB 的 zip 通过微信或 QQ 传输会非常痛苦,这时候你就得考虑网盘或者做体积优化(后面会专门聊)。
- 确定客户电脑有没有解压软件:Windows 11 自带 zip 解压,Windows 10 也没问题,但如果客户是更早的系统,不一定能双击打开 zip。这时候自解压格式就派上用场了。
2.2 解压路径、中文路径、杀毒软件误报这些历史遗留问题
这块我必须单独拎出来说,因为踩过的人太多了。
路径坑:客户把游戏解压到C:\Users\张三\Desktop\我的游戏\,运行正常。但解压到C:\Users\张三\AppData\Local\Temp\xxx\之类的目录,某些版本可能出问题。更严重的场景是:解压到路径特别深的目录,比如C:\Windows\System32\config\systemprofile\AppData\Local\...,这时候 Windows 的“最大化路径长度限制”被超出,资源加载失败。给客户的建议永远是:放一个纯英文、无空格的路径,比如D:\MyGame。
杀软误报:Unity 打包出的 exe 加一堆 dll,很容易触发 Windows Defender 或 360 的启发式扫描报警。尤其是使用 IL2CPP 构建的产品,有段时间 Defender 报 HackTool 的概率不低。这事不是你能完全避免的,但可以降低概率:用正规签名证书签名 exe(后面详聊),或者至少保证构建时关闭“Development Build”。
中文路径问题:这里的“中文路径”不是指游戏不支持从中文路径启动,实际上大部分情况下中文路径也没事。但是!如果客户用了解压即玩的绿色版,然后放在带某些特殊符号(如#、&、%)的目录里,资源加载的 Uri 解析有可能异常。保险起见还是顺嘴跟客户说一句“放英文目录”。
2.3 用 7-Zip 做自解压:让客户“双击一下就能玩”
如果客户属于“连解压都嫌麻烦”的类型,推荐用 7-Zip 制作自解压包。操作步骤:
- 安装 7-Zip。
- 选中整个构建输出目录(exe、
_Data、MonoBleedingEdge全部选中),右键 → 添加到压缩包。 - 压缩格式选
7z,压缩级别选极限(Ultra)。 - 勾选“创建自解压格式压缩文件”,扩展名会变成
.exe。 - 配置自解压选项:
- 解压路径填
{AppDir}\MyGame(意思是解压到当前目录的 MyGame 文件夹)。 - 解压后运行填
MyGame.exe。 - 显示模式选“隐藏全部”。
- 解压路径填
这样客户拿到一个MyGame_setup.exe,双击后自动解压到当前目录并启动游戏。原理说白了就是一个带图形界面的解压器,但使用体验会好很多。这个方法适合不要求安装到 Program Files 的绿色发布,比 Inno Setup 的安装包门槛低。
3. 更专业的交付方式:用 Inno Setup 做一个安装包
如果你是把游戏交付给企业客户、发行商,或者甲方要求“正儿八经有个安装程序”,那就别再甩 zip 了。用 Inno Setup 做一个安装包,专业感直接提升一个档次。
3.1 为什么推荐 Inno Setup 而不是其他安装工具
Windows 平台做安装包的工具有很多:InstallShield(贵、复杂,大型商业项目用)、WiX Toolset(基于 XML,学习成本高,但可以做 MSI)、NSIS(脚本式,灵活但写起来容易崩)、Inno Setup(免费、脚本式、文档齐全、支持 Pascal 脚本扩展)。
我个人在 Unity 交付场景里几乎只推荐 Inno Setup,原因很实际:
- 免费且轻量:InstallShield 动辄上万的授权费,纯做小交付没必要。
- 脚本简单:对于“把一堆文件复制到指定目录,创建快捷方式,写注册表”这种需求,用不到 WiX 那种学习曲线。
- 对 Unity 产物兼容好:不会因为文件多、文件长路径而卡死;你可以递归地添加整个目录。
- 生成的安装包体积小:安装器本身只有 1~2MB,不影响分发包体积。
3.2 一个能用的最小 Inno Setup 脚本长什么样
直接上脚本,假设你的 Unity 构建输出目录是C:\Builds\MyGame,我们给客户做安装包:
#define MyAppName "MyGame" #define MyAppVersion "1.0.0" #define MyAppPublisher "My Company" #define MyAppExeName "MyGame.exe" [Setup] AppId={{8A0DFB3C-5D0A-4A3E-8C71-5F0B4F0C9A12} AppName={#MyAppName} AppVersion={#MyAppVersion} AppPublisher={#MyAppPublisher} DefaultDirName={autopf}\{#MyAppName} DisableProgramGroupPage=yes OutputDir=C:\InstallerOutput OutputBaseFilename=MyGame_Setup_{#MyAppVersion} Compression=lzma2 SolidCompression=yes ArchitecturesInstallIn64BitMode=x64compatible PrivilegesRequiredOverridesAllowed=dialog [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" Name: "chinesesimp"; MessagesFile: "compiler:Languages\ChineseSimplified.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" [Files] Source: "C:\Builds\MyGame\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs [Icons] Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon [Run] Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent这段脚本完成的事情:
- 指定安装目录为 Program Files 下的
MyGame目录。 - 把
C:\Builds\MyGame下的所有文件递归复制到安装目录。 - 创建开始菜单快捷方式和可选桌面快捷方式。
- 安装完成后询问是否启动游戏。
关键点是recursesubdirs和createallsubdirs这两个标志,它们保证_Data这种多级目录结构被原样保留。如果你的 Unity 产品里有原生插件依赖 VC++ 运行库,还需要在脚本里加一个检测:
[Files] ; 假设你没让 Unity 自动带 VC++ 运行库,就手动带上 Source: "C:\Builds\MyGame\vcredist.x64.exe"; DestDir: "{tmp}"; Flags: deleteafterinstall [Run] Filename: "{tmp}\vcredist.x64.exe"; Parameters: "/quiet /norestart"; StatusMsg: "正在安装 VC++ 运行库..."; Flags: skipifdoesntexist3.3 安装路径、开始菜单快捷方式、卸载程序这些都要照顾
Inno Setup 默认会自动生成卸载程序。这一点对 Unity 游戏尤其重要,因为很多绿色版游戏删的时候会在注册表、AppData 里残留数据。有了卸载程序,客户可以干净地移除。
安装路径的选择:如果用{autopf}(Program Files),需要管理员权限,且杀毒软件更敏感。如果是小游戏,建议用{localappdata}或安装时让用户自己选目录。Unity 游戏跑在 Program Files 下偶尔会碰到“没有写权限导致存档失败”的问题(如果你的游戏存档写在安装目录里)。更稳的方式是安装到{autopf}但把存档、配置写到{userappdata}。这个架构决策应该在开发期就想好。
图标问题:Inno Setup 创建的快捷方式默认使用 exe 的图标,Unity 的 exe 默认是 Unity 图标。如果你做的是商业产品,记得在 Build Settings 里设置 Player Settings → Icon 为你的自定义图标,不然快接方式会露馅。
3.4 加装 VC++ 运行库和 DirectX 依赖的注意点
Unity 用到的原生依赖主要这几个:
- VC++ 运行库:Unity 引擎本身依赖 VC++ 2015-2022 Redistributable。多数机器上已经装了,但新装的精简版 Windows 可能缺失。很多 Unity 游戏启动报
0xc000007b或缺少 VCRUNTIME140.dll就是这个原因。 - DirectX:Unity 的 DX11 渲染大多调用系统自带的 DirectX。Windows 10/11 自带 DirectX 12 也能兼容跑 DX11,但老的 Windows 7 或 Win8 可能需要装 DirectX End-User Runtime。
在 Inno Setup 里处理这个问题有两条路:
- 让 Unity 自动带:Build Settings → Player Settings → XR Plug-in Management(不是这个),实际上 Unity 没有自动带 VC++ 运行库的选项。所以需要手动处理。
- 手动在 Inno 脚本里内置运行库安装,或检测注册表,缺失时触发下载、安装。上面脚本里的
vcredist.x64.exe方案是主流做法。
如果你做的是普通单机游戏,建议直接[Run]调用系统安装命令,而不是静默安装。静默安装失败时用户根本不知道发生了什么。让安装界面明确提示“正在安装 VC++ 运行库”,会降低很多售后成本。
4. 进阶:Build 选项与 IL2CPP/Mono 的选择对交付的影响
很多人在 Build Settings 里直接点 Build,什么都不改。但构建选项直接决定交付方式的复杂度。这里重点讲 Mono 和 IL2CPP 的区别,以及它对“发给客户”这件事的影响。
4.1 Mono 与 IL2CPP 的构建产物差异
| 对比项 | Mono | IL2CPP |
|---|---|---|
| 脚本执行方式 | 运行时 JIT | 预先编译为 C++,再编译为原生机器码 |
| 产物文件 | _Data/Managed/*.dll一堆托管 dll | Game_Data/Native/x86_64/Game.exe(其实主程序还是那个壳)+Game_Data/il2cpp_data |
| 体积 | 相对小 | 大很多,通常多 30%-50% |
| 启动速度 | 略慢 | 更快 |
| 性能 | 一般 | 更接近原生代码 |
| 反编译难度 | 用 dnSpy 能直接看 C# 源码 | 很难,只有汇编级别的代码 |
| 平台兼容性 | Windows/Linux/macOS 没问题 | 几乎全平台 |
在“发给客户”这个语境下,核心差异有两点:
一是反编译安全。你用 Mono 构建,交付给客户,意味着客户用 dnSpy 打开Assembly-CSharp.dll就能看到你的 C# 源码逻辑。即使加了混淆,也能看到结构。IL2CPP 把 C# 编译成 C++ 再转成原生二进制,代码很难被还原。商业项目、有资源保护需求的、不想被人扒代码的,直接用 IL2CPP。
二是体积。IL2CPP 的构建产物比 Mono 大得多。因为 IL2CPP 会把所有泛型实例化代码、所有被引用的引擎代码全部生成为原生代码,等于目标机器上不需要安装 .NET 运行库,但代价是包体大。不过 IL2CPP 有个优点:它生成的二进制可以裁剪掉很多用不到的代码( Managed Stripping Level 设为 High 时),整体体积差距没有想象中那么大。实际项目里,如果游戏不是特别大,我更倾向于 IL2CPP,因为交付省心,不会被扒代码、不需要客户机器装额外运行库。
4.2 裁剪与压缩:用 il2cpp 减小文件体积后哪些东西会出问题
Unity 在 Build Settings → Player Settings → Managed Stripping Level 里可以设置裁剪级别,从 Disabled 到 High。在 IL2CPP 模式下,High 裁剪可以删掉大量未被引用的托管代码,体积降低明显。但代价也明显:
- 反射调用可能被剪掉:如果你的代码里通过反射调用一个字符串指定的类,而该类没有被直接引用,High 裁剪会把它当成“没用”删掉。运行时抛
MissingMethodException。 - Unity 引擎内置的一些特殊功能(如某些 Editor 无关的序列化)可能被裁剪后表现异常。
- 第三方插件兼容性:某些老 SDK 没有做好裁剪兼容,High 裁剪后直接打不开或数组越界。建议先用 Medium,如果没问题再上 High。
我的建议是:先用默认裁剪级别构建,测试一遍核心流程,然后再调高裁剪级别,回归测试。别为了几十 MB 的体积把稳定性赔进去。
另外还有一个影响最终交付体积的点:纹理压缩格式。在 Player Settings → PC, Mac & Linux Standalone Settings 里,默认的 Default Texture Compression 是DXT5之类的格式。如果你没有特别设置,也可以不折腾。这个滑块对体积影响巨大,但涉及画面质量,一般不建议动。
4.3 关闭“Development Build”和“Auto Connect Profiler”这种低级却常见的失误
这个我放在这里讲,是因为很多人打包时不小心勾选了 Development Build,导致:
- 打包产物体积大很多(包含调试信息)。
- 性能明显下降(一堆调试代码在跑)。
- 客户反馈“卡”而你本地测不出来。
- 更可怕的是 Script Debugger 默认开启,游戏启动时会尝试连接 Profiler,如果连不上也不至于崩,但会有性能开销。
打包之前,请务必确认 Build Settings 窗口里:
- Development Build不勾选。
- Autoconnect Profiler不勾选。
- Script Debugging不勾选。
这个低级失误出现频率极高。我一个做外包的朋友,交付版本忘关 Development Build,客户装上后跑了两天说“这游戏帧率不行”,查了半天才发现是调试模式。所以每次打包前,我建议固定走一套“发布前检查清单”:
| 检查项 | 正确状态 |
|---|---|
| Development Build | 关闭 |
| Autoconnect Profiler | 关闭 |
| Copy PDB Files | 可选,通常关 |
| Compression Method | 默认或根据平台 |
| Scripting Backend | IL2CPP(交付用) |
| Api Compatibility Level | .NET Standard 2.1 / .NET Framework(按需) |
| Managed Stripping Level | Medium 起步 |
5. 对方双击 exe 没反应、闪退、黑屏的排查清单
“我打包出来好好的,发给客户就打不开了”——这句话堪称 Unity 开发者被售后问题淹没的第一大来源。你本地能跑,不代表客户机器能跑。硬件配置、系统组件、显卡驱动、权限设置都可能坑。这里给一份实操排查清单。
5.1 事件查看器里的 Unity Player 崩溃记录怎么读
客户反馈“双击没反应”或“闪退”时,先让他打开 Windows 事件查看器:
Win + R→ 输入eventvwr.msc→ 回车 → Windows 日志 → 应用程序
里面会有红色错误记录,来源是Application Error或.NET Runtime,错误信息里会带出崩溃模块和异常代码。常见几种:
| 崩溃模块 | 异常代码 | 大概率原因 |
|---|---|---|
UnityPlayer.dll | 0xc0000005 | 内存访问冲突,可能是显卡驱动或插件问题 |
KERNELBASE.dll | 0xe0434352 | .NET 运行时异常,托管代码崩溃 |
igd10umd64.dll | 任意 | 老款 Intel 核显驱动问题,很常见 |
Unknown | 0xc000007b | 依赖库缺失,比如 VC++ 运行库 |
读事件查看器有个技巧:看 Faulting module name 而不是只看 exe 名字。因为主程序 exe 只是壳,真正的崩溃往往在 UnityPlayer.dll 或某个系统 dll 里。这个信息能帮你把问题定位到“是引擎问题还是系统环境问题”。
5.2 显卡/驱动的锅:为什么在某些老机器上打不开
Unity 默认用 DirectX 11 渲染(Unity 2022 上 Windows 默认也会带 DX12 选项但自动回退)。老显卡或驱动太旧,可能不支持 DX11,就会黑屏、卡启动界面甚至直接崩。
解决办法是打包时在 Graphics APIs 里加上兼容选项:
路径:Project Settings → Player → Other Settings → Graphics APIs
建议把顺序设成:
Direct3D11(如果担心兼容性,可以把它放在最前面)Direct3D12(现代显卡优先)OpenGLCore(备用)
或者反过来:想要新特性就 DX12 优先,想最大化兼容就 DX11 优先。注意 Graphics APIs 有自动回退机制,但前提是你列出来的 API 系统里有。把 OpenGLCore 放在最后兜底,可以减少老机器打不开的概率。
另外,显卡驱动更新这种话跟客户说了等于没说,他们说“我会”但九成不会。你只能在构建层面尽量兜底。
还有一个容易忽略的点:Windows 和 Unity 版本兼容性。Unity 2020/2021 构建的产品通常能在 Win7 上跑(前提你还带着对应的运行库),但 Unity 2022 的某个版本后,官方明确停止了对 Win7 的支持。如果你交付的客户群体里还有 Win7,记得用旧 LTS 版本构建,或构建一个 Win7 兼容版本(如果你必须要用新版,一定要实测)。
5.3 日志文件 Player.log 的使用方法
Unity 游戏运行时会写日志到固定位置。当客户说“闪退”或“卡在某个界面”,最有效的办法是让他把日志发回来。
路径:
- Windows:
C:\Users\<用户名>\AppData\LocalLow\<公司名>\<产品名>\Player.log
公司名和产品名对应 Player Settings 里的 Company Name 和 Product Name。告诉客户怎么找文件,可以让他按Win + R输入%USERPROFILE%\AppData\LocalLow\<公司名>\<产品名>\,直接把 Player.log 拖出来发给你。
日志能告诉你:
- 崩溃前的最后几条日志(
.LogError调用记录)。 - 是不是加载某个 AssetBundle 失败。
- 是不是某个插件初始化失败抛了异常。
- 显卡设备信息(渲染器型号、驱动版本)都会打在最开头。
举个例子,我之前接过一个售后案例,客户玩到第二关卡死。日志一看,挂在一句NullReferenceException,是因为我的一个对象在某种场景加载顺序下没初始化。没有日志,这种问题只能靠猜。
6. 分发中的几个容易被忽略的细节
这部分内容不涉及代码,但经验不足的开发者几乎都会踩到。
6.1 杀毒软件误报与避免误报的打包方式
杀毒误报是 Unity 开发者的“老朋友”。尤其是加了 IL2CPP 后,exe 二进制特征更容易被启发式扫描误判。遇到 Defender / 360 / 金山毒霸报毒,基本处理方式:
- 关闭 Development Build(上面提过)。
- 不使用压缩壳:有些人为了缩小 exe 体积会用 UPX 压缩,这会让杀软更容易报毒。Unity 的构建本身就够复杂了,别再套壳。
- 代码签名:买个代码签名证书(OV 或 EV),给 exe 签名。签名后 Defender 的误报概率会显著降低,因为系统能识别发布者身份。
- 构建时选择干净的环境:有些杀软会把你自己机器上测试用的注入了某种调试 hook 的 exe 也标记为威胁。最好在打发布包时关闭第三方杀软或使用虚拟机。
别期待 100% 不误报,但至少能把“通知客户关闭杀软”这种沟通成本降到最低。
6.2 版本号、更新、渠道水印这些小提醒
正式交付时,版本管理如果没做好,会非常丢人。
在 Player Settings 里设置版本号:Version字段对应构建出来的 exe 的文件版本,客户右键属性-详细信息里能看到。如果你没有设置,默认是 1.0,客户反馈问题时你连他用的是哪一版都不知道。
如果游戏需要联网更新或走 Steam / 其他平台,版本号管理更要规范:1.0.0、1.1.0、1.1.1这种三段式,并保证和你的发布记录对应。
渠道水印:如果你同时给多个客户定制版本,建议在 UI 上加一个难以察觉的版本标识或水印,这样客户 A 把安装包传给客户 B,你能一眼看出是从哪个版本流出去的。游戏行业在泄漏追踪时常用这招,简单有效。
还有一个细节:Unity 构建产物的“公司名”和“产品名”。它们不仅影响日志路径,还会影响注册表项。如果你上一版叫MyCompany、MyGame,下一版改成了AnotherCompany、OtherGame,客户的存档位置和注册表就可能对不上,出现“升级后进度丢失”的假象。发布前定好,以后不要随便改。
7. 结尾:个人经验与偏好的分发工作流
每次给客户交付 Unity Windows 项目,我基本都走同一套流程。如果项目规模小、客户不太懂技术,就用7-Zip 自解压 + 英文目录 + 杀软签名组合,让客户双击一个 exe 就能玩。如果对方是企业客户或走正式发行,就上 Inno Setup 做安装包,把 VC++ 运行库、快捷方式、卸载程序都处理到位,再配一份简单的部署说明文档。
构建配置上,我个人的默认偏好是:IL2CPP + Medium Stripping + DX11/DX12/OpenGL 三选一 + 关闭 Development Build。遇到文件体积过于膨胀的项目,再用资源压缩工具和裁剪级别调整来压低体量,但锦上添花的事避免在交付前最后一刻才做。
最后分享一个小技巧:不管用什么方式交付,自己先从客户视角完整跑一遍流程。找一个没有装过 Unity、配置和客户机器差不多的机器,把交付包安装、启动、玩十分钟、卸载,完整走一遍。这样至少能把“缺 dll”“路径中文”“杀软误拦”这些最恼火的问题提前挡在门外。很多时候客户口中的“这游戏打不开”,真实情况只是安装包没找到入口或解压错了目录——分发这件事,细节确实比想象中更能决定成败。