4、GAS相关概念4.1 Ability System Component

4.1 Ability System Component

AbilitySystemComponentASC)是GAS的核心。它是一个UActorComponentUAbilitySystemComponent),负责处理与系统的所有交互。任何希望使用GameplayAbilities、拥有Attributes或接收GameplayEffectsActor都必须附加一个ASC。这些对象都存在于ASC中,并由其管理和同步(除了Attributes,它们由其AttributeSet同步)。开发者通常会对其进行子类化,但这不是必须的。

附加了ASCActor被称为ASCOwnerActorASC的物理表现Actor被称为AvatarActorOwnerActorAvatarActor可以是同一个Actor,例如在MOBA游戏中的简单AI小兵中。它们也可以是不同的Actor,例如在MOBA游戏中由玩家控制的英雄,其中OwnerActorPlayerState,而AvatarActor是英雄的Character类。大多数Actor会在自身上附加ASC。如果你的Actor会重生并需要在重生之间保持AttributesGameplayEffects的持久性(如MOBA中的英雄),那么ASC的理想位置是在PlayerState上。

注意: 如果你的ASCPlayerState上,那么你需要增加PlayerStateNetUpdateFrequency。它在PlayerState上的默认值非常低,可能会导致在客户端上发生诸如AttributesGameplayTags变化的延迟或感知到的滞后。确保启用Adaptive Network Update Frequency,Fortnite使用了它。

如果OwnerActorAvatarActor是不同的Actor,那么它们都应该实现IAbilitySystemInterface。这个接口有一个必须重写的函数,UAbilitySystemComponent* GetAbilitySystemComponent() const,它返回一个指向其ASC的指针。ASC通过查找此接口函数在系统内部相互交互。

ASCFActiveGameplayEffectsContainer ActiveGameplayEffects中保存其当前活动的GameplayEffects

ASCFGameplayAbilitySpecContainer ActivatableAbilities中保存其授予的Gameplay Abilities。任何时候你计划迭代ActivatableAbilities.Items时,确保在循环上方添加ABILITYLIST_SCOPE_LOCK();以锁定列表不被更改(由于移除能力)。在作用域内的每个ABILITYLIST_SCOPE_LOCK();都会增加AbilityScopeLockCount,并在作用域结束时递减。不要尝试在ABILITYLIST_SCOPE_LOCK();的作用域内移除能力(清除能力函数会在内部检查AbilityScopeLockCount以防止在列表被锁定时移除能力)。

4.1.1 同步模式(Replication Mode)

ASC定义了三种不同的同步模式,用于同步GameplayEffectsGameplayTagsGameplayCues - FullMixedMinimalAttributes由其AttributeSet同步。

同步模式何时使用描述
Full单人游戏每个GameplayEffect都会同步到每个客户端。
Mixed多人游戏,玩家控制的ActorsGameplayEffects只同步到拥有的客户端。只有GameplayTagsGameplayCues同步到所有人。
Minimal多人游戏,AI控制的ActorsGameplayEffects从不同步给任何人。只有GameplayTagsGameplayCues同步到所有人。

注意: Mixed同步模式期望OwnerActorOwnerControllerPlayerStateOwner默认是Controller,但Character的不是。如果使用Mixed同步模式且OwnerActor不是PlayerState,那么你需要在OwnerActor上调用SetOwner()并传入一个有效的Controller

从4.24开始,PossessedBy()会将PawnOwner设置为新的Controller

4.1.2 设置和初始化(Setup and Initialization)

ASCs通常在OwnerActor的构造函数中构建,并显式标记为可Replicated。这必须在C++中完成

AGDPlayerState::AGDPlayerState()
{
	// 创建能力系统组件,并设置为显式Replicated
	AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
	AbilitySystemComponent->SetIsReplicated(true);
	//...
}

ASC需要在服务器和客户端上用其OwnerActorAvatarActor进行初始化。你希望在PawnController设置之后(在占有之后)进行初始化。单人游戏只需关注服务器路径。

对于ASC位于Pawn上的玩家控制角色,我通常在服务器上在PawnPossessedBy()函数中初始化,并在客户端上在PlayerControllerAcknowledgePossession()函数中初始化。

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上的玩家控制角色,我通常在服务器上在PawnPossessedBy()函数中初始化,并在客户端上在PawnOnRep_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