欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Unity实战:2022年8月遇到的HybirdCLR和GameFramework问题及解决方案(华佗鲁班lutuan已解决)

最编程 2024-08-04 19:16:41
...

Unity-GameFramework+HybirdCLR-202208最新踩坑

    • Unity-GameFramework简介:
    • 下载项目
    • 坑位:
      • 1.error CS0227: Unsafe code may only appear if compiling with /unsafe. Ena…
      • 2.帧率和垂直同步
      • 3.核心Procedure*.cs和GameEntry.cs
      • 4.配置相关BuildEventHandler.cs和GameFrameworkConfigs.cs
      • 5.打AssetBundle和打包
        • 5.1如果是UnityEditor(不使用AssetBundle)
        • 5.1如果是UnityEditor(使用AssetBundle)
    • 运行:
    • Github项目地址:
    • 支持华佗热更[借鉴完整实现博客](https://blog.****.net/final5788/article/details/125965514)
    • TODO全项目GameFramework+HybirdCLR替换

Unity-GameFramework简介:

在这里插入图片描述
官网

Game Framework 是一个基于 Unity 引擎的游戏框架,主要对游戏开发过程中常用模块进行了封装,很大程度地规范开发过程、加快开发速度并保证产品质量。

适用于所有 Unity 5.3.0 及以上的版本,即包括 5.3.x、5.4.x、5.5.x、5.6.x、2017.x.x、2018.x.x 和 2019.x.x。

在最新的 Game Framework 版本中,包含 19 个内置模块,后续还将开发更多的扩展模块供开发者使用。

基础和工具 (Base) – 关于日志、引用池、工具集的文档。
全局配置 (Config) – 存储一些全局的只读的游戏配置,如玩家初始速度、游戏初始音量等。
数据结点 (Data Node) – 将任意类型的数据以树状结构的形式进行保存,用于管理游戏运行时的各种数据。
数据表 (Data Table) – 可以将游戏数据以表格(如 Microsoft Excel)的形式进行配置后,使用此模块使用这些数据表。数据表的格式是可以自定义的。
调试器 (Debugger) – 当游戏在 Unity 编辑器中运行或者以 Development 方式发布运行时,将出现调试器窗口,便于查看运行时日志、调试信息等。用户还可以方便地将自己的功能注册到调试器窗口上并使用。
下载 (Download) – 提供下载文件的功能,支持断点续传,并可指定允许几个下载器进行同时下载。更新资源时会主动调用此模块。
实体 (Entity) – 我们将游戏场景中,动态创建的一切物体定义为实体。此模块提供管理实体和实体组的功能,如显示隐藏实体、挂接实体(如挂接武器、坐骑,或者抓起另一个实体)等。实体使用结束后可以不立刻销毁,从而等待下一次重新使用。
事件 (Event) – 游戏逻辑监听、抛出事件的机制。Game Framework 中的很多模块在完成操作后都会抛出内置事件,监听这些事件将大大解除游戏逻辑之间的耦合。用户也可以定义自己的游戏逻辑事件。
文件系统 (File System) – 虚拟文件系统使用类似磁盘的概念对零散文件进行集中管理,优化资源加载时产生的内存分配,甚至可以对资源进行局部片段加载,这些都将极大提升资源加载时的性能。
有限状态机 (FSM) – 提供创建、使用和销毁有限状态机的功能,一些适用于有限状态机机制的游戏逻辑,使用此模块将是一个不错的选择。
本地化 (Localization) – 提供本地化功能,也就是我们平时所说的多语言。Game Framework 在本地化方面,不但支持文本的本地化,还支持任意资源的本地化,比如游戏中释放烟花特效也可以做出几个多国语言的版本,使得中文版里是“新年好”字样的特效,而英文版里是“Happy New Year”字样的特效。
网络 (Network) – 提供使用 Socket 长连接的功能,当前我们支持 TCP 协议,同时兼容 IPv4 和 IPv6 两个版本。用户可以同时建立多个连接与多个服务器同时进行通信,比如除了连接常规的游戏服务器,还可以连接语音聊天服务器。如果想接入 ProtoBuf 之类的协议库,只要派生自 Packet 类并实现自己的消息包类即可使用。
对象池 (Object Pool) – 提供对象缓存池的功能,避免频繁地创建和销毁各种游戏对象,提高游戏性能。除了 Game Framework 自身使用了对象池,用户还可以很方便地创建和管理自己的对象池。
流程 (Procedure) – 是贯穿游戏运行时整个生命周期的有限状态机。通过流程,将不同的游戏状态进行解耦将是一个非常好的习惯。对于网络游戏,你可能需要如检查资源流程、更新资源流程、检查服务器列表流程、选择服务器流程、登录服务器流程、创建角色流程等流程,而对于单机游戏,你可能需要在游戏选择菜单流程和游戏实际玩法流程之间做切换。如果想增加流程,只要派生自 ProcedureBase 类并实现自己的流程类即可使用。
资源 (Resource) – 为了保证玩家的体验,我们不推荐再使用同步的方式加载资源,由于 Game Framework 自身使用了一套完整的异步加载资源体系,因此只提供了异步加载资源的接口。不论简单的数据表、本地化字典,还是复杂的实体、场景、界面,我们都将使用异步加载。同时,Game Framework 提供了默认的内存管理策略(当然,你也可以定义自己的内存管理策略)。多数情况下,在使用 GameObject 的过程中,你甚至可以不需要自行进行 Instantiate 或者是 Destroy 操作。
场景 (Scene) – 提供场景管理的功能,可以同时加载多个场景,也可以随时卸载任何一个场景,从而很容易地实现场景的分部加载。
游戏配置 (Setting) – 以键值对的形式存储玩家数据,对 UnityEngine.PlayerPrefs 进行封装,也可以将这些数据直接存储在磁盘上。
声音 (Sound) – 提供管理声音和声音组的功能,用户可以自定义一个声音的音量、是 2D 声音还是 3D 声音,甚至是直接绑定到某个实体上跟随实体移动。
界面 (UI) – 提供管理界面和界面组的功能,如显示隐藏界面、激活界面、改变界面层级等。不论是 Unity 内置的 uGUI 还是其它类型的 UI 插件(如 NGUI),只要派生自 UIFormLogic 类并实现自己的界面类即可使用。界面使用结束后可以不立刻销毁,从而等待下一次重新使用。
Web 请求 (Web Request) – 提供使用短连接的功能,可以用 Get 或者 Post 方法向服务器发送请求并获取响应数据,可指定允许几个 Web 请求器进行同时请求。
Game Framework 由哪几部分组成?
完整的 Game Framework 包含三部分:

GameFramework – 封装基础游戏逻辑,如数据管理、资源管理、文件系统、对象池、有限状态机、本地化、事件、实体、网络、界面、声音等,此部分逻辑实现不依赖于 Unity 引擎,以程序集的形式提供。
UnityGameFramework.Runtime – 依赖 UnityEngine.dll 进行对 GameFramework.dll 的补充实现。为了方便兼容 Unity 的各个版本,此部分已经以代码的形式包含在 Unity 插件中。
UnityGameFramework.Editor – 依赖 UnityEditor.dll 进行对工具、Inspector 的实现。为了方便兼容 Unity 的各个版本,此部分已经以代码的形式包含在 Unity 插件中。

下载项目

官网真实项目-StarForce

坑位:

1.error CS0227: Unsafe code may only appear if compiling with /unsafe. Enable “Allow ‘unsafe’ code” in Player Settings to fix this error.

在这里插入图片描述
解决:
在这里插入图片描述

2.帧率和垂直同步

垂直同步默认需要关闭的,不然你动态改变GameFramework中的帧率没有变化
在这里插入图片描述

解决:
在这里插入图片描述

3.核心Procedure*.cs和GameEntry.cs

GameEntry.cs

public class GameEntry : MonoBehaviour
{
    #region 基础方法

    /// <summary>
    /// 获取游戏基础组件。
    /// </summary>
    public static BaseComponent Base { get; private set; }

    /// <summary>
    /// 获取配置组件。
    /// </summary>
    public static ConfigComponent Config { get; private set; }

    /// <summary>
    /// 获取数据结点组件。
    /// </summary>
    public static DataNodeComponent DataNode { get; private set; }

    /// <summary>
    /// 获取数据表组件。
    /// </summary>
    public static DataTableComponent DataTable { get; private set; }

    /// <summary>
    /// 获取调试组件。
    /// </summary>
    public static DebuggerComponent Debugger { get; private set; }

    /// <summary>
    /// 获取下载组件。
    /// </summary>
    public static DownloadComponent Download { get; private set; }

    /// <summary>
    /// 获取实体组件。
    /// </summary>
    public static EntityComponent Entity { get; private set; }

    /// <summary>
    /// 获取事件组件。
    /// </summary>
    public static EventComponent Event { get; private set; }

    /// <summary>
    /// 获取文件系统组件。
    /// </summary>
    public static FileSystemComponent FileSystem { get; private set; }

    /// <summary>
    /// 获取有限状态机组件。
    /// </summary>
    public static FsmComponent Fsm { get; private set; }

    /// <summary>
    /// 获取本地化组件。
    /// </summary>
    public static LocalizationComponent Localization { get; private set; }

    /// <summary>
    /// 获取网络组件。
    /// </summary>
    public static NetworkComponent Network { get; private set; }

    /// <summary>
    /// 获取对象池组件。
    /// </summary>
    public static ObjectPoolComponent ObjectPool { get; private set; }

    /// <summary>
    /// 获取流程组件。
    /// </summary>
    public static ProcedureComponent Procedure { get; private set; }

    /// <summary>
    /// 获取资源组件。
    /// </summary>
    public static ResourceComponent Resource { get; private set; }

    /// <summary>
    /// 获取场景组件。
    /// </summary>
    public static SceneComponent Scene { get; private set; }

    /// <summary>
    /// 获取配置组件。
    /// </summary>
    public static SettingComponent Setting { get; private set; }

    /// <summary>
    /// 获取声音组件。
    /// </summary>
    public static SoundComponent Sound { get; private set; }

    /// <summary>
    /// 获取界面组件。
    /// </summary>
    public static UIComponent UI { get; private set; }

    /// <summary>
    /// 获取网络组件。
    /// </summary>
    public static WebRequestComponent WebRequest { get; private set; }

    private static void InitBuiltinComponents()
    {
        Base = UnityGameFramework.Runtime.GameEntry.GetComponent<BaseComponent>();
        Config = UnityGameFramework.Runtime.GameEntry.GetComponent<ConfigComponent>();
        DataNode = UnityGameFramework.Runtime.GameEntry.GetComponent<DataNodeComponent>();
        DataTable = UnityGameFramework.Runtime.GameEntry.GetComponent<DataTableComponent>();
        Debugger = UnityGameFramework.Runtime.GameEntry.GetComponent<DebuggerComponent>();
        Download = UnityGameFramework.Runtime.GameEntry.GetComponent<DownloadComponent>();
        Entity = UnityGameFramework.Runtime.GameEntry.GetComponent<EntityComponent>();
        Event = UnityGameFramework.Runtime.GameEntry.GetComponent<EventComponent>();
        FileSystem = UnityGameFramework.Runtime.GameEntry.GetComponent<FileSystemComponent>();
        Fsm = UnityGameFramework.Runtime.GameEntry.GetComponent<FsmComponent>();
        Localization = UnityGameFramework.Runtime.GameEntry.GetComponent<LocalizationComponent>();
        Network = UnityGameFramework.Runtime.GameEntry.GetComponent<NetworkComponent>();
        ObjectPool = UnityGameFramework.Runtime.GameEntry.GetComponent<ObjectPoolComponent>();
        Procedure = UnityGameFramework.Runtime.GameEntry.GetComponent<ProcedureComponent>();
        Resource = UnityGameFramework.Runtime.GameEntry.GetComponent<ResourceComponent>();
        Scene = UnityGameFramework.Runtime.GameEntry.GetComponent<SceneComponent>();
        Setting = UnityGameFramework.Runtime.GameEntry.GetComponent<SettingComponent>();
        Sound = UnityGameFramework.Runtime.GameEntry.GetComponent<SoundComponent>();
        UI = UnityGameFramework.Runtime.GameEntry.GetComponent<UIComponent>();
        WebRequest = UnityGameFramework.Runtime.GameEntry.GetComponent<WebRequestComponent>();

        Debugger.ActiveWindow = false;
    }

    private static void InitCustomComponents()
    {
        //BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
    }

    #endregion

    private void Awake()
    {
        DontDestroyOnLoad(this);
    }

    private void Start()
    {
        InitBuiltinComponents();
        InitCustomComponents();
    }
}

在这里插入图片描述
ProcedureStartGame.cs

public class ProcedureStartGame : ProcedureBase
{
    private bool m_InitResourcesComplete = false;
    
    protected override void OnEnter(ProcedureOwner procedureOwner)
    {
        base.OnEnter(procedureOwner);
        
        m_InitResourcesComplete = false;
        
        // 注意:使用单机模式并初始化资源前,需要先构建 AssetBundle 并复制到 StreamingAssets 中,否则会产生 HTTP 404 错误
        GameEntry.Resource.InitResources(OnInitResourcesComplete);
    }

    protected override void OnUpdate(ProcedureOwner procedureOwner, float elapseSeconds, float realElapseSeconds)
    {
        base.OnUpdate(procedureOwner, elapseSeconds, realElapseSeconds);
        
        if (!m_InitResourcesComplete)
        {
            // 初始化资源未完成则继续等待
            return;
        }
        procedureOwner.SetData<VarString>("NextScene", "LoadingUI");
        ChangeState<ProcedureChangeScene>(procedureOwner);
    }
    
    private void OnInitResourcesComplete()
    {
        m_InitResourcesComplete = true;
        AudioManager.Instance.Init();
        Log.Info("Init resources complete.");
    }
}

4.配置相关BuildEventHandler.cs和GameFrameworkConfigs.cs

BuildEventHandler.cs

public sealed class BuildEventHandler : IBuildEventHandler
{
    public bool ContinueOnFailure
    {
        get { return false; }
    }

    public void OnPreprocessAllPlatforms(string productName, string companyName, string gameIdentifier,
        string gameFrameworkVersion, string unityVersion, string applicableGameVersion, int internalResourceVersion,
        Platform platforms, AssetBundleCompressionType assetBundleCompression, string compressionHelperTypeName,
        bool additionalCompressionSelected, bool forceRebuildAssetBundleSelected, string buildEventHandlerTypeName,
        string outputDirectory, BuildAssetBundleOptions buildAssetBundleOptions,
        string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected,
        string outputFullPath, bool outputPackedSelected, string outputPackedPath, string buildReportPath)
    {
        string streamingAssetsPath = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "StreamingAssets"));
        string[] fileNames = Directory.GetFiles(streamingAssetsPath, "*", SearchOption.AllDirectories);
        foreach (string fileName in fileNames)
        {
            if (fileName.Contains(".gitkeep"))
            {
                continue;
            }

            File.Delete(fileName);
        }

        Utility.Path.RemoveEmptyDirectory(streamingAssetsPath);
    }

    public void OnPostprocessAllPlatforms(string productName, string companyName, string gameIdentifier,
        string gameFrameworkVersion, string unityVersion, string applicableGameVersion, int internalResourceVersion,
        Platform platforms, AssetBundleCompressionType assetBundleCompression, string compressionHelperTypeName,
        bool additionalCompressionSelected, bool forceRebuildAssetBundleSelected, string buildEventHandlerTypeName,
        string outputDirectory, BuildAssetBundleOptions buildAssetBundleOptions,
        string workingPath, bool outputPackageSelected, string outputPackagePath, bool outputFullSelected,
        string outputFullPath, bool outputPackedSelected, string outputPackedPath, string buildReportPath)
    {
    }

    public void OnPreprocessPlatform(Platform platform, string workingPath, bool outputPackageSelected,
        string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected,
        string outputPackedPath)
    {
    }

    public void OnBuildAssetBundlesComplete(Platform platform, string workingPath, bool outputPackageSelected,
        string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected,
        string outputPackedPath, AssetBundleManifest assetBundleManifest)
    {
    }

    public void OnOutputUpdatableVersionListData(Platform platform, string versionListPath, int versionListLength,
        int versionListHashCode, int versionListCompressedLength, int versionListCompressedHashCode)
    {
    }

    public void OnPostprocessPlatform(Platform platform, string workingPath, bool outputPackageSelected,
        string outputPackagePath, bool outputFullSelected, string outputFullPath, bool outputPackedSelected,
        string outputPackedPath, bool isSuccess)
    {
        if (!outputPackageSelected)
        {
            return;
        }

        if (platform != Platform.Windows)
        {
            return;
        }

        string streamingAssetsPath = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "StreamingAssets"));
        string[] fileNames = Directory.GetFiles(outputPackagePath, "*", SearchOption.AllDirectories);
        foreach (string fileName in fileNames)
        {
            string destFileName =
                Utility.Path.GetRegularPath(Path.Combine(streamingAssetsPath,
                    fileName.Substring(outputPackagePath.Length)));
            FileInfo destFileInfo = new FileInfo(destFileName);
            if (!destFileInfo.Directory.Exists)
            {
                destFileInfo.Directory.Create();
            }

            File.Copy(fileName, destFileName);
        }
    }
}

GameFrameworkConfigs.cs

public static class GameFrameworkConfigs
{
    [BuildSettingsConfigPath]
    public static string BuildSettingsConfig = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "GameMain/Configs/BuildSettings.xml"));

    [ResourceCollectionConfigPath]
    public static string ResourceCollectionConfig = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "GameMain/Configs/ResourceCollection.xml"));

    [ResourceEditorConfigPath]
    public static string ResourceEditorConfig = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "GameMain/Configs/ResourceEditor.xml"));

    [ResourceBuilderConfigPath]
    public static string ResourceBuilderConfig = Utility.Path.GetRegularPath(Path.Combine(Application.dataPath, "GameMain/Configs/ResourceBuilder.xml"));
}

5.打AssetBundle和打包

5.1如果是UnityEditor(不使用AssetBundle)

在这里插入图片描述

5.1如果是UnityEditor(使用AssetBundle)

在这里插入图片描述
在这里插入图片描述

如果是运行AssetBundle-Windows
在这里插入图片描述

如果是运行AssetBundle-Andriod
在这里插入图片描述
生成后把ab相关的文件复制过来
在这里插入图片描述

运行:

请添加图片描述

Github项目地址:

V1-实验版
V2-实战版
V3-热更实验版

支持华佗热更借鉴完整实现博客

借鉴博客作者:我的朋友TopGames

华佗热更组件(继承GameFramework.Component)
HotFixComponent.cs

using System.Collections.Generic;
using UnityEngine;
using GameFramework;
using UnityGameFramework.Runtime;
using System;
using GameFramework.Resource;
using ProcedureOwner = GameFramework.Fsm.IFsm<GameFramework.Procedure.IProcedureManager>;

public class HotFixComponent : GameFrameworkComponent
{
    private List<System.Reflection.Assembly> mHotfixAssemblyList;
    public List<System.Reflection.Assembly> HotfixAssemblyList => mHotfixAssemblyList;

    protected override void Awake()
    {
        base.Awake();
        mHotfixAssemblyList = new List<System.Reflection.Assembly>();
    }

    /// <summary>
    /// 加载热更文件
    /// </summary>
    /// <param name="dllAssetName"></param>
    /// <param name="userData"></param>
    public void LoadHotfixDll(string dllAssetName, object userData)
    {
        MyGameEntry.Resource.LoadAsset(dllAssetName, typeof(TextAsset),
            new LoadAssetCallbacks(OnLoadDllSuccess, OnLoadDllFail), userData);
    }

    /// <summary>
    /// 加载并初始化元数据
    /// </summary>
    /// <param name="dllAssetName"></param>
    /// <param name="loadCallback"></param>
    public void LoadMetadataForAOTAssembly(string dllAssetName, GameFrameworkAction<string, byte[]> loadCallback)
    {
        MyGameEntry.Resource.LoadAsset(dllAssetName, new LoadAssetCallbacks((assetName, asset, duration, userData) =>
        {
            var textAsset = asset as TextAsset;
            if (textAsset == null) loadCallback.Invoke(dllAssetName, null);
            else
            {
                LoadMetadataForAOTAssembly(dllAssetName, textAsset.bytes);
                loadCallback.Invoke(dllAssetName, textAsset.bytes);
            }
        }, (assetName, status, errorMessage, userData) => { loadCallback.Invoke(dllAssetName, null); }));
    }

    private void OnLoadDllFail(string assetName, LoadResourceStatus status, string errorMessage, object userData)
    {
        Debug.LogErrorFormat("加载{0}失败! Error:{1}", assetName, errorMessage);
    }

    private void OnLoadDllSuccess(string assetName, object asset, float duration, object userData)
    {
        Debug.LogErrorFormat("加载{0}成功!", assetName);
        
        var dllTextAsset = asset as TextAsset;
        System.Reflection.Assembly dllAssembly = null;
        if (dllTextAsset != null)
        {
            try
            {
                dllAssembly = System.Reflection.Assembly.Load(dllTextAsset.bytes);
                mHotfixAssemblyList.Add(dllAssembly);
                ProcedureOwner procedureOwner = MyGameEntry.Fsm.GetFsm<GameFramework.Procedure.IProcedureManager>();
                procedureOwner.SetData<VarBoolean>("HotFix",true);
            }
            catch (Exception e)
            {
                Log.Error("Assembly.Load加载热更dll失败:{0},Error:{1}", assetName, e.Message);
                throw;
            }
        }
    }

    /// <summary>
    /// 从热更程序集中获取类
    /// </summary>
    /// <param name="className"></param>
    /// <returns></returns>
    public Type GetHotfixClass(string className)
    {
        var hotfixDll = GetHotfixAssembly("HotFix");
        if (hotfixDll == null)
        {
            Log.Error("GetHotfixAssembly HotFix failed");
            return null;
        }

        return hotfixDll.GetType(className, true);
    }

    /// <summary>
    /// 获取热更程序集
    /// </summary>
    /// <param name="dllName"></param>
    /// <returns></returns>
    public System.Reflection.Assembly GetHotfixAssembly(string dllName)
    {
        System.Reflection.Assembly result = null;
// #if UNITY_EDITOR
//         result = AppDomain.CurrentDomain.GetAssemblies()
//             .First(assembly => assembly.GetName().Name.CompareTo(dllName) == 0);
//
// #else
//         foreach (var item in mHotfixAssemblyList)
//         {
//             if (item.GetName().Name.CompareTo(dllName) == 0)
//             {
//                 result = item;
//                 break;
//             }
//         }
// #endif
        foreach (var item in mHotfixAssemblyList)
        {
            if (item.GetName().Name.CompareTo(dllName) == 0)
            {
                result = item;
                break;
            }
        }
        return result;
    }

    /// <summary>
    /// 为aot assembly加载原始metadata, 这个代码放aot或者热更新都行。
    /// 一旦加载后,如果AOT泛型函数对应native实现不存在,则自动替换为解释模式执行
    /// </summary>
    private unsafe void LoadMetadataForAOTAssembly(string dllName, byte[] dllBytes)
    {
        // 可以加载任意aot assembly的对应的dll。但要求dll必须与unity build过程中生成的裁剪后的dll一致,而不能直接使用原始dll。
        // 我们在BuildProcessor_xxx里添加了处理代码,这些裁剪后的dll在打包时自动被复制到 {项目目录}/HybridCLRData/AssembliesPostIl2CppStrip/{Target} 目录。
        // 注意,补充元数据是给AOT dll补充元数据,而不是给热更新dll补充元数据。
        // 热更新dll不缺元数据,不需要补充,如果调用LoadMetadataForAOTAssembly会返回错误

        fixed (byte* ptr = dllBytes)
        {
            // 加载assembly对应的dll,会自动为它hook。一旦aot泛型函数的native函数不存在,用解释器版本代码
            try
            {
                int err = HybridCLR.RuntimeApi.LoadMetadataForAOTAssembly((IntPtr) ptr, dllBytes.Length);
                Log.Info("LoadMetadataForAOTAssembly:{0}. ret:{1}", dllName, err);
            }
            catch (Exception e)
            {
                Log.Error("LoadMetadataForAOTAssembly失败:{0}", e.Message);
                throw;
            }
        }
    }
}

在这里插入图片描述

TODO全项目GameFramework+HybirdCLR替换

未完待续…