[Unity3D]Unity3D遊戲開發之Lua與遊戲的不解之緣終結篇:UniLua熱更新全然解讀

weixin_34292959發表於2017-08-04

        

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜歡我的部落格請記住我的名字:秦元培,我的部落格地址是blog.csdn.net/qinyuanpei

轉載請註明出處,本文作者:秦元培, 本文出處:http://blog.csdn.net/qinyuanpei/article/details/40213439

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  

       大家好,我是秦元培。歡迎大家關注我的部落格,我的部落格地址是blog.csdn.net/qinyuanpei。在之前的三篇系列文章《Unity3D遊戲開發之Lua與遊戲的不解之緣》中,博主帶領大家一起領略了Lua在遊戲開發中強大而迷人的作用,通過UniLua這個開源專案我們將Lua引入了Unity3D的世界。並且在此基礎上我們寫出了Lua'與Unity3D互動的第一個演示樣例程式。今天呢。我們來說說Unity3D配合AssetBundle和;Lua實現熱更新。

        首先,我們來了解一下什麼是熱更新吧!所謂熱更新是指在不停機的狀態下對系統進行更改,比如我們的Windows能夠在不重新啟動的狀態下完畢補丁的更新、Webserver在 不重新啟動的前提下完畢對資料和檔案的替換等都是熱更新的經典例項。那麼對於Unity3D而言,什麼是熱更新呢?假設我們終於公佈的Unity3D遊戲是一個Web遊戲。那麼每次遊戲載入的過程中實現對資原始碼的更新就是熱更新。

假設我們終於公佈的Unity3D遊戲是一個client遊戲,那麼我們在重重新啟動client以後實現對資原始碼的更新就是熱更新。為什麼這麼說呢?由於Web遊戲須要保證玩家能夠及時高速地進入遊戲。因此在遊戲載入完之前,我們必須完畢對遊戲資源和程式碼的更新。但是對於client遊戲而言,玩家能夠在等待本次更新結束後再進入遊戲。並且大部分的client程式在更新完後都會要求玩家重新啟動client,所以對於client遊戲而言。熱更新並非是嚴格意義上的熱更新。

那麼。我們為什麼要進行熱更新呢?答案是為了縮短使用者獲取新版本號client的流程、改進使用者體驗。這事實上就是博主在前文中提到的傳統單機遊戲依靠光碟載體進行發售所面臨的問題。玩家為了獲取最新版本號的遊戲,須要下載全新的client並將它安裝到計算機或者手機裝置上。在網際網路產品開發中有一種稱為高速迭代的理念,試想假設我們每次對client進行區域性的調整,就須要玩家去下載新版本號的client。試問這種使用者體驗真得能讓使用者愜意嗎?所以如今為了方便使用者、留住使用者、進而從留住的使用者身上賺到錢,我們總能在遊戲產品中找到熱更新的影子。我們知道在Unity3D中能夠通過AssetBundle來實現對遊戲中資源的更新,在http://blog.csdn.net/janeky/article/details/17666409這篇文章中作者janeky已經給出了較為完美地解決方式,由於博主使用的Unity3D免費版無法使用AssetBundle的功能。而博主本人不願意使用破解版,由於這是一個程式猿的良心。所以本文很多其它的是從程式碼更新的這個角度來講Unity3D的熱更新。對於資源的熱更新大家建議大家還是去看janeky的這篇文章吧。好了,以下正式開始Unity3D程式碼級的熱更新之旅!

       在Unity官方的API中官方給出了一種基於反射的思路。即將C#指令碼儲存為文字檔案。然後將其轉化為byte位元組,再通過反射技術取得該型別及其方法。理論上這樣當然沒有問題,但是我們知道由於IOS是一個封閉的系統,設計者出於安全的考慮不同意在該平臺下使用反射技術。那麼問題來了,反射並非一個完美地解決方式。

關於反射技術實現Unity3D的熱更新。大家能夠參考這篇文章:http://blog.csdn.net/janeky/article/details/25923151

好了。以下我們來說說博主是怎樣通過Lua實現Unity3D的熱更新的吧。

我們知道在Lua提供的C#介面中有一個DoString()的方法。該方法能夠直接利用Lua虛擬機器執行字串中的指令碼。

所以,我們能夠通過在本地讀取Lua指令碼來執行指令碼中的命令,假設我們指令碼中的命令能夠直接對Unity3D進行操作,那麼我們就能夠通過Lua指令碼來更新遊戲中的程式碼邏輯。那麼,我們怎麼能讓Lua指令碼操作Unity3D呢?在前一篇文章中,我們介紹了一種Require的方法,該方法能夠將C#庫引入到Lua指令碼中並通過Lua來執行C#庫中的方法。

順著這種思路。博主便有了以下的設想:


在這個設想中,我們首先須要將Unity API封裝成一個C#類庫,在這個類庫中我們將會涉及動態載入場景和動態建立場景。由於我們更新遊戲的邏輯的時候將會用到這些方法。這些方法通過封裝後我們便能夠在Lua指令碼通過Require方式來引用,進而我們就能夠通過Lua指令碼來動態地進行設計。我們設計一個固定的位置來儲存Lua指令碼更新檔案。這樣我們僅僅須要對照本地版本號和server版本號是否同樣就能夠知道我們是否須要更新。這裡我們通過WWW來從遠端server上下載最新的Lua指令碼更新檔案。下載下來的Lua指令碼處於專案外部,我們無法使用Resource.Load()這個方案來載入,但是我們能夠通過WWW來載入一個本地檔案。這樣我們就實現了Lua指令碼檔案的更新。

當然,我們能夠使用AssetBundle來更新Lua指令碼檔案,但是博主的免費版不支援AssetBundle,所以博主想出了這樣一個曲線救國的方法。當Lua指令碼檔案更新後。我們就能夠在遊戲主邏輯裡通過DoString()方法來執行指令碼檔案裡的程式碼。在遊戲主邏輯裡基本的任務是比較當前版本號號和server版本號號來推斷是否須要更新,假設須要更新就下載Lua指令碼更新檔案然後執行指令碼中的程式碼。這樣我們就實現了client程式的更新。好了,以下我們繼續曾經一篇文章中的專案為例來將博主的這個設想變成現實。

        首先,我們在CSharpLib.cs這個類中新增以下兩個方法並完畢方法的註冊:

	/// <summary>
	/// 設定場景中物體的座標
	/// </summary>
	/// <returns>返回當前座標</returns>
	/// <param name="lua">Lua.</param>
	public static int SetPosition(ILuaState lua)
	{
		//物體的名稱
		string mName=lua.L_CheckString(1);
		//傳入引數x,y,z
		float mX=(float)lua.L_CheckNumber(2);
		float mY=(float)lua.L_CheckNumber(3);
		float mZ=(float)lua.L_CheckNumber(4);
		//獲取物體
		GameObject go=GameObject.Find(mName);
		//獲取Transform
		Transform mTrans=go.transform;
		//設定遊戲體的位置
		mTrans.position=new Vector3(mX,mY,mZ);
		//返回遊戲體當前座標
		lua.PushNumber(mTrans.position.x);
		lua.PushNumber(mTrans.position.y);
		lua.PushNumber(mTrans.position.z);
		return 3;
	}

	/// <summary>
	/// 使用本地預設建立一個物體
	/// </summary>
	/// <returns>The resource.</returns>
	/// <param name="lua">Lua.</param>
	public static int CreateResource(ILuaState lua)
	{
		//傳入資源名稱
		string mName=lua.L_CheckString(1);
		//載入本地資源
		GameObject go=(GameObject)Resources.Load(mName);
		//傳入座標引數x,y,z
		float mX=(float)lua.L_CheckNumber(2);
		float mY=(float)lua.L_CheckNumber(3);
		float mZ=(float)lua.L_CheckNumber(4);
		//建立一個新物體
		Object.Instantiate(go,new Vector3(mX,mY,mZ),Quaternion.identity);
	    //返回該物體的名稱
		lua.PushString(go.name);
		return 1;
	}
好了,這樣我們就完畢了一個簡單的C#類庫,以下我們來在主邏輯程式碼中新增一個更新指令碼的方法UpdateScripts():

void UpdateScript()
	{
		StartCoroutine("Download");
	}

	/// <summary>
	/// 下載Lua指令碼更新檔案
	/// </summary>
	IEnumerator Download()
	{
		//從本地載入Lua指令碼更新檔案,假設檔案已經從server下載下來
		WWW _WWW=new WWW(mUpdateFilesPath);
		yield return _WWW;
		//讀取server版本號
		mLua.L_DoString(_WWW.text);
	}

這裡的程式碼邏輯非常easy。就是讀取指令碼更新本地檔案然後執行指令碼。當中mUpdateFilePath是指令碼更新檔案路徑:

		//初始化路徑
		mUpdateFilesPath="file://D:\\lua_update.txt";

這裡博主設想的是在本地儲存一個版本號號,每次更新前先獲取server端的版本號號,假設兩個版本號號不同則須要從server上下載更新指令碼檔案進行更新。只是博主這裡沒有想到什麼好方法來獲取版本號號。所以這裡就僅僅寫了更新。那麼,我們來看看更新指令碼檔案都做了哪些事情吧!

local csharplib=require"CSharpLib.cs"

csharplib.SetPosition("Cube",2,1,0)
csharplib.CreateResource("Sphere",0,0,0)
csharplib.CreateResource("Cube",1,1,0)
首先我們通過Require引入了CSharpLib.cs 這個類庫。接下來,我們將場景中名稱為Cube的物體的位置設為(2,1,0)、 利用本地的兩個Prefab資源建立了一個Cube和一個Sphere。

那麼,我們的設想能不能實現呢?我們一起來看終於效果吧!

執行Lua指令碼更新前:


執行Lua指令碼更新後:



        如我們所願。Lua指令碼成功地對場景實現了一次更新。可能有的朋友會問,這裡用的是本地資源,假設我想用server上的資源怎麼辦呢?答案是博主最不願意提及的AssetBundle,即利用AssetBundle載入遠端資源,然後用Lua實現更新。這些邏輯能夠加入到CSharpLib這個類庫中。

大家能夠設想一下,假設有一天我們能夠將Unity的全部方法都封裝起來,那麼我們就能夠直接用Lua來建立場景了。假設要更新client,僅僅要更換Lua檔案就能夠了。怎麼樣是不是非常easy呢?但是Unity不開源啊,這些想法終究僅僅是想法啦。

好了,今天的內容就是這樣了,歡迎大家關注我的部落格,謝謝大家!


每日箴言:成熟,不是你繃起臉。顯得多麼老道。不是你知道多少大是大非。懂得多少大道理,而是你能理解身邊發生的小事都可能有它的不得已。





-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜歡我的部落格請記住我的名字:秦元培,我的部落格地址是blog.csdn.net/qinyuanpei

轉載請註明出處。本文作者:秦元培, 本文出處:http://blog.csdn.net/qinyuanpei/article/details/40213439

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------




相關文章