[置頂] Unreal Engine 4哲學與實務:從Blueprint到C++

posted in: UnrealEngine | 8

前言…. (完成) 想寫這本書的想法其實已經在心中很久了,但由於各種現實的因素一直沒辦法付諸實行。一方面是有點懶,另一方面是覺得自己可能沒辦法整理的很好;尤其是官方已經有那麼多免費的教學課程了,我真的需要把時間花在這件事上嗎?然而在自己越來越熟悉引擎,看了越來越多相關的教學文章之後,便開始覺得相關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

UE4 Code Plugin Marketplace上架心得

posted in: UnrealEngine | 0

研究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販賣中

posted in: UnrealEngine | 10

  ==================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 使用介紹

posted in: C/C++程式設計, UnrealEngine | 0

  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

posted in: C/C++程式設計, UnrealEngine | 0

本文章使用引擎版本: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()

posted in: UnrealEngine | 0

本篇文章使用引擎版本: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++開發速度的小技巧

posted in: UnrealEngine | 0

分享幾個開發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; 加入這二行之後會發現速度有很明顯的提升

[UnrealEngine4] 在game模式下如何禁止在windows下拉window的border來調整大小

posted in: C/C++程式設計, UnrealEngine | 0

2023 6月 Update:剛剛試4.27,現在只要在ProjectSetting把Allow Window Resize拿掉就行了 引擎版本:4.9,以下內容已經有點舊,僅供參考 最近在看各別解析度對應的方法,發現unreal engine 4裡面已經有DPI Scale來很好處理這一塊了。但其實這個機制其實也並不完美,因為玩家能夠亂拉視窗的大小,隨便亂拉的結果則會有些UI會跑到看不見,尤其是當視窗大小被拉成不是預期的狀態時,整個UI會變成很奇怪,這對於一個好的遊戲體驗是沒辦法接受的。 在我的預期上調整短邊應該要能夠scale位置,而調整長邊去scale大小。但這個DPI Scale機制最終其實也只能選擇直的或橫的來做處理。雖然有一個custom可以實作自己的類別,看起來似乎有那麼一點戲可以做到想做的效果,但實在有點麻煩,而且實作出來應該也只是對值的customize而已,而不是根據動作(上面說的位置、大小),所以我開始在尋找禁止玩家去亂拉window大小的方法,最終目標是只在遊戲中提供幾個按鈕讓玩家選擇可用的解析度大小。這種做法也比較符合一般遊戲使用的機制,不用去煩惱那幾近無限的解析度適配的問題,只要我們提供的選項的aspect ratio是一致的,然後再根據我們的選項去把那個DPI Scale curve拉好,基本上就不會有什麼大問題。 為了這個問題我研究了好幾天,發現在void FWindowsWindow::Initialize這個function下面雖然有傳入一個FGenericWindowDefinition definition來指定這個視窗的樣式,但該參數並沒有開放給引擎的使用者進行調整,因為它在更上一層的FSlateApplication::MakeWindow被宣告成local的變數再傳進來的。那麼到底該怎麼去調整這視窗的樣式呢? 看來引擎並沒有一個generic的api來做這件事,因此我們能做的就是針對各個平台拿出NativeWindow再進行對應的實作,以下的程式碼可以達成這件事: 在遊戲跑起來之後呼叫一次這個方法之後,總算能讓玩家不能去亂拉window的大小啦。 當然在其他平台,如mac或linux,則必須要再survey相關的實作才行(如果有需要的話)。

如何使用C++動態載入UMG與Slot基本概念

posted in: UnrealEngine | 0

不囉唆,直接先上CODE: https://gist.github.com/dorgonman/cbfe159a83ba07f1c66e   可以看到在header檔裡面宣告了一個WidgetClass跟一個WidgetInstance。 其中尋找WidgetClass的動作必須放在constructor裡面,另外: ConstructorHelpers::FClassFinder<UGameWidget> PutNameHere(TEXT(“/Game/MyUMG”)); 這邊裡面的路徑指的是我們在editor做好放在Content資料夾的Widget Blueprint(名字是MyUMG.uasset 的那個),這裡不用寫完整路徑的原因是FClassFinder內部會自動加入相關的suffix,如下圖: 接下來在BeginPlay裡面做的事情就是用這個Class把WidgetInstance建立出來,並加入一個UImage進到這個Widget裡面去。 在這裡必須要注意的是並不是所有的Widget都有AddChild這個方法,所以我們才需要先拿出RootCanvasPanel之後才能把image加進去。 什麼是Slot?從名稱來看,它就是用來讓別人把東西掛上去的『洞』,只要我們的child掛上某個parent的洞之後,就獲得了該parent所提供的調整界面,如下圖: 在code裡面我們canvasPanelSlot->SetPosition(FVector2D(100, 100)),其實就是調整editor裡面的Position X跟Position Y。值得注意的是Slot裡面的參數都是相對於parent的,而不是絕對的。 下面再給出一個掛到Button下面之後能夠調整的slot參數: 這邊的Horizontal Alignment跟Vertical Alignment指的就是我們的Image在button中要對齊的是那個位置。 另外要注意的是UCanvasPanelSlot* canvasPanelSlot = (UCanvasPanelSlot*)image->Slot;這行必須要在AddChild之後,因為如果是放在之前的話,因為Slot還沒掛上parent,所以會得到空指標。

結合boost.log跟Unreal Engine 4的log系統

最近在研究怎麼把boost跟UE4做結合使用,目前已經成功的將一些好用的boost library應用在專案上了,就先來整理一下相關該注意的技術事項吧。 首先我嘗試導入的是boost的log機制,雖然UE4本身已經有LOG系統了,但稍微survey了一下,看起來其實還是不太完整,而且log寫起來其實還蠻費勁的;如果能把boost.log導進來的話,不僅輸出log的時候會更方便,而且又能夠利用boost.log本身上提供的許多強大的功能。 其實boost.log還蠻容易跟其他引擎做結合的,我們只需要寫一個backend,並將其所綁定的sink加入到boost.log系統裡面就行了。而這個backend要做的事情也很簡單,它只需要負責把傳進來的訊息再原封不動的傳給UE4原本的log系統。聽起來是不是很簡單?先讓我們來看一下下面這段code: https://gist.github.com/dorgonman/fdab8f1be4a11a4a4fbc 把sink加入到log系統的方式如下: typedef boost::log::sinks::synchronous_sink<HorizonLogBasicFormatedBackend> HorizonLogSink; boost::log::core::get()->add_sink(sink); 紅色的部份是我們自己寫的backend,其實作如下: https://gist.github.com/dorgonman/3f56b82a8116bddb3425 這個backend留有一個接口: inline void setLogMessageImplemen(HorizonLogImpFunc_t&& impl); 目的就是留一個log的實作方式給引擎方去設定,我們需要做的就是從UE4丟一個std::function到backend就行了。設定方式如下: https://gist.github.com/dorgonman/0db4138c2cee95b51582 然後由於UE4中的check macro跟boost中的有衝突,因此在include boost 相關的header之前需先做下面這件事: #pragma push_macro(“check”) #undef check //include your boost header here #pragma pop_macro(“check”) 接下來我們就可以很方便的使用boost所提供的log機制了: https://gist.github.com/dorgonman/575f2e4a3c78621bcff8 只是目前的機制只能用在64位元的版本中,不知道為什麼當運行起32位元的時候總是會在下面這行code crash: if (_Myostr.rdbuf() … Continued