247 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
## 前言
 | 
						||
大多数人应该是会通过学习UE4的ActionRPG项目来入门GAS框架的。但在最近自己做Demo的过程中才发现里面的部分不适用网络联机。比如ActionRPG将Character与Controller的指针存在GameMode,因为Server与Client只有一个GameMode的关系,就不符合多人联机游戏的规则。所以在经过实践之后这里我分享一些要点仅供抛砖引玉。这里我再推荐各位读者去看一下GASDocument以及附带的工程,它展现了正确的设计思路。
 | 
						||
 | 
						||
## Ability中的网络同步节点
 | 
						||
WaitNetSync
 | 
						||
 | 
						||
## GASDocument中的处理方式
 | 
						||
将AbilitySystemComponent以及AttributeSet放在PlayerState中,之后再Character类中的PossesseBy()事件(ServerOnly)与OnRep_PlayerState()事件(Client)给角色类的ASC弱智能指针与AttributeSet弱智能指针赋值。
 | 
						||
 | 
						||
```
 | 
						||
AGDPlayerState::AGDPlayerState()
 | 
						||
{
 | 
						||
	AbilitySystemComponent = CreateDefaultSubobject<UGDAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
 | 
						||
	AbilitySystemComponent->SetIsReplicated(true);
 | 
						||
 | 
						||
	// Mixed mode means we only are replicated the GEs to ourself, not the GEs to simulated proxies. If another GDPlayerState (Hero) receives a GE,
 | 
						||
	// we won't be told about it by the Server. Attributes, GameplayTags, and GameplayCues will still replicate to us.
 | 
						||
	AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
 | 
						||
 | 
						||
	AttributeSetBase = CreateDefaultSubobject<UGDAttributeSetBase>(TEXT("AttributeSetBase"));
 | 
						||
 | 
						||
	NetUpdateFrequency = 100.0f;
 | 
						||
}
 | 
						||
 | 
						||
// 	TWeakObjectPtr<class UGDAbilitySystemComponent> AbilitySystemComponent;
 | 
						||
//  TWeakObjectPtr<class UGDAttributeSetBase> AttributeSetBase;
 | 
						||
// Server only
 | 
						||
void AGDHeroCharacter::PossessedBy(AController * NewController)
 | 
						||
{
 | 
						||
	Super::PossessedBy(NewController);
 | 
						||
 | 
						||
	AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
 | 
						||
	if (PS)
 | 
						||
	{
 | 
						||
		// Set the ASC on the Server. Clients do this in OnRep_PlayerState()
 | 
						||
		AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
 | 
						||
 | 
						||
		// AI won't have PlayerControllers so we can init again here just to be sure. No harm in initing twice for heroes that have PlayerControllers.
 | 
						||
		PS->GetAbilitySystemComponent()->InitAbilityActorInfo(PS, this);
 | 
						||
        AttributeSetBase = PS->GetAttributeSetBase();
 | 
						||
	}
 | 
						||
	
 | 
						||
	//...
 | 
						||
}
 | 
						||
// Client only
 | 
						||
void AGDHeroCharacter::OnRep_PlayerState()
 | 
						||
{
 | 
						||
	Super::OnRep_PlayerState();
 | 
						||
 | 
						||
	AGDPlayerState* PS = GetPlayerState<AGDPlayerState>();
 | 
						||
	if (PS)
 | 
						||
	{
 | 
						||
		// Set the ASC for clients. Server does this in PossessedBy.
 | 
						||
		AbilitySystemComponent = Cast<UGDAbilitySystemComponent>(PS->GetAbilitySystemComponent());
 | 
						||
 | 
						||
		// Init ASC Actor Info for clients. Server will init its ASC when it possesses a new Actor.
 | 
						||
		AbilitySystemComponent->InitAbilityActorInfo(PS, this);
 | 
						||
        AttributeSetBase = PS->GetAttributeSetBase();
 | 
						||
	}
 | 
						||
 | 
						||
	// ...
 | 
						||
}
 | 
						||
```
 | 
						||
## 预测
 | 
						||

 | 
						||
 | 
						||
## PlayerState
 | 
						||
PlayerState只在开发多人游戏时有用,在设计上负责管理角色的变量(PlayerId、UniqueId、SessionName与自己定义的变量)。
 | 
						||
 | 
						||
### 指定与初始化
 | 
						||
在GameMode中设置实现的PlayerState类,之后在AController::InitPlayerState()中Spawn。
 | 
						||
 | 
						||
### 访问方法
 | 
						||
- Pawn->PlayerState
 | 
						||
- Controller->PlayerState(一般用这个)
 | 
						||
 | 
						||
另外还可以通过GameState->PlayerArray获取所有有效的GameState实例。
 | 
						||
 | 
						||
### PlayerState
 | 
						||
PlayerState在切换地图时会默认销毁,为了解决这个问题可以通过实现CopyProperties方法来解决。
 | 
						||
```
 | 
						||
class  ARPGPlayerState : public APlayerState
 | 
						||
{
 | 
						||
	GENERATED_BODY()
 | 
						||
public:
 | 
						||
	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "ActionRPG")
 | 
						||
	ARPGCharacterBase* Character;
 | 
						||
protected:
 | 
						||
	/** 这里是为了解决切换地图时的属性丢失问题 */
 | 
						||
	/** Copy properties which need to be saved in inactive PlayerState */
 | 
						||
	virtual void CopyProperties(APlayerState* PlayerState);
 | 
						||
};
 | 
						||
```
 | 
						||
```
 | 
						||
void ARPGPlayerState::CopyProperties(APlayerState* PlayerState)
 | 
						||
{
 | 
						||
	//如果不是因为断线
 | 
						||
	if (IsFromPreviousLevel())
 | 
						||
	{
 | 
						||
		ARPGPlayerState* RPGPlayerState=Cast<ARPGPlayerState>(PlayerState);
 | 
						||
		RPGPlayerState->Character = Character;
 | 
						||
	}
 | 
						||
}
 | 
						||
```
 | 
						||
 | 
						||
### NetUpdateFrequency
 | 
						||
PlayerState的NetUpdateFrequency和角色类的效果是一样的,都是调节更新频率。默认数值对于GAS太低了,会导致滞后性。100是个比较高的数值,你也可以按照需求自己调节。
 | 
						||
 | 
						||
## Controller
 | 
						||
### 获取PlayerControllers
 | 
						||
- GetWorld()->GetPlayerControllerIterator() 
 | 
						||
- PlayerState->GetOwner()
 | 
						||
- Pawn->GetController() 
 | 
						||
 | 
						||
### BeginPlay里不做任何与GAS相关的操作
 | 
						||
因为在服务端,Possession会发生在在BeginPlay前,客户端Possession发生在BeginPlay后,所以我们不能在BeginPlay中写任何有关ASC的逻辑除非PlayerState已经同步。
 | 
						||
 
 | 
						||
### 相关判断服务端客户端状态函数
 | 
						||
在Ability类中一些操作可能需要判断是服务端还是客户端,比如应用GameplayEffect就在服务端执行(使用HasAuthority()),而创建UI就需要在客户端执行(使用IsLocallyControlled())。
 | 
						||
 | 
						||
#### 蓝图
 | 
						||
- HasAuthority()判断是否是服务端或是作为主机的玩家
 | 
						||
- IsLocallyControlled()判断是否是本地控制器
 | 
						||
- IsServer()通过World的ENetMode来段是服务端
 | 
						||
 | 
						||
#### c++
 | 
						||
- 蓝图函数HasAuthority()在c++中为`return (GetLocalRole() == ROLE_Authority);`
 | 
						||
- IsLocallyControlled(),c++中有对应函数。
 | 
						||
 
 | 
						||
## NetSecurityPolicy
 | 
						||
GameplayAbility的网络安全策略决定了Ability应该在网络的何处执行. 它为尝试执行限制Ability的客户端提供了保护.
 | 
						||
 | 
						||
- ClientOrServer:没有安全需求. 客户端或服务端可以自由地触发该Ability的执行和终止.
 | 
						||
- ServerOnlyExecution:客户端对该Ability请求的执行会被服务端忽略, 但客户端仍可以请求服务端取消或结束该Ability.
 | 
						||
- ServerOnlyTermination:客户端对该Ability请求的取消或结束会被服务端忽略, 但客户端仍可以请求执行该Ability.
 | 
						||
- ServerOnly:服务端控制该Ability的执行和终止, 客户端的任何请求都会被忽略.
 | 
						||
 | 
						||
## NetExecutionPolicy
 | 
						||
推荐看这位大佬的文章:https://zhuanlan.zhihu.com/p/143637846
 | 
						||
 | 
						||
## 属性集属性UI更新
 | 
						||
这里推荐使用GASDocument中的GameplayTasks更新方法。
 | 
						||
```
 | 
						||
#pragma once
 | 
						||
#include "CoreMinimal.h"
 | 
						||
#include "Kismet/BlueprintAsyncActionBase.h"
 | 
						||
#include "AbilitySystemComponent.h"
 | 
						||
#include "AsyncTaskAttributeChanged.generated.h"
 | 
						||
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnAttributeChanged, FGameplayAttribute, Attribute, float, NewValue, float, OldValue);
 | 
						||
 | 
						||
/**
 | 
						||
 * Blueprint node to automatically register a listener for all attribute changes in an AbilitySystemComponent.
 | 
						||
 * Useful to use in UI.
 | 
						||
 */
 | 
						||
UCLASS(BlueprintType, meta=(ExposedAsyncProxy = AsyncTask))
 | 
						||
class RPGGAMEPLAYABILITY_API UAsyncTaskAttributeChanged : public UBlueprintAsyncActionBase
 | 
						||
{
 | 
						||
	GENERATED_BODY()
 | 
						||
	
 | 
						||
public:
 | 
						||
	UPROPERTY(BlueprintAssignable)
 | 
						||
	FOnAttributeChanged OnAttributeChanged;
 | 
						||
	
 | 
						||
	// Listens for an attribute changing.
 | 
						||
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
 | 
						||
	static UAsyncTaskAttributeChanged* ListenForAttributeChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayAttribute Attribute);
 | 
						||
 | 
						||
	// Listens for an attribute changing.
 | 
						||
	// Version that takes in an array of Attributes. Check the Attribute output for which Attribute changed.
 | 
						||
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
 | 
						||
	static UAsyncTaskAttributeChanged* ListenForAttributesChange(UAbilitySystemComponent* AbilitySystemComponent, TArray<FGameplayAttribute> Attributes);
 | 
						||
 | 
						||
	// You must call this function manually when you want the AsyncTask to end.
 | 
						||
	// For UMG Widgets, you would call it in the Widget's Destruct event.
 | 
						||
	UFUNCTION(BlueprintCallable)
 | 
						||
	void EndTask();
 | 
						||
 | 
						||
protected:
 | 
						||
	UPROPERTY()
 | 
						||
	UAbilitySystemComponent* ASC;
 | 
						||
 | 
						||
	FGameplayAttribute AttributeToListenFor;
 | 
						||
	TArray<FGameplayAttribute> AttributesToListenFor;
 | 
						||
 | 
						||
	void AttributeChanged(const FOnAttributeChangeData& Data);
 | 
						||
};
 | 
						||
```
 | 
						||
```
 | 
						||
#include "Abilities/AsyncTasks/AsyncTaskAttributeChanged.h"
 | 
						||
UAsyncTaskAttributeChanged* UAsyncTaskAttributeChanged::ListenForAttributeChange(UAbilitySystemComponent* AbilitySystemComponent, FGameplayAttribute Attribute)
 | 
						||
{
 | 
						||
	UAsyncTaskAttributeChanged* WaitForAttributeChangedTask = NewObject<UAsyncTaskAttributeChanged>();
 | 
						||
	WaitForAttributeChangedTask->ASC = AbilitySystemComponent;
 | 
						||
	WaitForAttributeChangedTask->AttributeToListenFor = Attribute;
 | 
						||
 | 
						||
	if (!IsValid(AbilitySystemComponent) || !Attribute.IsValid())
 | 
						||
	{
 | 
						||
		WaitForAttributeChangedTask->RemoveFromRoot();
 | 
						||
		return nullptr;
 | 
						||
	}
 | 
						||
 | 
						||
	AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(WaitForAttributeChangedTask, &UAsyncTaskAttributeChanged::AttributeChanged);
 | 
						||
 | 
						||
	return WaitForAttributeChangedTask;
 | 
						||
}
 | 
						||
 | 
						||
UAsyncTaskAttributeChanged * UAsyncTaskAttributeChanged::ListenForAttributesChange(UAbilitySystemComponent * AbilitySystemComponent, TArray<FGameplayAttribute> Attributes)
 | 
						||
{
 | 
						||
	UAsyncTaskAttributeChanged* WaitForAttributeChangedTask = NewObject<UAsyncTaskAttributeChanged>();
 | 
						||
	WaitForAttributeChangedTask->ASC = AbilitySystemComponent;
 | 
						||
	WaitForAttributeChangedTask->AttributesToListenFor = Attributes;
 | 
						||
 | 
						||
	if (!IsValid(AbilitySystemComponent) || Attributes.Num() < 1)
 | 
						||
	{
 | 
						||
		WaitForAttributeChangedTask->RemoveFromRoot();
 | 
						||
		return nullptr;
 | 
						||
	}
 | 
						||
 | 
						||
	for (FGameplayAttribute Attribute : Attributes)
 | 
						||
	{
 | 
						||
		AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(Attribute).AddUObject(WaitForAttributeChangedTask, &UAsyncTaskAttributeChanged::AttributeChanged);
 | 
						||
	}
 | 
						||
 | 
						||
	return WaitForAttributeChangedTask;
 | 
						||
}
 | 
						||
 | 
						||
void UAsyncTaskAttributeChanged::EndTask()
 | 
						||
{
 | 
						||
	if (IsValid(ASC))
 | 
						||
	{
 | 
						||
		ASC->GetGameplayAttributeValueChangeDelegate(AttributeToListenFor).RemoveAll(this);
 | 
						||
 | 
						||
		for (FGameplayAttribute Attribute : AttributesToListenFor)
 | 
						||
		{
 | 
						||
			ASC->GetGameplayAttributeValueChangeDelegate(Attribute).RemoveAll(this);
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	SetReadyToDestroy();
 | 
						||
	MarkPendingKill();
 | 
						||
}
 | 
						||
 | 
						||
void UAsyncTaskAttributeChanged::AttributeChanged(const FOnAttributeChangeData & Data)
 | 
						||
{
 | 
						||
	OnAttributeChanged.Broadcast(Data.Attribute, Data.NewValue, Data.OldValue);
 | 
						||
}
 | 
						||
``` |