View the directory –>
This article code address –>
Above, a simple tornado effect with particles:
Only write down ideas and techniques used
The core idea
Any special effects are debugged step by step:
- Defining particles, simulating the passage of time
- Define multiple particles, let the particles move, spiral up
- Twisters are more vivid, using bezier curves as the axis of the twister
- Moving tornadoes are the most vivid, allowing the Bezier curve to twist over time and allowing the tornado to spin left, right, back and forth
Technical point
- Use AnimationController and then repeat() to simulate time lapse. Notice that there is an addListener method, which is the callback for each frame. In the callbacks of each frame, the particle trajectory is processed.
- To simulate time lapse, define duration when creating the AnimationController, and use animation.value to get a gradient value that I originally used in the simulation of the trajectory, which turned out to be a pit. Remember not to use this value. Instead, you define a variable and change the trajectory through the tick() method. Remember, there are callbacks per frame, which is about 60 callbacks per second. Change your thinking.
- When the particle rotates around the path, the method computeMetrics() is used to obtain the path process, and then getTangentForOffset(), the information of the point on the path, including Angle and position, Angle is the Angle between the tangent line of the point and the positive direction of the X axis. Remember that Canvas can rotate to avoid complex calculations on the non-XY axis.
- Twist the Bezier curve by controlling its control points, and twist the tornado
code
Particle defines the class of particles
Particle.dart
import 'dart:ui'; class Particle { double x; double y; double z; double vx; double vy; double vz; double ax; double ay; double az; double radius; // double Angle; // path double rotate; // Double initRotate; // The initial rotation Angle double cur; // Double curStep; // The increment value of path.length, the Offset center along which the particle is rotated; // Particle({this.x = 0, this.y = 0, this.z = 0, this.vx = 0, this.vy = 0, this.vz = 0, this.vy = 0, this.vz = 0, this.ax = 0, this.ay = 0, this.az = 0, this.radius = 1, this.angle = 0, this.rotate = 0, this.initRotate =0, this.cur = 0, this.center = Offset.zero, this.curStep = 0 }); }Copy the code
AxisManager Tornado axis
AxisManager.dart
import 'dart:math'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; Class AxisManager extends ChangeNotifier {Path Path = Path(); // The axis of the tornado extends ChangeNotifier to call back the tick() method. double x1 = -160; // double y1 = -70; bool isX1Right = true; // double x2 = 160; // Double x2 = 160; double y2 = -200; bool isX2Right = true; double x3 = -190; double y3 = -400; bool isX3Right = true; double angle = 0; AxisManager(){path.moveto (0, 0); path.relativeCubicTo(x1, y1, x2, y2, x3, y3); } void tick() { update(); notifyListeners(); } void update(){ double dis = 2; If (x1<=-160){isX1Right = true; }else if(x1>=160){ isX1Right = false; } if(isX1Right){ x1+=dis; }else{ x1-=dis; } if(x2<=-160){ isX2Right = true; }else if(x2>=160){ isX2Right = false; } if(isX2Right){ x2+=dis; }else{ x2-=dis; } if(x3<=-190){ isX3Right = true; }else if(x3>=190){ isX3Right = false; } if(isX3Right){ x3+=dis; }else{ x3-=dis; } path.reset(); Angle - = 0.02; if(angle<-pi*2){ angle=0; } path.moveTo(150*sin(angle), x1/5); path.relativeCubicTo(x1, y1, x2, y2, x3, y3); } Path getAxis(){ return path; }}Copy the code
ParticleManager Particle management
Including particle initialization, addition, update and other operations:
ParticleManager.dart
import 'dart:math'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/AxisManager.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; class ParticleManager extends ChangeNotifier { AxisManager am; ParticleManager(this.am); List<Particle> list = []; void add(Particle p) { if (p ! = null) { list.add(p); notifyListeners(); } } void tick() { Random random = Random(); list.forEach((p) { doUpdate(p,random); }); notifyListeners(); } // Tornado void doUpdate(Particle p,Random Random) {Path axis = am.getaxis (); PathMetric pathMetric = axis.computeMetrics().first; p.cur += p.curStep; if(p.cur>pathMetric.length){ p.cur = 0; } Tangent tg = pathMetric.getTangentForOffset(p.cur); double angle = tg.angle; p.angle = angle; Offset center = tg.position; p.center = center; p.radius = p.cur/5; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); } // Snake topspin void doUpdate1(Particle p,Random Random) {Path axis = am.getaxis (); PathMetric pathMetric = axis.computeMetrics().first; p.cur += 2; if(p.cur>pathMetric.length){ p.cur = 0; } Tangent tg = pathMetric.getTangentForOffset(p.cur); double angle = tg.angle; p.angle = angle; Offset center = tg.position; p.center = center; p.radius = 100; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); } // void doUpdate3(Particle p,Random Random) {p.y += p.y; if (p.y < -500) { p.y = 0; } p.radius = 100 - p.y / 4; P.r otate + = 0.01; if(p.rotate>1){ p.rotate=0; } p.x = p.radius * sin(pi * 2 *p.rotate + p.initRotate); }}Copy the code
TimeLine simulates time lapse
TimeLine.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_can/c13_tornado/AxisManager.dart';
import 'package:flutter_can/c13_tornado/Particle.dart';
import 'package:flutter_can/c13_tornado/ParticleManager.dart';
import 'TornadoRender.dart';
class TimeLine extends StatefulWidget {
@override
_TimeLineState createState() => _TimeLineState();
}
class _TimeLineState extends State<TimeLine> with SingleTickerProviderStateMixin {
AnimationController _controller;
ParticleManager pm;
AxisManager am;
@override
void initState() {
super.initState();
am = new AxisManager();
pm = new ParticleManager(am);
initParticleManager();
_controller = new AnimationController(vsync: this,duration: const Duration(milliseconds: 2000));
_controller.addListener(() {
am.tick();
pm.tick();
});
_controller.repeat();
}
@override
Widget build(BuildContext context) {
return Container(
child: CustomPaint(
size: MediaQuery.of(context).size,
painter: TornadoRender(pm),
),
);
}
void initParticleManager() {
int num = 500;
for(int i=0;i<num;i++){
Random random = Random();
pm.add(Particle(
initRotate:pi*2*i/num,
cur:100*i/num,
curStep:random.nextDouble(),
vy:-1+random.nextDouble()
));
}
}
}
Copy the code
TornadoRender particles and axis drawing
TornadoRender.dart
import 'dart:ui'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_can/c13_tornado/AxisManager.dart'; import 'package:flutter_can/c13_tornado/Particle.dart'; import 'ParticleManager.dart'; class TornadoRender extends CustomPainter{ ParticleManager pm; TornadoRender(this.pm): super(repaint: pm); Paint _windPaint = Paint() .. color = Colors.grey .. style = PaintingStyle.fill .. isAntiAlias = true; @override void paint(Canvas canvas, Size size) { translateToCenter(canvas, size); drawAxis(canvas, size); drawParticles(canvas, size); } void drawAxis(Canvas canvas, Size size){ canvas.drawPath(pm.am.getAxis(), Paint() .. color=Colors.grey .. style=PaintingStyle.stroke .. strokeWidth=1); } void drawParticles(Canvas canvas, Size size){ int size = pm.list.length; for(int i=0; i<size; i++){ Particle particle = pm.list[i]; canvas.save(); canvas.translate(particle.center.dx, particle.center.dy); // canvas.rotate(-particle.angle+pi/2); canvas.drawCircle(Offset(particle.x,particle.y), 2, _windPaint); canvas.restore(); } } void translateToCenter(Canvas canvas, Size size){ canvas.translate(size.width/2, size.height-150); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { return true; }}Copy the code