xLua热更新总结

一、xLua配置方法:

xlua的迁入与hotfix的环境配置

上GitHit找到xLua压缩包xLua-master.zip解压后,进入Assets文件夹将里面的东西全部复制粘贴到工程的Assets文件夹下。
然后,还要把xLua-master里的Tools文件夹 整个复制粘贴到 工程Assets文件夹的同级目录下。
进入Unity将PlayerSettings里面的OtherSettings的Scripting Define Symbols设置为HOTFIX_ENABLE,然后按回车。


还要将Unity的Editor下的Data下的Manager的3个dll放入工程 分别是
unity2017.1.1f3的就是C:\Program Files (x86)\Unity\Editor\Data\Managed
Mono.Cecil.Pdb.dll、 Mono.Cecil.Mdb.dll、 Mono.Cecil.dll 这3个
2017.2.2f3的话就是Unity.Cecil.Pdb.dll、 Unity.Cecil.Mdb.dll、 Unity.Cecil.dll 这3个

可看到xLua菜单项,第一个是生成工程xLua代码,第二个是清除xLua代码,第三个是加载代码进入Editor

首先,我们的工程若有代码变动,则必须要再次点击 第一个生成Generate Code生成代码,在Console会报出Success就OK
若没报出Success则继续按!!按到出为止。

然后就可以点击第三个加载入Editor了,这个也会提示Success

注意:使用xLua的工程路径不能出现中文,不然会报错。

二、零碎总结:

using xLua;
[Hotfix]标签 给出现问题的C#脚本加上该标签
例如:
[Hotfix]
public class HotfixTest:MonoBehavior{}


若要对该类里面的Update方法进行更改可以这样:
LuaEnv luaenv=new LuaEnv();//获取xLua环境变量


//直接在Start方法加上下面内容即可用xLua代码,下面是替换掉了CS.HotfixTest类的Update方法function(self)就是xLua方法,用这个方法覆盖掉原来C#的Update方法!格式如下。
luaenv.DoString(@"
xlua.hotfix(CS.HotfixTest,'Update',function(self)
self.tick = self.tick+1
if(self.tick%50)==0 then
print('<<<<<Update in lua,tick=' .. self.tick)
end
end)"
);


注意冒号和括号! ..是连接符  self的意思相当于在Update方法中写的this代表脚本自身,可以用self.Xxx的形式来引用公有变量和方法,不可直接使用私有的,必须声明可以访问私有变量才可以用,
即--允许lua使用CS.Gun的私有成员 xlua.private_accessible(CS.Gun)


总结:引用xLua命名空间 加标签[Hotfix] 获取xLua环境变量 使用环境变量来直接用DoString方法写一个替换类方法的方法
上面的那个就是替换了Update方法,记住那个格式!!!(@"..."); 里面的内容 全是xLua代码 必须遵守xLua格式。
xlua.hotfix是一个官方方法,xlua是一个类可以理解,它有一个hotfix方法,第一个参数指定替换C#的哪个类,第二个参数指定替换该类的哪个方法(string方法名),第三个参数是一个xLua方法。


2.xLua代码里面若有调用C#代码,即用CS.命名空间.类名.方法名,调用C#代码我们需要在被xLua代码替换掉的方法前加上一个标签[LuaCallCSharp]标明可以让xLua调用C#代码,不然无法调用。
以上面的例子来说,就是在Update方法前加个[LuaCallCSharp],这样Update方法里的已被替换成xLua代码的代码就能够调用CShape代码,即,使它在xLua环境下运行!


注意:xLua代码编写完后,必须等待Unity小花花准备完毕,不然直接运行会出错,而且每次更新unity代码后必须在Unity重新生成,并且插入Editor。若发生一连串错误,要选择第二个按钮清除生成的代码。再重新生成。


xLua天坑之一:打包工程之前必须确保工程里面的xLua文件夹下的Examples文件夹已经被删除了,也就是所有案例都要删除,不然会各种报错!删除后,先清除生成的代码,再生成一下代码,再插入一下Editor,也就是xLua菜单的第2,1,3按钮


3.第一个xLua脚本的构建:(xLua虚拟环境的搭建)
引用命名空间xLua,创建一个xLua环境变量 LuaEnv luaEnv= new LuaEnv();
给luaEnv.AddLoader();加一个加载器,自定义加载器 加载器作用是加载文件txt中的xLua代码进入xLua环境中执行。
private byte[] MyLoader(ref string FilePath){
string absPath=@"" //使用绝对路径加载 ,可以用Resouces.Load<>方法加载
return 用System.Text.Encording.UTF8.GetBytes(File.ReadAllText(absPath));来转byte[] 用File.ReadAllText读全部文本信息。
}
luaEnv.DoString('require 'fish');这样来加载fish文件夹的xLua代码进入虚拟xLua环境执行,这个加载工作交给所有加载器去做,若有一个加载器成功加载了,那么会返回一个byte[]数组,交由DoString去执行xLua代码


开发过程:
首先开发业务代码(打代码)->在所有可能出现问题的类上提前打上hotfix的标签,在所有lua调用CSharp的方法上打上[LuaCallCSharp]标签,在所有CSharp调用Lua的方法上打上CSharpCallLua->打包发布->修改Bug时只需要更新Lua文件,修改资源时(声音、模型、贴图、图片、UI)只需要更新AssetBundle(ab包),用户只需要下载lua文件跟ab包


1.什么叫在所有Lua调用CSharp的方法上打上[LuaCallCSharp]标签?
 我们xLua代码的执行是在虚拟xLua环境下执行,是用虚拟xLua环境变量 luaEnv.DoString('')来执行的,那么调用这条代码的方法里面,调用代码必须在方法里面执行无论在哪里,那么就必须给该调用了该行代码的方法上加上这个标签,因xLua代码里面有调用CShar,代码的语句!
2. 反过来CSharpCallLua 的标签 也就是加在 那些用C#形式调用了xLua代码的方法前。目前还不清楚CSharp如何调用xLua方法


使用热更新的小细节:(超重点!!) 哪些代码中的值,千万别写死,都用变量来代替!!!不然热更新的时候 无法直接更新变量而需要替换整个方法!!来去修改那么几个变量的值!!!会变的很复杂!
而且方法不易于太长,把每个功能切分 切分 再切分最好不过,因为热更新需要更新一个功能的话 一般只会更新那么功能的一部分小细节,我们只需要找到那个细节进行更新即可。不过这个可以适当地调整方法的长度,不要切太细,也不能完全不切!


注意:在一个脚本内创建了虚拟xLua环境变量后,必须在销毁脚本的时候也要销毁该虚拟环境变量luaEnv.Dispose();
销毁虚拟Lua环境会有一个问题,就是必须在销毁环境之前 释放掉 执行中的xLua代码,也就是在OnDisable()方法内在xLua环境中调用下面的代码,可以将该代码放在一个新的文件下,用luaEnv.DoString("require 'filename'");来执行lua代码注意文件名要用''括起来!!!
--1.2 注销xLua的CreatePrize代码
xlua.hotfix(CS.Treasour,'CreatePrize',nil)
-- 注销xLua环境正在引用的替换Gun类Attack方法的代码(方法),也就是用lua代码替换掉Attack方法的代码必须释放才不会报错
xlua.hotfix(CS.Gun,'Attack',nil)

这样把之前使用过的xLua方法置空,相当于释放lua代码,这样才不会在中断游戏后出现bug,注意,必须放在OnDisable()中执行,因为这个系统回调方法是在 OnDestroy()方法前执行的!意味着你每次执行了xLua的方法其实是替换了C#中某个类的方法时,必须在结束的时候执行以下上面的释放lua代码xlua.hotfix(CS.类名,'方法名',nil)   nil的意思是空

使用  util.lua.txt库的方法:util.hotfix_ex(CS.MyClass,'MethodName',function(self)

    self.MethodName()  --这样可以直接用self.MethodName()来调用自身方法

    ...  ...                           --其他的一些lua代码,也就是说在原本方法的基础上对该方法进行补充,(建议少用)

end)  

其注销方法一样:xlua.hotfix(CS.MyClass,'MethodName',nil)

三、AB包的生成、加载AB包的资源、使用资源

AB包的生成

AssetBundles包的生成需要新建一个类来处理,最基础的方法:

新建一个文件夹Editor,创建一个C#文件,文件内容如下: 下面的方法是菜单方法,点击菜单的那个按钮就会自动进行打包

using UnityEditor;
using System.IO;
public class CreateAssetBundle{
    
    [MenuItem("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles()
    {
        string dir = "AssetBundles";
        if(Directory.Exists(dir)==false)
        {
            Directory.CreateDirectory(dir);
        }
        //所有指定了AssetBundle包相对打包路径的,都会放在这个名叫AssetBundles的目录下,该目录会自动生成于工程的主目录下
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);//注意第三个参数一定要配合你要打包出去的环境
    }

}

做完以上工作后,就可以去指定想要打包的资源的相对路径了,相对路径指的是在AssetBundles包自动存放的主目录下的相对。

例如:在U3D指定资源路径是gold,后缀是ab,那么就打包后会在工程下的AssetBundles目录下生成一个名叫gold.ab的东西,这就是AB包,AB包可理解为是一个资源集合体,里面可以放很多资源,获取资源的时候跟字典差不多。

加载AB包的资源:

直接从服务器加载AB包,因为我们想要的效果肯定是从服务器拿到AB包,给玩家的本地客户端用的,思路如下:

因为我们不可能在玩家每次登陆游戏的时候把所有AB包的资源都加载出来放在一个地方保存,那样太慢了,所以我们需要用xLua代码在需要使用某个资源的时候,才去加载资源,加载出的资源可能会被重复调用,所以我们可以存放入一个字典里面,需要的时候就从资源里面拿,也就是说一个资源只需要加载一次,后面还需要的话就直接拿就可以了。

下面是使用UnityWebRequest来加载本地服务器的AB包方法

   [LuaCallCSharp]
    public void LoadResource(string resName,string filePath)
    {
        StartCoroutine(LoadResourceCoroutine(resName,filePath));
        
    }

    IEnumerator LoadResourceCoroutine(string resName,string filePath)
    {    
        UnityWebRequest request= UnityWebRequest.GetAssetBundle(@"http://localhost/AssetBundles/" + filePath);
        yield return request.Send();//发送请求连接网络上的服务器                     
        AssetBundle ab = (request.downloadHandler as DownloadHandlerAssetBundle).assetBundle;//从请求得到的结果里面获取到ab包,再用.assetBundle强转下类型       
        GameObject gameObject = ab.LoadAsset<GameObject>(resName); //根据resName资源名称(预制体名)从AB包获取该预制体
        prefabDict.Add(resName, gameObject);//放入字典
    }   
    [LuaCallCSharp]
    public static GameObject GetGameObject(string goName)
    {
        return prefabDict[goName];  
    }
  

协程里面的http://localhost/AssetBundles/+filePath是指定服务器上的AB包路径,localhost是我的服务器搭建在本地上了。

(上面的3个方法假设放在了一个名叫ServerAb的类)
//注意在将会新增资源的类中必须事先使用[HotFix]标签,以及在会改动的方法前加上[LuaCallCSharp]标签,并且在该类中声明一个公有变量public ServerAb serverAb; 使用拖拽赋值方法给脚本变量赋值。(若是私有必须请求私有访问权)

xLua代码:

xlua.hotfix(CS.ClassName,'MethodName',function(self)

    --假设我要获取一个名叫Apple的预制体,在相应AB包里面

    self.serverAb:LoadResouce('Apple','gameobject\\food.ab')--注意该方法是成员方法,必须要用' : '来调用!!而且路径要写2个\\!这样名叫Apple的预制体放入了字典中,下面就可以获取了

    go=CS.ServerAb.GetGameObject('Apple') --因为该方法是静态方法可以直接用类名来调用,而且不用 : 来调用

    ...... .....

end)

在这里说一下,上面的方法因为不能百分百地肯定我们加载的AB包有那个资源,我们在上面的加载资源代码中要加安全校验。

最后

上面的东西最重要的还没说,那就是加载服务器的LUA文件,也就是把服务器的LUA文件放到本地上,这就是我们的最后一步,也是所谓的热更新。其实就是从服务器下载个文件到本地。


   /// <summary>
    /// 加载服务器上的lua文件到本地,以此来给程序能加载lua文件实现更新版本效果
    /// </summary>
    /// <returns></returns>
    IEnumerator LoadResourceCorotine()
    {        
        UnityWebRequest request = UnityWebRequest.Get(@"http://localhost/work.lua.txt");
        yield return request.Send();
        string str = request.downloadHandler.text;//获取处理器的文本 一堆资源的文本(字节)
        File.WriteAllText(@".../work.lua.txt",str);
        //若还有的话继续加载...注意这些都是内定的lua文件,一般都事先安排好。
    }

上面的有2点我没想明白,那就是lua文件从服务器上下载到本地的路径以及在加载器加载lua文件的路径,如何保持一致,我们下载到本地的路径肯定是与游戏所在目录有关的,确定从服务器下载到本地的那个路径是最关键,不能定死,也就是不可能用绝对路径的,比如我想把lua文件存放到 游戏主目录所在的外一级目录的名称叫Lua的文件,如何获得玩家本地游戏客户端主目录的目录?还是直接用lua定死一个路径?比如玩家把游戏放在E盘,我们定死了一个路径,把服务器下载下来的lua文件放在D盘的Xxx文件夹下,那么我们加载lua文件时也能够用绝对路径了。    

    我想明白了,自从我看了Lua框架的做法,那就是直接放在C盘的,哈哈哈哈~~我仰天大笑,我去!!放在C盘!!C盘可是系统盘,卡爆了有木有!!一堆资源放在C盘!!可是它就是这样做了。。。。因为C盘绝对存在。。


//注意在将会新增资源的类中必须事先使用[HotFix]标签,以及在会改动的方法前加上[LuaCallCSharp]标签,并且在该类中声明一个公有变量public ServerAb serverAb; 使用拖拽赋值方法给脚本变量赋值。

猜你喜欢

转载自blog.csdn.net/qq_39574690/article/details/80725308