在本篇日志中,我将会介绍游戏内最基础的物品:“星尘”数据类的实现,其中包括UCLASS,USTRUC的实现,以及FName,FStriing,FText的区别和转换,TArray,TMap的使用,UENUM的实现,TSharedPtr的实现,数据表格DataTable的读取
目录
1.字符串相关数据类型:FString、FText、FName
一、实现一个C++类
1.类的创建
我们在UE5的"内容侧滑菜单"中,在右侧空白中右键选择"新建C++类",然后可以选择一个想要的父类,这里我们要实现的是我们这款工厂游戏里的基础物品:星尘,所以选择适用于存储游戏中的各种数据的Object类,该类的特点是不能被拖入场景
在"类的类型"中选择公共,使其能够被其他模块调用
2.在.h文件中声明变量
在VS中打开我们刚刚生成的.h文件,"#pragma once"表示这个头文件不会被重复包含,"第三个头文件是必须最后一个编译的头文件,所以如果要新添头文件,必须放在这个头文件前面。
#pragma once
#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Stardust.generated.h"
接着在下面可以看到一个"UCLASS"宏,表明接下来所声明的类将会暴露给UE5的反射系统。
"Blueprintable"表示这个c++类可以派生出蓝图类。
"GENERATED_BODY()"会为这个类提供一些基础也是必要的功能,所有UCLASS都必须有这个宏,且这个宏要放在类内的开头
UCLASS(Blueprintable)
class ASTROMUTATE_2_API UStardust : public UObject
{
GENERATED_BODY()
public:
};
接下来我们在Stardust.h中创建一个结构体来存储物品的各个属性,在结构提前用USTRUCT宏将该结构体暴露给反射,
暴露给反射系统的结构体名称必须以F开头,在UE编辑器中该结构体的名称将不包含首字母F,且会自动分割首字母大写的单词。
在结构体的第一行同样须要使用"GENERATED_BODY()"。
USTRUCT(BlueprintType)
struct FStardustDataTable : public FTableRowBase
{//星尘数据
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CommonInfo")
FName StardustName{ "Empty" };//名称
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CommonInfo")
FText Description;//描述
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CommonInfo")
FName StardustId{ "Empty" };//编号
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReactionInfo")
EStardustClanEnum StardustClan{ EStardustClanEnum::Empty };//族系
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReactionInfo")
int StardustRarity{ 0 };//稀有度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReactionInfo")
EStardustElement StardustElement{ EStardustElement::Empty };//属性
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReactionInfo")
bool StardustCanReactWithItselve{ false };//能否自我反应
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ReactionInfo")
int StardustValue{ 0 };//价值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "InventoryInfo")
int StardustStackLimit{ 0 };//堆叠上限
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "InventoryInfo")
bool StardustStackable{ false };//能否堆叠
FStardustDataTable() = default;
};
(上图中的数据具体有什么用,我们后面的文章再具体介绍)
"BlueprintType"表示该结构体可以作为蓝图中的一种变量类型。
要声明暴露给反射系统的变量,需要使用"UPROPERTY"宏。
"EditAnywhere"表示该变量在编辑器中的默认设置和实例化细节面板中都可以被编辑,"Edit"也可以替换成"Visible",表示该变量只读,如果不写这一条,即该变量在编辑器中完全不可见。
而"Anywhere"可以替换成"DefaultsOnly",表示只在默认设置中可写或只读,如果是在结构中创建的变量,就在蓝图中获取并选择该结构体,然后在右边的"默认值"中就可以看到我们在结构体声明的变量:
(图中的数值为默认构造函数生成的数值)
"Anywhere"还可以被替换成"InstanceOnly",表示该变量在该类实例化到场景中时,可以点击实例化出的对象,在右侧"细节"面板中看到,前提是该变量可被实例化到场景中。
"BlueprintReadWrite"表示该变量在蓝图中可以获取和设置,可将"ReadWrite"替换为"ReadOnly"表示该变量在蓝图中可获取。
"Category"可以设置该变量所处的目录,在时间图表中双击右键时会看到我们划定的目录,在使用该结构体的表格中也可看到。
二.一些虚幻定义的常用数据类型
1.字符串相关数据类型:FString、FText、FName
FString是最接近std::string的类型,字符串本身可以看做一个存储char型的动态数组,也可以使用'+'来进行字符串连接
FName是不可修改的字符串类型,其会在定义时生成一个哈希值,适合作为TMap的键
FText最主要的功能是向玩家展示不同的语言包括中文,可以在DataTable中进行设置
UPROPERTY(EditAnywhere,BlueprintReadWrite)
FString S1 = "a";
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FName S2 = "b";
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FText S3 = FText::FromString("c");//FText无法直接用字符串赋值
(三种字符串在蓝图中显示的颜色是不同的)
三种数据类型是可以互相转化的,这里我们统一使用FString作为跳板:
S1 = S3.ToString();//FText向FString转化
S1 = S2.ToString();//FName向FString转化
S2 = FName(S1);//FString向FName转化
S3 = FText::FromString(S1);//FString向FText转化
FString和std::string之间也是可以相互转化的:
S4 = TCHAR_TO_UTF8(*S1);//FString向std::string转化
S1 = UTF8_TO_TCHAR(S4.c_str());//std::string向FString转化
2.容器:TArray,TMap
TArray是一种动态数组,类似于std::array或std::vector,其拥有严格的边界检查,下面是其常用的增删改查方法:
TArray<int>Array;
Array.Add(3);//添加一个元素到末尾
Array[0] = 5;//可以进行随机访问
auto x = Array.FindByPredicate([](const int& x) {return x > 3; });//返回找到的第一个符合条件的的值的指针,没找到返回nullptr
UE_LOG(LogTemp, Warning, TEXT("%d"), *x);
Array.RemoveAt(0);//删除某个索引值对应的元素
TMap是一个哈希表,类似于std::unordered_map,其提供了针对例如FString的UE特殊数据类型的哈希函数,使用方法如下:
TMap<FString, int>Map;
Map.Add("A", 1);//在哈希表中添加一个键值对
//Map["B"] = 2;//注意TMap不能像std::unordered_map一样自动创建一个"B",2的键值对,而是会报错
if(Map.Contains("A"))
{
UE_LOG(LogTemp, Warning, TEXT("warning:%d"), Map["A"]);//注意在用键求值是要先查询是否存在
Map["A"] = 2;
}
Map.Remove("A");//移除哈希表中对应的元素
3.枚举UENUM:
虚幻中枚举的声明前需要加入UENUM宏来暴露给反射,"BlueprintType"说明符表示该枚举可以作为编辑器中的一个变量类型,枚举的名称的第一个字母必须为E,枚举体内可以使用UMETA宏定义一些枚举值的属性,比如DisplayName可以改变其在编辑器中显示的名称,
UENUM(BlueprintType)
enum class EStardustElement:uint8//属性
{
Empty UMETA(DisplayName = "Empty(0)"),
Frost UMETA(DisplayName = "Frost(1)"),
Fire UMETA(DisplayName = " Fire(2)"),
Storm UMETA(DisplayName = "Storm(3)"),
Dust UMETA(DisplayName = "Dust(4)"),
Gravity UMETA(DisplayName = "Gravity(5)"),
Mega UMETA(DisplayName = "Mega(6)"),
};
4.智能指针:TSharedPtr<>
UEC++提供了类似于std::shared_ptr的智能指针TShardPtr,同样采用引用计数来控制对象的生命周期,能够适配其他的虚幻数据结构包括容器,但不能暴露给蓝图,可以像普通的指针一样使用"->"和"*",使用TSharedPtr<类名>来声明,使用MakeShared<类名>(变量名)来实例化
三.虚幻中数据表格的实现和读取
使用虚幻中的数据表格可以方便的进行数值调整,要在数据表格中包含一个结构体,需要使这个结构体继承"FTavleRowBase",然后就可以在创建表格时选择该结构体作为表格中的数据,接下来介绍一下怎么读取数据表格中的数据,这里的或者物品数据使全局通用的,所以我们将其存入全局单例GameInstance里面,首先创建一个继承自"GameInstance"的类"AstromutateGameInstance"
我们在其中定义好我们要存储物品数据的数据结构,这里用的是一个哈希表,然后声明加载表格的函数
UCLASS()
class ASTROMUTATE_2_API UAstromutateGameInstance : public UGameInstance
{
GENERATED_BODY()
UAstromutateGameInstance();
public:
std::unordered_map<std::string, TSharedPtr<FStardustDataTable>>StardustMap;//星尘数据表
void LoadStardustTable();//从表格中读取数据
};
在.cpp中实现函数:
void UAstromutateGameInstance::LoadStardustTable()
{
UDataTable* StardustTablePointer = LoadObject<UDataTable>(nullptr, UTF8_TO_TCHAR("DataTable'/Game/Data/StardustInformation.StardustInformation'"));//动态记载类,第一个参数为要加载的类所处的环境,第二个参数为类名,TCHAR*类型
StardustMap.clear();
if (StardustTablePointer == nullptr)
{//没找到表格
UE_LOG(LogTemp, Error, TEXT("Can't find StardustInformation"));
return;
}
TArray<FName> RowNames {StardustTablePointer->GetRowNames()};//将所有星尘的名字储存进数组里
for (const auto& it : RowNames)
{
FString ContextString;
FStardustDataTable* Row = StardustTablePointer->FindRow<FStardustDataTable>(it, ContextString);//获取对应名字这一行的信息
StardustMap[TCHAR_TO_UTF8(*Row->StardustId.ToString())] = MakeShared<FStardustDataTable>(temp);//放入map中
}
UE_LOG(LogTemp, Warning, TEXT("StardustInfoMap Loaded %llu Stardust"), StardustMap.size());//输出加载了多少个星尘数据
}
下一篇文章将会介绍一个基础的库存组件的实现