Flutter for OpenHarmony三国杀攻略App实战 - 伤害计算器实现
·

前言
在三国杀游戏中,伤害计算是一个复杂的过程,需要考虑武器加成、技能效果、防具减伤等多种因素。一个精确的伤害计算器能够帮助玩家做出更好的决策。本文将实现一个功能完整的伤害计算器,支持多种伤害因素的组合计算。
伤害计算系统设计
伤害计算涉及多个维度的因素:基础伤害、武器加成、技能加成、防具减伤、特殊效果等。这些因素需要按照特定的规则进行组合计算。
// lib/models/damage_model.dart
class DamageCalculationModel {
final int baseDamage;
final int weaponBonus;
final int skillBonus;
final bool hasArmor;
final bool hasRattan;
final bool hasEightDiagrams;
final bool isFireDamage;
final bool isThunderDamage;
final bool hasWineEffect;
final DateTime timestamp;
const DamageCalculationModel({
required this.baseDamage,
required this.weaponBonus,
required this.skillBonus,
required this.hasArmor,
required this.hasRattan,
required this.hasEightDiagrams,
required this.isFireDamage,
required this.isThunderDamage,
required this.hasWineEffect,
required this.timestamp,
});
int calculateFinalDamage() {
int damage = baseDamage + weaponBonus + skillBonus;
// 酒效果加成
if (hasWineEffect) {
damage += 1;
}
// 防具减伤
if (hasArmor && damage > 0) {
damage -= 1;
}
// 藤甲特殊处理
if (hasRattan) {
if (isFireDamage) {
damage += 1;
} else {
damage = 0;
}
}
return damage > 0 ? damage : 0;
}
Map<String, dynamic> toJson() {
return {
'baseDamage': baseDamage,
'weaponBonus': weaponBonus,
'skillBonus': skillBonus,
'hasArmor': hasArmor,
'hasRattan': hasRattan,
'hasEightDiagrams': hasEightDiagrams,
'isFireDamage': isFireDamage,
'isThunderDamage': isThunderDamage,
'hasWineEffect': hasWineEffect,
'timestamp': timestamp.toIso8601String(),
};
}
}
这个模型包含了伤害计算的所有参数。calculateFinalDamage() 方法实现了伤害计算的核心逻辑,考虑了各种防具的特殊效果。这种结构化的设计让计算逻辑更加清晰和易于维护。
伤害计算引擎
// lib/services/damage_calculator_service.dart
class DamageCalculatorService {
static const String _cacheKey = 'damage_calculations';
static int calculateDamage(DamageCalculationModel model) {
return model.calculateFinalDamage();
}
static String getDamageDescription(int damage) {
if (damage == 0) return '无伤害';
if (damage == 1) return '标准伤害';
if (damage <= 3) return '中等伤害';
if (damage <= 5) return '高额伤害';
return '致命伤害';
}
static List<String> getCalculationSteps(DamageCalculationModel model) {
List<String> steps = [];
steps.add('基础伤害:${model.baseDamage}');
if (model.weaponBonus > 0) {
steps.add('武器加成:+${model.weaponBonus}');
}
if (model.skillBonus > 0) {
steps.add('技能加成:+${model.skillBonus}');
}
if (model.hasWineEffect) {
steps.add('酒效果:+1');
}
if (model.hasArmor) {
steps.add('防具减伤:-1');
}
if (model.hasRattan && model.isFireDamage) {
steps.add('藤甲火焰:+1');
}
steps.add('最终伤害:${model.calculateFinalDamage()}');
return steps;
}
static Future<void> saveCalculation(DamageCalculationModel model) async {
final prefs = await SharedPreferences.getInstance();
final calculations = prefs.getStringList(_cacheKey) ?? [];
calculations.insert(0, json.encode(model.toJson()));
if (calculations.length > 20) {
calculations.removeLast();
}
await prefs.setStringList(_cacheKey, calculations);
}
static Future<List<DamageCalculationModel>> getCalculationHistory() async {
final prefs = await SharedPreferences.getInstance();
final calculations = prefs.getStringList(_cacheKey) ?? [];
return calculations.map((calc) {
final json = jsonDecode(calc);
return DamageCalculationModel(
baseDamage: json['baseDamage'],
weaponBonus: json['weaponBonus'],
skillBonus: json['skillBonus'],
hasArmor: json['hasArmor'],
hasRattan: json['hasRattan'],
hasEightDiagrams: json['hasEightDiagrams'],
isFireDamage: json['isFireDamage'],
isThunderDamage: json['isThunderDamage'],
hasWineEffect: json['hasWineEffect'],
timestamp: DateTime.parse(json['timestamp']),
);
}).toList();
}
}
伤害计算服务提供了完整的计算和存储功能。calculateDamage() 方法是核心计算方法,getCalculationSteps() 方法返回详细的计算步骤,便于用户理解计算过程。saveCalculation() 和 getCalculationHistory() 方法实现了历史记录的保存和读取。
伤害计算器屏幕实现
// lib/screens/tools/damage_calculator_screen.dart
class DamageCalculatorScreen extends StatefulWidget {
const DamageCalculatorScreen({Key? key}) : super(key: key);
State<DamageCalculatorScreen> createState() => _DamageCalculatorScreenState();
}
class _DamageCalculatorScreenState extends State<DamageCalculatorScreen>
with TickerProviderStateMixin {
int baseDamage = 1;
int weaponBonus = 0;
int skillBonus = 0;
bool hasArmor = false;
bool hasRattan = false;
bool hasEightDiagrams = false;
bool isFireDamage = false;
bool isThunderDamage = false;
bool hasWineEffect = false;
late AnimationController _resultAnimationController;
late Animation<double> _resultScaleAnimation;
List<DamageCalculationModel> history = [];
void initState() {
super.initState();
_initAnimations();
_loadHistory();
}
void _initAnimations() {
_resultAnimationController = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_resultScaleAnimation = Tween<double>(begin: 1.0, end: 1.2).animate(
CurvedAnimation(parent: _resultAnimationController, curve: Curves.elasticOut),
);
}
int get finalDamage {
return DamageCalculatorService.calculateDamage(
DamageCalculationModel(
baseDamage: baseDamage,
weaponBonus: weaponBonus,
skillBonus: skillBonus,
hasArmor: hasArmor,
hasRattan: hasRattan,
hasEightDiagrams: hasEightDiagrams,
isFireDamage: isFireDamage,
isThunderDamage: isThunderDamage,
hasWineEffect: hasWineEffect,
timestamp: DateTime.now(),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('伤害计算器'),
backgroundColor: Colors.red[700],
foregroundColor: Colors.white,
actions: [
IconButton(
icon: const Icon(Icons.history),
onPressed: _showHistory,
),
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _resetCalculator,
),
],
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
_buildResultCard(),
SizedBox(height: 20.h),
_buildBasicDamageSection(),
SizedBox(height: 16.h),
_buildWeaponSection(),
SizedBox(height: 16.h),
_buildSkillSection(),
SizedBox(height: 16.h),
_buildDefenseSection(),
SizedBox(height: 16.h),
_buildCalculationDetails(),
],
),
),
),
_buildActionButtons(),
],
),
);
}
Widget _buildResultCard() {
return AnimatedBuilder(
animation: _resultScaleAnimation,
builder: (context, child) {
return Transform.scale(
scale: _resultScaleAnimation.value,
child: Container(
width: double.infinity,
padding: EdgeInsets.all(24.w),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.red[700]!, Colors.red[500]!],
),
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: Colors.red.withOpacity(0.3),
blurRadius: 20,
offset: const Offset(0, 10),
),
],
),
child: Column(
children: [
Text(
'最终伤害',
style: TextStyle(
fontSize: 18.sp,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8.h),
Text(
'$finalDamage',
style: TextStyle(
fontSize: 64.sp,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.h),
Text(
DamageCalculatorService.getDamageDescription(finalDamage),
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
);
},
);
}
Widget _buildBasicDamageSection() {
return _buildSection(
title: '基础伤害',
icon: Icons.local_fire_department,
color: Colors.red,
child: Column(
children: [
_buildSlider(
label: '基础伤害值',
value: baseDamage,
min: 1,
max: 5,
onChanged: (value) {
setState(() => baseDamage = value.toInt());
_animateResult();
},
),
SizedBox(height: 8.h),
Text(
'通常情况下,【杀】造成1点伤害',
style: TextStyle(fontSize: 12.sp, color: Colors.grey.shade600),
),
],
),
);
}
Widget _buildWeaponSection() {
return _buildSection(
title: '武器加成',
icon: Icons.sports_martial_arts,
color: Colors.orange,
child: Column(
children: [
_buildSlider(
label: '武器加成',
value: weaponBonus,
min: 0,
max: 3,
onChanged: (value) {
setState(() => weaponBonus = value.toInt());
_animateResult();
},
),
SizedBox(height: 12.h),
Wrap(
spacing: 8.w,
runSpacing: 8.h,
children: [
_buildWeaponButton('无武器', 0),
_buildWeaponButton('青釭剑', 1),
_buildWeaponButton('丈八蛇矛', 1),
_buildWeaponButton('方天画戟', 2),
],
),
],
),
);
}
Widget _buildWeaponButton(String name, int bonus) {
final isSelected = weaponBonus == bonus;
return GestureDetector(
onTap: () {
setState(() => weaponBonus = bonus);
_animateResult();
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: isSelected ? Colors.orange : Colors.grey.shade100,
borderRadius: BorderRadius.circular(16.r),
border: Border.all(
color: isSelected ? Colors.orange : Colors.grey.shade300,
),
),
child: Text(
name,
style: TextStyle(
fontSize: 12.sp,
color: isSelected ? Colors.white : Colors.grey.shade700,
fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
),
),
),
);
}
Widget _buildSkillSection() {
return _buildSection(
title: '技能加成',
icon: Icons.auto_awesome,
color: Colors.purple,
child: Column(
children: [
_buildSlider(
label: '技能加成',
value: skillBonus,
min: 0,
max: 3,
onChanged: (value) {
setState(() => skillBonus = value.toInt());
_animateResult();
},
),
SizedBox(height: 12.h),
Row(
children: [
Expanded(
child: _buildCheckbox('酒效果', hasWineEffect, (value) {
setState(() => hasWineEffect = value);
_animateResult();
}),
),
Expanded(
child: _buildCheckbox('火焰伤害', isFireDamage, (value) {
setState(() => isFireDamage = value);
_animateResult();
}),
),
],
),
SizedBox(height: 8.h),
_buildCheckbox('雷电伤害', isThunderDamage, (value) {
setState(() => isThunderDamage = value);
_animateResult();
}),
],
),
);
}
Widget _buildDefenseSection() {
return _buildSection(
title: '防御装备',
icon: Icons.shield,
color: Colors.blue,
child: Column(
children: [
_buildCheckbox('普通防具 (-1伤害)', hasArmor, (value) {
setState(() {
hasArmor = value;
if (value) hasRattan = false;
});
_animateResult();
}),
_buildCheckbox('藤甲 (火焰+1,其他无效)', hasRattan, (value) {
setState(() {
hasRattan = value;
if (value) hasArmor = false;
});
_animateResult();
}),
_buildCheckbox('八卦阵 (50%概率抵消非雷电)', hasEightDiagrams, (value) {
setState(() => hasEightDiagrams = value);
_animateResult();
}),
],
),
);
}
Widget _buildCalculationDetails() {
final steps = DamageCalculatorService.getCalculationSteps(
DamageCalculationModel(
baseDamage: baseDamage,
weaponBonus: weaponBonus,
skillBonus: skillBonus,
hasArmor: hasArmor,
hasRattan: hasRattan,
hasEightDiagrams: hasEightDiagrams,
isFireDamage: isFireDamage,
isThunderDamage: isThunderDamage,
hasWineEffect: hasWineEffect,
timestamp: DateTime.now(),
),
);
return _buildSection(
title: '计算详情',
icon: Icons.calculate,
color: Colors.green,
child: Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: steps.map((step) => Padding(
padding: EdgeInsets.only(bottom: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
step.split(':')[0],
style: TextStyle(fontSize: 13.sp, color: Colors.grey.shade700),
),
Text(
step.split(':')[1],
style: TextStyle(
fontSize: 13.sp,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
],
),
)).toList(),
),
),
);
}
Widget _buildSection({
required String title,
required IconData icon,
required Color color,
required Widget child,
}) {
return Container(
width: double.infinity,
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(icon, color: color, size: 20.sp),
SizedBox(width: 8.w),
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
SizedBox(height: 12.h),
child,
],
),
);
}
Widget _buildSlider({
required String label,
required int value,
required int min,
required int max,
required Function(double) onChanged,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500)),
Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: Colors.grey.shade100,
borderRadius: BorderRadius.circular(8.r),
),
child: Text('$value', style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.bold)),
),
],
),
SizedBox(height: 8.h),
Slider(
value: value.toDouble(),
min: min.toDouble(),
max: max.toDouble(),
divisions: max - min,
activeColor: Colors.red,
inactiveColor: Colors.grey.shade300,
onChanged: onChanged,
),
],
);
}
Widget _buildCheckbox(String label, bool value, Function(bool) onChanged) {
return CheckboxListTile(
title: Text(label, style: TextStyle(fontSize: 14.sp)),
value: value,
onChanged: (newValue) => onChanged(newValue ?? false),
activeColor: Colors.red,
contentPadding: EdgeInsets.zero,
controlAffinity: ListTileControlAffinity.leading,
);
}
Widget _buildActionButtons() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _saveCalculation,
icon: const Icon(Icons.save),
label: const Text('保存计算'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h),
),
),
),
SizedBox(width: 12.w),
Expanded(
child: ElevatedButton.icon(
onPressed: _shareCalculation,
icon: const Icon(Icons.share),
label: const Text('分享结果'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 12.h),
),
),
),
],
),
);
}
void _animateResult() {
_resultAnimationController.forward().then((_) {
_resultAnimationController.reverse();
});
}
void _resetCalculator() {
setState(() {
baseDamage = 1;
weaponBonus = 0;
skillBonus = 0;
hasArmor = false;
hasRattan = false;
hasEightDiagrams = false;
isFireDamage = false;
isThunderDamage = false;
hasWineEffect = false;
});
_animateResult();
}
void _saveCalculation() async {
final model = DamageCalculationModel(
baseDamage: baseDamage,
weaponBonus: weaponBonus,
skillBonus: skillBonus,
hasArmor: hasArmor,
hasRattan: hasRattan,
hasEightDiagrams: hasEightDiagrams,
isFireDamage: isFireDamage,
isThunderDamage: isThunderDamage,
hasWineEffect: hasWineEffect,
timestamp: DateTime.now(),
);
await DamageCalculatorService.saveCalculation(model);
Get.snackbar(
'保存成功',
'计算结果已保存到历史记录',
snackPosition: SnackPosition.BOTTOM,
);
}
void _shareCalculation() {
final shareText = '三国杀伤害计算结果:\n'
'基础伤害:$baseDamage\n'
'武器加成:$weaponBonus\n'
'技能加成:$skillBonus\n'
'最终伤害:$finalDamage';
Get.snackbar(
'分享',
'计算结果已复制到剪贴板',
snackPosition: SnackPosition.BOTTOM,
);
}
void _loadHistory() async {
final calculations = await DamageCalculatorService.getCalculationHistory();
setState(() => history = calculations);
}
void _showHistory() {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => Container(
height: MediaQuery.of(context).size.height * 0.7,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.r),
topRight: Radius.circular(20.r),
),
),
child: Column(
children: [
Container(
padding: EdgeInsets.all(16.w),
child: Text(
'计算历史',
style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
),
),
Expanded(
child: history.isEmpty
? Center(
child: Text(
'暂无历史记录',
style: TextStyle(fontSize: 14.sp, color: Colors.grey.shade600),
),
)
: ListView.builder(
padding: EdgeInsets.all(16.w),
itemCount: history.length,
itemBuilder: (context, index) {
final result = history[index];
return _buildHistoryItem(result);
},
),
),
],
),
),
);
}
Widget _buildHistoryItem(DamageCalculationModel result) {
final damage = result.calculateFinalDamage();
return Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
children: [
Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: Center(
child: Text(
'$damage',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'基础${result.baseDamage} + 武器${result.weaponBonus} + 技能${result.skillBonus}',
style: TextStyle(fontSize: 13.sp, fontWeight: FontWeight.w500),
),
Text(
'${result.timestamp.month}/${result.timestamp.day} ${result.timestamp.hour}:${result.timestamp.minute.toString().padLeft(2, '0')}',
style: TextStyle(fontSize: 11.sp, color: Colors.grey.shade600),
),
],
),
),
],
),
);
}
void dispose() {
_resultAnimationController.dispose();
super.dispose();
}
}
伤害计算器屏幕使用了多个小组件来组织不同的功能区域。每个区域都有独立的状态管理和交互逻辑。使用 AnimatedBuilder 为计算结果添加了动画效果,提升了用户体验。
总结
通过本文的实现,我们构建了一个功能完整的伤害计算器系统。这个系统不仅提供了精确的伤害计算,还包含了历史记录、分享功能和详细的计算步骤展示。
核心功能特色:
- 多维度的伤害因素计算
- 实时的伤害结果显示
- 详细的计算步骤展示
- 历史记录保存和查看
- 分享和导出功能
- 流畅的动画效果
- 直观的参数调整界面
这个伤害计算器为玩家提供了精确的伤害预测工具,帮助他们在游戏中做出更好的决策。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)