编程语言
首页 > 编程语言> > 虚幻引擎编程基础(一)

虚幻引擎编程基础(一)

作者:互联网

虚幻引擎编程基础(一)

文章目录

一、前言

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

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

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

二、容器

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

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

UE中的主要容器有:

2.1 TArray

2.1.1 创建动态数组容器

TArray<int32> IntArray;

2.1.2 增加元素

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);

2.1.3 删除元素

// 若元素不存在则不会发生改变
StrArr.Remove(TEXT("hello"));
StrArr.Pop();
ValArr.RemoveSingle(30);
// 按条件删除
ValArr.RemoveAll(
    [](int32 Val) {
        return Val % 3 == 0;
    });
// RemoveSwap 删除匹配
// RemoveAtSwap 删除索引位置的
// RemoveAllSwap 按条件删除
ValArr2.Empty();

2.1.4 查询

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"));
});

2.1.5 遍历

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;
}

2.1.6 排序

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

2.2 TMap

2.2.1 增加元素

// 创建
// 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 删除元素

FruitMap.Remove(8);
FString Removed7 = FruitMap.FindAndRemoveChecked(7); //查找并移除
FruitMap.Empty();

2.2.3 查询

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);

2.2.4 排序

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
});

2.2.5 遍历

// 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();
    }
}

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 添加元素

2.3.2 删除元素

2.3.3 查询

2.3.4 遍历

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();

2.3.5 两个集合的操作

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等。

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

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

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智能指针类型

共享指针 (TSharedPtr)

共享引用 (TSharedRef)

弱指针 (TWeakPtr)

唯一指针 (TUniquePtr)

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

列举一些原因:

5.3 如何使用UE智能指针

5.3.1 助手类和函数

助手类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.
*/

函数 MakeSharedMakeShareable

其注释如下:

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

函数 ConstCastSharedRefConstCastSharedPtr

在这里插入图片描述

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

SharedPointer.h

5.3.2 使用测试

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

// 声明一个测试类
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;
    }
}

六、代理委托

6.1 什么是代理委托

C#中的委托delegate的定义:

简单来说:

6.2 UE中的Delegate

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

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

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

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

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

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方法。

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

/* 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 小结

类型整理

在这里插入图片描述

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

参考博文

标签:TEXT,编程,StrArr,Value,引擎,Key,虚幻,FString,指针
来源: https://blog.csdn.net/qjh5606/article/details/117872196