11 KiB
프로젝트 개요
작성일: 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/
실행 흐름
-
lib/main.dart- Flutter 바인딩을 초기화합니다.
ItemTable.load(),EnemyTable.load(),PlayerTable.load()로 JSON 데이터를 먼저 로드합니다.MultiProvider로SettingsProvider,ShopProvider,BattleProvider를 등록합니다.MainMenuScreen을 첫 화면으로 띄웁니다.
-
메인 메뉴
- 저장 데이터가 있으면
Continue가 노출됩니다. - 이어하기는
SaveManager.loadGame()결과를BattleProvider.loadFromSave()로 복원합니다. - 새 게임은
CharacterSelectionScreen으로 이동합니다.
- 저장 데이터가 있으면
-
새 게임 시작
CharacterSelectionScreen에서 전사를 선택합니다.BattleProvider.initializeBattle()로 플레이어와 1스테이지를 초기화합니다.StoryScreen을 거쳐MainWrapper로 진입합니다.
-
실제 플레이 화면
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를 내림 처리합니다.
전투 승리 골드는 다음 공식으로 지급됩니다.
baseGoldReward + stage * goldRewardPerStage + random(0..goldRewardVariance - 1)
저장 시스템
저장은 lib/game/save_manager.dart가 담당하며 shared_preferences를 사용합니다.
- 저장 키:
GameConfig.saveKey - 저장 시점: 새 스테이지 준비 시점
- 저장 내용: 스테이지, 턴 수, 플레이어 JSON, 저장 시각
- 패배 시 저장 데이터가 삭제됩니다.
현재 저장 구조는 아이템의 id를 중심으로 복원합니다. 따라서 런타임에 생성된 접두어, 이름, 세부 스탯 변형을 완전히 보존하려면 Item.toJson()/Item.fromJson() 형태의 상세 저장 구조가 필요합니다.
주요 폴더 구조
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/ 수학, 아이템, 토스트 유틸
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 상태 효과
주요 명령:
flutter pub get
flutter test
flutter run
웹으로 실행하려면 보통 다음 명령을 사용합니다.
flutter run -d chrome
확장 작업 가이드
새 아이템을 추가할 때:
assets/data/items.json에 템플릿을 추가합니다.- 이미지가 필요하면
assets/images/icons/...아래에 추가합니다. - 새 폴더를 추가했다면
pubspec.yaml의flutter.assets에 등록합니다. - 로딩 테스트나 랜덤 선택 테스트를 확인합니다.
새 적을 추가할 때:
assets/data/enemies.json의normal또는elite에 템플릿을 추가합니다.- 이미지가 필요하면
assets/images/enemies/에 추가합니다. - 티어와 장비 ID가 실제 아이템 데이터와 맞는지 확인합니다.
전투 밸런스를 조정할 때:
- 위험도별 성공률/효율은
BattleConfig를 수정합니다. - 스테이지 보상, 상점 주기, 회복량은
GameConfig를 수정합니다. - 장비 드롭 희귀도는
ItemConfig.defaultRarityWeights를 수정합니다.
현재 눈여겨볼 점
pubspec.yaml의 패키지 설명은 아직 Flutter 기본 문구입니다.- 앱 타이틀은
Colosseum's Choice지만 패키지명은game_test입니다. StoryScreen은 아직 플레이스홀더 이미지와 텍스트를 사용합니다.initializeBattle()에서 테스트용 아이템과 포션을 기본 지급하고 있습니다.- 저장은 아이템 ID 중심이라 생성된 접두어/세부 스탯 보존이 제한적입니다.
- 자산 폴더를 새로 추가할 때는
pubspec.yaml등록 여부를 반드시 확인해야 합니다.