feat(phase7): 고정 4색 팔레트 시스템 적용

- ascii_colors.dart 생성
  - 흰색(object): 캐릭터, 몬스터, 아이템
  - 시안(positive): 힐, 버프, 레벨업, 획득
  - 마젠타(negative): 데미지, 디버프, 사망, 손실
  - 검정(background): 배경

- 테마 선택 기능 제거
  - AsciiAnimationCard: colorTheme 파라미터 제거, 고정 색상 사용
  - TaskProgressPanel: 테마 버튼 제거
  - GamePlayScreen: 테마 관련 상태/메서드 제거

- 이펙트 색상 시스템 업데이트
  - '*' (히트) → 마젠타
  - '!' '+' (강조/버프) → 시안
  - '~' (디버프) → 마젠타
This commit is contained in:
JiWoong Sul
2025-12-17 17:54:07 +09:00
parent 97d9875e00
commit a6ba3d5d2e
4 changed files with 103 additions and 90 deletions

View File

@@ -1,16 +1,16 @@
import 'package:flutter/material.dart';
import 'package:askiineverdie/l10n/app_localizations.dart';
import 'package:askiineverdie/src/core/animation/ascii_animation_data.dart';
import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart';
import 'package:askiineverdie/src/core/l10n/game_data_l10n.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/core/storage/theme_preferences.dart';
import 'package:askiineverdie/src/core/util/pq_logic.dart' as pq_logic;
import 'package:askiineverdie/src/features/game/game_session_controller.dart';
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
///
/// Phase 7: colorTheme 제거됨, 고정 4색 팔레트 사용
class GamePlayScreen extends StatefulWidget {
const GamePlayScreen({super.key, required this.controller});
@@ -22,7 +22,6 @@ class GamePlayScreen extends StatefulWidget {
class _GamePlayScreenState extends State<GamePlayScreen>
with WidgetsBindingObserver {
AsciiColorTheme _colorTheme = AsciiColorTheme.green;
AsciiAnimationType? _specialAnimation;
// 이전 상태 추적 (레벨업/퀘스트/Act 완료 감지용)
@@ -30,28 +29,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
int _lastQuestCount = 0;
int _lastPlotStageCount = 0;
void _cycleColorTheme() {
setState(() {
_colorTheme = switch (_colorTheme) {
AsciiColorTheme.green => AsciiColorTheme.amber,
AsciiColorTheme.amber => AsciiColorTheme.white,
AsciiColorTheme.white => AsciiColorTheme.system,
AsciiColorTheme.system => AsciiColorTheme.green,
};
});
// 테마 변경 시 저장
ThemePreferences.saveColorTheme(_colorTheme);
}
Future<void> _loadColorTheme() async {
final theme = await ThemePreferences.loadColorTheme();
if (mounted) {
setState(() {
_colorTheme = theme;
});
}
}
void _checkSpecialEvents(GameState state) {
// 레벨업 감지
if (state.traits.level > _lastLevel && _lastLevel > 0) {
@@ -92,7 +69,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
super.initState();
widget.controller.addListener(_onControllerChanged);
WidgetsBinding.instance.addObserver(this);
_loadColorTheme();
// 초기 상태 설정
final state = widget.controller.state;
@@ -216,7 +192,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
),
body: Column(
children: [
// 상단: ASCII 애니메이션 + Task Progress
// 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트)
TaskProgressPanel(
progress: state.progress,
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
@@ -224,8 +200,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
widget.controller.loop?.cycleSpeed();
setState(() {});
},
colorTheme: _colorTheme,
onThemeCycle: _cycleColorTheme,
isPaused: !widget.controller.isRunning,
onPauseToggle: () async {
await widget.controller.togglePause();

View File

@@ -9,6 +9,7 @@ import 'package:askiineverdie/src/core/animation/battle_composer.dart';
import 'package:askiineverdie/src/core/animation/character_frames.dart';
import 'package:askiineverdie/src/core/animation/monster_size.dart';
import 'package:askiineverdie/src/core/animation/weapon_category.dart';
import 'package:askiineverdie/src/core/constants/ascii_colors.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
/// ASCII 애니메이션 카드 위젯
@@ -336,31 +337,26 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
return TextSpan(children: spans);
}
/// 이펙트 문자별 색상 반환
/// 이펙트 문자별 색상 반환 (Phase 7: 4색 팔레트)
Color _getEffectColor(String char) {
return switch (char) {
'*' => Colors.orange, // 히트/폭발
'!' => Colors.yellow, // 강조
'=' || '>' || '<' => Colors.cyan, // 슬래시/찌르기
'~' => Colors.purple, // 물결/마법
_ => Colors.white,
'*' => AsciiColors.negative, // 히트/폭발 (마젠타)
'!' => AsciiColors.positive, // 강조 (시안)
'=' || '>' || '<' => AsciiColors.positive, // 슬래시/찌르기 (시안)
'~' => AsciiColors.negative, // 물결/디버프 (마젠타)
'+' => AsciiColors.positive, // 회복/버프 (시안)
_ => AsciiColors.object, // 오브젝트 (흰색)
};
}
@override
Widget build(BuildContext context) {
final brightness = Theme.of(context).brightness;
final colors = getThemeColors(widget.colorTheme, brightness);
// 특수 애니메이션 중이면 특별한 배경색 적용
final isSpecial = _currentSpecialAnimation != null;
final bgColor = isSpecial
? colors.backgroundColor.withValues(alpha: 0.95)
: colors.backgroundColor;
// Phase 7: 고정 4색 팔레트 사용 (colorTheme 무시)
const bgColor = AsciiColors.background;
const textColor = AsciiColors.object;
// 프레임 텍스트 결정
String frameText;
Color textColor = colors.textColor;
if (_isBattleMode && _battleComposer != null) {
// 새 배틀 시스템 사용 (배경 포함)
@@ -380,13 +376,16 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
frameText = _animationData.frames[frameIndex];
}
// 특수 애니메이션 중이면 테두리 표시
final isSpecial = _currentSpecialAnimation != null;
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(4),
border: isSpecial
? Border.all(color: colors.textColor.withValues(alpha: 0.5))
? Border.all(color: AsciiColors.positive.withValues(alpha: 0.5))
: null,
),
child: _isBattleMode

View File

@@ -1,20 +1,19 @@
import 'package:flutter/material.dart';
import 'package:askiineverdie/l10n/app_localizations.dart';
import 'package:askiineverdie/src/core/animation/ascii_animation_data.dart';
import 'package:askiineverdie/src/core/animation/ascii_animation_type.dart';
import 'package:askiineverdie/src/core/model/game_state.dart';
import 'package:askiineverdie/src/features/game/widgets/ascii_animation_card.dart';
/// 상단 패널: ASCII 애니메이션 + Task Progress 바
///
/// Phase 7: colorTheme 제거됨, 고정 4색 팔레트 사용
class TaskProgressPanel extends StatelessWidget {
const TaskProgressPanel({
super.key,
required this.progress,
required this.speedMultiplier,
required this.onSpeedCycle,
required this.colorTheme,
required this.onThemeCycle,
required this.isPaused,
required this.onPauseToggle,
this.specialAnimation,
@@ -27,8 +26,6 @@ class TaskProgressPanel extends StatelessWidget {
final ProgressState progress;
final int speedMultiplier;
final VoidCallback onSpeedCycle;
final AsciiColorTheme colorTheme;
final VoidCallback onThemeCycle;
/// 일시 정지 상태
final bool isPaused;
@@ -56,13 +53,12 @@ class TaskProgressPanel extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// ASCII 애니메이션 카드
// ASCII 애니메이션 카드 (Phase 7: 고정 4색 팔레트)
SizedBox(
height: 120,
child: AsciiAnimationCard(
taskType: progress.currentTask.type,
monsterBaseName: progress.currentTask.monsterBaseName,
colorTheme: colorTheme,
specialAnimation: specialAnimation,
weaponName: weaponName,
shieldName: shieldName,
@@ -73,11 +69,9 @@ class TaskProgressPanel extends StatelessWidget {
),
const SizedBox(height: 8),
// 상태 메시지 + 버튼들
// 상태 메시지 + 버튼들 (Phase 7: 테마 버튼 제거됨)
Row(
children: [
_buildThemeButton(context),
const SizedBox(width: 4),
_buildPauseButton(context),
const SizedBox(width: 8),
Expanded(
@@ -102,41 +96,6 @@ class TaskProgressPanel extends StatelessWidget {
);
}
Widget _buildThemeButton(BuildContext context) {
final themeLabel = switch (colorTheme) {
AsciiColorTheme.green => 'G',
AsciiColorTheme.amber => 'A',
AsciiColorTheme.white => 'W',
AsciiColorTheme.system => 'S',
};
final themeColor = switch (colorTheme) {
AsciiColorTheme.green => const Color(0xFF00FF00),
AsciiColorTheme.amber => const Color(0xFFFFB000),
AsciiColorTheme.white => Colors.white,
AsciiColorTheme.system => Theme.of(context).colorScheme.primary,
};
return SizedBox(
height: 28,
child: OutlinedButton(
onPressed: onThemeCycle,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8),
visualDensity: VisualDensity.compact,
side: BorderSide(color: themeColor.withValues(alpha: 0.5)),
),
child: Text(
themeLabel,
style: TextStyle(
fontWeight: FontWeight.bold,
color: themeColor,
),
),
),
);
}
Widget _buildPauseButton(BuildContext context) {
return SizedBox(
height: 28,