HybrydCLR. Cómo actualizar el código Unity de un juego sin descargar actualizaciones a la tienda / Sudo Null IT News

Imagen del sitio web oficial.

Imagen del sitio web oficial.

Introducción

Hoy quiero presentarles un complemento para Unity que les permite actualizar el código del juego sin cargar actualizaciones en la tienda. Funciona mediante una modificación de il2cpp, convirtiéndolo en algo parecido a Mono.

Enlace a la documentación

Hay muchas soluciones de actualización en caliente en el mercado, pero todas tienen restricciones en la interacción de la actualización en caliente y el código AOT, o solo pueden actualizar parte del código a través de atributos y otros tipos de granjas colectivas.

Como dicen los desarrolladores:

CLR híbrido es una solución nativa para la actualización en caliente del código C#. En pocas palabras, el código compilado il2cpp es equivalente al módulo aot en mono, y HybridCLR es equivalente al intérprete, y su combinación se vuelve mono completo. HybridCLR convierte a il2cpp en un entorno de ejecución con todas las funciones, de forma nativa (es decir, a través de System.Reflection.Assembly.Load), lo que permite que los archivos DLL se carguen dinámicamente, lo que hace posibles las actualizaciones en caliente en iOS.

Debido a que HybridCLR se implementa en el nivel de tiempo de ejecución nativo, los tipos de las bibliotecas de actualización en caliente y los tipos del código AOT son equivalentes y están perfectamente unificados. Puede llamar, heredar, usar reflexión y subprocesos múltiples, sin generar código ni escribir adaptadores.

Otras soluciones de actualización en caliente son independientes de VM y vincular con il2cpp es esencialmente equivalente a vincular la incrustación de lua en mono. Por tanto, el sistema de tipos no es uniforme. Para permitir que un tipo de actualización en caliente herede algunos tipos de AOT, se debe escribir un adaptador y el sistema de tipos del proyecto principal no puede reconocer el tipo en el intérprete. Funciones incompletas, desarrollo problemático y rendimiento deficiente.

Como dicen sobre la publicación en Google Play y App Store:

HybridCLR es muy popular en China, actualmente hay al menos cientos de juegos que usan HybridCLR, todos ellos están disponibles en App Store y Google Play.

HybridCLR tiene que ver con la interpretación y la ejecución, y desde esa perspectiva el enfoque no es diferente de la integración y el intérprete lua de Unity. Por lo tanto, cumple con los requisitos de App Store y Google Play Store y no existe ningún riesgo particular de rechazo. Y debido a la alta integración de HybridCLR e il2cpp, es incluso mucho más seguro que el esquema lua, por lo que la probabilidad de que el juego sea rechazado por incumplimiento de las reglas del sitio es muy baja.

Cambios en el esquema de ejecución de código personalizado

Cambios en el esquema de ejecución de código personalizado

HybridCLR hace lo siguiente:

  1. Implementación de una biblioteca de análisis de metadatos eficiente (dll)

  2. Cambios en el módulo de gestión de metadatos para implementar el registro dinámico de metadatos

  3. Implementación del compilador desde un conjunto de instrucciones IL hasta un conjunto de instrucciones de registro personalizado

  4. Implementación de un intérprete de registros eficiente

  5. Proporcionar una gran cantidad de funciones instintivas para mejorar el desempeño del intérprete.

Cosas interesantes:

  1. Código de puntos de actualización en caliente, pero es necesario instalar su complemento. Unitech realizó una inicialización temprana de TypeManager sin la posibilidad de llamarlo manualmente; los chicos tuvieron que agregarlo ellos mismos. Aún no se ha entregado el soporte Burst, pero Jobs está funcionando y actualizándose

  2. actualización en caliente de código asincrónico

  3. aprendizaje cero y costos de uso cero

Instalación

https://hybridclr.doc.code-philosophy.com/en/docs/beginner/quickstart

Instale el complemento a través del Administrador de paquetes:

Enlace al complemento

A continuación, inicializamos el complemento:

Necesitas inicializar el complemento.

Necesitas inicializar el complemento.

Haga clic en instalar

Haga clic en instalar

Configuración

Como ejemplo, tomaré mi proyecto necro de 2019:

En Unity, todo el código que escribe un desarrollador que no está en otra definición de ensamblaje está contenido en Assembly-CSharp. El código del proyecto debe dividirse en una compilación AOT (es decir, compilada en el paquete principal del juego) y una compilación de actualización en caliente. En HybridCLR no hay restricciones sobre cómo dividir el ensamblado, e incluso el código de un proyecto de terceros se puede utilizar como una actualización en caliente. Cuando se inicia el juego, al menos un ensamblado de AOT debe ser responsable del trabajo asociado con la ejecución del código de actualización en caliente. Hay dos formas de configurar el código personalizado de ensamblaje AOT y actualización en caliente, según la configuración actual de su proyecto:

  • Assembly-CSharp como punto de entrada AOT. El resto del código en sí se divide en ensamblajes N AOT y ensamblajes de actualización en caliente M.

  • Assembly-CSharp como ensamblado de actualización en caliente. El resto del código en sí se divide en ensamblajes N AOT y ensamblajes de actualización en caliente M.

En el ejemplo, crearemos una definición de ensamblaje Maikn y el código de actualización en caliente estará en Assembly-CSharp, después de lo cual se cargará la escena o prefabricado, que contiene el código de actualización en caliente con el punto de entrada.

Puede usar cualquier cosa como sistema para administrar recursos y cargar activos: incluso AssetBundles básicos, incluso Addressables o incluso su propio sistema de administración de activos con carga desde su servidor.

En este ejemplo, usaré Addressables como la opción más sencilla de integración y configuración, que no lleva mucho tiempo (la mayoría de las veces esto no es cierto, por supuesto).

Aclaración importante: si parte del código se incluye en la actualización en caliente, no debería haber ningún vínculo explícito entre la parte AOT y la parte de la actualización en caliente. Aquí, o su proyecto tiene una división clara en áreas de responsabilidad por escenas/prefabricados/definición de ensamblaje, o todo su proyecto está en actualización en caliente y cargado desde un punto de entrada que contiene solo código AOT.

En el ejemplo, se utilizará Assembly-CSharp como punto de entrada de AOT con la parte de actualización en caliente cargada e inicializada.

Para hacer esto, creemos una Definición de ensamblaje principal y arrastremos todo el código a ella:

Cree una carpeta en Ensamblaje principal

Cree una carpeta en Ensamblaje principal

A continuación, creemos un ensamblaje principal y arrastremos todo el código dentro de la carpeta:

Creando ensamblaje principal

Creando ensamblaje principal

A continuación, creemos 3 grupos de direccionables:

  1. Grupo para nuestras bibliotecas de actualización activa (en este caso Principal)

  2. Grupo de metadatos. Los metadatos son parte de las dependencias externas de la DLL, que se consumen al eliminarlas debido a que en el momento del ensamblaje el proyecto no tiene dependencias de ninguna de ellas (debido al hecho de que el código se transfiere a la actualización en caliente desde el parte del proyecto compilado en el momento de la asamblea de la AOT).

  3. Grupo para la escena del juego, que cargará el código AOT desde los direccionables.

Grupos direccionables creados

Grupos direccionables creados

A continuación, en Configuración del proyecto, en la pestaña de configuración de HybridCLR, agregue la biblioteca de actualización en caliente creada anteriormente a la lista Definiciones de ensamblaje de actualización en caliente:

Agregue la biblioteca principal a la lista

Agregue la biblioteca principal a la lista

Puede actualizar utilizando la actualización en caliente solo aquellas bibliotecas que estaban en la lista en el momento en que se creó el proyecto. Relativamente hablando, si creamos una apk para Android y queremos agregar otro Dll Core, esto no será posible.

Para esto, existe un campo especial Preservar ensamblajes de actualización en caliente, en el que puede escribir los nombres de las bibliotecas que se pueden agregar en el futuro:

Puede actualizar no solo el código de usuario, sino también complementos de terceros, por ejemplo, UniTask, Dotween, etc., así como archivos DLL normales que ya están importados al proyecto en forma compilada.

Creemos una escena Hot Reload en la que se colgarán el cargador de escenas y las bibliotecas con metadatos:

Creando una escena HotReload

Creando una escena HotReload

Creo un repositorio simple en páginas de github para alojar archivos estáticos (no escribiré instrucciones, es fácil de encontrar), creo un perfil de direcciones, lo actualizo y establezco enlaces en su configuración:

Rutas expuestas a páginas de alojamiento de github en el perfil de Addressables

Rutas expuestas a páginas de alojamiento de github en el perfil de Addressables

Expongo rutas de compilación y carga de DLLS y DLLSMetadata en forma remota en los grupos de Direcciones:

Me aseguro de que los activos de este grupo se descarguen desde una dirección remota

Me aseguro de que los activos de este grupo se descarguen desde una dirección remota

Por si acaso, configuro el modo de nomenclatura del paquete en DLLS y DLLSMetadata Filename para que el hash de nomenclatura no cambie constantemente:

No olvide configurar Crear catálogo remoto en Configuración de activos direccionables:

A continuación, realizaremos la generación inicial de todos los datos de Hybrid CLR. El proceso puede llevar tiempo, no te alarmes.

A continuación, en cualquier carpeta del Editor, cree el siguiente script, que creará bibliotecas de actualización en caliente, las actualizará en carpetas y recopilará direcciones. El script utiliza extensiones que estarán disponibles a través del enlace en el repositorio al final del artículo.


(MenuItem("Finiki Games/Build/HybridCLR/Build hybrid clr fresh"))
public static void BuildHybridCLRFresh() {
    // Создаем installer hybrid clr
    var installerController = new HybridCLR.Editor.Installer.InstallerController();
    // Проверяем, был ли он проинициализирован
    if (!installerController.HasInstalledHybridCLR()) {
        installerController.InstallDefaultHybridCLR();
    }

    // Вызываем основную сборку
    MainBuild();

    // Собираем Addressables
    AddressableAssetSettings.BuildPlayerContent();
}

(MenuItem("Finiki Games/Build/HybridCLR/Build hybrid clr update"))
public static void BuildHybridCLRUpdate() {
    // Вызываем основную сборку
    MainBuild();

    // Создаем входящие настройки для сборки и обновления Addressables
    var input = new AddressablesDataBuilderInput(AddressableAssetSettingsDefaultObject.Settings);
    var updateBuild = new AddressablesBuildMenuUpdateAPreviousBuild();
    updateBuild.OnPrebuild(input);
    AddressableAssetSettings.BuildPlayerContent(out AddressablesPlayerBuildResult _);
}

private static void MainBuild() {
    // Генерируем всю необходимую информацию для Hybrid CLR
    HybridCLRExtensions.GenerateAllLite(true, BuildTarget.Android);

    string projectPath = Application.dataPath;
    projectPath = projectPath.Replace($"/Assets", "");

    var hybridCLRConfig = FindFirstAssetByType<HybridCLRConfig>();

    // Получаем список DLLS
    var assemblies = HybridCLR.Editor.SettingsUtil.HotUpdateAssemblyFilesExcludePreserved;
    var assembliesUrl = HybridCLR.Editor.SettingsUtil.GetHotUpdateDllsOutputDirByTarget(BuildTarget.Android);

    var settings = HybridCLR.Editor.SettingsUtil.HybridCLRSettings;

    foreach (var assemblyName in assemblies) {
        if (settings.hotUpdateAssemblies.Contains(assemblyName.Replace(".dll", ""))) continue;
        var fullAssemblyPath = projectPath + "\\" + assembliesUrl + "\\" + assemblyName;
        
        var newAssemblyPath = RenameFile(fullAssemblyPath, assemblyName + ".bytes");

        var inProjectAssemblyPath =
            projectPath + "\\" + hybridCLRConfig.DllPath + "\\" + assemblyName + ".bytes";
        // Копируем каждый скомпилированный файл в проект
        MoveFile(newAssemblyPath, inProjectAssemblyPath);
    }

    // Получаем список DLLSMetadata
    var metadataAssemblies = hybridCLRConfig.MetadataAssemblyList;
    var metadataUrl = HybridCLR.Editor.SettingsUtil.GetAssembliesPostIl2CppStripDir(BuildTarget.Android);

    foreach (var metadataAssemblyName in metadataAssemblies) {
        var fullAssemblyPath = projectPath + "\\" + metadataUrl + "\\" + metadataAssemblyName;
        
        var newAssemblyPath = RenameFile(fullAssemblyPath, metadataAssemblyName + ".bytes");

        var inProjectAssemblyPath =
            projectPath + "\\" + hybridCLRConfig.MetadataPath + "\\" + metadataAssemblyName + ".bytes";
        // Копируем каждый скомпилированный файл в проект
        MoveFile(newAssemblyPath, inProjectAssemblyPath);
    }
}

Creemos un script HotReloadEntry en el que cargaremos todos los DLLS y Metadatos por Etiqueta, luego de lo cual cargaremos la escena:

public class HotReloadEntry : MonoBehaviour {
        public AssetReference StartSceneReference;

        private void Awake() {
            LoadDLLS().Forget();
        }

        public async UniTask LoadDLLS() {
            try {
                // Загружаем все библиотеки по Label
                var dlls = await HotReloadAddressableAssetService.LoadByLabel<TextAsset>("DLLS");

                foreach (var dll in dlls) {
#if !UNITY_EDITOR
                    // Загружаем библиотеки
                    Assembly hotUpdateAss = Assembly.Load(dll.bytes);
#endif
                }
            }
            catch (Exception e) {
                Debug.LogError($"Load DLLs error: {e.Message}");
            }

            try {
                // Загружаем все метаданные по Label
                var supplementaryMetadataDlls =
                    await HotReloadAddressableAssetService.LoadByLabel<TextAsset>("DLLSMetadata");

                foreach (var dll in supplementaryMetadataDlls) {
#if !UNITY_EDITOR
                    // Загружаем метаданные через runtime библиотеку hybrid clr
                    var err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly(dll.bytes, HomologousImageMode.SuperSet);
                    Debug.Log($"LoadMetadataForAOTAssembly");
    #endif
                }
            }
            catch (Exception e) {
                Debug.LogError($"Load Metadata DLLs error: {e.Message}");
            }

            // Загружаем сцену, нак которой находится hot reload код
            await StartSceneReference.LoadSceneAsync();
        }
      }

Colguemos este script en el objeto creado en la escena HotReload y anulemos el enlace a la escena principal del juego, que contiene todo el código de actualización en caliente:

Colguemos el script en la etapa HotReload en un nuevo objeto.

Colguemos el script en la etapa HotReload en un nuevo objeto.

Para agregar automáticamente recursos y metadatos de la biblioteca, usaremos el importador de direcciones, que, de acuerdo con las reglas creadas, agrega recursos de la carpeta especificada al grupo Direcciones. Elimina la necesidad de escribir controladores en el script de compilación para configurar el grupo manualmente.

Enlace al importador direccionable

Cree un archivo de configuración:

Crear un archivo de configuración

Crear un archivo de configuración

Completamos el archivo de configuración para que agregue todos los archivos de las carpetas a un grupo de Direcciones específico y los marque con Etiqueta. Primero se deben crear las etiquetas y carpetas requeridas.

Configurar reglas para agregar a un grupo

Configurar reglas para agregar a un grupo

A continuación, llamamos al código para ensamblar el proyecto, que agregamos a cualquier carpeta del Editor en el proyecto:

Convocando a la asamblea

Después de una compilación exitosa, los archivos DLLS y DLLSMetadata deberían aparecer en el proyecto. En la carpeta ServerData (dependiendo de dónde seleccionó el conjunto de activos que deben cargarse de forma remota), aparecerá un catálogo remoto y activos que deberán cargarse en un alojamiento estático (en nuestro caso, páginas de github).

Metadatos después de una compilación exitosa

Metadatos después de una compilación exitosa

Bibliotecas después de una construcción exitosa

Bibliotecas después de una construcción exitosa

Estos archivos deben cargarse en su hosting.

Estos archivos deben cargarse en su hosting.

Luego recopilamos un archivo apk normal y comprobamos su funcionalidad. Si el juego se inicia y se descargan los scripts, todo funcionará igual que antes.

En el proyecto elegí cambiar a Hybrid CLR, cuando un pollo se topa con una moneda, la moneda desaparece. Hagamos que se duplique cuando golpee una moneda:

public void OnCoinGrab(GameObject coin) {
    var position = coin.transform.position;

    EffectManager.Instance.PlayCoinEffect(position);
    AudioManager.Instance.PlayCoinEvent();

    coin.transform.localScale *= 2;
    //coin.SetActive(false);
}

Luego llamamos:

Subimos archivos nuevos de ServerData al almacenamiento estático y vemos el comportamiento actualizado en el juego sin reconstruir la apk:

Si tienes alguna dificultad durante el proceso de integración o tienes alguna duda, escríbeme por telegram. Lea la documentación del complemento, hay mucha más información allí que la que proporcioné en el artículo.

Enlace al repositorio con todo el código del artículo

Nos vemos a todos)

Más información y anuncios de artículos en mi canal de telegram, donde hablo de Unity https://t.me/dedpilit.

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *