preface



Well, “particle engine” is a bit of an overstatement and a bit of a buzzword. No more nonsense, first look at [demo], click the screen after scanning surprise oh…


      

This article will teach you how to make a simple Canvas particle maker.



World view

This simple engine needs three elements: the World, the Launcher, and the Grain. In general, the emitter exists in the world, the emitter makes particles, the world and the emitter will affect the state of the particles, each particle after the influence of the world and the emitter, calculate the position of the next moment, draw themselves out.



The World (World)

The “world” is the environment that affects the particles that exist in it. If a particle chooses to exist in this “world”, it will be affected by this “world”.



The emitter (Launcher)

A unit used to emit particles. They can control the properties of the particles they produce. As the parents of particles, the Launcher can control the particle’s birth attributes: the location of birth, the size of birth, its lifespan, whether it is affected by the “World”, whether it is affected by the “Launcher” itself, and so on.

In addition, the emitter itself has to clean up the dead particles it has created.



Particles (Grain)

The smallest basic unit is every individual in turmoil. Each individual has its own location, size, life span, whether it is affected by the same name and other attributes, so that their form can be accurately depicted on the canvas at all times.



Particles draw master logic

Above is the main logic of particle drawing.

Let’s start by looking at what the world needs.



Create a world

I don’t know why IT took me for granted that the world should have an acceleration of gravity. But light doesn’t do much with gravity, so HERE I added two other influences: heat and wind. The acceleration of gravity and the hot gas are vertical, and the wind is horizontal, and with those three things, we can make particles behave in a very playful way.

Some states (such as the survival and death of particles) need to be maintained with time markers, so let’s add time to the world, so that we can make time pause, reverse flow effect later.

[Java]
Plain text view
Copy the code
define(function(require, exports, module) { var Util = require('./Util'); var Launcher = require('./Launcher'); /** * World constructor * @param config * backgroundImage Background picture * canvas canvas reference * context canvas context ** time world time ** gravity Gravitational acceleration * * Heat Heat * heatEnable Heat switch * minHeat Random minimum Heat * maxHeat Random maximum Heat * * Wind wind windEnable wind switch * minWind random minimum wind * MaxWind * * timeProgress timeProgress unit, Constructor */ function World(config){// Too long, Omit details} World. Prototype. UpdateStatus = function () {}; World.prototype.timeTick = function(){}; World.prototype.createLauncher = function(config){}; World.prototype.drawBackground = function(){}; module.exports = World; });Copy the code


As you all know, drawing an animation is constantly redrawing, so we need to expose a method to the external loop call:

[Java]
Plain text view
Copy the code
/** * looping triggers functions * when conditions are met * such as RequestAnimationFrame callback, */ world.prototype. timeTick = function(){this.updatestatus (); Enclosing context. ClearRect (0, 0, this. Canvas. Width, enclosing canvas, height); this.drawBackground(); For (var I = 0; iCopy the code

The timeTick method does these things each time it is called in the outer loop:

  • Update world status
  • Clear the canvas and redraw the background
  • Poll all emitters around the world and update their status, create new particles, draw particles

So, what is the state of the world to update?

Obviously, it’s easy to think of moving forward a little bit each time. Second, to make the particles as dynamic as possible, we keep wind and heat unstable — every gust of wind, every heat wave, you don’t realize

[Java]
Plain text view
Copy the code
World.prototype.updateStatus = function(){
    this.time+=this.timeProgress;
    this.wind = Util.randomFloat(this.minWind,this.maxWind);
    this.heat = Util.randomFloat(this.minHeat,this.maxHeat);
};
Copy the code

The world is made, we have to make the world can make particle emitters ah, otherwise how to make particles ~

[Java]
Plain text view
Copy the code
World.prototype.createLauncher = function(config){
    var _launcher = new Launcher(config);
    this.launchers.push(_launcher);
};Copy the code

Well, as Gods, we’ve made the world pretty much, and now it’s time to make up all kinds of creatures.




Squeeze out the first creature: emitter

Emitters are the world’s first living things, and they produce all kinds of weird particles. So what characteristics do launchers need to have?

First, figure out which world it belongs to (because there may be more than one world).

Secondly, it is the state of the emitter itself: position, wind power and heat in its own system. It can be said that the emitter is a small world in a world.

Finally, there is a description of his “genes”, emitters whose genes affect their offspring (particles). The more “genes” we give transmitters, the more biological traits their offspring will have. Specifically look at the following conscience comment code ~

[Java]
Plain text view
Copy the code
define(function (require, exports, module) { var Util = require('./Util'); var Grain = require('./Grain'); /** * launcher constructor * @param config * ID Id used for subsequent visual editor maintenance ** World host of this launcher ** grainImage particle image * grainList particle queue * Life of particles produced by grainLife * grainLifeRange Particle Life fluctuation range * maxAliveCount Maximum number of viable particles * * x emitter location x * y emitter location y * rangeX Emitter location x fluctuation range * RangeY emitter position y range * * sizeX particle size * sizeY particle size * sizeRange particle sizeRange * * mass particle mass (not currently useful) * massRange particle mass range * * Heat heat of the emitter system * heatEnable Heat effect switch of the emitter system * minHeat Minimum random heat * maxHeat Minimum random heat * * Wind Wind of the emitter system * windEnable Transmitter system itself effective wind switch * minWind random wind the minimum * maxWind random wind minimum grainInfluencedByWorldWind particles is affected by the world wind switch * * * Affected by the heat switch * grainInfluencedByWorldGravity grainInfluencedByWorldHeat particles affected by the gravity switch * * grainInfluencedByLauncherWind particles Affected by the emitter wind switch * grainInfluencedByLauncherHeat particles affected by heat emitter switch @ constructor * * * / function the Launcher (config) {/ / is too long. Omit details} the Launcher. The prototype. UpdateLauncherStatus = function () {}; Launcher.prototype.swipeDeadGrain = function (grain_id) {}; Launcher.prototype.createGrain = function (count) {}; Launcher.prototype.paintGrain = function () {}; module.exports = Launcher; });Copy the code

The launcher is responsible for giving birth. How?

[Java]
Plain text view
Copy the code
Launcher.prototype.createGrain = function (count) { if (count + this.grainList.length <= this.maxAliveCount) { Else if (this.grainlist. length >= this.maxAlivecount && count + this.grainList.length >= this.grainlist. length) MaxAliveCount) {count = this.maxAlivecount - this.grainList.length; } else { count = 0; } for (var i = 0; i < count; i++) { var _rd = Util.randomFloat(0, Math.PI * 2); Var _grain = new Grain({/* particle configuration */}); this.grainList.push(_grain); }};Copy the code

You have to clean up after the baby dies… (So sad, blame memory is not enough)

[Java]
Plain text view
Copy the code
Launcher.prototype.swipeDeadGrain = function (grain_id) { for (var i = 0; i < this.grainList.length; i++) { if (grain_id == this.grainList[i].id) { this.grainList = this.grainList.remove(i); //remove is a self-defined Array method this.createGrain(1); break; }}};Copy the code

After giving birth, you have to let the baby out to play:

[Java]
Plain text view
Copy the code
Launcher.prototype.paintGrain = function () { for (var i = 0; i < this.grainList.length; i++) { this.grainList[i].paint(); }};Copy the code

Don’t forget to maintain your own inner world.

[Java]
Plain text view
Copy the code
Launcher.prototype.updateLauncherStatus = function () { if (this.grainInfluencedByLauncherWind) { this.wind = Util.randomFloat(this.minWind, this.maxWind); } if(this.grainInfluencedByLauncherHeat){ this.heat = Util.randomFloat(this.minHeat, this.maxHeat); }};Copy the code

Well, we’ve made the world’s first living creature, and now their descendants (Whoops, god is so tired)

Children and grandchildren, endless also

Come out, little ones, you are the hero of the world!

As the protagonists of the world, particles have various states of their own: position, speed, size, life span, birth time

[Java]
Plain text view
Copy the code
define(function (require, exports, module) { var Util = require('./Util'); /** * particle constructor * @param config * id Unique identifier * world world host * launcher host ** x position x * y position y * vx horizontal speed * vy vertical speed ** sizeX Horizontal size * sizeY vertical size * * mass quality * life life length * birthTime birthTime * * color_r * color_g * color_b * alpha transparency * initAlpha Transparency during initialization * * influencedByWorldWind * influencedByWorldHeat * influencedByWorldGravity * influencedByLauncherWind * Error () {// too long; // too long; // too long; } prototype = function () {}; Grain.prototype.calculate = function () {}; Grain.prototype.paint = function () {}; module.exports = Grain; });Copy the code

Particles need to know what they are going to be like in the next moment in order to present themselves to the world. For the state of motion, of course, are junior high school physics knowledge 🙂

[Java]
Plain text view
Copy the code
Grain. Prototype. Calculate = function () {/ / calculate position if (this. InfluencedByWorldGravity) {enclosing vy + = This. World. Gravity + Util. RandomFloat (0,0.3 * this. The world. Gravity); } if (this.influencedByWorldHeat && this.world.heatEnable) { this.vy -= This. World. Heat + Util. RandomFloat (0,0.3 * this. The world. Heat); } if (this.influencedByLauncherHeat && this.launcher.heatEnable) { this.vy -= Enclosing the launcher. Heat + Util. RandomFloat (0,0.3 * this. The launcher. Heat); } if (this.influencedByWorldWind && this.world.windEnable) { this.vx += This. World. Wind + Util. RandomFloat (0,0.3 * this. The world. Wind). } if (this.influencedByLauncherWind && this.launcher.windEnable) { this.vx += Enclosing the launcher. The wind + Util. RandomFloat (0,0.3 * this. The launcher. The wind); } this.y += this.vy; this.x += this.vx; this.alpha = this.initAlpha * (1 - (this.world.time - this.birthTime) / this.life); //TODO calculates color and other};Copy the code

How do particles know if they’re dead?

[Java]
Plain text view
Copy the code
Grain.prototype.isDead = function () {
    return Math.abs(this.world.time - this.birthTime)>this.life;
};Copy the code

How should the particles present themselves?

[Java]
Plain text view
Copy the code
Grain.prototype.paint = function () { if (this.isDead()) { this.launcher.swipeDeadGrain(this.id); } else { this.calculate(); this.world.context.save(); this.world.context.globalCompositeOperation = 'lighter'; this.world.context.globalAlpha = this.alpha; this.world.context.drawImage(this.launcher.grainImage, this.x-(this.sizeX)/2, this.y-(this.sizeY)/2, this.sizeX, this.sizeY); this.world.context.restore(); }};Copy the code

Jie.




subsequent

In the future, I hope to expand this prototype and recreate a visual editor for everyone to use.

Here’s the code, by the way:
Github.com/jation/Canv…








If you want to know more, please scan the following QR code, follow our public account, you can get more technical dry goods, there are wonderful activities to share with you ~