Replicated

[ChronoSpace] ** Attribute Replication ** 본문

언리얼 엔진/ChronoSpace

[ChronoSpace] ** Attribute Replication **

라구넹 2025. 2. 17. 04:37

전 게시글에서 Patrol한테 맞아도, 클라이언트에서 UI에서 동기화가 안되어 보이길래 문제를 찾아봤다

단순 UI 문제라기 보다는, 애초에 Replication 설정을 안해놨다

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "CSAttributeSet.generated.h"

/**
 * 
 */

#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
	GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)


UCLASS()
class CHRONOSPACE_API UCSAttributeSet : public UAttributeSet
{
	GENERATED_BODY()

public:

	UCSAttributeSet();

	ATTRIBUTE_ACCESSORS(UCSAttributeSet, Energy);
	ATTRIBUTE_ACCESSORS(UCSAttributeSet, MaxEnergy);
	ATTRIBUTE_ACCESSORS(UCSAttributeSet, Damage);

	virtual void PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue) override;
	//virtual void PostAttributeChange(const FGameplayAttribute& Attribute, float OldValue, float NewValue) override;
	virtual bool PreGameplayEffectExecute(struct FGameplayEffectModCallbackData& Data) override;
	virtual void PostGameplayEffectExecute(const struct FGameplayEffectModCallbackData& Data) override;

protected:
	virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const override;

	UPROPERTY(BlueprintReadOnly, Category = "Energy", ReplicatedUsing = OnRep_Energy, Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData Energy;

	UPROPERTY(BlueprintReadOnly, Category = "Energy", ReplicatedUsing = OnRep_MaxEnergy, Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData MaxEnergy;

	UPROPERTY(BlueprintReadOnly, Category = "Energy", Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData Damage;

	UFUNCTION()
	void OnRep_Energy(const FGameplayAttributeData& OldEnergy);

	UFUNCTION()
	void OnRep_MaxEnergy(const FGameplayAttributeData& OldMaxEnergy);
};

Energy랑 MaxEnergy를 Replication 되도록 했다

Damage는 애초에 서버에서만 계산할 거고 메타 어트리뷰트니까 굳이 안 해주기도 했고,

게임플레이 이펙트로 계산되니까 알아서 되긴 할 거다

 

// Fill out your copyright notice in the Description page of Project Settings.


#include "Attribute/CSAttributeSet.h"
#include "ChronoSpace.h"
#include "GameplayEffectExtension.h"
#include "Player/CSPlayerController.h"
//#include "CoreMinimal.h"
#include "Net/UnrealNetwork.h"

UCSAttributeSet::UCSAttributeSet() : MaxEnergy(100.0f), Damage(0.0f)
{
	InitEnergy(GetMaxEnergy());
}

void UCSAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	
	if (Attribute == GetDamageAttribute())
	{
		NewValue = NewValue < 0.0f ? 0.0f : NewValue;
	}
	
	// -> 최소 데미지 0.0f 으로 설정. 
}

bool UCSAttributeSet::PreGameplayEffectExecute(FGameplayEffectModCallbackData& Data)
{
	return true;
}

void UCSAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data)
{
	Super::PostGameplayEffectExecute(Data);

	float MinimumEnergy = 0.0f;

	if (Data.EvaluatedData.Attribute == GetEnergyAttribute())
	{
		UE_LOG(LogTemp, Warning, TEXT("Direct Health Access : %f"), GetEnergy());
		SetEnergy(FMath::Clamp(GetEnergy(), MinimumEnergy, GetMaxEnergy()));
	}

	if ( Data.EvaluatedData.Attribute == GetDamageAttribute() )
	{
		SetEnergy(FMath::Clamp(GetEnergy() - GetDamage(), MinimumEnergy, GetMaxEnergy()));
		UE_LOG(LogCS, Log, TEXT("[NetMode : %d] Damage Detected : %f | Now Energy : %f"), GetWorld()->GetNetMode(), GetDamage(), GetEnergy());
		AActor* TargetActor = Data.Target.GetAvatarActor();
		if (TargetActor == nullptr) return;

		if (APawn* Pawn = Cast<APawn>(TargetActor))
		{
			ACSPlayerController* PC = Cast<ACSPlayerController>(Pawn->GetController());

			if (PC)
			{
				PC->ShakeCamera();
			}
		}
	}
}

void UCSAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);
	
	DOREPLIFETIME_CONDITION_NOTIFY(UCSAttributeSet, Energy, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(UCSAttributeSet, MaxEnergy, COND_None, REPNOTIFY_Always);
}

void UCSAttributeSet::OnRep_Energy(const FGameplayAttributeData& OldEnergy)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UCSAttributeSet, Energy, OldEnergy);
}

void UCSAttributeSet::OnRep_MaxEnergy(const FGameplayAttributeData& OldMaxEnergy)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UCSAttributeSet, MaxEnergy, OldMaxEnergy);
}

재밌는 점은

이걸 해줘야 GAS 시스템이 알아서 처리해주는게 좀 있는 것 같아

클라이언트 UI 업데이트가 이걸 해줘야 작동하던데 아마

 

 

저 델리게이트 실행시키는 로직이 위에 REPNOTOFY에 포함되어 있지 않나 싶다

 

로그 찍어보면 맞는 것 같긴 하다

 

동기화 잘 되는 걸 확인 가능하다