언리얼엔진5 공부

[TIL] UE 5 스킬 시스템 핵심 로직 - 스왑 및 장착 메커니즘

Client Side 2026. 2. 10. 21:37

 

1. 핵심 설계 원칙

  1. Single Source of Truth: 모든 데이터는 컴포넌트 내의 TArray 혹은 TMap에서 관리하며, UI는 이를 관찰(Observe)하여 출력만 수행합니다.
  2. Defensive Programming: 잘못된 인덱스, 유효하지 않은 스킬 ID, 중복 장착 시도를 로직 입구에서 return으로 차단합니다.

2. 상세 코드 구현 (C++)

① 중복 장착 방지 및 인벤토리 → 슬롯 배치

인벤토리에서 스킬을 끌어다 놓을 때, 이미 다른 슬롯에 해당 스킬이 있는지 검사하는 것이 핵심입니다.

C++
 
/**
 * 인벤토리에서 슬롯으로 스킬을 장착합니다.
 * @param SkillID 인벤토리에서 선택한 스킬의 고유 ID
 * @param TargetSlotIndex 장착하고자 하는 퀵슬롯의 번호 (0~3)
 */
bool UT3SkillComponent::EquipSkillFromInventory(int32 SkillID, int32 TargetSlotIndex)
{
    // 1. 인덱스 유효성 검사 (시니어의 기본기: 방어적 코딩)
    if (!EquippedSkills.IsValidIndex(TargetSlotIndex)) 
    {
        UE_LOG(LogTemp, Warning, TEXT("Invalid Slot Index: %d"), TargetSlotIndex);
        return false;
    }

    // 2. 중복 장착 확인 (이미 장착된 스킬인지 루프 검사)
    for (int32 i = 0; i < EquippedSkills.Num(); ++i)
    {
        if (EquippedSkills[i].SkillID == SkillID)
        {
            UE_LOG(LogTemp, Log, TEXT("Skill %d is already equipped in slot %d"), SkillID, i);
            
            // [심화] 이미 있다면 기존 위치를 비우거나 스왑할지 결정
            // 여기선 안전하게 중복 등록을 거부함
            return false; 
        }
    }

    // 3. 데이터 자산(Data Asset)에서 스킬 정보 가져오기
    FSkillData* NewSkillData = GetSkillDataByID(SkillID); // 앞서 만든 함수 활용
    if (NewSkillData)
    {
        // 4. 슬롯에 데이터 할당 (값 복사 발생)
        EquippedSkills[TargetSlotIndex] = *NewSkillData;

        // 5. UI 동기화를 위한 델리게이트 브로드캐스트
        OnSkillSlotChanged.Broadcast(TargetSlotIndex, EquippedSkills[TargetSlotIndex]);
        return true;
    }

    return false;
}

② 슬롯 간 스왑 (Slot-to-Slot Swap)

장착된 스킬끼리 위치를 바꿀 때는 데이터 유실이 없도록 임시 저장 공간을 활용해야 합니다.

C++
 
/**
 * 장착된 스킬 슬롯 간의 위치를 교체합니다.
 */
void UT3SkillComponent::SwapSkills(int32 SourceIndex, int32 TargetIndex)
{
    if (!EquippedSkills.IsValidIndex(SourceIndex) || !EquippedSkills.IsValidIndex(TargetIndex)) return;

    // Triple-Buffered Swap 로직
    FSkillData TempData = EquippedSkills[SourceIndex];
    EquippedSkills[SourceIndex] = EquippedSkills[TargetIndex];
    EquippedSkills[TargetIndex] = TempData;

    // 두 슬롯이 모두 변했으므로 각각 브로드캐스트
    OnSkillSlotChanged.Broadcast(SourceIndex, EquippedSkills[SourceIndex]);
    OnSkillSlotChanged.Broadcast(TargetIndex, EquippedSkills[TargetIndex]);

    UE_LOG(LogTemp, Log, TEXT("Swapped Slot %d and %d"), SourceIndex, TargetIndex);
}

3. UI 동기화 (C++ 기반 UserWidget)

UI에서 NativeConstruct를 통해 초기화 시점에 데이터를 긁어오는 상세 로직입니다.

C++
 
void USkillWidget::NativeConstruct()
{
    Super::NativeConstruct();

    if (APlayerController* PC = GetOwningPlayer())
    {
        // 캐릭터를 통해 스킬 컴포넌트 접근
        if (AT3CharacterBase* Char = Cast<AT3CharacterBase>(PC->GetPawn()))
        {
            SkillComp = Char->GetSkillComponent();
            if (SkillComp)
            {
                // 1. 변화가 생길 때 실행될 함수 바인딩
                SkillComp->OnSkillSlotChanged.AddDynamic(this, &USkillWidget::UpdateSlotUI);

                // 2. [중요] 초기화: 현재 컴포넌트가 가진 데이터를 UI 슬롯에 전부 뿌려줌
                SyncAllSlots();
            }
        }
    }
}

void USkillWidget::SyncAllSlots()
{
    if (!SkillComp) return;

    // 컴포넌트의 배열을 순회하며 각 슬롯(Child Widget) 업데이트
    for (int32 i = 0; i < SkillComp->GetMaxSlotCount(); ++i)
    {
        FSkillData Data;
        if (SkillComp->GetSkillDataInSlot(i, Data)) // 참조로 안전하게 데이터 가져오기
        {
            UpdateSlotUI(i, Data);
        }
    }
}