169 lines
4.9 KiB
Dart
169 lines
4.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../providers/settings_provider.dart';
|
|
import '../../game/enums.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: const Duration(milliseconds: 800),
|
|
);
|
|
_translateController = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 1000),
|
|
);
|
|
|
|
_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,
|
|
) async {
|
|
if (risk == RiskLevel.safe || risk == RiskLevel.normal) {
|
|
// Safe & Normal: Dash/Wobble without scale
|
|
final isSafe = risk == RiskLevel.safe;
|
|
final duration = isSafe ? 500 : 400;
|
|
final offsetFactor = isSafe ? 0.2 : 0.5;
|
|
|
|
_translateController.duration = Duration(milliseconds: duration);
|
|
_translateAnimation =
|
|
Tween<Offset>(
|
|
begin: Offset.zero,
|
|
end: targetOffset * offsetFactor,
|
|
).animate(
|
|
CurvedAnimation(
|
|
parent: _translateController,
|
|
curve: Curves.easeOutQuad,
|
|
),
|
|
);
|
|
|
|
await _translateController.forward();
|
|
if (!mounted) return;
|
|
onImpact();
|
|
await _translateController.reverse();
|
|
} else {
|
|
// Risky: Scale + Heavy Dash
|
|
final attackScale = context.read<SettingsProvider>().attackAnimScale;
|
|
_scaleAnimation = Tween<double>(begin: 1.0, end: attackScale).animate(
|
|
CurvedAnimation(parent: _scaleController, curve: Curves.easeOut),
|
|
);
|
|
|
|
_scaleController.duration = const Duration(milliseconds: 600);
|
|
_translateController.duration = const Duration(milliseconds: 500);
|
|
|
|
// 1. Scale Up (Preparation)
|
|
await _scaleController.forward();
|
|
if (!mounted) return;
|
|
|
|
// 2. Dash to Target (Impact)
|
|
// Adjust offset to prevent complete overlap (stop slightly short) since both share the same layer stack
|
|
final adjustedOffset = targetOffset * 0.5;
|
|
|
|
_translateAnimation =
|
|
Tween<Offset>(begin: Offset.zero, end: adjustedOffset).animate(
|
|
CurvedAnimation(
|
|
parent: _translateController,
|
|
curve: Curves.easeInExpo, // Heavy impact curve
|
|
),
|
|
);
|
|
|
|
await _translateController.forward();
|
|
if (!mounted) return;
|
|
|
|
// 3. Impact Callback (Shake)
|
|
onImpact();
|
|
|
|
// 4. Return (Reset)
|
|
_scaleController.reverse();
|
|
_translateController.reverse();
|
|
}
|
|
}
|
|
|
|
Future<void> animateDefense(VoidCallback onImpact) async {
|
|
// Defense: Wobble/Shake horizontally
|
|
_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();
|
|
}
|
|
|
|
@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,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|