笔者整理了自己在虚幻编程中经常需要使用的数据结构、或者一些具体的使用方案。
包括了容器、字符串的转换、智能指针、宏、委托、Log等。
无论是在喜欢虚幻中进行客户端开发、编辑器扩展、图形开发等,都需要掌握这些基础内容。
虚幻引擎实现了一套数据结构容器,并不推荐使用诸如 stl
的容器。
本小节主要介绍UE中容器的使用。主要涉及了增删改查以及遍历等操作。
UE中的主要容器有:
TArray<int32> IntArray;
TArray<int32> IntArray; // 初始化数组为5个10. [10,10,10,10,10] IntArray.Init(10,5);
TArray<int32> IntArray; IntArray.Add(5); IntArray.Add(4);
TArray<FString> StrArr; StrArr.Emplace(TEXT("Hello")); StrArr.Emplace(TEXT("World"));
TArray<FString> StrArr; FString Arr[] = { TEXT("of"), TEXT("Tomorrow") }; StrArr.Append(Arr, ARRAY_COUNT(Arr));
StrArr.AddUnique(TEXT("!")); StrArr.AddUnique(TEXT("!")); // 这一次添加就没有变化了.
StrArr.Insert(TEXT("Brave"), 0); // 后面的插入位置.
// 若大于当前个数.则会使用元素类型的默认构造函数创建新元素. // 若小于当前个数.则会删除后面的元素. StrArr.SetNum(8);
// 若元素不存在则不会发生改变 StrArr.Remove(TEXT("hello"));
StrArr.Pop();
ValArr.RemoveSingle(30);
// 按条件删除 ValArr.RemoveAll( [](int32 Val) { return Val % 3 == 0; });
// RemoveSwap 删除匹配 // RemoveAtSwap 删除索引位置的 // RemoveAllSwap 按条件删除
ValArr2.Empty();
int32 Count = StrArr.Num();
uint32 ElementSize = StrArr.GetTypeSize();
bool bValidM1 = StrArr.IsValidIndex(-1);
FString ElemEnd = StrArr.Last(); // 最后一个 FString ElemEnd1 = StrArr.Last(1); // 倒数第二个 FString ElemTop = StrArr.Top(); // 第一个
bool bHello = StrArr.Contains(TEXT("Hello"));
// 自定义查询规则 bool bLen5 = StrArr.ContainsByPredicate( [](const FString& Str) {return Str.Len() == 5;} );
int32 Index; StrArr.Find(TEXT("Hello"), Index); int32 LastIndex; StrArr.FindLast(TEXT("Hello"), IndexLast); int32 Index2 = StrArr.Find(TEXT("Hello")); // 直接返回索引.
int32 Index4 = StrArr.IndexOfByPredicate([](const FString& Str) { return Str.Contains(TEXT("r")); });
for(int32 i=0;i<arr.Num();++i) { auto element = arr[i]; }
for (auto val : arr) { //val }
// TArray<FString> arr; for(TArray<FString>::TConstIterator iter = arr.CreateConstIterator(); iter; ++iter) { // *iter; }
StrArr.Sort();
StrArr.Sort( [](const FString& A, const FString& B) { return A.Len() < B.Len(); } );
StrArr.HeapSort( [](const FString& A, const FString& B) { return A.Len() < B.Len(); } );
StrArr.StableSort( [](const FString& A, const FString& B) { return A.Len() < B.Len();} );
// 初始化数组 TArray<int32> HeapArr; for (int32 Val = 10; Val != 0; --Val) HeapArr.Add(Val); // HeapArr == [10,9,8,7,6,5,4,3,2,1] // 建立一个堆(默认小顶堆) HeapArr.Heapify(); // HeapArr == [1,2,4,3,6,5,8,10,7,9] // 往堆里插入一个元素 HeapArr.HeapPush(4); // HeapArr == [1,2,4,3,4,5,8,10,7,9,6] // 弹出堆顶 int32 TopNode; HeapArr.HeapPop(TopNode); // TopNode == 1 // HeapArr == [2,3,4,6,4,5,8,10,7,9] // 按索引移除元素 HeapArr.HeapRemoveAt(1); // HeapArr == [2,4,4,6,9,5,8,10,7] // 获得堆顶元素 int32 Top = HeapArr.HeapTop(); // Top == 2
// 创建 // key比较使用== // hashcode计算使用GetTypeHash TMap<int32, FString> FruitMap; FruitMap.Add(5, TEXT("Banana")); FruitMap.Add(2, TEXT("Grapefruit")); FruitMap.Add(7, TEXT("Pineapple")); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Grapefruit" }, // { Key: 7, Value: "Pineapple" } // ] FruitMap.Add(2, TEXT("Pear")); //相同key值,顶掉value // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" } // ] FruitMap.Add(4);//没有value值,会构造一个默认值进去 // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "" } // ] FruitMap.Emplace(3, TEXT("Orange")); // FruitMap == [ // { Key: 5, Value: "Banana" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "" }, // { Key: 3, Value: "Orange" } // ] TMap<int32, FString> FruitMap2; FruitMap2.Emplace(4, TEXT("Kiwi")); FruitMap2.Emplace(9, TEXT("Melon")); FruitMap2.Emplace(5, TEXT("Mango")); FruitMap.Append(FruitMap2); //已有的会顶掉,没有就完后叠 // FruitMap == [ // { Key: 5, Value: "Mango" }, // { Key: 2, Value: "Pear" }, // { Key: 7, Value: "Pineapple" }, // { Key: 4, Value: "Kiwi" }, // { Key: 3, Value: "Orange" }, // { Key: 9, Value: "Melon" } // ]
FruitMap.Remove(8);
FString Removed7 = FruitMap.FindAndRemoveChecked(7); //查找并移除
FruitMap.Empty();
int32 Count = FruitMap.Num();
FString Val7 = FruitMap[7];
bool bHas7 = FruitMap.Contains(7);
FString* Ptr7 = FruitMap.Find(7); //返回的是value的指针
FString& Ref7 = FruitMap.FindOrAdd(7); //返回的是引用 Ref7 = "Pineapple"; // 修改Value;
FString Val10 = FruitMap.FindRef(7);
const int32* KeyMangoPtr = FruitMap.FindKey(Text("Mango"));
Array<int32> FruitKeys; TArray<FString> FruitValues; FruitMap.GenerateKeyArray(FruitKeys); //生成key、value数组 FruitMap.GenerateValueArray(FruitValues);
FruitMap.KeySort([](int32 A, int32 B) { return A > B; // sort keys in reverse });
FruitMap.ValueSort([](const FString& A, const FString& B) { return A.Len() < B.Len(); // sort strings by length });
// TMap<int32, FString> map; for(TPair<int32,FString>& element : map) { element.Key; element.Value; }
for (TMap<int32, FString>::TConstIterator iter = _map.CreateConstIterator(); iter; ++iter) { iter->Key; iter->Value; }
for(TMap<int32, FString>::TConstIterator iter(map); iter; ++iter) { iter->Key; iter->Value; }
TArray、TMap可以在遍历中删除元素
// TArray for (int32 i = 0;i < mArr.Num (); ++i) { if (mArr [i] == 222) { mArr.RemoveAt (i); } } //TMap for (auto Iter = map1.CreateIterator (); Iter;++ Iter) { if (Iter ->Key == 3) { Iter.RemoveCurrent(); } }
TMultiMap,一个key可以对应多个Value。
TMultiMap<int32, FString> mtMap1; mtMap1.Add(5, TEXT("aaa")); mtMap1.Add(3, TEXT("bbb")); mtMap1.Add(7, TEXT("ccc")); mtMap1.Add(6, TEXT("ddd")); //添加三个相同的key值得键值对 mtMap1.Add(6, TEXT("eee")); mtMap1.Add(6, TEXT("fff")); TArray<FString> values; mtMap1.MultiFind(6, values); //找出所以key为6的value,并丢到values数组中 // values == ["fff","eee","ddd"]
TSet大部分操作与TMap类似。
TSet不能通过[]来访问容器里面的元素!!!
TSet<AActor*> ActorSet = GetSetFromSomewhere(); for (AActor* UniqueActor :ActorSet) { // ... }
TSet<AEnemy*>& EnemySet; for (auto EnemyIterator = EnemySet.CreateIterator(); EnemyIterator; ++EnemyIterator) { // * 运算符获得当前的元素 AEnemy* Enemy = *EnemyIterator; } // 其中迭代器的操作有: // 将迭代器移回一个元素 --EnemyIterator; // 以一定偏移前移或后移迭代器,此处的偏移为一个整数 EnemyIterator += Offset; EnemyIterator -= Offset; // 获得当前元素的索引 int32 Index = EnemyIterator.GetIndex(); // 将迭代器重设为第一个元素 EnemyIterator.Reset();
TSet<int> X; X.Add( 1 ); X.Add( 2 ); X.Add( 3 ); TSet<int> Y; Y.Add( 2 ); Y.Add( 3 ); Y.Add( 4 ); TSet<int> intersection = X.Intersect(Y); // intersection的内容为{2,3}
TSet<int> uni = X.Union(Y); // uni的内容为{1,2,3,4}
虚幻中存在多种字符串类型,如FString、FCHAR*、FNane、FText等。
在此记录一下类型之间的转换。
FString MyString = "Hello"; FName ConvertedFString = FName(*MyString);
// 加个*号即可 TCHAR* MyTchar = *SourceFString;
FString Str = TEXT("str"); FText Text = FText::FromString(Str);
std::string MyString = "Happy"; FString HappyString(MyString.c_str());
FString MyString= "Bunny"; std::string MyStdString(TCHAR_TO_UTF8(*MyString));
FString Name = NameDesc->GetText().ToString();
FString TheString = "1108.1110"; int32 MyStringtoInt = FCString::Atoi(*TheString);
FString TheString = "1108.1110"; float MyStringtoFloat = FCString::Atof(*TheString);
FString NewString = FString::FromInt(YourInt); FString VeryCleanString = FString::SanitizeFloat(YourFloat);
TestHUDString = TestHUDName.ToString();
TestHUDText = FText::FromName(TestHUDName);
UE_LOG(LogTemp, Warning, TEXT("Your message"));
DECLARE_LOG_CATEGORY_EXTERN(CategoryName, Log, All);
DEFINE_LOG_CATEGORY(CategoryName);
//(1) UE_LOG(CategoryName, Warning, TEXT("Problem on load Province Message!")); //(2) UE_LOG(CategoryName, Warning, TEXT("Content:%s"), *(Response->GetContentAsString()));
UE_LOG(CategoryName,Warning,TEXT("MyCharacter's Health is %d"), MyCharacter->Health ); UE_LOG(CategoryName,Warning,TEXT("MyCharacter's Health is %f"), MyCharacter->Health ); UE_LOG(CategoryName,Warning,TEXT("MyCharacter's Name is %s"), *MyCharacter->GetName());
虚幻中的内存管理一般分为两种:
UE智能指针库
UE的智能指针库为C++11智能指针的自定义实现。
实现了行业标准的:共享指针/弱指针/唯一指针。
还可添加 共享引用。此类引用的行为与不可为空的共享指针相同。
UE智能指针类型
共享指针 (TSharedPtr)
共享引用 (TSharedRef)
弱指针 (TWeakPtr)
唯一指针 (TUniquePtr)
列举一些原因:
助手类TSharedFromThis
其注释如下:
/** * Derive your class from TSharedFromThis to enable access to a TSharedRef directly from an object * instance that's already been allocated. Use the optional Mode template argument for thread-safety. */
函数 MakeShared 和 MakeShareable
其注释如下:
/** * MakeShared utility function. Allocates a new ObjectType and reference controller in a single memory block. * Equivalent to std::make_shared. */ template <typename InObjectType, ESPMode InMode = ESPMode::Fast, typename... InArgTypes> FORCEINLINE TSharedRef<InObjectType, InMode> MakeShared(InArgTypes&&... Args); /** * MakeShareable utility function. Wrap object pointers with MakeShareable to allow them to be implicitly * converted to shared pointers! This is useful in assignment operations, or when returning a shared * pointer from a function. */ template< class ObjectType > FORCEINLINE SharedPointerInternals::FRawPtrProxy< ObjectType > MakeShareable( ObjectType* InObject );
函数 StaticCastSharedRef 和 StaticCastSharedPtr
函数 ConstCastSharedRef 和 ConstCastSharedPtr
更多相关信息,参考以下文件:
SharedPointer.h
首先,声明定义一个测试的类:
// 声明一个测试类 class TestA { public: int32 a; float b; };
TSharedPtr(共享指针)用法
void TestTSharedPtr() { //声明 TSharedPtr<TestA> MyTestA; //分配内存 MyTestA = MakeShareable(new TestA()); //先判断智能指针是否有效 if(MyTestA.IsValid()||MyTestA.Get()) { //访问 int32 a = Mytest->a; //复制指针 TSharedPtr<TestA> MyTestA2 = MyTestA; //获得共享指针引用技术 int32 Count = MyTestA.GetSharedReferenceCount(); //销毁对象 MyTestA2.Reset(); } }
TSharedRef(共享引用)用法
void TestTSharedRef() { //声明: TSharedRef<TestA> MyTestA(new TestA)); //访问 int32 a = MyTestA->a; float b = (*MyTest).a; //销毁对象 MyTestA.Reset(); }
TSharedPtr 和 TSharedRef之间的相互转换
void TestSharedRefAndPtr() { //创建原生C++指针 TestA* MyTestARawPtr = new TestA(); //创建共享指针对象 TSharedPtr<TestA> MyTestASharedPtr; //创建共享引用 TSharedRef<TestA> MyTestASharedRef(new TestA); //共享引用转换为共享指针. 支持隐式转换. MyTestASharedPtr = MyTestASharedRef; //普通指针转换为共享指针 MyTestASharedPtr = MakeShareable(MyTestARawPtr); //共享指针转换为共享引用,共享指针不能为空 MyTestASharedRef = MyTestASharedPtr.ToSharedRef(); }
使用TWeakPtr(弱指针)用法
void TestTWeakPtr() { //创建共享指针 TSharedPtr<TestA> TestA_Ptr = MakeShareable(new TestA); //创建共享引用 TSharedRef<TestA> TestA_Ref(new TestA); //声明弱指针 TWeakPtr<TestA> TestA_WeakPtr; //共享指针转弱指针 TWeakPtr<TestA> TestA_WeakPtr1(TestA_Ptr); //共享引用转弱指针 TWeakPtr<TestA> TestA_WeakPtr2(TestA_Ref); //共享指针操作,如赋值 TestA_WeakPtr = TestA_WeakPtr1; TestA_WeakPtr = TestA_WeakPtr2; //使用完弱指针可以重置为空. TestA_WeakPtr = nullptr; //弱指针转换为共享指针 TSharedPtr<TestA> NewTestA_Ptr(TestA_WeakPtr.Pin()); if(NewTestA_Ptr.IsValid()||NewTestA_Ptr.Get()) { // 访问指针成员 NewTestA_Ptr->a; } }
C#中的委托delegate的定义:
简单来说:
UE建立了自己的一套代理绑定实现了在不知道具体类的情况下也能回调。
这种方式使得架构更加清晰,不用到处获取实例。
同时该方式解决很多耦合架构,代理的方式有很多。
要了解UE中的不同代理使用,比如封装嵌套,实现解耦合操作。
其中需要使用UE特别的封装,包括了:
UE提供的宏定义在DelegateCombinations.h文件中。
Delegate大致的使用流程:
单播委托宏定义,主要包括以下几个:
/*Single-cast delegate declaration. No parameters*/ DECLARE_DELEGATE(MyDelegate) /*Single-cast delegate declaration. One parameter*/ DECLARE_DELEGATE_OneParam(MyDelegate, paramtype1) /*Single-cast delegate declartion. Two parameters*/ DECLARE_DELEGATE_TwoParams(MyDelegate, paramtype1, paramtype2) /*Single-cast delegate declartion. Three parameters*/ DECLARE_DELEGATE_ThreeParams(MyDelegate, paramtype1, paramtype2, paramtype3) /*Single-cast delegate declartion. Four parameters*/ DECLARE_DELEGATE_FourParams(MyDelegate, paramtype1, paramtype2, paramtype3, paramtype4) /*Single-cast delegate declaration. One parameter with return value */ DECLARE_DELEGATE_RetVal_OneParam(RetType, MyDelegate, paramtype1) ...
绑定方法非常多,针对不同的对象类型,需要使用到不同的Bind方法。
比如单播委托主要包括以下几个:
/* Binds to an existing delegate object. */ Bind() /* Binds a raw C++ pointer global function delegate. */ BindStatic() /* Binds a raw C++ pointer delegate. Raw pointer does not use any sort of reference, * so may be unsafe to call if the object was deleted out from underneath your delegate. * Be careful when calling Execute()! */ BindRaw() /* Binds a shared pointer-based member function delegate. * Shared pointer delegates keep a weak reference to your object. * You can use ExecuteIfBound() to call them. */ BindSP() /* Binds a UObject-based member function delegate. * UObject delegates keep a weak reference to your object. * You can use ExecuteIfBound() to call them. */ BindUObject() /* Binds a UFunction member function delegate. */ BindUFuntion() /* Unbinds this delegate. */ UnBind()
调用的方式比较简单,直接使用代理实例进行调用即可。
注意不同类型的稍有区别。
// 无参数 MyDelegateMemVar.Execute() // 有参数 MyDelegateMemVar.Execute(Param1, Param2 ...) // 最好在Execute前确认下是否绑定了方法 MyDelegateMemVar.IsBound() // 还有一个方法: ExecuteIfBound()
类型整理:
单播委托
多播委托
动态委托
可序列化;
其函数可以按照命名查找,但执行速度比常规委托慢。
动态及动态多播委托的声明宏结尾必须要有分号,所以建议给所有委托都加分号,这样可以统一样式
动态委托变量可以作为函数参数,可在蓝图调用的同时绑定一个委托。
使用场景总结: