여러 게임플레이 어트리뷰트를 확인할 수 있는 상태창을 만들어보자.
Widget -> Widget Controller -> Data 방향의 의존성을 가지고 있는 구조는 이전 글에 자세히 나와있다.
여러 개의 위젯 블루프린트를 쌓아서 만드는 편이 모듈화 측면에서 효율적일 것이다. 먼저 숫자를 표시하는 위젯을 만들고, 그 위젯을 포함해서 어트리뷰트의 이름과 함께 하나의 줄을 이루는 Text Value Row를 만든다.
특성 포인트를 할당할 수 있는 버튼이 있는 버전도 함께 만든다.
마찬가지로 이 버튼 또한 별도의 위젯이다.
그리고 이것들을 예쁘게 조합해서 하나의 어트리뷰트 메뉴 창을 만든다.
버튼을 먼저 만들어주고
이 버튼에다가 AttributeMenu 위젯을 만들고 AddtoViewport 하는 커스텀 이벤트 AttributeMenuClicked
를 만들어 붙인다.
아래쪽 시퀀스 노드는 AttributeMenu의 X 버튼을 눌렀을 때 창이 꺼지면서 다시 버튼을 활성화하는 부분이다.
게임플레이 태그를 키로 하여 찾을 수 있는 데이터 에셋을 만든다.
예를 들어 UI상에 어떻게 표시될지를 DisplayName
이라는 변수로 만들 수도 있고, 마우스를 올렸을 때 어떤 태그인지를 알려주는 도움말로 사용될 Comment
등의 변수 등을 하나의 구조체로 묶을 수 있다.
먼저 하나의 구조체를 만들어 주고, 멤버들을 넣는다.
USTRUCT(BlueprintType)
struct FAuraAttributeInfo
{
GENERATED_BODY()
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FGameplayTag AttributeTag = FGameplayTag();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText DisplayText = FText();
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FText AttributeDesc = FText();
UPROPERTY(BlueprintReadOnly)
float AttributeValue = 0.f;
};
태그로 Info를 찾을 수 있는 함수와 함께 데이터 자료구조를 가지고 있는 클래스를 만든다.
UCLASS()
class AURA_API UAttributeInfo : public UDataAsset
{
GENERATED_BODY()
public:
FAuraAttributeInfo FindAttributeInfoByTag(const FGameplayTag& AttributeTag, bool bLogNotFound = false) const;
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
TArray<FAuraAttributeInfo> AttributeInfo;
};
이제 miscellaneous -> Data Asset을 통해 데이터 에셋을 만들어주고 태그 정보들을 기입한다.
WidgetController에서 Widget 방향으로 브로드캐스트 할 때, 이 데이터 에셋에서 정보들을 찾아 전달해주는 용도로 사용할 것이다.
이전에는 GameplayTag를 config -> DefaultGameplayTags.ini
파일을 이용해서 만들었지만, C++상에서 직접적으로 태그를 정의하고 사용할 수 있는 Native한 방식을 사용해보자.
게임 상에서 static하게 존재하는 싱글톤으로 구현할 것이며, 따라서 전역에서 자유롭게 태그에 접근할 수 있도록 한다.
FAuraGameplayTags
라는 이름의 구조체를 만들었으며, static 멤버를 private 섹션에, 이 객체에 접근할 수 있는 Getter 함수를 public으로 만든다.
struct FAuraGameplayTags
{
public:
static const FAuraGameplayTags& Get() {return GameplayTags;}
static void InitializeNativeGameplayTags();
FGameplayTag Attributes_Primary_Strength;
FGameplayTag Attributes_Primary_Intelligence;
FGameplayTag Attributes_Primary_Resilience;
FGameplayTag Attributes_Primary_Vigor;
private:
// Singleton instance
static FAuraGameplayTags GameplayTags;
};
#include "AuraGameplayTags.h"
#include "GameplayTagsManager.h"
// 정적(static) 멤버 변수를 선언하고, 동시에 그것을 인스턴스화
FAuraGameplayTags FAuraGameplayTags::GameplayTags;
void FAuraGameplayTags::InitializeNativeGameplayTags()
{
/*
* Primary Attributes
*/
GameplayTags.Attributes_Primary_Strength = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName("Attributes.Primary.Strength"),
FString("Increases physical damage")
);
GameplayTags.Attributes_Primary_Intelligence = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName("Attributes.Primary.Intelligence"),
FString("Increases magical damage")
);
GameplayTags.Attributes_Primary_Resilience = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName("Attributes.Primary.Resilience"),
FString("Increases Armor and Armor Penetration")
);
GameplayTags.Attributes_Primary_Vigor = UGameplayTagsManager::Get().AddNativeGameplayTag(
FName("Attributes.Primary.Vigor"),
FString("Increases Health")
);
}
OverlayWidgetController와 마찬가지로, 거의 유사하게 동작하는 AttributeMenuWidgetController
클래스를 만든다. 방금 만든 AttributeInfo ObjectPtr 멤버와 델리게이트 선언, 이를 브로드캐스트하는 함수를 만든다.
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FAttributeInfoSignature, const FAuraAttributeInfo&, Info);
UCLASS(BlueprintType, Blueprintable)
class AURA_API UAttributeMenuWidgetController : public UAuraWidgetController
{
GENERATED_BODY()
public:
virtual void BroadcastInitialValues() override;
UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes")
FAttributeInfoSignature AttributeInfoDelegate;
protected:
UPROPERTY(EditDefaultsOnly)
TObjectPtr<UAttributeInfo> AttributeInfo;
};
...
void UAttributeMenuWidgetController::BroadcastInitialValues()
{
const UAuraAttributeSet* AS = Cast<UAuraAttributeSet>(AttributeSet);
check(AttributeInfo);
FAuraAttributeInfo Info = AttributeInfo->FindAttributeInfoByTag(FAuraGameplayTags::Get().Attributes_Primary_Strength);
Info.AttributeValue = AS->GetStrength();
AttributeInfoDelegate.Broadcast(Info);
}
우선 Strength 태그에 대한 초기값 설정만을 하기로 하고 BroadcastInitValue
함수를 만든다. AttributeInfo에서 태그를 찾아서 데이터 에셋을 찾은 후에, 이 값을 그대로 AttributeInfoDelegate
로 브로드캐스트한다.
이벤트가 발생할 때 Label Text를 갱신하는 식으로 사용
GetAttributeMenuWidgetController
는 WidgetController를 쉽게 연결하기 위해 만든 blueprint pure 함수이다.
UAttributeMenuWidgetController* UAuraAbilitySystemLibrary::GetAttributeMenuWidgetController(const UObject* WorldContextObject)
{
if (APlayerController* PC = UGameplayStatics::GetPlayerController(WorldContextObject, 0))
{
if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(PC->GetHUD()))
{
AAuraPlayerState* PS = PC->GetPlayerState<AAuraPlayerState>();
UAbilitySystemComponent* ASC = PS->GetAbilitySystemComponent();
UAttributeSet* AS = PS->GetAttributeSet();
const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
return AuraHUD->GetAttributeMenuWidgetController(WidgetControllerParams);
}
}
return nullptr;
}
이제 어트리뷰트 창이 열리고 난 뒤에 위젯 컨트롤러를 연결한 후, BroadcastInitialValues()
함수를 통해 Strength 태그에 대한 정보가 브로드캐스트되었고, 이 브로드캐스트를 통해 UI에 반영된다.
각 row마다 어떤 태그의 변화를 받을지를 아직 명시하지 않았기 때문에 모든 row가 strength 태그에 따라 바뀌고 있는 것을 볼 수 있다.
이제 각각의 row 위젯이 특정한 태그에만 반응하여 그 값을 변경하도록 만들면 된다.
row 위젯에 GameplayTag 변수를 추가하고, 블루프린트에 노출한다.
그리고 attribute menu 위젯에서 이 태그들을 모두 다르게 설정해준다.
void UAttributeMenuWidgetController::BroadcastAttributeInfo(const FGameplayTag& AttributeTag,
const FGameplayAttribute& Attribute) const
{
FAuraAttributeInfo Info = AttributeInfo->FindAttributeInfoByTag(AttributeTag);
Info.AttributeValue = Attribute.GetNumericValue(AttributeSet);
AttributeInfoDelegate.Broadcast(Info);
}
마지막으로 row 위젯의 AttributeInfoDelegate에서 Matches Tag
옵션을 추가함으로써, 각 row가 가지고 있는 태그와 일치하는 태그가 파라미터일 때만 값을 갱신하도록 수정한다.
브로드캐스트하는 파라미터의 경우 AttributeInfo GameAsset에서 Tag를 기반으로 찾은 결과를 담아 전달한다.
그러면 각 row가 서로 다른 태그에만 반응하여 보여주고 있는 것을 볼 수 있다.