Unity3D熱更新技術(shù)點(diǎn)——ToLua(上)

作者:朔宇
注: 本文主要介紹tolua的基本原理及其在unity中的使用,希望閱讀本文的讀者有l(wèi)ua基礎(chǔ),可通過(guò)Lua教程?(其中也有IDE的推薦等)或其他途徑先進(jìn)行l(wèi)ua 的學(xué)習(xí)?
熱更新
在介紹tolua前,我們首先來(lái)了解一下在游戲開(kāi)發(fā)中,熱更新的概念。
熱更新是一種手游及App常用的更新方式,舉例來(lái)說(shuō),游戲上線(xiàn)后,玩家需要通過(guò)應(yīng)用商店及其他渠道下載第一個(gè)版本。在運(yùn)營(yíng)的過(guò)程中,如游戲需要更換UI、修改邏輯、開(kāi)放功能等,此時(shí)若不使用熱更新技術(shù),就需要重新打包,那么玩家也就需要通過(guò)應(yīng)用商店或其他渠道重新下載游戲。 熱更新可以在不重新下載客戶(hù)端的情況下,更新游戲的內(nèi)容。

然而c#是一門(mén)編譯型語(yǔ)言,其運(yùn)行之前需要進(jìn)行編譯,而編譯的過(guò)程在移動(dòng)平臺(tái)無(wú)法完成,所以當(dāng)我們游戲的邏輯更改,代碼發(fā)生變化時(shí),我們就需要重新在開(kāi)發(fā)環(huán)境下編譯,然后重新打包,讓玩家下載最新版本。這個(gè)過(guò)程中,會(huì)下載很多不需要更新的資源,便會(huì)增加玩家的時(shí)間及流量消耗,造成不好的用戶(hù)體驗(yàn)。因此在移動(dòng)平臺(tái)中便就出現(xiàn)了熱更新技術(shù)。
在unity中,主要的熱更新方式有如下三種:
1.使用Lua編寫(xiě)游戲邏輯;
Lua是一個(gè)小巧的腳本語(yǔ)言,由標(biāo)準(zhǔn)C編寫(xiě)而成,幾乎在所有操作系統(tǒng)和平臺(tái)上都可以編譯,運(yùn)行。
使用lua熱更新就是在Unity環(huán)境里內(nèi)嵌一個(gè)lua虛擬機(jī),經(jīng)常變動(dòng)的和對(duì)執(zhí)行效率沒(méi)要求的邏輯用Lua實(shí)現(xiàn),游戲啟動(dòng)時(shí)加載服務(wù)器上最新的lua字節(jié)碼來(lái)執(zhí)行游戲。lua代碼都是運(yùn)行時(shí)才編譯的,不運(yùn)行的時(shí)候就如同一張圖片、一段音頻一樣,都是文件資源;所以更新邏輯只需要更新腳本,不需要再編譯,因而lua能輕松實(shí)現(xiàn)“熱更新”。
其實(shí)諸如python,javascript等腳本語(yǔ)言的話(huà),都是可以實(shí)現(xiàn)這個(gè)功能的。只不是目前幾個(gè)開(kāi)源的、成熟的熱更新方案都是基于lua的。?
2.C#Light
C#Light是一個(gè)簡(jiǎn)單的嵌入式腳本,模仿c#的語(yǔ)法風(fēng)格,完全由pure c#寫(xiě)成?
3.C#反射技術(shù)
可以將部分邏輯提取至一個(gè)單獨(dú)的代碼庫(kù)工程中,打包為DLL,將DLL打包為AssetBundle,Unity程序動(dòng)態(tài)加載AssetBundle中的DLL文件,使用反射機(jī)制來(lái)調(diào)用代碼。用C#反射加載程序集的方式可以動(dòng)態(tài)的從assetBundle資源包或其他資源包里加載腳本到工程中。但因?yàn)樘O(píng)果官方禁止iOS下的程序熱更新,JIT在iOS下無(wú)效,所以這種方式無(wú)法在ios使用?
本文中主要來(lái)了解第一種方式,在unity的lua熱更新中,有ulua、slua、xlua、tolua等多種熱更新方案,這些方案提供了C#與Lua的互相調(diào)用機(jī)制。在本文我們以現(xiàn)今市面最常見(jiàn)的tolua為例,通過(guò)一個(gè)小項(xiàng)目給讀者介紹tolua在unity中的使用方法。
注:本文中僅介紹tolua的用法,不會(huì)詳細(xì)介紹tolua熱更新的方法,如需要了解tolua熱更新可以通過(guò)toluaLuaFramework框架作者的博客:http://www.ulua.org/index.html
及LuaFramework基礎(chǔ):http://gyunch.org/2017/09/13/LuaFramework/來(lái)了解、學(xué)習(xí)。?
ToLua使用
Tolua是Unity靜態(tài)綁定lua的一個(gè)解決方案,它通過(guò)C#中集成lua的插件,可以自動(dòng)生成用于在lua中訪(fǎng)問(wèn)Unity的綁定代碼,并把C#中的常量、變量、函數(shù)、屬性、類(lèi)以及枚舉暴露給lua。其從cstolua衍變而來(lái)。
既然要了解Tolua,第一步肯定是先從Tolua作者的GitHub下載Tolua資源:https://github.com/topameng/tolua?
同時(shí)我們可以通過(guò)其GitHub來(lái)了解Tolua的主要特性,也可以加tolua技術(shù)交流群進(jìn)行討論及學(xué)習(xí)(在GitHub中有群號(hào))。
下載完成后可以看到tolua文件夾中的目錄結(jié)構(gòu),如下圖:

我們只需要將「Assets」、「Unity5.x」、「Luajit64」、「Luajit」四個(gè)文件夾復(fù)制到我們的工程文件夾中。 加載完成后,Unity中會(huì)出現(xiàn)如下提示框,我們點(diǎn)擊確定

然后在unity中就可以看到Tolua的主要文件列表

ToLua案例
在了解了基本的熱更新概念,及ToLua資源的加載后,我們通過(guò)一個(gè)小的案例,來(lái)初步的掌握ToLua在Unity中的使用方法。 在本文中,我們使用ToLua來(lái)制作一個(gè)可以用按鍵控制滾動(dòng)的小球,如下圖所示:

學(xué)習(xí)或使用過(guò)Unity的讀者應(yīng)該能夠非常輕松的使用C#寫(xiě)出這個(gè)小游戲,那么我們現(xiàn)在來(lái)看使用ToLua是如何達(dá)到上圖效果的。
我們直接來(lái)看代碼,下面會(huì)對(duì)代碼進(jìn)行逐行解釋?zhuān)?/strong>
Control.lu
Control = {}
local this = Control
require('Music')
local GameObject = UnityEngine.GameObject
local Input = UnityEngine.Input
local AudioSource = UnityEngine.AudioSource
local Rigidbody = UnityEngine.Rigidbody
local Color = UnityEngine.Color
local Sphere
local rigi
local force
?
function this.Start()
??? Sphere = GameObject.Find('Sphere')
??? Sphere : GetComponent('Renderer').material.color = Color(1, 0.1, 1)
??? Sphere : AddComponent(typeof(AudioSource))
??? coroutine.start(Music.PlaySound)
??? Sphere : AddComponent(typeof(Rigidbody))
??? rigi = Sphere : GetComponent('Rigidbody')
??? force = 5
end
?
function this.Update()
??? local h = Input.GetAxis('Horizontal')
??? local v = Input.GetAxis('Vertical')
??? rigi : AddForce(Vector3(h, 0, v) * force)
end
?
Music.lua
--協(xié)程下載
--這里使用Tolua中提供的coroutine.www
Music = {}
local this = Music
function this.PlaySound()
??? local audio = UnityEngine.GameObject.Find('Sphere') : GetComponent('AudioSource')
??? local url = UnityEngine.WWW('https://etnly.oss-cn-shanghai.aliyuncs.com/%E5%B2%A1%E9%83%A8%E5%95%93%E4%B8%80%20-%20%E9%81%BA%E3%82%B5%E3%83%AC%E3%82%BF%E5%A0%B4%E6%89%80%EF%BC%8F%E6%96%9C%E5%85%89.ogg')
??? coroutine.www(url)
??? audio.clip = url : GetAudioClip()
??? audio : Play()
end
?
首先,我們?cè)趐roject面板中創(chuàng)建Script文件夾,下一層創(chuàng)建Lua文件夾,這個(gè)文件夾會(huì)存放我們所有的lua腳本。
然后我們來(lái)詳細(xì)解釋上方的代碼:
Control = {} : 在lua中沒(méi)有類(lèi)的概念,第一行中我們用lua表模擬一個(gè)類(lèi),類(lèi)名為Control;
local this = Control : 既然在lua中不存在類(lèi)的概念,那也不會(huì)存在this的用法,這里同樣的,我們模擬一個(gè)this,讓this = Control類(lèi);?
require('Music’) : lua通過(guò)require函數(shù)來(lái)加載模塊(希望讀者可以自行了解lua中模塊的含義)在本行中Muisc模塊是一個(gè)下載并播放音樂(lè)的協(xié)程模塊,具體代碼在Muisc.lua中;
local GameObject = UnityEngine.GameObject : 這里及其以下四行都是對(duì)Unity中的類(lèi)、方法進(jìn)行加載或者說(shuō)調(diào)用,我們可以直接在代碼中使用UnityEngine.GameObject,或把UnityEngine.GameObject賦給一個(gè)變量(如第一行中,lua使用表來(lái)模擬類(lèi),那這里的GameObject也就是一個(gè)table類(lèi)型);?
local Sphere : 這里及以下兩行,我們定義了幾個(gè)變量;?
function this.Start() end :這是lua中的函數(shù),this的含義在之前有提到,我們也可以寫(xiě)成Control.Start(), 我們?cè)趕tart函數(shù)中進(jìn)行一些必要的初始化操作,可以把它看成Unity中的start(我們也可以給它起別的名字,這個(gè)函數(shù)的名字和之后我們?cè)赨nity中的調(diào)用沒(méi)有必然聯(lián)系),不要忘了加后面的end,這句話(huà)代表此方法的結(jié)尾;?
Sphere = GameObject.Find('Sphere') : 我們通過(guò)GameObject.Find來(lái)找到Unity中的小球(需要注意,lua腳本無(wú)法綁定在Unity中的物體上,所以也無(wú)法直接在Unity的面板中綁定Unity中的對(duì)象,我們只有通過(guò)查找的方式來(lái)找到Unity中的物體);
Sphere : GetComponent('Renderer').material.color = Color(1, 0.1, 1) :這一行中,我們使用GetComponent通過(guò)獲取Renderer組件,改變小球的顏色 ;?
Sphere : AddComponent(typeof(AudioSource)) : 使用AddComponent,給小球添加AudioSource組件;?
coroutine.start(Music.PlaySound) : 開(kāi)啟協(xié)程, 我們可以直接看到 Music.lua這個(gè)腳本,在腳本中,我們使用ToLua封裝的coroutine.www()方法,來(lái)下載音樂(lè),并通過(guò)之后的代碼來(lái)播放音樂(lè);?
Sphere : AddComponent(typeof(Rigidbody)) : 給小球添加剛體 ;
rigi = Sphere : GetComponent('Rigidbody') : 獲取小球的剛體,將其賦給rigi ;
force = 5 : 這里作為力的大??;
function this.Update() end : 與之前start函數(shù)類(lèi)似,我們可以將其看作是Unity中的Update(再次聲明,此處Lua函數(shù)名和Unity中的調(diào)用沒(méi)有必然聯(lián)系);
local h = Input.GetAxis('Horizontal') : 這一行及下一行,就是獲取按下相應(yīng)按鍵,相應(yīng)軸上的位移量 ;
rigi : AddForce(Vector3(h, 0, v) * force) : 通過(guò)剛體的AddForce方法,給小球施加力;
這就是我們整個(gè)例子的Tolua腳本使用說(shuō)明,那么我們?cè)撊绾卧赨nity中來(lái)調(diào)用這些腳本呢。 下面我們來(lái)了解在C#中調(diào)用ToLua的方法,代碼如下所示:
using UnityEngine;
using LuaInterface;
public class Control : MonoBehaviour {
??? LuaState lua = null;
??? LuaFunction luaFunc = null;
??? void Start () {
??????? new LuaResLoader();
??????? lua = new LuaState();
??????? lua.Start();
??????? LuaBinder.Bind(lua);
??????? string luaPath = Application.dataPath + "/Scripts/Lua";//注意這里的文件位置
??????? lua.AddSearchPath(luaPath);
??????? lua.DoFile("Control.lua");
??????? CallFunc("Control.Start", gameObject);//調(diào)用lua中的this.Start函數(shù)
??? }
??? void Update () {
??? ????CallFunc("Control.Update", gameObject);////調(diào)用lua中的this.Update函數(shù)
??? }
??? private void OnApplicationQuit()
??? {
??????? lua.Dispose();
??????? lua = null;
??? }
??? void CallFunc(string func, GameObject obj){
??????? luaFunc = lua.GetFunction(func);
??????? luaFunc.Call(obj);
??????? luaFunc.Dispose();
??????? luaFunc = null;
??? }
}
?
我們可以通過(guò)這段代碼來(lái)了解一些C#中調(diào)用Tolua的基本操作:
1.new LuaState()?: 初始化Lua虛擬機(jī)
2.LuaState.Start()?:開(kāi)啟Lua虛擬機(jī)
3.LuaState.AddSearchPath(fullPath):添加目錄地址
4.LuaBinder.Bin(LuaState):向Lua虛擬機(jī)注冊(cè)Wrap類(lèi)
5.new LuaResLoader()?:自定義加載器加載lua文件
6.LuaState.DoFile()、LuaState.Require():加載Lua文件/模塊
7.LuaState.GetFunction:獲取Lua方法
8.LuaState.GetFunction.Call():調(diào)用Lua函數(shù)
9.Luafunction.Dispose(),LuaState.Dispose():釋放內(nèi)存?
到此為止,我們已經(jīng)了解了如何在Unity中使用ToLua來(lái)進(jìn)行邏輯編寫(xiě),讀者可以根據(jù)以上的例子來(lái)擴(kuò)展,實(shí)現(xiàn)更多的功能,或在自己的項(xiàng)目中加入lua代碼來(lái)深入學(xué)習(xí)lua。
在下一篇文章中,我們會(huì)通過(guò)一個(gè)相對(duì)復(fù)雜一些的例子,給讀者帶來(lái)更多Tolua在Unity中的使用方法、技巧及原理。
想系統(tǒng)學(xué)習(xí)游戲開(kāi)發(fā)的童鞋,歡迎訪(fǎng)問(wèn)?http://levelpp.com/? ? ? ? ? ?
游戲開(kāi)發(fā)攪基QQ群:869551769? ? ? ? ? ??
微信公眾號(hào):皮皮關(guān)