Repository navigation
16_MonsterDex_System
몬스터를 처음 처치하거나 포획하면 도감에 등록하고, 누적 기록에 따라 보상을 수령할 수 있습니다. 도감 목록과 상세 화면에는 발견 상태, 몬스터 모델, 처치·포획 횟수와 단계별 보상을 표시합니다.
도감에 정의된 종은 발견 전에도 목록에 나타나지만 이름은 ???로, 모델은 실루엣으로 표시합니다. 첫 처치·포획 이후 원래 이름과 외형, 속성 정보가 열립니다.
도감은 몬스터 정의, 플레이어 기록, UI 표시 데이터를 구분해 처리합니다.
| 데이터 | 주요 내용 | 사용하는 곳 |
|---|---|---|
DT_MonsterDex |
AreaObjectID, 도감 번호·이름·설명, RewardTiers, DisplayClass
|
종별 정보와 보상 조건 |
dt_AreaObject |
몬스터 기본 아이콘·속성 | 목록 이미지 대체, 상세 속성 |
FMonsterDexEntry |
AreaObjectID, 처치·포획 횟수, ClaimedTiers
|
플레이어별 진행 기록 |
FMonsterDexBookView |
목록 Pages, 발견·포획한 종의 수 |
도감 전체 화면 |
FMonsterDexPageView / FMonsterDexTierView |
발견 여부, 표시 이름·모델·진행도·보상 상태 | 목록 Cell·상세·보상 Row |

UMonsterDexSubsystem은 DT_MonsterDex의 항목을 Number 순으로 정렬합니다. MonsterDexView::Build()는 각 항목의 AreaObjectID로 플레이어 기록과 dt_AreaObject를 조회해 화면에 사용할 페이지를 구성합니다. 위젯이 도감 정의와 진행 기록을 직접 비교하지 않도록 발견 여부와 표시 규칙을 View 구성 단계에서 결정합니다.
// MonsterDexView.cpp — Build() 발췌
const FMonsterDexEntry* Entry = Dex.FindEntry(Page.AreaObjectID);
View.bDiscovered = Entry != nullptr;
View.bCaptured = Entry && Entry->CaptureCount > 0;
View.Kills = Entry ? Entry->KillCount : 0;
View.Captures = Entry ? Entry->CaptureCount : 0;
if (View.bDiscovered)
{
View.Name = Page.DisplayName;
View.Description = Page.Description;
if (Species) View.Elements = Species->DefenceElementalAttributes.Array();
}
else
{
View.Name = INVTEXT("???");
View.Description = LOCTABLE(SONHEIM_ST_DEX, "Page.Unknown.Description");
}발견한 종은 FMonsterDexEntry의 존재 여부로 판단합니다. 포획 표시는 CaptureCount > 0으로 따로 계산합니다. Book.Discovered와 Book.Captured는 각각 발견한 종의 수와 한 번 이상 포획한 종의 수이며, 처치·포획 횟수의 합이 아닙니다.
전투·포획 시스템에서 ASonheimPlayerState::ReportDeed로 이벤트를 전달하면 UMonsterDexComponent::Record가 도감에 정의된 종의 처치 또는 포획 횟수를 갱신합니다. 처음 처리하는 종은 FMonsterDexEntry를 추가합니다.
flowchart LR
A["전투 · 포획<br/>ReportDeed"] --> B["MonsterDexComponent<br/>Record · Commit"]
B --> C["OnDexChanged"]
C --> D["MonsterDexView::Build"]
D --> E["도감 목록 · 상세 · 보상"]
B --> F["저장 · FastArray"]
F -->|"소유 클라이언트 변경 통지"| C
Commit()은 MarkItemDirty와 OnDexChanged를 실행하고 저장 데이터를 갱신합니다. 기록은 PlayerState에 보관하며 OwnerOnly FastArray로 소유 클라이언트에 복제합니다. 복제된 항목의 Add/Change 콜백도 OnDexChanged를 발생시킵니다.
UI에서는 기록 변경 시 페이지 표시 데이터를 다시 계산합니다. 반면 선택한 종은 기존 Picked ID가 여전히 목록에 있을 때 유지합니다.
// MonsterDexWidget.cpp — Redraw() 발췌
Book = MonsterDexView::Build(*Component);
if (!Book.Pages.ContainsByPredicate(
[this](const FMonsterDexPageView& P) { return P.AreaObjectID == Picked; }))
{
Picked = Book.Pages.Num() ? Book.Pages[0].AreaObjectID : 0;
}Redraw()는 목록 Cell을 다시 구성하고 상세 화면을 갱신합니다. 하지만 사용자가 다른 종을 선택하는 동작은 Pick()에서 선택 프레임과 상세 화면만 바꿉니다. 목록의 정지 이미지도 도감을 열 때 생성한 CellPictures를 계속 사용합니다. 이 때문에 종을 선택할 때마다 전체 목록을 다시 캡처하지 않습니다.
첫 등록 시에는 Headline, 새 보상 단계 달성 시에는 Feed로 알림을 보냅니다. UI 알림은 기존 UNoticeSubsystem의 슬롯에 표시됩니다.

도감 화면은 상세용 Stage와 목록 썸네일용 SnapStage, 두 AMonsterDexStage를 생성합니다. 둘 다 로컬 SceneCaptureComponent2D의 RenderTarget으로 모델을 UI에 표시하지만 갱신 방식은 다릅니다.
상세 프리뷰 Stage
|
목록 썸네일 SnapStage
|
|---|---|
| 선택한 종의 모델을 실시간 렌더링 | 도감에 정의된 종을 순서대로 정지 캡처 |
| 드래그 회전·일정 시간 뒤 자동 회전 |
SnapSettleFrames만큼 기다린 뒤 CaptureScene()
|
| 640×640 RenderTarget(기본 설정) | 종별 256×256 RenderTarget |
종 선택 시 Show(DisplayClass)
|
화면을 여는 동안 한 번 Snap(Classes, 256)
|
DisplayClass의 Character 기본 객체(CDO)에서 Skeletal Mesh·애니메이션 클래스·상대 Transform·오버라이드 머티리얼을 읽어 캡처용 Mesh에 적용합니다. ShowOnlyComponents를 사용하므로 배경 월드의 Actor가 프리뷰에 들어오지 않습니다.
// MonsterDexStage.cpp — Frame()의 카메라 거리 계산
const FBox Box = Asset->GetImportedBounds().GetBox()
.TransformBy(Mesh->GetComponentTransform());
const FVector Extent = Box.GetExtent();
const float Across = FVector2D(Extent.X, Extent.Y).Size();
const float Half = FMath::Max(10.f, FMath::Max(Extent.Z, Across));
const float Distance = Half /
FMath::Tan(FMath::DegreesToRadians(FieldOfView * 0.5f)) * Margin;종별 모델의 크기가 달라도 프레임 안에 들어오도록 메시 Imported Bounds와 FOV로 카메라 거리를 계산합니다. 목록 캡처는 클래스별로 모델을 교체하고 몇 프레임 대기해 애니메이션 포즈를 반영한 뒤 찍습니다.
미발견 상태의 실루엣은 모델을 따로 준비하지 않고 UMonsterDexCellWidget::Show / UMonsterDexWidget::DrawPicked에서 RenderTarget을 표시하는 이미지의 색을 변경합니다. 도감을 닫으면 두 Stage Actor와 이미지 자료를 정리하고, OnDexChanged 구독을 해제합니다.
DT_MonsterDex.RewardTiers에 처치·포획 목표와 아이템·경험치 보상을 설정합니다. MonsterDexView::TierView는 기록의 수량과 수령 비트를 읽어 아래 세 상태를 구성합니다.
| 표시 상태 | 계산 기준 |
|---|---|
InProgress |
단계별 필요 처치·포획 횟수 미달성 |
Ready |
목표 달성, 미수령 |
Claimed |
ClaimedTiers의 해당 단계 비트 설정 |
UMonsterDexTierWidget은 Current / Required 수치와 ProgressBar를 표시하고, 보상 아이템에는 인벤토리에서도 사용하는 USlotWidget::AddReadOnly를 사용합니다. 클릭 시에는 UMonsterDexWidget::Claim()이 ServerClaimReward(AreaObjectID, Tier)를 요청합니다.
서버는 CanClaimReward()로 단계 범위·달성 조건·수령 여부를 다시 확인합니다. 조건을 통과하면 ClaimedTiers 비트를 먼저 설정하고 기록한 뒤 SonheimReward::Grant로 지급합니다. 같은 단계로 다시 요청해도 이미 수령한 비트 때문에 지급하지 않습니다.
도감 View에는 종별 서식지 지역 ID(Habitats)도 포함됩니다. 도감의 지도에서 보기 버튼은 현재 종 이름과 지역 ID 목록을 복사한 뒤 OpenTab(UITags::WorldMap, OnReady)를 호출합니다. 월드맵이 비동기로 열릴 수 있으므로 데이터는 화면이 열린 뒤 UWorldMapWidget::ShowHabitat()에 전달합니다.
ShowHabitat()은 지역 ID를 지도 마커의 위치로 변환하고, 서식지들이 들어오도록 지도 중심과 표시 범위를 조정합니다. 지도의 발견 기록·안개는 기존 UWorldMapComponent 데이터를 사용하며 도감에서 별도로 보관하지 않습니다.

- MonsterDexView.cpp · MonsterDexWidget.cpp — View 데이터·목록/상세/보상 UI
- MonsterDexStage.cpp — 3D 상세·목록 캡처
- MonsterDexTypes.h · MonsterDexSubsystem.cpp — 정의·저장
- MonsterDexComponent.cpp — 기록·복제·보상 처리
- 19. World Map & Fast Travel — 서식지 지도 연동
- 20. UI Performance — 3D 프리뷰의 SceneCapture 비용과 화면 종료 시 Render Target 참조
- 17. Quest System · 18. UMG v2 Migration
이 기술 문서는 프로젝트의 상세 구현을 다루는 '심층 분석' 자료입니다. README에서 생긴 궁금증을 이곳에서 코드를 통해 확인해 보세요.
| 문서 | 역할 | 내용 |
|---|---|---|
| 📋 Project Gallery | Root (전체 개요) | 주요 프로젝트 목록, 핵심 역량 요약 |
| 📁 Repository README | What (개요) | 프로젝트 목표, 데모 영상, 핵심 기능, 아키텍처 |
| 🔗 Tech Docs | How & Why (상세 구현) | 코드 분석, 설계 과정, 기술 회고, 트러블슈팅 |
- 04. Player & Character Systems
- 05. Combat, Skill & Animation
- 06. World Interaction Systems
- 07. Inventory & Crafting
- 08. Pal Capture & Partner Lifecycle
- 16. MonsterDex System
- 17. Quest System
- 19. World Map & Fast Travel