1. 项目概述与核心价值
在Unity3D项目开发中,尤其是涉及到持续迭代更新的游戏或应用,版本管理是一个看似微小却至关重要的环节。你有没有遇到过这样的场景:测试同事反馈了一个Bug,你修复后打了个新包发过去,对方却问“这是哪个版本?和我刚才测的是同一个包吗?”。或者,线上版本出现了一个问题,你需要快速确认用户当前运行的究竟是哪个具体的构建版本,以便在代码仓库中精准定位对应的提交记录。如果只是在打包时手动修改文件名,不仅效率低下,而且极易出错。
这个功能的核心价值,就是将项目的构建版本号动态、自动地显示在游戏运行时界面中。它解决的远不止是“显示一串文字”这么简单,而是打通了“开发构建”与“运行时状态”之间的信息壁垒。对于开发团队内部,它能避免版本混淆,提升测试和沟通效率;对于线上产品,它是问题排查和版本追踪的关键依据。实现这个功能,我们通常会利用Unity提供的PlayerSettings.bundleVersion或从Application类中获取版本信息,并结合UI系统进行展示。下面,我将从一个资深开发者的角度,拆解几种主流、稳定且可扩展的实现方案,并分享在实际项目中容易踩到的“坑”和最佳实践。
2. 版本号信息源与获取方式解析
在动手写代码之前,我们必须清楚Unity为我们提供了哪些版本信息来源,以及它们各自的含义和适用场景。选对信息源,是功能稳定可靠的前提。
2.1 Application.version:最直接的运行时版本号
Application.version是最常用、最直接的属性,它返回的值就是在Player Settings中设置的Version字段。
原理与操作路径:在Unity Editor中,你可以通过菜单栏Edit -> Project Settings -> Player打开Player设置面板。在Other Settings部分,找到Version输入框。你在这里填写的字符串,就是打包后Application.version所读取的内容。这个值会直接写入到构建出的应用程序的配置信息中(例如,在Windows上是.exe文件的属性详情,在Android上是AndroidManifest.xml里的versionName)。
重要注意事项:
直接使用
Application.version虽然方便,但它有一个关键限制:它在Editor模式下运行游戏时,获取到的是Project Settings里配置的版本号,而不是最后一次打包的版本号。这意味着,如果你在Editor里按Play按钮测试,显示的版本号可能并不是你最终发布包的那个版本。对于需要严格区分“开发中版本”和“已发布版本”的场景,这一点需要特别留意。
2.2 使用自定义脚本动态生成版本号
为了获得更精确、包含更多构建信息的版本号(例如集成Git提交哈希、构建时间等),我们通常会编写一个构建后处理脚本,在打包过程中动态生成并写入版本信息。
核心思路:利用Unity的IPostprocessBuildWithReport接口,在构建完成后,自动生成一个包含版本信息的数据文件(如JSON、Text或ScriptableObject),并将其包含在构建资源中。游戏运行时,再读取这个文件来显示版本号。
一个基础的构建后处理脚本示例:
using UnityEngine; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; using System.IO; using System; public class BuildVersionWriter : IPostprocessBuildWithReport { public int callbackOrder => 0; public void OnPostprocessBuild(BuildReport report) { // 定义版本信息 var versionInfo = new VersionInfo { // 使用Player Settings中的版本作为基础 baseVersion = Application.version, // 获取当前时间作为构建时间 buildTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss 'UTC'"), // 可以在这里调用命令获取Git提交哈希(需要系统支持) // gitCommitHash = GetGitCommitHash(), // 构建类型,如Development, Release buildType = EditorUserBuildSettings.development ? "Development" : "Release", // Unity版本 unityVersion = Application.unityVersion }; // 将版本信息序列化为JSON string json = JsonUtility.ToJson(versionInfo, true); // 确定输出路径:放在Resources文件夹下,以便运行时使用Resources.Load加载 string resourcesPath = Path.Combine(Application.dataPath, "Resources"); if (!Directory.Exists(resourcesPath)) { Directory.CreateDirectory(resourcesPath); } string filePath = Path.Combine(resourcesPath, "BuildVersion.json"); File.WriteAllText(filePath, json); AssetDatabase.Refresh(); // 刷新AssetDatabase,让Unity识别新文件 Debug.Log($"构建版本信息已写入: {filePath}"); } // 示例方法:获取Git提交哈希(需确保系统环境支持git命令) private string GetGitCommitHash() { // 这是一个简化示例,实际使用中需要考虑跨平台和错误处理 try { var process = new System.Diagnostics.Process(); process.StartInfo.FileName = "git"; process.StartInfo.Arguments = "rev-parse --short HEAD"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.CreateNoWindow = true; process.Start(); string output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); return output.Trim(); } catch (Exception) { return "Unknown"; } } } // 用于存储版本信息的可序列化类 [System.Serializable] public class VersionInfo { public string baseVersion; public string buildTime; public string gitCommitHash; public string buildType; public string unityVersion; }实操要点:
- 脚本放置:这个脚本需要放在Editor文件夹下,例如
Assets/Editor/BuildVersionWriter.cs。只有放在Editor文件夹中的脚本才会在Unity编辑器中编译执行,而不会被打包到运行时。 - 接口说明:
IPostprocessBuildWithReport是Unity提供的构建后处理接口,callbackOrder属性决定了执行顺序(数字越小越先执行),OnPostprocessBuild方法会在每次构建成功完成后被调用。 - 文件路径:我们将生成的
BuildVersion.json文件放在Assets/Resources目录下。这是因为Resources文件夹内的资源会被Unity特殊处理,打包时会包含进去,并且可以通过Resources.Load接口在运行时无需路径直接加载,非常方便。 - 安全与兼容性:获取Git信息的代码 (
GetGitCommitHash) 在实际项目中需要更健壮的错误处理,并且要考虑到团队中并非所有成员的开发环境都配置了Git命令行工具。一种更稳妥的做法是将其设为可选功能,或者通过CI/CD流水线在打包时传入这些信息。
2.3 读取自定义版本信息文件
构建时生成了信息文件,运行时就需要读取它。我们创建一个通用的版本号管理器。
using UnityEngine; using System.IO; public class VersionManager : MonoBehaviour { // 单例模式,方便全局访问 private static VersionManager _instance; public static VersionManager Instance { get { if (_instance == null) { GameObject go = new GameObject("VersionManager"); _instance = go.AddComponent<VersionManager>(); DontDestroyOnLoad(go); // 跨场景不销毁 _instance.Initialize(); } return _instance; } } // 公开的属性供其他脚本访问 public string FullVersionString { get; private set; } public string BuildTime { get; private set; } public string CommitHash { get; private set; } private void Initialize() { LoadVersionInfo(); } private void LoadVersionInfo() { // 首先尝试加载自定义的构建信息文件 TextAsset versionFile = Resources.Load<TextAsset>("BuildVersion"); if (versionFile != null) { VersionInfo info = JsonUtility.FromJson<VersionInfo>(versionFile.text); if (info != null) { // 组合完整的版本字符串,格式可按需定制 FullVersionString = $"{info.baseVersion} ({info.buildType})"; if (!string.IsNullOrEmpty(info.gitCommitHash) && info.gitCommitHash != "Unknown") { FullVersionString += $" - Git: {info.gitCommitHash}"; } BuildTime = info.buildTime; CommitHash = info.gitCommitHash; Debug.Log($"已加载构建版本信息: {FullVersionString}"); return; } } // 如果自定义文件不存在或加载失败,则回退到Application.version Debug.LogWarning("未找到自定义构建版本文件,将使用Application.version。"); FullVersionString = $"Ver: {Application.version} (Editor/Unknown Build)"; BuildTime = "N/A"; CommitHash = "N/A"; } // 提供一个快速获取显示用字符串的方法 public string GetDisplayString() { return $"Version: {FullVersionString}\nBuild: {BuildTime}"; } }这个管理器做了几件事:首先,它尝试从Resources/BuildVersion.json加载我们打包时生成的信息。如果成功,就解析并使用这些丰富的信息组合成完整的版本字符串。如果加载失败(比如在Editor中直接运行),则优雅地降级到使用Application.version,并标记为“Editor/Unknown Build”,这样在开发阶段也能清晰地区分。
3. 在游戏UI中动态显示版本号
获取到版本信息后,下一步就是将其展示给玩家或测试人员。这里我们结合Unity的UGUI系统,实现一个灵活、可配置的版本号显示组件。
3.1 创建UI显示组件
我们创建一个VersionDisplay组件,它可以挂载在任何包含Text或TextMeshPro - Text组件的UI对象上。
using UnityEngine; using UnityEngine.UI; // 如果是UGUI Text // 如果使用TextMeshPro,需要引入对应的命名空间 // using TMPro; public class VersionDisplay : MonoBehaviour { public enum DisplayMode { FullInfo, // 显示完整信息(版本号+构建时间) VersionOnly, // 只显示版本号 CustomFormat // 自定义格式 } [Header("显示设置")] public DisplayMode displayMode = DisplayMode.FullInfo; [Header("自定义格式")] [Tooltip("使用 {0} 代表版本号,{1} 代表构建时间,{2} 代表提交哈希")] [SerializeField] private string customFormat = "v{0} | {1}"; [Header("UI组件引用")] [SerializeField] private Text versionText; // UGUI Text引用 // [SerializeField] private TMP_Text versionTextTMP; // TextMeshPro引用,二选一 private void Start() { // 自动获取Text组件(如果未手动赋值) if (versionText == null) { versionText = GetComponent<Text>(); } // 对于TextMeshPro,取消下方注释 // if (versionTextTMP == null) // { // versionTextTMP = GetComponent<TMP_Text>(); // } UpdateVersionDisplay(); } public void UpdateVersionDisplay() { string displayString = ""; switch (displayMode) { case DisplayMode.FullInfo: displayString = VersionManager.Instance.GetDisplayString(); break; case DisplayMode.VersionOnly: displayString = $"Version: {VersionManager.Instance.FullVersionString}"; break; case DisplayMode.CustomFormat: displayString = string.Format(customFormat, VersionManager.Instance.FullVersionString, VersionManager.Instance.BuildTime, VersionManager.Instance.CommitHash); break; } // 更新UI文本 if (versionText != null) { versionText.text = displayString; } // 如果使用TextMeshPro // if (versionTextTMP != null) // { // versionTextTMP.text = displayString; // } } // 提供一个编辑器按钮,方便预览 #if UNITY_EDITOR [ContextMenu("预览版本显示")] private void PreviewInEditor() { // 在编辑器中模拟初始化VersionManager并更新显示 if (VersionManager.Instance != null) { UpdateVersionDisplay(); UnityEditor.EditorUtility.SetDirty(this); // 标记对象已修改,确保更改被保存 } } #endif }组件使用步骤:
- 在UI Canvas下创建一个Text对象(例如,在屏幕右上角创建一个空GameObject,添加
Text组件)。 - 将
VersionDisplay脚本挂载到这个GameObject上。 - 在Inspector窗口中,将自身的
Text组件拖拽到VersionDisplay脚本的Version Text字段上。 - 选择你想要的
Display Mode。如果选择CustomFormat,可以在输入框中按提示定义格式。
设计考量:为什么提供多种显示模式?因为在不同场景下需求不同。在游戏的“设置”或“关于”页面,你可能希望显示完整的构建信息;而在主界面角落的常驻水印,可能只需要简洁的版本号。自定义格式则提供了最大的灵活性,比如你可以格式化为v1.2.3.4567 | 2023-10-27这样的样式。
3.2 实现屏幕角落的常驻水印
对于需要始终显示的版本水印,我们还需要考虑UI适配和干扰问题。
public class VersionWatermark : MonoBehaviour { public Text versionText; // 关联的Text组件 public Vector2 screenMargin = new Vector2(10, 10); // 距离屏幕边缘的像素偏移 public TextAnchor alignment = TextAnchor.UpperRight; // 默认在右上角 private RectTransform rectTransform; private void Start() { rectTransform = versionText.GetComponent<RectTransform>(); UpdateWatermark(); } private void UpdateWatermark() { if (versionText == null) return; // 设置文本内容 versionText.text = $"v{VersionManager.Instance.FullVersionString}"; // 根据对齐方式设置锚点和位置 rectTransform.pivot = GetPivotFromAnchor(alignment); rectTransform.anchorMin = rectTransform.anchorMax = GetAnchorFromAnchor(alignment); // 根据锚点和边距计算位置 Vector2 anchoredPosition = Vector2.zero; switch (alignment) { case TextAnchor.UpperLeft: anchoredPosition = new Vector2(screenMargin.x, -screenMargin.y); break; case TextAnchor.UpperRight: anchoredPosition = new Vector2(-screenMargin.x, -screenMargin.y); break; case TextAnchor.LowerLeft: anchoredPosition = new Vector2(screenMargin.x, screenMargin.y); break; case TextAnchor.LowerRight: anchoredPosition = new Vector2(-screenMargin.x, screenMargin.y); break; // 可以扩展其他对齐方式... } rectTransform.anchoredPosition = anchoredPosition; // 可选:降低水印的视觉干扰 Color textColor = versionText.color; textColor.a = 0.7f; // 设置一定的透明度 versionText.color = textColor; versionText.fontStyle = FontStyle.Italic; // 使用斜体区分 } // 工具方法:将TextAnchor转换为对应的锚点和中心点 private Vector2 GetAnchorFromAnchor(TextAnchor anchor) { switch (anchor) { case TextAnchor.UpperLeft: return new Vector2(0, 1); case TextAnchor.UpperRight: return new Vector2(1, 1); case TextAnchor.LowerLeft: return new Vector2(0, 0); case TextAnchor.LowerRight: return new Vector2(1, 0); default: return new Vector2(1, 1); } } private Vector2 GetPivotFromAnchor(TextAnchor anchor) { // Pivot通常与Anchor对应,使文本向期望的方向扩展 return GetAnchorFromAnchor(anchor); } }这个水印脚本自动将文本定位到屏幕角落,并设置了半透明和斜体,使其既能提供信息,又不会过度干扰游戏主视觉。screenMargin参数让你可以微调水印与屏幕边缘的距离。
4. 高级应用与自动化集成
基础功能实现后,我们可以进一步探索如何将其融入现代游戏开发工作流,实现真正的自动化。
4.1 与CI/CD流水线集成(以Jenkins为例)
在团队协作和自动化构建环境中,版本号通常由CI/CD工具生成和管理。下面以Jenkins为例,展示如何将构建信息从Jenkins传递到Unity构建过程中。
思路:Jenkins在构建时,可以生成包含版本号、构建编号、Git分支、提交ID等信息的文件(如JSON),或者直接设置为环境变量。然后,我们通过Unity命令行构建参数,将这些信息传递给Unity,并在构建后处理脚本中读取和使用。
步骤一:在Jenkins中定义构建参数和环境变量在Jenkins的构建配置中,你可以使用插件(如Environment Injector Plugin)或直接通过Pipeline脚本定义变量:
pipeline { agent any environment { // 从Git获取短提交哈希 GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim() // 使用Jenkins的构建编号作为内部版本号 BUILD_NUMBER = "${env.BUILD_NUMBER}" // 组合成完整的版本字符串,例如 "1.0.0.125" BUNDLE_VERSION = "1.0.0.${BUILD_NUMBER}" // 构建时间 BUILD_TIMESTAMP = sh(script: 'date -u +"%Y-%m-%dT%H:%M:%SZ"', returnStdout: true).trim() } stages { stage('Build Unity') { steps { // 将环境变量作为参数传递给Unity命令行 bat """ "C:\\Program Files\\Unity\\Hub\\Editor\\2022.3.XXf1\\Editor\\Unity.exe" ^ -projectPath "." ^ -quit -batchmode -nographics ^ -executeMethod BuildScript.PerformBuild ^ -buildVersion ${BUNDLE_VERSION} ^ -buildNumber ${BUILD_NUMBER} ^ -gitCommit ${GIT_COMMIT_SHORT} ^ -buildTime ${BUILD_TIMESTAMP} """ } } } }步骤二:修改Unity构建脚本以接收参数我们需要修改之前的构建脚本,使其能够从命令行参数读取信息。
// BuildScript.cs - 放置于Assets/Editor目录下 using UnityEditor; using System; using System.Linq; public static class BuildScript { public static void PerformBuild() { // 从命令行参数获取版本信息 string buildVersion = GetArgument("-buildVersion"); string buildNumber = GetArgument("-buildNumber"); string gitCommit = GetArgument("-gitCommit"); string buildTime = GetArgument("-buildTime"); // 如果传入了自定义版本,则覆盖Player Settings if (!string.IsNullOrEmpty(buildVersion)) { PlayerSettings.bundleVersion = buildVersion; PlayerSettings.Android.bundleVersionCode = int.Parse(buildNumber); // Android版本码 // iOS的build number设置类似,略 } // 准备构建选项 BuildPlayerOptions buildOptions = new BuildPlayerOptions(); buildOptions.scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(); buildOptions.locationPathName = "Builds/MyGame.exe"; // 输出路径 buildOptions.target = BuildTarget.StandaloneWindows64; buildOptions.options = BuildOptions.None; // 执行构建 BuildPipeline.BuildPlayer(buildOptions); // 构建完成后,可以调用之前的BuildVersionWriter来生成包含详细信息的文件 // 这里需要将获取到的gitCommit, buildTime等信息传递给VersionInfo GenerateBuildInfoFile(buildVersion, buildNumber, gitCommit, buildTime); } private static string GetArgument(string name) { string[] args = Environment.GetCommandLineArgs(); for (int i = 0; i < args.Length; i++) { if (args[i] == name && i + 1 < args.Length) { return args[i + 1]; } } return null; } private static void GenerateBuildInfoFile(string version, string buildNum, string commit, string time) { // 创建VersionInfo对象并写入Resources目录的逻辑,与之前类似 // 但此时使用从命令行传入的参数 VersionInfo info = new VersionInfo { baseVersion = version, buildNumber = buildNum, gitCommitHash = commit, buildTime = time, buildType = EditorUserBuildSettings.development ? "Development" : "Release", unityVersion = Application.unityVersion }; // ... 写入文件的代码 ... } }通过这种方式,版本号的生成和注入完全由自动化流水线控制,确保了每次构建版本信息的唯一性和可追溯性。
4.2 扩展:在日志、错误报告和Analytics中集成版本号
版本号不应该只显示在UI上,将其集成到系统的其他部分能极大提升运维和调试效率。
集成到日志系统: 修改你的日志工具类,在每一条日志的开头或结尾自动附加当前版本号。
public static class GameLogger { public static void Log(string message, LogType type = LogType.Log) { string versionPrefix = $"[{VersionManager.Instance.FullVersionString}] "; string formattedMessage = versionPrefix + message; switch (type) { case LogType.Log: Debug.Log(formattedMessage); break; case LogType.Warning: Debug.LogWarning(formattedMessage); break; case LogType.Error: Debug.LogError(formattedMessage); break; } // 同时可以写入本地文件或发送到远程服务器 WriteToFile(formattedMessage); } }集成到错误上报: 当游戏发生未处理的异常时,在将错误信息发送到后端服务器(如Sentry, Bugsnag)时,务必包含版本号。
void OnEnable() { Application.logMessageReceived += HandleLog; } void HandleLog(string logString, string stackTrace, LogType type) { if (type == LogType.Exception || type == LogType.Error) { CrashReport report = new CrashReport { version = VersionManager.Instance.FullVersionString, buildTime = VersionManager.Instance.BuildTime, logMessage = logString, stackTrace = stackTrace, deviceInfo = SystemInfo.deviceModel, // ... 其他信息 }; SendReportToServer(report); } }集成到Analytics事件: 向游戏数据分析平台(如Unity Analytics, Firebase)发送自定义事件时,将版本号作为一个固定参数。
public void SendAnalyticsEvent(string eventName, Dictionary<string, object> parameters = null) { if (parameters == null) parameters = new Dictionary<string, object>(); // 确保每个事件都带有版本信息 parameters["game_version"] = VersionManager.Instance.FullVersionString; parameters["build_type"] = VersionManager.Instance.BuildType; // 需要在VersionInfo中增加此字段 // 调用Analytics SDK // Analytics.CustomEvent(eventName, parameters); }这样做的好处是,当你在分析后台查看数据或错误报告时,可以立即根据版本号进行筛选,快速定位某个特定版本的问题,分析不同版本的用户行为差异。
5. 常见问题、调试技巧与最佳实践
即使功能看似简单,在实际开发和团队协作中也会遇到各种问题。这里总结了一些典型场景和解决方案。
5.1 版本号不更新或显示错误
这是最常见的问题,通常由以下几个原因导致:
- Player Settings中的Version未修改:
Application.version直接读取此值。确保在打包前检查并更新它。 - 构建后处理脚本未执行或执行失败:检查脚本是否放在
Assets/Editor目录下,并且实现了正确的接口(如IPostprocessBuildWithReport)。查看Unity构建日志,确认是否有相关错误。 - Resources.Load 路径或文件名错误:确保构建脚本生成的文件路径是
Assets/Resources/BuildVersion.json,并且运行时加载的路径是Resources.Load<TextAsset>("BuildVersion")(注意没有后缀名)。 - 版本管理器单例未初始化:确保在显示版本号的UI脚本
Start()或Awake()方法被调用之前,VersionManager.Instance已经被访问过以触发初始化。一个稳妥的做法是在游戏启动的第一个场景中,就通过一个启动脚本访问一次VersionManager.Instance。
调试技巧:在VersionManager的LoadVersionInfo方法中加入详细的Debug.Log,打印出它尝试加载的路径、加载结果和最终决定的版本字符串。在Editor中播放和打真机包后分别测试,对比日志输出。
5.2 多平台构建的兼容性处理
不同平台对文件系统的访问权限和路径有差异,我们的方案需要具备跨平台兼容性。
- 文件路径:构建脚本中使用
Application.dataPath组合路径是安全的,它在Editor中指向Assets文件夹,在构建后指向只读的数据目录。而使用Resources.Load是Unity跨平台资源加载的标准方式,无需担心路径问题。 - Android/iOS平台注意事项:在这些移动平台上,
Resources文件夹内的资源在构建后会被压缩并打包到特定格式中(如.assets文件)。我们的JSON文件作为TextAsset被打包进去,Resources.Load可以正确读取,这没有问题。但要绝对避免在运行时尝试用System.IO.File去读写Application.dataPath下的原始文件,这在移动平台上是不可行的。 - 平台特定版本号:除了通用的
bundleVersion,各平台还有自己的版本号设置:- Android:
PlayerSettings.Android.bundleVersionCode(整数,每次提交市场必须递增)。 - iOS:
PlayerSettings.iOS.buildNumber(字符串)。 我们的构建脚本在接收命令行参数时,可以同时更新这些平台特定的版本号,确保应用商店后台的版本信息与游戏内显示的一致。
- Android:
5.3 版本号命名规范建议
一个清晰、自动化的版本号命名规则,能省去很多沟通成本。推荐使用语义化版本控制(Semantic Versioning)的变体,并结合构建号。
- 格式:
主版本号.次版本号.修订号.构建号(例如:1.2.3.4567)主版本号:重大更新,通常包含不兼容的API变更。次版本号:功能性更新,向下兼容。修订号:问题修复和小幅优化,向下兼容。构建号:由CI/CD系统自动生成的单调递增数字(如Jenkins的BUILD_NUMBER),用于唯一标识每次构建。
- 在CI/CD中实现:在Jenkins等工具中,
主版本号.次版本号.修订号可以作为一个配置项或从某个配置文件(如project.version)中读取,然后与BUILD_NUMBER拼接成完整版本。 - 游戏内显示:在UI上,你可能只显示
主版本号.次版本号.修订号,而在“关于”页面或日志中显示完整的带构建号的版本。VersionManager可以轻松提供这两种格式。
5.4 性能与内存考量
对于这样一个基础功能,性能开销通常可以忽略不计,但好的实践仍值得遵循:
- 懒加载与缓存:我们的
VersionManager在首次访问时加载并解析JSON文件,然后将结果缓存到属性中。之后多次访问FullVersionString等属性都是直接读取内存,没有重复的IO或解析操作,效率很高。 - 避免每帧更新:版本号在运行时是固定的,所以显示它的UI组件只在
Start()时更新一次即可,千万不要放在Update()里。 - Resources文件夹的使用:我们将版本文件放在
Resources文件夹内,这会使它在打包时被包含进一个统一的资源包。对于这种极小的文本文件来说完全可以接受。如果你的项目严格规定不使用Resources系统(出于资源管理策略),可以考虑使用Addressables或AssetBundle,但复杂度会大大增加,对于版本文件这种必备资源来说有些“杀鸡用牛刀”。
5.5 在团队中的协作规范
为了让这个功能在团队中顺畅运行,需要建立简单的规范:
- 脚本和预制体归档:将
VersionManager、VersionDisplay、BuildVersionWriter等脚本以及设置好的版本显示UI预制体,放在项目版本控制中一个公认的目录下(如Assets/_Core/Versioning/)。 - 构建流程文档化:在团队的Wiki或README中,说明版本号的生成规则、如何通过命令行参数传递版本信息,以及开发者在本地测试时需要注意什么(例如,理解Editor模式与真机模式的版本号差异)。
- UI预设:提供几个常用的版本显示UI预制体,比如“右上角半透明水印”、“关于页面完整信息框”、“调试菜单中的版本行”,方便团队成员直接拖拽使用,保持UI风格统一。
经过以上从原理到实践,从基础到进阶的详细拆解,这个“显示版本号”的小功能已经演变成一个健壮、可扩展、与自动化流程深度集成的开发基础设施。它不再是一个简单的文本显示,而是连接开发、构建、测试、运维各个环节的信息枢纽。实现它可能只需要一两个小时,但它为项目带来的长期秩序和效率提升,会远超你的投入。