AbilitySystemComponent没有发现有地方赋值AttributeSet,但是发现可以直接获取到AttributeSet为什么呢?
今天在学习一个gas rpg的视频的时候,有这样的一句代码
const UAuraAttributeSet* AuraAttributeSet = Cast<UAuraAttributeSet>(ASCInterface->GetAbilitySystemComponent()->GetAttributeSet(UAuraAttributeSet::StaticClass()));
我百思不得其解,我没有给AbilitySystemComponent赋值AttributeSet啊,但是为什么可以获取到呢?
学习了下面的文章之后,结合我的代码得到了理解。
我们发现在AAuraCharacter里面的PossessedBy和OnRep_PlayerState里面是这样写的
//服务器初始化ASC
void AAuraCharacter::PossessedBy(AController* NewController)
{
Super::PossessedBy(NewController);
//初始化ASC的OwnerActor和AvatarActor
InitAbilityActorInfo();
}
//客户端初始化ASC
void AAuraCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
//初始化ASC的OwnerActor和AvatarActor
InitAbilityActorInfo();
}
这两个都调用了InitAbilityActorInfo函数
void AAuraCharacter::InitAbilityActorInfo()
{
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
check(AuraPlayerState);
/*
* InitAbilityActorInfo 是一个重要的方法,用于初始化与能力系统(Ability System)相关的 FGameplayAbilityActorInfo 结构体。
* 这个结构体包含了关于执行能力时所需的关键信息,例如谁拥有这个组件、谁控制这个组件以及实际执行动作的 Actor。
* InitAbilityActorInfo 的作用InitAbilityActorInfo 方法的主要目的是设置以下关键信息:
* •OwnerActor:逻辑上拥有这个组件的 Actor。通常是拥有这个组件的 Actor,例如一个角色或 NPC。
* •AvatarActor:实际在世界中执行动作的 Actor。通常是 APawn 或 ACharacter,但也可能是其他类型的 Actor,例如塔、建筑或炮台等。
*/
//初始化ASC
AuraPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(AuraPlayerState, this);
//从playerState获取ASC
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();
//从playerState获取AS
AttributeSet = AuraPlayerState->GetAttributeSet();
}
AuraPlayerState作为了我们的OwnerActor。
我们的代码在AuraPlayerState里面定义了UAbilitySystemComponent以及UAttributeSet。
按照下面的文件的理解在GAS使用中,我们会在GAS组件(简称ASC)的OwnerActor(大部分情况下就是Character)中声明并初始化一个AttributeSet。实际上UAbilitySystemComponent在UE的源代码里面执行了InitializeComponent函数,这个InitializeComponent进行了相关的处理。
下面是:UE4 GAS学习小记1:AttributeSet的自动注册 - 知乎 (zhihu.com)的原文,防止原文丢失特意复制过来
在GAS使用中,我们会在GAS组件(简称ASC)的OwnerActor(大部分情况下就是Character)中声明并初始化一个AttributeSet。官方文档中提到,“在OwnerActor的构造函数中创建AttributeSet会自动注册到其ASC”(GitHub - BillEliot/GASDocumentation_Chinese 4.4.1 ),实际使用中也是如此,我们不需要写额外的逻辑就可以通过ASC对AttributeSet进行操作,那么这种自动注册是如何实现的呢?
答案很简单也就是ASC中重写的InitializeComponent()函数啦,但是这里很诡异的是,代码中ASC的实现文件有两个,一个叫AbilitySystemComponent.cpp,一个叫AbilitySystemComponent_Abilities.cpp,而InitializeComponent()是写在后一个文件里的。
(我也不知道为什么要分开两个文件写反正它就是分开了,而且后缀名加了个“Abilities”很容易让人联想到是Ability相关的东西就写在后面这个文件,结果Ability相关的东西确实写在这里,但是其他很多很重要的东西例如Initialize、tick、destroy啥的也写在这里你到底是怎么命名的啊给我老老实实写在AbilitySystemComponent.cpp里面啊)
好以上是吐槽,说回正题,直接贴代码:
void UAbilitySystemComponent::InitializeComponent()
{
Super::InitializeComponent();
// Look for DSO AttributeSets (note we are currently requiring all attribute sets to be subobjects of the same owner. This doesn't *have* to be the case forever.
AActor *Owner = GetOwner();
InitAbilityActorInfo(Owner, Owner); // Default init to our outer owner
// cleanup any bad data that may have gotten into SpawnedAttributes
TArray<UAttributeSet*>& SpawnedAttributesRef = GetSpawnedAttributes_Mutable();
for (int32 Idx = SpawnedAttributesRef.Num()-1; Idx >= 0; --Idx)
{
if (SpawnedAttributesRef[Idx] == nullptr)
{
SpawnedAttributesRef.RemoveAt(Idx);
}
}
TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Owner, ChildObjects, false, RF_NoFlags, EInternalObjectFlags::PendingKill);
for (UObject* Obj : ChildObjects)
{
UAttributeSet* Set = Cast<UAttributeSet>(Obj);
if (Set)
{
SpawnedAttributesRef.AddUnique(Set);
bIsNetDirty = true;
}
}
}
可以看到ASC的整个Initialize过程就干了一件事,清除掉旧的AttributeSet,然后添加新的。具体来讲,ASC是通过一个叫做SpawnedAttributes的数组保存所有注册的AttributeSet(要注意一个OwnerActor可以有多个不同的AttributeSet),在InitializeComponent()中,先遍历清空该数组,再通过GetObjectsWithOuter()拿到OwnerActor所有的subobject,遍历,只要是AttributeSet类的,就把它塞到SpawnedAttributes中。
那么InitializeComponent()会在什么时候调用呢,在ActorComponent.h中的官方描述中,总结一句话就是“在组件注册之后,在BeginPlay之前”,所以,当我们在游戏中生成了一个ASC的OwnerActor出来,ASC相关的函数调用顺序会是:构造——注册——InitializeComponent(),然后才是Actor和它的所有组件的BeginPlay。
这样我们一开始游戏就可以直接通过ASC操作AttributeSet啦。