Compare commits
3 Commits
8d477cdc61
...
e69f8921e6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e69f8921e6 | ||
|
|
5d58239313 | ||
|
|
595b0cc7d1 |
@@ -40,6 +40,7 @@ class _ParagraphCacheKey {
|
|||||||
/// 레이어 기반으로 ASCII 문자를 Canvas에 그린다.
|
/// 레이어 기반으로 ASCII 문자를 Canvas에 그린다.
|
||||||
/// 각 문자는 고정 크기 그리드 셀에 배치된다.
|
/// 각 문자는 고정 크기 그리드 셀에 배치된다.
|
||||||
/// Paragraph 캐싱으로 GC 압박 최소화.
|
/// Paragraph 캐싱으로 GC 압박 최소화.
|
||||||
|
/// 테마 인식 색상 지원 (Phase 5).
|
||||||
class AsciiCanvasPainter extends CustomPainter {
|
class AsciiCanvasPainter extends CustomPainter {
|
||||||
AsciiCanvasPainter({
|
AsciiCanvasPainter({
|
||||||
required this.layers,
|
required this.layers,
|
||||||
@@ -48,6 +49,9 @@ class AsciiCanvasPainter extends CustomPainter {
|
|||||||
this.backgroundColor = AsciiColors.background,
|
this.backgroundColor = AsciiColors.background,
|
||||||
this.backgroundOpacity = 0.5,
|
this.backgroundOpacity = 0.5,
|
||||||
this.layerVersion = 0,
|
this.layerVersion = 0,
|
||||||
|
this.objectColor = AsciiColors.object,
|
||||||
|
this.positiveColor = AsciiColors.positive,
|
||||||
|
this.negativeColor = AsciiColors.negative,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 렌더링할 레이어 목록 (z-order 정렬 필요)
|
/// 렌더링할 레이어 목록 (z-order 정렬 필요)
|
||||||
@@ -59,7 +63,7 @@ class AsciiCanvasPainter extends CustomPainter {
|
|||||||
/// 그리드 높이 (행 수)
|
/// 그리드 높이 (행 수)
|
||||||
final int gridHeight;
|
final int gridHeight;
|
||||||
|
|
||||||
/// 배경색
|
/// 배경색 (테마 인식 가능)
|
||||||
final Color backgroundColor;
|
final Color backgroundColor;
|
||||||
|
|
||||||
/// 배경 투명도 (0.0 ~ 1.0, 기본값 0.5 = 50%)
|
/// 배경 투명도 (0.0 ~ 1.0, 기본값 0.5 = 50%)
|
||||||
@@ -68,6 +72,15 @@ class AsciiCanvasPainter extends CustomPainter {
|
|||||||
/// 레이어 버전 (변경 감지용)
|
/// 레이어 버전 (변경 감지용)
|
||||||
final int layerVersion;
|
final int layerVersion;
|
||||||
|
|
||||||
|
/// 오브젝트 색상 (테마 인식)
|
||||||
|
final Color objectColor;
|
||||||
|
|
||||||
|
/// 포지티브 이펙트 색상 (테마 인식)
|
||||||
|
final Color positiveColor;
|
||||||
|
|
||||||
|
/// 네거티브 이펙트 색상 (테마 인식)
|
||||||
|
final Color negativeColor;
|
||||||
|
|
||||||
/// Paragraph 캐시 (문자+색상+크기 조합별)
|
/// Paragraph 캐시 (문자+색상+크기 조합별)
|
||||||
static final Map<_ParagraphCacheKey, ui.Paragraph> _paragraphCache = {};
|
static final Map<_ParagraphCacheKey, ui.Paragraph> _paragraphCache = {};
|
||||||
|
|
||||||
@@ -189,13 +202,13 @@ class AsciiCanvasPainter extends CustomPainter {
|
|||||||
canvas.drawParagraph(paragraph, Offset(offsetX, offsetY));
|
canvas.drawParagraph(paragraph, Offset(offsetX, offsetY));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// AsciiCellColor를 Flutter Color로 변환
|
/// AsciiCellColor를 Flutter Color로 변환 (테마 인식 색상 사용)
|
||||||
Color _getColor(AsciiCellColor cellColor) {
|
Color _getColor(AsciiCellColor cellColor) {
|
||||||
return switch (cellColor) {
|
return switch (cellColor) {
|
||||||
AsciiCellColor.background => AsciiColors.background,
|
AsciiCellColor.background => backgroundColor,
|
||||||
AsciiCellColor.object => AsciiColors.object,
|
AsciiCellColor.object => objectColor,
|
||||||
AsciiCellColor.positive => AsciiColors.positive,
|
AsciiCellColor.positive => positiveColor,
|
||||||
AsciiCellColor.negative => AsciiColors.negative,
|
AsciiCellColor.negative => negativeColor,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:askiineverdie/src/core/animation/canvas/ascii_canvas_painter.dart';
|
import 'package:askiineverdie/src/core/animation/canvas/ascii_canvas_painter.dart';
|
||||||
import 'package:askiineverdie/src/core/animation/canvas/ascii_layer.dart';
|
import 'package:askiineverdie/src/core/animation/canvas/ascii_layer.dart';
|
||||||
|
import 'package:askiineverdie/src/core/constants/ascii_colors.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// ASCII Canvas 위젯 (RepaintBoundary 포함)
|
/// ASCII Canvas 위젯 (RepaintBoundary 포함)
|
||||||
@@ -7,6 +8,7 @@ import 'package:flutter/material.dart';
|
|||||||
/// AsciiCanvasPainter를 감싸는 위젯.
|
/// AsciiCanvasPainter를 감싸는 위젯.
|
||||||
/// RepaintBoundary로 성능 최적화.
|
/// RepaintBoundary로 성능 최적화.
|
||||||
/// willChange를 애니메이션 상태에 따라 동적 설정.
|
/// willChange를 애니메이션 상태에 따라 동적 설정.
|
||||||
|
/// 테마 인식 색상 자동 적용 (Phase 5).
|
||||||
class AsciiCanvasWidget extends StatelessWidget {
|
class AsciiCanvasWidget extends StatelessWidget {
|
||||||
const AsciiCanvasWidget({
|
const AsciiCanvasWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -38,14 +40,24 @@ class AsciiCanvasWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// 테마 인식 색상 (다크/라이트 모드 자동 전환)
|
||||||
|
final bgColor = AsciiColors.backgroundOf(context);
|
||||||
|
final objColor = AsciiColors.objectOf(context);
|
||||||
|
final posColor = AsciiColors.positiveOf(context);
|
||||||
|
final negColor = AsciiColors.negativeOf(context);
|
||||||
|
|
||||||
return RepaintBoundary(
|
return RepaintBoundary(
|
||||||
child: CustomPaint(
|
child: CustomPaint(
|
||||||
painter: AsciiCanvasPainter(
|
painter: AsciiCanvasPainter(
|
||||||
layers: layers,
|
layers: layers,
|
||||||
gridWidth: gridWidth,
|
gridWidth: gridWidth,
|
||||||
gridHeight: gridHeight,
|
gridHeight: gridHeight,
|
||||||
|
backgroundColor: bgColor,
|
||||||
backgroundOpacity: backgroundOpacity,
|
backgroundOpacity: backgroundOpacity,
|
||||||
layerVersion: layerVersion,
|
layerVersion: layerVersion,
|
||||||
|
objectColor: objColor,
|
||||||
|
positiveColor: posColor,
|
||||||
|
negativeColor: negColor,
|
||||||
),
|
),
|
||||||
size: Size.infinite,
|
size: Size.infinite,
|
||||||
isComplex: true,
|
isComplex: true,
|
||||||
|
|||||||
@@ -1,15 +1,57 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:askiineverdie/src/shared/retro_colors.dart';
|
||||||
|
|
||||||
/// ASCII 애니메이션 4색 팔레트 (Phase 7)
|
/// ASCII 애니메이션 4색 팔레트 (Phase 7)
|
||||||
///
|
///
|
||||||
/// 시각적 명확성을 위해 4가지 색상만 사용한다.
|
/// 시각적 명확성을 위해 4가지 색상만 사용한다.
|
||||||
/// - 흰색: 오브젝트 (캐릭터, 몬스터, 아이템)
|
/// - 흰색/검정: 오브젝트 (캐릭터, 몬스터, 아이템) - 테마에 따라 변환
|
||||||
/// - 시안: 포지티브 이펙트 (힐, 버프, 레벨업, 획득)
|
/// - 시안/파랑: 포지티브 이펙트 (힐, 버프, 레벨업, 획득) - 테마에 따라 변환
|
||||||
/// - 마젠타: 네거티브 이펙트 (데미지, 디버프, 사망, 손실)
|
/// - 마젠타/빨강: 네거티브 이펙트 (데미지, 디버프, 사망, 손실) - 테마에 따라 변환
|
||||||
/// - 검정: 배경
|
/// - 검정/밝은색: 배경 - 테마에 따라 변환
|
||||||
class AsciiColors {
|
class AsciiColors {
|
||||||
AsciiColors._();
|
AsciiColors._();
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 동적 색상 Getter (테마에 따라 자동 전환)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// 오브젝트 색상 (캐릭터, 몬스터, 아이템) - 테마 인식
|
||||||
|
static Color objectOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? object : _lightObject;
|
||||||
|
|
||||||
|
/// 포지티브 이펙트 색상 - 테마 인식
|
||||||
|
static Color positiveOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? positive : _lightPositive;
|
||||||
|
|
||||||
|
/// 네거티브 이펙트 색상 - 테마 인식
|
||||||
|
static Color negativeOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? negative : _lightNegative;
|
||||||
|
|
||||||
|
/// 배경 색상 - 테마 인식
|
||||||
|
static Color backgroundOf(BuildContext context) =>
|
||||||
|
RetroColors.isDarkMode(context) ? background : _lightBackground;
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 라이트 모드 색상 (양피지/크림 기반)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
/// 라이트 모드 오브젝트 색상 (어두운 갈색)
|
||||||
|
static const Color _lightObject = Color(0xFF2D1B0E);
|
||||||
|
|
||||||
|
/// 라이트 모드 포지티브 이펙트 (진한 청록)
|
||||||
|
static const Color _lightPositive = Color(0xFF006666);
|
||||||
|
|
||||||
|
/// 라이트 모드 네거티브 이펙트 (진한 자주)
|
||||||
|
static const Color _lightNegative = Color(0xFFAA0066);
|
||||||
|
|
||||||
|
/// 라이트 모드 배경 (양피지 크림)
|
||||||
|
static const Color _lightBackground = Color(0xFFF5E6C8);
|
||||||
|
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
// 레거시 정적 색상 (다크 모드 기본값 / context 없는 곳에서 사용)
|
||||||
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
/// 오브젝트 색상 (캐릭터, 몬스터, 아이템)
|
/// 오브젝트 색상 (캐릭터, 몬스터, 아이템)
|
||||||
static const Color object = Colors.white;
|
static const Color object = Colors.white;
|
||||||
|
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
/// TaskType 기반 BGM 전환 (애니메이션과 동기화)
|
/// TaskType 기반 BGM 전환 (애니메이션과 동기화)
|
||||||
///
|
///
|
||||||
/// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용
|
/// 애니메이션은 TaskType으로 결정되므로, BGM도 동일한 기준 사용
|
||||||
|
/// 전환 감지 외에도 현재 BGM이 TaskType과 일치하는지 검증
|
||||||
void _updateBgmForTaskType(GameState state) {
|
void _updateBgmForTaskType(GameState state) {
|
||||||
final audio = widget.audioService;
|
final audio = widget.audioService;
|
||||||
if (audio == null) return;
|
if (audio == null) return;
|
||||||
@@ -262,19 +263,20 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
final taskType = state.progress.currentTask.type;
|
final taskType = state.progress.currentTask.type;
|
||||||
final isInBattleTask = taskType == TaskType.kill;
|
final isInBattleTask = taskType == TaskType.kill;
|
||||||
|
|
||||||
if (isInBattleTask && !_wasInBattleTask) {
|
// 전투 태스크 상태 결정
|
||||||
// 전투 태스크 시작: 보스 여부에 따라 BGM 선택
|
if (isInBattleTask) {
|
||||||
|
// 전투 태스크: 보스 여부에 따라 BGM 선택
|
||||||
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
final monsterLevel = state.progress.currentTask.monsterLevel ?? 0;
|
||||||
final playerLevel = state.traits.level;
|
final playerLevel = state.traits.level;
|
||||||
final isBoss = monsterLevel >= playerLevel + 5;
|
final isBoss = monsterLevel >= playerLevel + 5;
|
||||||
|
final expectedBgm = isBoss ? 'boss' : 'battle';
|
||||||
|
|
||||||
if (isBoss) {
|
// 전환 시점이거나 현재 BGM이 일치하지 않으면 재생
|
||||||
audio.playBgm('boss');
|
if (!_wasInBattleTask || audio.currentBgm != expectedBgm) {
|
||||||
} else {
|
audio.playBgm(expectedBgm);
|
||||||
audio.playBgm('battle');
|
|
||||||
}
|
}
|
||||||
} else if (!isInBattleTask && _wasInBattleTask) {
|
} else if (_wasInBattleTask || audio.currentBgm == 'battle' || audio.currentBgm == 'boss') {
|
||||||
// 전투 태스크 종료: 마을 BGM으로 복귀
|
// 전투 태스크 종료 또는 BGM 불일치: 마을 BGM으로 복귀
|
||||||
audio.playBgm('town');
|
audio.playBgm('town');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,6 +677,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => GamePlayScreen(
|
builder: (_) => GamePlayScreen(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
|
audioService: widget.audioService,
|
||||||
currentThemeMode: widget.currentThemeMode,
|
currentThemeMode: widget.currentThemeMode,
|
||||||
onThemeModeChange: widget.onThemeModeChange,
|
onThemeModeChange: widget.onThemeModeChange,
|
||||||
),
|
),
|
||||||
@@ -784,6 +787,7 @@ class _GamePlayScreenState extends State<GamePlayScreen>
|
|||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
builder: (_) => GamePlayScreen(
|
builder: (_) => GamePlayScreen(
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
|
audioService: widget.audioService,
|
||||||
currentThemeMode: widget.currentThemeMode,
|
currentThemeMode: widget.currentThemeMode,
|
||||||
onThemeModeChange: widget.onThemeModeChange,
|
onThemeModeChange: widget.onThemeModeChange,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -529,8 +529,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// 검정 배경 위에 배경 레이어(50% 투명)가 그려짐
|
// 테마 인식 배경색 (다크: 검정, 라이트: 양피지)
|
||||||
const bgColor = AsciiColors.background;
|
final bgColor = AsciiColors.backgroundOf(context);
|
||||||
|
final positiveColor = AsciiColors.positiveOf(context);
|
||||||
|
|
||||||
// 테두리 효과 결정 (전투 이벤트 또는 특수 애니메이션)
|
// 테두리 효과 결정 (전투 이벤트 또는 특수 애니메이션)
|
||||||
final isSpecial = _currentSpecialAnimation != null;
|
final isSpecial = _currentSpecialAnimation != null;
|
||||||
@@ -560,9 +561,9 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
width: 2,
|
width: 2,
|
||||||
);
|
);
|
||||||
} else if (isSpecial) {
|
} else if (isSpecial) {
|
||||||
// 특수 애니메이션: 시안 테두리
|
// 특수 애니메이션: 포지티브 색상 테두리
|
||||||
borderEffect = Border.all(
|
borderEffect = Border.all(
|
||||||
color: AsciiColors.positive.withValues(alpha: 0.5),
|
color: positiveColor.withValues(alpha: 0.5),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user