1. 程式人生 > >[Unity XLua]熱更新XLua入門(一)-基礎篇

[Unity XLua]熱更新XLua入門(一)-基礎篇

Aladdin_XLua

前言

前段時間騰訊開源了一個內部熱更框架XLua在Unity開發群裡引起一陣熱議,也受到廣大開發者的熱捧,然後我當然也抱著好奇的心去學習學習。後面也會將擴充套件之後的工程放在git上,大家一起學習交流!在此感謝XLua作者創造出這麼好用的框架!

相關連結

個人對XLua看法

  1. 簡潔易用,容易上手
  2. 可擴充套件性高,新增自定義的CS模組或者第三方外掛非常方便
  3. 大廠維護,可靠
  4. 特色:HotFix
    關於這個HotFix是其他熱更lua框架所不具備的,也是他最大的優勢和特色之一,原理就是通過特性標記然後在IL邏輯層判斷修改邏輯,使程式支援熱更的lua邏輯程式碼而不是走之前的C#邏輯

自己擴充套件XLua支援NGUI開發

現在開源熱更Lua框架都很少支援NGUI了,可能現在趨勢都是用原生的UGUI,但估計還有一些NGUI粉喜歡用NGUI開發,畢竟NGUI用了很長時間,XLua例子裡面已經支援了lua使用UGUI,這裡我就自己補充讓它支援NGUI開發。後續我也會多新增一些UGUI的例子。先看看擴充套件的NGUI做的介面效果圖,然後下面再講解怎麼讓XLua支援第三方外掛。

效果圖

這裡寫圖片描述

快速上手

在學習一個東西之前最好先學習一下XLua作者辛辛苦苦寫的那些多教程文件,包括案例以及wiki和issu,如果還有什麼不明白的地方可以在加入文章最後的群我們一起交流學習。

1.自定義C#類供Lua訪問

這裡可以學習一下作者寫的Test中的例子,還是比較詳細,但我還是想記錄一下我自己嘗試的過程。

(1)特性方式

XLua支援使用特性標籤讓自定義的類可供Lua訪問
C#:

public class CSModelWithAttribute
{
    public static void SayHello1()
    {
        Debug.Log("Hello Aladdin_XLua, I am static model function");
    }
    public void SayHello2()
    {
        Debug.Log("Hello Aladdin_XLua, I am model function"
); } public void SayHello3(string s) { Debug.Log("Hello Aladdin_XLua, I am model function whih param:" + s); } public string SayHello4(string s) { Debug.Log("Hello Aladdin_XLua, 我是具有返回值的CS方法:" + s); return "你好,我獲得了lua,我是C#"; } public void SayHelloWithRefParam(ref string s) { Debug.Log("傳入的引數是:" + s); s = "Hello 我是C#"; } public string SayHelloWithRefParamAndReturnString(ref string s) { Debug.Log("傳入的引數是:" + s); s = "Hello 我是C#"; return "我是返回的字串"; } public void SayHelloWithOutParam(out string s) { s = "Hello,我是C#"; Debug.Log("Hello Aladdin_XLua, I am model function whih out param:" + s); } }

新增上特性標籤之後,程式在啟動的會自動查詢具有特性標記的類然後蒐集進入lua棧,使得我們可以用Lua訪問自定義的C#類和方法。

Lua訪問:

using UnityEngine;
using System.Collections;
using XLua;
public class SelfExampleSrc : MonoBehaviour
{
    LuaEnv luaenv = new LuaEnv();
    void Start()
    {
        luaenv.DoString(@"

            print('Lua訪問特性標記的物件方法')
            local luaM2 = CS.CSModelWithAttribute
            local luaO2 = luaM2()
            luaM2:SayHello1()
            luaO2:SayHello2()
            luaO2:SayHello3('我是阿拉丁')  --讀者反饋新增一個C#方法 PC不需要重新 Generate,安卓、ios需要

            --測試字串返回
            local value = luaO2:SayHello4('你好,我是lua')
            print(value)                              

            --測試ref
            local inputValue = '你好,我是lua'
            local outputValue = luaO2:SayHelloWithRefParam(inputValue)
            print(outputValue)                          --lua是通過字串返回

            local outValue1,outValue2 = luaO2:SayHelloWithRefParamAndReturnString(inputValue)
            print(outValue1)
            print(outValue2)

            --測試out
            inputValue = '我是測試lua'
            outputValue = luaO2:SayHelloWithOutParam(inputValue)
            print(outputValue)

            local luaM3 = CS.CSModelTest
            local luaO3 = luaM3()
            luaO3.TestDelegate('lua中測試委託')


            luaO3.onClick = function(obj)
                print('hello 我是lua')
                print(obj)
            end
            luaO3.onClick('我是lua')
        ");
    }
}
(2)wrap方式

如果是我們自己寫的C#程式碼,我們可以通過第一種方式新增特性來讓Lua支援也是比較方便,如果是用的開源第三方外掛,我們如何快速的讓XLua支援,就可以用過Generate Wrap的方式,這一點也是其他lua框架所採取的策略。

a)有名稱空間的類

C#:

namespace Aladdin_XLua
{
    public class CSModel
    {
        public void SayHello()
        {
            Debug.Log("Hello Aladdin_XLua");
        }
    }
}

Lua:

print('Lua訪問有名稱空間的物件/靜態方法')
local luaModel = CS.Aladdin_XLua.CSModel
local luaObj = luaModel()
luaObj:SayHello()

luaModel是C#類,下面luaObj是類生成的物件,最後是訪問物件方法,關於Lua冒號呼叫和點呼叫的區別:冒號呼叫預設傳入了self引數,不清楚的可以百度相關文章。

b)無名稱空間的類

C#:

public class CSModelWidhoutNameSpace
{
    public void SayHello()
    {
        Debug.Log("Hello Aladdin_XLua without Namespace");
    }
}

Lua:

print('Lua訪問無名稱空間的物件方法')
local luaM = CS.CSModelWidhoutNameSpace
local luaO = luaM()
luaO:SayHello()

如果沒有名稱空間的話直接CS後面就是類名,其實CS也是一個更外面一層的名稱空間,只不過是作者幫我們分裝的。

3)委託型別

C#:

public class CSModelTest
{
    public SelfVoidDelegate onClick;
    public delegate void SelfVoidDelegate(GameObject go);
    void OnClick() { Debug.Log("測試"); }

    public Action<string> TestDelegate = (param) =>
    {
        Debug.Log("TestDelegate in c#:" + param);
    };
}

委託其實也是跟Class平級的,委託也是一種型別,所以我們也需要對它像對待類那樣處理,通過新增特性標記或者通過Wrap方式處理,這裡委託是放在類裡面,其實也可以直接放在名稱空間下面,.NET庫是這樣操作的,但我們看NGUI原始碼會發現,NGUI原始碼都是這樣操作的,比如按鈕的onClick事件,看它的委託型別VoidDelegate就會發現也是這樣操作的,所以我這裡例子也放在類的裡面。

C#

public class CSModelTest
{
    public SelfVoidDelegate onClick;
    public delegate void SelfVoidDelegate(GameObject go);
    void OnClick() { Debug.Log("測試"); }

    public Action<string> TestDelegate = (param) =>
    {
        Debug.Log("TestDelegate in c#:" + param);
    };
}

Lua:

local luaM3 = CS.CSModelTest
local luaO3 = luaM3()
luaO3.TestDelegate('lua中測試委託')

luaO3.onClick = function(obj)
    print('hello 我是lua')
    print(obj)
end
luaO3.onClick('我是lua')
4)帶有ref out 引數的函式如何處理

因為Lua是弱型別沒有C#那麼多型別,有時候一些引數可能就不太好處理,比如C#的不同型別引數的過載,lua就不太好處理,這裡可以檢視XLua中的issues,作者有一個問題的相關解答。下面我舉例ref和out引數型別的函式Lua如何訪問。
C#:

public void SayHelloWithRefParam(ref string s)
{
    Debug.Log("傳入的引數是:" + s);
    s = "Hello 我是C#";
}

public string SayHelloWithRefParamAndReturnString(ref string s)
{
    Debug.Log("傳入的引數是:" + s);
    s = "Hello 我是C#";
    return "我是返回的字串";
}

public void SayHelloWithOutParam(out string s)
{
    s = "Hello,我是C#";
    Debug.Log("Hello Aladdin_XLua, I am model function whih out param:" + s);
}

Lua:

--測試ref
local inputValue = '你好,我是lua'
local outputValue = luaO2:SayHelloWithRefParam(inputValue)
print(outputValue)                          --lua是通過字串返回

local outValue1,outValue2 = luaO2:SayHelloWithRefParamAndReturnString(inputValue)
print(outValue1)
print(outValue2)

--測試out
inputValue = '我是測試lua'
outputValue = luaO2:SayHelloWithOutParam(inputValue)
print(outputValue)

一開始我測試的時候是本以為lua呼叫ref傳入的引數,也會返回出修改的結果,但出乎我的意料,並沒能修改,經過作者提示,lua是通過返回值返回的ref引數,如果函式本身就有返回值,那麼最後一個引數是返回的ref或者out引數,這個讀者可以嘗試一下。

執行結果

這裡寫圖片描述

關於Wrap

Wrap是C#跟Lua之間的一個橋樑,Lua想要訪問C#必須要用過Wrap訪問,相信看過其他Lua框架的這一點應該不陌生,XLua對生成Wrap也是非常方便。

我們只要新建一個類然後繼承一個GenConfig的介面,下面是介面內容,關於這幾個型別XLua文件中也有介紹,我們只需要把自定義的類新增到LuaCallCSharp集合中即可,然後點選Generate就會自動幫我們生成對應的Wrap檔案

//注意:使用者自己程式碼不建議在這裡配置,建議通過標籤來宣告!!
    public interface GenConfig 
    {
        //lua中要使用到C#庫的配置,比如C#標準庫,或者Unity API,第三方庫等。
        List<Type> LuaCallCSharp { get; }

        //C#靜態呼叫Lua的配置(包括事件的原型),僅可以配delegate,interface
        List<Type> CSharpCallLua { get; }

        //黑名單
        List<List<string>> BlackList { get; }
    }

當然作者也說了,我們自定的C#程式碼最好不要通過這種方式,我這裡只是演示如何新增,下面會說第三方外掛通過這話總方式支援。
C#:

public static class AladdinGenConfig
{
    //lua擴充套件第三方或者自定義類庫
    public class LuaCallCSharpExtern : GenConfig
    {

        public List<Type> LuaCallCSharp
        {
            get
            {
                return new List<Type>()
                {
                    typeof(CSModelWidhoutNameSpace),
                    typeof(CSModel),
                    typeof(CSModelTest),
                }
            }
        }
    }
}

2.NGUI擴充套件

正如上圖所示的效果,下面講述一下我是如何支援NGUI擴充套件的,也參考了作者UGUI的一個例子修改的。

a)生成Wrap介面

這一步上上面說的一樣,只要把NGUI的元件類全部都新增到LuaCallCSharp列表中然後Generate一下即可,這裡要注意的是元件中委託型別也需要新增進去。

b)搭建兩個UI介面,UI邏輯介面用C#,Lua是呼叫邏輯呼叫介面中C#的方法。

這裡寫圖片描述
這裡寫圖片描述

C#:
購買

using UnityEngine;
using System.Collections;
using XLua;
public class AsyncBuy : MonoBehaviour
{
    LuaEnv luaenv = null;

    void Start()
    {
        luaenv = new LuaEnv();
        luaenv.DoString("require 'async_buy'");
    }

    // Update is called once per frame
    void Update()
    {
        if (luaenv != null)
        {
            luaenv.Tick();
        }
    }
}

Panel邏輯:

using UnityEngine;
using UnityEngine.UI;
using XLua;
using System.Collections.Generic;
using System;
using UnityEngine.Events;

public class MessagePanel : MonoBehaviour
{
    /// <summary>
    /// 顯示對話彈框
    /// </summary>
    /// <param name="message"></param>
    /// <param name="title"></param>
    /// <param name="onFinished"></param>
    public static void ShowAlertPanel(string message, string title, Action onFinished = null)
    {
        Debug.Log("顯示提示彈框");
        var rootPanel = GameObject.Find("Panel").transform;
        var alertPanel = rootPanel.Find("AlertPanel");
        if (alertPanel == null)
        {
            alertPanel = (Instantiate(Resources.Load("AlertPanel")) as GameObject).transform;
            alertPanel.gameObject.name = "AlertPanel";
            alertPanel.SetParent(rootPanel);
            alertPanel.localPosition = Vector3.zero;
            alertPanel.localScale = Vector3.one;
        }
        alertPanel.Find("Title").GetComponent<UILabel>().text = title;
        alertPanel.Find("Content").GetComponent<UILabel>().text = message;
        alertPanel.gameObject.SetActive(true);
        if (onFinished != null)
        {
            var buyBtn = alertPanel.Find("BtnBuy").gameObject;
            buyBtn.SetActive(true);
            var button = buyBtn.GetComponent<UIButton>();
            UIEventListener.Get(buyBtn).onClick = go =>
            {
                onFinished();
                alertPanel.gameObject.SetActive(false);
            };
        }
    }

    /// <summary>
    /// 顯示確認彈框
    /// </summary>
    /// <param name="message"></param>
    /// <param name="title"></param>
    /// <param name="onFinished"></param>
    public static void ShowConfirmPanel(string message, string title, Action<bool> onFinished = null)
    {
        var rootPanel = GameObject.Find("Panel").transform;
        var confirmPanel = rootPanel.Find("ConfirmPanel");
        if (confirmPanel == null)
        {
            confirmPanel = (Instantiate(Resources.Load("ConfirmPanel")) as GameObject).transform;
            confirmPanel.gameObject.name = "ConfirmPanel";
            confirmPanel.SetParent(rootPanel);
            confirmPanel.localPosition = Vector3.zero;
            confirmPanel.localScale = Vector3.one;
        }
        confirmPanel.Find("Title").GetComponent<UILabel>().text = title;
        confirmPanel.Find("Content").GetComponent<UILabel>().text = message;
        confirmPanel.gameObject.SetActive(true);
        if (onFinished != null)
        {
            var confirmBtn = confirmPanel.Find("BtnBuy").GetComponent<UIButton>();
            var cancelBtn = confirmPanel.Find("CancelBuy").GetComponent<UIButton>();

            UIEventListener.Get(confirmBtn.gameObject).onClick = go =>
            {
                onFinished(true);
                confirmPanel.gameObject.SetActive(false);
            };

            UIEventListener.Get(cancelBtn.gameObject).onClick = go =>
            {
                confirmPanel.gameObject.SetActive(false);
            };
        }
    }
}

Lua:
lua檔案放在對應的Resources下即可
async_buy.lua


local util = require 'xlua.util'
local message_panel = require 'message_panel'

-------------------------async_recharge-----------------------------
local function async_recharge(num, cb) --模擬的非同步充值
    print('requst server...')
    cb(true, num)
end

local recharge = util.async_to_sync(async_recharge)
-------------------------async_recharge end----------------------------
local buy = function()
    message_panel.alert("餘額提醒","您餘額不足,請充值!")
    if message_panel.confirm("確認充值10元嗎?","確認框" ) then
        local r1, r2 = recharge(10)
        print('recharge result', r1, r2)
        message_panel.alert("提示","充值成功!")
    else
        print('cancel')
        message_panel.alert("提示","取消充值!")
    end
end

CS.UIEventListener.Get(CS.UnityEngine.GameObject.Find("BtnBuy").gameObject).onClick = util.coroutine_call(buy)

message_panel.lua


local util = require 'xlua.util'

local sync_alert = util.async_to_sync(CS.MessagePanel.ShowAlertPanel)
local sync_confirm = util.async_to_sync(CS.MessagePanel.ShowConfirmPanel) 

--構造alert和confirm函式
return {
    alert = function(title, message)
        if not message then
            title, message = message, title
        end
         sync_alert(message,title)
    end;

    confirm = function(title, message)
        local ret = sync_confirm(title,message)
        return ret == true
    end;
 }

執行的結果就如第一張圖所示

後續計劃

  • 新增資源熱更
  • 新增一個小遊戲Demo
  • 新增UGUI的案例

歡迎加群交流

1.unity遊戲開發群
QQ群
unity3d unity 遊戲開發

2.專門探討XLua的程式群:437645698

下載地址

相關推薦

[Unity XLua]更新XLua入門()-基礎

Aladdin_XLua 前言 前段時間騰訊開源了一個內部熱更框架XLua在Unity開發群裡引起一陣熱議,也受到廣大開發者的熱捧,然後我當然也抱著好奇的心去學習學習。後面也會將擴充套件之後的工程放在git上,大家一起學習交流!在此感謝XLua作者創造出

[Unity XLua]更新XLua入門(二)-俄羅斯方塊例項

前言 在xLua沒出來之前,開源的lua框架基本都是以介面用Lua開發為主,核心戰鬥用C#開發,但xLua出來之後主推C#開發,Lua用作HotFix,這裡我展示的第一個例子就是基於介面的經典2D小遊戲——俄羅斯方塊,介面邏輯是用C#寫,啟動載入邏輯是用lua

Xlua更新筆記

成員方法:通過物件呼叫 靜態方法:通過類呼叫 static全域性的,靜態方法只能放在靜態方法中,普通方法生命週期小於靜態方法(全域性的)如協程不能在靜態方法中呼叫 ***************************環境配置與匯入******************** 匯入

XLua----更新

一、xLua 環境配置 1).Xlua中  Plugin  Xlua複製到 需要熱更新的工程中---àAssets子目錄     2).開啟巨集HOTFIX_ENABLE File---->buildSetting---->playerSettin

Xlua更新

byte oid mage image 定義 btn 目錄 sources engine ---恢復內容開始--- 1.當配置好xlua環境後,依次點擊編輯欄的Xlua選項,如下: 2.在工程文件中找到Assets\Xlua\Resources,在此目錄下新建兩個t

XLua---更新

一.XLua環境配置 1.將XLua中  Plugin 和 XLua 複製到熱更新的工程中,注意要放在 Assets 子目錄     2.開啟巨集 HOTFIX_ENABLE File--->bulidSetting--->playerSetting---&g

XLua更新用法全流程總結(所有容易出問題的點)

Xlua熱更新流程總結 本文提供全流程,中文翻譯。 Chinar 堅持將簡單的生活方式,帶給世人!(擁有更好的閱讀體驗 —— 高解析度使用者請根據需求調整網頁縮放比例) China

xLua更新外掛

一.xLua外掛下載安裝 1.從GitHub上搜索並下載外掛    2.將檔案複製到unity中        3.檢查是否有錯誤 二.在unity中呼叫lua 1.簡單呼叫 在c#指令碼中使用LuaEnv類可以執行lua,建議LuaEnv例項全

Qt入門基礎 ( ) :Qt4及Qt5的下載與安裝

mingw ins 第3版 點擊 調試 但我 關系 構建 eas 轉載請註明出處:CN_Simo. 導語: Qt是一個跨平臺的C++圖形界面應用程序框架。它提供給開發者建立圖形用戶界面所需的功能,廣泛用於開發GUI程序,也可用於開發非GUI程序。Qt很容易擴展,並

Unity Dll更新

pac all bsp empty component instance oid config 最簡 最簡單的案例代碼,備後需使用 using System.Collections;using System.Collections.Generic;using System.

用ECMAScript4 ( ActionScript3) 實現Unity更新 -- 在腳本中使用MonoBehaviour

blog 腳本 tool urn 技術 build 右鍵 lan www. 繼上次分析了熱更新的Demo後,這次來介紹如何在熱更新代碼中使用MonoBehaviour。 MonoBehaviour掛載到GameObject對象上的腳本的基類。平常Unity開發時,簡單的做法

用ECMAScript4 ( ActionScript3) 實現Unity更新 -- 使用原型鏈和EventTrigger

rip sta untiy poi lib stat package 匿名 對象 原型鏈是JS的必備,作為ECMAScript4,原型鏈也是支持的。 特別說明,ActionScript3是支持完整的面向對象繼承支持的,原型鏈只在某些非常特殊的情況下使用。 本文旨在介紹如何使

用ECMAScript4 ( ActionScript3) 實現Unity更新 -- 使用FairyGUI (二)

src class 測試 資源 isp ola 物體 ddp onclick 上次講解了FairyGUI的最簡單的熱更新辦法,並對其中一個Demo進行了修改並做成了熱更新的方式。 這次我們來一個更加復雜一些的情況:Emoji. FairyGUI的 Example 04

用ECMAScript4 ( ActionScript3) 實現Unity更新 -- 更新Live2D

ini dma public lin img uil edi package module live2D是一個很強大的2D動畫組件。我們可以使用AS3腳本對它進行熱更新。 live2D在Unity中的使用請看這裏: 如何獲取Live2D 總得來說,我們可以先去live

Spring Boot 入門基礎

一、前言 Spring Boot 是由 Pivotal 團隊提供的全新框架,其設計目的是用來簡化新 Spring 應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。 本系列以快速入門為主,可當作工具小手冊閱讀

Unity 更新之ULua 踩坑

Unity 的原生c#是無法在移動端上進行熱更新的,那麼如果線上釋出遇到重大閃退事故的話,那麼就不可以通過遊戲內的熱更新進行bug修復,只能重新提交版本,而往往在提交版本到釋出的時間內,必然有玩家遇到這種問題,導致流失的,對於團隊來說,這個可是很嚴重的。 所以我google

unity 資源更新+顯示進度條:根據uri下資源

實現傳入uri,下載對應的資源,並且能支援多個uri的下載 例如: List<string> listUri = new List<string>(); List<string> lis

springboot入門基礎()

一、前言Spring Boot 是由 Pivotal 團隊提供的全新框架,其設計目的是用來簡化新 Spring 應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。本系列以快速入門為主,可當作工具小手冊閱讀二、環境搭建建立一個 maven 工程,目錄結構如下圖

Unity程式碼更新解決方案測試結果總結

這幾天一直在研究熱更新方案 主要思路是: 1.先將程式碼打包成dll,然後用unity 打包成assetsbundle, 2.WWW載入進入主程式, 3使用System.Reflection.Assembly來建立程式集, 4.然後通過GetType(className)

Unity伺服器更新專案總結

1.       當下載unity3D、txt、xml檔案時,切記在IIS中新增相應的MIME型別,否則會出現400錯誤 2.      www、bundleAsset、Asset之間的關係: 1.              WebStream:包括了壓縮的檔案,解壓所需的