142 lines
3.3 KiB
Dart
142 lines
3.3 KiB
Dart
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
|
|
class Particle {
|
|
Offset position;
|
|
Offset velocity;
|
|
Color color;
|
|
double size;
|
|
double life; // 1.0 to 0.0
|
|
double decay;
|
|
|
|
Particle({
|
|
required this.position,
|
|
required this.velocity,
|
|
required this.color,
|
|
required this.size,
|
|
required this.life,
|
|
required this.decay,
|
|
});
|
|
}
|
|
|
|
class ExplosionWidget extends StatefulWidget {
|
|
const ExplosionWidget({super.key});
|
|
|
|
@override
|
|
ExplosionWidgetState createState() => ExplosionWidgetState();
|
|
}
|
|
|
|
class ExplosionWidgetState extends State<ExplosionWidget>
|
|
with SingleTickerProviderStateMixin {
|
|
late AnimationController _controller;
|
|
final List<Particle> _particles = [];
|
|
final Random _random = Random();
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(milliseconds: 1000),
|
|
);
|
|
_controller.addListener(_updateParticles);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller.removeListener(_updateParticles);
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _updateParticles() {
|
|
if (_particles.isEmpty) return;
|
|
|
|
for (var i = _particles.length - 1; i >= 0; i--) {
|
|
final p = _particles[i];
|
|
p.position += p.velocity;
|
|
p.velocity += Offset(0, 0.5); // Gravity
|
|
p.life -= p.decay;
|
|
if (p.life <= 0) {
|
|
_particles.removeAt(i);
|
|
}
|
|
}
|
|
|
|
if (_particles.isEmpty) {
|
|
_controller.stop();
|
|
}
|
|
setState(() {});
|
|
}
|
|
|
|
void explode(Offset position) {
|
|
// Clear old particles if any (optional, or just add more)
|
|
// _particles.clear();
|
|
|
|
// Create new particles
|
|
for (int i = 0; i < 30; i++) {
|
|
final double angle = _random.nextDouble() * 2 * pi;
|
|
final double speed = _random.nextDouble() * 5 + 2;
|
|
final double dx = cos(angle) * speed;
|
|
final double dy = sin(angle) * speed;
|
|
|
|
// Random colors for fire/explosion effect
|
|
Color color;
|
|
final r = _random.nextDouble();
|
|
if (r < 0.33) {
|
|
color = Colors.redAccent;
|
|
} else if (r < 0.66) {
|
|
color = Colors.orangeAccent;
|
|
} else {
|
|
color = Colors.yellowAccent;
|
|
}
|
|
|
|
_particles.add(
|
|
Particle(
|
|
position: position,
|
|
velocity: Offset(dx, dy),
|
|
color: color,
|
|
size: _random.nextDouble() * 4 + 2,
|
|
life: 1.0,
|
|
decay: _random.nextDouble() * 0.02 + 0.01,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (!_controller.isAnimating) {
|
|
_controller.repeat(); // Use repeat to keep loop running until empty
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return IgnorePointer(
|
|
child: CustomPaint(
|
|
painter: ExplosionPainter(_particles),
|
|
size: Size.infinite,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class ExplosionPainter extends CustomPainter {
|
|
final List<Particle> particles;
|
|
|
|
ExplosionPainter(this.particles);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size) {
|
|
for (final p in particles) {
|
|
final paint = Paint()
|
|
..color = p.color.withOpacity(p.life.clamp(0.0, 1.0))
|
|
..style = PaintingStyle.fill;
|
|
|
|
canvas.drawCircle(p.position, p.size, paint);
|
|
}
|
|
}
|
|
|
|
@override
|
|
bool shouldRepaint(covariant ExplosionPainter oldDelegate) {
|
|
return true; // Always repaint when animating
|
|
}
|
|
}
|