【UE4C++-ActionRougelike-27】资产、数据、异步加载


【UE4C++-ActionRougelike-27】资产、数据、异步加载

一、AI机器人数据表和资产

1、创建数据表结构

在MyGameModeBase.h中创建怪物信息结构体MonsterInforRow

// MyGameModeBase.h
USTRUCT(BlueprintType)
struct FMonsterInfoRow : public FTableRowBase
{
	GENERATED_BODY()
public:
	// 默认构造函数
	FMonsterInfoRow()
	{
		Weight = 1.0f;
		SpawnCost = 5.0f;
		KillReward = 20.0f;
	}
	// 定义一个名为MonsterClass的字段,类型为AActor子类,该字段用于标识怪物
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	TSubclassOf<AActor> MonsterClass;
	// 定义一个名为Weight的字段,表示选择此怪物的相对机会
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float Weight;
	// 定义一个名为SpawnCost的字段,表示游戏模式生成此单位所需的点数
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
    float SpawnCost;
	// 定义一个名为KillReward的字段,表示杀死此单位的奖励金额
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float KillReward;
};

2、创建怪物DataAsset

2.1 创建怪物数据类

以PrimaryAssetLabel为父类创建怪物数据类MyMonsterData

UCLASS()
class ACTIONROGUELIKE_API UMyMonsterData : public UPrimaryAssetLabel 
{
	GENERATED_BODY()
public:
    // 定义一个怪物类,它是AActor的子类
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawn Info") 
	TSubclassOf<AActor> MonsterClass;
	// 定义一个数组,存储怪物可以执行的动作,这些动作是UMyAction的子类
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Spawn Info")
	TArray<TSubclassOf<UMyAction>> Actions;
    // 定义一个2D纹理,表示怪物的图标
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	UTexture2D* Icon;
};
2.2 创建怪物类资产

以MyMonsterData为基础创建DataAsset

DataAsset设置

3、创建怪物DataTable

以MonsterInforRow为结构创建怪物数据表DT_Monsters,并设置相应属性值

DataAsset设置

4、调用怪物数据表

在MyGameModeBase.h中增加数据表属性MonsterTable

// MyGameModeBase.h
class ACTIONROGUELIKE_API AMyGameModeBase : public AGameModeBase
{
protected:
	// 怪物数据表
	UPROPERTY(EditDefaultsOnly, Category = "AI")
	UDataTable* MonsterTable;
    //指定要生成的AI角色的类
	//UPROPERTY(EditDefaultsOnly, Category = "AI")
	//TSubclassOf<AActor> MinionClass;
}
// MyGameModeBase.cpp

在MyGameModeBase.cpp中修改OnBotSpawnQueryCompleted方法,使得生成AI机器人时是从数据表MonsterTable中获取信息。

void AMyGameModeBase::OnBotSpawnQueryCompleted(UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus)
{
	//...
    if (Locations.IsValidIndex(0))
	{
		// 如果怪物表存在
		if (MonsterTable)
		{
			// 定义一个MonsterInfoRow指针的数组
			TArray<FMonsterInfoRow*> Rows;
			// 从怪物表中获取所有行,将其存入Rows数组
			MonsterTable->GetAllRows("", Rows);
			// 随机选择一个怪物
			int32 RandomIndex = FMath::RandRange(0, Rows.Num() - 1);
			FMonsterInfoRow* SelectedRow = Rows[RandomIndex];
			// 在选定的位置生成这个怪物
			GetWorld()->SpawnActor<AActor>(SelectedRow->MonsterData->MonsterClass, Locations[0], FRotator::ZeroRotator);
		}
	}
}

二、弱引用和强引用

1、弱引用和强引用

弱引用和强引用都是描述资产引用方式的术语

Hard Reference (强引用)

  • 概念:强引用指的是直接引用其他资产的方式。当资产A强引用了资产B,那么资产A被加载到内存中时,资产B也将被自动加载;只有当所有对资产B的强引用都被删除或卸载时,资产B才可能被卸载。
  • 使用:通常使用强引用来引用你需要立即访问的资产。例如,一个角色可能强引用其模型和纹理,因为这些资产需要在角色被创建时立即可用。

Soft Reference (弱引用)

  • 概念:弱引用不会自动加载其引用的资产。一个资产A弱引用资产B,只是存储了资产B的标识符,但不会立即加载资产B。只有当明确请求加载弱引用的资产时,资产B才会被加载到内存中。
  • 使用:弱引用通常用于引用可能需要一些时间来加载的大型资产,或可能并不总是需要的资产。例如,一个角色可能弱引用一些高解析度的纹理或复杂的动画,这些资产只在特定的情况下需要,并且可能需要一些时间来加载。
  • 注意事项:虽然弱引用不会自动加载其引用的资产,但必须确保在访问弱引用的资产之前,明确加载了这个资产。否则会遇到无法访问资产的问题。

2、弱引用示例

class ACTIONROGUELIKE_API UMyAction : public UObject
{
protected:
	// Buff状态图标
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "UI")
	TSoftObjectPtr<UTexture2D> Icon;
}
弱引用示例

三、异步加载

使用FPrimaryAssetIdFPrimaryAssetId实现对主要资产(Primary Asset)的异步加载。

  • 创建FPrimaryAssetId:创建一个FPrimaryAssetId来标识想要加载的主要资产。由两部分组成PrimaryAssetType和PrimaryAssetName。
    • PrimaryAssetType:表示资产的类型,
    • PrimaryAssetName:表示资产的名称。
  • 调用LoadPrimaryAsset加载主要资产:调用这个方法的时候,它会阻塞当前的执行流,直到资产加载完成为止
  • 设置完成回调:FStreamableDelegate是一个回调委托,在资产加载完成后被调用。创建一个FStreamableDelegate并把它绑定到 LoadPrimaryAsset方法上,当资产加载完成时,可以得到通知。

1、设置异步加载

异步加载前怪物数据表DT_Monsters的SizeMap:

异步加载前SizeMap

在MyGameModeBase.h中修改怪物数据表结构

struct FMonsterInfoRow : public FTableRowBase
{
public:
	//...
	// 定义一个名为MonsterId的字段,类型为FPrimaryAssetId,该字段用于标识怪物
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	FPrimaryAssetId MonsterId;
	//...
}

在MyMonsterData.h中添加常量函数,用于返回PrimaryAssetId

// 定义一个常量函数,返回FPrimaryAssetId类型
FPrimaryAssetId GetPrimaryAssetId() const override
{
	// 返回一个FPrimaryAssetId类型的对象,该对象用于唯一标识一个主要资产。
	// 这里使用的是本类对象的名称(GetFName()),和字符串"Monsters"组合生成这个唯一标识符。
	return FPrimaryAssetId("Monsters", GetFName()); 
}

2、设置资产管理器

Asset Manager设置

此时的DT_Monsters的SizeMap

异步加载后的SizeMap

3、启动异步加载

void AMyGameModeBase::OnBotSpawnQueryCompleted(UEnvQueryInstanceBlueprintWrapper* QueryInstance, EEnvQueryStatus::Type QueryStatus)
{
    // ...
	TArray<FVector> Locations = QueryInstance->GetResultsAsLocations();
	if (Locations.IsValidIndex(0))
	{
		// 如果怪物表存在
		if (MonsterTable)
		{
			// 定义一个MonsterInfoRow指针的数组
			TArray<FMonsterInfoRow*> Rows;
			// 从怪物表中获取所有行,将其存入Rows数组
			MonsterTable->GetAllRows("", Rows);
			// 随机选择一个怪物
			int32 RandomIndex = FMath::RandRange(0, Rows.Num() - 1);
			FMonsterInfoRow* SelectedRow = Rows[RandomIndex];
			// 获取资产管理器实例
			UAssetManager* Manager = UAssetManager::GetIfValid();
			if (Manager)
			{
				// 输出加载怪物的提示信息
				LogOnScreen(this, "Loading monster...", FColor::Green);
				// 输出加载怪物的提示信息
				TArray<FName> Bundles;
				// 创建一个回调代理
                 // FStreamableDelegate是一个特殊类型的委托,它主要用于异步加载资源时,在加载完成后执行指定的函数。
                 // 当LoadPrimaryAsset成功完成怪物资源的加载后,Delegate委托会被调用,并执行AMyGameModeBase类的OnMonsterLoaded成员函数。
				FStreamableDelegate Delegate = FStreamableDelegate::CreateUObject(this, &AMyGameModeBase::OnMonsterLoaded, SelectedRow->MonsterId, Locations[0]);
				// 使用资产管理器加载选择的怪物
				Manager->LoadPrimaryAsset(SelectedRow->MonsterId, Bundles, Delegate);
			}
		}
	}
}
void AMyGameModeBase::OnMonsterLoaded(FPrimaryAssetId LoadedId, FVector SpawnLocation)
{
	LogOnScreen(this, "Finished loading.", FColor::Green);
	// 获取资产管理器实例
	UAssetManager* Manager = UAssetManager::GetIfValid();
	if (Manager)
	{
		// 通过管理器获取主资产对象(即怪物数据资源)
		UMyMonsterData* MonsterData = Cast<UMyMonsterData>(Manager->GetPrimaryAssetObject(LoadedId));
		if (MonsterData)
		{
			// 通过怪物数据资源的信息生成一个新的怪物角色
			AActor* NewBot = GetWorld()->SpawnActor<AActor>(MonsterData->MonsterClass, SpawnLocation, FRotator::ZeroRotator);
			if (NewBot)
			{
				// 输出生成新怪物的提示信息
				LogOnScreen(this, FString::Printf(TEXT("Spawned enemy: %s (%s)"), *GetNameSafe(NewBot), *GetNameSafe(MonsterData)));
				// 获取怪物角色的行为组件
				UMyActionComponent* ActionComp = Cast<UMyActionComponent>(NewBot->GetComponentByClass(UMyActionComponent::StaticClass()));
				if (ActionComp)
				{
					// 为怪物角色添加其特有的行为
					for (TSubclassOf<UMyAction> ActionClass : MonsterData->Actions)
					{
						ActionComp->AddAction(NewBot, ActionClass);
					}
				}
			}
		}
	}
}

文章作者: Woilin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Woilin !
评论
  目录