[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

[C++]template metaprogramming簡單介紹

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

  簡單來說,這玩意就是在編譯期做原本運行期在做的事情,你完全可以不需要用到這個技巧而達到同樣的功能。 例如原本運行期的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

[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相關的實作才行(如果有需要的話)。

結合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

[讀書筆記]Universal References in C++11 part 7: Small String Optimize(SSO) 與 move

什麼是SSO?用了這麼久的C++,還是第一次聽到這個名詞。其實,這個是標準庫對std::string所進行的一種優化:對於長度小的字串使用預先分配的stack storage(通常是16 Byte),長字串就根據長度動態的new出free storage。──我們知道,stack storage的速度比free storage還要快出許多。 上一篇我們有談到,若是一個類別裡面沒有使用到free storage的話,那麼其實move就等同於從一個stack storage複制到另一個stack storage。當std::string滿足SSO啟動條件的時候,就是一個典型的例子。但其實我們並不需要擔心太多這個優化問題,因為這邊的成本小到我們可以忽視掉。 不過,多知道一些東西說不定什麼時候能夠派上用場,因此還是讓我們來看看這個優化的細節到底是怎麼進行的吧! 首先,建議先閱讀下面二篇文章: http://stackoverflow.com/questions/10315041/meaning-of-acronym-sso-in-the-context-of-stdstring https://akrzemi1.wordpress.com/2014/04/14/common-optimizations/ 本篇文章接下來將會探討VC++的std::string的實作。 首先,先讓我們看看VC++裡面的SSO的宣告:   enum {    // length of internal buffer, [1, 16] _BUF_SIZE = 16 / sizeof(value_type) < 1 ? 1 : 16 / sizeof(value_type) };   … Continued

[讀書筆記]Universal References in C++11 part 6: 儘量避免overloading

本篇主要參考effective modern C++ : Item 17: Understand special member function generation. Item 26: Avoid overloading on universal references. Item 27: Familiarize yourself with alternatives to overloading on universal references.   本文開始 在理解完Universal Reference(UR)的強大之處後,應該有很多人躍躍欲試的想把這個概念導入到自己撰寫的程式碼裡面吧?不過這東西也不是沒有它的限制。由於template編譯期動態產生程式碼的特性,因此編譯器會針對傳入的參數,非常積極的去產生對應的程式碼。 雖然上一篇我們談到了該怎麼樣用Type Trait在編譯期防止使用者傳入非預期的參數型態,但那其實只是一種防呆機制,實際上的問題在於別處:若是我們試圖去overload 該function的話,那麼將會有高達八成的使用情境程式會呼叫到UR版本。為什麼會這樣呢?讓我們先來看看以下這個正常運作的例子:   class Resource{ public: … Continued

[讀書筆記]Universal References in C++11 part 4: Reference Collapsing, Perfect Forwarding and Universal Reference

  Universal,這個字在其實是一種抽象的表達。任何抽象的表達,在下面大多會有一套自動化的機制,來將本該由使用者操作的複雜概念隱藏起來。Universal References可以說是一種抽象的介面,它提供了一個統一的窗口給lvalue reference以及rvalue reference。但是從上面幾篇文章我們不是才知道,將這二個reference的概念區分出來就是為了要減少暫存物件記憶體操作的成本嗎?現在說要把它們又合在一起不是本末倒置了嗎?──當然不會,這裡所說提供的統一窗口,是為了給在設計功能時,還不知道傳入物件型態的,需要在編譯期推導出來的情況下使用。   想想看,在什麼情況下會有這種情形?其實這種實作在標準函式中到處都是。應該呼之欲出了吧?答案是template。   template又稱為編譯期靜態多型,跟使用繼承方式的執型期動態多型的方式不同,它會在我們按下編譯的時候才將需要的程式碼產生出來。雖然介面上看起來只有一個,但實際上它在底下可是產生了lvalue reference跟rvalue reference二個版本。這種性質可以說是一種拯救;它讓我們程序猿們省下了不小維護程式碼的心力,因為,這樣我們就不需要對每個member variable製作二個不同的版本的setter。例如,若沒使用universal reference的程式碼會是下面的情況:   class Widget { public: Widget(){}; void setName(const std::string& newName) { m_name = newName; } // const lvalue void setName(std::string&& newName) { m_name = std::move(newName); } // … Continued

[讀書筆記]Universal References in C++11 part 3: 記憶體管理以及Rule of Three(Five)

    [insert page=’338′ display=’link’] 上一篇主要介紹了rvalue reference的概念,以及它導入用來解決無法區分lvalue reference以及暫存物件之間的copy問題。基本上這些操作的本質,其實也就是怎麼用更有效率、更簡單的方法去控管我們所使用的資源而已。──記憶體管理,一直是C++程式設計中一個非常重要的課題。 一個最常被提到的準則便是RAII的概念。何謂RAII?全名Resource Acquisition Is Initialisation,意即所有接下來這個物件所會用到的資源,在建構階段就已經全部建立完成。例如檔案的開啟、變數的初始化或記憶體配置。但這句話只說了一半,另外一個對應概念是Resource Release Is Destruction (RRID),意即在解構階段必須將所有要來的資源還回去。 自己new出來的要記得delete,之前開檔案要記得關。它是一種資源所有權管理的概念,每個物件要在自己的生命週期內管理自己擁有的資源;它也是一個設計的大原則,用來提醒程式設計師要記得將不要的資源釋放出去。不然帶來的後果,便是系統的不穩定或是直接崩潰。 可是我們該怎麼在C++中正確的實作RAII、RRID呢?只要在建構子跟解構子中做完對應的初始化以及釋放的動作就行了嗎?答案是,對,可是也不對。正確的解答應該要看我們對於這個類別在功能性上的設計與期待。若是該物件有將別的物件拷貝一份到自己身上的需求,則需要思考的是我們要進行的是deep copy、shallow copy還是將別人身上所管理的資源move到自己身上。嗯?這個不是上一篇所提到東西嗎?──沒錯,只是上一篇在程式碼的實作上並沒有符合我們對於一個類別在資源管理機制上的期待。一份完整的實作,必須要遵循 Rule of Three的設計準則:若是設計師有實作下列任何一個function,則其他的function都必須要有合適的實作: class Widget{ public: /** Copy constructor */ Widget(const Widget& other){ memberwiseCopy(other); } /** Destructor */ ~Widget(){} /** … Continued