257 lines
11 KiB
Markdown
257 lines
11 KiB
Markdown
# 프로젝트 개요
|
|
|
|
작성일: 2026-04-27
|
|
|
|
## 한 줄 요약
|
|
|
|
`Colosseum's Choice`는 Flutter로 만든 콜로세움 테마의 턴제 로그라이트 전투 게임입니다. 플레이어는 전투, 상점, 휴식 스테이지를 반복하며 장비와 소모품을 얻고, 위험도 선택 기반의 공격/방어 액션으로 더 높은 스테이지를 진행합니다.
|
|
|
|
## 기본 정보
|
|
|
|
- 앱 타이틀: `Colosseum's Choice`
|
|
- Flutter 패키지명: `game_test`
|
|
- 주요 기술: Flutter, Dart, Provider, SharedPreferences
|
|
- 지원 플랫폼 폴더: `android/`, `ios/`, `web/`, `macos/`, `linux/`, `windows/`
|
|
- 주요 소스 루트: `lib/`
|
|
- 주요 데이터 루트: `assets/data/`
|
|
|
|
## 실행 흐름
|
|
|
|
1. `lib/main.dart`
|
|
- Flutter 바인딩을 초기화합니다.
|
|
- `ItemTable.load()`, `EnemyTable.load()`, `PlayerTable.load()`로 JSON 데이터를 먼저 로드합니다.
|
|
- `MultiProvider`로 `SettingsProvider`, `ShopProvider`, `BattleProvider`를 등록합니다.
|
|
- `MainMenuScreen`을 첫 화면으로 띄웁니다.
|
|
|
|
2. 메인 메뉴
|
|
- 저장 데이터가 있으면 `Continue`가 노출됩니다.
|
|
- 이어하기는 `SaveManager.loadGame()` 결과를 `BattleProvider.loadFromSave()`로 복원합니다.
|
|
- 새 게임은 `CharacterSelectionScreen`으로 이동합니다.
|
|
|
|
3. 새 게임 시작
|
|
- `CharacterSelectionScreen`에서 전사를 선택합니다.
|
|
- `BattleProvider.initializeBattle()`로 플레이어와 1스테이지를 초기화합니다.
|
|
- `StoryScreen`을 거쳐 `MainWrapper`로 진입합니다.
|
|
|
|
4. 실제 플레이 화면
|
|
- `MainWrapper`는 하단 탭 구조입니다.
|
|
- 첫 탭은 현재 스테이지 타입에 따라 `Battle`, `Shop`, `Rest`로 표시됩니다.
|
|
- 나머지 탭은 `Inventory`, `Settings`입니다.
|
|
|
|
## 핵심 게임 루프
|
|
|
|
- 스테이지 타입은 `BattleProvider._prepareNextStage()`에서 결정됩니다.
|
|
- 우선순위는 `elite -> shop -> rest -> battle`입니다.
|
|
- 현재 설정 기준:
|
|
- 엘리트: 12스테이지마다
|
|
- 상점: 5스테이지마다
|
|
- 휴식: 8스테이지마다
|
|
- 티어 1: 1-12 스테이지
|
|
- 티어 2: 13-24 스테이지
|
|
- 티어 3: 25 스테이지 이후
|
|
|
|
전투에서 승리하면 골드와 보상 선택지가 지급되고, 보상을 선택하거나 스킵하면 체력을 일부 회복한 뒤 다음 스테이지로 넘어갑니다. 상점/휴식 스테이지는 별도 UI를 거쳐 다음 스테이지로 진행합니다.
|
|
|
|
## 전투 시스템
|
|
|
|
전투는 `BattleProvider`와 `CombatCalculator`가 중심입니다.
|
|
|
|
- 플레이어 액션: `attack`, `defend`
|
|
- 위험도: `safe`, `normal`, `risky`
|
|
- 공격은 `totalAtk`, 방어는 `totalDefense`를 기반으로 계산됩니다.
|
|
- 위험도는 성공 확률과 효율 배율을 함께 바꿉니다.
|
|
- 운(`luck`)은 성공 확률에 더해지고 최대 100%로 제한됩니다.
|
|
- 적은 매 턴 `EnemyIntent`를 생성해 다음 행동과 위험도를 미리 보여줍니다.
|
|
|
|
현재 전투 수치 설정은 `lib/game/config/battle_config.dart`에 있습니다.
|
|
|
|
- Safe: 성공률 100%, 공격 50%, 방어 100%
|
|
- Normal: 성공률 80%, 공격 100%, 방어 200%
|
|
- Risky: 성공률 40%, 공격 200%, 방어 300%
|
|
- 적 행동 비율: 공격 70%, 방어 30%
|
|
|
|
피해 계산은 방어도와 상태 이상을 반영합니다.
|
|
|
|
- 방어도는 HP 피해보다 먼저 피해를 흡수합니다.
|
|
- `vulnerable` 상태면 받는 피해가 `GameConfig.vulnerableDamageMultiplier`만큼 증가합니다.
|
|
- 회피(`dodge`)가 성공하면 공격 피해가 들어가지 않습니다.
|
|
- 현재 방어도 감소율은 `GameConfig.armorDecayRate`로 관리됩니다.
|
|
|
|
## 상태 이상
|
|
|
|
상태 이상 타입은 `lib/game/enums.dart`의 `StatusEffectType`에 정의되어 있습니다.
|
|
|
|
- `stun`: 해당 턴 행동 불가
|
|
- `vulnerable`: 받는 피해 증가
|
|
- `bleed`: 턴 시작 시 고정 피해
|
|
- `defenseForbidden`: 방어 액션 사용 불가
|
|
- `disarmed`: 공격력 감소
|
|
- `attackUp`: 공격력 증가 버프
|
|
|
|
아이템 효과는 `ItemEffect`로 표현되며, 공격 성공 후 `CombatCalculator.getAppliedEffects()`와 `BattleProvider._tryApplyStatusEffects()`를 통해 대상에게 적용됩니다.
|
|
|
|
## 보상, 아이템, 장비
|
|
|
|
아이템 데이터는 `assets/data/items.json`에서 로드되고 `ItemTable`이 관리합니다.
|
|
|
|
- 슬롯: weapon, armor, shield, accessory, consumable
|
|
- 희귀도: normal, magic, rare, legendary, unique
|
|
- 티어: tier1, tier2, tier3
|
|
- 장비 스탯: 공격력, HP, 방어도, 회피, 운
|
|
- 소모품: 즉시 회복, 방어도 증가, 버프 적용 등에 사용됩니다.
|
|
|
|
아이템 생성은 `LootGenerator`가 담당합니다.
|
|
|
|
- Normal 등급은 가중치 기반 접두어로 기본 스탯 변형을 받을 수 있습니다.
|
|
- Magic 등급은 접두어를 통해 하나 이상의 스탯 보너스를 받을 수 있습니다.
|
|
- Rare 등급은 별도 이름 생성과 강한 스탯 변형을 받을 수 있습니다.
|
|
- Legendary/Unique는 기본 템플릿 성격을 유지합니다.
|
|
|
|
인벤토리는 `Character.inventory`에 보관되고, 최대 크기는 `GameConfig.maxInventorySize`입니다. 장비 장착/해제 시 최대 HP 변화로 인한 체력 악용을 막기 위해 현재 HP 비율을 유지합니다.
|
|
|
|
## 경제와 상점
|
|
|
|
상점 로직은 `ShopProvider`와 `widgets/stage/shop_ui.dart`가 담당합니다.
|
|
|
|
- 상점 스테이지에서 장비 4개와 소모품 2개를 생성합니다.
|
|
- 아이템 구매는 골드와 인벤토리 여유 공간을 검사합니다.
|
|
- 재입고 비용은 `GameConfig.shopRerollCost`입니다.
|
|
- 판매 가격은 `item.price * GameConfig.sellPriceMultiplier`를 내림 처리합니다.
|
|
|
|
전투 승리 골드는 다음 공식으로 지급됩니다.
|
|
|
|
```text
|
|
baseGoldReward + stage * goldRewardPerStage + random(0..goldRewardVariance - 1)
|
|
```
|
|
|
|
## 저장 시스템
|
|
|
|
저장은 `lib/game/save_manager.dart`가 담당하며 `shared_preferences`를 사용합니다.
|
|
|
|
- 저장 키: `GameConfig.saveKey`
|
|
- 저장 시점: 새 스테이지 준비 시점
|
|
- 저장 내용: 스테이지, 턴 수, 플레이어 JSON, 저장 시각
|
|
- 패배 시 저장 데이터가 삭제됩니다.
|
|
|
|
현재 저장 구조는 아이템의 `id`를 중심으로 복원합니다. 따라서 런타임에 생성된 접두어, 이름, 세부 스탯 변형을 완전히 보존하려면 `Item.toJson()`/`Item.fromJson()` 형태의 상세 저장 구조가 필요합니다.
|
|
|
|
## 주요 폴더 구조
|
|
|
|
```text
|
|
lib/
|
|
main.dart 앱 진입점, 데이터 선로딩, Provider 등록
|
|
game/
|
|
model/ Character, Item, Stage, StatusEffect 등 도메인 모델
|
|
data/ JSON 로더, 아이템/적/플레이어 테이블, 이름/접두어 데이터
|
|
logic/ 전투 계산, 전리품 생성, 전투 로그 관리
|
|
config/ 게임 밸런스, 전투 수치, 테마, 문구 상수
|
|
enums.dart 게임 전반에서 쓰는 enum 정의
|
|
save_manager.dart SharedPreferences 저장/불러오기
|
|
providers/ BattleProvider, ShopProvider, SettingsProvider
|
|
screens/ 메인 메뉴, 캐릭터 선택, 스토리, 전투, 인벤토리, 설정
|
|
widgets/
|
|
battle/ 전투 UI, 애니메이션, 피드백 텍스트, 로그, 컨트롤
|
|
inventory/ 캐릭터 스탯, 장비, 인벤토리 그리드
|
|
stage/ 상점, 휴식 스테이지 UI
|
|
common/ 공통 버튼, 아이콘, 아이템 카드
|
|
utils/ 수학, 아이템, 토스트 유틸
|
|
```
|
|
|
|
```text
|
|
assets/
|
|
data/
|
|
items.json 아이템 템플릿
|
|
enemies.json 적 템플릿
|
|
players.json 플레이어 템플릿
|
|
icon/ 레거시 아이콘 데이터
|
|
images/
|
|
character/ 플레이어 캐릭터 이미지와 공격 프레임
|
|
enemies/ 적 이미지
|
|
background/ 배경 이미지
|
|
icons/ 장비, 포션, 테두리 등 UI 아이콘
|
|
```
|
|
|
|
## 화면 구성
|
|
|
|
- `MainMenuScreen`: 시작 화면, 저장 데이터 확인, 이어하기/새 게임
|
|
- `CharacterSelectionScreen`: 플레이어 템플릿 선택
|
|
- `StoryScreen`: 전투 시작 전 스토리 화면
|
|
- `MainWrapper`: 하단 탭과 현재 스테이지 화면 전환
|
|
- `BattleScreen`: 전투 UI, 이펙트 스트림 구독, 애니메이션 처리
|
|
- `InventoryScreen`: 캐릭터 능력치, 장착 장비, 인벤토리
|
|
- `SettingsScreen`: 애니메이션, 흔들림 옵션, 재시작, 메인 메뉴 복귀
|
|
|
|
## 이벤트와 UI 동기화
|
|
|
|
전투 로직과 UI 애니메이션은 이벤트 스트림으로 느슨하게 연결되어 있습니다.
|
|
|
|
- `DamageEvent`: 실제 HP 피해 텍스트 표시
|
|
- `EffectEvent`: 공격/방어/실패/회피 등 시각 효과와 충돌 타이밍 전달
|
|
- `BattleScreen`은 두 스트림을 구독하고, 애니메이션이 끝나는 시점에 `BattleProvider.handleImpact()`를 호출해 실제 피해/방어도 변화를 적용합니다.
|
|
|
|
이 구조 덕분에 계산 로직과 화면 효과가 분리되어 있지만, 턴 전환은 애니메이션 완료 콜백에 의존하는 부분이 있으므로 전투 UI 수정 시 `handleImpact()` 호출 흐름을 함께 확인해야 합니다.
|
|
|
|
## 설정과 밸런스 조정 위치
|
|
|
|
- 스테이지, 경제, 저장, 회복, 피해 배율: `lib/game/config/game_config.dart`
|
|
- 전투 성공률, 효율, 이펙트 크기/색상: `lib/game/config/battle_config.dart`
|
|
- 아이템 희귀도 가중치, 접두어 확률: `lib/game/config/item_config.dart`
|
|
- 앱 문구: `lib/game/config/app_strings.dart`
|
|
- 색상, 폰트 크기, UI 상수: `lib/game/config/theme_config.dart`
|
|
|
|
## 테스트
|
|
|
|
테스트는 `test/` 아래에 있습니다.
|
|
|
|
- 아이템 로딩과 랜덤 선택
|
|
- 아이템 희귀도/티어
|
|
- 적 데이터 로딩
|
|
- 캐릭터 장비와 HP 비율 처리
|
|
- 전투 Provider의 방어도 초기화
|
|
- 적 의도 생성/실행
|
|
- disarm 상태 효과
|
|
|
|
주요 명령:
|
|
|
|
```bash
|
|
flutter pub get
|
|
flutter test
|
|
flutter run
|
|
```
|
|
|
|
웹으로 실행하려면 보통 다음 명령을 사용합니다.
|
|
|
|
```bash
|
|
flutter run -d chrome
|
|
```
|
|
|
|
## 확장 작업 가이드
|
|
|
|
새 아이템을 추가할 때:
|
|
|
|
1. `assets/data/items.json`에 템플릿을 추가합니다.
|
|
2. 이미지가 필요하면 `assets/images/icons/...` 아래에 추가합니다.
|
|
3. 새 폴더를 추가했다면 `pubspec.yaml`의 `flutter.assets`에 등록합니다.
|
|
4. 로딩 테스트나 랜덤 선택 테스트를 확인합니다.
|
|
|
|
새 적을 추가할 때:
|
|
|
|
1. `assets/data/enemies.json`의 `normal` 또는 `elite`에 템플릿을 추가합니다.
|
|
2. 이미지가 필요하면 `assets/images/enemies/`에 추가합니다.
|
|
3. 티어와 장비 ID가 실제 아이템 데이터와 맞는지 확인합니다.
|
|
|
|
전투 밸런스를 조정할 때:
|
|
|
|
1. 위험도별 성공률/효율은 `BattleConfig`를 수정합니다.
|
|
2. 스테이지 보상, 상점 주기, 회복량은 `GameConfig`를 수정합니다.
|
|
3. 장비 드롭 희귀도는 `ItemConfig.defaultRarityWeights`를 수정합니다.
|
|
|
|
## 현재 눈여겨볼 점
|
|
|
|
- `pubspec.yaml`의 패키지 설명은 아직 Flutter 기본 문구입니다.
|
|
- 앱 타이틀은 `Colosseum's Choice`지만 패키지명은 `game_test`입니다.
|
|
- `StoryScreen`은 아직 플레이스홀더 이미지와 텍스트를 사용합니다.
|
|
- `initializeBattle()`에서 테스트용 아이템과 포션을 기본 지급하고 있습니다.
|
|
- 저장은 아이템 ID 중심이라 생성된 접두어/세부 스탯 보존이 제한적입니다.
|
|
- 자산 폴더를 새로 추가할 때는 `pubspec.yaml` 등록 여부를 반드시 확인해야 합니다.
|