game/lib/widgets/battle/battle_animation_widget.dart

185 lines
5.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/settings_provider.dart';
import '../../game/enums.dart';
import '../../game/config.dart';
class BattleAnimationWidget extends StatefulWidget {
final Widget child;
const BattleAnimationWidget({super.key, required this.child});
@override
BattleAnimationWidgetState createState() => BattleAnimationWidgetState();
}
class BattleAnimationWidgetState extends State<BattleAnimationWidget>
with TickerProviderStateMixin {
late AnimationController _scaleController;
late AnimationController _translateController;
late Animation<double> _scaleAnimation;
late Animation<Offset> _translateAnimation;
@override
void initState() {
super.initState();
_scaleController = AnimationController(
vsync: this,
duration: AnimationConfig.attackRiskyScale,
);
_translateController = AnimationController(
vsync: this,
duration: AnimationConfig.attackRiskyDash,
);
_scaleAnimation = Tween<double>(
begin: 1.0,
end: 2.0,
).animate(CurvedAnimation(parent: _scaleController, curve: Curves.easeOut));
// Default translation, will be updated on animateAttack
_translateAnimation = Tween<Offset>(
begin: Offset.zero,
end: Offset.zero,
).animate(_translateController);
}
@override
void dispose() {
_scaleController.dispose();
_translateController.dispose();
super.dispose();
}
Future<void> animateAttack(
Offset targetOffset,
VoidCallback onImpact,
RiskLevel risk, {
VoidCallback? onAnimationStart,
VoidCallback? onAnimationMiddle,
VoidCallback? onAnimationEnd,
}) async {
_resetControllers();
onAnimationStart?.call();
if (risk == RiskLevel.safe || risk == RiskLevel.normal) {
final isSafe = risk == RiskLevel.safe;
final duration = AnimationConfig.getAttackDuration(risk);
final offsetFactor = isSafe ? 0.2 : 0.5;
final curve = isSafe
? AnimationConfig.attackSafeCurve
: AnimationConfig.attackNormalCurve;
_translateController.duration = duration;
_translateAnimation = Tween<Offset>(
begin: Offset.zero,
end: targetOffset * offsetFactor,
).animate(CurvedAnimation(parent: _translateController, curve: curve));
await _translateController.forward();
if (!mounted) return;
onAnimationEnd?.call();
onImpact();
await _translateController.reverse();
} else {
final attackScale = context.read<SettingsProvider>().attackAnimScale;
_scaleAnimation = Tween<double>(begin: 1.0, end: attackScale).animate(
CurvedAnimation(parent: _scaleController, curve: Curves.easeOut),
);
_scaleController.duration = AnimationConfig.attackRiskyScale;
_translateController.duration = AnimationConfig.attackRiskyDash;
await _scaleController.forward();
if (!mounted) return;
onAnimationMiddle?.call();
final adjustedOffset = targetOffset * 0.5;
_translateAnimation =
Tween<Offset>(begin: Offset.zero, end: adjustedOffset).animate(
CurvedAnimation(
parent: _translateController,
curve: AnimationConfig.attackRiskyDashCurve,
),
);
await _translateController.forward();
if (!mounted) return;
onAnimationEnd?.call();
onImpact();
await Future.wait([
_scaleController.reverse(),
_translateController.reverse(),
]);
}
}
Future<void> animateDefense(VoidCallback onImpact) async {
_resetControllers();
_translateController.duration = const Duration(milliseconds: 800);
// Sequence: Left -> Right -> Center
_translateAnimation =
TweenSequence<Offset>([
TweenSequenceItem(
tween: Tween<Offset>(begin: Offset.zero, end: const Offset(-10, 0)),
weight: 25,
),
TweenSequenceItem(
tween: Tween<Offset>(
begin: const Offset(-10, 0),
end: const Offset(10, 0),
),
weight: 50,
),
TweenSequenceItem(
tween: Tween<Offset>(begin: const Offset(10, 0), end: Offset.zero),
weight: 25,
),
]).animate(
CurvedAnimation(
parent: _translateController,
curve: Curves.easeInOut,
),
);
await _translateController.forward();
if (!mounted) return;
onImpact();
_translateController.reset();
}
void _resetControllers() {
if (_scaleController.isAnimating) {
_scaleController.stop();
}
if (_translateController.isAnimating) {
_translateController.stop();
}
_scaleController.reset();
_translateController.reset();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: Listenable.merge([_scaleController, _translateController]),
builder: (context, child) {
return Transform.translate(
offset: _translateAnimation.value,
child: Transform.scale(
scale: _scaleAnimation.value,
child: widget.child,
),
);
},
);
}
}