1. 程式人生 > >【Unity遊戲開發】tolua之wrap文件的原理與使用

【Unity遊戲開發】tolua之wrap文件的原理與使用

nop 微信 attr hiera n) 接下來 system 作者 prim

  本文內容轉載自:https://www.cnblogs.com/blueberryzzz/p/9672342.html 。非常感謝原作者慷慨地授權轉載,比心!@blueberryzzz 是位大神,歡迎大家關註他的博客。馬三對原文的排版與結構做了微調,以便更合適閱讀。

一、什麽是wrap文件

  每個wrap文件都是對一個c#類的包裝,在lua中,通過對wrap類中的函數調用,間接的對c#實例進行操作。

二、wrap類文件生成和使用的總體流程

技術分享圖片

三、生成一個wrap文件的流程

  這部分主要通過分析類的反射信息完成。

技術分享圖片

四、wrap文件內容解析

使用UnityEngine_GameObjectWrap.cs進行舉例。

1.註冊部分

 1 public static void Register(LuaState L)
 2 {
 3     L.BeginClass(typeof(UnityEngine.GameObject), typeof(UnityEngine.Object));
 4     L.RegFunction("CreatePrimitive", CreatePrimitive);
 5     L.RegFunction("GetComponent", GetComponent);
 6     L.RegFunction("GetComponentInChildren
", GetComponentInChildren); 7 L.RegFunction("GetComponentInParent", GetComponentInParent); 8 L.RegFunction("GetComponents", GetComponents); 9 L.RegFunction("GetComponentsInChildren", GetComponentsInChildren); 10 L.RegFunction("GetComponentsInParent", GetComponentsInParent); 11
L.RegFunction("SetActive", SetActive); 12 L.RegFunction("CompareTag", CompareTag); 13 L.RegFunction("FindGameObjectWithTag", FindGameObjectWithTag); 14 L.RegFunction("FindWithTag", FindWithTag); 15 L.RegFunction("FindGameObjectsWithTag", FindGameObjectsWithTag); 16 L.RegFunction("Find", Find); 17 L.RegFunction("AddComponent", AddComponent); 18 L.RegFunction("BroadcastMessage", BroadcastMessage); 19 L.RegFunction("SendMessageUpwards", SendMessageUpwards); 20 L.RegFunction("SendMessage", SendMessage); 21 L.RegFunction("New", _CreateUnityEngine_GameObject); 22 L.RegFunction("__eq", op_Equality); 23 L.RegFunction("__tostring", ToLua.op_ToString); 24 L.RegVar("transform", get_transform, null); 25 L.RegVar("layer", get_layer, set_layer); 26 L.RegVar("activeSelf", get_activeSelf, null); 27 L.RegVar("activeInHierarchy", get_activeInHierarchy, null); 28 L.RegVar("isStatic", get_isStatic, set_isStatic); 29 L.RegVar("tag", get_tag, set_tag); 30 L.RegVar("scene", get_scene, null); 31 L.RegVar("gameObject", get_gameObject, null); 32 L.EndClass(); 33 }

這部分代碼由GenRegisterFunction()生成,可以看到,這些代碼分為了4部分:

  • 1.BeginClass部分,負責類在lua中的初始化部分
  • 2.RegFunction部分,負責將函數註冊到lua中
  • 3.RegVar部分,負責將變量和屬性註冊到lua中
  • 4.EndClass部分,負責類結束註冊的收尾工作

BeginClass部分

  ①用於創建類和類的元表,如果類的元表的元表(類的元表是承載每個類方法和屬性的實體,類的元表的元表就是類的父類)
  ②將類添加到loaded表中。
  ③設置每個類的元表的通用的元方法和屬性,__gc,name,ref,__cal,__index,__newindex。

RegFunction部分

  每一個RefFunction做的事都很簡單,將每個函數轉化為一個指針,然後添加到類的元表中去,與將一個c函數註冊到lua中是一樣的。

RegVar部分

  每一個變量或屬性或被包裝成get_xxx,set_xxx函數註冊添加到類的元表的gettag,settag表中去,用於調用和獲取。

EndClass部分

  做了兩件事:
  ①設置類的元表
  ②把該類加到所在模塊代表的表中(如將GameObject加入到UnityEngine表中)

2.每個函數的實體部分

  由於構造函數,this[],get_xxx,set_xxx的原理都差不多,都是通過反射的信息生成的,所以放在一起用一個實例講一下(使用GameObject的GetComponent函數進行說明)。

 1 [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
 2 static int GetComponent(IntPtr L)
 3 {
 4     try
 5     {
 6         //獲取棧中參數的個數
 7         int count = LuaDLL.lua_gettop(L);
 8         //根據棧中元素的個數和元素的類型判斷該使用那一個重載
 9         if (count == 2 && TypeChecker.CheckTypes<string>(L, 2))
10         {
11             //將棧底的元素取出來,這個obj在棧中是一個fulluserdata,需要先將這個fulluserdata轉化成對應的c#實例,也就是調用這個GetComponent函數的GameObject實例
12             UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
13             //將棧底的上一個元素取出來,也就是GetComponent(string type)的參數
14             string arg0 = ToLua.ToString(L, 2);
15             //通過obj,arg0直接第調用GetCompent(string type)函數
16             UnityEngine.Component o = obj.GetComponent(arg0);
17             //將調用結果壓棧
18             ToLua.Push(L, o);
19             //返回參數的個數
20             return 1;
21         }
22         //另一個GetComponent的重載,跟上一個差不多,就不詳細說明了
23         else if (count == 2 && TypeChecker.CheckTypes<System.Type>(L, 2))
24         {
25             UnityEngine.GameObject obj = (UnityEngine.GameObject)ToLua.CheckObject(L, 1, typeof(UnityEngine.GameObject));
26             System.Type arg0 = (System.Type)ToLua.ToObject(L, 2);
27             UnityEngine.Component o = obj.GetComponent(arg0);
28             ToLua.Push(L, o);
29             return 1;
30         }
31         //參數數量或類型不對,沒有找到對應的重載,拋出錯誤
32         else
33         {
34             return LuaDLL.luaL_throw(L, "invalid arguments to method: UnityEngine.GameObject.GetComponent");
35         }
36     }
37     catch (Exception e)
38     {
39         return LuaDLL.toluaL_exception(L, e);
40     }
41 }

  可以看到,GetComponent函數的內容,其實就是通過反射分析GetComponent的重載個數,每個重載的參數個數,類型生成的。具體內容和lua調用c函數差不多。

3.每個函數實際的調用過程

假如說在lua中有這麽一個調用:

1 local tempGameObject = UnityEngine.GameObject("temp")
2 local transform = tempGameObject.GetComponent("Transform")

第二行代碼對應的實際調用過程是:

  • 1.先去tempGameObject的元表GameObject元表中嘗試去取GetComponent函數,取到了。
  • 2.調用取到的GetComponent函數,調用時會將tempGameObject,"Transform"作為參數先壓棧,然後調用GetComponent函數。
  • 3.接下來就進入GetComponent函數內部進行操作,因為生成了新的ci,所以此時棧中只有tempGameOjbect,"Transfrom"兩個元素。
  • 4.根據參數的數量和類型判斷需要使用的重載。
  • 5.通過tempGameObject代表的c#實例的索引,在objects表中找到對應的實例。同時取出"Transform"這個參數,準備進行真正的函數調用。
  • 6.執行obj.GetComponent(arg0),將結果包裝成一個fulluserdata後壓棧,結束調用。
  • 7.lua中的transfrom變量賦值為這個壓棧的fulluserdata。
  • 8.結束。

其中3-7的操作都在c#中進行,也就是wrap文件中的GetComponent函數。

五、一個類通過wrap文件註冊進lua虛擬機後是什麽樣子的

  使用GameObjectWrap進行舉例。

技術分享圖片

可以看到GameObject的所有功能都是通過一個元表實現的,通過這個元表可以調用GameObjectWrap文件中的各個函數來實現對GameObject實例的操作,這個元表對使用者來說是不可見的,因為我們平時只會在代碼中調用GameObject類,GameObject實例,並不會直接引用到這個元表,接下來來分析一下GameObject類,GameObject實例與這個元表的關系:

  • GameObject類:其實只是一個放在_G表中供人調用的一個充當索引的表,我們通過它來觸發GameObject元表的各種元方法,實現對c#類的使用。
  • GameObject的實例:是一個fulluserdata,內容為一個整數,這個整數代表了這個實例在objects表中的索引(objects是一個用list實現的回收鏈表,lua中調用的c#類實例都存在這個裏面,後面會講這個objects表),每次在lua中調用一個c#實例的方法時,都會通過這個索引找到這個索引在c#中對應的實例,然後進行操作,最後將操作結果轉化為一個fulluserdata(或lua的內建類型,如bool等)壓棧,結束調用。

六、在lua中調用一個c#實例中的函數或變量的過程

local tempGameObject = UnityEngine.GameObject("temp")
local instanceID = tempGameObject.GetInstanceID()

技術分享圖片

  在了解了GameObject元表後,這些只是一些基礎的元表操作,就不多做解釋。

七、lua中c#實例的真正存儲位置

  前面說了每一個c#實例在lua中是一個內容為整數索引的fulluserdata,在進行函數調用時,通過這個整數索引查找和調用這個索引代表的實例的函數和變量。生成或使用一個代表c#實例的lua變量的過程大概是這樣的。還用這個例子來說明:

local tempGameObject = UnityEngine.GameObject("temp")
local transform = tempGameObject.GetComponent("Transform")

技術分享圖片

所以說lua中調用和創建的c#實例實際都是存在c#中的objects表中,lua中的變量只是一個持有該c#實例索引位置的fulluserdata,並沒有直接對c#實例進行引用。
對c#實例進行函數的調用和變量的修改都是通過元表調用操作wrap文件中的函數進行的。以上就是c#類如何通過wrap類在lua中進行使用的原理。

如果覺得本篇博客對您有幫助,可以掃碼小小地鼓勵下馬三,馬三會寫出更多的好文章,支持微信和支付寶喲!

 技術分享圖片 技術分享圖片

作者:馬三小夥兒
出處:https://www.cnblogs.com/msxh/p/9813147.html
請尊重別人的勞動成果,讓分享成為一種美德,歡迎轉載。另外,文章在表述和代碼方面如有不妥之處,歡迎批評指正。留下你的腳印,歡迎評論!

【Unity遊戲開發】tolua之wrap文件的原理與使用