虚幻引擎编程基础(一)

2021/6/13 12:21:39

本文主要是介绍虚幻引擎编程基础(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

虚幻引擎编程基础(一)

文章目录

  • 虚幻引擎编程基础(一)
    • 一、前言
    • 二、容器
      • 2.1 TArray
        • 2.1.1 创建动态数组容器
        • 2.1.2 增加元素
        • 2.1.3 删除元素
        • 2.1.4 查询
        • 2.1.5 遍历
        • 2.1.6 排序
      • 2.2 TMap
        • 2.2.1 增加元素
        • 2.2.2 删除元素
        • 2.2.3 查询
        • 2.2.4 排序
        • 2.2.5 遍历
        • 2.2.6 **TMultiMap**
      • 2.3 TSet
        • 2.3.1 添加元素
        • 2.3.2 删除元素
        • 2.3.3 查询
        • 2.3.4 遍历
        • 2.3.5 两个集合的操作
    • 三、字符串转换
      • 3.1 FString 转 FName
      • 3.2 FString 转 TCHAR*
      • 3.3 FString 转 FText
      • 3.4 std::string 转 FString
      • 3.5 FString 转 std::string
      • 3.6 FText 转 FString
      • 3.7 FText 转 FName
      • 3.8 FString 转 Integer
      • 3.9 FString 转 Float
      • 3.10 Float/Int 转 FString
      • 3.11 FName 转 FString
      • 3.12 FName 转 FText
    • 四、打印Log
      • 4.1 快速使用
      • 4.2 定义Log仓库使用
    • 五、智能指针
      • 5.1 UE智能指针
      • 5 .2 为什么使用UE智能指针库
      • 5.3 如何使用UE智能指针
        • 5.3.1 助手类和函数
        • 5.3.2 使用测试
    • 六、代理委托
      • 6.1 什么是代理委托
      • 6.2 UE中的Delegate
        • 6.2.1 Delegate宏
        • 6.2.2 委托的绑定(注册或者关联)
        • 6.2.3 调用代理实例管理的方法
        • 6.2.4 小结
    • 参考博文

一、前言

笔者整理了自己在虚幻编程中经常需要使用的数据结构、或者一些具体的使用方案。

包括了容器、字符串的转换、智能指针、宏、委托、Log等。

无论是在喜欢虚幻中进行客户端开发、编辑器扩展、图形开发等,都需要掌握这些基础内容。

二、容器

虚幻引擎实现了一套数据结构容器,并不推荐使用诸如 stl 的容器。

本小节主要介绍UE中容器的使用。主要涉及了增删改查以及遍历等操作。

UE中的主要容器有:

  • TArray,线性容器,和std::vector功能一致。
  • TMap,非线性容器,和std::map功能一致。
  • TSet,非线性容器,和std::set功能一致。

2.1 TArray

2.1.1 创建动态数组容器

TArray<int32> IntArray;

2.1.2 增加元素

  • Init方法,初始化。
TArray<int32> IntArray;
// 初始化数组为5个10. [10,10,10,10,10]
IntArray.Init(10,5);
  • Add方法,会引入临时变量,优先使用Emplace方法。
TArray<int32> IntArray;
IntArray.Add(5);
IntArray.Add(4);
  • Emplace方法,构造方法
TArray<FString> StrArr;
StrArr.Emplace(TEXT("Hello"));
StrArr.Emplace(TEXT("World"));
  • Append方法,追加多个元素
TArray<FString> StrArr;
FString Arr[] = { TEXT("of"), TEXT("Tomorrow") };
StrArr.Append(Arr, ARRAY_COUNT(Arr));
  • AddUnique方法,只有容器中不存在该元素才添加
StrArr.AddUnique(TEXT("!"));
StrArr.AddUnique(TEXT("!")); // 这一次添加就没有变化了.
  • Insert方法,插入新元素
StrArr.Insert(TEXT("Brave"), 0); // 后面的插入位置.
  • SetNum方法,直接设置数组的元素个数
// 若大于当前个数.则会使用元素类型的默认构造函数创建新元素.
// 若小于当前个数.则会删除后面的元素.
StrArr.SetNum(8);

2.1.3 删除元素

  • Remove方法,删除所有匹配的元素。
// 若元素不存在则不会发生改变
StrArr.Remove(TEXT("hello"));
  • Pop方法,删除最后一个元素
StrArr.Pop();
  • RemoveSingle方法,删除第一个匹配的元素。
ValArr.RemoveSingle(30);
  • RemoveAll方法,按条件删除。
// 按条件删除
ValArr.RemoveAll(
    [](int32 Val) {
        return Val % 3 == 0;
    });
  • RemoveSwap / RemoveAtSwap / RemoveAllSwap方法,交换到末尾再删除,使用的条件是删除之后数组无顺序要求。效率更高!
// RemoveSwap 删除匹配
// RemoveAtSwap 删除索引位置的
// RemoveAllSwap 按条件删除
  • Empty方法,清空数组。
ValArr2.Empty();

2.1.4 查询

  • Num方法,查询数组大小。
int32 Count = StrArr.Num();
  • GetTypeSize方法,查询一个元素大小。
uint32 ElementSize = StrArr.GetTypeSize();
  • IsValidIndex方法,判断索引是否合法。
bool bValidM1 = StrArr.IsValidIndex(-1);
  • Last / Top 查询
FString ElemEnd = StrArr.Last(); // 最后一个
FString ElemEnd1 = StrArr.Last(1); // 倒数第二个
FString ElemTop = StrArr.Top(); // 第一个
  • Contains方法,是否存在某个元素
bool bHello = StrArr.Contains(TEXT("Hello"));	
  • ContainsByPredicate方法,自定义查询规则
// 自定义查询规则
bool bLen5 = StrArr.ContainsByPredicate(
    [](const FString& Str) {return Str.Len() == 5;}
);
  • Find/ FindLast 查找,找到了返回索引,找不到会返回INDEX_NONE。
int32 Index;
StrArr.Find(TEXT("Hello"), Index);
int32 LastIndex;
StrArr.FindLast(TEXT("Hello"), IndexLast);
int32 Index2 = StrArr.Find(TEXT("Hello"));  // 直接返回索引.
  • FindByPredicate 按自定義条件查找,返回索引。
int32 Index4 = StrArr.IndexOfByPredicate([](const FString& Str) {
    return Str.Contains(TEXT("r"));
});

2.1.5 遍历

  • 按照索引遍历
for(int32 i=0;i<arr.Num();++i)
{
    auto element = arr[i];
}
  • 范围For循环
for (auto val : arr)
{
     //val
}
  • 迭代器遍历
// TArray<FString> arr;
for(TArray<FString>::TConstIterator iter = arr.CreateConstIterator(); iter; ++iter)
{
    // *iter;
}

2.1.6 排序

  • 快排(不稳定),默认按照operator <
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堆排序
// 初始化数组
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

2.2 TMap

2.2.1 增加元素

  • Add / Emplace / Append
// 创建
// 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" }
// ]

2.2.2 删除元素

  • 通过key直接删除k-v对。
FruitMap.Remove(8);
  • 查找并移除,不存在,运行时崩溃。
FString Removed7 = FruitMap.FindAndRemoveChecked(7); //查找并移除
  • Empty清空
FruitMap.Empty();

2.2.3 查询

  • 查询 Map的大小
int32 Count = FruitMap.Num();
  • **[]**通过Key获得Value,注意没有对应的会造成崩溃!
FString Val7 = FruitMap[7];
  • Contains,查询是否存在。
bool bHas7 = FruitMap.Contains(7);
  • Find查找value,返回的是指针
FString* Ptr7 = FruitMap.Find(7); //返回的是value的指针
  • FindOrAdd 查找或添加,返回的是引用.。不存在则构造一个添加进去,返回引用。
FString& Ref7 = FruitMap.FindOrAdd(7); //返回的是引用
Ref7 = "Pineapple"; // 修改Value;
  • FindRef 查找返回引用,不存在则构造一个。
FString Val10 = FruitMap.FindRef(7);
  • FindKey,通过Value查找key。
const int32* KeyMangoPtr = FruitMap.FindKey(Text("Mango"));
  • 生成Key/Value数组
Array<int32> FruitKeys;
TArray<FString> FruitValues;
FruitMap.GenerateKeyArray(FruitKeys); 		//生成key、value数组
FruitMap.GenerateValueArray(FruitValues);

2.2.4 排序

  • KeySort,按照key来排序
FruitMap.KeySort([](int32 A, int32 B) {
    return A > B; // sort keys in reverse
});
  • ValueSort,按照value来排序
FruitMap.ValueSort([](const FString& A, const FString& B) {
    return A.Len() < B.Len(); // sort strings by length
});

2.2.5 遍历

  • 范围For循环
// TMap<int32, FString> map;
for(TPair<int32,FString>& element : map)
{
    element.Key;
    element.Value;
}
  • 迭代器遍历1
for (TMap<int32, FString>::TConstIterator iter = _map.CreateConstIterator(); iter; ++iter)
{
    iter->Key;
    iter->Value;
}
  • 迭代器遍历2
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();
    }
}

2.2.6 TMultiMap

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"]

2.3 TSet

TSet大部分操作与TMap类似。

2.3.1 添加元素

  • Add / Emplace 添加元素

2.3.2 删除元素

  • Remove 删除元素
  • Empty 清空TSet

2.3.3 查询

2.3.4 遍历

TSet不能通过[]来访问容器里面的元素!!!

  • 范围for循环
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();

2.3.5 两个集合的操作

  • 交集 Intersect
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}
  • 并集 Union
TSet<int> uni = X.Union(Y); // uni的内容为{1,2,3,4}

三、字符串转换

虚幻中存在多种字符串类型,如FString、FCHAR*、FNane、FText等。

在此记录一下类型之间的转换。

3.1 FString 转 FName

FString MyString = "Hello";
FName ConvertedFString = FName(*MyString);	

3.2 FString 转 TCHAR*

// 加个*号即可
TCHAR* MyTchar = *SourceFString;

3.3 FString 转 FText

FString Str = TEXT("str");
FText Text = FText::FromString(Str);

3.4 std::string 转 FString

std::string MyString = "Happy";
FString HappyString(MyString.c_str());

3.5 FString 转 std::string

FString MyString= "Bunny";
std::string MyStdString(TCHAR_TO_UTF8(*MyString));

3.6 FText 转 FString

FString Name = NameDesc->GetText().ToString();

3.7 FText 转 FName

  • 没有直接方法,先转换成FString,再转换成FName

3.8 FString 转 Integer

FString TheString = "1108.1110";
int32 MyStringtoInt = FCString::Atoi(*TheString);

3.9 FString 转 Float

FString TheString = "1108.1110";
float MyStringtoFloat = FCString::Atof(*TheString);

3.10 Float/Int 转 FString

FString NewString = FString::FromInt(YourInt);
FString VeryCleanString = FString::SanitizeFloat(YourFloat);

3.11 FName 转 FString

TestHUDString = TestHUDName.ToString();

3.12 FName 转 FText

TestHUDText = FText::FromName(TestHUDName);

四、打印Log

4.1 快速使用

UE_LOG(LogTemp, Warning, TEXT("Your message"));

4.2 定义Log仓库使用

  1. 在.h 文件中,填写如下代码,声明一个Log库类别
DECLARE_LOG_CATEGORY_EXTERN(CategoryName, Log, All);
  1. 在.cpp文件中,定义实现
DEFINE_LOG_CATEGORY(CategoryName);
  1. LOG的输出格式如下:
//(1)
UE_LOG(CategoryName, Warning, TEXT("Problem on load Province Message!"));
//(2)
UE_LOG(CategoryName, Warning, TEXT("Content:%s"), *(Response->GetContentAsString()));
  1. 带格式类型的输出:
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());

五、智能指针

虚幻中的内存管理一般分为两种:

  1. 自动垃圾回收(针对是所有继承UObject的类);
  2. 虚幻的智能指针功能(对于原生的C++类一般用智能指针);

5.1 UE智能指针

UE智能指针库

  • UE的智能指针库为C++11智能指针的自定义实现。

  • 实现了行业标准的:共享指针/弱指针/唯一指针

  • 还可添加 共享引用。此类引用的行为与不可为空的共享指针相同。

UE智能指针类型

共享指针 (TSharedPtr)

  • 共享指针拥有其引用的对象。防止对象被析构,并在无引用之时处理对象的删除。
  • 共享指针可以为,意味着不引用对象。

共享引用 (TSharedRef)

  • 共享引用与共享指针行为相似。
  • 但共享引用须固定引用非空对象,因此共享引用可以转为共享指针。

弱指针 (TWeakPtr)

  • 弱指针类与共享指针类似,但不拥有引用对象。因此不会改变其生命周期,此属性中断引用循环。
  • 弱指针可在无预警的情况下随时变为空

唯一指针 (TUniquePtr)

  • 唯一指针仅有一个唯一的指针指向引用的对象。
  • 可以转移所有权,但无法共享。

5 .2 为什么使用UE智能指针库

列举一些原因:

  • std::shared_ptr不是在所有的平台都能用的;
  • 可以和其它虚幻容器以及类型无缝协作;
  • 更好的控制平台特性,包括线程处理和优化;
  • 可以像常规C++指针那样复制/解引用/比较共享指针等;
  • 防止内存泄漏;
  • 可选择的线程安全,虚幻智能指针库包括了线程安全的代码,可跨线程管理引用计数;
  • 可以创建任何类型的对象的共享指针;

5.3 如何使用UE智能指针

5.3.1 助手类和函数

助手类TSharedFromThis

  • 继承这个类会添加 AsShared 函数;
  • 通过这个函数返回一个TSharedRef;

其注释如下:

/**
* 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.
*/

函数 MakeSharedMakeShareable

  • 通过原生C++指针中创建共享指针
  • 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 );

函数 StaticCastSharedRefStaticCastSharedPtr

  • downcast to a derived type,向下转型为派生类型;

函数 ConstCastSharedRefConstCastSharedPtr

  • const 智能引用或智能指针分别转换为 mutable 智能引用或智能指针

在这里插入图片描述

更多相关信息,参考以下文件:

SharedPointer.h

5.3.2 使用测试

首先,声明定义一个测试的类:

// 声明一个测试类
class TestA
{
public:
    int32 a;
    float  b;
};

TSharedPtr(共享指针)用法

  • 共享指针可以置为空"NULL"
  • 在访问共享指针的时候要先判断一下这个共享指针是否有效。如果无效直接访问,将会导致程序崩溃。
  • MyTestA.IsValid()中**".“是访问共享指针的成员**,而MyTestA->a中”->"是访问这个指针指向的对象中的成员
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;
    }
}

六、代理委托

6.1 什么是代理委托

C#中的委托delegate的定义:

  • 委托是一种引用类型,表示对具有特定参数列表返回类型方法的引用。
    • 可以通过委托实例 注册(关联) 任何兼具签名和返回类型的方法;
    • 并通过委托实例 调用注册或关联的方法;

简单来说:

  • Delegate实现的就是将 一个方法(静态方法/成员函数等) 传递给其他类;
  • 其实本质上就是C++中的函数指针;
  • 常用于解耦不同对象之间的关联。委托的触发者不与监听者有直接关联。二者通过委托对象建立联系。

6.2 UE中的Delegate

UE建立了自己的一套代理绑定实现了在不知道具体类的情况下也能回调。

这种方式使得架构更加清晰,不用到处获取实例。

同时该方式解决很多耦合架构,代理的方式有很多。

要了解UE中的不同代理使用,比如封装嵌套,实现解耦合操作。

其中需要使用UE特别的封装,包括了:

  • 使用 Delegate宏 声明代理,需要根据不同函数签名(参数和返回值)来选择对应的宏;
  • UE中Delegate的分类。有如单播委托/多播委托/动态委托。需要了解每种代理的声明,绑定以及调用。

6.2.1 Delegate宏

UE提供的宏定义在DelegateCombinations.h文件中。

Delegate大致的使用流程:

  1. 使用DECLARE_*宏声明一个自定义delegate类型FDelegateXXX;
  2. 声明(定义)一个FDelegateXXX类型的代理对象;
  3. 绑定需要执行的函数指针到代理对象上;
  4. 触发代理对象中的函数指针会立即执行;
  5. 不需要某个函数指针时,可将其从代理对象中解绑;

单播委托宏定义,主要包括以下几个:

  • 支持无参到最多四个参数以及有没有返回值。
/*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)

...

6.2.2 委托的绑定(注册或者关联)

绑定方法非常多,针对不同的对象类型,需要使用到不同的Bind方法。

  • 直接Bind一个已经存在的Delegate;
  • BindRaw则是绑定到原生的C++指针指向的方法;
  • BindUObject则是绑定继承UObject类型的成员方法;
  • 如果这个成员方法是UFUNCTION,则可以用BindUFucntion来绑定;
  • 如果是TSharedPtr(智能指针)则需要用BindSp来绑定;

比如单播委托主要包括以下几个:

/* 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()

6.2.3 调用代理实例管理的方法

调用的方式比较简单,直接使用代理实例进行调用即可。

注意不同类型的稍有区别。

// 无参数
MyDelegateMemVar.Execute()
// 有参数
MyDelegateMemVar.Execute(Param1, Param2 ...)

// 最好在Execute前确认下是否绑定了方法
MyDelegateMemVar.IsBound()

// 还有一个方法:
ExecuteIfBound()

6.2.4 小结

类型整理

  • 单播委托

    • 有且只能绑定一个函数;
    • 可以绑定一个返回值或返回值的函数;
  • 多播委托

    • 可以绑定多个函数,但不能有返回值
    • 功能类似于单播委托;
  • 动态委托

    • 可序列化;

    • 其函数可以按照命名查找,但执行速度比常规委托慢。

    • 动态及动态多播委托的声明宏结尾必须要有分号,所以建议给所有委托都加分号,这样可以统一样式

    • 动态委托变量可以作为函数参数,可在蓝图调用的同时绑定一个委托

在这里插入图片描述

  • 动态委托变量可以作为函数参数,可在蓝图调用的同时绑定一个委托

在这里插入图片描述
在这里插入图片描述
使用场景总结

  • 单播/多播委托:
    • 一般就是函数的回调,支持的个数不同;
  • 动态委托:
    • 允许动态绑定,可序列化,也就是说可以在蓝图中使用;
    • 动态委托作为变量在函数中对委托调用;而委托的绑定可以在蓝图中进行;
  • 动态多播委托:
    • 蓝图中的事件调度器(EventDispatcher);
    • 可以用其在C++和蓝图中绑定和调用委托;

参考博文

  • UE4容器TArray/TMap的使用
  • TMap的使用
  • UE4 TSet
  • UE4中的集合:TSet容器
  • UE4字符串相关转换
  • UE断言总结
  • UE_LOG
  • 官方文档-虚幻智能指针库
  • 虚幻4:智能指针基础
  • UE4智能指针学习记录
  • UE4智能指针
  • 虚幻4(Unreal Engine4)中的代理委托Delegate的使用方法
  • UE4各种Delegate区别与应用
  • 全面理解UE4委托


这篇关于虚幻引擎编程基础(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程