[25.08.12] :: 가디언 앤 시커 프로젝트 24

chooha·2025년 8월 12일

가디언앤시커

목록 보기
24/25

📝 개발일지 - 룬 시스템 프리셋 기능 구현 (2)

👨💻 오늘의 개발 작업

오늘은 구축한 프리셋 데이터 구조를 바탕으로 실제 UI에서 프리셋 전환이 동작하도록 구현했음

핵심적으로는 프리셋 버튼 클릭 시 저장확인 팝업 표시, 세이브 파일 호환성 문제 해결, 그리고 최근 사용 프리셋 자동 로드 기능을 완성했음

특히 기존 LoadBoardConfig() 함수가 너무 많은 책임을 가지고 있어서 발생했던 버그들을 함수 분리를 통해 근본적으로 해결했음


💡 오늘의 5분 기록

1. 프리셋 전환 시 저장확인 로직 구현

기존 문제점

  • 프리셋 버튼 클릭 시 변경사항이 있어도 바로 전환됨
  • 사용자가 실수로 작업한 내용을 잃을 수 있음

해결책: 팝업 기반 확인 시스템

// GS_ArcaneBoardWidget.h에 추가
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "ArcaneBoard")
TSubclassOf<class UGS_CommonTwoBtnPopup> PresetSaveConfirmPopupClass;

UPROPERTY()
int32 PendingPresetIndex; // 사용자가 선택한 대기 중인 프리셋

// 프리셋 버튼 함수들 수정
void UGS_ArcaneBoardWidget::OnPresetButton1Clicked()
{
    if (HasUnsavedChanges())
    {
        ShowPresetSaveConfirmPopup(1); // 저장확인 팝업 표시
    }
    else
    {
        SwitchToPreset(1); // 바로 전환
    }
}

팝업 응답 처리

void UGS_ArcaneBoardWidget::OnPresetSaveYes()
{
    // 현재 프리셋에 저장 후 새 프리셋으로 전환
    ArcaneBoardLPS->ApplyBoardChanges();
    SwitchToPreset(PendingPresetIndex);
}

void UGS_ArcaneBoardWidget::OnPresetSaveNo()
{
    // 저장하지 않고 바로 전환
    SwitchToPreset(PendingPresetIndex);
}

2. 세이브 파일 호환성 문제 해결

발생한 문제

LogClass: Warning: Struct Property SavedRunesByClass has a struct type mismatch 
(tag RunePlacementData(/Script/GAS) != prop ArcaneBoardPresets(/Script/GAS))

기존 RunePlacementData 구조체로 저장된 파일과 새로운 FArcaneBoardPresets 구조체 간의 타입 불일치

임시 해결: 기존 세이브 파일 삭제

  • 개발 단계에서는 기존 파일 삭제로 빠른 해결
  • 향후 버전 관리 시스템 도입 예정

새로운 문제 발견: 기본 룬 없음
세이브 파일 삭제 후 테스트용 룬이 없어서 기능 테스트 불가

기본 룬 시스템 도입

void UGS_ArcaneBoardLPS::InitializeDefaultRunes()
{
    OwnedRuneIDs.Empty();
    
    // 모든 플레이어가 기본 룬 2개로 시작
    OwnedRuneIDs.Add(1);
    OwnedRuneIDs.Add(2);
    
    UE_LOG(LogTemp, Log, TEXT("기본 룬 초기화 완료: 룬 ID 1, 2"));
}

3. LoadBoardConfig 함수 리팩토링

기존 함수의 문제점

  • 단일 함수가 너무 많은 책임을 가짐
  • 룬 인벤토리 초기화, 프리셋 로드, 보드 적용, 에러 처리 등 모든 것을 담당
  • 디버깅이 어렵고 예외 케이스 처리가 복잡함

함수 분리를 통한 해결

// 1. 룬 인벤토리 초기화만 담당
void EnsureRuneInvenInit()
{
    if (OwnedRuneIDs.Num() > 0) return; // 이미 초기화됨
    
    // 세이브 파일에서 룬 데이터만 로드 시도
    // 실패 시 기본 룬으로 초기화
}

// 2. 프리셋 데이터 로드만 담당  
TArray<FPlacedRuneInfo> LoadPresetData(ECharacterClass CharClass, int32 PresetIndex)
{
    // 모든 예외 상황에서 빈 배열 반환
    // 크래시 방지를 위한 안전한 설계
}

// 3. 보드에 데이터 적용만 담당
void ApplyPresetToBoard(ECharacterClass CharClass, const TArray<FPlacedRuneInfo>& PresetData)
{
    BoardManager->LoadSavedData(CharClass, PresetData);
    BoardManager->bHasUnsavedChanges = false;
}

// 4. 전체 흐름 조율 (단순화된 메인 함수)
void LoadBoardConfig(int32 PresetIndex)
{
    EnsureRuneInvenInit();           			// 1단계
    TargetPresetIndex = DeterminePresetIndex(); // 2단계  
    PresetData = LoadPresetData();              // 3단계
    ApplyPresetToBoard(PresetData);             // 4단계
}

단일 책임 원칙(SRP) 적용 효과

  • 각 함수가 명확한 하나의 책임만 가짐
  • 디버깅 시 문제 구간을 쉽게 특정 가능
  • 개별 기능을 독립적으로 테스트 가능
  • 재사용성 향상

4. 최근 사용 프리셋 자동 로드 기능

기존 문제
아케인보드 창을 열면 항상 프리셋 1이 로드됨. 사용자가 프리셋 2에서 작업했어도 다음에 다시 프리셋 1부터 시작해야 함

GetLastUsedPresetIndex 함수 구현

int32 UGS_ArcaneBoardLPS::GetLastUsedPresetIndex()
{
    // 세이브 파일에서 LastUsedPresetIndex 값 읽기
    const FArcaneBoardPresets& ClassPresets = LoadedSaveGame->SavedRunesByClass[CurrClass];
    int32 LastUsedIndex = ClassPresets.LastUsedPresetIndex;
    
    if (LastUsedIndex >= 1 && LastUsedIndex <= 3)
    {
        return LastUsedIndex;
    }
    else
    {
        return 1; // 기본값
    }
}

LoadBoardConfig에서 최근 프리셋 우선 로드

void UGS_ArcaneBoardLPS::LoadBoardConfig(int32 PresetIndex)
{
    int32 TargetPresetIndex;
    if (PresetIndex == -1)
    {
        // 최근 사용 프리셋 로드
        TargetPresetIndex = GetLastUsedPresetIndex();
    }
    else
    {
        // 지정된 프리셋 로드
        TargetPresetIndex = PresetIndex;
    }
    // ...
}

Apply 시 최근 사용 프리셋 업데이트

void UGS_ArcaneBoardLPS::ApplyBoardChanges()
{
    BoardManager->ApplyChanges();
    
    // 현재 프리셋에 저장하면서 최근 사용 프리셋으로 기록
    SaveBoardConfig(CurrentPresetIndex);
    
    UE_LOG(LogTemp, Log, TEXT("변경사항을 프리셋 %d에 적용"), CurrentPresetIndex);
}

0개의 댓글