【Unity实战】Jenkins打包机联调脚本

本文介绍了一个GitHub上的开源脚本,用于在Jenkins中简化Unity项目使用HybridCLR的热更新构建过程,包括编译DLL、AOT元数据处理以及与Addressables的集成,支持多种平台构建。
摘要由CSDN通过智能技术生成

本文章主要围绕本人在Github上的一个开源脚本:

GitHub - Shepherd0619/JenkinsBuildUnity: A little script that connect Unity (with HybridCLR hot update) and Jenkins together.

这个脚本是一个用于在Jenkins中构建Unity项目的辅助工具。它的主要功能是构建HybridCLR热更新,并将生成的DLL文件和AOT元数据DLL文件复制到指定的目录,并将它们添加到Unity的Addressable Assets系统中。

如果您还不知道Jenkins和HybridCLR的话,建议先阅读一下往期博客和相关官方文档。

【Unity实战】HybridCLR热更快速集成-CSDN博客

Unity与Jenkins打包机实战-CSDN博客

脚本讲解

首先,我们来看一下这个脚本的结构。它是一个继承自MonoBehaviour的类,并且包含了一些静态方法用于构建热更新(也必须得是静态,否则的话后续Jenkins通过命令行调起Unity会比较麻烦,找不到这个函数)。

// JenkinsBuild
// Shepherd Zhu
// Jenkins Build Helper
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using HybridCLR.Editor;
using HybridCLR.Editor.Commands;
using HybridCLR.Editor.Settings;
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Build;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;

public class JenkinsBuild : MonoBehaviour
{
    // 重要提醒:建议先在工作电脑上配好Groups和Labels,本脚本虽说遇到新文件可以添加到Addressables,但是不太可靠。

    [MenuItem("Shepherd0619/Build Hot Update")]
    /// <summary>
    /// 开始执行HybridCLR热更打包,默认打当前平台
    /// </summary>
    public static void BuildHotUpdate()
    {
        BuildHotUpdate(EditorUserBuildSettings.activeBuildTarget);
    }

    /// <summary>
    /// 开始执行HybridCLR热更打包
    /// </summary>
    /// <param name="target">目标平台</param>
    public static void BuildHotUpdate(BuildTarget target)
    {
        
    }

    public static void BuildHotUpdateForWindows64()
    {
        BuildHotUpdate(BuildTarget.StandaloneWindows64);
    }

    public static void BuildHotUpdateForiOS()
    {
        BuildHotUpdate(BuildTarget.iOS);
    }

    public static void BuildHotUpdateForLinux64()
    {
        BuildHotUpdate(BuildTarget.StandaloneLinux64);
    }

    public static void BuildHotUpdateForAndroid()
    {
        BuildHotUpdate(BuildTarget.Android);
    }

    /// <summary>
    /// 将热更DLL加入到Addressables
    /// </summary>
    /// <param name="dllPath">DLL完整路径</param>
    private static void SetHotUpdateDllLabel(string dllPath)
    {
        
    }

    /// <summary>
    /// 将AOT元数据DLL加入到Addressables
    /// </summary>
    /// <param name="dllPath">DLL完整路径</param>
    private static void SetAOTMetadataDllLabel(string dllPath)
    {
        
    }

    private static bool buildAddressableContent()
    {
        
    }
}

整个脚本的核心是`BuildHotUpdate`方法。这个方法接受一个`BuildTarget`参数,用于指定构建的目标平台。在方法中,首先打印出正在构建的目标平台,然后依次执行一系列构建热更新所需的命令。

/// <summary>
    /// 开始执行HybridCLR热更打包
    /// </summary>
    /// <param name="target">目标平台</param>
    public static void BuildHotUpdate(BuildTarget target)
    {
        Console.WriteLine(
            $"[JenkinsBuild] Start building hot update for {Enum.GetName(typeof(BuildTarget), target)}"
        );
        try
        {
            CompileDllCommand.CompileDll(target);
            Il2CppDefGeneratorCommand.GenerateIl2CppDef();

            // 这几个生成依赖HotUpdateDlls
            LinkGeneratorCommand.GenerateLinkXml(target);

            // 生成裁剪后的aot dll
            StripAOTDllCommand.GenerateStripedAOTDlls(target);

            // 桥接函数生成依赖于AOT dll,必须保证已经build过,生成AOT dll
            MethodBridgeGeneratorCommand.GenerateMethodBridge(target);
            ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper(target);
            AOTReferenceGeneratorCommand.GenerateAOTGenericReference(target);
        }
        catch (Exception e)
        {
            Console.WriteLine(
                $"[JenkinsBuild] ERROR while building hot update! Message:\n{e.ToString()}"
            );
            return;
        }

        // 复制打出来的DLL并进行替换
        string sourcePath = Path.Combine(
            Application.dataPath,
            $"../{SettingsUtil.GetHotUpdateDllsOutputDirByTarget(target)}"
        );
        string destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs");

        if (!Directory.Exists(sourcePath))
        {
            Console.WriteLine(
                "[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
            );
            return;
        }

        if (!Directory.Exists(destinationPath))
        {
            Console.WriteLine(
                "[JenkinsBuild] Destination directory does not exist! Abort the build!"
            );
            return;
        }

        // string[] dllFiles = Directory.GetFiles(sourcePath, "*.dll");
        
        // foreach (string dllFile in dllFiles)
        // {
        //     string fileName = Path.GetFileName(dllFile);
        //     string destinationFile = Path.Combine(destinationPath, fileName + ".bytes");
        //     Console.WriteLine($"[JenkinsBuild] Copy: {dllFile}");
        //     File.Copy(dllFile, destinationFile, true);
        // }

        List<string> hotUpdateAssemblyNames = SettingsUtil.HotUpdateAssemblyNamesExcludePreserved;
        for (int i = 0; i < hotUpdateAssemblyNames.Count; i++)
        {
            Console.WriteLine($"[JenkinsBuild] Copy: {hotUpdateAssemblyNames[i] + ".dll"}");
            File.Copy(sourcePath + "/" + hotUpdateAssemblyNames[i] + ".dll", Path.Combine(destinationPath, hotUpdateAssemblyNames[i] + ".dll.bytes"), true);
        }

        Console.WriteLine("[JenkinsBuild] Hot Update DLLs copied successfully!");

        // 复制打出来的AOT元数据DLL并进行替换
        Console.WriteLine("[JenkinsBuild] Start copying AOT Metadata DLLs!");
        sourcePath = Path.Combine(
            Application.dataPath,
            $"../{SettingsUtil.GetAssembliesPostIl2CppStripDir(target)}"
        );
        destinationPath = Path.Combine(Application.dataPath, "HotUpdateDLLs/AOTMetadata");

        if (!Directory.Exists(sourcePath))
        {
            Console.WriteLine(
                "[JenkinsBuild] Source directory does not exist! Possibly HybridCLR build failed!"
            );
            return;
        }

        if (!Directory.Exists(destinationPath))
        {
            Console.WriteLine(
                "[JenkinsBuild] Destination directory does not exist! Abort the build!"
            );
            return;
        }

        // 获取AOTGenericReferences.cs文件的路径
        string aotReferencesFilePath = Path.Combine(
            Application.dataPath,
            SettingsUtil.HybridCLRSettings.outputAOTGenericReferenceFile
        );

        if (!File.Exists(aotReferencesFilePath))
        {
            Console.WriteLine(
                "[JenkinsBuild] AOTGenericReferences.cs file does not exist! Abort the build!"
            );
            return;
        }

        // 读取AOTGenericReferences.cs文件内容
        string[] aotReferencesFileContent = File.ReadAllLines(aotReferencesFilePath);

        // 查找PatchedAOTAssemblyList列表
        List<string> patchedAOTAssemblyList = new List<string>();

        for (int i = 0; i < aotReferencesFileContent.Length; i++)
        {
            if (aotReferencesFileContent[i].Contains("PatchedAOTAssemblyList"))
            {
                while (!aotReferencesFileContent[i].Contains("};"))
                {
                    if (aotReferencesFileContent[i].Contains("\""))
                    {
                        int startIndex = aotReferencesFileContent[i].IndexOf("\"") + 1;
                        int endIndex = aotReferencesFileContent[i].LastIndexOf("\"");
                        string dllName = aotReferencesFileContent[i].Substring(
                            startIndex,
                            endIndex - startIndex
                        );
                        patchedAOTAssemblyList.Add(dllName);
                    }
                    i++;
                }
                break;
            }
        }

        // 复制DLL文件到目标文件夹,并添加后缀名".bytes"
        foreach (string dllName in patchedAOTAssemblyList)
        {
            string sourceFile = Path.Combine(sourcePath, dllName);
            string destinationFile = Path.Combine(
                destinationPath,
                Path.GetFileName(dllName) + ".bytes"
            );

            if (File.Exists(sourceFile))
            {
                Console.WriteLine($"[JenkinsBuild] Copy: {sourceFile}");
                File.Copy(sourceFile, destinationFile, true);
                //SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/" + Path.GetFileName(dllName) + ".bytes");
            }
            else
            {
                Console.WriteLine("[JenkinsBuild] AOTMetadata DLL file not found: " + dllName);
            }
        }

        AssetDatabase.SaveAssets();

        Console.WriteLine("[JenkinsBuild] BuildHotUpdate complete!");

        AssetDatabase.Refresh();

		// 刷新后开始给DLL加标签
		//SetHotUpdateDllLabel("Assets/HotUpdateDLLs/Assembly-CSharp.dll.bytes");
        for (int i = 0; i < hotUpdateAssemblyNames.Count; i++)
        {
            SetHotUpdateDllLabel("Assets/HotUpdateDLLs/" + hotUpdateAssemblyNames[i] + ".dll.bytes");
        }

		foreach(string dllName in patchedAOTAssemblyList)
		{
			SetAOTMetadataDllLabel("Assets/HotUpdateDLLs/AOTMetadata/" + Path.GetFileName(dllName) + ".bytes");
		}

		Console.WriteLine("[JenkinsBuild] Start building Addressables!");
		buildAddressableContent();
    }

在`BuildHotUpdate`方法中,首先调用`CompileDllCommand.CompileDll`方法来编译DLL文件。然后调用`Il2CppDefGeneratorCommand.GenerateIl2CppDef`方法来生成Il2Cpp的定义文件。接下来,调用`LinkGeneratorCommand.GenerateLinkXml`方法来生成链接文件。然后,调用`StripAOTDllCommand.GenerateStripedAOTDlls`方法来生成裁剪后的AOT DLL文件。接着,调用`MethodBridgeGeneratorCommand.GenerateMethodBridge`方法和`ReversePInvokeWrapperGeneratorCommand.GenerateReversePInvokeWrapper`方法来生成桥接函数和反向PInvoke包装器。最后,调用`AOTReferenceGeneratorCommand.GenerateAOTGenericReference`方法来生成AOT泛型引用。

在执行完所有的构建命令后,脚本会将生成的DLL文件和AOT元数据DLL文件复制到指定的目录。这里使用了`File.Copy`方法来实现复制。复制完成后,脚本会打印出复制成功的消息。

接下来,脚本会将生成的DLL文件和AOT元数据DLL文件添加到Unity的Addressable Assets系统中。这里使用了`AddressableAssetSettings`类来实现。首先通过`AddressableAssetSettingsDefaultObject.Settings`属性获取到Addressable Assets的设置对象,然后通过`FindGroup`方法找到指定的Group,接着使用`CreateOrMoveEntry`方法创建或移动Asset Entry,并将其添加到指定的Group中。最后,通过设置Entry的标签和地址来完成添加操作。

当然这一通操作完,得必须SetDirty以通知Unity这块有改动。

/// <summary>
    /// 将热更DLL加入到Addressables
    /// </summary>
    /// <param name="dllPath">DLL完整路径</param>
    private static void SetHotUpdateDllLabel(string dllPath)
    {
        var settings = AddressableAssetSettingsDefaultObject.Settings;
        AddressableAssetGroup group = settings.FindGroup("DLLs");
        var guid = AssetDatabase.AssetPathToGUID(dllPath);
        if (settings.FindAssetEntry(guid) != null)
        {
            Console.WriteLine(
                $"[JenkinsBuild.SetHotUpdateDLLLabel] {dllPath} already exist in Addressables. Abort!"
            );
            return;
        }
        var entry = settings.CreateOrMoveEntry(guid, group);
		entry.labels.Add("default");
        entry.labels.Add("HotUpdateDLL");
        entry.address = Path.GetFileName(dllPath);
        settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
    }

    /// <summary>
    /// 将AOT元数据DLL加入到Addressables
    /// </summary>
    /// <param name="dllPath">DLL完整路径</param>
    private static void SetAOTMetadataDllLabel(string dllPath)
    {
        var settings = AddressableAssetSettingsDefaultObject.Settings;
        AddressableAssetGroup group = settings.FindGroup("DLLs");
        var guid = AssetDatabase.AssetPathToGUID(dllPath);
        if (settings.FindAssetEntry(guid) != null)
        {
            Console.WriteLine(
                $"[JenkinsBuild.SetAOTMetadataDLLLabel] {dllPath} already exist in Addressables. Abort!"
            );
            return;
        }
        var entry = settings.CreateOrMoveEntry(guid, group);
		entry.labels.Add("default");
        entry.labels.Add("AOTMetadataDLL");
        entry.address = Path.GetFileName(dllPath);
        settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entry, true);
    }

最后,脚本会调用`buildAddressableContent`方法来构建Addressable Assets的内容。这里使用了`AddressableAssetSettings.BuildPlayerContent`方法来实现构建。构建完成后,脚本会打印出构建结果,并返回构建是否成功的标志。

private static bool buildAddressableContent()
    {
        string path = Path.Combine(Application.dataPath, "../ServerData/"+Enum.GetName(typeof(BuildTarget), EditorUserBuildSettings.activeBuildTarget));
        if(Directory.Exists(path)){
            Directory.Delete(path, true);
        }

        AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult result);
        bool success = string.IsNullOrEmpty(result.Error);

        if (!success)
        {
            Console.WriteLine("[JenkinsBuild.buildAddressableContent] Addressables build error encountered: " + result.Error);
        }
        return success;
    }

以上就是这个脚本的主要功能和实现逻辑。通过这个脚本,我们可以方便地在Jenkins中构建Unity项目的热更新,并将生成的DLL文件和AOT元数据DLL文件添加到Unity的Addressable Assets系统中。

命令行参数样例

Unity.exe -nographics -batchmode -quit -executeMethod JenkinsBuild.BuildHotUpdateForWindows64

这个会只打热更新,不会打完整客户端。

更多Unity编辑器命令行参数,请查阅官方文档
Unity - Manual: Unity Editor command line arguments

希望这篇博客能给你带来一些思路。

(理论上我应该出个图文讲解视频,但是Hmm,我实在是在视频剪辑这块拉夸的一批)

  • 21
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity中打包WebGL应用程序时,可以通过JavaScript与网页中的DOM元素进行交互,从而实现手机在WebGL应用程序中拉起键盘的功能。 首先,在Unity中创建一个JavaScript文件。在该文件中,可以使用document.getElementById()函数获取到对应的DOM元素,并通过调用该元素的focus()函数来拉起键盘。例如,如果想要在用户点击一个按钮时拉起键盘,可以在JavaScript文件中编写以下代码: ```javascript function LaunchKeyboard() { var inputField = document.getElementById("inputField"); inputField.focus(); } ``` 然后,在Unity中创建一个UI按钮,并将其与上述的JavaScript函数关联起来。可以通过在按钮的OnClick事件处理程序中调用JavaScript方法来实现。在Unity的UI系统中,可以在Button组件的OnClick事件列表中添加一个新的事件,并将其设为调用JavaScript函数。例如,可以将上述的LaunchKeyboard()函数与按钮的OnClick事件关联起来。 最后,将Unity项目打包为WebGL。在打包的过程中,要确保已将上述的JavaScript文件包含在生成的HTML文件中。这可以通过在Unity中的Build Settings中,点击Player Settings按钮,在Inspector窗口中的WebGL Template字段中选择Default,并勾选Custom Template选项来实现。 通过上述步骤,当在Unity应用程序中点击与JavaScript函数关联的按钮时,就会调用JavaScript代码,使WebGL应用程序拉起手机键盘。注意,为了实现该功能,需要确保设备支持WebGL,并且在WebGL应用程序中正确设置了DOM元素的ID。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值