淘先锋技术网

首页 1 2 3 4 5 6 7

介绍了Unity热更新的整个流程,其中用到了部分lua知识,有兴趣的可以自行了解

一、AssetBundle

1、AssetBundle的作用

将一些资源(声音,图片,预设体等)分类打包,在游戏开始前统一加载。每次从服务器上下载,与本地资源的CRC值进行比较,更新本地资源来确保热更新。

2、AssetBundle的依赖

将一个预设体打包的时候,如果当前预设里含有其他AssetBundle的声音文件、动画文件或其他的文件时,该预设体AssetBundle包中会标注所依赖的包文件,在加载的时候要先加载被依赖的包,含有脚本文件时要确保项目中是否存在相应的脚本。

3、AssetBundle的建立

  1. 在项目根目录创建AssetBundles文件夹用来存放所有AssetBundle文件
  2. 编写生成AssetBundle文件的代码,放到Assets/Editor下
using UnityEditor;
using System.IO;

public class CreateAssetBundles
{
    /// <summary>
    /// 创建所有设置的AssetBundle文件
    /// </summary>
    [MenuItem("Assets/Build AssetBundles")] //设置到编辑器模式的Assets下Build AssetBundles选项方便调用
    static void BuildAllAssetBundles()
    {
        string dir = "AssetBundles"; //设置AssetBundle根目录路径

        //不存在就去创建
        if (Directory.Exists(dir) == false)
        {
            Directory.CreateDirectory(dir);
        }

        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.UncompressedAssetBundle, BuildTarget.StandaloneWindows64); //生成所有设置的AssetBundle
    }
}

  1. 从Project视图下选中需要打到一个AssetBundle包的资源,设置其AssetBundle名称(名称中带有/表示路径,不区分大小写)和后缀,点击Assets下Build AssetBundles选项生成AssetBundle(不会删除之前存在的文件),完成AssetBundle的建立
  • 点击资源
    点击资源

  • 找到Inspector面板最下方的Blue双击打开设置AssetBundle
    在这里插入图片描述

  • 打开的AssetBundle属性
    在这里插入图片描述

  • 设置AssetBundle
    在这里插入图片描述

4、AssetBundle的加载

1)manifest文件

        //通过AssetBundles包获取manifest文件
        string manifestFilePath = "AssetBundles/AssetBundles";
        AssetBundle assetBundle = AssetBundle.LoadFromFile(manifestFilePath); 
        AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest"); //从AssetBundles包中加载manifest文件
        string[] dependencies = manifest.GetAllDependencies("cube.ab"); //获取加载包中的所有依赖
        foreach (string dependency in dependencies)
        {
            AssetBundle.LoadFromFile(Path.Combine(@"D:\mineunity\Test\Test\AssetBundles", dependency)); //加载所有依赖
        }

2)内存加载AssetBundle

        //异步内存加载
        string path1 = "AssetBundles/main/01.ab"; //cube.ab依赖01.ab
        AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path1));
        yield return request;
        AssetBundle ab1 = request.assetBundle;

        //内存加载
        string path2 = "AssetBundles/cube.ab"; //预设体保存在cube.ab中
        AssetBundle ab2 = AssetBundle.LoadFromMemory(File.ReadAllBytes(path2));

3)本地文件加载AssetBundle

        //异步文件加载
        string path1 = "AssetBundles/main/01.ab"; //cube.ab依赖01.ab
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path1);
        yield return request;
        AssetBundle ab1 = request.assetBundle;

        //文件加载
        string path2 = "AssetBundles/cube.ab"; //预设体保存在cube.ab中
        AssetBundle ab2 = AssetBundle.LoadFromFile(path2);

4)WWW加载AssetBundle

现已弃用

5)UnityWebRequest加载AssetBundle

	string uri1 = @"file:///D:\mineunity\Test\Test\AssetBundles\main\01.ab"; //绝对路径
        string uri2 = @"file:///D:\mineunity\Test\Test\AssetBundles\cube.ab";

        //string uri1 = @"http://localhost/AssetBundles/main/01.ab"; //服务器地址
        //string uri2 = @"http://localhost/AssetBundles/cube.ab";

        //通过UnityWebRequest获取
        UnityWebRequest request1 = UnityWebRequest.GetAssetBundle(uri1); //加载对应地址AssetBundle
        yield return request1.SendWebRequest();
        UnityWebRequest request2 = UnityWebRequest.GetAssetBundle(uri2);
        yield return request2.SendWebRequest();

        //AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
        AssetBundle ab1 = (request1.downloadHandler as DownloadHandlerAssetBundle).assetBundle;
        AssetBundle ab2 = (request2.downloadHandler as DownloadHandlerAssetBundle).assetBundle;

6)通过AssetBundle加载资源

        //实例化指定资源,名字需要对应
        GameObject cubePrefab = ab2.LoadAsset<GameObject>("Cube"); //也可以指定非GameObject类型资源
        Instantiate(cubePrefab);

        //实例化包中所有资源
        Object[] objs = ab2.LoadAllAssets();
        foreach (Object o in objs)
        {
            Instantiate(o);
        }

5、AssetBundle的卸载

1)Resources卸载

  1. Resources.UnloadAsset卸载指定的资源
  2. Resources.UnloadUnusedAssets卸载所有没在使用的资源

2)AssetBundle卸载

  1. AssetBundle.Unload(false),卸载AssetBundle所有没在使用的资源,如果卸载后重新加载了资源,内存中会加载两个同样的AssetBundle文件(一个被引用,另一个没有),造成冗余
  2. AssetBundle.Unload(true),卸载AssetBundle所有资源,包括正在使用的资源,一般在场景切换时使用

二、xlua

1、xlua的优势

十分方便编译(可以认为不需要编译就可以运行),而C#需要编译后在其他系统中使用,修改起来非常麻烦,在热更新时需要将修改后的项目编译重新打包发布,这时候就可以借助xlua修改部分C#代码进行发布,只需要将xlua文件重新发布即可,修改文件大小比重新发布项目小很多,方便下载。

2、xlua插件的下载

3、xlua插件的导入

  1. 解压下载好的xlua后,将xLua-master\Assets目录下的所有文件拷贝到项目Assets目录下(可将XLua\Examples删除,不删除可能会报错)
  2. 将xLua-master下的Tools文件夹拷贝到项目文件夹下
  3. 将unity编辑器目录下的Editor\Data\Managed中的Unity.Cecil.dll、Unity.Cecil.Mdb.dll、Unity.Cecil.Pdb.dll三个文件拷贝到项目Assets\XLua\Src\Editor目录下
  4. 打开Unity编辑器中file下的Build Settings,打开player Settings
  5. 修改Other Settings下的Scripting Define Symbols为HOTFIX_ENABLE,回车确认
  • 修改Scripting Define Symbols
    在这里插入图片描述
    Tips:目录中最好不要有中文

4、xlua的使用

1)命名空间

使用using XLua;

2)C#中xlua的特性

a)[hotfix]

一般标注在有可能要修改的类上,让lua知道这段代码有热更新

b)[LuaCallCSharp]

一般用于将lua中的代码映射到C#中

c)[CSharpCallLua]

一般标注在需要修改的方法上,没有该特性将无法用lua修改

3)C#中使用xlua

在添加HOTFIX_ENABLE后,每次更改代码后,都要调用xlua下的Generate Code,看到finish提示后,在调用xlua下的Hotfix Inject进行注入,然后才能运行代码

4)C#中加载lua代码

一般使用自定义的加载器加载lua代码,然后通过一句lua代码进行调用

  1. 自定义loader装载器:
    private byte[] MyLoader(ref string filePath)
    {
        string absPath = @"C:\Users\Administrator\Desktop\XluaProjects\PlayerGamePackage\"+filePath+".lua.txt"; //使用Recourse加载时,只能识别txt后缀
        return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath)); //注意编码格式,转译成二进制数组返回
    }

  1. 使用自定义loader
    private void Awake()
    {
        luaEnv = new LuaEnv(); //实例化lua环境,程序中应唯一
        luaEnv.AddLoader(MyLoader); //加载自定义loader,在读取时会先使用自带loader寻找,寻找不到使用自定义loader寻找,还寻找不到就返回找不到文件
        luaEnv.DoString("require'{0}'", luaName); //luaName为自定义lua文件名称
    }

  1. 销毁lua环境
    private void OnDestroy()
    {
        luaEnv.DoString("require'{0}'", luaDisposeName); //调用lua代码销毁所有lua连接
        luaEnv.Dispose(); //销毁lua环境
    }

5)lua中修改C#

a)lua热更新文件

--由于需要多次使用UnityEngine下的方法,直接存放起来,需要的时候直接使用
local UnityEngine=CS.UnityEngine

--调用该代码可访问C#类中的私有属性
xlua.private_accessible(CS.Test)

--调用C#中的静态方法可直接.方法名,若不为静态方法则需要把自身传递过去或者使用:调用方法

--使用hotfix函数修改C#中代码,第一个参数为类名,第二个参数为方法名,第三个参数为修改的函数内容
xlua.hotfix(CS.Test,'TestMethod',function(self)
	UnityEngine.Debug.Log('Hello lua')
end)

--如果想访问私有方法,则应在引用private_accessible后,使用lua的util访问

local util=require 'util'
xlua.private_accessible(CS.Test2)
util.hotfix_ex(CS.Test2, 'Start', function(self)
	--lua中self和C#中this类似
	self.Start(self)

	--lua中没有+=等运算符号
	self.number = self.number + 1

	--lua中for循环第一个参数为起始值,第二个参数为结束值<=,第三个参数为步长
	for i=0, self.number, 1 do
		self:TestMethod(self.number)
	end
end)

--在lua中数字变量统一为number类型,在C#中去值时如果类型不对应(比如number中存了float值而在C#中用int类型取值),则会返回0

--由于lua不能在C#中创建类和方法,我们可以采取在C#中预先创建几个空类和空方法提供给lua进行修改,作为在lua给C#添加新类的解决方案

b)lua销毁文件

--将所有引用过的方法销毁,之后才能销毁lua环境
xlua.hotfix(CS.Test, 'TestMethod', nil)
xlua.hotfix(CS.Test2, 'TestMethod', nil)

5、总结

  1. lua提供给C#修复部分代码,而不需要将整个项目重新发布的功能,用于每个小版本的更新或者bug修复,在大版本更新时(大量更改项目中的类和方法),就不推荐使用lua修改了
  2. 预先估计好有可能更改的类及方法,打上特性标记,才能使用lua修改代码
  3. 在项目中设置好类(最好是单例),单独运行lua脚本