Lua热更新的架构分析

首先是资源(我这里是UI,一个Panel为一个资源),对应着一个MonoBehaviour的脚本和一个功能一致的Lua脚本,将资源打包成AssetBundle,lua脚本不需要打包(加密应该是必须的),都放到远程服务器上。通过配置表Table(也是远程加载的)决定是使用MonoBehavoiur还是Lua脚本,使用一个UILuaBehaviour来承载保存lua的模块名,UILuaBehaviour是一些UI常用到的方法,从而在Lua脚本中可以获取这个脚本调用这些功能。


启动的时候,在GameLuaManager中初始化时实例化LuaState或LuaScriptMgr,调用Start方法开始,调用一个Lua脚本的管理器加载一些公用的依赖,以便后面lua脚本使用这些公用方法。

using UnityEngine;
using System.Collections;
using System.IO;
using System;

public class GameLuaManager : Singleton<GameLuaManager> {

	LuaScriptMgr luaScript = null;
	public void Initialize()
	{
        if (luaScript == null)
        {
            luaScript = new LuaScriptMgr();
            luaScript.Start();
            luaScript.DoFile("Logic/GameManager");
            Util.CallMethod("GameManager", "Main");
        }
	}

	public LuaScriptMgr LuaManager
	{
		get{return luaScript;}
	}
}


--Lua GameManager.lua
require "Common/define" --在这里就引入全局了,一些全局定义
require "Net/Network" --网络通信
require "Common/functions" --一些工具的方法
--管理器--
GameManager = {};
local this = GameManager;

function GameManager.Main()
	require("Net/Network").new()
end

--例如functions定义了一些打印日志的方法,Util是c#脚本warp映射到lua中的
--输出日志--
function log(str)
    Util.Log(str);
end

--错误日志--
function error(str) 
	Util.LogError(str);
end

--警告日志--
function warn(str) 
	Util.LogWarning(str);
end


然后各个管理器初始化完成,检查资源更新,会把需要的UI面板和对应的Lua脚本下载到本地上,GameUIManager会根据需求加载UI面板并通过配置查看是否使用lua从而添加UILuaBehaviour,UILuaBehaviour会根据面板的名字找到Lua模块,然后Awake,Start等方法触发是直接Call的Lua中的方法。

//GameUIManager
public PanelBase Show(string strName, bool useCoroutine = true, bool fromPakLoad = false, bool bNeedDarkFollow = false)
{
    UIAsset asset = GetUIAsset(strName);
    if (asset == null)
        return null;
    //控制主界面隐藏
    if (asset.ui.IsCloseMain)
    {
        GetUIAsset("Main").panel.gameObject.SetActive(false);
    }
    if (asset.ui.IsHideChar)
    {
        CharMagr.Get().GetLoader().IsVisible = false;
    }
    if (Main.gameState == Game_State_Type.GST_Fight)
    {
        if (asset.ui.fightShow == 0)
            return null;
    }

    HideAll(asset);

    if (asset.ui.enableDark)
        ShowDark(true , false ,asset);

    uiTimeList.Remove(asset);
    Main.Game.IsHoverUI = false;
    if (asset.oUIObject != null)
    {
        asset.oUIObject.SetActive(true);

        if (asset.panel != null)
        {
            asset.panel.OnShow();
        }
        else if (asset.luaBehaviour != null)
        {
            asset.luaBehaviour.OnShow();
        }

        uiActive.Add(asset);
        return asset.panel;
    }

    //主要在这里,首次加载的
    UnityEngine.Object obj = null;
    if (asset.ui.lua == "")
    {	// cs的界面
        obj = CorResMgr.Get().LoadResource(CONST_VALUE.UI_ROOT_PATH + asset.ui.prefab);
        if (obj == null)
            return null;

        asset.oUIObject = (GameObject)GameObject.Instantiate(obj);
        asset.oUIObject.transform.parent = root.transform;
        asset.oUIObject.transform.localScale = Vector3.one * CalcScale(asset);
        asset.oUIObject.transform.localPosition = Vector3.zero;
        asset.panel = asset.oUIObject.GetComponent<PanelBase>();
    }
    else
    {	// lua的界面
        obj = CorResMgr.Get().LoadResource(CONST_VALUE.UI_ROOT_PATH + asset.ui.prefab);
        if (obj == null)
            return null;

        asset.oUIObject = (GameObject)GameObject.Instantiate(obj);
        asset.oUIObject.transform.parent = root.transform;
        asset.oUIObject.transform.localScale = Vector3.one * CalcScale(asset);
        asset.oUIObject.transform.localPosition = Vector3.zero;

        GameLuaManager.Get().LuaManager.DoFile("UI/" + asset.ui.lua);//编译配置表指定Lua脚本
        //添加UILuaBehaviour脚本来做中间人去驱动调用lua方法
        asset.luaBehaviour = asset.oUIObject.AddComponent<UILuaBehaviour>();
    }

    UIPanel n_ObjPanel = asset.oUIObject.GetComponent<UIPanel>();
    if (n_ObjPanel)
        n_ObjPanel.depth = asset.ui.Depth;

    UIAnchor[] anchors = asset.oUIObject.GetComponentsInChildren<UIAnchor>();

    for (int i = 0; i < anchors.Length; i++)
    {
        anchors[i].uiCamera = uiCamera;
    }

    if (asset.panel != null)
    {
        asset.panel.OnShow();
    }
    else if (asset.luaBehaviour != null)
    {
        asset.luaBehaviour.OnShow();
    }
    
    uiActive.Add(asset);
    if (asset.panel != null)
    {
        asset.panel.m_UIName = strName;
    }
    obj = null;
    return asset.panel;
}


下面看一下UILuaBehaviour

public class UILuaBehaviour : PanelBase
{
    protected static bool initialize = false;

    private string data = null;

    private List<LuaFunction> buttons = new List<LuaFunction>();

    private string luaFileName = "";
    protected void Awake()
    {
        luaFileName = name.Split('_')[1]; //这里自动就知道模块lua的文件名了
        CallMethod("Awake", gameObject);//把gameobject传递给lua知道,然它知道控制的是谁
    }

    void Start()
    {
        if (initialize)
            return;

        CallMethod("Start");
        initialize = true;
    }

    public override void OnShow()
    {
        CallMethod("OnShow");
    }


    public virtual void OnHide()
    {
        CallMethod("OnHide");
    }

    public override void ValueChange(UICmd cmd)
    {
        CallMethod("ValueChange", cmd.key, cmd.values);
    }

    /// <summary>
    /// 添加单击事件
    /// </summary>
    public void AddClick(string ctrName, LuaFunction luafunc)
    {

        if (string.IsNullOrEmpty(ctrName))
            return;
        GameObject go = transform.FindChild(ctrName).gameObject;
        if (go == null)
            return;
        UIEventListener.Get(go).onClick = delegate (GameObject o)
        {
            luafunc.Call(go);
            buttons.Add(luafunc);
        };
    }

    /// <summary>
    /// 根据gameobject绑定按钮事件
    /// </summary>
    /// <param name="btn"></param>
    /// <param name="luafunc"></param>
    public void AddClickForObj(GameObject btn, LuaFunction luafunc)
    {
        if (btn == null)
            return;
        UIEventListener.Get(btn).onClick = delegate (GameObject o)
        {
            luafunc.Call(btn);
            buttons.Add(luafunc);
        };
    }


    /// <summary>
    /// 带一个整形参数的回调
    /// </summary>
    public void AddClickForObjWithIntParam(GameObject btn, LuaFunction luafunc, int iParam)
    {

        if (btn == null)
            return;
        UIEventListener.Get(btn).onClick = delegate (GameObject o)
        {
            luafunc.Call(iParam);
            buttons.Add(luafunc);
        };
    }

    /// <summary>
    /// 清除单击事件
    /// </summary>
    public void ClearClick()
    {
        for (int i = 0; i < buttons.Count; i++)
        {
            if (buttons[i] != null)
            {
                buttons[i].Dispose();
                buttons[i] = null;
            }
        }
    }

    /// <summary>
    /// 执行Lua方法
    /// </summary>
    protected object[] CallMethod(string func, params object[] args)
    {
        return Util.CallMethod(luaFileName, func, args);
    }

    public override void OnDestroy()
    {
        ClearClick();
        initialize = false;
        CallMethod("OnDestroy");
    }
}


最后看一下上面最常用到的Util.CallMethod方法,工具方法调用lua的方法

/// <summary>
/// 执行Lua方法
/// </summary>
public static object[] CallMethod(string module, string func, params object[] args) {
	LuaScriptMgr luaMgr = GameLuaManager.Get().LuaManager;
	if (luaMgr == null)
        return null;
	string funcName = module + "." + func;
	funcName = funcName.Replace("(Clone)", "");
        return luaMgr.CallLuaFunction(funcName, args);
}


最后看一个ui的lua脚本例子

--LoginZiJia.lua 
LoginZiJia = {} --这个模块,对应LoginZiJia这个UIPrefab
local this = LoginZiJia
local gameObject = nil
local playerName = ""
local playerSex = 0
local playerProfession = 0
local iP = ""
local port = 0

function LoginZiJia.Awake(obj)

	gameObject = obj --把实例化的GameObject传递过来了
end

function LoginZiJia.Start()

	playerName = GameObject.Find('Scale/Input_Name'):GetComponent('UIInput')
	playerName.text = "test2018"
	playerSex = GameObject.Find('Scale/Input_Sex'):GetComponent('UIInput')
	playerSex.text ='0'
	playerProfession = GameObject.Find('Scale/Input_Profession'):GetComponent('UIInput')
	playerProfession.text = '0'
	iP = GameObject.Find('Scale/Input_IP'):GetComponent('UIInput')
	iP.text = GameAPI.getZiJiaIP()
	port = GameObject.Find('Scale/Input_Port'):GetComponent('UIInput')
	port.text = tostring(GameAPI.getZiJiaPort())
	--获取UILuaBehaviour可以使用一些方便的方法
	local uilua = gameObject:GetComponent('UILuaBehaviour')
	uilua:AddClick('Scale/LoginBtn',this.onLogin)
	uilua:AddClick('Scale/LoginReturn',this.onBack)
end

首页 我的博客
粤ICP备17103704号