4.1 Ability System Component
AbilitySystemComponent(ASC)是GAS的核心。它是一个UActorComponent(UAbilitySystemComponent),负责处理与系统的所有交互。任何希望使用GameplayAbilities、拥有Attributes或接收GameplayEffects的Actor都必须附加一个ASC。这些对象都存在于ASC中,并由其管理和同步(除了Attributes,它们由其AttributeSet同步)。开发者通常会对其进行子类化,但这不是必须的。
附加了ASC的Actor被称为ASC的OwnerActor。ASC的物理表现Actor被称为AvatarActor。OwnerActor和AvatarActor可以是同一个Actor,例如在MOBA游戏中的简单AI小兵中。它们也可以是不同的Actor,例如在MOBA游戏中由玩家控制的英雄,其中OwnerActor是PlayerState,而AvatarActor是英雄的Character类。大多数Actor会在自身上附加ASC。如果你的Actor会重生并需要在重生之间保持Attributes或GameplayEffects的持久性(如MOBA中的英雄),那么ASC的理想位置是在PlayerState上。
注意: 如果你的ASC在PlayerState上,那么你需要增加PlayerState的NetUpdateFrequency。它在PlayerState上的默认值非常低,可能会导致在客户端上发生诸如Attributes和GameplayTags变化的延迟或感知到的滞后。确保启用Adaptive Network Update Frequency,Fortnite使用了它。
如果OwnerActor和AvatarActor是不同的Actor,那么它们都应该实现IAbilitySystemInterface。这个接口有一个必须重写的函数,UAbilitySystemComponent* GetAbilitySystemComponent() const,它返回一个指向其ASC的指针。ASC通过查找此接口函数在系统内部相互交互。
ASC在FActiveGameplayEffectsContainer ActiveGameplayEffects中保存其当前活动的GameplayEffects。
ASC在FGameplayAbilitySpecContainer ActivatableAbilities中保存其授予的Gameplay Abilities。任何时候你计划迭代ActivatableAbilities.Items时,确保在循环上方添加ABILITYLIST_SCOPE_LOCK();以锁定列表不被更改(由于移除能力)。在作用域内的每个ABILITYLIST_SCOPE_LOCK();都会增加AbilityScopeLockCount,并在作用域结束时递减。不要尝试在ABILITYLIST_SCOPE_LOCK();的作用域内移除能力(清除能力函数会在内部检查AbilityScopeLockCount以防止在列表被锁定时移除能力)。
4.1.1 同步模式(Replication Mode)
ASC定义了三种不同的同步模式,用于同步GameplayEffects、GameplayTags和GameplayCues - Full、Mixed和Minimal。Attributes由其AttributeSet同步。
| 同步模式 | 何时使用 | 描述 |
|---|---|---|
Full | 单人游戏 | 每个GameplayEffect都会同步到每个客户端。 |
Mixed | 多人游戏,玩家控制的Actors | GameplayEffects只同步到拥有的客户端。只有GameplayTags和GameplayCues同步到所有人。 |
Minimal | 多人游戏,AI控制的Actors | GameplayEffects从不同步给任何人。只有GameplayTags和GameplayCues同步到所有人。 |
注意: Mixed同步模式期望OwnerActor的Owner是Controller。PlayerState的Owner默认是Controller,但Character的不是。如果使用Mixed同步模式且OwnerActor不是PlayerState,那么你需要在OwnerActor上调用SetOwner()并传入一个有效的Controller。
从4.24开始,PossessedBy()会将Pawn的Owner设置为新的Controller。
4.1.2 设置和初始化(Setup and Initialization)
ASCs通常在OwnerActor的构造函数中构建,并显式标记为可Replicated。这必须在C++中完成。
AGDPlayerState::AGDPlayerState()
{
// 创建能力系统组件,并设置为显式Replicated
AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
//...
}ASC需要在服务器和客户端上用其OwnerActor和AvatarActor进行初始化。你希望在Pawn的Controller设置之后(在占有之后)进行初始化。单人游戏只需关注服务器路径。
对于ASC位于Pawn上的玩家控制角色,我通常在服务器上在Pawn的PossessedBy()函数中初始化,并在客户端上在PlayerController的AcknowledgePossession()函数中初始化。
void APACharacterBase::PossessedBy(AController * NewController)
{
Super::PossessedBy(NewController);
if (AbilitySystemComponent)
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
// ASC MixedMode同步要求ASC Owner的Owner是Controller。
SetOwner(NewController);
}void APAPlayerControllerBase::AcknowledgePossession(APawn* P)
{
Super::AcknowledgePossession(P);
APACharacterBase* CharacterBase = Cast<APACharacterBase>(P);
if (CharacterBase)
{
CharacterBase->GetAbilitySystemComponent()->InitAbilityActorInfo(CharacterBase, CharacterBase);
}
//...
}对于ASC位于PlayerState上的玩家控制角色,我通常在服务器上在Pawn的PossessedBy()函数中初始化,并在客户端上在Pawn的OnRep_PlayerState()函数中初始化。这确保了客户端上存在PlayerState。
// 仅服务器
void AGDHeroCharacter::PossessedBy(AController * NewController)
{
Super::PossessedBy(NewController);
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
if (PS)
{
// 在服务器上设置ASC。客户端在OnRep_PlayerState()中执行此操作。
AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// AI没有PlayerControllers,所以我们可以在这里再次初始化。对于有PlayerControllers的英雄,初始化两次没有害处。
PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
}
//...
}// 仅客户端
void AGDHeroCharacter::OnRep_PlayerState()
{
Super::OnRep_PlayerState();
AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
if (PS)
{
// 为客户端设置ASC。服务器在PossessedBy中执行此操作。
AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
// 为客户端初始化ASC Actor Info。服务器将在占有新Actor时初始化其ASC。
AbilitySystemComponent->InitAbilityActorInfo(PS, this);
}
// ...
}如果你收到错误信息LogAbilitySystem: Warning: Can't activate LocalOnly or LocalPredicted ability %s when not local!,那么你没有在客户端初始化你的ASC。