feat(phase7): 고정 4색 팔레트 시스템 적용
- ascii_colors.dart 생성 - 흰색(object): 캐릭터, 몬스터, 아이템 - 시안(positive): 힐, 버프, 레벨업, 획득 - 마젠타(negative): 데미지, 디버프, 사망, 손실 - 검정(background): 배경 - 테마 선택 기능 제거 - AsciiAnimationCard: colorTheme 파라미터 제거, 고정 색상 사용 - TaskProgressPanel: 테마 버튼 제거 - GamePlayScreen: 테마 관련 상태/메서드 제거 - 이펙트 색상 시스템 업데이트 - '*' (히트) → 마젠타 - '!' '+' (강조/버프) → 시안 - '~' (디버프) → 마젠타
This commit is contained in:
81
lib/src/core/constants/ascii_colors.dart
Normal file
81
lib/src/core/constants/ascii_colors.dart
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// ASCII 애니메이션 4색 팔레트 (Phase 7)
|
||||||
|
///
|
||||||
|
/// 시각적 명확성을 위해 4가지 색상만 사용한다.
|
||||||
|
/// - 흰색: 오브젝트 (캐릭터, 몬스터, 아이템)
|
||||||
|
/// - 시안: 포지티브 이펙트 (힐, 버프, 레벨업, 획득)
|
||||||
|
/// - 마젠타: 네거티브 이펙트 (데미지, 디버프, 사망, 손실)
|
||||||
|
/// - 검정: 배경
|
||||||
|
class AsciiColors {
|
||||||
|
AsciiColors._();
|
||||||
|
|
||||||
|
/// 오브젝트 색상 (캐릭터, 몬스터, 아이템)
|
||||||
|
static const Color object = Colors.white;
|
||||||
|
|
||||||
|
/// 포지티브 이펙트 색상 (힐, 버프, 레벨업, 획득)
|
||||||
|
static const Color positive = Colors.cyan;
|
||||||
|
|
||||||
|
/// 네거티브 이펙트 색상 (데미지, 디버프, 사망, 손실)
|
||||||
|
static const Color negative = Color(0xFFFF00FF); // 마젠타
|
||||||
|
|
||||||
|
/// 배경 색상
|
||||||
|
static const Color background = Colors.black;
|
||||||
|
|
||||||
|
/// 상황에 따른 색상 반환
|
||||||
|
static Color forContext(AsciiColorContext context) {
|
||||||
|
return switch (context) {
|
||||||
|
AsciiColorContext.idle => object,
|
||||||
|
AsciiColorContext.attack => object,
|
||||||
|
AsciiColorContext.critical => negative,
|
||||||
|
AsciiColorContext.heal => positive,
|
||||||
|
AsciiColorContext.buff => positive,
|
||||||
|
AsciiColorContext.debuff => negative,
|
||||||
|
AsciiColorContext.levelUp => positive,
|
||||||
|
AsciiColorContext.death => negative,
|
||||||
|
AsciiColorContext.itemGain => positive,
|
||||||
|
AsciiColorContext.itemLoss => negative,
|
||||||
|
AsciiColorContext.dodge => object,
|
||||||
|
AsciiColorContext.block => object,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ASCII 애니메이션 색상 컨텍스트
|
||||||
|
enum AsciiColorContext {
|
||||||
|
/// 대기 상태
|
||||||
|
idle,
|
||||||
|
|
||||||
|
/// 일반 공격
|
||||||
|
attack,
|
||||||
|
|
||||||
|
/// 크리티컬 히트
|
||||||
|
critical,
|
||||||
|
|
||||||
|
/// 회복
|
||||||
|
heal,
|
||||||
|
|
||||||
|
/// 버프 획득
|
||||||
|
buff,
|
||||||
|
|
||||||
|
/// 디버프 적용
|
||||||
|
debuff,
|
||||||
|
|
||||||
|
/// 레벨업
|
||||||
|
levelUp,
|
||||||
|
|
||||||
|
/// 사망
|
||||||
|
death,
|
||||||
|
|
||||||
|
/// 아이템 획득
|
||||||
|
itemGain,
|
||||||
|
|
||||||
|
/// 아이템 손실
|
||||||
|
itemLoss,
|
||||||
|
|
||||||
|
/// 회피 성공
|
||||||
|
dodge,
|
||||||
|
|
||||||
|
/// 방패 방어
|
||||||
|
block,
|
||||||
|
}
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:askiineverdie/l10n/app_localizations.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/animation/ascii_animation_type.dart';
|
||||||
import 'package:askiineverdie/src/core/l10n/game_data_l10n.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/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/core/util/pq_logic.dart' as pq_logic;
|
||||||
import 'package:askiineverdie/src/features/game/game_session_controller.dart';
|
import 'package:askiineverdie/src/features/game/game_session_controller.dart';
|
||||||
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
|
import 'package:askiineverdie/src/features/game/widgets/task_progress_panel.dart';
|
||||||
|
|
||||||
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
|
/// 게임 진행 화면 (Main.dfm 기반 3패널 레이아웃)
|
||||||
|
///
|
||||||
|
/// Phase 7: colorTheme 제거됨, 고정 4색 팔레트 사용
|
||||||
class GamePlayScreen extends StatefulWidget {
|
class GamePlayScreen extends StatefulWidget {
|
||||||
const GamePlayScreen({super.key, required this.controller});
|
const GamePlayScreen({super.key, required this.controller});
|
||||||
|
|
||||||
@@ -22,7 +22,6 @@ class GamePlayScreen extends StatefulWidget {
|
|||||||
|
|
||||||
class _GamePlayScreenState extends State<GamePlayScreen>
|
class _GamePlayScreenState extends State<GamePlayScreen>
|
||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
AsciiColorTheme _colorTheme = AsciiColorTheme.green;
|
|
||||||
AsciiAnimationType? _specialAnimation;
|
AsciiAnimationType? _specialAnimation;
|
||||||
|
|
||||||
// 이전 상태 추적 (레벨업/퀘스트/Act 완료 감지용)
|
// 이전 상태 추적 (레벨업/퀘스트/Act 완료 감지용)
|
||||||
@@ -30,28 +29,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
int _lastQuestCount = 0;
|
int _lastQuestCount = 0;
|
||||||
int _lastPlotStageCount = 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) {
|
void _checkSpecialEvents(GameState state) {
|
||||||
// 레벨업 감지
|
// 레벨업 감지
|
||||||
if (state.traits.level > _lastLevel && _lastLevel > 0) {
|
if (state.traits.level > _lastLevel && _lastLevel > 0) {
|
||||||
@@ -92,7 +69,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
super.initState();
|
super.initState();
|
||||||
widget.controller.addListener(_onControllerChanged);
|
widget.controller.addListener(_onControllerChanged);
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_loadColorTheme();
|
|
||||||
|
|
||||||
// 초기 상태 설정
|
// 초기 상태 설정
|
||||||
final state = widget.controller.state;
|
final state = widget.controller.state;
|
||||||
@@ -216,7 +192,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
// 상단: ASCII 애니메이션 + Task Progress
|
// 상단: ASCII 애니메이션 + Task Progress (Phase 7: 고정 4색 팔레트)
|
||||||
TaskProgressPanel(
|
TaskProgressPanel(
|
||||||
progress: state.progress,
|
progress: state.progress,
|
||||||
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
|
speedMultiplier: widget.controller.loop?.speedMultiplier ?? 1,
|
||||||
@@ -224,8 +200,6 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
widget.controller.loop?.cycleSpeed();
|
widget.controller.loop?.cycleSpeed();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
colorTheme: _colorTheme,
|
|
||||||
onThemeCycle: _cycleColorTheme,
|
|
||||||
isPaused: !widget.controller.isRunning,
|
isPaused: !widget.controller.isRunning,
|
||||||
onPauseToggle: () async {
|
onPauseToggle: () async {
|
||||||
await widget.controller.togglePause();
|
await widget.controller.togglePause();
|
||||||
|
|||||||
@@ -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/character_frames.dart';
|
||||||
import 'package:askiineverdie/src/core/animation/monster_size.dart';
|
import 'package:askiineverdie/src/core/animation/monster_size.dart';
|
||||||
import 'package:askiineverdie/src/core/animation/weapon_category.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';
|
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||||
|
|
||||||
/// ASCII 애니메이션 카드 위젯
|
/// ASCII 애니메이션 카드 위젯
|
||||||
@@ -336,31 +337,26 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
return TextSpan(children: spans);
|
return TextSpan(children: spans);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 이펙트 문자별 색상 반환
|
/// 이펙트 문자별 색상 반환 (Phase 7: 4색 팔레트)
|
||||||
Color _getEffectColor(String char) {
|
Color _getEffectColor(String char) {
|
||||||
return switch (char) {
|
return switch (char) {
|
||||||
'*' => Colors.orange, // 히트/폭발
|
'*' => AsciiColors.negative, // 히트/폭발 (마젠타)
|
||||||
'!' => Colors.yellow, // 강조
|
'!' => AsciiColors.positive, // 강조 (시안)
|
||||||
'=' || '>' || '<' => Colors.cyan, // 슬래시/찌르기
|
'=' || '>' || '<' => AsciiColors.positive, // 슬래시/찌르기 (시안)
|
||||||
'~' => Colors.purple, // 물결/마법
|
'~' => AsciiColors.negative, // 물결/디버프 (마젠타)
|
||||||
_ => Colors.white,
|
'+' => AsciiColors.positive, // 회복/버프 (시안)
|
||||||
|
_ => AsciiColors.object, // 오브젝트 (흰색)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final brightness = Theme.of(context).brightness;
|
// Phase 7: 고정 4색 팔레트 사용 (colorTheme 무시)
|
||||||
final colors = getThemeColors(widget.colorTheme, brightness);
|
const bgColor = AsciiColors.background;
|
||||||
|
const textColor = AsciiColors.object;
|
||||||
// 특수 애니메이션 중이면 특별한 배경색 적용
|
|
||||||
final isSpecial = _currentSpecialAnimation != null;
|
|
||||||
final bgColor = isSpecial
|
|
||||||
? colors.backgroundColor.withValues(alpha: 0.95)
|
|
||||||
: colors.backgroundColor;
|
|
||||||
|
|
||||||
// 프레임 텍스트 결정
|
// 프레임 텍스트 결정
|
||||||
String frameText;
|
String frameText;
|
||||||
Color textColor = colors.textColor;
|
|
||||||
|
|
||||||
if (_isBattleMode && _battleComposer != null) {
|
if (_isBattleMode && _battleComposer != null) {
|
||||||
// 새 배틀 시스템 사용 (배경 포함)
|
// 새 배틀 시스템 사용 (배경 포함)
|
||||||
@@ -380,13 +376,16 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
frameText = _animationData.frames[frameIndex];
|
frameText = _animationData.frames[frameIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 특수 애니메이션 중이면 테두리 표시
|
||||||
|
final isSpecial = _currentSpecialAnimation != null;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bgColor,
|
color: bgColor,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: isSpecial
|
border: isSpecial
|
||||||
? Border.all(color: colors.textColor.withValues(alpha: 0.5))
|
? Border.all(color: AsciiColors.positive.withValues(alpha: 0.5))
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: _isBattleMode
|
child: _isBattleMode
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:askiineverdie/l10n/app_localizations.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/animation/ascii_animation_type.dart';
|
||||||
import 'package:askiineverdie/src/core/model/game_state.dart';
|
import 'package:askiineverdie/src/core/model/game_state.dart';
|
||||||
import 'package:askiineverdie/src/features/game/widgets/ascii_animation_card.dart';
|
import 'package:askiineverdie/src/features/game/widgets/ascii_animation_card.dart';
|
||||||
|
|
||||||
/// 상단 패널: ASCII 애니메이션 + Task Progress 바
|
/// 상단 패널: ASCII 애니메이션 + Task Progress 바
|
||||||
|
///
|
||||||
|
/// Phase 7: colorTheme 제거됨, 고정 4색 팔레트 사용
|
||||||
class TaskProgressPanel extends StatelessWidget {
|
class TaskProgressPanel extends StatelessWidget {
|
||||||
const TaskProgressPanel({
|
const TaskProgressPanel({
|
||||||
super.key,
|
super.key,
|
||||||
required this.progress,
|
required this.progress,
|
||||||
required this.speedMultiplier,
|
required this.speedMultiplier,
|
||||||
required this.onSpeedCycle,
|
required this.onSpeedCycle,
|
||||||
required this.colorTheme,
|
|
||||||
required this.onThemeCycle,
|
|
||||||
required this.isPaused,
|
required this.isPaused,
|
||||||
required this.onPauseToggle,
|
required this.onPauseToggle,
|
||||||
this.specialAnimation,
|
this.specialAnimation,
|
||||||
@@ -27,8 +26,6 @@ class TaskProgressPanel extends StatelessWidget {
|
|||||||
final ProgressState progress;
|
final ProgressState progress;
|
||||||
final int speedMultiplier;
|
final int speedMultiplier;
|
||||||
final VoidCallback onSpeedCycle;
|
final VoidCallback onSpeedCycle;
|
||||||
final AsciiColorTheme colorTheme;
|
|
||||||
final VoidCallback onThemeCycle;
|
|
||||||
|
|
||||||
/// 일시 정지 상태
|
/// 일시 정지 상태
|
||||||
final bool isPaused;
|
final bool isPaused;
|
||||||
@@ -56,13 +53,12 @@ class TaskProgressPanel extends StatelessWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// ASCII 애니메이션 카드
|
// ASCII 애니메이션 카드 (Phase 7: 고정 4색 팔레트)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 120,
|
height: 120,
|
||||||
child: AsciiAnimationCard(
|
child: AsciiAnimationCard(
|
||||||
taskType: progress.currentTask.type,
|
taskType: progress.currentTask.type,
|
||||||
monsterBaseName: progress.currentTask.monsterBaseName,
|
monsterBaseName: progress.currentTask.monsterBaseName,
|
||||||
colorTheme: colorTheme,
|
|
||||||
specialAnimation: specialAnimation,
|
specialAnimation: specialAnimation,
|
||||||
weaponName: weaponName,
|
weaponName: weaponName,
|
||||||
shieldName: shieldName,
|
shieldName: shieldName,
|
||||||
@@ -73,11 +69,9 @@ class TaskProgressPanel extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
// 상태 메시지 + 버튼들
|
// 상태 메시지 + 버튼들 (Phase 7: 테마 버튼 제거됨)
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_buildThemeButton(context),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
_buildPauseButton(context),
|
_buildPauseButton(context),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
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) {
|
Widget _buildPauseButton(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 28,
|
height: 28,
|
||||||
|
|||||||
Reference in New Issue
Block a user