(轉)Unity使用tolua框架教程: LuaFramewrk

 

 

一、tolua下載

toluaGitHub下載地址:https://github.com/topameng/tolua

 

 

假設我們下載的是LuaFramework_UGUI,它是基於Unity 5.0 + UGUI + tolua構建的工程

 

 

 

下載下來得到一個LuaFramework_UGUI-master.zip

 

 

 

二、運行Demo

1、生成註冊文件

解壓之後就是一個Unity的工程,直接用Unity打開,首次打開工程會詢問生成註冊文件,點擊確定即可

 

 

 

2、將lua打成AssetBundle

首先要執行lua資源的生成(打AssetBundle),點擊菜單【LuaFramework】-【Build Windows Resource】

 

 

 

會把lua代碼打成AssetBundle放在StreamingAssets中。

3、解決報錯

如果你用的不是Unity5.x,而是Unity2020,那麼可能會報錯:

 

 

 

這是因爲新版本的Unity有些屬性和接口已經廢棄了的原因,我們需要特殊處理一下
一個是Light類,一個是QualitySettings類,這兩個類我們一般不需要在lua中使用,所以我們不對他們生產Wrap即可:

  1、打開CustomSettings.cs,把 _GT(typeof(Light)),和 _GT(typeof(QualitySettings)),這兩行註釋掉
  2、然後單擊菜單【Lua】-【Clear wrap files】清理掉Wrap
  3、然後再單擊菜單【Lua】-【Generate All】重新生成Wrap,
  4、然後再重新點擊菜單【LuaFramework】-【Build Windows Resource】生成lua資源。

執行【Lua】-【Generate All】菜單的時候,你可能會報錯

 

 

 

定位到報錯的位置

 

 

 

添加判空

 

 

 

重新執行【Lua】-【Generate All】菜單
生成後應該還有報錯

 

 

 

這是因爲新版的ParticleSystem類新增了一些接口,我們可以定位到對應報錯的地方,把報錯的地方註釋掉。
不過爲了防止下次執行【Lua】-【Generate All】菜單時又被覆蓋導致報錯,我們可以把UnityEngine_ParticleSystemWrap.cs移動到BaseType目錄中

 

 

 

並把CustomSettings.cs中的_GT(typeof(ParticleSystem)),註釋掉。
並在LuaState.cs註冊ParticleSystemWrap類,要注意調用點要放在對應的BeginModul和EndModule之間,是什麼命名空間下的,就放在什麼Modul之下,如果是多級命名空間,則是嵌套多個BeginModul和EndModule。

// LuaState.cs
void OpenBaseLibs()
{
    // ...

    BeginModul("UnityEngine");
    // ...
    UnityEngine_ParticleSystemWrap.Register(this);
    EndModule();    //end UnityEngine
    
}

 

同理,UnityEngine_MeshRendererWrap.cs可能也會報錯,按上面的處理方式處理。

4、爲何一些沒有在CustomSettings.cs註冊的類也會生成Wrap類
假設我們把某個Wrap類手動移動到BaseType目錄中,並在CustomSettings.cs中註釋掉對應的_GT(typeof(xxx)),理論上應該不會生成對應的Wrap類,但事實上可能還是生成了,爲什麼?
這是因爲ToLua會將在CustomSettings.cs中註冊的類的父類進行遞歸生成。
舉個例子,CustomSettings.cs中把_GT(typeof(Component))註釋掉,執行【Lua】-【Generate All】菜單,依然會生成UnityEngine_ComponentWrap.cs,爲什麼?
因爲在CustomSettings.cs中有_GT(typeof(Transform)),而Transform的父類是Component,所以依然會生成UnityEngine_ComponentWrap.cs。
具體邏輯可以看ToLuaMenu.cs的AutoAddBaseType函數,它裏面就是進行遞歸生成父類的Wrap類的。
如果你將UnityEngine_ComponentWrap.cs移動到BaseType目錄中,並且不想重新生成UnityEngine_ComponentWrap.cs,可以在ToLuaMenu.cs的dropType數組中添加typeof(UnityEngine.Component)即可,不過不建議這麼做,因爲這裏有個坑!
這個坑就是Component的子類生成Wrap類是錯誤的。舉個例子,Transform是繼承Component,生成的UnityEngine_TransformWrap代碼是這樣的:

public class UnityEngine_TransformWrap
{
    public static void Register(LuaState L)
    {    
        L.BeginClass(typeof(UnityEngine.Transform), typeof(UnityEngine.Component));
    
        // ...
    }
}

當你在dropType數組中添加typeof(UnityEngine.Component),那麼生成出來的UnityEngine_RendererWrap是這樣的:

public class UnityEngine_TransformWrap
{
    public static void Register(LuaState L)
    {    
        L.BeginClass(typeof(UnityEngine.Transform), typeof(UnityEngine.Object));
    
        // ...
    }
}

發現沒有,會認爲Transform是繼承Object,而事實上,Transform是繼承Component的,這樣會導致你在lua中對於Component子類的對象無法訪問Component的public成員、屬性和方法。
比如下面這個會報錯,提示不存在gameObject成員或屬性。

-- 假設r是Transform對象
print(t.gameObject)

解決辦法就是不要在dropType數組中添加過濾類,而是在ToLuaExport.cs類的Generate方法中進行過濾,例:

// ToLuaExport.cs
public static void Generate(string dir)
{
    // ...
    if(type(Component) == type)
    {
        return;
    }
    // ...
}

5、順利生成AssetBundle

最後,【LuaFramework】-【Build Windows Resource】成功生成AssetBundle,我們可以在StreamingAssets中看到很多AssetBundle文件。

 

 

 

6、運行Demo場景

接下來,我們就可以運行Demo場景了。打開main場景

 

 

 

運行效果

 

 

 

7、Unity2020無報錯版LuaFramework-UGUI
如果你不想手動修復上報的報錯,我將修復好的版本上傳到了GitHub,使用Unity2020可以直接運行。
GitHub工程地址:https://github.com/linxinfa/Unity2020-LuaFramework-UGUI

 

 

 

三、開發環境IDE

可以使用subline,也可以使用visual studio,個人偏好使用visual studio,配合插件BabeLua

  Unity寫lua代碼的vs插件:BabeLua: https://blog.csdn.net/linxinfa/article/details/88191485

 

四、接口講解

1、MVC框架

 

 

 

上面這個Lua動態創建出來的面板的控制邏輯在PromptCtrl.lua腳本中,我們可以看到lua工程中使用了經典的MVC框架。

 

 

 

MVC全名是Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫,一種軟件設計典範,用一種業務邏輯、數據、界面顯示分離的方法組織代碼,
將業務邏輯聚集到一個部件裏面,在改進和個性化定製界面及用戶交互的同時,不需要重新編寫業務邏輯。

 

所有的controlerCtrlManager中註冊

-- CtrlManager.lua
function CtrlManager.Init()
    logWarn("CtrlManager.Init----->>>");
    ctrlList[CtrlNames.Prompt] = PromptCtrl.New();
    ctrlList[CtrlNames.Message] = MessageCtrl.New();
    return this;
end

 

通過CtrlManager獲取對應的controler對象,調用Awake()方法

-- CtrlManager.lua
local ctrl = CtrlManager.GetCtrl(CtrlNames.Prompt);
if ctrl ~= nil then
    ctrl:Awake();
end

 

controler類中,Awake()方法中調用C#PanelManagerCreatePanel方法

-- PromptCtrl.lua
function PromptCtrl.Awake()
    logWarn("PromptCtrl.Awake--->>");
    panelMgr:CreatePanel('Prompt', this.OnCreate);
end

 

C#PanelManagerCreatePanel方法去加載界面預設,並掛上LuaBehaviour腳本

 

 

 

這個LuaBehaviour腳本,主要是管理panel的生命週期,調用luapanelAwake,獲取UI元素對象

-- PromptPanel.lua

local transform;
local gameObject;

PromptPanel = {};
local this = PromptPanel;

--啓動事件--
function PromptPanel.Awake(obj)
    gameObject = obj;
    transform = obj.transform;

    this.InitPanel();
    logWarn("Awake lua--->>"..gameObject.name);
end

--初始化面板--
function PromptPanel.InitPanel()
    this.btnOpen = transform:Find("Open").gameObject;
    this.gridParent = transform:Find('ScrollView/Grid');
end

--單擊事件--
function PromptPanel.OnDestroy()
    logWarn("OnDestroy---->>>");
end

 

panelAwake執行完畢後,就會執行controlerOnCreate(),在controler中對UI元素對象添加一些事件和控制

-- PromptCtrl.lua
--啓動事件--
function PromptCtrl.OnCreate(obj)
    gameObject = obj;
    transform = obj.transform;

    panel = transform:GetComponent('UIPanel');
    prompt = transform:GetComponent('LuaBehaviour');
    logWarn("Start lua--->>"..gameObject.name);

    prompt:AddClick(PromptPanel.btnOpen, this.OnClick);
    resMgr:LoadPrefab('prompt', { 'PromptItem' }, this.InitPanel);
end

 

2、StartUp啓動框架

AppFacade.Instance.StartUp();   //啓動遊戲

 

這個接口會拋出一個NotiConst.START_UP事件,對應的響應類是StartUpCommand

using UnityEngine;
using System.Collections;
using LuaFramework;

public class StartUpCommand : ControllerCommand {

    public override void Execute(IMessage message) {
        if (!Util.CheckEnvironment()) return;

        GameObject gameMgr = GameObject.Find("GlobalGenerator");
        if (gameMgr != null) {
            AppView appView = gameMgr.AddComponent<AppView>();
        }
        //-----------------關聯命令-----------------------
        AppFacade.Instance.RegisterCommand(NotiConst.DISPATCH_MESSAGE, typeof(SocketCommand));

        //-----------------初始化管理器-----------------------
        AppFacade.Instance.AddManager<LuaManager>(ManagerName.Lua);
        AppFacade.Instance.AddManager<PanelManager>(ManagerName.Panel);
        AppFacade.Instance.AddManager<SoundManager>(ManagerName.Sound);
        AppFacade.Instance.AddManager<TimerManager>(ManagerName.Timer);
        AppFacade.Instance.AddManager<NetworkManager>(ManagerName.Network);
        AppFacade.Instance.AddManager<ResourceManager>(ManagerName.Resource);
        AppFacade.Instance.AddManager<ThreadManager>(ManagerName.Thread);
        AppFacade.Instance.AddManager<ObjectPoolManager>(ManagerName.ObjectPool);
        AppFacade.Instance.AddManager<GameManager>(ManagerName.Game);
    }
}

 

這裏初始化了各種管理器,我們可以根據具體需求進行改造和自定義。

 

3、LuaManager核心管理器

LuaManager這個管理器是必須的,掌管整個lua虛擬機的生命週期。它主要是加載lua庫,加載lua腳本,啓動lua虛擬機,執行Main.lua

 

4、AppConst常量定義

AppConst定義了一些常量。
其中AppConst.LuaBundleMode是lua代碼AssetBundle模式。它會被賦值給LuaLoader的beZip變量,在加載lua代碼的時候,會根據beZip的值去讀取lua文件,false則去search path中讀取lua文件,否則從外部設置過來的bundle文件中讀取lua文件。默認爲true。在Editor環境下,建議把AppConst.LuaBundleMode設爲false,這樣方便運行,否則寫完lua代碼需要生成AssetBundle纔可以運行到。

#if UNITY_EDITOR
        public const bool LuaBundleMode = false;                    //Lua代碼AssetBundle模式
#else
        public const bool LuaBundleMode = true;                    //Lua代碼AssetBundle模式
#endif

 

5、Lua代碼的讀取

LuaLoader和LuaResLoader都繼承LuaFileUtils。lua代碼會先從LuaFramework.Util.AppContentPath目錄解壓到LuaFramework.Util.DataPath目錄中,lua文件列表信息記錄在files.txt中,此文件也會拷貝過去。然後從LuaFramework.Util.DataPath目錄中讀取lua代碼。

 

 

 

/// LuaFramework.Util.DataPath

/// <summary>
/// 應用程序內容路徑
/// AppConst.AssetDir = "StreamingAssets"
/// </summary>
public static string AppContentPath() {
    string path = string.Empty;
    switch (Application.platform) {
        case RuntimePlatform.Android:
            path = "jar:file://" + Application.dataPath + "!/assets/";
        break;
        case RuntimePlatform.IPhonePlayer:
            path = Application.dataPath + "/Raw/";
        break;
        default:
            path = Application.dataPath + "/" + AppConst.AssetDir + "/";
        break;
    }
    return path;
}

/// <summary>
/// 取得數據存放目錄
/// </summary>
public static string DataPath {
    get {
        string game = AppConst.AppName.ToLower();
        if (Application.isMobilePlatform) {
            return Application.persistentDataPath + "/" + game + "/";
        }
        if (AppConst.DebugMode) {
            return Application.dataPath + "/" + AppConst.AssetDir + "/";
        }
        if (Application.platform == RuntimePlatform.OSXEditor) {
            int i = Application.dataPath.LastIndexOf('/');
            return Application.dataPath.Substring(0, i + 1) + game + "/";
        }
        return "c:/" + game + "/";
    }
}

 

完了之後,再進行遠程的更新檢測,看看用不用熱更lua代碼,遠程url就是AppConst.WebUrl,先下載files.txt,然後再讀取lua文件列表進行下載。

 

6、GameManager遊戲管理器

啓動框架後,會創建GameManager遊戲管理器,它負責檢測lua邏輯代碼的更新檢測和加載(Main.lua是在LuaManager中執行的),我們可以在GameManagerDoFile我們自定義的lua腳本,比如Game.lua腳本。

 

7、C#中如何直接調用lua的某個方法

GameManager可以獲取到LuaManager對象,通過LuaManager.CallFunction接口調用。
也可以用Util.CallMethod接口調用,兩個接口的參數有差異,需要注意。

/// LuaManager.CallFunction接口
public object[] CallFunction(string funcName, params object[] args) {
      LuaFunction func = lua.GetFunction(funcName);
      if (func != null) {
          return func.LazyCall(args);
      }
      return null;
  }

/// Util.CallMethod接口
public static object[] CallMethod(string module, string func, params object[] args) {
    LuaManager luaMgr = AppFacade.Instance.GetManager<LuaManager>(ManagerName.Lua);
    if (luaMgr == null) return null;
    return luaMgr.CallFunction(module + "." + func, args);
}

 

8、lua中如何調用C#的方法

假設現在我們有一個C#

using UnityEngine;

public class MyTest : MonoBehaviour
{
    public int myNum;

    public void SayHello()
    {
        Debug.Log("Hello,I am MyTest,myNum: " + myNum);
    }

    public static void StaticFuncTest()
    {
        Debug.Log("I am StaticFuncTest");
    }
}

 

我們想在lua中訪問這個MyTest類的函數。首先,我們需要在CustomSettings.cs中的customTypeList數組中添加類的註冊:
_GT(typeof(MyTest)),
然後然後再單擊菜單【Lua】-【Generate All】生成Wrap,生成完我們會看到一個MyTestWrap類

 

 

接下來就可以在lua中訪問了。(注意AppConst.LuaBundleMode的值要設爲false,方便Editor環境下運行lua代碼,否則需要先生成AssetBundle才能運行)

function Game.TestFunc()
    -- 靜態方法訪問
    MyTest.StaticFuncTest()
   
    local go = UnityEngine.GameObject("go")
    local myTest = go:AddComponent(typeof(MyTest))
    
    -- 成員變量
    myTest.myNum = 5
    -- 成員方法
    myTest:SayHello()
end

 

調用Game.TestFunc()

 

 注意,靜態方法、靜態變量、成員變量、成員屬性使用 “.” 來訪問,比如上面的 myTest.myNum,成員函數使用 “:” 來訪問,比如上面的 myTest:SayHello()

9、lua中如何使用協程

function fib(n)
    local a, b = 0, 1
    while n > 0 do
        a, b = b, a + b
        n = n - 1
    end

    return a
end

function CoFunc()
    print('Coroutine started')    
    for i = 0, 10, 1 do
        print(fib(i))                    
        coroutine.wait(0.1)                     
    end 
    print("current frameCount: "..Time.frameCount)
    coroutine.step()
    print("yield frameCount: "..Time.frameCount)

    local www = UnityEngine.WWW("http://www.baidu.com")
    coroutine.www(www)
    local s = tolua.tolstring(www.bytes)
    print(s:sub(1, 128))
    print('Coroutine ended')
end

調用

coroutine.start(CoFunc)

 

 

 

如果要stop協程,則需要這樣

local co = coroutine.start(CoFunc)
coroutine.stop(co)

 

10、lua解析json

假設現在有這麼一份json文件

{
    "glossary": {
        "title": "example glossary",
                "GlossDiv": {
            "title": "S",
                        "GlossList": {
                "GlossEntry": {
                    "ID": "SGML",
                                        "SortAs": "SGML",
                                        "GlossTerm": "Standard Generalized Mark up Language",
                                        "Acronym": "SGML",
                                        "Abbrev": "ISO 8879:1986",
                                        "GlossDef": {
                        "para": "A meta-markup language, used to create markup languages such as DocBook.",
                                                "GlossSeeAlso": ["GML", "XML"]
                    },
                                        "GlossSee": "markup"
                }
            }
        }
    }
}

 

假設我們已經把上面的json文件的內容保存到變量jsonStr字符串中,現在在lua中要解析它

local json = require 'cjson'

function Test(str)
    local data = json.decode(str)
    print(data.glossary.title)
    s = json.encode(data)
    print(s)
end

 

調用Test(jsonStr)

 

 

11、lua調用C#的託管

// c#傳託管給lua
System.Action<string> cb = (s) => { Debug.Log(s); };
Util.CallMethod("Game", "TestCallBackFunc", cb);

 

-- lua調用C#的託管
function Game.TestCallBackFunc(cb)
    if nil ~= cb then
       System.Delegate.DynamicInvoke(cb,"Hello, I am lua, I call Delegate")
    end
end

 

 

 

12、lua通過反射調用C#

有時候,我們沒有把我們的C#類生成Wrap,但是又需要在lua中調用,這個時候,可以通過反射來調用。
假設我們有一個C#類:MyClass

// MyClass.cs
public sealed class MyClass
{
    //字段
    public string myName;
    //屬性
    public int myAge { get; set; }
    
    //靜態方法
    public static void SayHello()
    {
        Debug.Log("Hello, I am MyClass's static func: SayHello");
    }

    public void SayNum(int n)
    {
        Debug.Log("SayNum: " + n);
    }

    public void SayInfo()
    {
        Debug.Log("SayInfo, myName: " + myName + ",myAge: " + myAge);
    }
}

 

lua

-- Game.lua
function Game.TestReflection()
    require 'tolua.reflection'
    tolua.loadassembly('Assembly-CSharp')
    local BindingFlags = require 'System.Reflection.BindingFlags'

    local t = typeof('MyClass')
    -- 調用靜態方法
    local func = tolua.getmethod(t, 'SayHello')
    func:Call()
    func:Destroy()
    func = nil

    -- 實例化
    local obj = tolua.createinstance(t)
    -- 字段
    local field = tolua.getfield(t, 'myName')
    -- 字段Set
    field:Set(obj, "linxinfa")
    -- 字段Get
    print('myName: ' .. field:Get(obj))
    field:Destroy()
    
    -- 屬性
    local property = tolua.getproperty(t, 'myAge')
    -- 屬性Set
    property:Set(obj, 29, null)
    -- 屬性Get
    print('myAge: ' .. property:Get(obj, null))
    property:Destroy()
    
    --public成員方法SayNum
    func = tolua.getmethod(t, 'SayNum', typeof('System.Int32'))
    func:Call(obj, 666)
    func:Destroy()
    
    --public成員方法SayInfo
    func = tolua.getmethod(t, 'SayInfo')
    func:Call(obj)
    func:Destroy()
end

 

調用Game.TestReflection()

 

 

13、nil和null

nil是lua對象的空,null表示c#對象的空。假設我們在c#中有一個GameObject對象傳遞給了lua的對象a,接下來我們把這個GameObject對象Destroy了,並在c#中把這個GameObject對象賦值爲null,此時lua中的對象a並不會等於nil
如果要在lua中判斷一個對象是否爲空,安全的做法是同時判斷nil和null

-- lua中對象判空
function IsNilOrNull(o)
    return nil == o or null == o
end

 

14、獲取今天是星期幾

-- 1是週日,2是週一,以此類推
function GetTodayWeek()
    local t = os.date("*t", math.floor(os.time()))
    return t.wday
end

 

15、獲取今天的年月日

方法一

function GetTodayYMD()
    local t = os.date("*t", math.floor(os.time()))
    return t.year .. "/" .. t.month .. "/" .. t.day
end

 

方法二

function GetTodayYMD()
    -- 如果要顯示時分秒,則用"%H:%M:%S"
    return os.date("%Y/%m%d", math.floor(os.time()))
end

 

16、字符串分割

-- 參數str是你的字符串,比如"小明|小紅|小剛"
-- 參數sep是分隔符,比如"|"
-- 返回值爲{"小明","小紅","小剛"}
function SplitString(str, sep)
    local sep = sep or " "
    local result = {}
    local pattern = string.format("([^%s]+)", sep)
    string.gsub(s, pattern, function(c) result[#result + 1] = c end)
    return result 
end

 

17、大數字加逗號分割(數字會轉成字符串)

-- 參數num是數字,如3428439,轉換結果"3,428,439"
function FormatNumStrWithComma(num)
    local numstr = tostring(num)
    local strlen = string.len(numstr)
    local splitStrArr = {}
    for i = strlen, 1, -3 do
        local beginIndex = (i - 2 >= 1) and (i - 2) or 1
        table.insert(splitStrArr, string.sub(numstr, beginIndex, i))
    end
    local cnt = #splitStrArr
    local result = ""
    for i = cnt, 1, -1 do
        if i == cnt then
            result = result .. splitStrArr[i]
        else
            result = result .. "," .. splitStrArr[i]
        end
    end
    return result
end

 

18、通過組件名字添加組件

-- 緩存
local name2Type = {}
-- 參數gameObject物體對象
-- 參數componentName,組件名字,字符串
function AddComponent(gameObject, componentName)
    local component = gameObject:GetComponent(componentName)
    if nil ~= component then return component end

    local componentType = name2Type[componentName]
    if nil == componentType then
        componentType  = System.Type.GetType(componentName)
        if nil == componentType then
            print("AddComponent Error: " .. componentName)
            return nil
        else
            name2Type[componentName] = componentType 
        end
    end
    return gameObject:AddComponent(componentType)
end

 

19、深拷貝

lua中的table是引用類型,有時候我們爲了不破壞原有的table,可能要用到深拷貝

function DeepCopy(t)
    if nil == t then return nil end
    local result = ()
    for k, v in pairs(t) do
        if "table" == type(v) then
            result[k] = DeepCopy(v)
        else
            result[k] = v
        end
    end
    return result
end

 

20、四捨五入

function Round(fnum)
    return math.floor(fnum + 0.5)
end

 

21、檢測字符串是否含有中文

-- 需要把C#的System.Text.RegularExpressions.Regex生成Wrap類
function CheckIfStrContainChinese(str)
    return System.Text.RegularExpressions.Regex.IsMatch(str, "[\\u4e00-\\u9fa5]")
end

 

22、數字的位操作get、set

-- 通過索引獲取數字的某一位,index從1開始
function GetBitByIndex(num, index)
    if nil == index then
        print("LuaUtil.GetBitByIndex Error, nil == index")
        return 0
    end
    local b = bit32.lshift(1,(index - 1))
    if nil == b then
        print("LuaUtil.GetBitByIndex Error, nil == b")
        return 0
    end
    return bit32.band(num, b)
end

-- 設置數字的某個位爲某個值,num:目標數字,index:第幾位,從1開始,v:要設置成的值,0或1
function SetBitByIndex(num, index, v)
    local b = bit32.lshift(1,(index - 1))
    if v > 0 then
        num = bit32.bor(num, b)
    else
        b = bit32.bnot(b)
        num = bit32.band(num, b)
    end
    return num
end

 

23、限制字符長度,超過進行截斷

有時候,字符串過長需要截斷顯示,比如有一個暱稱叫“我的名字特別長一行顯示不下”,需求上限制最多顯示5個字,超過的部分以…替代,即"我的名字特…"。首先要計算含有中文的字符串長度,然後再進行截斷

-- 含有中文的字符串長度
function StrRealLen(str)
    if str == nil then return 0 end
    local count = 0
    local i = 1
    while (i < #str) do
        local curByte = string.byte(str, i)
        local byteCount = 1
        if curByte >= 0 and curByte <= 127 then
            byteCount = 1
        elseif curByte >= 192 and curByte <= 223 then
            byteCount = 2
        elseif curByte >= 224 and curByte <= 239 then
            byteCount = 3
        elseif curByte >= 240 and curByte <= 247 then
            byteCount = 4
        end
        local char = string.sub(str, i, i + byteCount - 1)
        i = i + byteCount
        count = count + 1
    end
    return count
end

-- 限制字符長度(多少個字)
-- 參數str,爲字符串
-- 參數limit爲限制的字數,如8
-- 參數extra爲當超過字數時,在尾部顯示的字符串,比如"..."
function LimitedStr(str, limit, extra)
    limit = limit or 8
    extra = extra or ""
    local text = ""
    -- 含有中文的字符串長度
    if StrRealLen(str) > limit then
        text = LuaUtil.sub_chars(str, limit) .. "..." .. extra
    else
        text = str .. extra
    end
    return text
end

 

24、判斷字符串A是否已某個字符串B開頭

-- 判斷字符串str是否是以某個字符串start開頭
function StringStartsWith(str, start)
    return string.sub(str, 1, string.len(start)) == start
end

 

五、熱更lua與資源

1、熱更lua

打app整包的時候,備份一份lua全量文件,後面打lua增量包的時候,根據文件差異進行比對,新增和差異的lua文件打成一個lua_update.bundle,放在一個update文件夾中,並壓縮成zip,放到服務器端,客戶端通過https下載增量包並解壓到Application.persistentDataPath目錄。遊戲加載lua文件的時候,優先從update文件夾中的lua_update.bundle中查找lua腳本。

2、熱更資源熱更資源

做個編輯器工具,指定某個或某些資源文件(預設、音頻、動畫、材質等),打成多個assetbundle,放在一個update文件夾中,並壓縮成一個zip,放到服務器端,客戶端通過https下載增量包並解壓到Application.persistentDataPath目錄。
遊戲加載資源文件的時候,優先從update文件夾中查找對應的資源文件。

3、真機熱更資源存放路徑

persistentDataPath/res/
                      ├──/update/
                      │       ├──/lua/   
                      │       │    └──lua_update.bundle            #lua增量bundle
                      │       ├──/res/
                      │       │    ├──aaa.bundle                   #預設aaa的bundle
                      │       │    ├──bbb.bundle                   #音頻bbb的bundle
                      │       │    └──...                          #其他各種格式的資源bundle
                      │       └──/cfg/
                      │            ├──cfg.bundle                   #配置增量bundle
                      │            └──...                          #其他文本或二進制文件增量bundle
                      ├──out_put.log                               #遊戲日誌
                      └──...

 

關於persistentDataPath,可以參見我這篇博客:https://blog.csdn.net/linxinfa/article/details/51679528

 

 

轉載鏈接:https://blog.csdn.net/linxinfa/article/details/88246345

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章