前言

那么,开始重点部分吧。

——qswwltn


那么,先简单的说下基本的过程,方便使用其他方法的来查看是不是有自己需要的内容,我使用的办法是使用JSON格式来读取数据表,这是因为JSON这个格式方便传输数据和储存数据,以后要改的话可以直接套过去就行了,读取JSON格式的数据表后使用UWorld类型中SpawnActor()函数来创建一个Actor之后保存到一个TArray数组中方便之后使用,同样的,本文中除非必要,不会具体的解释基础知识。

所以,如果不是很了解JSON格式的话,我这边推荐可以去自己看下JSON的格式就行了。现在,开始重点内容吧。

操作数据表(读取)


在上章里已经创建好了数据表并在人物的构造函数中引入了数据表蓝图

AUear_Player::AUear_Player()
{	//获取数据表
	static ConstructorHelpers::FObjectFinder ArmsTable_BP(TEXT("DataTable'/Game/Arms/ArmsInfo_BP.ArmsInfo_BP'"));
	if (ArmsTable_BP.Succeeded()) {
		ArmsTable = ArmsTable_BP.Object;
	}
}

UDataTable类中读取全部数据的方法主要有4种

//返回CSV格式的字符串
FString GetTableAsCSV
(
    const EDataTableExportFlags InDTExportFlags
) const

//返回JSON格式的字符串
FString GetTableAsJSON
(
    const EDataTableExportFlags InDTExportFlags
) const

//返回一个字符串,这个字符串中会直接包含全部的数据
//比如:"Name,Sex,Class"
FString GetTableAsString
(
    const EDataTableExportFlags InDTExportFlags
) const

//返回一个二维的TArray数组
TArray < TArray < FString > > GetTableData
(
    const EDataTableExportFlags InDTExportFlags
) const

这边如果推荐新手使用的话我是比较推荐使用GetTableData()这个函数来获取一个二维的TArray数组,这个的使用方式比较接近于C/C++的二维数组,对这个的使用可能会更加好用点。同时,最不推荐使用GetTableAsString()这个函数,没有具体的数据格式处理会非常麻烦,不过如果有需求的话也是可以使用的。

这4个函数的参数都是EDataTableExportFlags,这个参数是选择导出的格式

enum EDataTableExportFlags
{
    //没有特定的选择
    None                        = 0,
         
    //将导出文本中嵌套的文本转换成JSONObject
    UseJsonObjectsForStructs    = 1 << 0,
  
    //将导出的文本当成字符串来处理
    UseSimpleText               = 1 << 1,
}

这里可以直接使用EDataTableExportFlags::None或者EDataTableExportFlags::UseJsonObjectForStructs就行了。比如这样:

//添加武器
FString ArmsInfo_Json = ArmsTable->GetTableAsJSON(EDataTableExportFlags::UseJsonObjectsForStructs);

现在,稍微打个岔,先来讲解下FJsonValue和FJsonObject的区别,简单的来讲,FJsonValue包括了FJsonObject,FJsonValue可以解析出FJsonObject对象,也就是上面那个EDataTableExportFlags::UseJsonObjectsForStructs参数的用处。

这边推荐把接下来的一些内容写成一个数据表的基类,因为基本全部数据表都是要使用这个。

TArray<TSharedPtr> UTableBeas::GetJsonObject(FString JsonValue) {

	//保存解析出来的数据
	TArray<TSharedPtr> JsonParsed;

	TSharedRef<TJsonReader> JsonReader = TJsonReaderFactory::Create(JsonValue);		//将JsonValue字符串中读取到JsonReader

	TArray<TSharedPtr> Return_JsonObject;

	if (FJsonSerializer::Deserialize(JsonReader, JsonParsed)) {
		for (int index = 0; index != JsonParsed.Num(); index++) {
			Return_JsonObject.Add(JsonParsed[index]->AsObject());
		}
	}
	return Return_JsonObject;
}

那么来解释下代码,TSharedPtr这个是虚幻引擎中的智能指针,与C/C++中指针不同的是,如果使用这个来声明的指针会在必要的时候才会被垃圾回收机制释放,使用这个可以避免内存中准备调用的地址被提前释放带来的程序崩溃,这里因为如果详细说的话会有点长所以直接给出一个链接给想要深入了解的人

https://zhuanlan.zhihu.com/p/94198883

如果不深究的话就当知道是防止指针被提前释放的措施就行了。

//将JsonValue字符串中读取到JsonReader
TSharedRef<TJsonReader> JsonReader = TJsonReaderFactory::Create(JsonValue);

这个是以TCHAR数据类型读取到JsonReader中,如果你想,也可以使用其他你自己定义的数据类型,TCHAR是虚幻引擎的字符类型。

当然,如果你不想使用JsonObject类型的话也是可以,JsonValue类型也是可以转换成FString类型,使用AsString()就行了。

现在,已经可以取到数据表中所有行的数据了,现在就是按照自己的武器内容来读取数据就行了。

for (int index = 0; index != JsonObject.Num(); index++) {
		UArms_Table::ArmsInfo Temp_ArmsInfo;

		Temp_ArmsInfo.Arms = JsonObject[index]->GetStringField("Arms");
		Temp_ArmsInfo.Sight = JsonObject[index]->GetStringField("Sight");
		Temp_ArmsInfo.Vertgrip = JsonObject[index]->GetStringField("Vertgrip");
		Temp_ArmsInfo.Mag = JsonObject[index]->GetStringField("Mag");
		Temp_ArmsInfo.Now_Bullets = JsonObject[index]->GetIntegerField("Now_Bullets");
		Temp_ArmsInfo.Spare_Bullets = JsonObject[index]->GetIntegerField("Spare_Bullets");
		Temp_ArmsInfo.Default_Bullets = JsonObject[index]->GetIntegerField("Default_Bullets");
		Temp_ArmsInfo.MaxRange = JsonObject[index]->GetIntegerField("MaxRange");
		Temp_ArmsInfo.Recoil = FCString::Atof(*(JsonObject[index]->GetStringField("Recoil")));
		Temp_ArmsInfo.Damage = FCString::Atof(*(JsonObject[index]->GetStringField("Damage")));

		ArmsInfos.Add(Temp_ArmsInfo);
	}

还记得上章创建的结构体嘛,这个就是要使用的地方。

生成武器


现在,已经读取到之前保存在数据表中的内容了,现在就是到了生成武器的时候了,注意,数据表保存的数据不是持久保存的,这意味着如果你要持久保存数据的话需要保存到本地中。这部分的内容以后会讲下。

从类中动态生成Actor的话一般使用StaticLoadClass()和LoadClass()这2个函数来动态加载UClass类,在源代码中这LoadClass()其实就是调用了StaticLoadClass()函数

template< class T > 
inline UClass* LoadClass( UObject* Outer, const TCHAR* Name, const TCHAR* Filename=nullptr, uint32 LoadFlags=LOAD_None, UPackageMap* Sandbox=nullptr )
{
	return StaticLoadClass( T::StaticClass(), Outer, Name, Filename, LoadFlags, Sandbox );
}

但是这边推荐使用LoadClass()函数,这样比较方便使用点。不过如果你觉得想省下那几个CPU时钟周期的时间的话也是可以直接使用StaticLoadClass()。

这里本来是准备展开讲解下这个函数的参数的,但是UClass和UObject类的内容好像会特别多,所以还是直接给个链接,如果想深入了解就可以去看看,不感兴趣的话可以直接按照我的来做。

https://www.jianshu.com/p/1f2de6ea383c

那么就来动态加载一个UClass类先

UClass* TempActorClass = LoadClass(nullptr, *(ArmsInfos[0].Arms));

还记得我上章说过在数据表中UClass其实是一个字符串嘛,现在就可以直接使用这个来生成UClass。

if (TempActorClass != nullptr) {
AArms_Beas* TempActor = GetWorld()->SpawnActor(TempActorClass);

//隐藏组件
if (TempActor->SkeletalMesh != nullptr) {

TempActor->SkeletalMesh->USkinnedMeshComponent::HideBoneByName(FName(TEXT("b_gun_mag")), EPhysBodyOp::PBO_None);

}

TempActorClass = LoadClass(nullptr, *(ArmsInfos[index].Sight));

if (TempActorClass != nullptr) {

AActor* FittingActor = GetWorld()->SpawnActor(TempActorClass);

FittingActor->AttachToComponent(TempActor->SkeletalMesh, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true), TEXT("Sight"));
}
}

那么来解释下这个

AArms_Beas* TempActor = GetWorld()->SpawnActor(TempActorClass);

这个AArms_Beas是我创建的一个武器基类,继承于AActor,我这边推荐在搞某个要派生出其他蓝图的类的时候先创建一个基类,这样会方便很多。

UWorld类中的SpawnActor()函数是在游戏运行的场景中创建一个Actor,并返回这个Actor的指针,原型是

AActor * SpawnActor
(
    UClass * InClass,
    FVector const * Location,
    FRotator const * Rotation,
    const FActorSpawnParameters & SpawnParameters
)

如果使用默认的话,也就是只传递一个UClass类型的参数的话是默认生成在场景的(0, 0, 0)处。这边要把AActor指针转换成AArms_Beas类型,如果你是使用其他的类型当基类的话就是转换成对应的基类就行了。

TempActor->SkeletalMesh->USkinnedMeshComponent::HideBoneByName(FName(TEXT("b_gun_mag")), EPhysBodyOp::PBO_None);

这个的话其实和蓝图的隐藏骨骼一样,使用不多说了。

FittingActor->AttachToComponent(TempActor->SkeletalMesh, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true), TEXT("Sight"));

这个的话是附加到骨骼体上,使用方法和蓝图一样,体验不进行解释。

现在,你就可以在游戏中生成武器了。

待续


嗯….好像代码量还是不是特别多,不过先消化下吧,感觉教别人的时候本来挺多的东西会被精简掉,之前去看UClass和UObject这2个东西的时候真的看得要死要死的,那个智能指针也是一样,不过嘛,也是学习吧,明天再来更新怎么把数据保存到本地。那么,下次再见。