25.09.03 변동 사항
ClassComponentBase에 있는 ClassChange 함수에 직업과 직업 차수를 가리키는 Gameplay Tag를 매개변수로 추가해주는 작업을 하였다.
ClassChange 함수를 생성한지 꽤 돼서, Delegate를 통해 다른 클래스에서 전직 퀘스트라던가 계보 시스템에 사용되고 있었기에 찾아서 전부 수정해주었다.
우선은 ClassChange 함수가 들어있는 ClassBaseComponent의 수정 사항이다.
ClassBaseComponent
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
.h
/* 클래스와 티어 태그를 전달하도록 2개의 파라미터 추가 */
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnClassChanged, const FGameplayTag&, NewClassTag, const FGameplayTag&, NewTierTag);
.cpp
void UClassComponentBase::ClassChange(const FGameplayTag& NewClassTag, const FGameplayTag& NewTierTag)
{
//UE_LOG(LogTemp, Warning, TEXT("[ClassComp] ClassChange() called on %s Role=%d"), *GetOwner()->GetName(), GetOwnerRole());
// 클라이언트에서 호출된 경우, 파라미터를 그대로 담아 서버에 RPC 요청
if (GetOwnerRole() < ROLE_Authority)
{
Server_ClassChange(NewClassTag, NewTierTag);
return;
}
// 서버에서 직접 호출된 경우 (에디터 또는 콘솔 등), 바로 서버 함수 로직 실행
Server_ClassChange(NewClassTag, NewTierTag);
}
/** 외부에서 호출: 새로운 클래스/티어를 적용하고 오라 재적용 */
void UClassComponentBase::ApplyClassAndAura(FGameplayTag NewClass, int32 NewTierOneBased)
{
const bool bAuth = (GetOwnerRole() >= ROLE_Authority);
//UE_LOG(LogTemp, Log, TEXT("[ClassComp] ApplyClassAndAura: Auth=%d NewClass=%d NewTier=%d"), bAuth ? 1 : 0, (int32)NewClass, NewTierOneBased);
if (!bAuth)
{
Server_ApplyClassAndAura(NewClass, NewTierOneBased);
return;
}
IAbilitySystemInterface* ASI = Cast<IAbilitySystemInterface>(GetOwner());
UAbilitySystemComponent* ASC = ASI ? ASI->GetAbilitySystemComponent() : nullptr;
if (!ASC)
{
return;
}
const int32 TierOneBased = FMath::Max(1, NewTierOneBased);
ReapplyAuraGE(ASC, NewClass, TierOneBased);
ClassType = NewClass;
PendingTierOneBased = TierOneBased;
if (AActor* OwnerActor = GetOwner())
{
OwnerActor->ForceNetUpdate();
}
{
FGameplayTagContainer Owned;
ASC->GetOwnedGameplayTags(Owned);
int32 ClassCount = 0, TierCount = 0;
for (const FGameplayTag& T : Owned)
{
if (T.MatchesTag(ClassRootTag)) ++ClassCount;
if (T.MatchesTag(TierRootTag)) ++TierCount;
}
//UE_LOG(LogTemp, Log, TEXT("[ClassComp] OwnedTags: Class=%d, Tier=%d under roots"), ClassCount, TierCount);
}
}
void UClassComponentBase::Server_ClassChange_Implementation(const FGameplayTag& NewClassTag, const FGameplayTag& NewTierTag)
{
// 새로운 Tier 태그를 기존 로직에서 사용하는 int32로 변환
const int32 NewTierOneBased = GetTierAsIntFromTag(NewTierTag);
// Tag 부착
IAbilitySystemInterface* ASI = Cast<IAbilitySystemInterface>(GetOwner());
UAbilitySystemComponent* ASC = ASI ? ASI->GetAbilitySystemComponent() : nullptr;
if (ASC)
{
RefreshClassTag(ASC, NewClassTag);
RefreshTierTag(ASC, NewTierOneBased);
}
// AuraGE 재적용(서버에서 확정) -> GCN 트리거가 클라로 복제됨
ApplyClassAndAura(NewClassTag, NewTierOneBased);
// 전직 FX 토글(복제)
bEffectActive = !bEffectActive;
// 서버에서도 즉시 재생
OnRep_EffectActive(); // (서버 로컬 재생)
//태그 설정
SetGamePlayTagTier();
// 델리게이트 브로드캐스트
OnClassChanged.Broadcast(NewClassTag, NewTierTag);
}
|
cs |
선언부에서 다른 클래스에서 사용할 Delegate에 Param을 추가해주고, 구현부에서 ClassChange에 Gameplay Tag를 매개변수로 넣어주면서 연관된 함수들에도 매개변수가 적용되도록 해주었다.
ClassChange 함수가 전직 퀘스트에 사용되고 있었기에 전직 퀘스트의 구조체인 NPCDialogueRow에 전직 퀘스트의 결과로 Tag를 붙여주기 위해 아래와 같이 코드를 추가해주었다.
퀘스트임에도 Dialogue인 이유는 해당 프로젝트는 NPC와의 대화가 곧 퀘스트이기 때문이다.
NPCDialogueRow.h
|
1
2
3
4
5
6
7
|
/** 전직 퀘스트 완료 시 부여할 클래스 태그 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Dialogue")
FGameplayTag TargetClassTag;
/** 전직 퀘스트 완료 시 부여할 티어 태그 */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Dialogue")
FGameplayTag TargetTierTag;
|
cs |
그리고 전직과 관련된 AC에서도 사용 중이었기에 해당 AC에서도 수정해주었다.
JobAdvancementComponent
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
.h
/* 전직 시 변경될 클래스/티어 태그를 임시 저장하기 위한 변수 */
FGameplayTag PendingAdvancementClassTag;
FGameplayTag PendingAdvancementTierTag;
/* NPC 대화창의 OnJobQuestCompletedDelegate에 바인딩될 함수 */
UFUNCTION()
void HandleJobAdvancement();
.cpp
/* 델리게이트에 연결되어 ClassChange를 호출하는 핸들러 함수 */
void UJobAdvancementComponent::HandleJobAdvancement()
{
// ClassComponent가 유효하고, 임시 저장된 태그가 모두 유효할 때만 실행
if (ClassComponentBase && PendingAdvancementClassTag.IsValid() && PendingAdvancementTierTag.IsValid())
{
ClassComponentBase->ClassChange(PendingAdvancementClassTag, PendingAdvancementTierTag);
}
}
void UJobAdvancementComponent::ClientRPC_ShowDialogue_Implementation(const FNPCDialogueRow& RowName)
{
//대화창 팝업
if (NPCDialogueClass)
{
NPCDialogueWidget = CreateWidget<UUserWidget>(GetWorld(), NPCDialogueClass);
UNPCDialogue* NPCDialogue = Cast<UNPCDialogue>(NPCDialogueWidget);
if (NPCDialogue)
{
NPCDialogueWidget->AddToViewport();
NPCDialogue->SetNPCData(RowName.NPCID, RowName.QuestID);
//전직 퀘스트 완료 관련 대화창 바인딩
if (RowName.bIsCompletedJobQuest)
{
if (ClassComponentBase)
{
// 전직할 클래스와 티어 정보를 멤버 변수에 임시 저장 */
PendingAdvancementClassTag = RowName.TargetClassTag;
PendingAdvancementTierTag = RowName.TargetTierTag;
// 새로 만든 핸들러 함수에 델리게이트를 바인딩 */
NPCDialogue->OnJobQuestCompletedDelegate.AddDynamic(this, &UJobAdvancementComponent::HandleJobAdvancement);
}
}
}
}
}
|
cs |
Gameplay Tag를 저장할 변수를 추가해주고, 매개변수가 추가된 ClassChange를 실행할 함수를 만들어준 뒤 해당 함수를 실행하도록 해주었다.
마지막으로 계보 시스템에서 사용 중인 ClassChange도 수정해주었다.
GenealogyMainWidget
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
.h
/* 델리게이트 시그니처에 맞춰 파라미터 추가 */
UFUNCTION()
void OnClassChanged(const FGameplayTag& NewClassTag, const FGameplayTag& NewTierTag);
.cpp
void UGenealogyMainWidget::NativeConstruct()
{
Super::NativeConstruct();
if (auto* ClassComp = GetOwningPlayer()->GetPawn()->FindComponentByClass<UClassComponentBase>())
{
int32 LastDotIndex;
FString LastTag;
FString TagString = ClassComp->GetCharacterClass().GetTagName().ToString();
if (TagString.FindLastChar('.', LastDotIndex))
{
LastTag = TagString.Right(TagString.Len() - (LastDotIndex + 1));
}
const FName RowName(LastTag);
InitGenealogy(RowName);
CacheSeparators();
ClassComp->OnClassChanged.RemoveDynamic(this, &ThisClass::OnClassChanged);
ClassComp->OnClassChanged.AddDynamic(this, &ThisClass::OnClassChanged);
if (const FGenealogyData* Row = GenealogyDataTable->FindRow<FGenealogyData>(RowName, TEXT("Init")))
{
ApplyDefaultSelection(*Row);
}
}
RefreshUnlocksFromASC(); // OnClassChanged는 파라미터가 필요하기에 직접 호출 불가하므로 동일한 기능을 하는 함수로 대체
//OnClassChanged();
JobComp = GetOwningPlayerPawn()->FindComponentByClass<UJobAdvancementComponent>();
if (auto* PS = GetOwningPlayerState<APlayerStateBase>()) {
CachedPS = PS;
}
if (CachedPS.IsValid())
CachedASC = CachedPS->GetAbilitySystemComponent();
CachedPS->OnLevelUp.AddDynamic(this, &ThisClass::HandleLevelUp);
}
/* 델리게이트 시그니처에 맞춰 파라미터 추가 */
void UGenealogyMainWidget::OnClassChanged(const FGameplayTag& NewClassTag, const FGameplayTag& NewTierTag)
{
RefreshUnlocksFromASC();
}
|
cs |
마지막으로 테스트 용으로 Player BP에서 ClassChange 노드를 사용 중이었는데, 함수의 매개변수가 바뀌면 해당 노드에 연결해줘야하는 핀도 바뀌기에 아래와 같이 수정해주었다.

'Unreal Engine > Functional Implementation' 카테고리의 다른 글
| 언리얼 엔진 NPC 대화 UI 애니메이션 구현 - 열기, 닫기, 다음 대사 버튼 효과 추가 (0) | 2025.09.09 |
|---|---|
| 언리얼 엔진 플레이어 사망 UI 구현 - 공격자 이름 표시와 경험치 페널티 계산 (0) | 2025.09.08 |
| 언리얼 엔진 스킬 해금 UI 구현 - 레벨업 시 신규 스킬 정보를 위젯에 표시하기 (0) | 2025.09.01 |
| 언리얼 엔진 신규 스킬 해금 UI 구현 - 위젯 출력과 스킬창 알림 배지 만들기 (5) | 2025.08.25 |
| 언리얼 엔진 GAS Aura 시스템 구현 - Gameplay Tag로 직업과 직업 차수에 맞는 Aura 출력하기 (0) | 2025.08.21 |