feat(ui): Phase 8 UI/UX 개선 완료
- CombatLog 위젯 게임 화면에 통합 - HP/MP 바 추가 (HP < 20% 깜빡임 효과) - SkillPanel 추가 (쿨타임 완료 시 glow 효과) - combatLog 로컬라이제이션 (4개 언어) - 테스트 수정 (skipOffstage 처리)
This commit is contained in:
163
lib/src/features/game/widgets/hp_mp_bar.dart
Normal file
163
lib/src/features/game/widgets/hp_mp_bar.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// HP/MP 바 위젯 (Phase 8: 사망 위험 시 깜빡임)
|
||||
///
|
||||
/// HP가 20% 미만일 때 빨간색 깜빡임 효과 표시
|
||||
class HpMpBar extends StatefulWidget {
|
||||
const HpMpBar({
|
||||
super.key,
|
||||
required this.hpCurrent,
|
||||
required this.hpMax,
|
||||
required this.mpCurrent,
|
||||
required this.mpMax,
|
||||
});
|
||||
|
||||
final int hpCurrent;
|
||||
final int hpMax;
|
||||
final int mpCurrent;
|
||||
final int mpMax;
|
||||
|
||||
@override
|
||||
State<HpMpBar> createState() => _HpMpBarState();
|
||||
}
|
||||
|
||||
class _HpMpBarState extends State<HpMpBar> with SingleTickerProviderStateMixin {
|
||||
late AnimationController _blinkController;
|
||||
late Animation<double> _blinkAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_blinkController = AnimationController(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_blinkAnimation = Tween<double>(begin: 1.0, end: 0.3).animate(
|
||||
CurvedAnimation(parent: _blinkController, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_updateBlinkState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(HpMpBar oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_updateBlinkState();
|
||||
}
|
||||
|
||||
void _updateBlinkState() {
|
||||
final hpRatio = widget.hpMax > 0 ? widget.hpCurrent / widget.hpMax : 1.0;
|
||||
|
||||
// HP < 20% 시 깜박임 시작
|
||||
if (hpRatio < 0.2 && hpRatio > 0) {
|
||||
if (!_blinkController.isAnimating) {
|
||||
_blinkController.repeat(reverse: true);
|
||||
}
|
||||
} else {
|
||||
_blinkController.stop();
|
||||
_blinkController.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_blinkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hpRatio = widget.hpMax > 0 ? widget.hpCurrent / widget.hpMax : 0.0;
|
||||
final mpRatio = widget.mpMax > 0 ? widget.mpCurrent / widget.mpMax : 0.0;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// HP 바
|
||||
_buildBar(
|
||||
label: 'HP',
|
||||
current: widget.hpCurrent,
|
||||
max: widget.hpMax,
|
||||
ratio: hpRatio,
|
||||
color: Colors.red,
|
||||
isLow: hpRatio < 0.2 && hpRatio > 0,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
// MP 바
|
||||
_buildBar(
|
||||
label: 'MP',
|
||||
current: widget.mpCurrent,
|
||||
max: widget.mpMax,
|
||||
ratio: mpRatio,
|
||||
color: Colors.blue,
|
||||
isLow: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBar({
|
||||
required String label,
|
||||
required int current,
|
||||
required int max,
|
||||
required double ratio,
|
||||
required Color color,
|
||||
required bool isLow,
|
||||
}) {
|
||||
final bar = Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 24,
|
||||
child: Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
child: LinearProgressIndicator(
|
||||
value: ratio.clamp(0.0, 1.0),
|
||||
backgroundColor: color.withValues(alpha: 0.2),
|
||||
valueColor: AlwaysStoppedAnimation<Color>(color),
|
||||
minHeight: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
child: Text(
|
||||
'$current/$max',
|
||||
style: const TextStyle(fontSize: 9),
|
||||
textAlign: TextAlign.right,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// HP < 20% 시 깜박임 효과 적용
|
||||
if (isLow) {
|
||||
return AnimatedBuilder(
|
||||
animation: _blinkAnimation,
|
||||
builder: (context, child) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withValues(alpha: (1 - _blinkAnimation.value) * 0.3),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: bar,
|
||||
);
|
||||
}
|
||||
|
||||
return bar;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user