学习笔记——基于Unity3D的Lua热更新

记录最近在学习Lua热更新的过程中遇到的一些坑

笔者这里使用的是Slua,关于Lua文件的打包下载和读取,不论是什么Lua框架,应该都是通用的

Unity3D加载.lua/.txt文件

在Slua官方给出的Demo中,加载的lua文件是.txt后缀,而大多数编辑器只能打开.lua文件,所以需要让Unity3D能够加载.lua文件是必要的一步,否则开发过程中就需要频繁的修改后缀名,非常麻烦,Slua中其实也提供了接口(如果直接用记事本写代码这一步可以省略。。。):

LuaSvr svr;
LuaTable self;
LuaFunction update;
[CustomLuaClass]
public delegate void UpdateDelegate(object self);

//调用这个方法,读取Entry.lua文件并执行
private void StartLua()
    {
        string dataPath="lua文件所在的根文件夹"
        svr = new LuaSvr();
        svr.init(null, () =>
        {
            string initLua = @"return require(""Entry"")";
            IntPtr l = LuaSvr.mainState.L;
            LuaDLL.lua_getglobal(l, "package");
            LuaDLL.lua_pushstring(l,dataPath+ "?.lua");//这里也可以改成.txt

            LuaDLL.lua_setfield(l, -2, "path");
            LuaDLL.lua_pop(l, 1);
            self = LuaSvr.mainState.doString(initLua) as LuaTable;

            update = (LuaFunction)self["update"];//绑定Entry.lua中的update方法
            UD = update.cast();

        });
    }

在Unity3D所加载的Entry.lua文件中,一定要返回一个class,否则绑定Update方法会错,这是Entry.lua文件:

import "UnityEngine"
if not UnityEngine.GameObject or not  UnityEngine.UI then
	error("Click Make/All to generate lua wrap file")
end

print("Unity3D你好,我是Lua")
local class = { }
function class:update() -- gc alloc is zero
    print("这里是Lua脚本的Update方法")
end
return class

如果需要在Lua脚本中调用C#中的类,只需要在类的上面加上一个 [CustomLuaClass] 特性,然后点击Slua/All/Make会自动导出

Lua文件打包成AssetBundle

这里有个坑,Unity3D在打AB包的时候是无法识别.lua文件的,所以打包的时候需要将.lua文件拷贝一份,改成.txt或者其他Unity3D支持的后缀名,再进行打包,打包完毕后再删除,笔者是改成.txt文件,写了一个自用的打包工具,注释不多但是应该是可以看懂,直接用就好了,在菜单栏Tool/Resouce Manager下打开,注意:要选中Lua脚本的代码文件夹,且不要选中场景中的物体,否则会报错!!!

学习笔记——基于Unity3D的Lua热更新_第1张图片

以下是工具代码,要放记得在Unity3D的Editor文件夹下:

using System.IO;
using UnityEditor;
using UnityEngine;

public class Tools  {

    [MenuItem("Tools/Resources Manager #q", false, 1)]
    static void ResourcesManager()
    {
        ScriptableWizard.DisplayWizard("资源打包工具","确定");
    }
}

public enum BuildPath
{
    Android,
    Windows,
    //IOS
}

public class ResourcesWindow : ScriptableWizard
{
    //检测create按钮的点击
    public BuildPath type = BuildPath.Android;
    public string LuaPath = "D:/Unity3D/Slua/Assets/Slua/Resources/LuaCode";//lua脚本所在文件夹
    public string BuildRootPath= "C:/Users/Administrator.SC-201902281819/Desktop/坦克/AssetBoundles";//AB存放路径
    public bool Code = true;
    public bool Resouce = false;

    void OnWizardCreate()
    {
        string dir = BuildRootPath;
        if (type == BuildPath.Android)
        {
            dir += "/Android";
        }
        else
        {
            dir += "/Windows";
        }
        if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);

        DirectoryInfo TheFolder = new DirectoryInfo(LuaPath);
        string bundlePath = dir + "/LuaCode.unity3d";//Lua的AB包名称
        ChangeLua2Txt(TheFolder, LuaPath);//拷贝并修改后缀为.txt
        AssetDatabase.Refresh();//刷新一下

        if (type == BuildPath.Android)
        {
            if(Resouce)
                BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.Android);
            if(Code)
            {
                Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
                //打包
                BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, bundlePath,
                    BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.Android);
            }

        }
        else
        {
            if (Resouce)//打包资源
                BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
            if (Code)//打包lua脚本
            {
                Object[] selection = Selection.GetFiltered(typeof(Object), SelectionMode.DeepAssets);
                //打包
                BuildPipeline.BuildAssetBundle(Selection.activeObject, selection, bundlePath,
                    BuildAssetBundleOptions.CollectDependencies | BuildAssetBundleOptions.CompleteAssets, BuildTarget.StandaloneWindows64);
            }
        }

        DeleteTxt(TheFolder);//删除.txt文件
        AssetDatabase.Refresh();
    }

    static private void ChangeLua2Txt(DirectoryInfo TheFolder, string path)
    {
        foreach (var NextFile in TheFolder.GetFiles("*.lua"))
        {
            var utf8WithoutBom = new System.Text.UTF8Encoding(false);
            StreamReader sr = new StreamReader(NextFile.FullName, utf8WithoutBom);
            string text = sr.ReadToEnd();
            FileStream fs = new FileStream(path + "/" + Path.ChangeExtension(NextFile.Name, "txt"), FileMode.Create);
            StreamWriter sw = new StreamWriter(fs, utf8WithoutBom);
            sw.Write(text);

            sr.Close();

            sw.Flush();
            sw.Close();
            fs.Close();
        }
        foreach (DirectoryInfo NextFolder in TheFolder.GetDirectories())
        {
            ChangeLua2Txt(NextFolder, path);
        }
    }

    static private void DeleteTxt(DirectoryInfo TheFolder)
    {
        foreach (var NextFile in TheFolder.GetFiles("*.txt"))
        {
            NextFile.Delete();
        }
        foreach (DirectoryInfo NextFolder in TheFolder.GetDirectories())
        {
            DeleteTxt(NextFolder);
        }
    }

    //当前字段值修改的时候会被调用,对话框弹出时也会调用一次
    void OnEnable()
    {
        Debug.Log("本地IP地址:" + Network.player.ipAddress);
    }
}

这样就可以把lua代码打包成AB文件了

从AB包中读取Lua文件并运行

设置LuaSvr.mainState.loaderDelegate,返回AB包中的lua文件就可以了,每次在require lua文件都会调用这个委托,如果打包的时候是.txt文件,就要改成.txt后缀

// 加载lua文件Delagate
    private byte[] LuaFileLoader(string strFile, ref string absoluteFn)
    {       
        if (strFile == null)
        {
            return null;
        }

        string filename = "assets/resources/hotfix/lua/" + strFile.Replace('.', '/') + ".bytes";              
        ab = AssetBundle.LoadFromFile(Application.persistentDataPath + "/lua.unity3d");
        TextAsset textAsset = ab.LoadAsset(filename);
        return textAsset.bytes;
    }

因为笔者之前打包进AB包的lua文件本身就是.txt格式,有一个比较笨的办法(不建议使用。。。)——将AB包中的lua文件写入txt中保存到StreamingAssets目录下,然后再从StreamingAssets下去加载运行lua代码,到这里lua的热更新就实现了

using SLua;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;

[CustomLuaClass]
public class Entry : MonoBehaviour {

    LuaSvr svr;
    LuaTable self;
    LuaFunction update;

    [CustomLuaClass]
    public delegate void UpdateDelegate(object self);

    UpdateDelegate UD;
    AssetBundle AB;
    string dataPath
    {
        get { return Application.streamingAssetsPath + "/"; }
    }

    void Start()
    {
        StartCoroutine(LoadAssetsBoundle());
    }

    void Update()
    {
        if (UD != null) UD(self);
    }

    IEnumerator LoadAssetsBoundle()
    {
        //这里可以先下载版本文件,对比服务器版本,如果版本有更新再下载资源
        string path= @"http://127.0.0.1/C:/Users/Administrator.SC-201902281819/Desktop/坦克/AssetBoundles/Windows/LuaCode.unity3d";
        UnityWebRequest webRequest = UnityWebRequest.GetAssetBundle(path);
        yield return webRequest.SendWebRequest();
        if (webRequest.error != null) Debug.Log(webRequest.error);
        AssetBundle AB = DownloadHandlerAssetBundle.GetContent(webRequest);

        object[] luaList = AB.LoadAllAssets();
        foreach (var item in luaList)
        {
            var lua = item as TextAsset;
            var savePath = dataPath + lua.name + ".txt";
            FileStream FS = new FileStream(savePath, FileMode.Create, FileAccess.Write);//创建写入文件 
            StreamWriter SW = new StreamWriter(FS);
            SW.Write(lua.text);
            SW.Close();
            FS.Close();
        }
        StartLua();
    }

    private void StartLua()
    {
        //将AB中的lua文件保存到本地,读取 
        svr = new LuaSvr();
        svr.init(null, () =>
        {
            string initLua = @"return require(""Entry"")";
            IntPtr l = LuaSvr.mainState.L;
            LuaDLL.lua_getglobal(l, "package");
            LuaDLL.lua_pushstring(l,dataPath+ "?.txt");

            LuaDLL.lua_setfield(l, -2, "path");
            LuaDLL.lua_pop(l, 1);
            self = LuaSvr.mainState.doString(initLua) as LuaTable;

            update = (LuaFunction)self["update"];
            UD = update.cast();

        });
    }

    // lua调用 C# 脚本函数
    public void Log(string str)
    {
        Debug.Log("Lua调用C#Log方法:" + str);
    }
}

 

你可能感兴趣的:(学习笔记——基于Unity3D的Lua热更新)