Home > Web Front-end > H5 Tutorial > Implement a simple particle engine code example using HTML5 canvas

Implement a simple particle engine code example using HTML5 canvas

黄舟
Release: 2017-03-22 15:26:37
Original
2227 people have browsed it

Preface

Well, saying it is a "particle engine" is still a big statement and a bit of a title, and it is still a bit far from a real particle engine. Without further ado, let’s take a look at the demo

This article will teach you how to make a simple canvasparticlemaker (hereinafter referred to as the engine).

World view

This simple engine requires three elements: World, Launcher, and Grain. To sum up, the emitter exists in the world, and the emitter creates particles. The world and the emitter will affect the state of the particles. After each particle is affected by the world and the emitter, the next particle is calculated. Draw yourself in the position of the moment.

World

The so-called "world" is the environment that globally affects the particles that exist in this "world". If a particle chooses to exist in this "world", then this particle will be affected by this "world".

Launcher

The unit used to emit particles. They can control various properties of particles generated by particles. As the parent of particles, the emitter can control the birth attributes of particles: birth position, birth size, lifespan, whether it is affected by "World", whether it is affected by "Launcher" itself, etc...

In addition, the emitter itself must clean up the dead particles it has produced.

Particle(Grain)

The smallest basic unit is every turbulent individual. Each individual has its own position, size, lifespan, and whether it is affected by the same name, etc., so that their shape can be accurately depicted on the canvas at every moment.

Main logic of particle drawing

Implement a simple particle engine code example using HTML5 canvas

The above is the main logic of particle drawing.

Let’s first look at what the world needs.

Create a world

I don’t know why I naturally think that the world should have gravitational acceleration. But gravity acceleration alone cannot show many tricks, so here I added two other influencing factors to him: heat and wind. The direction of gravitational acceleration and heat is vertical, and the direction of wind influence is horizontal. With these three things, we can make the particles move very coquettishly.

The maintenance of some states (such as the survival and death of particles) requires time markers, so let's add time to the world, so as to facilitate the effect of time suspension and reverse flow later.

define(function(require, exports, module) {
    var Util = require('./Util');
    var Launcher = require('./Launcher');

    /**
     * 世界构造函数
     * @param config
     *          backgroundImage     背景图片
     *          canvas              canvas引用
     *          context             canvas的context
     *
     *          time                世界时间
     *
     *          gravity             重力加速度
     *
     *          heat                热力
     *          heatEnable          热力开关
     *          minHeat             随机最小热力
     *          maxHeat             随机最大热力
     *
     *          wind                风力
     *          windEnable          风力开关
     *          minWind             随机最小风力
     *          maxWind             随机最大风力
     *
     *          timeProgress        时间进步单位,用于控制时间速度
     *          launchers           属于这个世界的发射器队列
     * @constructor
     */
    function World(config){
        //太长了,略去细节
    }
    World.prototype.updateStatus = function(){};
    World.prototype.timeTick = function(){};
    World.prototype.createLauncher = function(config){};
    World.prototype.drawBackground = function(){};
    module.exports = World;
});
Copy after login

As we all know, drawing animation means constantly having to redraw it, so we need to expose a method for external loop calls:

/**
 * 循环触发函数
 * 在满足条件的时候触发
 * 比如RequestAnimationFrame回调,或者setTimeout回调之后循环触发的
 * 用于维持World的生命
 */
 
World.prototype.timeTick = function(){

    //更新世界各种状态
    this.updateStatus();

    this.context.clearRect(0,0,this.canvas.width,this.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 after login

When this timeTick method is called by the external loop, every time Do these things every time:

  1. Update world status

  2. Clear the canvas and redraw the background

  3. Poll all emitters around the world and update their status, create new particles, and draw particles

So, what exactly are the states of the world that need to be updated?

Obviously, it is easy to think of adding a little more time every time. Secondly, in order to make the particles move as coquettishly as possible, we keep the state of wind and heat unstable - every gust of wind and every gust of heat wave is something you are not aware of~

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 after login

The world was created , we have to make the world capable of making particle emitters, otherwise how can we make particles~

World.prototype.createLauncher = function(config){
    var _launcher = new Launcher(config);
    this.launchers.push(_launcher);
};
Copy after login

Okay, as God, we have almost built the world, and the next step is to fabricate various kinds of particles. Such a creature.

Pinch out the first creature: emitter

The emitter is the first creature in the world. It relies on the emitter to reproduce all kinds of strange particles. So what characteristics does a transmitter need to have?

First of all, you need to figure out which world it belongs to (because this world may be more than one world).

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

Finally, let’s describe his “genes”. The genes of the emitter will affect their offspring (particles). The more "genes" we give transmitters, the more biological traits their offspring will have. See the conscienceCommentscode below for details~

define(function (require, exports, module) {
    var Util = require(&#39;./Util&#39;);
    var Grain = require(&#39;./Grain&#39;);

    /**
     * 发射器构造函数
     * @param config
     *          id              身份标识用于后续可视化编辑器的维护
     *          world           这个launcher的宿主
     *
     *          grainImage      粒子图片
     *          grainList       粒子队列
     *          grainLife       产生的粒子的生命
     *          grainLifeRange  粒子生命波动范围
     *          maxAliveCount   最大存活粒子数量
     *
     *          x               发射器位置x
     *          y               发射器位置y
     *          rangeX          发射器位置x波动范围
     *          rangeY          发射器位置y波动范围
     *
     *          sizeX           粒子横向大小
     *          sizeY           粒子纵向大小
     *          sizeRange       粒子大小波动范围
     *
     *          mass            粒子质量(暂时没什么用)
     *          massRange       粒子质量波动范围
     *
     *          heat            发射器自身体系的热气
     *          heatEnable      发射器自身体系的热气生效开关
     *          minHeat         随机热气最小值
     *          maxHeat         随机热气最小值
     *
     *          wind            发射器自身体系的风力
     *          windEnable      发射器自身体系的风力生效开关
     *          minWind         随机风力最小值
     *          maxWind         随机风力最小值
     *
     *          grainInfluencedByWorldWind      粒子受到世界风力影响开关
     *          grainInfluencedByWorldHeat      粒子受到世界热气影响开关
     *          grainInfluencedByWorldGravity   粒子受到世界重力影响开关
     *
     *          grainInfluencedByLauncherWind   粒子受到发射器风力影响开关
     *          grainInfluencedByLauncherHeat   粒子受到发射器热气影响开关
     *
     * @constructor
     */

    function Launcher(config) {
        //太长了,略去细节
    }

    Launcher.prototype.updateLauncherStatus = function () {};
    Launcher.prototype.swipeDeadGrain = function (grain_id) {};
    Launcher.prototype.createGrain = function (count) {};
    Launcher.prototype.paintGrain = function () {};

    module.exports = Launcher;

});
Copy after login

The transmitter is responsible for giving birth to a child, how to give birth:

 Launcher.prototype.createGrain = function (count) {
        if (count + this.grainList.length <= this.maxAliveCount) {
            //新建了count个加上旧的还没达到最大数额限制
        } else if (this.grainList.length >= this.maxAliveCount &&
            count + this.grainList.length > this.maxAliveCount) {
            //光是旧的粒子数量还没能达到最大限制
            //新建了count个加上旧的超过了最大数额限制
            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({/*粒子配置*/});
            this.grainList.push(_grain);
        }
    };
Copy after login

After giving birth to a child, what if the child dies? I have to clean... (So sad, I blame insufficient memory)

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是自己定义的一个Array方法
            this.createGrain(1);
            break;
        }
    }
};
Copy after login

After giving birth, you still 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 after login

Don’t forget to maintain your own little internal world. Yeah~ (It’s similar to the big world outside)

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 after login

Okay, so far, we have completed the creation of the first creature in the world, and next will be their descendants (Huhu, God is so tired)

Descendants, endless descendants, come out, little ones, you are the protagonists of the world!

作为世界的主角,粒子们拥有各种自身的状态:位置、速度、大小、寿命长度、出生时间当然必不可少

define(function (require, exports, module) {
    var Util = require(&#39;./Util&#39;);

    /**
     * 粒子构造函数
     * @param config
     *          id              唯一标识
     *          world           世界宿主
     *          launcher        发射器宿主
     *
     *          x               位置x
     *          y               位置y
     *          vx              水平速度
     *          vy              垂直速度
     *
     *          sizeX           横向大小
     *          sizeY           纵向大小
     *
     *          mass            质量
     *          life            生命长度
     *          birthTime       出生时间
     *
     *          color_r
     *          color_g
     *          color_b
     *          alpha           透明度
     *          initAlpha       初始化时的透明度
     *
     *          influencedByWorldWind
     *          influencedByWorldHeat
     *          influencedByWorldGravity
     *          influencedByLauncherWind
     *          influencedByLauncherHeat
     *
     * @constructor
     */
    function Grain(config) {
        //太长了,略去细节
    }

    Grain.prototype.isDead = function () {};
    Grain.prototype.calculate = function () {};
    Grain.prototype.paint = function () {};
    module.exports = Grain;
});
Copy after login

粒子们需要知道自己的下一刻是怎样子的,这样才能把自己在世界展现出来。对于运动状态,当然都是初中物理的知识了:-)

Grain.prototype.calculate = function () {
    //计算位置
    if (this.influencedByWorldGravity) {
        this.vy += this.world.gravity+Util.randomFloat(0,0.3*this.world.gravity);
    }
    if (this.influencedByWorldHeat && this.world.heatEnable) {
        this.vy -= this.world.heat+Util.randomFloat(0,0.3*this.world.heat);
    }
    if (this.influencedByLauncherHeat && this.launcher.heatEnable) {
        this.vy -= this.launcher.heat+Util.randomFloat(0,0.3*this.launcher.heat);
    }
    if (this.influencedByWorldWind && this.world.windEnable) {
        this.vx += this.world.wind+Util.randomFloat(0,0.3*this.world.wind);
    }
    if (this.influencedByLauncherWind && this.launcher.windEnable) {
        this.vx += this.launcher.wind+Util.randomFloat(0,0.3*this.launcher.wind);
    }
    this.y += this.vy;
    this.x += this.vx;
    this.alpha = this.initAlpha * (1 - (this.world.time - this.birthTime) / this.life);

    //TODO 计算颜色 和 其他

};
Copy after login

粒子们怎么知道自己死了没?

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

粒子们又该以怎样的姿态把自己展现出来?

Grain.prototype.paint = function () {
    if (this.isDead()) {
        this.launcher.swipeDeadGrain(this.id);
    } else {
        this.calculate();
        this.world.context.save();
        this.world.context.globalCompositeOperation = &#39;lighter&#39;;
        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 after login

嗟乎。

The above is the detailed content of Implement a simple particle engine code example using HTML5 canvas. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template