>  기사  >  웹 프론트엔드  >  HTML5 캔버스를 사용하여 간단한 입자 엔진 코드 예제 구현

HTML5 캔버스를 사용하여 간단한 입자 엔진 코드 예제 구현

黄舟
黄舟원래의
2017-03-22 15:26:372192검색

머리말

글쎄, '입자 엔진'이라고 부르는 것은 아직 거창한 표현이자 약간의 제목이고, 아직 실제 입자 엔진과는 거리가 좀 있습니다. 더 이상 고민하지 말고 데모

를 살펴보겠습니다. 이 기사에서는 간단한 캔버스파티클maker(이하 엔진이라고 함)를 만드는 방법을 설명합니다. ).

월드뷰

이 간단한 엔진에는 월드, 런처, 그레인 세 가지 요소가 필요합니다. 요약하자면, 이미터는 월드에 존재하며, 이미터는 파티클을 생성합니다. 월드와 이미터는 각 파티클이 월드와 이미터의 영향을 받은 후 상태에 영향을 미칩니다. 다음 계산이 계산됩니다. 순간의 위치에 자신을 그려보세요.

세계

소위 "세계"는 이 "세계"에 존재하는 입자에 전체적으로 영향을 미치는 환경입니다. 입자가 이 "세계"에 존재하기로 선택하면 이 입자는 이 "세계"의 영향을 받게 됩니다.

런처

입자를 방출하는 데 사용되는 장치입니다. 생성된 입자의 다양한 속성을 제어할 수 있습니다. 파티클의 부모로서 이미터는 파티클의 생성 속성(생성 위치, 생성 크기, 수명, "월드"의 영향을 받는지 여부, "런처" 자체의 영향을 받는지 등)을 제어할 수 있습니다.

또한 이미터 자체가 생성한 죽은 입자를 청소해야 합니다.

입자(곡물)

가장 작은 기본 단위는 모든 격동하는 개체입니다. 개체마다 위치, 크기, 수명, 동일한 이름의 영향을 받는지 여부 등이 있어 그 형태가 항상 캔버스에 정확하게 나타날 수 있습니다.

입자 그리기의 주요 논리

HTML5 캔버스를 사용하여 간단한 입자 엔진 코드 예제 구현

위가 입자 그리기의 주요 논리입니다.

먼저 세상에 필요한 것이 무엇인지 살펴보겠습니다.

세상을 창조하다

왜 세상은 중력가속도가 있어야 한다고 자연스럽게 생각했는지 모르겠습니다. 하지만 중력 가속도만으로는 많은 트릭을 보여줄 수 없으므로 여기에 바람이라는 두 가지 다른 영향 요소를 추가했습니다. 중력 가속도와 열의 방향은 수직이고, 바람의 영향 방향은 수평입니다. 이 세 가지를 사용하면 입자를 매우 요염하게 움직일 수 있습니다.

일부 상태(예: 입자의 생존)를 유지하려면 시간 표시가 필요하므로 나중에 시간 정지 및 역방향 흐름 효과를 촉진할 수 있도록 세계에 시간을 추가해 보겠습니다.

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

우리 모두 알다시피 애니메이션을 그리는 것은 끊임없이 다시 그리는 것을 의미하므로 외부 루프 호출을 위한 메서드를 노출해야 합니다.

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

이 timeTick 메서드가 외부 루프에 의해 호출될 때마다 매번 다음 작업을 수행하세요.

  1. 세계 상태 업데이트

  2. 캔버스를 지우고 배경을 다시 그리기

  3. 전 세계의 모든 이미터를 폴링하여 상태를 업데이트하고, 새로운 파티클을 생성하고, 파티클을 그립니다.

그럼, 업데이트해야 할 월드의 상태는 정확히 무엇인가요?

물론 매번 시간을 조금씩 앞당긴다고 생각하기 쉽습니다. 둘째, 입자를 최대한 교묘하게 움직이게 하기 위해 바람과 열의 상태를 불안정하게 유지합니다. 모든 돌풍과 모든 돌풍은 당신이 알지 못하는 것입니다~

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

세계가 창조되었으니 입자 방사체를 만들 수 있는 세계를 만들어야 하는데 그렇지 않으면 어떻게 입자를 만들 수 있겠는가~

아아아아

좋아, 신으로서 우리는 세계를 거의 건설했고 다음 단계는 다양한 입자를 제작하는 것입니다. 일종의 입자.

첫 번째 생물을 잡아라: 방사체

이미터는 세계 최초의 생물이다. 이미터는 모든 종류의 이상한 입자를 재현하는 데 사용됩니다. 그렇다면 송신기에는 어떤 특성이 필요합니까?

우선 그것이 어느 세계에 속하는지 파악해야 합니다(이 세계는 하나 이상의 세계일 수 있기 때문입니다).

두 번째는 송신기 자체의 상태입니다. 자체 시스템 내의 위치, 바람, 열은 송신기가 세계 속의 작은 세계라고 할 수 있습니다.

마지막으로 그의 '유전자'에 대해 설명하겠습니다. 방출자의 유전자가 자손(입자)에 영향을 미칩니다. 우리가 전달자에게 더 많은 "유전자"를 제공할수록 그들의 자손은 더 많은 생물학적 특성을 갖게 됩니다. 자세한 내용은 아래 양심댓글코드를 참고하세요~

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

아이를 낳는 것은 송신기가 담당하고, 출산 방법은

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;

});

아이를 낳은 후 아이야, 아이가 죽으면 청소를 해야지... (기억력이 부족해서 아쉽다)

으으으

아이를 낳고 나면 놀아줘야지:

으으으으

자신만의 작은 내부 세계를 유지하는 것을 잊지 마세요. 응~ (밖의 큰 세계와 거의 동일합니다)

으으으

자, 지금까지 세계 최초의 생명체 생성을 완료했습니다. 다음 단계는 그들의 후손입니다 (후후, 신은 너무 피곤합니다)

후손과 손자, 끝이 없습니다

나오세요 꼬마 여러분이 세상의 주인공입니다!

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

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

嗟乎。

위 내용은 HTML5 캔버스를 사용하여 간단한 입자 엔진 코드 예제 구현의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.