Replicated

[Drag Down] 멀티 환경에서 스테미나 UI 제작 및 GAS 연결 본문

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

[Drag Down] 멀티 환경에서 스테미나 UI 제작 및 GAS 연결

라구넹 2025. 4. 2. 19:33

일단 UI는 플레이어 컨트롤러에다가 달아둘 거다

일단 만들어만 주자

 

그리고 GAS와 연결해야 하니, 세팅을 좀 많이 해줘야 한다

정확히는, 위젯과 위젯 컴포넌트를 확장해야 한다

 

일단 위젯을 확장하자

 

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

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "AbilitySystemInterface.h"
#include "DDGASUserWidget.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API UDDGASUserWidget : public UUserWidget, public IAbilitySystemInterface
{
	GENERATED_BODY()
	
public:
	virtual void SetOwner(AActor* InOwner);
	virtual void SetAbilitySystemComponent(AActor* InOwner);
	virtual class UAbilitySystemComponent* GetAbilitySystemComponent() const override;

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

	UPROPERTY()
	TObjectPtr<AActor> Owner;
};

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


#include "UI/DDGASUserWidget.h"
#include "AbilitySystemBlueprintLibrary.h"

void UDDGASUserWidget::SetOwner(AActor* InOwner)
{
	if (IsValid(InOwner))
	{
		Owner = InOwner;
	}
}

void UDDGASUserWidget::SetAbilitySystemComponent(AActor* InOwner)
{
	if (IsValid(InOwner))
	{
		ASC = UAbilitySystemBlueprintLibrary::GetAbilitySystemComponent(InOwner);
	}
}

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

일단 위젯에 오너를 설정할 수 있도록 하고, 해당 오너에서 어빌리티 시스템 컴포넌트를 뜯어올 수 있도록 한다

 

 

다음은 위젯 컴포넌트를 확장한다

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

#pragma once

#include "CoreMinimal.h"
#include "Components/WidgetComponent.h"
#include "DDGASWidgetComponent.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API UDDGASWidgetComponent : public UWidgetComponent
{
	GENERATED_BODY()
	
public:
	void ActivateGAS(); 

protected:
	virtual void InitWidget() override; 
};

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


#include "UI/DDGASWidgetComponent.h"
#include "UI/DDGASUserWidget.h"
#include "DragDown.h"

void UDDGASWidgetComponent::ActivateGAS()
{
	UDDGASUserWidget* GASUserWidget = Cast<UDDGASUserWidget>(GetWidget());
	if (GASUserWidget)
	{
		GASUserWidget->SetAbilitySystemComponent(GetOwner());
	}
	else
	{
		UE_LOG(LogDD, Log, TEXT("ActivateGAS Failed - No GASUserWidget"));
	}
}

void UDDGASWidgetComponent::InitWidget()
{
	Super::InitWidget();
	//UE_LOG(LogCS, Log, TEXT("InitWidget IS Called"));
	UDDGASUserWidget* GASUserWidget = Cast<UDDGASUserWidget>(GetWidget());
	if (GASUserWidget)
	{
		GASUserWidget->SetOwner(GetOwner());
	}
	else
	{
		UE_LOG(LogDD, Log, TEXT("InitWidget Failed - No GASUserWidget"));
	}
}

위젯 컴포넌트다

 

InitWidget에서 처음에 위젯에 오너를 설정하고, ActivateGAS에서 ASC 설정.. 인데,

캐릭터에 붙어 다니는 거면 위젯 컴포넌트가 필요한데 단순히 화면에 붙여서 띄우는 건 필요가 없다

그냥 만들어놓고 나중에 쓰자 (이름같은 거 넣을 때는 필요할 것)

 

어찌 되었든, 위에서 만든 GAS 위젯 기반으로 위젯을 새로 만들자

 

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

#pragma once

#include "CoreMinimal.h"
#include "UI/DDGASUserWidget.h"
#include "GameplayEffectTypes.h"
#include "DDGASStaminaBarUserWidget.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API UDDGASStaminaBarUserWidget : public UDDGASUserWidget
{
	GENERATED_BODY()
	
public:
	virtual void SetAbilitySystemComponent(AActor* InOwner) override;

	void UpdateStaminaBar();
protected:
	virtual void OnStaminaChanged(const FOnAttributeChangeData& ChangeData);
	virtual void OnMaxStaminaChanged(const FOnAttributeChangeData& ChangeData);

protected:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<class UProgressBar> PbStaminaBar;

	float CurrentStamina = 0.0f;

	float CurrentMaxStamina = 0.1f;
};

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


#include "UI/DDGASStaminaBarUserWidget.h"
#include "AbilitySystemComponent.h"
#include "Attribute/DDAttributeSet.h"
#include "Components/ProgressBar.h"
#include "DragDown.h"

void UDDGASStaminaBarUserWidget::SetAbilitySystemComponent(AActor* InOwner)
{
	Super::SetAbilitySystemComponent(InOwner);

	if (ASC)
	{
		ASC->GetGameplayAttributeValueChangeDelegate(UDDAttributeSet::GetStaminaAttribute()).AddUObject(this, &UDDGASStaminaBarUserWidget::OnStaminaChanged);
		ASC->GetGameplayAttributeValueChangeDelegate(UDDAttributeSet::GetMaxStaminaAttribute()).AddUObject(this, &UDDGASStaminaBarUserWidget::OnMaxStaminaChanged);
		//UE_LOG(LogCS, Log, TEXT("[NetMode : %d] SetAbilitySystemComponent"), GetWorld()->GetNetMode());
		const UDDAttributeSet* CurrentAttributeSet = ASC->GetSet<UDDAttributeSet>();

		if (CurrentAttributeSet)
		{
			CurrentStamina = CurrentAttributeSet->GetStamina();
			CurrentMaxStamina = CurrentAttributeSet->GetMaxStamina();

			if (CurrentMaxStamina > 0.0f)
			{
				UpdateStaminaBar();
			}
			else
			{
				UE_LOG(LogDD, Warning, TEXT("CurrentMaxEnergy is 0"));
			}
		}
		else
		{
			UE_LOG(LogDD, Warning, TEXT("CurrentAttributeSet is null!"));
		}
	}
	else
	{
		UE_LOG(LogDD, Warning, TEXT("ASC is null! Ensure that the Ability System Component is properly initialized before calling this function."));
	}
}

void UDDGASStaminaBarUserWidget::UpdateStaminaBar()
{
	if (PbStaminaBar)
	{
		UE_LOG(LogDD, Log, TEXT("UpdateEnergyBar : %f / %f"), CurrentStamina, CurrentMaxStamina); 
		PbStaminaBar->SetPercent(CurrentStamina / CurrentMaxStamina);
	}
}

void UDDGASStaminaBarUserWidget::OnStaminaChanged(const FOnAttributeChangeData& ChangeData)
{
	UE_LOG(LogDD, Log, TEXT("[NetMode %d]OnEnergyChanged"), GetWorld()->GetNetMode());
	CurrentStamina = ChangeData.NewValue;
	UpdateStaminaBar();
}

void UDDGASStaminaBarUserWidget::OnMaxStaminaChanged(const FOnAttributeChangeData& ChangeData)
{
	CurrentMaxStamina = ChangeData.NewValue;
	UpdateStaminaBar();
}

GAS랑 연동해서 Stamina가 변경되면 알아서 변경되도록 델리게이트에 연결해둔다

그렇게 만든 클래스 기반으로 위젯을 만들어보자

 

패널 밑에 스테미나 바 위치 (이름 반드시 코드와 일치 필요)

 

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

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "DDPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class DRAGDOWN_API ADDPlayerController : public APlayerController
{
	GENERATED_BODY()
	
public:
	ADDPlayerController();

protected:
	virtual void BeginPlayingState() override;

protected:
	void InitGASWidget();

	// UMG 위젯 클래스 (블루프린트에서 설정)
	UPROPERTY(EditDefaultsOnly, Category = "UI")
	TSubclassOf<class UDDGASStaminaBarUserWidget> StaminaBarWidgetClass;

	UPROPERTY()
	TObjectPtr<class UDDGASStaminaBarUserWidget> StaminaBarWidget;
};

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


#include "Player/DDPlayerController.h"
#include "UI/DDGASStaminabarUserWidget.h"
#include "AbilitySystemComponent.h"
#include "DragDown.h"

ADDPlayerController::ADDPlayerController()
{
}

void ADDPlayerController::BeginPlayingState()
{
	Super::BeginPlayingState();

	if (IsLocalController())
	{
		InitGASWidget();
	}
}

void ADDPlayerController::InitGASWidget()
{
	UE_LOG(LogDD, Log, TEXT("InitGASWidget Start"));
	if ( StaminaBarWidgetClass == nullptr || StaminaBarWidget != nullptr) return;
	UE_LOG(LogDD, Log, TEXT("InitGASWidget Start - 2"));
	StaminaBarWidget = CreateWidget<UDDGASStaminaBarUserWidget>(this, StaminaBarWidgetClass);

	if (StaminaBarWidget == nullptr)
	{
		UE_LOG(LogDD, Log, TEXT("InitGASWidget: StaminaBarWidget Creation Is Failed"));
		return;
	}

	StaminaBarWidget->AddToViewport();

    APawn* ControlledPawn = GetPawn();
    if (!ControlledPawn)
    {
        UE_LOG(LogDD, Log, TEXT("InitGASWidget: No Pawn"));
        return;
    }

    StaminaBarWidget->SetAbilitySystemComponent(ControlledPawn);

    
}

플레이어 컨트롤러에선 InitGASWidget에서 위젯을 만들고 어빌리티 시스템을 설정해준다

BeginPlayingState에서 설정해주면 되는데, 이 시점은 컨트롤러가 폰에 빙의하고, 본격적으로 시작 가능한 시점에서 생성된다. 즉, 클라이언트가 설정하기 최적의 타이밍이다.

 

 

동기화까지 잘 되어 실행되는 것을 알 수 있다.

 

다음은 일정 시간마다 자동으로 스테미나를 회복하도록 하자.