update
|
|
@ -1,5 +1,14 @@
|
||||||
{
|
{
|
||||||
"normal": [
|
"normal": [
|
||||||
|
{
|
||||||
|
"name": "Park",
|
||||||
|
"baseHp": 20,
|
||||||
|
"baseAtk": 5,
|
||||||
|
"baseDefense": 5,
|
||||||
|
"image": "assets/images/enemies/park.png",
|
||||||
|
"equipment": ["rusty_dagger"],
|
||||||
|
"tier": 1
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Goblin",
|
"name": "Goblin",
|
||||||
"baseHp": 20,
|
"baseHp": 20,
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
|
@ -723,14 +723,9 @@ class _BattleScreenState extends State<BattleScreen> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
bool success = battleProvider.selectReward(item);
|
bool success = battleProvider.selectReward(item);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ToastUtils.showTopToast(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text(
|
"${AppStrings.inventoryFull} Cannot take item.",
|
||||||
"${AppStrings.inventoryFull} Cannot take item.",
|
|
||||||
),
|
|
||||||
backgroundColor:
|
|
||||||
ThemeConfig.snackBarErrorBg,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export 'utils/game_math.dart';
|
export 'utils/game_math.dart';
|
||||||
export 'utils/item_utils.dart';
|
export 'utils/item_utils.dart';
|
||||||
|
export 'utils/toast_utils.dart';
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import '../game/config.dart';
|
||||||
|
|
||||||
|
class ToastUtils {
|
||||||
|
static void showTopToast(
|
||||||
|
BuildContext context,
|
||||||
|
String message, {
|
||||||
|
Color? color,
|
||||||
|
}) {
|
||||||
|
final overlay = Overlay.of(context);
|
||||||
|
late OverlayEntry overlayEntry;
|
||||||
|
|
||||||
|
overlayEntry = OverlayEntry(
|
||||||
|
builder: (context) => Positioned(
|
||||||
|
top: MediaQuery.of(context).padding.top + 10,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: TweenAnimationBuilder<double>(
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
curve: Curves.easeOutBack,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Transform.translate(
|
||||||
|
offset: Offset(0, -50 * (1 - value)),
|
||||||
|
child: Opacity(
|
||||||
|
opacity: value.clamp(0.0, 1.0),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color ?? ThemeConfig.snackBarErrorBg,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.info_outline, color: Colors.white),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
message,
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: ThemeConfig.fontSizeMedium,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
overlay.insert(overlayEntry);
|
||||||
|
|
||||||
|
// Auto remove after duration
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
overlayEntry.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,13 +7,12 @@ class BattleHeader extends StatelessWidget {
|
||||||
const BattleHeader({super.key});
|
const BattleHeader({super.key});
|
||||||
|
|
||||||
String _getStageDisplayString(int stage) {
|
String _getStageDisplayString(int stage) {
|
||||||
// 3 Stages per Tier logic
|
// Tier 1: 1-12 -> Underground Illegal Arena
|
||||||
// Tier 1: 1, 2, 3 -> Underground Illegal Arena
|
// Tier 2: 13-24 -> Colosseum
|
||||||
// Tier 2: 4, 5, 6 -> Colosseum
|
// Tier 3: 25-36 -> King's Arena
|
||||||
// Tier 3: 7, 8, 9 -> King's Arena
|
|
||||||
|
|
||||||
final int tier = (stage - 1) ~/ 3 + 1;
|
final int tier = (stage - 1) ~/ 12 + 1;
|
||||||
final int round = (stage - 1) % 3 + 1;
|
final int round = (stage - 1) % 12 + 1;
|
||||||
String tierName = "";
|
String tierName = "";
|
||||||
|
|
||||||
switch (tier) {
|
switch (tier) {
|
||||||
|
|
@ -29,6 +28,10 @@ class BattleHeader extends StatelessWidget {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (round == 12) {
|
||||||
|
return "$tierName - BOSS";
|
||||||
|
}
|
||||||
|
|
||||||
return "$tierName - $round";
|
return "$tierName - $round";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,11 +122,24 @@ class CharacterStatusCard extends StatelessWidget {
|
||||||
size: 60,
|
size: 60,
|
||||||
color: ThemeConfig.textColorWhite,
|
color: ThemeConfig.textColorWhite,
|
||||||
) // 플레이어 아이콘
|
) // 플레이어 아이콘
|
||||||
: Image.asset(
|
: (character.image != null && character.image!.isNotEmpty)
|
||||||
'assets/data/enemy/enemy_01.png',
|
? Image.asset(
|
||||||
|
character.image!,
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 200,
|
height: 200,
|
||||||
fit: BoxFit.contain,
|
fit: BoxFit.contain,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return const Icon(
|
||||||
|
Icons.psychology,
|
||||||
|
size: 60,
|
||||||
|
color: ThemeConfig.textColorWhite,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const Icon(
|
||||||
|
Icons.psychology,
|
||||||
|
size: 60,
|
||||||
|
color: ThemeConfig.textColorWhite,
|
||||||
), // 적 이미지
|
), // 적 이미지
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import '../../../game/model/item.dart';
|
||||||
import '../../../game/config/theme_config.dart';
|
import '../../../game/config/theme_config.dart';
|
||||||
import '../../../game/config/game_config.dart';
|
import '../../../game/config/game_config.dart';
|
||||||
import '../../../game/model/entity.dart';
|
import '../../../game/model/entity.dart';
|
||||||
|
import '../../utils.dart'; // Import for ToastUtils
|
||||||
import '../inventory/inventory_grid_widget.dart';
|
import '../inventory/inventory_grid_widget.dart';
|
||||||
import '../common/item_card_widget.dart';
|
import '../common/item_card_widget.dart';
|
||||||
|
|
||||||
|
|
@ -115,6 +116,7 @@ class ShopUI extends StatelessWidget {
|
||||||
item,
|
item,
|
||||||
shopProvider,
|
shopProvider,
|
||||||
player,
|
player,
|
||||||
|
battleProvider, // Pass battleProvider
|
||||||
),
|
),
|
||||||
child: ItemCardWidget(
|
child: ItemCardWidget(
|
||||||
item: item,
|
item: item,
|
||||||
|
|
@ -155,11 +157,9 @@ class ShopUI extends StatelessWidget {
|
||||||
battleProvider.stage,
|
battleProvider.stage,
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ToastUtils.showTopToast(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text("Not enough gold to reroll!"),
|
"Not enough gold to reroll!",
|
||||||
backgroundColor: Colors.red,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,13 +205,13 @@ class ShopUI extends StatelessWidget {
|
||||||
Item item,
|
Item item,
|
||||||
ShopProvider shopProvider,
|
ShopProvider shopProvider,
|
||||||
Character player,
|
Character player,
|
||||||
|
BattleProvider battleProvider,
|
||||||
) {
|
) {
|
||||||
if (player.gold < item.price) {
|
if (player.gold < item.price) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ToastUtils.showTopToast(
|
||||||
const SnackBar(
|
context,
|
||||||
content: Text("Not enough gold!"),
|
"Not enough gold!",
|
||||||
backgroundColor: Colors.red,
|
color: ThemeConfig.snackBarErrorBg,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -236,18 +236,16 @@ class ShopUI extends StatelessWidget {
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
battleProvider.refreshUI(); // Update UI
|
battleProvider.refreshUI(); // Update UI
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ToastUtils.showTopToast(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text("Bought ${item.name}"),
|
"Bought ${item.name}",
|
||||||
backgroundColor: Colors.green,
|
color: Colors.green,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ToastUtils.showTopToast(
|
||||||
SnackBar(
|
context,
|
||||||
content: Text(shopProvider.lastShopMessage),
|
shopProvider.lastShopMessage,
|
||||||
backgroundColor: Colors.red,
|
color: ThemeConfig.snackBarErrorBg,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,114 @@
|
||||||
|
|
||||||
### B. 전투 시스템 (`BattleProvider`)
|
### B. 전투 시스템 (`BattleProvider`)
|
||||||
|
|
||||||
|
- **턴제 전투:** 플레이어 턴 -> 적 턴.
|
||||||
|
- **행동 선택:** 공격(Attack) / 방어(Defend).
|
||||||
|
- **리스크 시스템 (Risk System):**
|
||||||
|
- **Safe:** 성공률 100%+, 효율 50%.
|
||||||
|
- **Normal:** 성공률 80%+, 효율 100%.
|
||||||
|
- **Risky:** 성공률 40%+, 효율 200% (성공 시 강력한 이펙트).
|
||||||
|
- **Luck 보정:** `totalLuck` 1당 성공률 +1%.
|
||||||
|
- **적 인공지능 (Enemy AI & Intent):**
|
||||||
|
- **Intent UI:** 적의 다음 행동(공격/방어, 데미지/방어도) 미리 표시.
|
||||||
|
- **동기화된 애니메이션:** 적 행동 결정(`_generateEnemyIntent`)은 이전 애니메이션이 완전히 끝난 후 이루어짐.
|
||||||
|
- **선제 방어:** 적이 방어 행동을 선택하면 턴 시작 시 **데이터상으로 즉시 방어도가 적용되나, 시각적 애니메이션은 플레이어가 행동을 선택하는 시점에 발동됨.**
|
||||||
|
- **애니메이션 및 타격감 (Visuals & Impact):**
|
||||||
|
- **UI 주도 Impact 처리:** 애니메이션 타격 시점(`onImpact`)에 정확히 데미지가 적용되고 텍스트가 뜸 (완벽한 동기화).
|
||||||
|
- **적 돌진:** 적도 공격 시 플레이어 위치로 돌진함 (설정에서 끄기 가능).
|
||||||
|
- **이펙트:** 타격 아이콘, 데미지 텍스트(Floating Text, Risky 공격 시 크기 확대), 화면 흔들림(`ShakeWidget`), 폭발(`ExplosionWidget`). **적 방어 시 성공/실패 이펙트 추가.**
|
||||||
|
- **상태이상:** `Stun`, `Bleed`, `Vulnerable`, `DefenseForbidden`.
|
||||||
|
- **UI 알림 (Toast):** 하단 네비게이션을 가리지 않는 상단 `Overlay` 기반 알림 시스템.
|
||||||
|
|
||||||
|
### C. 데이터 및 로직 (Architecture)
|
||||||
|
|
||||||
|
- **Data-Driven:** `items.json`, `enemies.json`, `players.json`.
|
||||||
|
- **Logic 분리:**
|
||||||
|
- `BattleProvider`: UI 상태 관리 및 이벤트 스트림(`damageStream`, `effectStream`) 발송.
|
||||||
|
- `CombatCalculator`: 데미지 공식, 확률 계산, 상태이상 로직 순수 함수화. **공격/방어 액션 타입별 효율 분리.**
|
||||||
|
- `BattleLogManager`: 전투 로그 관리.
|
||||||
|
- `LootGenerator`: 아이템 생성, 접두사(Prefix) 부여, 랜덤 스탯 로직.
|
||||||
|
- `SettingsProvider`: 전역 설정(애니메이션 on/off 등) 관리 및 영구 저장.
|
||||||
|
- **Soft i18n:** UI 텍스트는 `lib/game/config/app_strings.dart`에서 통합 관리.
|
||||||
|
- **Config:** `GameConfig`, `BattleConfig`, `ItemConfig` 등 설정 값 중앙화. **BattleConfig의 공격/방어 효율 분리.**
|
||||||
|
|
||||||
|
### D. 아이템 및 경제
|
||||||
|
|
||||||
|
- **장비:** 무기, 방어구, 방패, 장신구.
|
||||||
|
- **시스템:**
|
||||||
|
- **Rarity:** Common ~ Unique.
|
||||||
|
- **Tier:** 라운드 진행도에 따라 상위 티어 아이템 등장.
|
||||||
|
- **Prefix:** Rarity에 따라 접두사가 붙으며 스탯이 변형됨 (예: "Sharp Wooden Sword").
|
||||||
|
- **상점 (`ShopProvider`):** 아이템 구매/판매, 리롤(Reroll), 인벤토리 관리.
|
||||||
|
|
||||||
|
### E. 저장 및 진행 (Persistence)
|
||||||
|
|
||||||
|
- **자동 저장:** 스테이지 클리어 시 `SaveManager`를 통해 자동 저장.
|
||||||
|
- **Permadeath:** 패배 시 저장 데이터 삭제 (로그라이크 요소).
|
||||||
|
|
||||||
|
### G. 스테이지 시스템 (Stage System)
|
||||||
|
|
||||||
|
- **Map Generation:** 진행에 따라 랜덤하게 다음 스테이지 타입이 결정됨 (현재는 단순 랜덤).
|
||||||
|
- **Underground Colosseum System (Rounds/Tiers):**
|
||||||
|
- **Round Progression:** 스테이지 진행(`stage` count)에 따라 난이도(Tier)가 상승.
|
||||||
|
- **Tier:**
|
||||||
|
- Tier 1: Stage 1 ~ 12 (지하 불법 투기장)
|
||||||
|
- Tier 2: Stage 13 ~ 24 (콜로세움)
|
||||||
|
- Tier 3: Stage 25+ (왕의 투기장)
|
||||||
|
- **Stage Types:**
|
||||||
|
- **Battle:** 일반 몬스터 전투.
|
||||||
|
- **Elite:** 강화된 몬스터 전투 (보상 증가, 12 스테이지마다 등장).
|
||||||
|
- **Shop:** 아이템 구매/판매/리롤 (5 스테이지마다 등장).
|
||||||
|
- **Rest:** 휴식 (8 스테이지마다 등장).
|
||||||
|
|
||||||
|
### F. 코드 구조 (Code Structure - Barrel Pattern)
|
||||||
|
|
||||||
|
- **Barrel File Pattern:** `lib/` 내의 모든 주요 디렉토리는 해당 폴더의 파일들을 묶어주는 단일 진입점 파일(`.dart`)을 가집니다.
|
||||||
|
- `lib/game/models.dart`, `lib/game/config.dart`, `lib/game/data.dart`, `lib/game/logic.dart`
|
||||||
|
- `lib/providers.dart`, `lib/utils.dart`, `lib/screens.dart`, `lib/widgets.dart`
|
||||||
|
- **Imports:** 개별 파일 import 대신 위 Barrel File을 사용하여 가독성과 유지보수성을 높였습니다.
|
||||||
|
|
||||||
|
## 3. 작업 컨벤션 (Working Conventions)
|
||||||
|
|
||||||
|
- **Prompt Driven Development:** `prompt/XX_description.md` 유지. (유사 작업 통합 및 인덱스 정리 권장)
|
||||||
|
- **i18n Strategy (Soft i18n):** UI에 표시되는 문자열은 하드코딩하지 않고 `lib/game/config/app_strings.dart`의 상수를 사용해야 합니다. (전투 로그 등 동적 문자열 제외)
|
||||||
|
- **Config Management:** 하드코딩되는 값들은 `config` 폴더 내 파일들(`lib/game/config/` 등)에서 통합 관리할 수 있도록 작성해야 합니다.
|
||||||
|
- **State Management:** `Provider` (UI 상태) + `Stream` (이벤트성 데이터).
|
||||||
|
- **Data:** JSON 기반 + `Table` 클래스로 로드.
|
||||||
|
- **Barrel File Pattern (Strict):** `lib/` 하위의 모든 주요 디렉토리는 Barrel File을 유지해야 하며, 외부에서 참조 시 **반드시** 이 Barrel File을 import 해야 합니다. 개별 파일에 대한 직접 import는 허용되지 않습니다.
|
||||||
|
|
||||||
|
## 4. 최근 주요 변경 사항 (Change Log)
|
||||||
|
|
||||||
|
- **[Refactor] BattleProvider:** `CombatCalculator`, `BattleLogManager`, `LootGenerator` 분리로 코드 다이어트.
|
||||||
|
- **[Refactor] Animation Sync:** `Future.delayed` 예측 방식을 버리고, UI 애니메이션 콜백(`onImpact`)을 통해 로직을 트리거하는 방식으로 변경하여 타격감 동기화 해결.
|
||||||
|
- **[Refactor] Settings:** `SettingsProvider` 도입 및 적 애니메이션 토글 기능 추가.
|
||||||
|
|
||||||
|
# 00. 프로젝트 컨텍스트 및 복구 (Project Context & Restore Point)
|
||||||
|
|
||||||
|
이 파일은 다른 개발 환경이나 새로운 AI 세션에서 프로젝트의 현재 상태를 빠르게 파악하고 작업을 이어가기 위해 작성되었습니다.
|
||||||
|
|
||||||
|
## 1. 프로젝트 개요
|
||||||
|
|
||||||
|
- **프로젝트명:** Colosseum's Choice
|
||||||
|
- **플랫폼:** Flutter (Android/iOS/Web/Desktop)
|
||||||
|
- **장르:** 텍스트 기반의 턴제 RPG + GUI (로그라이크 요소 포함)
|
||||||
|
- **상태:** 핵심 시스템 구현 완료 및 안정화 (i18n 구조 적용, 애니메이션 동기화 완료)
|
||||||
|
|
||||||
|
## 2. 현재 구현된 핵심 기능 (Feature Status)
|
||||||
|
|
||||||
|
### A. 게임 흐름 (Game Flow)
|
||||||
|
|
||||||
|
1. **메인 메뉴 (`MainMenuScreen`):** 게임 시작, 이어하기(저장된 데이터 있을 시), 설정 버튼.
|
||||||
|
2. **캐릭터 선택 (`CharacterSelectionScreen`):** 'Warrior' 직업 구현 (스탯 확인 후 시작).
|
||||||
|
3. **메인 게임 (`MainWrapper`):** 하단 탭 네비게이션 (Battle / Inventory / Settings).
|
||||||
|
4. **설정 (`SettingsScreen`):**
|
||||||
|
- 적 애니메이션 활성화/비활성화 토글 (`SettingsProvider` 연동).
|
||||||
|
- 게임 재시작, 메인 메뉴로 돌아가기 기능.
|
||||||
|
5. **반응형 레이아웃 (Responsive UI):**
|
||||||
|
- `ResponsiveContainer`를 통해 다양한 화면 크기 대응 (최대 너비/높이 제한).
|
||||||
|
- Battle UI: 플레이어(좌하단) vs 적(우상단) 대각선 구도.
|
||||||
|
|
||||||
|
### B. 전투 시스템 (`BattleProvider`)
|
||||||
|
|
||||||
- **턴제 전투:** 플레이어 턴 -> 적 턴.
|
- **턴제 전투:** 플레이어 턴 -> 적 턴.
|
||||||
- **행동 선택:** 공격(Attack) / 방어(Defend).
|
- **행동 선택:** 공격(Attack) / 방어(Defend).
|
||||||
- **리스크 시스템 (Risk System):**
|
- **리스크 시스템 (Risk System):**
|
||||||
|
|
@ -111,6 +219,10 @@
|
||||||
- **[Refactor] Balancing System:** `BattleConfig`에서 공격/방어 효율 상수를 분리하고 `CombatCalculator` 및 관련 로직에 적용하여 밸런싱의 유연성 확보.
|
- **[Refactor] Balancing System:** `BattleConfig`에서 공격/방어 효율 상수를 분리하고 `CombatCalculator` 및 관련 로직에 적용하여 밸런싱의 유연성 확보.
|
||||||
- **[Fix] UI Stability:** `CharacterStatusCard`의 Intent UI 텍스트 길이에 따른 레이아웃 흔들림 방지 (`FittedBox`). `BattleScreen` 내 `Stack` 위젯 구성 문법 오류 수정.
|
- **[Fix] UI Stability:** `CharacterStatusCard`의 Intent UI 텍스트 길이에 따른 레이아웃 흔들림 방지 (`FittedBox`). `BattleScreen` 내 `Stack` 위젯 구성 문법 오류 수정.
|
||||||
- **[Refactor] Barrel Pattern Adoption:** 프로젝트 전체(`lib/` 하위)에 Barrel File 패턴을 적용하여 Import 구문을 통합하고 디렉토리 의존성을 명확하게 정리.
|
- **[Refactor] Barrel Pattern Adoption:** 프로젝트 전체(`lib/` 하위)에 Barrel File 패턴을 적용하여 Import 구문을 통합하고 디렉토리 의존성을 명확하게 정리.
|
||||||
|
- **[UI] Top-Aligned Toast:** SnackBar를 상단 토스트 알림으로 교체하여 하단 네비게이션 가림 현상 해결 및 애니메이션 버그 수정.
|
||||||
|
- **[Fix] Asset 404 Error:** 적 이미지 누락 문제 해결(Placeholder 적용) 및 `pubspec.yaml` 경로 업데이트.
|
||||||
|
- **[Refactor] ShopUI:** 상점 UI의 문법 및 로직 오류 수정.
|
||||||
|
- **[Feature] Enhanced Enemy Display:** 적 이미지 동적 로딩 및 크기 확대, 스테이지 헤더에 Boss/Tier 정보 상세 표시.
|
||||||
|
|
||||||
## 5. 다음 단계 (Next Steps)
|
## 5. 다음 단계 (Next Steps)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# 64. UI 개선 및 에셋 시스템 복구 (UI Enhancements & Asset System Fixes)
|
||||||
|
|
||||||
|
## 1. 개요 (Overview)
|
||||||
|
|
||||||
|
이 작업은 사용자 경험(UX)을 저해하는 UI 요소(하단 알림)를 개선하고, 프로젝트 구조 변경 중 발생한 에셋 로딩 오류(404)를 해결하며, 상점 UI의 문법적 오류를 수정하는 데 중점을 두었습니다. 또한 적 이미지 표시 방식을 개선하여 시각적 완성도를 높였습니다.
|
||||||
|
|
||||||
|
## 2. 주요 변경 사항 (Key Changes)
|
||||||
|
|
||||||
|
### A. Top-Aligned Toast Notification (`ToastUtils`)
|
||||||
|
|
||||||
|
- **문제:** 기존 `SnackBar`가 하단 네비게이션 바를 가려 사용자가 버튼을 조작하는 데 불편함이 있었음.
|
||||||
|
- **해결:** `Overlay`를 사용하여 화면 상단에 알림을 띄우는 `ToastUtils.showTopToast`를 구현.
|
||||||
|
- **개선:** `Curves.easeOutBack` 애니메이션 사용 시 `Opacity` 값이 1.0을 초과하여 발생하는 크래시 버그를 `clamp(0.0, 1.0)`으로 수정하여 해결.
|
||||||
|
- **적용:** `BattleScreen`, `ShopUI` 등 주요 화면의 모든 알림을 교체.
|
||||||
|
|
||||||
|
### B. Enemy Asset System & UI
|
||||||
|
|
||||||
|
- **이미지 로딩:** `CharacterStatusCard`에서 적 이미지를 `enemies.json`의 경로(`assets/images/enemies/`)를 통해 동적으로 로드하도록 변경.
|
||||||
|
- **크기 확대:** 적 이미지의 표시 크기를 200x200으로 확대하여 시각적 비중을 높임.
|
||||||
|
- **404 에러 수정:** `assets/images/enemies/` 폴더 내에 `slime.png` 등 다수의 이미지 파일이 누락되어 발생한 404 에러를, `park.png`를 플레이스홀더로 복사하여 해결. (추후 실제 리소스로 교체 필요)
|
||||||
|
- **Pubspec 업데이트:** `pubspec.yaml`에 `assets/images/enemies/` 경로를 명시적으로 선언.
|
||||||
|
|
||||||
|
### C. Stage System UI
|
||||||
|
|
||||||
|
- **Tier 표시:** `BattleHeader`에서 스테이지 진행에 따라 Tier 1(지하 불법 투기장), Tier 2(콜로세움), Tier 3(왕의 투기장) 텍스트가 표시되도록 로직 개선.
|
||||||
|
- **Boss 표시:** 각 Tier의 마지막 스테이지(12, 24, 36...)에서 "BOSS" 라벨이 표시되도록 구현.
|
||||||
|
|
||||||
|
### D. ShopUI Syntax Fixes
|
||||||
|
|
||||||
|
- **버그 수정:** `ShopUI.dart`에서 발생한 괄호 매칭 오류, 중괄호 누락, `BattleProvider` 전달 오류 등을 전면 수정하여 정상 동작하도록 복구.
|
||||||
|
|
||||||
|
## 3. 결과 (Result)
|
||||||
|
|
||||||
|
- 알림이 더 이상 중요 UI를 가리지 않음.
|
||||||
|
- 적 이미지가 정상적으로 로드되며(플레이스홀더 포함), 에러 로그가 제거됨.
|
||||||
|
- 상점 기능이 정상화됨.
|
||||||
63
pubspec.yaml
|
|
@ -1,38 +1,16 @@
|
||||||
name: game_test
|
name: game_test
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
# The following line prevents the package from being accidentally published to
|
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.9.0
|
sdk: ^3.9.0
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.8
|
cupertino_icons: ^1.0.8
|
||||||
provider: ^6.0.5
|
provider: ^6.0.5
|
||||||
shared_preferences: ^2.5.3
|
shared_preferences: ^2.5.3
|
||||||
|
|
@ -41,51 +19,12 @@ dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^5.0.0
|
flutter_lints: ^5.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
assets:
|
assets:
|
||||||
- assets/data/
|
- assets/data/
|
||||||
- assets/data/icon/
|
- assets/data/icon/
|
||||||
- assets/data/enemy/
|
- assets/images/enemies/
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/to/resolution-aware-images
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/to/asset-from-package
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/to/font-from-package
|
|
||||||
|
|
|
||||||