1 前言
关于TeamCity的安装教程以及Unity插件安装无需赘述,参考:
工作流篇(2): TeamCity,15分钟搭建U3d CI环境,自动测试、版本发布、部署
目前项目热更新方案是使用的HybridCLR;资源管理使用的是YooAsset. 整个构建过程是囊括了以上两个方案的流程。
2 版本控制
2.1 VCS checkout
目前项目分支结构为 v1/release、v1/hotfix。出包使用release分支,同时将release分支内容同步到hotfix分支,后续热更新往hotfix分支提交。需要出新包的时候就是新建v2/release分支。
起初是准备在TeamCity上面创建两个Build,分别监听hotfix和release分支 为了省一点硬盘空间 将两个Build的目录指向同一个,但两个不同Build启动的时候会将当前目录清空,重新拉取仓库内容,unity切换平台导入资源每次都会耗时2个小时,所以后来hotfix、release共用一个Build,构建步骤用条件进行区分


头三个checkout模式在两个不同的Build任务执行时都会将当前的目录重新checkout,最后作罢选择了两个流程共用一个Build。
2.2 Artifact
jenkins中每个step可以做一些成功或失败后的操作,TeamCity这边暂时没发现。所以release分支及hotfix分支的构建产物我直接在 Artifact paths中一起配置了,构建成功之后没收集到对应规则的产物会有警告,但不会终止当前构建。
例:
1 2
| +:release/%build.number%.apk +:Bundles/Android/%build.number% => %build.number%
|
3 构建流程
3.1 出包
流程整理如下图

出包期间会执行到HybridCLR与YooAsset的构建过程
3.1.1 流程大概
HybridCLR:
- 出一次包:获取裁剪后的dll
- 生成桥接函数(如果有新增)
- 编译hotfix部分的dll
- 重新出包
YooAsset的大概流程为:
- 调用AssetBundleBuilder的Run方法就好了(需要提前给到一些参数的,例如:构建模式[增量、重构];资源版本号等)
3.1.2 配置步骤
HybridCLR
- 编译DLL:CustomBuild.BuildDLL
- 编译HotFix工程:CustomBuild.BuildHotfixProject
- 导出工程: CustomBuild.Build
YooAsset
- CustomBuild.BuildInternal
生成APK
Git提交代码
- TeamCity不支持push操作,所以需要自己写命令行提交
- 在构建过程中手动push会触发配置的分支Triggers,所以先禁用Trigger,但build就变成了半自动
挖个坑:尝试使用TeamCity的Dependencies,让增量更新依赖上次的构建,这样就不用从git上拉取之前的版本内容了
Gradle任务中的自定义参数在Additional Gradle command line parameters中填写,需要注意的是参数前面需要追加一个“-P”不留空格。这样就可以将值传到AndroidStudio工程gradle.properties
中的配置字段了

伪代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| public class CustomBuild { public static void BuildDLL() { string[] args = System.Environment.GetCommandLineArgs(); var outDir = System.Environment.CurrentDirectory + "/../output/" + buildTarget.ToString(); string[] scenes = new[] { "Assets/Main.unity" }; PlayerSettings.SetScriptingBackend(buildTargetGroup, ScriptingImplementation.IL2CPP); BuildPipeline.BuildPlayer(scenes, outDir, buildTarget, BuildOptions.CompressWithLz4); }
public static void BuildHotfixProject(BuildTarget target) { HybridCLR.Editor.CompileDllCommand.CompileDll(target);
string dllPath = $"{HybridCLR.Editor.SettingsUtil.GetHotFixDllsOutputDirByTarget(target)}/{dll}"; var myPath = "xxx/xxx/xxx.bytes"; File.Copy(dllPath, myPath, true); }
public static void BuildInternal() { string[] args = System.Environment.GetCommandLineArgs();
string defaultOutputRoot = AssetBundleBuilderHelper.GetDefaultOutputRoot(); BuildParameters buildParameters = new BuildParameters(); buildParameters.VerifyBuildingResult = true; buildParameters.OutputRoot = defaultOutputRoot; buildParameters.BuildTarget = buildTarget; buildParameters.BuildVersion = buildVersion; buildParameters.CompressOption = ECompressOption.LZ4; buildParameters.AppendFileExtension = true; buildParameters.BuildMode = buildType; AssetBundleBuilder builder = new AssetBundleBuilder(); builder.Run(buildParameters); }
|
TeamCity中有Unity插件 所以只需要在Execute method处填上对应的方法即可,其他追加的参数填在Command line arguments处。

在jenkins中使用命令行操作
1
| "%unity%" -quit -batchmode -logFile log/${UNITY_LOG_BUILD_DLL} -projectPath ../unity -executeMethod CustomBuild.BuildDLL ${BUILD_TARGET}
|
3.2 资源更新

资源更新部分其实就是把打包过程中YooAsset部分的构建模式换为增量,以及给一个版本号即可,YooAsset的增量更新每次都是把所有文件打出来了,为了节省上传时间及本地磁盘占用 我在YooAsset的任务中追加了一个剔除重复资源的BuildTask,每次打包的资源与首包进行对比。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| using System; using System.Collections.Generic; using System.IO; using UnityEngine;
namespace YooAsset.Editor { [TaskAttribute("删除重复资源")] public class TaskDeleteDuplicate : IBuildTask { void IBuildTask.Run(BuildContext context) { var buildParameters = context.GetContextObject<AssetBundleBuilder.BuildParametersContext>(); var buildMode = buildParameters.Parameters.BuildMode; if (buildMode == EBuildMode.IncrementalBuild && buildParameters.Parameters.DeleteDuplicate) { ComparePatch(buildParameters); } } private void ComparePatch(AssetBundleBuilder.BuildParametersContext buildParameters) { string packageDirectory = buildParameters.GetPackageDirectory(); PatchManifest patchManifest1 = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory, 1); PatchManifest patchManifest2 = AssetBundleBuilderHelper.LoadPatchManifestFile(buildParameters.PipelineOutputDirectory, buildParameters.Parameters.BuildVersion); foreach (var patchBundle2 in patchManifest2.BundleList) { if (patchManifest1.Bundles.TryGetValue(patchBundle2.BundleName, out PatchBundle patchBundle1)) { if (patchBundle2.Hash == patchBundle1.Hash) { string delPath = $"{packageDirectory}/{patchBundle2.Hash}"; File.Delete(delPath); } else { Debug.Log($"变动文件:{patchBundle2.BundleName}"); } } else { Debug.Log($"新增文件:{patchBundle2.BundleName}"); } } Debug.Log("重复资源删除完成!"); } } }
|
先结束