TCHAR 字符


C++支持两种字符集:即常见的ANSI编码和宽字符的Unicode编码,实际对应的字符类型分别是char和wchar_t,在不同平台环境下,我们可能需要不同的字符类型。

TCHAR就是UE4通过对char和wchar_t的封装,将其中的操作进行了统一,使程序具有可移植性。

使用TEXT()宏包裹字符串字面量

博主之前编码规范的笔记中曾提到必须得用TEXT()宏来包裹字符串字面量,其原因就在于无包裹的字符串字面量默认就是表示ANSI字符,字符串字面量前面多个L就是表示宽字符,通过TEXT包裹则可以让UE4自动选择适合当前平台环境的编码(例如宏可能展开成L"Hello World!"也可能展开成u“Hello World!”)

  1. "Hello World!"; //ANSI字符
  2. L"Hello World!"; //16位宽字符
  3. TEXT("Hello World!"); //具有可移植性,在部分平台是16位宽字符
  4. //这样我们可以用TCHAR*表示这种字符串
  5. const TCHAR* TcharString = TEXT("Hello World!");

转换字符编码

当我们调用UE4以外的API且必须得将TCHAR类型与char/wchar_t类型相互转换时,就要使用UE4提供的转换宏:

  1. // 引擎字符串(TCHAR*) -> ANSI字符串(char*)
  2. TCHAR_TO_ANSI(TcharString);
  3. // 引擎字符串(TCHAR*) -> Unicode字符串(wchar_t*)
  4. TCHAR_TO_UTF8(TcharString);
  5. // ANSI字符串(char*) -> 引擎字符串(TCHAR*)
  6. ANSI_TO_TCHAR(CharString);
  7. // Unicode字符串(wchar_t*) -> 引擎字符串(TCHAR*)
  8. UTF8_TO_TCHAR(WChartString);

注意1:传入的参数必须是一个字符串(TCHAR),因为参数无论是指针还是字符类型都会在宏里被强制类型转换为指针,错误的类型转换会导致运行时崩溃(编译期无法检测出该转型错误)。典型的例子就是假如传入的是 TCHAR 而非 TCHAR,编译后运行时会出现崩溃。

  1. SomeAPI(TCHAR_TO_ANSI(TcharString)); // OK
  2. 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 使用

  1. // 构造:通过字符串字面量构造时应记得使用TEXT()宏
  2. FString MyFString = FString(TEXT("Hello"));
  3. // 格式化方式创建
  4. // 注:像C的printf函数那样,使用格式化参数创建FString对象
  5. FString MyFString = FString::Printf(TEXT("%s,%d"), *TestFString, 2333);
  6. // 比较
  7. // 注:前一种不忽略大小写,后两种忽略大小写;使用Equals可以更加清晰表示是否忽略大小写
  8. if(MyFString.Equals(OtherFString, ESearchCase::CaseSensitive)){...}
  9. if(MyFString.Equals(OtherFString, ESearchCase::IgnoreCase)){...}
  10. if(MyFString == OtherFString){...}
  11. // 返回是否存在子字符串
  12. // 注:参数ESearchCase(是否忽略大小写)、ESearchDir(搜索方向),默认参数为忽略大小写,从前往后搜索
  13. if(MyFString.Contains(TEXT("ello"), ESearchCase::IgnoreCase, ESearchDir::FromStart){...}
  14. // 返回找到的第一个子字符串实例的索引,若未找到则返回INDEX_NONE
  15. if(MyFString.Find(TEXT("ello"), ESearchCase::IgnoreCase, ESearchDir::FromStart, INDEX_NONE) != INDEX_NONE){...}
  16. // 用 + 或 += 运算符拼接字符串
  17. FString MyFString,A,B;
  18. MyFString = A + B;
  19. MyFString += A;
  20. // FString -> TCHAR* (TCHAR*与FString基本都能自动隐式转换)
  21. const FString MyFString;
  22. const TCHAR *TcharString = *MyFString;
  23. // FString -> int32/float
  24. const FString MyFString = TEXT("23333");
  25. int32 MyStringtoInt = FCString::Atoi(*MyFString);
  26. const FString TheString = TEXT("1234.12");
  27. float MyStringtoFloat = FCString::Atof(*MyFString);
  28. // int32/float -> FString
  29. const FString MyFString = FString::FromInt(23333);
  30. const FString MyFString = FString::SanitizeFloat(1234.12f);
  31. // std::string -> FString
  32. std::string StdString = "Hello";
  33. const FString FStringFromStdString(StdString.c_str());
  34. // FString -> std::string
  35. const FString MyFString= TEXT("Hello");
  36. std::string str(TCHAR_TO_UTF8(*MyFString));
  37. // FName -> FString
  38. FString MyFString = MyFName.ToString();
  39. // FText -> FString
  40. // 注:FText转换为FString会丢失本地化信息
  41. FString MyFString = MyFText.ToString();

FName 字符串


FName 是一种静态(不可变)字符串,主要被用来作为标识符等不变的字符串(例如:资源路径/资源文件类型/平台标识/数据表格原始数据等...)

FName 的主要特点有:

  • 比较字符串操作非常快
  • 即使多个相同的字符串,也只在内存存储一份副本,避免了冗余的内存分配操作
  • 不区分大小写

FName 剖析

FName 实际上就是一个索引编号,整个FName系统主要是通过哈希表来实现的,代价是不允许对字符串进行修改操作(静态特性)。

FName 用字符串构造时只进行一次字符串哈希映射,分配得到在哈希表的索引编号。在此系统中,即使在多个地方声明字符串,只要其字符串元素都一样,那么它在数据表中只有一份副本(哈希映射到同一个索引编号)。通过这个索引编号,我们也可以在表中快速定位 FName 所代表的字符串。

为了优化字符串,在游戏开发过程中,如果可以确定哪些字符串是固定不变的数据且无需考虑文本国际化,应该尽可能对它们使用FName,只在必要的时候才将 FName 转换为其他字符串类型进行操作。

UE4的UObject的就是使用的FName来储存对象名称,在内容浏览器中为新资源命名时/变更动态材质实例中的参数/访问骨骼网格体中的一块骨骼时都会需要使用 FName

FName 使用

  1. // 构造:记得TEXT()宏
  2. FName TestName = FName(TEXT("D:\UnrealEngine\Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectBase.h"));
  3. // 比较
  4. // 它实际上并不比较每个字符,而是对比索引编号,可极大地节约CPU开销
  5. // == 运算符 用于对比两个 FNames,返回 true 或 false
  6. if(TestFName == OtherFName){...}
  7. // FName::Compare 若等于 Other 将返回0;若小于/大于 Other 将返回小与/大于0的数
  8. if(TestFName.Compare(OtherFName)==0){...}
  9. // 搜索名称表
  10. // 如果确定 FName 是否在表中(但不希望进行自动添加),可在构造函数中补充一个搜索参数 FNAME_Find
  11. // 若名称不在名称表中,FName 的索引将被设为 NAME_None
  12. // 注:将不对指针进行null检查,因为使用的是普通字符串
  13. if(FName(TEXT("pelvis"), FNAME_Find) != NAME_None){...}
  14. // 检查 FName 在特定使用情况下的有效性
  15. // 注:执行转换时,需注意可能包含对创建中的 FName 类型无效的字符(例如框内的字符:【\"' ,\n\r\t】)
  16. if(MyFName.IsValidObjectName()){...}
  17. // FString -> FName
  18. // 注:FString转换至FName时会丢失原始字符串的大小写信息
  19. FName MyFName = FName(*MyFString);
  20. // FText -> FName
  21. // 没有直接的转换方法,需要 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个宏中的一个来包裹字符串字面量:

  1. NSLOCTEXT( namespace , key , source )

    • namespace 命名空间:一个工程中可以存在多个命名空间,用于区分翻译的不同用途(例如我们可以将要翻译的源码区分为调试和发行2个命名空间)
    • key 上下文:区分在不同场景下相同的源文(例如同样一句话“Fuck!”在两种场合可能会翻译成不同意思:“去你的!”、“真是见鬼了!”)
    • source 源文:需要翻译的原始文本
  1. FText constFTextHelloWorld = NSLOCTEXT("MyOtherNamespace","Scene1","Hello World!");
  1. LOCTEXT( key , source )

    • 需要使用 LOCTEXT 必须在源文件头定义 LOCTEXT_NAMESPACE 宏,然后在需要在结尾处取消该宏
    • 可以看作是 NSLOCTEXT 的一种简便写法,不用多次重复写namespace
  1. #define LOCTEXT_NAMESPACE "MyOtherNamespace" // 定义 LOCTEXT 命令空间
  2. FText constFTextGoodbyeWorld= LOCTEXT("Scene1","Goodbye World!");
  3. //...
  4. #undef LOCTEXT_NAMESPACE // 注意:必须取消宏定义

UE4的本地化系统编辑器可以把所有的 FText 收集起来,然后就在编辑器中对目标Text进行不同语言的翻译:

UE4编辑器具体的本地化功能操作可以参考 UE4制作多语言游戏(本地化功能详解)

  1. // 数字/日期/时间变量 -> 当前文化(语言)下的FText文本
  2. FText::AsNumber()
  3. FText::AsPercent()
  4. FText::AsCurrency()
  5. FText::AsDate()
  6. FText::AsTime()
  7. // 格式化创建:排序参数
  8. // 注:格式化参数都需是FText类型
  9. // 占位符是大括号,其标识格式参数的开头和结尾,数值代表对应第x个已传递的参数
  10. FText PlayerName;
  11. FText MyFText = FText::Format(
  12. NSLOCTEXT("MyNamespace","ExampleScene", "Hello {0}!You have {1} Hp!"),
  13. PlayerName,
  14. FText::AsNumber(CurrentHealth)
  15. );
  16. // 格式化创建:命名参数
  17. // 占位符是大括号,其标识格式参数的开头和结尾,命名代表在传入的 FFormatNamedArgs 集合中找到的参数名称
  18. FFormatNamedArguments Arguments;
  19. Arguments.Add(TEXT("CurrentHealth"), FText::AsNumber(CurrentHealth));
  20. FText MyFText = FText::Format(
  21. NSLOCTEXT("MyNamespace","ExampleScene", "You currently have {CurrentHealth} health left."),
  22. Arguments
  23. );
  24. // 比较
  25. // FText 不支持重载运算符比较,但是提供多个函数以根据不同的比较规则进行比较(第二个参数ETextComparisonLevel决定要使用的比较规则)
  26. // 返回bool
  27. if(MyFText.EqualTo(OtherFText,ETextComparisonLevel::Default)){...}
  28. // 实质还是调用EqualTo,只是第二个参数ETextComparisonLevel使用了IgnoreCase值(省略大小写)
  29. if(MyFText.EqualToCaseIgnored(OtherFText)){...}
  30. // 返还0表示相等,而负值或正值分别表示比较结果的低于或高于
  31. if(MyFText.CompareTo(OtherText,ETextComparisonLevel::Default)==0){...}
  32. // 实质还是调用CompareTo,只是第二个参数ETextComparisonLevel使用了IgnoreCase值
  33. if(MyFText.CompareToCaseIgnored(OtherText)){...}
  34. // FName -> FText
  35. FText MyFText = FText::FromName(MyFName);
  36. // 创建非本地化的(即"语言不变")文本
  37. // 例如:在UI中显示一个玩家名字(即使不是同一文化的玩家,也应该看到他国文字的命名)
  38. FText MyFText = FText::AsCultureInvariant(MyFString);
  39. // FString -> FText
  40. // 注:此效果等同于非编辑器版本中的 AsCultureInvariant。在编辑器版本中,此函数不会将文本标记为语言不变,也就是说若将其指定到已保存资源中的 FText 属性,其仍为可本地化状态。
  41. FText MyFText = FText::FromString(MyFString);

总结


  1. 一般情况,使用 FString 以支持复杂字符串操作。
  2. 确定字符串固定不变(这类字符串往往起标识作用)时,使用 FName 可以提高性能。
  3. 当字符串需要显示给玩家时,使用 FText 以支持文本本地化和增强字体渲染性能。

参考


C++字符类型 char/wchar_t/char16_t/char32_t | Visual Studio 文档

虚幻引擎4 官方文档 | 字符串

虚幻引擎4 官方文档 | Text Localization

UE4 C++基础教程 - 字符串和本地化

UE4制作多语言游戏(本地化功能详解)

UE4入门-常见基本数据类型-字符串

Aery的UE4 C++游戏开发之旅(5)字符&字符串的更多相关文章

  1. Aery的UE4 C++游戏开发之旅(1)基础对象模型

    目录 UObject Actor种类 AActor APawn(可操控单位) AController(控制器) AGameMode(游戏模式) AHUD(HUD) ... Component种类 UA ...

  2. Aery的UE4 C++游戏开发之旅(3)蓝图

    目录 蓝图 蓝图命名规范 蓝图优化 暴露C++至蓝图 暴露C++类 暴露C++属性 暴露C++函数 暴露C++结构体/枚举 暴露C++接口 蓝图和C++的结合方案 使用继承重写蓝图 使用组合重写蓝图 ...

  3. Aery的UE4 C++游戏开发之旅(4)加载资源&创建对象

    目录 资源的硬引用 硬指针 FObjectFinder<T> / FClassFinder<T> 资源的软引用 FSoftObjectPaths.FStringAssetRef ...

  4. Aery的UE4 C++游戏开发之旅(2)编码规范

    目录 C++基础类型规范 命名规范 头文件规范 字符串规范 字符集规范 参考 C++基础类型规范 由于PC.XBOX.PS4等各平台的C++基础类型大小可能不同(实际上绝大部分都是整型类型的大小不同) ...

  5. Android游戏开发之旅 View类详解

    Android游戏开发之旅 View类详解 自定义 View的常用方法: onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onMeasure(int, int) ...

  6. 《cocos2d-x游戏开发之旅》问题2016-10-7

    今天按书上做,遇到问题卡住了 书P115 项目是 littlerunner

  7. Cocos2d-x 3.x游戏开发之旅 笔记

    #include "HelloWorldScene.h"#include "SimpleAudioEngine.h"#include "MyHello ...

  8. Cocos2d-x 3.x游戏开发之旅

    Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...

  9. cocos2d-x 游戏开发之有限状态机(FSM) (四)

    cocos2d-x 游戏开发之有限状态机(FSM) (四) 虽然我们了解了FSM,并且可以写自己的FSM,但是有更好的工具帮我们完成这个繁琐的工作.SMC(http://smc.sourceforge ...

随机推荐

  1. Mac最新Flutter环境搭建运行和对比理解声明式UI

    前言 这段时间一直都在学习和写关于SwiftUI的东西,前面也总结了四篇文章来大体上说了下Demo中功能实现的一些细节,后面准备开始了解学习一下Flutter,争取在年前能再用Flutter写一份项目 ...

  2. 【Oracle】更改oracle中的用户名称

    修改oracle中的用户名,要需要修改oracle基表中的相关内容, 1.查看user#, select user#,name from user$ s where s.name='用户修改前的'; ...

  3. kubernets之job资源

    一  介绍job资源 1.1   前面介绍的RC,RS,DS等等,管控的pod都是需要长期持久的运行的应用,但是尝试考虑另外一种场景,在微服务的场景下,有些pod的作用就是需要 执行完一些命令之后正常 ...

  4. 用CSS实现蒙德里安名画|学习麻瓜编程以项目为导向入门前端 HTML+CSS+JS

    实现项目:用CSS实现蒙德里安名画 1.首先,献上代码和效果图 1.1代码: <head> <style> .centerframe{ display: flex; heigh ...

  5. kubernetes之为每个命名空间的pod设置默认的requests以及limits

    一  为啥需要为命名空间里面添加pod添加默认的requests和limits? 通过前面的学习我们已经知道,如果节点上面的pod没有设置requests和limits,这些容器就会受那些设置了的控制 ...

  6. centos7.4使用filrewalld打开关闭防火墙与端口

    1.firewalld的基本使用启动: systemctl start firewalld关闭: systemctl stop firewalld查看状态: systemctl status fire ...

  7. .NET Core 问题记录

    前言: 最近在项目中遇到了遇到了写部署步骤过多的问题,为了减少.net core项目部署步骤:需要对一些基础问题进行验证: 如端口设置.单页应用程序(angluar)合并部署方式等相关问题,特将解决过 ...

  8. Java优先队列PriorityQueue的各种打开方式以及一些你不知道的细节

    目录 Java优先队列PriorityQueue的各种打开方式以及一些你不知道的细节 优先队列的默认用法-从小到大排序 对String类用优先队列从大到小排序 通过自定义比较器对自定义的类进行从小到大 ...

  9. 使用Jmeter对SHA1加密接口进行性能测试

    性能测试过程中,有时候会遇到需要对信息头进行加密鉴权,下面我就来介绍如何针对SHA1加密鉴权开发性能测试脚本1.首先了解原理,就是需要对如下三个参数进行SHA1加密,(AppSecret + Nonc ...

  10. Lambda架构正是这样一种用来处理不能够直接实时计算问题的通用架构

    https://mp.weixin.qq.com/s/BGHOw12iCASJy1pgkYZi3w 当数据处理做不到实时,应该怎么办?