目錄
- TCHAR 字符
- 使用TEXT()宏包裹字串字面量
- 轉換字符編碼
- FString 字串
- FString 剖析
- FString 使用
- FName 字串
- FName 剖析
- FName 使用
- FText 字串
- FText 剖析
- FText 使用
- 總結
- 參考
TCHAR 字符
C++支持兩種字符集:即常見的ANSI編碼和寬字符的Unicode編碼,實際對應的字符型別分別是char和wchar_t,在不同平臺環境下,我們可能需要不同的字符型別,
TCHAR就是UE4通過對char和wchar_t的封裝,將其中的操作進行了統一,使程式具有可移植性,
使用TEXT()宏包裹字串字面量
博主之前編碼規范的筆記中曾提到必須得用TEXT()宏來包裹字串字面量,其原因就在于無包裹的字串字面量默認就是表示ANSI字符,字串字面量前面多個L就是表示寬字符,通過TEXT包裹則可以讓UE4自動選擇適合當前平臺環境的編碼(例如宏可能展開成L"Hello World!"也可能展開成u“Hello World!”)
"Hello World!"; //ANSI字符
L"Hello World!"; //16位寬字符
TEXT("Hello World!"); //具有可移植性,在部分平臺是16位寬字符
//這樣我們可以用TCHAR*表示這種字串
const TCHAR* TcharString = TEXT("Hello World!");
轉換字符編碼
當我們呼叫UE4以外的API且必須得將TCHAR型別與char/wchar_t型別相互轉換時,就要使用UE4提供的轉換宏:
// 引擎字串(TCHAR*) -> ANSI字串(char*)
TCHAR_TO_ANSI(TcharString);
// 引擎字串(TCHAR*) -> Unicode字串(wchar_t*)
TCHAR_TO_UTF8(TcharString);
// ANSI字串(char*) -> 引擎字串(TCHAR*)
ANSI_TO_TCHAR(CharString);
// Unicode字串(wchar_t*) -> 引擎字串(TCHAR*)
UTF8_TO_TCHAR(WChartString);
注意1:傳入的引數必須是一個字串(TCHAR*),因為引數無論是指標還是字符型別都會在宏里被強制型別轉換為指標,錯誤的型別轉換會導致運行時崩潰(編譯期無法檢測出該轉型錯誤),典型的例子就是假如傳入的是 TCHAR 而非 TCHAR*,編譯后運行時會出現崩潰,
SomeAPI(TCHAR_TO_ANSI(TcharString)); // OK
const char* SomePointer = TCHAR_TO_ANSI(TcharString); // Bad!!!
注意2:只在給函式傳參時使用這個宏轉換,千萬不要保留指向它們的指標,
如果字串相對較小,轉換器類物件的預分配陣列(presized array)直接可以容納所有字符,也就是記憶體都分配在堆疊中; 如果字串較長,則需要在堆中分配一個臨時緩沖區,轉換宏實際上就是產生一個臨時的轉換器類物件,當該物件釋放時也會釋放其生成的字串,所以不要保留指向它們的指標,不然泄露給另一個作用域將會是巨大災難,
FString 字串
FString 是一種動態字串,實際上就類似于我們所熟悉的std::string型別,是我們平時撰寫UE4 C++代碼時最常需要用到的字串型別,
由于動態的特性,FString擁有以下特點:
- 支持很多字串操作(例如轉換int32/float,字串拼接,查找子字串,逆置)
- 開銷比靜態(不可變)字串類(FName、FText)要更大
FString 剖析
FString 本質是構建在TArray<TCHAR> 之上,即字串的元素使用TCHAR型別而非char型別,
FString 使用
// 構造:通過字串字面量構造時應記得使用TEXT()宏
FString MyFString = FString(TEXT("Hello"));
// 格式化方式創建
// 注:像C的printf函式那樣,使用格式化引數創建FString物件
FString MyFString = FString::Printf(TEXT("%s,%d"), *TestFString, 2333);
// 比較
// 注:前一種不忽略大小寫,后兩種忽略大小寫;使用Equals可以更加清晰表示是否忽略大小寫
if(MyFString.Equals(OtherFString, ESearchCase::CaseSensitive)){...}
if(MyFString.Equals(OtherFString, ESearchCase::IgnoreCase)){...}
if(MyFString == OtherFString){...}
// 回傳是否存在子字串
// 注:引數ESearchCase(是否忽略大小寫)、ESearchDir(搜索方向),默認引數為忽略大小寫,從前往后搜索
if(MyFString.Contains(TEXT("ello"), ESearchCase::IgnoreCase, ESearchDir::FromStart){...}
// 回傳找到的第一個子字串實體的索引,若未找到則回傳INDEX_NONE
if(MyFString.Find(TEXT("ello"), ESearchCase::IgnoreCase, ESearchDir::FromStart, INDEX_NONE) != INDEX_NONE){...}
// 用 + 或 += 運算子拼接字串
FString MyFString,A,B;
MyFString = A + B;
MyFString += A;
// FString -> TCHAR* (TCHAR*與FString基本都能自動隱式轉換)
const FString MyFString;
const TCHAR *TcharString = *MyFString;
// FString -> int32/float
const FString MyFString = TEXT("23333");
int32 MyStringtoInt = FCString::Atoi(*MyFString);
const FString TheString = TEXT("1234.12");
float MyStringtoFloat = FCString::Atof(*MyFString);
// int32/float -> FString
const FString MyFString = FString::FromInt(23333);
const FString MyFString = FString::SanitizeFloat(1234.12f);
// std::string -> FString
std::string StdString = "Hello";
const FString FStringFromStdString(StdString.c_str());
// FString -> std::string
const FString MyFString= TEXT("Hello");
std::string str(TCHAR_TO_UTF8(*MyFString));
// FName -> FString
FString MyFString = MyFName.ToString();
// FText -> FString
// 注:FText轉換為FString會丟失本地化資訊
FString MyFString = MyFText.ToString();
FName 字串
FName 是一種靜態(不可變)字串,主要被用來作為識別符號等不變的字串(例如:資源路徑/資源檔案型別/平臺標識/資料表格原始資料等...)
FName 的主要特點有:
- 比較字串操作非常快
- 即使多個相同的字串,也只在記憶體存盤一份副本,避免了冗余的記憶體分配操作
- 不區分大小寫
FName 剖析
FName 實際上就是一個索引編號,整個FName系統主要是通過哈希表來實作的,代價是不允許對字串進行修改操作(靜態特性),
FName 用字串構造時只進行一次字串哈希映射,分配得到在哈希表的索引編號,在此系統中,即使在多個地方宣告字串,只要其字串元素都一樣,那么它在資料表中只有一份副本(哈希映射到同一個索引編號),通過這個索引編號,我們也可以在表中快速定位 FName 所代表的字串,
為了優化字串,在游戲開發程序中,如果可以確定哪些字串是固定不變的資料且無需考慮文本國際化,應該盡可能對它們使用FName,只在必要的時候才將 FName 轉換為其他字串型別進行操作,
UE4的UObject的就是使用的FName來儲存物件名稱,在內容瀏覽器中為新資源命名時/變更動態材質實體中的引數/訪問骨骼網格體中的一塊骨骼時都會需要使用 FName
FName 使用
// 構造:記得TEXT()宏
FName TestName = FName(TEXT("D:\UnrealEngine\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectBase.h"));
// 比較
// 它實際上并不比較每個字符,而是對比索引編號,可極大地節約CPU開銷
// == 運算子 用于對比兩個 FNames,回傳 true 或 false
if(TestFName == OtherFName){...}
// FName::Compare 若等于 Other 將回傳0;若小于/大于 Other 將回傳小與/大于0的數
if(TestFName.Compare(OtherFName)==0){...}
// 搜索名稱表
// 如果確定 FName 是否在表中(但不希望進行自動添加),可在建構式中補充一個搜索引數 FNAME_Find
// 若名稱不在名稱表中,FName 的索引將被設為 NAME_None
// 注:將不對指標進行null檢查,因為使用的是普通字串
if(FName(TEXT("pelvis"), FNAME_Find) != NAME_None){...}
// 檢查 FName 在特定使用情況下的有效性
// 注:執行轉換時,需注意可能包含對創建中的 FName 型別無效的字符(例如框內的字符:【\"' ,\n\r\t】)
if(MyFName.IsValidObjectName()){...}
// FString -> FName
// 注:FString轉換至FName時會丟失原始字串的大小寫資訊
FName MyFName = FName(*MyFString);
// FText -> FName
// 沒有直接的轉換方法,需要 FText -> FString -> FText
FText 字串
FText 是一種靜態字串,在UE4中主要負責處理文本本地化,從而顯示給不同語言的玩家,當你的游戲需要支持不止一種語言時,就需要考慮文本本地化,遵循這個規則:當字串需要顯示(面向玩家)時,應當使用 FText
因此當 FString 字串需要顯示(面向玩家)時,應當轉換為 FText 型別再作顯示,
FText 的主要特點有:
- 支持文本本地化
- 提高文本渲染的性能
- 較快的copy操作
FText 剖析
FText 核心實質是一個TSharedRef<ITextData>,即實際文本資料的智能參考,這也使得 FText 的拷貝成本很低(只需拷貝指標),
此外 FText 通過 flags 記錄一些屬性,這樣就可以利用 FTextSnapshot 工具來高效地檢測 FText 要顯示的內容是否發生改變(例如實時的語言文化切換),從而再立即編譯相應的字體,
FText 的不可變是指它的各語言文化等條件下的文本內容不會改變,但當前語言文化顯示的內容仍然可能會切換(切換語言)
FText 的設計符合UI性能優化的一個思想,讓UI更新盡可能基于通知而不是基于輪詢,這樣當內容發生改變時,UMG可以不必每幀主動檢測顯示字串內容的每個字符,而是檢測FTextSnapshot的flags即可,只有內容發生改變的時候,才將新字串內容的每個字符編譯成對應字體然后更新渲染字體
FText 使用
構造 FText 時,需要用如下2個宏中的一個來包裹字串字面量:
- NSLOCTEXT( namespace , key , source )
- namespace 命名空間:一個工程中可以存在多個命名空間,用于區分翻譯的不同用途(例如我們可以將要翻譯的原始碼區分為除錯和發行2個命名空間)
- key 背景關系:區分在不同場景下相同的源文(例如同樣一句話“Fuck!”在兩種場合可能會翻譯成不同意思:“去你的!”、“真是見鬼了!”)
- source 源文:需要翻譯的原始文本
FText constFTextHelloWorld = NSLOCTEXT("MyOtherNamespace","Scene1","Hello World!");
- LOCTEXT( key , source )
- 需要使用 LOCTEXT 必須在源檔案頭定義 LOCTEXT_NAMESPACE 宏,然后在需要在結尾處取消該宏
- 可以看作是 NSLOCTEXT 的一種簡便寫法,不用多次重復寫namespace
#define LOCTEXT_NAMESPACE "MyOtherNamespace" // 定義 LOCTEXT 命令空間
FText constFTextGoodbyeWorld= LOCTEXT("Scene1","Goodbye World!");
//...
#undef LOCTEXT_NAMESPACE // 注意:必須取消宏定義
UE4的本地化系統編輯器可以把所有的 FText 收集起來,然后就在編輯器中對目標Text進行不同語言的翻譯:
UE4編輯器具體的本地化功能操作可以參考 UE4制作多語言游戲(本地化功能詳解)
// 數字/日期/時間變數 -> 當前文化(語言)下的FText文本
FText::AsNumber()
FText::AsPercent()
FText::AsCurrency()
FText::AsDate()
FText::AsTime()
// 格式化創建:排序引數
// 注:格式化引數都需是FText型別
// 占位符是大括號,其標識格式引數的開頭和結尾,數值代表對應第x個已傳遞的引數
FText PlayerName;
FText MyFText = FText::Format(
NSLOCTEXT("MyNamespace","ExampleScene", "Hello {0}!You have {1} Hp!"),
PlayerName,
FText::AsNumber(CurrentHealth)
);
// 格式化創建:命名引數
// 占位符是大括號,其標識格式引數的開頭和結尾,命名代表在傳入的 FFormatNamedArgs 集合中找到的引數名稱
FFormatNamedArguments Arguments;
Arguments.Add(TEXT("CurrentHealth"), FText::AsNumber(CurrentHealth));
FText MyFText = FText::Format(
NSLOCTEXT("MyNamespace","ExampleScene", "You currently have {CurrentHealth} health left."),
Arguments
);
// 比較
// FText 不支持多載運算子比較,但是提供多個函式以根據不同的比較規則進行比較(第二個引數ETextComparisonLevel決定要使用的比較規則)
// 回傳bool
if(MyFText.EqualTo(OtherFText,ETextComparisonLevel::Default)){...}
// 實質還是呼叫EqualTo,只是第二個引數ETextComparisonLevel使用了IgnoreCase值(省略大小寫)
if(MyFText.EqualToCaseIgnored(OtherFText)){...}
// 返還0表示相等,而負值或正值分別表示比較結果的低于或高于
if(MyFText.CompareTo(OtherText,ETextComparisonLevel::Default)==0){...}
// 實質還是呼叫CompareTo,只是第二個引數ETextComparisonLevel使用了IgnoreCase值
if(MyFText.CompareToCaseIgnored(OtherText)){...}
// FName -> FText
FText MyFText = FText::FromName(MyFName);
// 創建非本地化的(即"語言不變")文本
// 例如:在UI中顯示一個玩家名字(即使不是同一文化的玩家,也應該看到他國文字的命名)
FText MyFText = FText::AsCultureInvariant(MyFString);
// FString -> FText
// 注:此效果等同于非編輯器版本中的 AsCultureInvariant,在編輯器版本中,此函式不會將文本標記為語言不變,也就是說若將其指定到已保存資源中的 FText 屬性,其仍為可本地化狀態,
FText MyFText = FText::FromString(MyFString);
總結
- 一般情況,使用 FString 以支持復雜字串操作,
- 確定字串固定不變(這類字串往往起標識作用)時,使用 FName 可以提高性能,
- 當字串需要顯示給玩家時,使用 FText 以支持文本本地化和增強字體渲染性能,
參考
C++字符型別 char/wchar_t/char16_t/char32_t | Visual Studio 檔案
虛幻引擎4 官方檔案 | 字串
虛幻引擎4 官方檔案 | Text Localization
UE4 C++基礎教程 - 字串和本地化
UE4制作多語言游戲(本地化功能詳解)
UE4入門-常見基本資料型別-字串
系列其他文章:Aery的UE4 C++開發之旅系列文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/257673.html
標籤:其他
