프로젝트 MyaCat을 개발하면서 구현한 내용 정리


구현 핵심 기능

세이브 시스템

ProjectFT의 현재 세이브 시스템은 UFTSaveSubsystem 중심의 레벨 이동 자동 저장 구조다.
GameFlow를 통해 레벨을 이동하면 자동으로 현재 상태가 캡처되고 디스크에 저장된다.
새 레벨에서는 AFTGameMode::StartPlay()가 저장된 인벤토리, 창고, 퀘스트 상태를 복원한다.

세이브의 중심은 UFTSaveSubsystem이며, 실제 디스크 저장 객체는 UFTSaveGame이다.

핵심 클래스

  • UFTSaveGame
    USaveGame을 상속한 실제 저장 데이터 컨테이너
    저장 필드:

    필드 역할
    SaveVersion 세이브 데이터 버전
    SlotName 저장 슬롯 이름
    SavedAt 마지막 저장 시각
    LastLevelName 마지막으로 이동하려던 레벨 이름
    FlowState 마지막 GameFlow 상태
    PlayerInventory 플레이어 인벤토리 스냅샷
    StorageInventory 거점 창고 인벤토리 스냅샷
    QuestData 퀘스트 상태와 진행도
    UnlockedRecipes 해금된 레시피 목록
    BaseUpgradeState 거점 업그레이드 상태
    StoredItems 기타 보관 아이템 목록
  • UFTSaveSubsystem
    초기화 시 LoadOrCreateSave()를 호출해 기존 슬롯을 로드하거나 새 UFTSaveGame 객체를 만든다.
    주요 책임:

    함수 역할
    LoadOrCreateSave() 메모리의 현재 세이브를 반환하거나 슬롯에서 로드한다. 없으면 새 객체를 만든다.
    SaveToDisk() UGameplayStatics::SaveGameToSlot으로 디스크에 저장한다.
    DeleteSaveData() 디스크 세이브를 삭제하고 메모리 상태를 초기화한다.
    ResetForNewGame() 새 게임 시작용 세이브 객체를 만든다.
    CaptureCurrentWorldState() 현재 월드의 인벤토리, 창고, 퀘스트 상태를 UFTSaveGame에 캡처한다.
    SaveBeforeLevelTransition() 상태 캡처 후 디스크 저장을 수행한다.
    RestoreCurrentWorldState() 저장된 플레이어 인벤토리, 창고, 퀘스트 상태를 현재 월드에 복원한다.
    ClearPlayerInventoryForRaidFailure() 레이드 실패 시 플레이어 인벤토리를 비우고 세이브에도 반영한다.

저장 데이터 구조

  • 인벤토리 저장
    아이템은 FFTSavedInventoryItemStruct로 저장

    FName ItemId;
    int32 Quantity;

    인벤토리는 FFTSavedInventoryStateStruct로 저장

    TArray<FFTSavedInventoryItemStruct> Items;
    TArray<FName> QuickSlots;
    float MaxWeight;
  • 퀘스트 저장
    퀘스트 상태는 FFTQuestSaveData에 저장된다.

    필드 역할
    bHasQuestData 실제 퀘스트 스냅샷이 있는지 구분
    AvailableQuestIDs 수락 가능한 퀘스트
    ActiveQuestIDs 진행 중인 퀘스트
    CompletedQuestIDs 완료된 퀘스트
    EventProgressByQuest GameplayMessage 기반 조건 진행도
    CurrentQuestID 기존 단일 현재 퀘스트 API 호환용 값

저장 시점

flowchart TD
    A["GameFlow requests level change"] --> B["OpenLevelByName(LevelName)"]
    B --> C["SaveSubsystem.SaveBeforeLevelTransition(LevelName, CurrentFlowState)"]
    C --> D["CaptureCurrentWorldState"]
    D --> E["SaveToDisk"]
    E --> F["Apply BGM / restore input"]
    F --> G["UGameplayStatics::OpenLevel"]

복원 시점

flowchart TD
    A["AFTGameMode::StartPlay"] --> B["GameFlow.SyncFlowStateWithCurrentLevel"]
    B --> C["SaveSubsystem.RestoreCurrentWorldState"]
    C --> D["RestorePlayerInventory"]
    C --> E["RestoreStorageInventory"]
    C --> F["RestoreQuestState"]
  • 복원 흐름 예시) Continue Game
    flowchart TD
      A["Continue Game"] --> B["SaveSubsystem.LoadOrCreateSave"]
      B --> C{"HasSaveData?"}
      C -- "No" --> D["Ignore continue request"]
      C -- "Yes" --> E["Resolve saved FlowState and Level"]
      E --> F["TravelToState"]
      F --> G["Level opens"]
      G --> H["GameMode restores saved world state"]

GameFlow와 저장 상태

FlowState enum:

enum class EFTFlowStateType : uint8
{
    MainMenu,
    Base,
    RaidEntering,
    RaidInProgress,
    Escaping,
    Escaped,
    Failed
};

현재 저장 시스템은 GameFlow와 강하게 연결되어 있다.
레벨 이동은 GameFlow를 통해 일어나고, GameFlow가 레벨 이동 직전에 자동 저장을 호출한다.

캐치바

  • ShelfSearch Mode
    스크린샷 2026-07-27 121848
    ShelfSearch Mode는 선반 탐색처럼 채널형 상호작용이 진행 중일 때 사용된다.
    타이밍 스킬체크가 활성화되면 Timing Bar, Success Zone, Cursor, Space Key 힌트가 표시된다.
    ```
    입력
    AFTPlayerController
  • UFTInteractionComponent

  • UFTChanneledInteractionComponent

판정
UFTChanneledInteractionComponent

표시
UFTProgressWidget / WBP_Progress


채널링이 시작되면 `StartChannel()`에서 `ScheduleNextSkillCheck()`를 호출한다. 여기서 다음 타이밍 바가 뜰 시간을 랜덤으로 정한다.

선반 탐색을 시작하면 지정한 랜덤 타이밍에 스킬체크가 뜬다.
스킬체크가 시작되면 `StartSkillCheck()`가 호출된다. 여기서 성공 지점인 `SkillCheckTarget`을 랜덤으로 정한다.

Good 성공 구간:
Target - SuccessHalfWidth ~ Target + SuccessHalfWidth

Great 성공 구간:
Target - GreatHalfWidth ~ Target + GreatHalfWidth


스킬체크가 활성화되면 Tick에서 커서가 이동
```cpp
SkillCheckCursor += (DeltaTime / SkillCheckSweepSeconds) * SkillCheckCursorDirection;

커서가 0에서 1까지 가는 데 1초 걸린다. 끝까지 가면 방향이 반대로 바뀌어서 왕복

if (SkillCheckCursor >= 1.0f)
{
    SkillCheckCursor = 1.0f;
    SkillCheckCursorDirection = -1.0f;
}
else if (SkillCheckCursor <= 0.0f)
{
    SkillCheckCursor = 0.0f;
    SkillCheckCursorDirection = 1.0f;
}

UFTProgressWidget::UpdateTimingWidgets()UFTInteractionComponent에서 값을 읽는다.
그리고 TimingBar의 Canvas Slot 크기를 기준으로 성공 영역과 커서 위치를 배치한다.

SuccessSlot->SetSize(...)
SuccessSlot->SetPosition(... TargetPercent ...)
CursorSlot->SetPosition(... CursorPercent ...)
  • Escape Mode
    스크린샷 2026-07-27 124345
    Escape Mode는 Security에게 잡힌 상태에서 탈출 입력을 진행할 때 사용된다.
    [A] DA [D]가 번갈아 보이도록 Tick에서 텍스트와 Opacity를 갱신
반응형

'Unreal Engine' 카테고리의 다른 글

UE NetMode / NetConnection / NetDriver  (0) 2026.07.29
MyaCat 정리 2  (0) 2026.07.28
AssetManager ⇄ Bundle System 구조 수정  (0) 2026.07.13
AssetManager ⇄ Bundle System(Data Asset)  (0) 2026.07.09
Unreal Engine GC  (0) 2026.07.08