Unreal 개발 본 캠프 26일차

HappyCircle·2026년 1월 5일

Unreal 개발

목록 보기
43/163

오늘 진행 내용

[CH2: 팀 프로젝트] 텍스트 콘솔 RPG

몬스터 드랍 시스템 & UI 리팩토링 (SOLID 적용)


0. 오늘의 목표

  • 데이터 기반(monster_drops.dat) 몬스터 드랍 시스템 구조 정리
  • 기존 전투 / 상점 / 인벤토리 로직 최대한 유지
  • SOLID 원칙을 지키는 책임 분리
  • 콘솔 UI를 Grid 기반 ASCII UI로 통합
  • UTF-8(한글) 환경에서도 UI 정렬 안정화

1. 몬스터 드랍 시스템 리팩토링

1.1 작업 목표

  • monster_drops.dat 단일 파일에서 다음을 모두 관리
    • ITEM / EFFECT 정의
    • ASCII 아트
    • 몬스터별 드랍 규칙(REWARD)
  • “30% 확률 드랍” 규칙을 코드 ↔ 데이터 1:1 대응
  • 드랍 로직을 전투 시스템과 완전히 분리

1.2 핵심 책임 분리

컴포넌트책임
DropTable아이템 프로토타입 저장 + 이름 기반 clone
RewardRepository몬스터별 드랍 규칙 저장 & 확률 계산
RewardManager전투 보상 오케스트레이션
ItemBootstrapdat 파일 로딩 및 의존성 연결
SimpleDataLoaderITEM / EFFECT / ASCII / REWARD 파싱

2. monster_drops.dat 설계

2.1 ITEM / EFFECT 영역

ITEM|작은 포션|0
EFFECT|HealFlat|Health|30|0

ITEM|강화된 철 검(+15)|0
EFFECT|AddStatFlat|Attack|15|0

2.2 몬스터 드랍 규칙 (REWARD)

REWARD|VariableSlime
PICK|작은 포션|22
PICK|초보자의 검(+5)|5
PICK|초보자의 갑옷(+10)|3
ENDREWARD
  • 추가 아이템은 30% 확률로 1개

  • PICK은 가중치 랜덤 선택

  • GUARANTEE는 특수 아이템만 허용

  • NONE 같은 예외 아이템 제거

3. RewardRepository 핵심 로직

3.1 rollNames()

std::vector<std::string>
RewardRepository::rollNames(const std::string& monsterName) const {
    std::vector<std::string> result;

    const Rule* rule = find(monsterName);
    if (!rule) return result;

    // 1) GUARANTEE
    for (const auto& g : rule->guarantees)
        result.push_back(g);

    // 2) 30% 확률 체크
    if (randInt(1, 100) > 30)
        return result;

    // 3) 가중치 PICK 1개
    // ...
    return result;
}
  • 아이템 생성은 하지 않음

  • 이름 목록만 반환

  • dat 규칙과 코드가 정확히 대응

4. 삽질 포인트 — 룰이 있는데 “룰을 찾을 수 없음”

4.1 증상

monster_drops.dat에 VariableSlime REWARD 존재

게임에서는 “룰을 찾을 수 없음” 로그 출력

4.2 원인

ItemBootstrap::build()

ITEM / EFFECT / ASCII만 로딩

REWARD 규칙을 RewardRepository에 주입하지 않음

4.3 해결

SimpleDataLoader::loadRewards()

같은 dat 파일을 두 번 읽음

1회: DropTable (아이템 프로토타입)

1회: RewardRepository (드랍 규칙)

5. 최종 로딩 흐름

ItemBootstrap::build(ctx, ...);

// 아이템 프로토타입
loadToVector("monster_drops.dat");   // DropTable

// 드랍 규칙
loadRewards("monster_drops.dat");    // RewardRepository

구조는 단순

책임은 명확

SOLID 준수

6. 전투 보상 시스템 구조

6.1 DropTable

Item* cloneByName(const std::string& name) const;

아이템 프로토타입 소유

아이템 생성 책임을 DropTable로 고정

6.2 RewardRepository

std::vector<std::string>
rollNames(const std::string& monsterName);

몬스터별 드랍 아이템 이름 목록 반환

확률 계산만 담당

6.3 RewardManager

기존 구조 유지
generateRewards()

applyRewards()

displayRewards()

void bind(DropTable&, RewardRepository&, Inventory&);

RewardRepository → 드랍 이름 목록

DropTable.cloneByName() → 실제 아이템 생성

Inventory에 추가

6.4 BattleResult 확장

std::vector<std::string> droppedItemNames;

Item* 대신 이름만 저장

소유권 / 메모리 문제 방지

UI / 지급 단계 분리

7. 콘솔 UI 리팩토링 (Grid 기반)

7.1 UI 책임 분리

컴포넌트 책임
UIGrid 출력 / 입력 유틸
UIManager 화면 전환 / 메뉴 흐름
GameManager / BattleService 실제 게임 로직

7.2 인벤토리 UI 통합

╔════════════════════════════════════════════════════════════╗
║                    🎒  인 벤 토 리  🎒                 ║
╚════════════════════════════════════════════════════════════╝

  ┌────────────────────────────────────────────────────────┐
  │  1. 💊 아이템 사용                                  │
  │  2. ⚔️  장비 장착                                   │
  │  3. 🛡️  장비 해제                                   │
  │  0. 🚪 나가기                                      │
  └────────────────────────────────────────────────────────┘

7.3 장비 해제 UI

╔════════════════════════════════════════════════════════════╗
║                    🛡️  장 비  해 제  🛡️                ║
╚════════════════════════════════════════════════════════════╝

  ┌────────────────────────────────────────────────────────┐
  │  1. ⚔️  무기 해제                                     │
  │  2. 🛡️  방어구 해제                                   │
  │  0. ↩️  취소                                         │
  └────────────────────────────────────────────────────────┘

8. UTF-8 Grid 정렬 문제 해결

8.1 문제

std::string::size() 기준 폭 계산

한글(2칸) + 영문(1칸) 혼합 시 Grid 붕괴

8.2 해결

utf8NextCodepoint(...)
displayWidthUTF8(...)
fitWidthUTF8(...)
autoCellWidthUTF8(...)

UTF-8 코드포인트 기반 실제 표시 폭 계산

한글 / 영문 / 기호 혼합 환경에서도 Grid 안정화

9. 버그 수정 — 장착 아이템이 사용되는 문제

9.1 해결 (Inventory 레벨에서 차단)

if (isEquippedPtr(it)) {
    std::cout <<; "장착 중인 아이템은 사용할 수 없습니다.\n";
    return;
}

if (it->isEquipItem()) {
    std::cout << "장비 아이템은 '사용'이 아니라 '장착'하세요.\n";
    return;
}

UI가 아니라 Inventory 계층에서 방어

profile
개발합시다!

0개의 댓글