GameplayTag 는 일종의 이름으로써 FGameplayTag 타입이고, Tag Manager에 의해 등록된다.
GameplayTag 는 계층적인 구조로 이루어져 있다.
예시
Door
Abilityes.Fire
GameplayCue.Ability.fireBolt.Impact
GameplayTag 는 FString이나 FName 또는 ENum과 같은 것들을 사용하지 않고 고유의 이름을 가지므로 좀 더 유연한 개발이 가능하다.
GameplayTag 는 GameplayTag Container 라는 컨테이너에 TArray대신 저장가능하다.
ASC에서 IGameplayTagAssetInterface 인터페이스 호출을 통해 GameplayTag 의 여러 기능을 사용할 수 있다.
- GetOwnedGameplayTags
- HasMatchingGamepalyTag
- HasAllMatchingGameplayTags
- HasAnyMatchingGameplayTags
...
GameplayTag 는 여러가지로 사용될 수 있다.
예시
- Inputs
- Abilities
- Attributes
- Damage Types
- Buff/Debuffs
- Messages
- Data
- 기타 등등
GameplayTag 를 에디터에 바로 추가하거나 데이터 테이블을 통해 추가할 수 있다.
먼저 에디터에 바로 추가하는 방법이다.
Project Settings -> GameplayTags -> Manage Gameplay Tags... 창을 연다.

+ 키를 누르고 Name과 Comment를 작성한 뒤, Source : DefaultGameplayTags.ini 로 설정하고 엔터를 누르면 GameplayTag 가 생성된다.(Primary탭은 6.3에서 진행)


태그를 추가하고 싶을 경우, Name탭에 직접 입력해도 되지만 상위 계층(여기서는 Vital) 옆의 화살표 버튼을 클릭하고 Add Sub Tag 를 클릭하면 앞부분은 자동생성되므로 뒤에 추가할 부분만 타이핑을 하여 생성하는 것도 가능하다.




동일하게 MaxHealth 와 MaxMana Tag도 생성해준다.

위의 방식으로 추가된 Tag에 코멘트를 추가하고 싶다면 경로상의 Config 파일에 있는 DefaultGamepalyTags 파일을 메모장으로 연 다음 추가 가능하다.


먼저 기본적인 Attribute 들의 GameplayTag 를 만들기 위한 데이터 테이블을 생성한다.



상단의 + Add 버튼을 누르면 태그 추가가 가능하다.

하단에서 Tag명과 Comment 추가가 가능하다.


동일한 방법으로 필요한 태그들을 더 추가한다.

데이터 테이블을 통해 생성한 태그를 사용하기 위해서는 Project Settings -> GameplayTags -> Gameplay Tag Table List 에 방금 생성한 DT_PrimaryAttributes 를 추가해주어야 한다.

확인해보면 Attributes.Primary 의 하위 계층에 추가된 것을 확인할 수 있다.

GameplayEffect 는 GameplayTag 를 소유할 수 있고, 수많은 타입의 태그들이 존재한다.

- Assert Tags Gameplay Effect Comopnent
즉시 적용되는 GameplayEffect에 대해서 태그가 적용
특정 GameplayEffect가 어떤 조건 하에서 발동될지, 어떤 상태에서 영향을 받는지를 정의하는 역할
- Combined Tags
상속받은 태그와 추가된 태그에서 제거된 태그를 뺀 최종적인 태그 집합을 의미
A태그(상속받음) + B태그(추가됨) - C태그(제거한 태그)
GameplayEffect를 적용할 때 사용됨- Added
현재 GameplayEffect에 새로 추가된 태그들
상속받지 않고 직접적으로 추가됨
Combined Tags에 포함됨- Removed
현재 GameplayEffect에서 제거된 태그들
상속받거나 추가된 태그 중에서 제외된 태그들
Combined Tags에서 제외됨- Target Tags GameplayEffect Component
기간이 있는 GameplayEffect에 대해서 태그가 적용
- Combined Tags
Assert Tags Gameplay Effect Comopnent와 동일- Added
- Removed
확인을 위해 GE_CrystalHeal 에서 Added : Attributes.Vital.Health , Scalable Float Magnitude : 4 (1로 하면 짧아서 확인하기 어려움)로 설정하고 실행하면 태그가 생기는 것을 확인할 수 있다.


또한 스택이 적용된 GameplayEffect 가 여러번 적용될 경우 하나의 Tag만 발생하지만, 스택이 적용되지 않은 GameplayEffect 가 여러번 적용될 경우 여러 Tag가 발생한다.


ASC는 상속받은 델리게이트들이 있다. 해당 델리게이트를 통한 바인드를 통해 GameplayEffect 를 적용시킬 수 있다.
물약 효과를 적용시키기 위한 델리게이트인
FOnGameplayEffectAppliedDeligate OnGameplayEffectAppliedDelegateToSelf
가 필요하다.
FOnGameplayEffectAppliedDeligate 의 정의를 살펴보면 아래 코드와 같다.
DECLARE_MULTICAST_DELEGATE_THREEPARAM(FOnGameplayEffectAppliedDelegate, UAbilitySystemComponent*, const FGameplayEffectSpec&, FActiveGameplayEffectHandle);
먼저 ASC에서 GameplayEffect 를 적용시키기 위한 함수를 생성한다.
// AuraAbilitySystemComponent.h
...
protected:
void EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& EffectSpec, FActiveGameplayEffectHandle ActiveEffectHandle);
...
// AuraAbilitySystemComponent.cpp
void UAuraAbilitySystemComponent::EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& EffectSpec, FActiveGameplayEffectHandle ActiveEffectHandle)
{
}
이제 콜백함수가 생겼으므로 델리게이트에 바인딩할 수 있다.
바인딩은 보통 게임이 시작할 때 되도록 생성자에서 바인딩하는 것을 선호하지만, 생성자에서 하는 것은 너무 일찍 실행되기 때문에 초기의 어떤 지점에서 바인딩하도록 하는 것이 좋다.
AuraCharacter 클래스에 있는 InitAbilityActorInfo() 함수가 그 예이다.
GameplayEffect 는 캐릭터와 적 양쪽 다 적용가능하기 때문에 InitAbilityActorInfo() 함수를 부모클래스인 AuraCharacterBase 클래스로 옮기고 상속받도록 코드를 수정한다.
// AuraCharacterBase.h
...
protected:
...
virtual void InitAbilityActorInfo();
// AuraCharacterBase.cpp
...
void AAuraCharacterBase::InitAbilityActorInfo()
{
// 선언만
}
// Auracharacter.h
...
private:
/** 코드 수정 */
// AuraCharacterBase으로부터 상속받게 수정하기 위해 virtual 사용
virtual void InitAbilityActorInfo() override;
/** 코드 수정 */
// AuraEnemy.h
...
protected:
...
virtual void InitAbilityActorInfo() override;
// AuraEnemy.cpp
...
void AAuraEnemy::BeginPlay()
{
Super::BeginPlay();
// AbilitySystemComponent->InitAbilityActorInfo(this, this); InitAbilityActorInfo()로 옮기기
InitAbilityActorInfo();
}
void AAuraEnemy::InitAbilityActorInfo()
{
AbilitySystemComponent->InitAbilityActorInfo(this, this);
}
이제 AuraAbilitySystemComponent클래스에서 함수 호출을 위한 함수를 하나 생성한다.
// AuraAbilitySystemComponent.h
...
public:
void AbilityActorInfoSet();
...
// AuraAbilitySystemComponent.cpp
...
void UAuraAbilitySystemComponent::AbilityActorInfoSet()
{
OnGameplayEffectAppliedDelegateToSelf.AddUObject(this, &UAuraAbilitySystemComponent::EffectApplied);
}
void UAuraAbilitySystemComponent::EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectdSpec& EffectSpec, FActiveGameplayEffectHandle ActiveEffectHandle)
{
GEngine->AddOnScreenDebugMessage(1, 8.f, FColor::Blue, FString("Effect Applied"));
}
// AuraCharacter.cpp
...
#include "AbilitySystem/AuraAbilitySystemComponent.h"
...
...
void AAuraCharacter::InitAbilityActorInfo()
{
AAuraPlayerState* AuraPlayerState = GetPlayerState<AAuraPlayerState>();
check(AuraPlayerState);
AuraPlayerState->GetAbilitySystemComponent()->InitAbilityActorInfo(AuraPlayerState, this);
/** 코드 추가 */
Cast<UAuraAbilitySystemComponent>(AuraPlayerState->GetAbilitySystemComponent())->AbilityActorInfoSet();
/** 코드 추가 */
AbilitySystemComponent = AuraPlayerState->GetAbilitySystemComponent();
AttributeSet = AuraPlayerState->GetAttributeSet();
if (AAuraPlayerController* AuraPlayerController = Cast<AAuraPlayerController>(GetController()))
{
if (AAuraHUD* AuraHUD = Cast<AAuraHUD>(AuraPlayerController->GetHUD()))
{
AuraHUD->InitOverlay(AuraPlayerController, AuraPlayerState, AbilitySystemComponent, AttributeSet);
}
}
}
// AuraEnemy.cpp
...
void AAuraEnemy::InitAbilityActorInfo()
{
...
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->AbilityActorInfoSet();
}
컴파일 후 실행하여 크리스탈같은 액터에 오버랩 발생시 스크린에 디버그 메세지가 나타나는 것을 확인할 수 있다.

추가
오류발생시InitAbilityActorInfo()가 아닌InitAbilityActerInfo()오타때문일 확률 높으므로 해당 부분 확인 필요
ASC에서 Widget Controller로 아이템을 Broadcast하고, Widget Controller를 통해 HUD에 표시하게 한다.
먼저 아이템 획득시 디버그 메세지가 출력되도록 코드를 작성한다.
// AuraAbilitySystemComponent.cpp
...
void UAuraAbilitySystemComponent::EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& EffectSpec, FActiveGameplayEffectHandle ActiveEffectHandle)
{
// 스크린 디버그 메세지 코드 삭제
FGameplayTagContainer TagContainer;
EffectSpec.GetAllAssetTags(TagContainer);
for(const FGameplayTag& Tag : TagContainer)
{
// TODO: 나중에 WidgetController로 태그 broadcast
const FString Msg = FString::Printf(TEXT("GE Tag: %s"), *Tag.ToString());
GEngine->AddOnScreenDebugMessage(-1, 8.f, FColor::Blue, Msg);
}
}
컴파일 후 에디터로 돌아와서, 기간을 두고 GameplayEffect 를 적용시키는 GE_CrystalMana 파일을 열고, Asset Tags Gameplay Effect Component 로 설정한 뒤 Added 에 Attributes.Vital.Mana 를 적용시킨다.
TODO: 캡쳐그림 마나로 수정

즉시 GameplayEffect 가 적용되는 GE_PotionHeal 과 GE_PotionMana 에도 동일하게 적용시켜주고


실행하면 정상적으로 해당 아이템이 스크린에 디버그 메세지로 출력되는 것을 확인할 수 있다.



여러 태그를 가지게 하는 것도 가능하다.


이제 OverlayWidgetController 로 Tag 를 Broadcast하기 위한 델리게이트가 필요하다.
// AuraAbilitySystemComponent.h
...
DECLARE_MULTICAST_DELEGATE_OneParam(FEffectAssetTags, const FGameplayTagContainer&);
...
public:
...
FEffectAssetTags EffectAssetTags;
// AuraAbilitySystemComponent.cpp
...
void UAuraAbilitySystemComponent::EffectApplied(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayEffectSpec& EffectSpec, FActiveGameplayEffectHandle ActiveEffectHandle)
{
FGameplayTagContainer TagContainer;
EffectSpec.GetAllAssetTags(TagContainer);
/** 코드 추가 */
EffectAssetTags.Broadcast(TagContainer);
/** 코드 추가 */
for(...)
{
...
}
}
WidgetController에서 Broadcast된 태그를 받기 위한 코드도 작성한다.
// OverlayWidgetContainer.cpp
...
#include "AbilitySystem/AuraAbilitySystemComponent.h"
...
void UOverlayWidgetContainer::BindCallbacksToDependencies()
{
...
// 람다를 이용한 익명함수로 콜백
// AuraAbilitySystemComponent에서 EffectAssetTag Broadcast 발생시 람다함수 실행
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[](const FGameplayTagContainer& AssetTags)
{
// void UAuraAbilitySystemComponent::EffectApplied() 내의 for문 잘라서 붙여넣기
for(const FGameplayTag& Tag : AssetTags)
{
const FString Msg = FString::Printf(TEXT("GE Tag: %s"), *Tag.ToString());
GEngine->AddOnScreenDebugMessage(-1, 8.f, FColor::Blue, Msg);
}
}
);
}
컴파일 후 실행시 정상적으로 Broadcast되는 것을 확인할 수 있다.
(AuraAbilitySystemComponent 클래스의 for문 제거 안해서 OverlayWidgetController의 for문과 같이 나오기 때문에 같은 디버그메세지 두번 출력됨)

이제 UI Widget에서 Asset들에 대한 데이터 테이블을 가지도록 코드를 수정할 것이다.
// OverlayWidgetComponent.h
...
// FGameplayTag 사용하려면 필요함
#include "GameplayTagContainer.h"
...
class UAuraUserWidget;
...
DECLARE_...
struct FOnAttributeChangeData;
USTRUCT(BlueprintType)
struct FUIWidgetRow : public FTableRowBase
{
GENERATED_BODY()
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FGameplayTag MessageTag = FGameplayTag();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
FText Message = FText();
UPROPERTY(EditAnywhere, BlueprintReadOnly)
TSubclassOf<UAuraUserWidget> MessageWidget;
UPROPERTY(EditAnywhere, BlueprintReadOnly)
UTexture2D* Image = nullptr;
};
...
에디터로 돌아와서 생성한 데이터 테이블 기반의 블루프린트를 생성한다.


그리고 Project Settings 에서 새로운 GameplayTag 를 추가해준다.



이제 OverlayWidgetController 클래스에서 생성한 데이터테이블을 사용할 수 있도록 코드를 추가한다.
// OverlayWidgetController.h
...
protected:
...
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = "Widget Data")
TObjectPtr<UDataTable> MessageWidgetDataTable;
컴파일 후 에디터로 돌아와서 BP_OverlayWodgetController 파일에 DT_MessageWidgetData 데이터 테이블을 추가한다.

이제 DT_MessageWidgetData 에 데이터를 추가해주어야 한다.
먼저 RowName 을 Message.HealthCrystal 로 설정하고, MessageTag : Message.HealthCrystal 로 설정한다.
Message 는 Picked up a Health Crystal 로 지정해주고, Image를 추가한다.

HealthPotion , ManaCrystal , ManaPotion 에 대해서도 동일한 과정을 진행한다.



GameplayEffect 가 Message Tag 를 가지도록 해야 한다.GE_PotionHeal 파일을 열고, 기존에 추가한 Tag를 삭제한 다음, Message.HealthPotion 을 추가한다.
GE_PotionMana , GE_CrystalHeal , GE_CrystalMana 도 동일한 과정을 진행한다.


Message.* 디버그 메세지가 출력되는 것을 확인할 수 있다.
이제 GameplayTag 에 따라 그에 알맞는 DataTableRow를 얻도록 하는 함수가 필요하다.
// OverlayWidgetController.h
...
protected:
...
template<typename T>
T* GetDataTableRowByTag(UDataTable* DataTable, const FGameplayTag& Tag);
...
};
template<typename T>
T* UOverlayWidgetController::GetDataTableRowByTag(UDataTable* DataTable, const FGameplaytag& Tag)
{
// 데이터테이블의 특정 row(행) 을 검색하고, 해당 행의 데이터를 반환함
// Tag.GetTagName(): 검색하려는 행의 키
// TEXT(""): 함수 호출에 대한 컨텍스트 문자열, 디버깅용으로 사용, 빈문자열 전달은 선택사항
return DataTable->FindRow<T>(Tag.GetTagName(), TEXT(""));
}
// OverlayWidgetController.cpp
...
void UOverlayWidgetContainer::BindCallbacksToDependencies()
{
...
// 람다를 이용한 익명함수로 콜백
/**코드 수정 : []캡쳐 변수 추가, this로 자기 자신을 캡쳐함으로써 멤버 함수인 GetDataTableRowByTag() 사용 가능 */
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[this](const FGameplayTagContainer& AssetTags)
{
// void UAuraAbilitySystemComponent::EffectApplied() 내의 for문 잘라서 붙여넣기
for(const FGameplayTag& Tag : TagContainger)
{
const FString Msg = FString::Printf(TEXT("GE Tag: %s"), *Tag.ToString());
GEngine->AddOnScreenDebugMessage(-1, 8.f, FColor::Blue, Msg);
/** 코드 추가 */
// Boradcast에 반응하여 반복문을 통해 Tag에 따라 MessageWidgetDataTable의 row(행) 반환
FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);
/** 코드 추가 */
}
}
);
}
반환된 row를 Broadcast하기 위한 델리게이트를 하나 생성한다.
// OverlayWidgetController.h
class UAuraUserWidget;
// 구조체 선언 위치 위로 이동
struct...
...
DECLARE_...
// FUIWigdetRow에 대한 선언 및 정의가 델리게이트 선언보다 아래에 있으므로 구조체 선언 위치 변경
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMessageWidgetRowSignature, FUIWidgetRow, Row);
...
public:
...
UPROPERTY(BlueprintAssignable, Category = "GAS|Messages")
FMessageWidgetRowSignature MessageWidgetRowDelegate;
델리게이트를 통한 Broadcast 전에 태그가 Message(Message.ManaPotion과 같은 태그이므로)와 일치하는지 확인한 후 Broadcast하도록 코드를 수정한다.
// OverlayWidgetController.cpp
void UOverlayWidgetController::BindCallbakcsToDependencies()
{
...
Cast<UAuraAbilitySystemComponent>(AbilitySystemComponent)->EffectAssetTags.AddLambda(
[this](const FGameplayTagContainer& AssetTags)
{
for (const FGameplayTag& Tag : AssetTags)
{
/** 코드추가 및 수정 */
// RequestGameplayTag(): 지정된 FName을 사용하여 FGameplayTag를 반환하는 스태틱 함수
// "Mesage" 태그가 존재하는 경우 해당 태그 반환
FGameplayTag MessageTag = FGameplayTag::RequestGameplayTag(FName("Message"));
// 태그가 MessageTag와 일치하는지 확인, 일치할 경우 Row의 Broadcast 진행
if(Tag.MatchesTag(MessageTag))
{
// Boradcast에 반응하여 반복문을 통해 Tag에 따라 MessageWidgetDataTable의 row(행) 반환
const FUIWidgetRow* Row = GetDataTableRowByTag<FUIWidgetRow>(MessageWidgetDataTable, Tag);
// FUIWidgetRow* Row 이므로 *Row 형태로 전달
MessageWidgetRowDelegate.Broadcast(*Row);
}
/** 코드추가 및 수정*/
/** 코드 삭제 */
const FString Msg = FString::Printf(TEXT("GE Tag: %s"), *Tag.ToString());
GEngine->AddOnScreenDebugMessage(-1, 8.f, FColor::Blue, Msg);
/** 코드 삭제 */
}
}
);
WBP_Overlay 의 그래프 탭을 살펴보면 WBP_Overlay 가 준비되자마자 이벤트를 발생시켜 Health 와 Mana 의 WidgetController 를 set하도록 되어 잇다.
해당 이벤트노드에 델리게이트를 연결하여 WBP_Overlay 가 준비됨과 동시에 델리게이트 호출시 Row 를 Broadcast 하도록 수정한다.
우선 Sequence 노드를 생성하여 Then0 에는 WidgetController 를 BP_OverlayWidgetController 로 캐스팅하고 이를 BP_OverlayWidgetController 라는 변수로 승격시키고, Then1 에는 기존의 노드를 연결시킨다.

Then2 에서는 변수로 승격시킨 BP_OverlayWidgetcontroller 를 통해 델리게이트 호출시 Broadcast된 row에 있는 Message를 출력하도록 노드를 추가한다.
(BP_OverlayWidgetcontroller 에서 Assign Message Widget Row Delegate 검색)

컴파일 후 실행시 태그에 따른 메세지가 출력되는 것을 확인할 수 있다.

이제 최종적으로 MessageWidget 을 추가하여 Widget을 HUD에 출력할 일만 남았다.

먼저 화면에 출력할 위젯을 만들기 위한 Widget Blueprint 를 생성한다.



생성한 WBP_MessageWidget 은 아이템 획득시 어떤 아이템을 획득하였는지 표시해주도록 할 것이다.
Horizontal Box 를 생성해주고

사이즈를 custom으로 설정해주고

Image 를 생성해주고

Text 를 추가해준 뒤

텍트스 내용을 수정한다.

텍스트 크기에 맞게 HorizontalBox_Root 의 사이즈를 수정하고

Image_Icon 선택후 이미지를 추가해준다음, 이미지 사이즈를 적절히 수정한다.
(예시에서는Image Size: X, Y 75로 한 후, 화살표를 움직여 사이즈 수정)

텍스트의 위치를 중앙에 두도록 Vertical Alignment 를 수정하고

폰트를 추가하고 Outline 을 추가하여 좀 더 실제 게임처럼 보이도록 한다.
( Font Family : Amarante-Regular_Font , Outline Size : 1 )

Spacer 를 추가하여 텍스트와 이미지 사이에 살짝 공간을 둔다.
( Appearance : 18 )


이제 WBP_EffectdMessage 가 잘 생성되었는지 확인하기 위해 WBP_Overlay 에 해당 Widget을 추가하여 확인한다.


정상적으로 출력되는 것을 확인했다면 아이템에 따라 이미지를 출력하도록 함수를 하나 만든다.
그전에 WBP_EffectMessage 에 추가해둔 Image_Icon 과 Text_Message 를 변수화하여서 노드로 사용가능하도록 한다.


SetImageAndText 함수를 하나 만들어준 다음 Input으로 Texture 2D -> Object Reference 와 Text 를 받도록 하고

변수화한 Image_Icon 을 Target으로 하고, 함수에서 파라미터로 받은 이미지를 Iamage_Icon 으로 출력하도록 노드를 구성한다.

이미지 사이즈를 정해주기 위해 Vector 2D 타입의 변수 ImageSize 를 생성하고

크기를 지정해준다.(컴파일 해야 Image Size 입력란 나옴)

TextMessage 도 동일하게 노드를 추가한다.

이제 생성해둔 함수를 이용해 WBP_Overlay 에서 델리게이트를 통해 Broadcast된 값들을 파라미터로 사용하여 해당 위젯을 출력하기만 하면 된다.
그전에 생성해둔 데이터테이블에서 비워둔 Message Widget 에 생성한 WBP_EffectMessage 를 추가해준다.

다시 WBP_Overlay 로 돌아와서 필요없는 문자열 출력 노드를 삭제하고, Create Widget 노드를 추가한 뒤 Get Player Controller 노드를 생성하여 Owner Player 핀과 연결하고, 생성할 위젯인 Message Widget 핀을 Class 핀과 연결한다.

리턴값을 WBP_EffectMessage 로 캐스팅하고, WBP_EffectMessage 블루프린트 클래스에서 생성한 Set Image And Text 함수를 호출한 다음, Image 와 Message 를 연결한다.

위젯을 뷰포트에 추가하기 위해 Add to Veiwport 노드를 추가하여 연결한 후, 확인용으로 추가해둔 WBP_EffectMessage 를 제거한다.


컴파일 후 실행하면 뷰포트에 출력되는 것을 확인할 수 있다.

위젯의 위치와 사이즈가 이상하게 출력되는 것을 수정하기 위해 WBP_EffectMessage 에서 HorizontalBox_Root 우클릭 -> Wrap With... -> Overlay 를 선택해준다.

실행하면 정상적으로 출력되는 것을 확인할 수 있다.

위젯의 위치를 변경하기 위해 다시 WBP_Overlay 로 돌아와서 생성한 위젯 핀에서 Set Position in Viewport 노드를 생성하여 연결한 다음, Get Viewport Size 노드를 통해 얻은 뷰포트 사이즈의 x, y 값에 0.5를 곱하고, 적당히 위치를 조절하여 화면 중앙에 출력되도록 수정한다.


수정
이미지를 출력하기 전, 유효성 검사를 통해 이미지가 유효할 경우에만 출력하도록 변경
애니메이션을 추가하기 위해 하단의 Animation 탭을 클릭한다.
MessageAnimation 을 추가하고

+Track -> All Named Widgets -> Text_Message 를 클릭한다.

트랜스폼 수정을 위해 트랜스폼을 추가하고

0.10초에 텍스트가 위치할 Y축을 지정한 후

0.45초에 텍스트가 위치할 X축을 지정한다.

0.10초에 텍스트의 x축 변화를 0으로 추가해주고

fps 옆의 그래프 아이콘을 선택해준 다음 입맛에 맞게 커브를 수정한다.
(축의 경우 damping하는 것처럼 보이기 위해 해당 타이밍 부분에 우클릭 -> Add Key 를 통해 수정)




잘 적용됬는지 확인하기 위해 Graph 탭으로 돌아와서 Get Message Animation 노드를 생성하고 Play Animation 노드와 연결시킨다.

일정시간이 지난 후 위젯을 파괴시키기 위해 Custom Event 를 생성하고 딜레이를 준 다음 파괴시킨다.


추가 수정
애니메이션 속도가 너무 빠른 관계로 추가수정
x축은 천천히 리니어하게 0.25초간 이동없고, 이후 0.85초간 우측으로 50만큼 이동하도록
y축은 천천히 리니어하게 0.25초간 위로 200만큼 이동하도록
페이드아웃을 나타내기 위해 + -> Render Opacity 를 추가해주고, 0.8초에 0이 되도록 값을 지정해준다.


이미지도 동일하게 애니메이션에 추가하여 수정해준다.


이미지의 경우 Color and Opacity 의 알파값을 수정하여 0.5초간 페이드아웃되도록 설정한다.

추가
OverlayWidgetController 클래스에 있는 델리게이트 4개 전부 비슷하므로 수정
- OverlayWidgetController.h
// OverlayWidgetController.h ... DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAttributeChangedSignature, float, NewValue); // DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChangedSignature, float, NewHealth); 삭제 // DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxHealthChangedSignature, float, NewMaxHealth); 삭제 // DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnManaChangedSignature, float, NewMana); 삭제 // DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMaxManaChangedSignature, float, NewMaxMana); 삭제 ... public: ... // FOnAttributeChangedSignature로 변경 UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes") FOnAttributeChangedSignature OnHealthChanged; // FOnAttributeChangedSignature로 변경 UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes") FOnAttributeChangedSignature OnMaxHealthChanged; // FOnAttributeChangedSignature로 변경 UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes") FOnAttributeChangedSignature OnManaChanged; // FOnAttributeChangedSignature로 변경 UPROPERTY(BlueprintAssignable, Category = "GAS|Attributes") FOnAttributeChangedSignature OnMaxManaChanged;
- OvelrayWidgetController.cpp
// OverlayWidgetController.cpp ... void UOverlayWidgetController::BindCallbacksToDependencies() { const UAuraAttributeSet* AuraAttributeSet = CastChecked<UAuraAttributeSet>(AttributeSet); AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetHealthAttribute()).AddLambda( [this](const FOnAttributeChangeData& Data) { OnHealthChanged.Broadcast(Data.NewValue); } ); AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxHealthAttribute()).AddLambda( [this](const FOnAttributeChangeData& Data) { OnMaxHealthChanged.Broadcast(Data.NewValue); } ); AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetManaAttribute()).AddLambda( [this](const FOnAttributeChangeData& Data) { OnManaChanged.Broadcast(Data.NewValue); } ); AbilitySystemComponent->GetGameplayAttributeValueChangeDelegate(AuraAttributeSet->GetMaxManaAttribute()).AddLambda( [this](const FOnAttributeChangeData& Data) { OnMaxManaChanged.Broadcast(Data.NewValue); } ); /** 헤더파일에 선언한 것들도 전부 삭제 */ // void UOverlayWidgetController::HealthChanged(const FOnAttributeChangeData& Data) const 삭제 // void UOverlayWidgetController::MaxHealthChanged(const FOnAttributeChangeData& Data) const 삭제 // void UOverlayWidgetController::ManaChanged(const FOnAttributeChangeData& Data) const 삭제 // void UOverlayWidgetController::MaxManaChanged(const FOnAttributeChangeData& Data) const 삭제 ...컴파일 후 실행하면 블루프린트 에러가 발생했다는 알람이 뜨는데
Open all errored blueprints를 클릭하고 블루프린트를 확인하면 에러가 발생하는 부분을 확인할 수 있다.
(델리게이트를 하나로 합치는 과정에서 변경점이 발생하였으므로 수정 필요.)
에러가 발생한 노드를 우클릭하고Refresh Nodes를 선택하면 노드가 새로고침 되므로 다시 연결시켜준다.
HP 또는 MP가 감소될 때 감소될 수치만큼 회색처리후 해당 부분만 감소하도록 구현한다.
먼저 WBP_GlobeProgressBar 을 열고(HP, MP Globe의 부모 블루프린트 클래스) Progress Bar 를 추가한 뒤, ProgressBar_Ghost 로 명명한다.

전체를 다 채우도록 Horizontal Alignment 와 Vertical Alignment 를 최대로 설정해주고

이미지 Tint의 알파값을 0으로 설정한다.

Progress Bar 의 Fill Color and Opacity 에서 색을 흰색으로 변경하고


Bar Fill Type : Bottom to Top 으로 변경한다.

Image 에 사용할 에셋을 적용시키고, Draw As : Image 로 변경해서 이미지대로 적용되도록 하고

ProgressBar_Ghost 를 가장 뒤쪽으로 배치시킨다.

ProgressBar_Ghost 가 보이도록 ProgressBar_Globe 의 알파값을 임시로 살짝 줄이고

ProgressBar_Ghost 의 경우 Is Variable 활성화를 통해 노드화하여 사용가능하도록 한다.

이제 텀을 주고 GhostGlobe 가 수정되도록 해야 한다.
먼저 Graph 탭에서 GhostGlobe가 목표로 하는 퍼센트를 할당하기 위한 float 타입 변수를 하나 생성하고

딜레이에 따라 GhostGlobe가 기존의 ProgressBar를 추적하여 따라가도록 딜레이를 발생시키는 커스텀 이벤트를 하나 생성하고, input을 하나 추가한다.

하드코딩을 위해 Duration 을 변수화시키고, 0.5를 할당한다.

카테고리도 지정해줘서 정리한다.

Percent 를 Ghost Percent Target 으로 연결시켜주고

Set Progress Bar Percent 함수에 추가한 커스텀 이벤트인 Ghost Percent Set 노드를 연결시켜준다.

함수쪽에서 기존의 Set Globe Image 를 Update Globe Brush 로 변경하고(헷갈림 방지)

GhostGlobe의 ProgressBar 이미지 변경을 위한 변수 GhostProgressBarFillBrush 를 하나 추가한 뒤 카테고리를 변경한다.


추가
ProgressBarFillImage->ProgressBarFillBrush로 변경
이미지를 추가하기 위해 컴파일 후 이미지를 추가해주고

GhostGlobe 업데이트 관련 기능을 가질 함수를 하나 생성한다.

기존의 Update Globe Brush 와 동일한 기능이므로 해당 함수의 노드를 복사해서 연결하고 변수만 새로 생성해둔 GhostProgressBarFillBrush 와 ProgressBarGhost 로 변경한다.

추가
ProgressBarFillBrush->ProgressBarBrush로 변경
GhostProgressBarFillBrush->GhostProgressBarBrush로 변경
마지막으로 생성한 함수를 기존의 Event Pre Construct 노드에 연결한다.

먼저 함수를 하나 생성한다.

ProgressBarGhost 의 퍼센트르 지정하기 위한 SetPercent 노드를 연결하고

함수에 input을 하나 받도록 한다음 연결한다.

이제 틱에 따라 보간을 해야 한다.
Event Tick 노드를 생성하고 보간을 위한 FInterp To 노드를 생성한 다음, ProgressBarGhost 의 퍼센트를 Current 핀과 연결시킨다.

GhostPercentTarget 을 Target 핀과 연결시키고, Event Tick 노드로부터 DeltaTime 을 얻도록 한 후, Interp Speed 의 경우 변수로 승격시킨 다음 카테고리를 지정해주고 1로 초기화한다.



SetGhostProgressBarPercent 함수에 Percent 를 연결시켜 준다.

틱이 자식 클래스에도 적용되도록 하기 위해 WBP_HealthGlobe 와 WBP_ManaGlobe Event Tick 노드를 우클릭한 후 Add Call to Parent Function 을 선택하여 핀을 연결시켜준다.
(기존에 노드가 있어도 삭제후 새로 생성하지 않으면 적용안됨)

마지막으로 알파값을 다시 1.0으로 복구한 뒤 실행시 정상적으로 동작하는 것을 확인할 수 있다.

HealthGlobe 의 경우, 부모 블루프린트 클래스에서 MI_GhostHealthGlobe 를 사용하므로 변경할 필요가 없지만 ManaGlobe 의 경우 MI_GhostManaGlobe 로의 변경이 필요하다.

추가1
Ghost Delay 수치 0.5 -> 1.0 변경
추가2
ProgressBar의 퍼센트를 지정하기 전에 globe가 유요한지 확인이 필요함
먼저 bool 타입의GlobeInitialized를 만들고 false로 초기화
Branch를 통해 Globe가 유효하다면 기존의 퍼센트 노드와 연결시키고
유효하지 않다면ProgressBarGlobe와ProgressBarGhost가 퍼센트를 지정하도록 노드 구성 및GhostPercentTarget도 퍼세트를 지정하도록 변경
추가3
Globe가 아직 초기화되지 않았을 경우를 대비해(초기화되지 않은 경우 퍼센트는 0이 됨) 퍼센트가 0을 초과하였을 경우에만 퍼센트 세팅
추가4
초록선 난잡도 해결을 위해 변수로 승격후 노드 사용
추가5
해당 부분 함수로 리팩토링
추가6
GhostGlobe약간씩 남는 문제
ProgressBar_Ghost 의 padding을 ProgressBar_Globe 와 동일한 값 추가(해당 정리에서는 10)