unity手游項目中,怎麼維護lua代碼,手寫還是翻譯?
現在unity手游要使用lua熱更方案,有些人會使用ToLua#,底層用C#,業務層用lua。那麼lua這塊代碼量是比較龐大的。
遊戲開發一般會用面向對象來寫,lua雖然可以使用metadata來實現面向對象,但寫起來有些不自然。另外由於是動態類型,ide也做不到很好的重構和智能提示支持,這樣手寫lua似乎是挺低效(工作效率)的。我看到過一個能將C#代碼翻譯成lua代碼的工具,cs2lua,這樣可以直接用C#來開發,工作效率會高很多。當然會有少量語法限制,但相對於帶來的好處,是可以忍受的。我想請問一下,你們的項目是怎麼做的,手寫還是翻譯?
關於面向對象,可以考慮我的庫,目前也有幾個Unity遊戲在用(但我本人不從事這塊),這個庫相對現有的各種Lua OOP來說都要大很多,完整的實現多繼承,重載,切面編程,類型驗證等
kurapica/PLoop?github.comC#轉換Lua開發的問題,多數都在於細節,一個簡單的例子,群里有人優化性能,查到一處Date使用的地方,他們的做法是每次創建一個Date對象,返回給調用處,通常日期訪問很頻繁,也導致Date對象被大量的創建,這樣的代碼對於C#來說很平常,創建一個對象,用完拋棄,自然會被回收也不影響效率。
但對於Lua這種單線程運行的語言來說,這裡就是很糟的性能點,即便Lua的GC很完善,但gc也是性能耗費點。實際這個Date對象返回後,只是讀取,沒有修改之類的處理,所以,我給他們的方案就是記錄一個Date對象,只有當秒數不一致時,創建新的替代它,之後每次調用都僅返回它,不創建新的,性能問題就解決了。
這是使用腳本語言的開發思路,如果不用Lua做原生開發,很難保證性能和以後的維護,畢竟你們無法熟悉它。
翻譯吧。
之前沒人考慮翻譯這個事情,只是在將就罷了。
說白了這個要看團隊性質,如果團隊成員一直習慣強類型語言的開發模式,比如像我這樣一開始變數命名都全瞎寫,函數隨便拆,寫完了才整理代碼的,使用弱類型語言會非常不便,因為不好重構。沒有編譯期檢查的語言必須一次性成型,通用性也必須一開始就建得很高,因為等到需求變更的時候並不方便修改。
這些換來的都是開發效率。但如果你用了強類型語言依然是一次性成型,極少重構——又或者命名結構什麼的都是隨緣的人,那是感覺不到強類型語言的優勢,也就不能理解為啥別人不願意用lua。
開發效率這個玩意兒,沒對比就沒傷害,反正如果用lua的話,那再多加1/3的人吧,再平時落筆小心一些,把代碼質量太差的人開除出團隊,差不多就能和C#編程一樣了。
而且好多東西都分人的。比如有的人,寫代碼的時間和調代碼的時間是1:3,他們就會覺得,花很多時間在複測調試上不是挺正常的么?他么就沒法理解那些代碼一次通過率&>30%的人的世界是啥樣的,對開發一個功能應該花的時間也和那些人概念不一樣。
不過嘛,那些動態腳本語言也不是一點優點都沒有。比如說,0編譯時間,實機測試時不用更包。但是就我實際感受,這兩個優點並不是特別重要。如果編譯時間要1分鐘那肯定重要,但通常限制在5秒內很正常的,這在整個測試過程中浪費的時間其實很少,只是打斷較多心理上不爽而已。實機測試其實大多是兼容多點操作類測試,主要的調試過程都是在PC上的,這部分影響也不大。而且也完全可以C#熱更,或者像你說的,轉成lua通過熱更更新,一樣能實現。這部分是熱更的優點,而不是lua的,不中斷程序直接更代碼支持熱更的也都能做到。另外這還要看環境,比如更包的問題,我們打個包&<10分鐘,那和更個包2小時的團隊,對不更包測試的需求肯定不一樣,但後者顯然應該去解決打包時間長的問題吧?
還有些什麼調試的問題……我個人覺得log調試和斷點單步沒啥特別大的區別,調試的時間主要還是在思考過程,而且大部分低級錯誤從錯誤堆棧就能看的七七八八。對了,如果你用翻譯的方法,最大的問題就是只能使用log調試了。lua本身其實還是可以做一點斷點的。但就我看,這並不是太大的問題。
現在翻譯這個方法唯一的問題是:用的人少。
因為大家都愛將就啊,也不想犯險,沒人帶頭干啊。lua雖然是不方便,但也沒不方便到哪裡去。而且就國內的情況,lua本來就是用在UI上居多,而UI呢?拼Prefab的時間才是大頭,代碼里,拷貝控制項名,拷貝數據名也佔了很大一部分,剩下的部分怎麼樣都好了。
但是如果是對OOP真正有要求的複雜邏輯部分,我倒是干過一次,是C#花幾天寫完調試好(期間當然有多次結構調整),然後花幾個小時手動轉成lua的。當時是根本沒有發布的翻譯器,而且只是一次性的話,一般人也會覺得手翻比「別人的工具」靠譜吧。
你也可以用這樣的方式,先C#寫完調試完,再轉成lua,然後看看有沒啥可修改優化的地方……現在有個問題是穿透,還有值類型等等幾個lua的坑,翻譯器確實處理不好這東西。但是呢?我覺得檢查一遍這玩意兒真的就是個一人日的活兒,這有啥關係呢……
反正,多層結構的複雜邏輯強烈建議C#寫完翻譯,直接lua開發不可能效率更高的,誰寫都不可能。但是UI邏輯什麼的,確實可隨意。我的經驗是用lua大概慢個30%,然後需求變化慢60%,大概如此。
對了,如果不做一次性翻譯,而是任何時候都用lua執行,其實還可以用HaXe。
只說翻譯這個活兒,其實HaXe的實例更多一點(不是在Unity而是其他環境),可靠性也高點。現在那幾個csToLua畢竟都是個人開發者。
一個針對Unity的接入庫:bjfumac/LuHaxe
但你要平時就用C#,然後最後轉lua就不能用這個了。我也挺想驗證那個cs2lua的可靠性的。但是針對C#的代碼直接翻譯成lua還是會有好多地方不太一樣的,而HaXe方案其實一開始就是針對lua寫的(你看了翻譯規則都能腦補出翻譯後的代碼),可以用動態類型,也容易嵌入lua代碼,只是個lua的補強,不同時考慮C#的兼容,這樣在翻譯的可靠性上更容易控制一些。
翻譯方案……其實,風險不高,因為翻譯出來的代碼依然可讀,也可以手寫,可以在必要的時候拋棄掉翻譯器,直接用其結果來繼續開發。但也不是完全沒風險,因為翻譯出來的代碼並沒有特別在意可讀性,比如HaXe的類是這樣的。
原類:
https://github.com/bjfumac/LuHaxe/blob/master/Haxe/Example/example/Example.hx
翻譯結果:
example.Example = _hx_e()
example.Example.new = function()
local self = _hx_new(example.Example.prototype)
example.Example.super(self)
return self
end
example.Example.super = function(self)
self:TestGameObject();
self:TestUGUI();
self:TestCoroutine();
self:TestSocket();
end
example.Example.__name__ = true
example.Example.prototype = _hx_a(
Update, function(self)
self:MoveCube();
self:FollowCube();
self:HandleSocket();
end,
TestGameObject, function(self)
self.go = UnityEngine.GameObject.New("go");
self.go:AddComponent(typeof(UnityEngine.ParticleSystem));
self.cube = UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Cube);
self.sphere = UnityEngine.GameObject.CreatePrimitive(UnityEngine.PrimitiveType.Sphere);
self.cube.transform.position = Vector3.New(3,0,0);
end,
TestUGUI, function(self)
local canvas = UnityEngine.GameObject.Find("Canvas");
self.txt = UnityEngine.Object.Instantiate(UnityEngine.Resources.Load("TextPreb"));
self.txt.transform.parent = canvas.transform;
self.camera = UnityEngine.GameObject.Find("Main Camera");
end,
TestCoroutine, function(self)
TimerHelper.AddUpdateListener(self,_hx_bind(self,self.Update));
coroutine.start(_hx_bind(self,self.CoFunc));
end,
TestSocket, function(self)
self.client = hxnet.tcp.Client.new();
self.client:set_protocol(example.TCPClientHandler.new());
self.client:connect("localhost",4000);
self.client:set_blocking(false);
end,
HandleSocket, function(self)
self.client:update();
local _this = self.client;
haxe.Log.trace("Network Connected = " .. Std.string((_this.client ~= nil) and (_this.protocol ~= nil)),_hx_o({__fields__={fileName=true,lineNumber=true,className=true,methodName=true},fileName="Example.hx",lineNumber=91,className="example.Example",methodName="HandleSocket"}));
end,
MoveCube, function(self)
local pos = self.cube.transform.position;
local newPos = Vector3.New(pos.x + 0.01,pos.y,pos.z);
self.cube.transform.position = newPos;
end,
FollowCube, function(self)
local camPos = self.cube.transform.position:Add(Vector3.up:Mul(5)):Sub(self.cube.transform.forward:Mul(5));
self.camera.transform.position = camPos;
self.camera.transform:LookAt(self.cube.transform);
end,
CoFunc, function(self)
local www = UnityEngine.WWW.New("http://www.baidu.com");
coroutine.www(www);
local content = DataHelper.WWWtoString(www);
haxe.Log.trace(content,_hx_o({__fields__={fileName=true,lineNumber=true,className=true,methodName=true},fileName="Example.hx",lineNumber=113,className="example.Example",methodName="CoFunc"}));
self.txt:GetComponent(typeof(UnityEngine.UI.Text)).text = "Bytes Downloaded:" .. www.bytesDownloaded;
coroutine.wait(3);
self.txt:GetComponent(typeof(UnityEngine.UI.Text)).text = "Coroutine Ended";
end
,__class__, example.Example
)
確實還湊合,但是比起lua的類模板庫還是要差一點點的。
另外HaXe當然也能輸出C#,但沒太關心過效果。
現在其實還有一個ilRuntime陣營,就是想徹底拋棄掉Lua……我是沒這麼激進了。
謝邀。
我們項目使用Lua,是手寫的,不會考慮翻譯。
任何一門編程語言,無論靜態語言還是動態語言,都有著一個習慣和上手的過程,題主這樣問,感覺是之前沒有太多使用動態語言的經驗。
Lua這樣的動態語言在IDE方面的支持的確不如C#這樣的靜態語言方便,Class的默認實現方式也的確不夠好用(其實如果你花功夫,是可以實現出一套不錯的Class結構的),但是如果說這導致工作效率低,我個人是不贊同的。我認為這種開發效率低只是還需要一個上手和熟悉的時間,再加上基礎特性的擴展和周邊工具的開發,團隊是可以做到很好的開發效率的。
而語言轉換的工具,我個人只在特定的情況下,比如非常緊急的需求,或者臨時方案中來用。這就像你面對一個問題,不是想如何去從正面解決它,而是想辦法繞過去。現代編譯器可以做到很好的優化效果,但是我個人不相信語言之間的翻譯工具可以很好地利用目標語言的特性,尤其這種靜態語言轉為動態語言的工具,除非你的代碼只是Demo/玩具形式的簡答邏輯。
如果一個大型項目在正式的開發流程中大量地採用語言轉換的工具,我對於後期的優化和代碼的維護表示堪憂。畢竟不同語言有著不同的特性和優化方法,應該逐漸去學習和理解語言背後的設計原理以及使用技巧,提升團隊整體的技術寬度和深度,而不是守著熟悉的語言,待在舒服區里「高效地」工作。
正好近期整理了兩篇在團隊中使用Lua的文章,供題主參考:
Unity手游開發札記——我們是如何使用Lua來開發大型遊戲的?(上)
Unity手游開發札記——我們是如何使用Lua來開發大型遊戲的?(下)
既然你想用C#來寫,那麼要不就用ILRuntime來做熱更,徹底杜絕這些煩惱
我們也都是手寫的。原先c#的代碼,如果要復用,也是手動從c#翻譯到lua的。
當然手寫了,我在我們的項目裡面寫了一套純lua的框架,部分代碼可以自動生成。除了語法不能檢查,寫起來還是滿方便的。
推薦閱讀: