【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
3、创建怪物DataTable
以MonsterInforRow为结构创建怪物数据表DT_Monsters,并设置相应属性值
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;
}
三、异步加载
使用FPrimaryAssetId和FPrimaryAssetId实现对主要资产(Primary Asset)的异步加载。
- 创建FPrimaryAssetId:创建一个FPrimaryAssetId来标识想要加载的主要资产。由两部分组成PrimaryAssetType和PrimaryAssetName。
- PrimaryAssetType:表示资产的类型,
- PrimaryAssetName:表示资产的名称。
- 调用LoadPrimaryAsset加载主要资产:调用这个方法的时候,它会阻塞当前的执行流,直到资产加载完成为止
- 设置完成回调:FStreamableDelegate是一个回调委托,在资产加载完成后被调用。创建一个FStreamableDelegate并把它绑定到 LoadPrimaryAsset方法上,当资产加载完成时,可以得到通知。
1、设置异步加载
异步加载前怪物数据表DT_Monsters的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、设置资产管理器
此时的DT_Monsters的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);
}
}
}
}
}
}