Replicated

[Drag Down] 기본세팅 #2 : Attribute, PlayerState 본문

언리얼 엔진/Drag Down (캡스톤 디자인)

[Drag Down] 기본세팅 #2 : Attribute, PlayerState

라구넹 2025. 3. 24. 20:59

Attribute

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

#pragma once

#include "CoreMinimal.h"
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "DDAttributeSet.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 DRAGDOWN_API UDDAttributeSet : public UAttributeSet
{
	GENERATED_BODY()
	
public:
	UDDAttributeSet();

	ATTRIBUTE_ACCESSORS(UDDAttributeSet, Stamina);
	ATTRIBUTE_ACCESSORS(UDDAttributeSet, MaxStamina); 

	// Meta Attribute
	ATTRIBUTE_ACCESSORS(UDDAttributeSet, 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 = "Stamina", ReplicatedUsing = OnRep_Stamina, Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData Stamina;

	UPROPERTY(BlueprintReadOnly, Category = "Stamina", ReplicatedUsing = OnRep_MaxStamina, Meta = (AllowPrivateAccess = true))
	FGameplayAttributeData MaxStamina;

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

	UFUNCTION()
	void OnRep_Stamina(const FGameplayAttributeData& OldStamina);

	UFUNCTION()
	void OnRep_MaxStamina(const FGameplayAttributeData& OldMaxStamina);
};

일단 Stamina와 MaxStamina라는 어트리뷰트를 만들고, Damage는 메타 어트리뷰트로 사용한다.

Stamina와 MaxStamina는 리플리케이션 처리 해준다.

 

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


#include "Attribute/DDAttributeSet.h"
#include "GameplayEffectExtension.h"
#include "Net/UnrealNetwork.h"
#include "DragDown.h"

UDDAttributeSet::UDDAttributeSet() : MaxStamina(100.0f), Damage(0.0f)
{
	InitStamina(GetMaxStamina());
}

void UDDAttributeSet::PreAttributeChange(const FGameplayAttribute& Attribute, float& NewValue)
{
	if (Attribute == GetDamageAttribute())
	{
		NewValue = NewValue < 0.0f ? 0.0f : NewValue;
	}
}

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

	float MinimumStamina = 0.0f;

	if (Data.EvaluatedData.Attribute == GetStaminaAttribute())
	{
		SetStamina(FMath::Clamp(GetStamina(), MinimumStamina, GetMaxStamina()));
		UE_LOG(LogDD, Warning, TEXT("Direct Health Access : %f"), GetStamina());
	}
	else if (Data.EvaluatedData.Attribute == GetDamageAttribute())
	{
		SetStamina(FMath::Clamp(GetStamina() - GetDamage(), MinimumStamina, GetMaxStamina()));
		UE_LOG(LogDD, Log, TEXT("[NetMode : %d] Damage Detected : %f | Now Energy : %f"), GetWorld()->GetNetMode(), GetDamage(), GetStamina());
	}
}

void UDDAttributeSet::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME_CONDITION_NOTIFY(UDDAttributeSet, Stamina, COND_None, REPNOTIFY_Always);
	DOREPLIFETIME_CONDITION_NOTIFY(UDDAttributeSet, MaxStamina, COND_None, REPNOTIFY_Always);
}

void UDDAttributeSet::OnRep_Stamina(const FGameplayAttributeData& OldStamina)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UDDAttributeSet, Stamina, OldStamina);
}

void UDDAttributeSet::OnRep_MaxStamina(const FGameplayAttributeData& OldMaxStamina)
{
	GAMEPLAYATTRIBUTE_REPNOTIFY(UDDAttributeSet, MaxStamina, OldMaxStamina); 
}

PreAttributeChange에서는 이펙트 적용 전 값을 검사한다.

Stamina는 0 미만이 될 수 없다.

 

PostGameplayEffectExecute에선 다시 한 번 스테미나를 검사(굳이 안해도 되긴 함)

그리고 대미지가 들어온 경우 스테미나를 깎아줌 (0미만 불가, 100 초과 불가)

카메라 흔들기 같은 효과가 필요한 경우 PostGameplayEffectExecute Damage 변경 부분에 처리해주면 됨

* 대미지를 받으면 카메라 흔들기

 

GetLifetimeReplicatedProps에서 Stamina와 MaxStamina 동기화 처리

OnRep_Stamina와 OnRep_MaxStamina에서 GAMEPLAYATTRIBUTE_REPNOTIFY를 해주는데,

이걸 안해주면 추후 값 리플리케이션에 의한 UI 업데이트가 제대로 안된다. 필수적으로 해줘야 한다.

 

 

Player State

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "AbilitySystemInterface.h"
#include "DDPlayerState.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API ADDPlayerState : public APlayerState, public IAbilitySystemInterface
{
	GENERATED_BODY()
	
public:
	ADDPlayerState();

	virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;

protected:
	UPROPERTY(EditAnywhere, Category = GAS)
	TObjectPtr<class UAbilitySystemComponent> ASC;

	UPROPERTY()
	TObjectPtr<class UDDAttributeSet> AttributeSet;
};

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


#include "Player/DDPlayerState.h"
#include "AbilitySystemComponent.h"
#include "Attribute/DDAttributeSet.h"

ADDPlayerState::ADDPlayerState()
{
	ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
	ASC->SetIsReplicated(true);
	AttributeSet = CreateDefaultSubobject<UDDAttributeSet>(TEXT("AttributeSet"));
}

UAbilitySystemComponent* ADDPlayerState::GetAbilitySystemComponent() const
{
	return ASC;
}

상태 관리 등을 위해 PlayerState에서 ASC와 AttributeSet을 관리

 

* AttributeSet을 ASC에 등록시키지 않은 이유?

UAbilitySystemComponent::InitializeComponent()에 같은 오브젝트에 붙어있는 어트리뷰트 찾아서 알아서 붙여줌

내가 안해줘도 알아서 해주니까 안해도 됨