Init
This commit is contained in:
247
03-UnrealEngine/Gameplay/GAS/(16)在联机项目中使用GAS的注意点.md
Normal file
247
03-UnrealEngine/Gameplay/GAS/(16)在联机项目中使用GAS的注意点.md
Normal file
@@ -0,0 +1,247 @@
|
||||
## 前言
|
||||
大多数人应该是会通过学习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);
|
||||
}
|
||||
```
|
Reference in New Issue
Block a user