Recently i have been trying to learn more about the gameplay ability system and how to implement it in my current project, which is a multiplayer game. I’ve chosen to put the GAS component and the attribute set in the Player State, by it being unique to each player and replicated by default. In my Attribute set all my attributes have been set up properly and flagged to be replicated.
But my main problem is that the movement component seems to not be in sync with the server, as the client shows a jittery movement in its owning character while the server does not.
As far as i tested, my attributes are being replicated correctly, the player does change the speed when affected by an effect. The speed attribute (WalkSpeed and Crouched Walk Speed) changes the movement component speed.
Some code:
Attribute Set (StandardAttributes)
//header
public:
UPROPERTY(BlueprintReadOnly, Category = "Speed", ReplicatedUsing = OnRep_WalkSpeed)
FGameplayAttributeData WalkSpeed;
ATTRIBUTE_ACCESSORS(UStandardAttributes, WalkSpeed);
UPROPERTY(BlueprintReadOnly, Category = "Speed", ReplicatedUsing = OnRep_CrouchedWalkSpeed)
FGameplayAttributeData CrouchedWalkSpeed;
ATTRIBUTE_ACCESSORS(UStandardAttributes, CrouchedWalkSpeed);
protected:
UFUNCTION()
void OnRep_WalkSpeed(const FGameplayAttributeData& OldSpeedMultiplier);
UFUNCTION()
void OnRep_CrouchedWalkSpeed(const FGameplayAttributeData& OldSpeedMultiplier);
//cpp file
UStandardAttributes::UStandardAttributes()
{
//Setting base values
WalkSpeed.SetBaseValue(500.0f);
WalkSpeed.SetCurrentValue(500.0f);
CrouchedWalkSpeed.SetBaseValue(250.0f);
CrouchedWalkSpeed.SetCurrentValue(250.0f);
}
void UStandardAttributes::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME_CONDITION_NOTIFY(UStandardAttributes, WalkSpeed, COND_None, REPNOTIFY_Always);
DOREPLIFETIME_CONDITION_NOTIFY(UStandardAttributes, CrouchedWalkSpeed, COND_None, REPNOTIFY_Always);
}
void UStandardAttributes::PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue)
{
Super::PostAttributeChange(Attribute, OldValue, NewValue);
const FGameplayAbilityActorInfo* ActorInfo = GetActorInfo();
if (ActorInfo)
{
//handle the attribute changes to affect the component
if (Attribute == GetWalkSpeedAttribute())
{
if (UCharacterMovementComponent* MovementComponent = Cast<UCharacterMovementComponent>(ActorInfo->MovementComponent))
{
MovementComponent->MaxWalkSpeed = NewValue;
//MovementComponent->bIgnoreClientMovementErrorChecksAndCorrection = true;
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Ability Error: Could not get Movement Component"));
}
}
if (Attribute == GetCrouchedWalkSpeedAttribute())
{
if (UCharacterMovementComponent* MovementComponent = Cast<UCharacterMovementComponent>(ActorInfo->MovementComponent))
{
MovementComponent->MaxWalkSpeedCrouched = NewValue;
}
}
}
}
void UStandardAttributes::OnRep_WalkSpeed(const FGameplayAttributeData& OldWalkSpeed)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UStandardAttributes, WalkSpeed, OldWalkSpeed);
}
void UStandardAttributes::OnRep_CrouchedWalkSpeed(const FGameplayAttributeData& OldCrouchedWalkSpeed)
{
GAMEPLAYATTRIBUTE_REPNOTIFY(UStandardAttributes, CrouchedWalkSpeed, OldCrouchedWalkSpeed);
}
It does have the PreAttributeChange() and PostGameplayEffectExecute() but it is only use for value clamping.
PlayerState
//Header
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attributes", replicated, meta = (AllowPrivateAccess = "True"))
UAbilitySystemComponent* AbilitySystemComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Attributes", replicated, meta = (AllowPrivateAccess = "True"))
UStandardAttributes* StandardAttributes;
//CPP
AGatosPlayerState::AGatosPlayerState()
{
/* Ability System Component */
//Create and setup ASC
AbilitySystemComponent = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("AbilitySystemComponent"));
AbilitySystemComponent->SetIsReplicated(true);
AbilitySystemComponent->SetReplicationMode(EGameplayEffectReplicationMode::Mixed);
//initialize Standard Attributes Set
StandardAttributes = CreateDefaultSubobject<UStandardAttributes>(TEXT("StandardAttributes"));
//increase PlayerState Net update frequency
NetUpdateFrequency = 100.f;
}
void AGatosPlayerState::BeginPlay()
{
Super::BeginPlay();
//Bind Functions to handle stats and tags changes to the delegate in the ASC
if (AbilitySystemComponent && StandardAttributes)
{
//Attributes
AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(StandardAttributes->GetWalkSpeedAttribute()).AddUObject(this, &AGatosPlayerState::HandleWalkSpeedChanged);
//Tags
AbilitySystemComponent->RegisterGameplayTagEvent(FGameplayTag::RequestGameplayTag(FName("State.Status.Passive.Tired")),
EGameplayTagEventType::NewOrRemoved).AddUObject(this, &AGatosPlayerState::TiredTagReceived);
}
}
void AGatosPlayerState::InitializeAttributes()
{
if (AbilitySystemComponent && StandardAttributes)
{
//Apply a gameplay effect to initialize the attributes -> LoadObject not working for some reason
UGameplayEffect* InitEffect = LoadObject<UGameplayEffect>(nullptr, TEXT("D:/Documentos/Unreal Projects/Gatos 5.4/Content/ThirdPerson/Blueprints/GAS/GE_IncreaseHealth.uasset"));
if (InitEffect)
{
FGameplayEffectContextHandle EffectContext = AbilitySystemComponent->MakeEffectContext();
FGameplayEffectSpecHandle SpecHandle = AbilitySystemComponent->MakeOutgoingSpec(InitEffect->GetClass(), 1, EffectContext);
AbilitySystemComponent->ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("ERROR: Could not find InitEffect UAsset!"));
}
}
}
void AGatosPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
//ensure that Ability System and attributes are replicated for the client
DOREPLIFETIME(AGatosPlayerState, AbilitySystemComponent);
DOREPLIFETIME(AGatosPlayerState, StandardAttributes);
}
I hope i didn’t forget anything, but basically this is my implementation of the GAS to change the speed attribute.