Well, “particle engine” is a bit of a bombshell and a bit of a tagline, but it’s a long way from being a particle engine. Without further ado, this article will teach you how to make a simple Canvas particle maker.

I. 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.

1. 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”.

2. 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.

3. The 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.

Two. Particle rendering 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.

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:

/** * 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; i<this.launchers.length; i++){ this.launchers[i].updateLauncherStatus(); this.launchers[i].createGrain(1); this.launchers[i].paintGrain(); }};Copy 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 the emitters in the world and update their state, create new particles, draw particles so what’s 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

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 ~

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.

4. Pinch 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 ~

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?

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)

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:

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.

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)

Zi Zi Sun sun, endless also come out, small people, you are the leading role of the world!

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

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 🙂

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?

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

How should the particles present themselves?

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.

5. Subsequent

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

By the way, the code is here: github.com/jation/Canv…