Xlua_Unity_使用AB包进行热更新

    以下使用Unity20174.10 + Vs2017 + XLua

     在这之前,请阅读以下两篇文章

    Unity加载AB:https://blog.csdn.net/MikuLingSSS/article/details/82838300

    XLua基本使用:https://blog.csdn.net/MikuLingSSS/article/details/82873510

    服务器地址可以填写127.0.0.1:端口号 // 但是请保证你们已经搭建了本地的FTP,并且可以使用网页和我的电脑访问

==================================================================================================

    首先,创建两个场景,一个是加载XLua脚本,另一个是主场景

    加载AssetBundle还是使用上篇博客的代码,进行了小范围修改

public class LoadWWW : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(LoadManifest("ftp://127.0.0.1:10091/StreamingAssets/PC/LuaScripts/", "LuaScripts", "ftp://127.0.0.1:10091/StreamingAssets/PC/LuaScripts/Version.txt"));
        StartCoroutine(Loading("LuaRunningTest"));
    }

    private AsyncOperation async;  // 新加了一个异步加载场景
    private int ToJourney, YetJourney;
    IEnumerator Loading(string scendID)
    {
        async = SceneManager.LoadSceneAsync(scendID);
        async.allowSceneActivation = false;
        while (async.progress < 0.9f)
        {
            ToJourney = (int)(async.progress * 100);
            while (YetJourney < ToJourney)
            {
                YetJourney++;
                yield return new WaitForEndOfFrame();
            }
            yield return new WaitForEndOfFrame();
        }
        while (true)
        {
            if (IsLoadScene) // 这是我们设立的条件 下面加载Lua脚本的地方会有介绍
            {
                ToJourney = 100;
                while (YetJourney < ToJourney)
                {
                    YetJourney++;
                    yield return new WaitForEndOfFrame();
                }
                async.allowSceneActivation = true;
                yield return async;
            }
            yield return 0.1f;
        }
    }
    // LoadManifest 修改了一下,让AB包版本从网络上获取
    IEnumerator LoadManifest(string path, string filename, string versionPath)
    {
        int version = 1;
        using (WWW versionW = new WWW(versionPath))
        {
            yield return versionW;
            Debug.Log(versionW.text+"::" + versionW.ToString());
            version = Convert.ToInt16(versionW.text);
        }
        ///.... 中间代码没变 省略
    }    

这里说明一下思路,我们Lua脚本名称会和C#脚本名字一样,因为这样我们可以直接在C#里面使用键值对来直接获取脚本,而不需要在一个个配置
    IEnumerator LoadPrefab(string path, string filename, int version, bool IsLoad = true)
    {
        ... 上面代码没变 省略
        foreach (var item in asbundle.GetAllAssetNames())
        {
            // 这个脚本只是修改了一下遍历里面
            string scripts = asbundle.LoadAsset<TextAsset>(item).text; // 加载网络上AB包的Lua脚本
            string methodName = item.Replace(".lua.txt", ""); // 我们获取到的包是全称,所以要去掉一些东西 后续方便使用
            methodName = methodName.Substring(methodName.LastIndexOf("/") + 1); // 获得名称
            LuaManager.Instance.SetScripts(methodName, scripts); // 下面有介绍
        }    
    }
}

-------------------------------------------------------------------------------------
这个脚本会在我们加载成功AB包中的Lua脚本时保存相应的Lua脚本,
并且以Lua脚本的名字作为自己的键,脚本代码作为值  ----   
using ...; 
public class LuaManager { // 单例,因为我们需要保证场景中只有一个实例

    private static LuaManager instance;
    public static LuaManager Instance
    {
        get
        {
            if (instance == null)
                instance = new LuaManager();
            return instance;
        }
    }

    public Dictionary<string, string> luaScripts; // 保存脚本信息

    private LuaManager() { // 初始化
        luaScripts = new Dictionary<string, string>();
    }

    public string GetScripts(string methodName) // 获取脚本
    {
        if (!luaScripts.ContainsKey(methodName))
            return null;
        return luaScripts[methodName];
    }

    public void SetScripts(string methodName, string scripts) // 设置脚本,就是加载AB包额时候用到的
    {
        if (luaScripts.ContainsKey(methodName))
        {
            luaScripts[methodName] = scripts;
        }
        else
        {
            luaScripts.Add(methodName,scripts);
        }
    }

    public static string RetType(object o) // 通过反射获取自己的类名,我们的类想要取出Lua脚本就必须要知道自己的类名
    {
        StackTrace trace = new StackTrace();
        StackFrame frame = trace.GetFrame(1);
        MethodBase method = frame.GetMethod();
        string className = method.ReflectedType.Name;
        return className;
    }

}

FTP文档结构:Version.txt里面只有一个数字 这里暂时是1

Xlua_Unity_使用AB包进行热更新

然后,开始写主场景的脚本,这里就暂时只写一个脚本,写完记得点击 Xlua ->  Hotfix Inject In Editor

// 这个写崩了 今天是没心情改了 各位大佬就当他不存在
[Hotfix]
public class LoadLuaS : MonoBehaviour {

    protected LuaEnv luaenv = new LuaEnv();
    protected LuaTable scriptEnv;
    protected Action luaStart;
    protected Action luaUpdate;
    protected Action luaDestroy;
    private void Awake()
    {
        OnAwake();
    }

    protected virtual void OnAwake()
    {

    }

    protected virtual void OnDestroy()
    {
        if (luaDestroy != null)
            luaDestroy();
        luaenv = null;
        scriptEnv = null;
    }
}

----------------------------------------------------------------------------------------
// 因为我们的Lua脚本名字是PersonMove.lua.txt 所以这个类名会是PersonMove.cs
using ...;
[Hotfix]
public class PersonMove : LoadLuaS
{
    // 上篇博客有介绍,这里不再多说
    protected override void OnAwake()
    {
        scriptEnv = luaenv.NewTable();
        using (LuaTable meta = luaenv.NewTable())
        {
            meta.Set("__index", luaenv.Global);
            scriptEnv.SetMetaTable(meta);
        }
        //注意这两句=我们会通过反射获取到我们的类名,然后进行判断我这个脚本有没有对应的Lua脚本
        string methodName = LuaManager.RetType(this);
        //ToLower是转化为小写,因为我们解包之后所有的字符都是小写,故而我们保存到字典的键也是小写
    //所以这里将其转为小写
        string scripts = LuaManager.Instance.GetScripts(methodName.ToLower());
        注意这两句====================================
        if (scripts != null)
        {
            scriptEnv.Set("self", this);
            luaenv.DoString(scripts, methodName, scriptEnv);
            scriptEnv.Get("OnStart",out luaStart);
            scriptEnv.Get("OnUpdate",out luaUpdate);
            scriptEnv.Get("OnDestroy",out luaDestroy);
        }
    }

    void Start()
    {
        if (luaStart != null)
            luaStart();
    }

    void Update()
    {
        if (luaUpdate != null)
            luaUpdate();
    }

    protected new void OnDestroy()
    {
        base.OnDestroy();
    }

}

Lua代码

-------------------------------------Manager.lua.txt--------------------------------- 
Manager = {}

Manager.__index = Manager;

Manager.MoveSpeed = 10;

Manager.RotationSpeed = 90;

-------------------------------------PersonMove.lua.txt------------------------------
-- 一个非常简单的Move
require("Manager");
PersonMove = {}
setmetatable(PersonMove, Manager)
PersonMove.self = nil; -- 自身类
PersonMove.hero = nil; -- 英雄GameObject
PersonMove.camera = nil;
PersonMove.__index = Manager; -- 

local forward = 0; -- 移动增量
local right = 0; -- 
--------------方法体----------------

function OnStart()
    print('Lua OnStart')
end

function Print()
    print('This Is SiBaDa!!!')
end

function Move() -- 移动事件
    position = self.transform.position;
    position = position + self.transform.forward * CS.UnityEngine.Time.deltaTime * forward * Manager.MoveSpeed + self.transform.right * CS.UnityEngine.Time.deltaTime * right * Manager.MoveSpeed;
    self.transform.position = position;

    --颜色改变 这个是Demo代码,拷过来用一哈
    self.transform:GetComponent(typeof(CS.UnityEngine.MeshRenderer)).material.color = CS.UnityEngine.Color(CS.UnityEngine.Mathf.Sin(CS.UnityEngine.Time.time) / 2 + 0.5, 0, 0, 1)
end

function OnUpdate() -- Update
    forward = CS.UnityEngine.Input.GetAxis('Vertical');
    right = CS.UnityEngine.Input.GetAxis('Horizontal');
    forward = CS.UnityEngine.Mathf.Clamp(forward,-1,1);
    right = CS.UnityEngine.Mathf.Clamp(right,-1,1);
    Move(); 
end

xlua.hotfix(CS.XLuaLoadScripts,'HotfixTest',function(self)
    print('This Is SiBaDa!!!');
    end
)

function OnDestroy()

end

然后,打包一下,打包的结构和AssetBundle那篇博客一致,此时运行

Xlua_Unity_使用AB包进行热更新

说明一下:最先出现的那个反射调用是我写的一个例子,忘记注释了 = =

=============================================热更================================================

    假设,我们现在需要一个跳跃的功能,那么修改一下Lua代码,并且将Version.txt中的版本+1(因为我们用的加载是WWW.LoadFromCacheOrDownload,他会根据版本查找自己的缓存,也就是说,如果没有新版本的话,他会使用原先也就是上次我们缓存的AB包)

Lua新增代码如下:

function Jump()
    if(CS.UnityEngine.Input.GetKeyDown('space'))
    then 
        velocity = 5;
    end
    velocity = velocity - gracity * CS.UnityEngine.Time.deltaTime;
    velocity = CS.UnityEngine.Mathf.Clamp(velocity,-20,20);
    self.transform:GetComponent(typeof(CS.UnityEngine.Rigidbody)).velocity = CS.UnityEngine.Vector3(0,velocity,0);
end

function OnUpdate() -- Update
    forward = CS.UnityEngine.Input.GetAxis('Vertical');
    right = CS.UnityEngine.Input.GetAxis('Horizontal');
    forward = CS.UnityEngine.Mathf.Clamp(forward,-1,1);
    right = CS.UnityEngine.Mathf.Clamp(right,-1,1);
    Move();
    Jump();
end

然后,进入Unity中打个AB包,将新包覆盖进入FTP中,运行一哈

Xlua_Unity_使用AB包进行热更新

注:由于我也是刚接触XLua不到三四天,所以做了很多的实验,很多地方都用到服务器,所以在这里无法上传完整项目,见谅

- -  

代码下载:https://download.csdn.net/download/mikulingsss/10694584

注意:这里面没有场景,没有AssetBundle,没有教程之类的,只有几个代码而已,看下图

上面RAR截图:

Xlua_Unity_使用AB包进行热更新