Maison >interface Web >Tutoriel H5 >Implémenter un exemple de code de moteur de particules simple à l'aide d'un canevas HTML5

Implémenter un exemple de code de moteur de particules simple à l'aide d'un canevas HTML5

黄舟
黄舟original
2017-03-22 15:26:372223parcourir

Avant-propos

Eh bien, l'appeler un "moteur à particules" est toujours une grande déclaration et un peu un titre, et c'est encore un peu loin d'un véritable moteur de particules. Sans plus tarder, jetons un coup d'œil à la démo

Cet article vous apprendra comment créer un simple canvascréateur de particules (ci-après dénommé le moteur). ).

Vue du monde

Ce moteur simple nécessite trois éléments : World, Launcher et Grain. En résumé, ceci est : l'émetteur existe dans le monde, et l'émetteur crée des particules. Le monde et l'émetteur affecteront l'état des particules. Après que chaque particule soit affectée par le monde et l'émetteur, le calcul suivant est effectué : Dessinez-vous dans la position du moment.

Monde

Le soi-disant « monde » est le environnement qui affecte globalement les particules qui existent dans ce « monde ». Si une particule choisit d'exister dans ce « monde », alors cette particule sera affectée par ce « monde ».

Lanceur

Une unité utilisée pour émettre des particules. Ils peuvent contrôler diverses propriétés des particules qu'ils génèrent. En tant que parent des particules, l'émetteur peut contrôler les attributs de naissance des particules : position de naissance, taille de naissance, durée de vie, s'il est affecté par le « Monde », s'il est affecté par le « Lanceur » lui-même, etc...

De plus, l'émetteur lui-même doit nettoyer les particules mortes qu'il a produites.

Particule (Grain)

La plus petite unité de base est chaque individu turbulent. Chaque individu a son propre emplacement, sa taille, sa durée de vie, s'il porte le même nom, etc., afin que sa forme puisse être représentée avec précision sur la toile à tout moment.

Logique principale du dessin de particules

Implémenter un exemple de code de moteur de particules simple à l'aide d'un canevas HTML5

Ce qui précède est la logique principale du dessin de particules.

Regardons d’abord ce dont le monde a besoin.

Créer un monde

Je ne sais pas pourquoi j'ai naturellement pensé que le monde devrait avoir une accélération gravitationnelle. Cependant, l'accélération gravitationnelle à elle seule ne peut pas montrer beaucoup d'astuces, j'ai donc ajouté ici deux autres facteurs d'influence : la chaleur et le vent. La direction de l'accélération gravitationnelle et de la chaleur est verticale, et la direction de l'influence du vent est horizontale. Avec ces trois choses, nous pouvons faire bouger les particules de manière très coquette.

Le maintien de certains états (comme la survie des particules) nécessite des marqueurs temporels, ajoutons donc du temps au monde, afin de faciliter l'effet de suspension du temps et de flux inverse plus tard.

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;
});

Comme nous le savons tous, dessiner une animation signifie devoir constamment la redessiner, nous devons donc exposer une méthode pour les appels de boucle externe :

/**
 * 循环触发函数
 * 在满足条件的时候触发
 * 比如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();
    }
};

Cette méthode timeTick est dans le boucle externe Lorsque vous appelez, faites ces choses à chaque fois :

  1. Mettre à jour le statut du monde

  2. Effacer le canevas et redessiner l'arrière-plan

  3. Interrogez tous les émetteurs du monde entier et mettez à jour leur statut, créez de nouvelles particules et dessinez des particules

Alors, quels sont exactement les statuts du monde qui doivent être mis à jour ?

Évidemment, il est facile de penser à avancer un peu le temps à chaque fois. Deuxièmement, afin de faire bouger les particules le plus coquettement possible, nous maintenons l'état du vent et de la chaleur instable - chaque rafale de vent et chaque rafale de canicule est quelque chose dont vous n'êtes pas conscient ~

World.prototype.updateStatus = function(){
    this.time+=this.timeProgress;
    this.wind = Util.randomFloat(this.minWind,this.maxWind);
    this.heat = Util.randomFloat(this.minHeat,this.maxHeat);
};

Création du monde Après sa sortie, nous devons rendre le monde capable de fabriquer des émetteurs de particules. Sinon, comment pouvons-nous fabriquer des particules~

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

D'accord, en tant que Dieu, nous avons presque construit le monde, et la prochaine étape est que toutes sortes de créatures ont été fabriquées.

Pincez la première créature : l'émetteur

L'émetteur est la première créature au monde. Il s'appuie sur l'émetteur pour reproduire toutes sortes de particules étranges. Alors, quelles caractéristiques doit avoir un émetteur ?

Tout d'abord, vous devez déterminer à quel monde il appartient (car ce monde peut être plus d'un monde).

Deuxièmement, c'est l'état de l'émetteur lui-même : position, vent et chaleur au sein de son propre système. On peut dire que l'émetteur est un petit monde dans un monde.

Enfin, décrivons ses « gènes ». Les gènes de l’émetteur affecteront leur progéniture (particules). Plus nous donnons de « gènes » aux transmetteurs, plus leur progéniture aura de traits biologiques. Voir la conscience commentaire code ci-dessous pour plus de détails~

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;

});

L'émetteur est responsable de donner naissance à un enfant, comment accoucher :

 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);
        }
    };

Après donner naissance à l'enfant, l'enfant que je dois encore nettoyer après ma mort... (C'est tellement triste, je blâme le manque de mémoire)

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;
        }
    }
};

Après l'accouchement, je dois encore laisser l'enfant jouer :

Launcher.prototype.paintGrain = function () {
    for (var i = 0; i < this.grainList.length; i++) {
        this.grainList[i].paint();
    }
};

Le mien N'oubliez pas d'entretenir le petit monde intérieur ~ (C'est similaire au grand monde extérieur)

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);
    }
};

D'accord, jusqu'à présent, nous ont achevé la création de la première créature du monde, et ensuite viennent leurs descendants (Huhu, Dieu est si fatigué)

Enfants et petits-enfants, sans fin

Sortez, petits, vous êtes les protagonistes du monde !

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

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;
});

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

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 计算颜色 和 其他

};

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

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

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

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();
    }
};

嗟乎。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn