feat(ui): 게임 위젯 개선
- AsciiAnimationCard 확장 - EnhancedAnimationPanel 개선 - HpMpBar UI 개선
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:asciineverdie/src/core/animation/ascii_animation_data.dart';
|
import 'package:asciineverdie/src/core/animation/ascii_animation_data.dart';
|
||||||
|
import 'package:asciineverdie/src/shared/widgets/ascii_disintegrate_widget.dart';
|
||||||
import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart';
|
import 'package:asciineverdie/src/core/animation/ascii_animation_type.dart';
|
||||||
import 'package:asciineverdie/src/core/animation/background_layer.dart';
|
import 'package:asciineverdie/src/core/animation/background_layer.dart';
|
||||||
import 'package:asciineverdie/src/core/animation/canvas/ascii_canvas_widget.dart';
|
import 'package:asciineverdie/src/core/animation/canvas/ascii_canvas_widget.dart';
|
||||||
@@ -47,6 +48,8 @@ class AsciiAnimationCard extends StatefulWidget {
|
|||||||
this.monsterLevel,
|
this.monsterLevel,
|
||||||
this.monsterGrade,
|
this.monsterGrade,
|
||||||
this.isPaused = false,
|
this.isPaused = false,
|
||||||
|
this.isInCombat = true,
|
||||||
|
this.monsterDied = false,
|
||||||
this.latestCombatEvent,
|
this.latestCombatEvent,
|
||||||
this.raceId,
|
this.raceId,
|
||||||
this.weaponRarity,
|
this.weaponRarity,
|
||||||
@@ -59,6 +62,12 @@ class AsciiAnimationCard extends StatefulWidget {
|
|||||||
/// 일시정지 상태 (true면 애니메이션 정지)
|
/// 일시정지 상태 (true면 애니메이션 정지)
|
||||||
final bool isPaused;
|
final bool isPaused;
|
||||||
|
|
||||||
|
/// 전투 활성 상태 (false면 kill 태스크여도 walking 애니메이션)
|
||||||
|
final bool isInCombat;
|
||||||
|
|
||||||
|
/// 몬스터 사망 여부 (true면 분해 애니메이션 재생)
|
||||||
|
final bool monsterDied;
|
||||||
|
|
||||||
/// 전투 중인 몬스터 기본 이름 (kill 타입일 때만 사용)
|
/// 전투 중인 몬스터 기본 이름 (kill 타입일 때만 사용)
|
||||||
final String? monsterBaseName;
|
final String? monsterBaseName;
|
||||||
final AsciiColorTheme colorTheme;
|
final AsciiColorTheme colorTheme;
|
||||||
@@ -162,6 +171,11 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
// 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
|
// 특수 애니메이션 프레임 수는 ascii_animation_type.dart의
|
||||||
// specialAnimationFrameCounts 상수 사용
|
// specialAnimationFrameCounts 상수 사용
|
||||||
|
|
||||||
|
// 몬스터 사망 분해 애니메이션 상태
|
||||||
|
bool _showDeathAnimation = false;
|
||||||
|
List<String>? _deathAnimationMonsterLines;
|
||||||
|
String? _lastMonsterBaseName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -204,12 +218,32 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 몬스터 사망 애니메이션 트리거
|
||||||
|
if (!oldWidget.monsterDied && widget.monsterDied && !_showDeathAnimation) {
|
||||||
|
// 현재 몬스터 프레임 캡처 (분해 애니메이션용)
|
||||||
|
_deathAnimationMonsterLines = _captureMonsterFrame();
|
||||||
|
if (_deathAnimationMonsterLines != null) {
|
||||||
|
setState(() {
|
||||||
|
_showDeathAnimation = true;
|
||||||
|
});
|
||||||
|
return; // 사망 애니메이션 중에는 다른 업데이트 무시
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 사망 애니메이션 중에는 다른 업데이트 무시
|
||||||
|
if (_showDeathAnimation) return;
|
||||||
|
|
||||||
// 전투 이벤트 동기화 (Phase 5)
|
// 전투 이벤트 동기화 (Phase 5)
|
||||||
if (widget.latestCombatEvent != null &&
|
if (widget.latestCombatEvent != null &&
|
||||||
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
widget.latestCombatEvent!.timestamp != _lastEventTimestamp) {
|
||||||
_handleCombatEvent(widget.latestCombatEvent!);
|
_handleCombatEvent(widget.latestCombatEvent!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 몬스터 이름 저장 (사망 시 프레임 캡처용)
|
||||||
|
if (widget.monsterBaseName != null) {
|
||||||
|
_lastMonsterBaseName = widget.monsterBaseName;
|
||||||
|
}
|
||||||
|
|
||||||
if (oldWidget.taskType != widget.taskType ||
|
if (oldWidget.taskType != widget.taskType ||
|
||||||
oldWidget.monsterBaseName != widget.monsterBaseName ||
|
oldWidget.monsterBaseName != widget.monsterBaseName ||
|
||||||
oldWidget.weaponName != widget.weaponName ||
|
oldWidget.weaponName != widget.weaponName ||
|
||||||
@@ -218,7 +252,8 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
oldWidget.raceId != widget.raceId ||
|
oldWidget.raceId != widget.raceId ||
|
||||||
oldWidget.weaponRarity != widget.weaponRarity ||
|
oldWidget.weaponRarity != widget.weaponRarity ||
|
||||||
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
oldWidget.opponentRaceId != widget.opponentRaceId ||
|
||||||
oldWidget.opponentHasShield != widget.opponentHasShield) {
|
oldWidget.opponentHasShield != widget.opponentHasShield ||
|
||||||
|
oldWidget.isInCombat != widget.isInCombat) {
|
||||||
_updateAnimation();
|
_updateAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,12 +540,18 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
|
|
||||||
switch (animationType) {
|
switch (animationType) {
|
||||||
case AsciiAnimationType.battle:
|
case AsciiAnimationType.battle:
|
||||||
_animationMode = AnimationMode.battle;
|
// 전투 비활성 상태면 walking 모드로 전환 (몬스터 처치 후 이동 중)
|
||||||
_setupBattleComposer();
|
if (!widget.isInCombat) {
|
||||||
_battlePhase = BattlePhase.idle;
|
_animationMode = AnimationMode.walking;
|
||||||
_battleSubFrame = 0;
|
_walkingComposer = CanvasWalkingComposer(raceId: widget.raceId);
|
||||||
_phaseIndex = 0;
|
} else {
|
||||||
_phaseFrameCount = 0;
|
_animationMode = AnimationMode.battle;
|
||||||
|
_setupBattleComposer();
|
||||||
|
_battlePhase = BattlePhase.idle;
|
||||||
|
_battleSubFrame = 0;
|
||||||
|
_phaseIndex = 0;
|
||||||
|
_phaseFrameCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
case AsciiAnimationType.town:
|
case AsciiAnimationType.town:
|
||||||
_animationMode = AnimationMode.town;
|
_animationMode = AnimationMode.town;
|
||||||
@@ -556,6 +597,31 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 현재 몬스터 프레임을 텍스트 라인으로 캡처 (분해 애니메이션용)
|
||||||
|
List<String>? _captureMonsterFrame() {
|
||||||
|
final monsterName = _lastMonsterBaseName ?? widget.monsterBaseName;
|
||||||
|
if (monsterName == null) return null;
|
||||||
|
|
||||||
|
final monsterCategory = getMonsterCategory(monsterName);
|
||||||
|
final monsterSize = getMonsterSize(widget.monsterLevel);
|
||||||
|
|
||||||
|
// 몬스터 Idle 프레임 가져오기
|
||||||
|
final frames = getMonsterIdleFrames(monsterCategory, monsterSize);
|
||||||
|
if (frames.isEmpty) return null;
|
||||||
|
|
||||||
|
return frames.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 사망 애니메이션 완료 콜백
|
||||||
|
void _onDeathAnimationComplete() {
|
||||||
|
setState(() {
|
||||||
|
_showDeathAnimation = false;
|
||||||
|
_deathAnimationMonsterLines = null;
|
||||||
|
});
|
||||||
|
// Walking 모드로 전환
|
||||||
|
_updateAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
void _advanceBattleFrame() {
|
void _advanceBattleFrame() {
|
||||||
_phaseFrameCount++;
|
_phaseFrameCount++;
|
||||||
final currentPhase = _battlePhaseSequence[_phaseIndex];
|
final currentPhase = _battlePhaseSequence[_phaseIndex];
|
||||||
@@ -617,6 +683,7 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
isDot: _showDotEffect,
|
isDot: _showDotEffect,
|
||||||
isBlock: _showBlockEffect,
|
isBlock: _showBlockEffect,
|
||||||
isParry: _showParryEffect,
|
isParry: _showParryEffect,
|
||||||
|
hideMonster: _showDeathAnimation,
|
||||||
) ??
|
) ??
|
||||||
[AsciiLayer.empty()],
|
[AsciiLayer.empty()],
|
||||||
AnimationMode.walking =>
|
AnimationMode.walking =>
|
||||||
@@ -676,7 +743,26 @@ class _AsciiAnimationCardState extends State<AsciiAnimationCard> {
|
|||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
border: borderEffect,
|
border: borderEffect,
|
||||||
),
|
),
|
||||||
child: AsciiCanvasWidget(layers: _composeLayers()),
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
// 기본 애니메이션
|
||||||
|
AsciiCanvasWidget(layers: _composeLayers()),
|
||||||
|
|
||||||
|
// 몬스터 사망 분해 애니메이션 오버레이
|
||||||
|
// 몬스터 위치: 캔버스 60열 중 30~48열 (중앙값 41열)
|
||||||
|
// Alignment x = (41/60) * 2 - 1 = 0.37
|
||||||
|
if (_showDeathAnimation && _deathAnimationMonsterLines != null)
|
||||||
|
Align(
|
||||||
|
alignment: const Alignment(0.37, 0.0),
|
||||||
|
child: AsciiDisintegrateWidget(
|
||||||
|
characterLines: _deathAnimationMonsterLines!,
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
textColor: widget.monsterGrade?.displayColor,
|
||||||
|
onComplete: _onDeathAnimationComplete,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,10 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
int _lastMp = 0;
|
int _lastMp = 0;
|
||||||
int _lastMonsterHp = 0;
|
int _lastMonsterHp = 0;
|
||||||
|
|
||||||
|
// 몬스터 사망 상태 추적
|
||||||
|
bool _monsterDied = false;
|
||||||
|
bool _wasInCombat = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -145,6 +149,27 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
} else if (newMonsterHp == null) {
|
} else if (newMonsterHp == null) {
|
||||||
_lastMonsterHp = 0;
|
_lastMonsterHp = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 몬스터 사망 감지: 전투 중 → 비전투 전환 시 몬스터 사망
|
||||||
|
final combat = widget.progress.currentCombat;
|
||||||
|
final isNowInCombat = combat != null && combat.isActive;
|
||||||
|
if (_wasInCombat && !isNowInCombat) {
|
||||||
|
// 전투가 끝났고, 태스크가 여전히 kill이면 몬스터 사망 (플레이어 승리)
|
||||||
|
if (widget.progress.currentTask.type == TaskType.kill) {
|
||||||
|
setState(() {
|
||||||
|
_monsterDied = true;
|
||||||
|
});
|
||||||
|
// 잠시 후 리셋 (애니메이션 완료 후)
|
||||||
|
Future.delayed(const Duration(milliseconds: 900), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_monsterDied = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_wasInCombat = isNowInCombat;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get _currentHp =>
|
int get _currentHp =>
|
||||||
@@ -197,6 +222,8 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
monsterLevel: widget.monsterLevel,
|
monsterLevel: widget.monsterLevel,
|
||||||
monsterGrade: widget.monsterGrade,
|
monsterGrade: widget.monsterGrade,
|
||||||
isPaused: widget.isPaused,
|
isPaused: widget.isPaused,
|
||||||
|
isInCombat: isInCombat,
|
||||||
|
monsterDied: _monsterDied,
|
||||||
latestCombatEvent: widget.latestCombatEvent,
|
latestCombatEvent: widget.latestCombatEvent,
|
||||||
raceId: widget.raceId,
|
raceId: widget.raceId,
|
||||||
weaponRarity: widget.weaponRarity,
|
weaponRarity: widget.weaponRarity,
|
||||||
@@ -480,10 +507,14 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 몬스터 HP 바 (전투 중)
|
/// 몬스터 HP 바 (전투 중)
|
||||||
|
/// - HP바 중앙에 HP% 오버레이
|
||||||
|
/// - 하단에 레벨.이름 표시
|
||||||
Widget _buildMonsterHpBar(CombatState combat) {
|
Widget _buildMonsterHpBar(CombatState combat) {
|
||||||
final max = _currentMonsterHpMax ?? 1;
|
final max = _currentMonsterHpMax ?? 1;
|
||||||
final current = _currentMonsterHp ?? 0;
|
final current = _currentMonsterHp ?? 0;
|
||||||
final ratio = max > 0 ? current / max : 0.0;
|
final ratio = max > 0 ? current / max : 0.0;
|
||||||
|
final monsterName = combat.monsterStats.name;
|
||||||
|
final monsterLevel = widget.monsterLevel ?? combat.monsterStats.level;
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _monsterFlashAnimation,
|
animation: _monsterFlashAnimation,
|
||||||
@@ -492,7 +523,7 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
height: 32,
|
height: 36,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.orange.withValues(alpha: 0.1),
|
color: Colors.orange.withValues(alpha: 0.1),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
@@ -501,27 +532,58 @@ class _EnhancedAnimationPanelState extends State<EnhancedAnimationPanel>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
// HP 바
|
// HP 바 (HP% 중앙 오버레이)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
child: ClipRRect(
|
child: Stack(
|
||||||
borderRadius: BorderRadius.circular(2),
|
alignment: Alignment.center,
|
||||||
child: LinearProgressIndicator(
|
children: [
|
||||||
value: ratio.clamp(0.0, 1.0),
|
// HP 바
|
||||||
backgroundColor: Colors.orange.withValues(alpha: 0.2),
|
ClipRRect(
|
||||||
valueColor: const AlwaysStoppedAnimation(Colors.orange),
|
borderRadius: BorderRadius.circular(2),
|
||||||
minHeight: 8,
|
child: LinearProgressIndicator(
|
||||||
),
|
value: ratio.clamp(0.0, 1.0),
|
||||||
|
backgroundColor: Colors.orange.withValues(
|
||||||
|
alpha: 0.2,
|
||||||
|
),
|
||||||
|
valueColor: const AlwaysStoppedAnimation(
|
||||||
|
Colors.orange,
|
||||||
|
),
|
||||||
|
minHeight: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// HP% 중앙 오버레이
|
||||||
|
Text(
|
||||||
|
'${(ratio * 100).toInt()}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 9,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
|
blurRadius: 2,
|
||||||
|
),
|
||||||
|
const Shadow(color: Colors.black, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 2),
|
const SizedBox(height: 2),
|
||||||
// 퍼센트
|
// 레벨.이름 표시
|
||||||
Text(
|
Padding(
|
||||||
'${(ratio * 100).toInt()}%',
|
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||||
style: const TextStyle(
|
child: Text(
|
||||||
fontSize: 9,
|
'Lv.$monsterLevel $monsterName',
|
||||||
color: Colors.orange,
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontSize: 9,
|
||||||
|
color: Colors.orange,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class HpMpBar extends StatefulWidget {
|
|||||||
this.monsterHpCurrent,
|
this.monsterHpCurrent,
|
||||||
this.monsterHpMax,
|
this.monsterHpMax,
|
||||||
this.monsterName,
|
this.monsterName,
|
||||||
|
this.monsterLevel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final int hpCurrent;
|
final int hpCurrent;
|
||||||
@@ -30,6 +31,7 @@ class HpMpBar extends StatefulWidget {
|
|||||||
final int? monsterHpCurrent;
|
final int? monsterHpCurrent;
|
||||||
final int? monsterHpMax;
|
final int? monsterHpMax;
|
||||||
final String? monsterName;
|
final String? monsterName;
|
||||||
|
final int? monsterLevel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HpMpBar> createState() => _HpMpBarState();
|
State<HpMpBar> createState() => _HpMpBarState();
|
||||||
@@ -368,11 +370,17 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// 몬스터 HP 바 (레트로 스타일)
|
/// 몬스터 HP 바 (레트로 스타일)
|
||||||
|
/// - HP바 중앙에 HP% 오버레이
|
||||||
|
/// - 하단에 레벨.이름 표시
|
||||||
Widget _buildMonsterBar() {
|
Widget _buildMonsterBar() {
|
||||||
final max = widget.monsterHpMax!;
|
final max = widget.monsterHpMax!;
|
||||||
final ratio = max > 0 ? widget.monsterHpCurrent! / max : 0.0;
|
final ratio = max > 0 ? widget.monsterHpCurrent! / max : 0.0;
|
||||||
const segmentCount = 10;
|
const segmentCount = 10;
|
||||||
final filledSegments = (ratio.clamp(0.0, 1.0) * segmentCount).round();
|
final filledSegments = (ratio.clamp(0.0, 1.0) * segmentCount).round();
|
||||||
|
final levelPrefix = widget.monsterLevel != null
|
||||||
|
? 'Lv.${widget.monsterLevel} '
|
||||||
|
: '';
|
||||||
|
final monsterName = widget.monsterName ?? '';
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _monsterFlashAnimation,
|
animation: _monsterFlashAnimation,
|
||||||
@@ -396,62 +404,78 @@ class _HpMpBarState extends State<HpMpBar> with TickerProviderStateMixin {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// 몬스터 아이콘
|
// HP 바 (HP% 중앙 오버레이)
|
||||||
const Icon(
|
Stack(
|
||||||
Icons.pest_control,
|
alignment: Alignment.center,
|
||||||
size: 12,
|
children: [
|
||||||
color: RetroColors.gold,
|
// 세그먼트 HP 바
|
||||||
),
|
Container(
|
||||||
const SizedBox(width: 6),
|
height: 12,
|
||||||
// 세그먼트 HP 바
|
decoration: BoxDecoration(
|
||||||
Expanded(
|
color: RetroColors.hpRedDark.withValues(alpha: 0.3),
|
||||||
child: Container(
|
border: Border.all(
|
||||||
height: 10,
|
color: RetroColors.panelBorderOuter,
|
||||||
decoration: BoxDecoration(
|
width: 1,
|
||||||
color: RetroColors.hpRedDark.withValues(alpha: 0.3),
|
),
|
||||||
border: Border.all(
|
|
||||||
color: RetroColors.panelBorderOuter,
|
|
||||||
width: 1,
|
|
||||||
),
|
),
|
||||||
),
|
child: Row(
|
||||||
child: Row(
|
children: List.generate(segmentCount, (index) {
|
||||||
children: List.generate(segmentCount, (index) {
|
final isFilled = index < filledSegments;
|
||||||
final isFilled = index < filledSegments;
|
return Expanded(
|
||||||
return Expanded(
|
child: Container(
|
||||||
child: Container(
|
decoration: BoxDecoration(
|
||||||
decoration: BoxDecoration(
|
color: isFilled
|
||||||
color: isFilled
|
? RetroColors.gold
|
||||||
? RetroColors.gold
|
: RetroColors.panelBorderOuter.withValues(
|
||||||
: RetroColors.panelBorderOuter.withValues(
|
alpha: 0.3,
|
||||||
alpha: 0.3,
|
),
|
||||||
),
|
border: Border(
|
||||||
border: Border(
|
right: index < segmentCount - 1
|
||||||
right: index < segmentCount - 1
|
? BorderSide(
|
||||||
? BorderSide(
|
color: RetroColors.panelBorderOuter
|
||||||
color: RetroColors.panelBorderOuter
|
.withValues(alpha: 0.3),
|
||||||
.withValues(alpha: 0.3),
|
width: 1,
|
||||||
width: 1,
|
)
|
||||||
)
|
: BorderSide.none,
|
||||||
: BorderSide.none,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}),
|
||||||
}),
|
),
|
||||||
),
|
),
|
||||||
),
|
// HP% 중앙 오버레이
|
||||||
|
Text(
|
||||||
|
'${(ratio * 100).toInt()}%',
|
||||||
|
style: TextStyle(
|
||||||
|
fontFamily: 'PressStart2P',
|
||||||
|
fontSize: 8,
|
||||||
|
color: RetroColors.textLight,
|
||||||
|
shadows: [
|
||||||
|
Shadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
|
blurRadius: 2,
|
||||||
|
),
|
||||||
|
const Shadow(color: Colors.black, blurRadius: 4),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(height: 4),
|
||||||
// HP 퍼센트
|
// 레벨.이름 표시
|
||||||
Text(
|
Text(
|
||||||
'${(ratio * 100).toInt()}%',
|
'$levelPrefix$monsterName',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontFamily: 'PressStart2P',
|
fontFamily: 'PressStart2P',
|
||||||
fontSize: 8,
|
fontSize: 7,
|
||||||
color: RetroColors.gold,
|
color: RetroColors.gold,
|
||||||
),
|
),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
maxLines: 1,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user