[UE4]關於C++、Blueprint與Script的一些雜談
我們知道,除了使用Blueprint之外,還可以選擇使用C++來進行開發。然而,UE4中的C++有著許多跟標準C++不太一樣的規則。這是由於引擎為了減少使用C++編程時的門檻,因此在底層實作了一套Reflection(UProperty)跟Garbage Collection(GC)的機制。只要照著UE4的編程規則進行C++類別的設計,不僅可以減少各種資源管理上的議題,還能夠自動享受到由引擎方所提供的許多便利功能。 或許有人會有疑問:C#或JAVA也有Reflection跟GC,為什麼UE4還要自己用C++實作一套?直接跟Unity一樣用C#當成Script來撰寫遊戲邏輯部份不就好了嗎? 其實UE4++跟.NET C#在機制上有著根本上的不同。前者是在執行編譯之前,會先使用Unreal Header Tool(UHT)產生相關需要的資訊,然後用著同一套C++ Compiler將這些產生出來的Code編譯成可以執行的Native Code;而C#則是先編譯成Bytecode,在.NET中稱為Common Intermediate Language(CIL),然後再實際安裝到目標機器或第一次呼叫相關方法時,呼叫Just-In-Time(JIT) Compiler將Bytecode編譯成Native Code執行。 UE4的方法可以想見,其執行速度會比較快,但由於最後的執行檔內包含了所有的Reflection資訊,因此檔案會變大;而使用Bytecode的方法,我們所損失的就只有第一次執行程式時的速度,以及一些可以使用在C++中的優化技巧。 其實在UE4釋出之前的幾代引擎,是有自己的一套UnrealScript來當成遊戲邏輯的編程使用,其第一代遊戲引擎更是在1998年就已經釋出。因此我們的問題應該要換成:為什麼要將UnrealScript從四代的移除?雖然使用Script會需要以執行期的效能為代價,但先編成Bytecode的方式,不僅可以避免因為C++語言的複雜性而造成新手進入的障礙,而且還能讓開發者減少許多等待C++編譯的時間。若真的非常需要效能應用,還是有技術能將這些Script轉譯成C++後再編譯。 相較之下,似乎沒有特別的理由堅持使用C++來進行開發,不是嗎? 是的,這也是為何官方將三代中的Kismet這套視覺編程系統進行強化,重新命名為Blueprint後,強勢回歸到四代引擎的原因。對於完全不懂程式的使用者,其實可以完全使用這套系統建構出一個遊戲。 這套強化過後的視覺化Script系統就是官方所給出的解答。 只是完全使用Blueprint來寫程式還是有不少的限制,尤其視覺編程系統實在是不適合用來撰寫複雜的邏輯。而且在多人協作上,由於寫出的檔案為Binary的格式,目前也沒辦法使用git進行merge。 基於以上限制,我們還是需要一個完整的純文字格式的Script語言給進階使用者不是嗎?不管是直譯式語言或是編譯式語言,將C++的複雜性從開發流程中隔離出來,對開發不是會比較有效率嗎? 事實上,在引擎底層是有開放接口,讓使用者自己綁定其他想使用的Script語言。而且目前在markplace上也可以找到Unreal.js這套免費的Plugin將V8 JavaScript Engine跟引擎進行綁定。其他如python、lua、C#……等等,也都有非官方的實現。 但問題是,為什麼官方堅持不在引擎層提供一套正式的純文字格式Script語言支持?根據官方於論壇上的回答,主要是因為以下幾點原因: 在引擎過去幾十年來的演進中,隨著使用人數的增加,有越來越多的使用者要求將C++中的某個特性開放給Script使用。而不斷開放這些進階功能的後果,這層為了減少複雜性而封裝起來的Sandbox環境看起來就跟C++中的宣告沒二樣。這時候再透過一層Script介面層的封裝,反而增加了整體理解的複雜度。 隨著Script介面層的擴張,其在跟C++層溝通的成本跟複雜度會變的非常大。又尤其要將一些比較複雜的資料型態互相傳輸時,例如Container,在Script跟C++的語意跟語法都不太相同的情況下,肯定會造成不少的維護成本。 當開發者在尋求一些更進階的功能時,勢必要將程式的邏輯分割成「Script部份」跟「C++部份」,而開發時間就在雙方的呼叫邏輯撰寫地獄中損失掉了。 當開發者需要進行斷點做程式的追蹤時,馬上就會發現script的debug工具跟C++使用的debug工具是完全的二回事。若你沒辦法直接從script層追到C++層的話,那麼在做除錯時就會變的非常的困難。(反之亦然) 從上面所列出的原因中我們可以發現,不支援Script有很大的原因,都是由於C++跟Script之間的互相操作性(Interoperability)在引擎演化到最後完全失控了。回歸純粹的C++架構,不僅可以解決引擎維護與除錯上的痛點,而且附加好處,則是效能上的增進與簡化跟第三方C++ Library的整合。 這個決策或許有些人並不認同,但至少我們可以看出官方目前對內建其他script上的態度。 本系列文章為個人原創,未經授權,謝絕轉載
[置頂] Unreal Engine 4哲學與實務:從Blueprint到C++
前言…. (完成) 想寫這本書的想法其實已經在心中很久了,但由於各種現實的因素一直沒辦法付諸實行。一方面是有點懶,另一方面是覺得自己可能沒辦法整理的很好;尤其是官方已經有那麼多免費的教學課程了,我真的需要把時間花在這件事上嗎?然而在自己越來越熟悉引擎,看了越來越多相關的教學文章之後,便開始覺得相關C++的教材實在是少的可憐。尤其是深入一點的觀念議題,通常也只能從國外論壇討論中取得零碎的答案;有些進階一點的功能還沒有官方文檔,常常你能做的就是從引擎的原始碼著手,慢慢的往下去推敲出設計者的想法。 我自己本身使用過不少其他遊戲引擎,在一開始轉換到UE4的時候也是一直想要把之前使用的開發流程套用進來。只是,這些心血後來發現也只是在繞遠路。像一開始我完全不信任UE4的GC機制,因此能不用UObject的時候就不使用,想盡辦法的導入C++標準庫中的smart pointer機制;覺得他的log機制不好用,還試著把boost.log給整合了進來。作為一套歷經淬煉的遊戲引擎,UE4自然有他自己的一套設計理論。我個人從原本的完全排斥Blueprint,到現在讚嘆於其精美設計的學習過程,也是經過了不少的掙扎與實驗。 本書並不試圖去教導怎麼建構一個完整的遊戲,相反的,我會從架構面著手,希望能夠從整體工作流開始去引導大家理解引擎的用法與設計。是否在看完官方的教學引擎後常常不知道該怎麼套用到自己的專案身上呢?希望在閱讀內文的同時,能夠帶給這些使用者一些突破困境的指引。這本書算是我使用引擎到現在的一些經驗總結,希望能幫助到有志於投入UE4遊戲程式開發的夥伴們減少其研究的時間。 推薦對象: 有C++程式基礎,對UE4程式開發有興趣的人 除了Blueprint,想進一步的使用C++來實現進階功能的UE4開發者 暫定目錄(可能隨時更改) 前言 (完成) 第1章 開發環境與UE4專案架構 Hello Unreal! 過去與現在(完成 ) 事前準備 (完成) Epic Games Launcher簡介 (完成) 認識UE4專案目錄架構與Game Module (完成 ) 認識Editor環境與UE4檔案命名系統 (完成 ) 如何使用git及git lfs來進行版本控制 (完成 ) 如何切換引擎版本 (完成) 關於C++、Blueprint與Script的一些雜談 (完成 ) 關於UE4++、GC與標準庫的一些雜談 (完成 ) 為UE4++加入程式碼:初探UObject (完成 ) … Continued
軟體專案管理導入心得
最近陸陸續續的花了一些時間導入專案軟體開發工具跟流程進到現在的公司中,我想就藉由這次的機會來描述我想像中一個軟體專案應該要有的體制。當然,這些都是基於我過去在其他公司參與遊戲開發專案所得出的結論,或許很多並不是最佳解,但我想裡面應該有一些可以參考的觀點。 基本上,我所認為一個好的軟體專案開發體制需要能解決以下這三個主要的問題: 一、工作要能夠連續,不要輕易的被打斷:尤其是當一個人才剛進入工作的狀態卻被其他的臨時事項或溝通所打斷時,要再重新進入原本的任務內容所耗費的時間與心神勞力在無形之中都是個浪費。 二、任務內容要能夠有效傳達:由於軟體開發在本質上複雜與不可見的特性,很多時候口頭上所討論的內容每個人所理解的都不盡相同。又尤其開發過程中肯定會有不少任務內容展開,而開發人員肯定是一件一件的對於這些任務進行消化。而在這期間,軟體需求的各種修改與追加其實會是一種常態,特別是對於新創公司而言,要如何讓這些任務內容有效的傳達到所有相關的參與者身上,而不是藉由開會來佔用人員寶貴的開發時間,則是一件軟體專案管理上必須要思考的議題。 三、軟體設計過程要能夠追蹤:軟體開發不同於其他產業,它很難用任何量化的指標去評估。公司的價值基本上就是圍繞在裡面成員的技能與組成,因此,要如何將開發經驗傳承下去則是每個軟體公司都會面臨到的挑戰。這點在程式人員中通常都是使用版本控制軟體(如Git),但只有這個是遠遠不夠的。程式碼無法體現想要達成的使用者經驗以及其他各種功能面上的決策與情境,因此如何為這些東西提供一個互相參照的機制,則會直接影響到公司的體質。體質好的公司,這些開發經驗是要能有系統的累積下來的。人員若能夠藉由這個機制而能簡單且快速的存取到跟手邊任務相關的資訊,則能夠大大的提升軟體開發與維護的效率。 其實,上面這些問題在本質上都是怎麼「溝通」。要怎麼讓這件事更有效率的被執行,其實在軟體界中有一門叫「軟體工程」的理論專門在探討相關專案管理的開發議題,從各種從上千人參與的軟體開發專案到十人以下的小團隊都有提出一些方法論來解決各種軟體開發上所會面臨到的情境。其中一套被稱作agile的方法論普遍被世界上各個開發團隊所採用,其核心思想便是藉由每天不斷的溝通、頻繁的交付版本來快速的適應需求的變化。 然而,溝通是有成本的,因為溝通需求而被中斷的開發時間意味著時間的浪費。因此在agile中的 「scrum」框架下才會有每天的站立會議的出現。藉由每天在進入工作之前跟成員間互相分享彼此在任務上的狀況與需要協助的地方,以解決當面互相溝通的需求。 當然,就如同人月神話一書中所提到:「沒有銀彈」──軟體開發並沒有一套完美的方法論可以完美的套用。各個軟體專案管理必須要根據人員的組成與專案的特質進行不同的管理流程,一切都要以如何進行有效與簡潔的溝通為基礎來進行發展。 基於上面的需求,市面上其實也已經有幾套專案軟體系統可以用來協助我們減輕成員間溝通的負擔。 其中最有名的是jira( https://www.atlassian.com/software/jira ),它是目前公認做的最好的專案軟體管理工具,只是如果要完整的使用該公司的方案的話,在價格上會有些昂貴。 再來就是redmine( http://www.redmine.org/ ),由於這套解決方案是免費的,因此在市場上佔有不小的份量。只不過我個人用過的感覺是缺乏了不少關鍵性的整合能力,而且server必須要自己架設,適合有專門資訊人員在管理的團隊。 接下來便是微軟的vsts( https://www.visualstudio.com/team-services/ ),由於其完整度還蠻高的,各種整合跟服務都很不錯,而且5人以下免費,git跟git lfs的空間無上限,因此作為startup使用的專案軟體管理工具是蠻適合的。 另外也有不少小團隊採用Trello( https://trello.com/ ),只是我覺得這套系統作為個人使用的任務管理工具雖然還蠻完美的,但當團隊成員擴張到5個人以上的時候就會開始顯得有些零亂,到10個人以上時基本上就會呈現一種無法管理的狀態。 當然,除了使用專案管理系統之外,還是會有即時溝通的需求。基本上,市面上的專案管理系統會在每天匯整當天的任務資訊並寄到你的email,但若我們在專案管理上的任何更動或任務的指派都能夠即時通知到相關人員的話,那麼團隊就能夠更敏捷的去處理各種突發狀況。 通常大部份的人想到的都會是建立一個line或Skype群組並手動的通知對方相關的任務變更,但「手動」這件事其實是一件勞心勞力的事,若能夠自動化的話該有多好?而且建立line或skype群組意味著公司需要再去尋問員工私下個人的帳號資訊,這對於一些重要資訊的控管與流程的制度化其實是不好的。 這就是為何市面上會有slack、hipchat或microsoft teams……這些群組聊天軟體出現,它們不僅僅跟專案軟體系統有一些自動化機制上的整合,更重要的是如果這們軟體是直接跟公司的email帳號綁在一起,就能夠讓員工們做到公私分離,在公事上分享相關機密資訊時就不會有太多的疑慮。 最後,根據agile的精神,我們必須要頻繁的交付版本以達到快速適合需求變更。但交付版本這件事其實是一件勞心勞力的事,若是每幾天都要繳交一個版本的話,那麼就會必須要配置一個工程師整天都在做這件事。這其實也是一種浪費,若是這件事也能夠自動化的話該有多好? 其實所謂的持續集成(Continuous Integration)就是在處理這件事。基本上要做的事情就是一套自動化建置與佈署的流程,撰寫自動化的測試程式以確保基本的軟體質量,並在每天晚上自動執行這些自動化出版的腳本。其中這套持續集成系統最有名的是jenkins( https://jenkins.io/ )。這套流程雖然一開始的建置需要花費一些時間,但是當整個機制完成之後,團隊成員每天能夠拿到一個最新的版本以檢視其前一天的工作成果,而且最大的好處是能夠讓各種潛在的議題在軟體實作的初期就浮現出來。一個好的CI流程能夠體現軟體專案的心跳聲,若是我們能夠越早傾聽出專案中各種潛在的病症,那麼便能讓我們後期專案的開發與維護成本降至最低。
UE4 Code Plugin Marketplace上架心得
研究UE4也一段時間了,其中也做了幾套Plugin上架,今天就來說一些上架的心得吧。 首先先來介紹一下我目前放出來的Plugin HorizonUI Plugin : https://www.unrealengine.com/marketplace/horizon-ui-plugin 基本上這套我是規畫以後有任何新寫的UMG widget都會放在這裡,目前裡面主要有二個功能: HorizonFlipbookWidget: 由於引建內建機制要在UMG中做動畫是需要去寫Material來實現,這個Widget能讓UMG直接去吃引擎做好的flipbook。 HorizonDialogueMsgTextBlock:基本上是實現Rich Text的功能,文字裡面還能插入Texture跟Material再加上Dialogue模式的切換(打字機效果)是我跟別家實現不一樣的地方。 ========================================================== HorizonTween Plugin: https://www.unrealengine.com/marketplace/horizontween-plugin 基本上這套看名字應該就知道他在幹嘛了,裡面該有的東西我想都有了,應該沒有什麼明顯的bug,接下來就看大家用了之後有什麼問題回報我再進行修正。 還蠻多人問我做這些plugin上架到底賺不賺。我只能說賺的說真的不多,目前只有二套在上面根本沒辦法當正職,但至少一個月的飯錢可以靠上面的賣的回收。不過,如果有10套在上面的話或許情況就不一樣了…… 嘛,不過前提是做出來的東西要夠好才行,由於上架到marketplace是需要提交給Epic game審查的,基本上如果已經有類似的功能在marketplace上面,而你做的東西沒有比較好的話是會被reject的。 這二套我從提交到審查通過上架大概花費了3~4個禮拜的時間,有興趣提交plugin的人可以去看提交流程:https://publish.unrealengine.com/submission-guidelines 提交的第一步是先到下面這個網址填完該填的東西: https://publish.unrealengine.com/marketplace-submission 基本上他只看screenshot跟demo video,基本上是一個禮拜內會收到回應,如果你的通過審查的話你會收到下面這封信: 接下就是把你的plugin打包回給他們,他們會先做初步的編譯(在各平台)以及檢查是否有任何檔案缺損,有任何問題的話他們會寫信請你補上或修正。 如果都沒問題的話,他們就會送給他們內部的code review做審查,基本上的重點就是看code的質量以及裡面有沒有什麼license之類的問題。有任何問題的話code reviewer會寫信請你修正。這段期間大概要等1~2個禮拜,如果沒問題的話,下一步他們就會把你的plugin排入上架流程,接下來等大概1個多禮拜就會通知確切的上架時間。 ======================================== HorizonDatabase Plugin: https://github.com/dorgonman/HorizonDatabaseDemo (open source, Boost Software License) … Continued
[UE4][置頂] 自製Plugin販賣中
==================HorizonUIPlugin============================ Market連結: https://www.unrealengine.com/marketplace/horizon-ui-plugin 裡面目前主要的功能有二個: 1.HorizonFlipbookWidget:網路上找到的大多是用material來做UMG的動畫效果,但這個Widget可以直接吃在UE4中創建好的Flipbook。 2.HorizonDialogueMsgTextBlock:有打字機效果的RichText,支援在字段裡面顯示圖片,會自動計算區塊大小來進行斷行的顯示,因此不用怕像預設的Textblock一樣英文字沒空白的話會被切掉。Widget裡面有flag可以切換RichText跟dialogue打字機的效果。裡面的顏色跟各種控制主要使用<text>跟<img>這二個tag在做控制。 sample可以在我的github找到:https://github.com/dorgonman/HorizonUIPluginDemo 以後如果自己有做什麼跟UMG相關的widget的話會一直在這個plugin中進行更新:) 如果有什麼bug或建議的話也歡迎提出~ ======================HorizonTweenPlugin========================= Market連結:https://www.unrealengine.com/marketplace/horizontween-plugin Sample Project: https://github.com/dorgonman/HorizonTweenDemo 文檔: http://horizon-studio.net/ue4/horizon_tween_plugin/doc/doxygen/html/
[UE4++] BitMask 使用介紹
BitMask是使用bit來表達True或false的概念,它可以使用一個int來表達多組的功能是否啟用。 先讓我們來看下圖: 若我們想要依照PlayerLevel跟Speed來進行排序的話,一般不知道這個技巧的人可能會使用二個int來處理這件事。在使用了BitMask之後我們就可以藉由將第一跟第三個bit設為1來表達這件事。在一般的C++中我們常常在enum的設定中就直接將該bit位移,如下面的code: https://gist.github.com/dorgonman/5c45efee5766a481c4db0d2f059eeed2 但在UE4中的Bitmask沒辦法使用跟上面一樣的做法,由於enum class被限制成只能使用uint8,因此在UE4中是直接將enum中的值當成位移數,如下面的code: https://gist.github.com/dorgonman/2fcb2068fdd4d044ebba7f8e5e299870 其中 int32 bitFlag = static_cast<int32>((1 << (int32)EHorizonPlayerSortType::Level) | (1 << (INT32)EHorizonPlayerSortType::Speed)); 就是模擬Blueprint中選擇要啟用哪個enum的C++實作。
[UE4++] 深入unreal asset loading
本文章使用引擎版本:4.11 當我們將png或其他格式的圖檔放到Content這個folder下面,我們會發現引擎會自動把它轉成uassets的格式,之後我們就可以在editor中很方便的對它進行各種操作。但是,我們到底該怎麼樣用C++把這個檔案動態的讀進來?官方文件對這方面的文件實在是少的可憐,不過沒關係,原始碼就是我們最好的文件,深入引擎源碼閱讀之後,還是可以找到相關的方法。 簡單來說,引擎中所有的asset分為被控管(managed)以及沒被控管(unmanaged)二種: 被控管(managed)的asset副檔名為.uasset、.umap,會出現在editor的content browser中,我們可以直接在editor中對它進行各種操作,這些檔案包UMG、blueprint、material或匯進來的圖檔……等等。另外一些則是沒被控管(unmanaged)的檔案,如png、json……等等其他不是經過引擎處理所產生的檔案。 被控管(managed)跟沒被控管(unmanaged)的檔案其實都是可以動態的用C++讀進引擎裡面的,只是不同的是它們所要求的檔案路徑不同。前者要求的是LongPackageName,而後者要求的是實際的檔案路徑。 被控管檔案(managed)的讀取方法 讀取的方法目前我所知的有二種。 第一種是先在constructor中先拿到Class,然後再NewObject,下面的範例展示如果把一個做好的UMG讀進來: https://gist.github.com/dorgonman/b669fbc6382e938dc4262d80cb46b56e 其中CreateWidget只是對NewObject做一些檢查跟Widget相關的設定,最終還是會呼叫UUserWidget* NewWidget = NewObject<UUserWidget>(Outer, UserWidgetClass);。widget->AddToViewport()這個方法可以讓這個UMG顯示在營幕上。 只是這個方法有個缺點,就是所有需要用到的uasset都必須要先在constructor中先把其class讀進來。若是在其他地方呼叫ConstructorHelpers::FClassFinder的話則會馬上噴出一個錯誤:UE_CLOG(!ThreadContext.IsInConstructor, LogUObjectGlobals, Fatal, TEXT(“FObjectFinders can’t be used outside of constructors to find %s”), ObjectToFind); 因此要做到真正的動態讀取的話,我們可以使用第二種方法:FStringAssetReference。以下的code試著去讀取UMG、umap、flipbook跟UTexture2D: https://gist.github.com/dorgonman/700b83f04da2ca8865745d9430759e6e 我們可以看到FPackageName::LongPackageNameToFilename,從範例中應該大概可以看出來這個方法的用途是什麼。它把我們餵入的package path轉換成檔案路徑。 餵入PackageFileNamePath: /Game/UMG/NewWidgetBlueprint 轉成FileNamePath: ../../../../../../Users/dorgon/Documents/Unreal Projects/MyProject/Content/UMG/NewWidgetBlueprint.uasset 上述的方法雖然我們可以發現在editor模式下可以運行的很好,但是在打包出來的版本卻發現會無法讀入UMG或blueprint這二個asset。這是因為在cooked … Continued
[UnrealEngine4][C++] GENERATED_BODY() vs GENERATED_UCLASS_BODY()
本篇文章使用引擎版本:4.11 在剛開始寫C++專案的時候肯定是被這二個macro搞的有點混亂,稍微有經驗一點的程式設計師馬上就會知道這二個macro肯定是用來產生某些預設方法用的。 其產生的檔案會放在YouProject\Intermediate\Build\Win64\UE4\Inc\YouProject\xxx.generated.h裡面。 然而在使用的過程當然,總是會看到一些範例程式一下子用GENERATED_BODY(),又一下子用GENERATED_UCLASS_BODY(),實在搞得我好混亂阿,到底要用那個才對?先說結論,GENERATED_UCLASS_BODY()其實是4.6版本之前留下來的macro,在4.6的時候其release note有以下說明: You now define and use “normal” C++ constructors for your classes. You can use parameterless constructors too now! You no longer need to supply a category for your properties to expose them to the editor … Continued
[UnrealEngine4] 幾個增加C++開發速度的小技巧
分享幾個開發C++時的小技巧。 1. 在安裝完 Engine\Extras\UnrealVS之後,於Visual Studio中上面的launch bar中可以加入運行時的參數如下:“$(SolutionDir)$(ProjectName).uproject” -debug -game debug:可以在VS斷點時顯示一些原本被優化掉的參數。game: 可以跳過打開編輯器的階段直接開始遊戲(這個很重要,可以減少很多開發時間) 2.另外原本預設的編輯機制是Unity編譯,也就是把所有的檔案合成一個再開始,這對於常常只是改個小地方的開發時期其實會有點痛苦,只要在build.cs中加入以下宣告:MinFilesUsingPrecompiledHeaderOverride = 1;bFasterWithoutUnity = true; 加入這二行之後會發現速度有很明顯的提升
[C++]template metaprogramming簡單介紹
簡單來說,這玩意就是在編譯期做原本運行期在做的事情,你完全可以不需要用到這個技巧而達到同樣的功能。 例如原本運行期的OO繼承是這樣寫: class Base{ public: virtual void someMethod() = 0; }; class Derived : public Base{ public: virtual void someMethod() override{}; }; 在運行期呼叫裡面的someMethod()的時候需要再去找v-table才有辦法定位置該方法。由於所有事情都在運行時期發生,所以會有一些cost存在(方法定位的時間以及儲存v-table的空間)。只要該類別任何一個方法宣告virtual就會建立出v-table。 而用template寫就變成: template <class Derived> class Base{ void someMethod(){ static_cast<Derived*>(this)->someMethodImplement(); }; }; class Derived : Base<Derived>{ … Continued