自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

【騰訊Bugly干貨】canvas粒子引擎手把手教學(xué),教你驚艷領(lǐng)導(dǎo)和用戶

移動(dòng)開發(fā)
好吧,說是“粒子引擎”還是大言不慚而標(biāo)題黨了,離真正的粒子引擎還有點(diǎn)遠(yuǎn)。廢話少說,先看[demo],掃描后點(diǎn)擊屏幕有驚喜哦...

 

[[165460]]

前言

好吧,說是“粒子引擎”還是大言不慚而標(biāo)題黨了,離真正的粒子引擎還有點(diǎn)遠(yuǎn)。廢話少說,先看[demo],掃描后點(diǎn)擊屏幕有驚喜哦... 

本文將教會(huì)你做一個(gè)簡(jiǎn)單的canvas粒子制造器(下稱引擎)。

世界觀

這個(gè)簡(jiǎn)單的引擎里需要有三種元素:世界(World)、發(fā)射器(Launcher)、粒子(Grain)。總得來說就是:發(fā)射器存在于世界之中,發(fā)射器制造粒子,世界和發(fā)射器都會(huì)影響粒子的狀態(tài),每個(gè)粒子在經(jīng)過世界和發(fā)射器的影響之后,計(jì)算出下一刻的位置,把自己畫出來。

世界(World)

所謂“世界”,就是全局影響那些存在于這這個(gè)“世界”的粒子的環(huán)境。一個(gè)粒子如果選擇存在于這個(gè)“世界”里,那么這個(gè)粒子將會(huì)受到這個(gè)“世界”的影響。

發(fā)射器(Launcher)

用來發(fā)射粒子的單位。他們能控制粒子生成的粒子的各種屬性。作為粒子們的爹媽,發(fā)射器能夠控制粒子的出生屬性:出生的位置、出生的大小、壽命、是否受到“World”的影響、是否受到"Launcher"本身的影響等等……

除此之外,發(fā)射器本身還要把自己生出來的已經(jīng)死去的粒子清掃掉。

粒子(Grain)

最小基本單位,就是每一個(gè)騷動(dòng)的個(gè)體。每一個(gè)個(gè)體都擁有自己的位置、大小、壽命、是否受到同名度的影響等屬性,這樣才能在canvas上每時(shí)每刻準(zhǔn)確描繪出他們的形態(tài)。

粒子繪制主邏輯

上面就是粒子繪制的主要邏輯。

我們先來看看世界需要什么。

創(chuàng)造一個(gè)世界

不知道為什么我理所當(dāng)然得會(huì)想到世界應(yīng)該有重力加速度。但是光有重力加速度不能表現(xiàn)出很多花樣,于是這里我給他增加了另外兩種影響因素:熱氣和風(fēng)。重力加速度和熱氣他們的方向是垂直的,風(fēng)影響方向是水平的,有了這三個(gè)東西,我們就能讓粒子動(dòng)得很風(fēng)騷了。

一些狀態(tài)(比如粒子的存亡)的維護(hù)需要有時(shí)間標(biāo)志,那么我們把時(shí)間也加入到世界里吧,這樣方便后期做時(shí)間暫停、逆流的效果。

  1. define(function(require, exports, module) { 
  2.    var Util = require('./Util'); 
  3.    var Launcher = require('./Launcher'); 
  4.  
  5.    /** 
  6.     * 世界構(gòu)造函數(shù) 
  7.     * @param config 
  8.     *          backgroundImage     背景圖片 
  9.     *          canvas              canvas引用 
  10.     *          context             canvas的context 
  11.     * 
  12.     *          time                世界時(shí)間 
  13.     * 
  14.     *          gravity             重力加速度 
  15.     * 
  16.     *          heat                熱力 
  17.     *          heatEnable          熱力開關(guān) 
  18.     *          minHeat             隨機(jī)最小熱力 
  19.     *          maxHeat             隨機(jī)最大熱力 
  20.     * 
  21.     *          wind                風(fēng)力 
  22.     *          windEnable          風(fēng)力開關(guān) 
  23.     *          minWind             隨機(jī)最小風(fēng)力 
  24.     *          maxWind             隨機(jī)最大風(fēng)力 
  25.     * 
  26.     *          timeProgress        時(shí)間進(jìn)步單位,用于控制時(shí)間速度 
  27.     *          launchers           屬于這個(gè)世界的發(fā)射器隊(duì)列 
  28.     * @constructor 
  29.     */ 
  30.     function World(config){ 
  31.     //太長了,略去細(xì)節(jié) 
  32.     } 
  33.     World.prototype.updateStatus = function(){}; 
  34.     World.prototype.timeTick = function(){}; 
  35.     World.prototype.createLauncher = function(config){}; 
  36.     World.prototype.drawBackground = function(){}; 
  37.     module.exports = World
  38.  }); 

大家都知道,畫動(dòng)畫就是不斷得重畫,所以我們需要暴露出一個(gè)方法,提供給外部循環(huán)調(diào)用:

  1. /** 
  2.   * 循環(huán)觸發(fā)函數(shù) 
  3.   * 在滿足條件的時(shí)候觸發(fā) 
  4.   * 比如RequestAnimationFrame回調(diào),或者setTimeout回調(diào)之后循環(huán)觸發(fā)的 
  5.   * 用于維持World的生命 
  6.   */ 
  7.  
  8. World.prototype.timeTick = function(){ 
  9.  
  10.     //更新世界各種狀態(tài) 
  11.     this.updateStatus(); 
  12.  
  13.     this.context.clearRect(0,0,this.canvas.width,this.canvas.height); 
  14.     this.drawBackground(); 
  15.  
  16.     //觸發(fā)所有發(fā)射器的循環(huán)調(diào)用函數(shù) 
  17.     for(var i = 0;i<this.launchers.length;i++){ 
  18.        this.launchers[i].updateLauncherStatus(); 
  19.        this.launchers[i].createGrain(1); 
  20.        this.launchers[i].paintGrain(); 
  21.     } 
  22.  }; 

這個(gè) timeTick 方法在外部循環(huán)調(diào)用時(shí),每次都做著這幾件事:

  1. 更新世界狀態(tài)
  2. 清空畫布重新繪制背景
  3. 輪詢?nèi)澜缢邪l(fā)射器,并更新它們的狀態(tài),創(chuàng)建新的粒子,繪制粒子

那么,世界的狀態(tài)到底有哪些要更新?

顯然,每一次都要讓時(shí)間往前增加一點(diǎn)是容易想到的。其次,為了讓粒子盡可能動(dòng)得風(fēng)騷,我們讓風(fēng)和熱力的狀態(tài)都保持不穩(wěn)定——每一陣風(fēng)和每一陣熱浪,都是你意識(shí)不到的~

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

世界造出來了,我們還得讓世界能造粒子發(fā)射器呀,要不然怎么造粒子呢~

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

好了,做為上帝,我們已經(jīng)把世界打造得差不多了,接下來就是捏造各種各樣的生靈了。

捏出第一個(gè)生物:發(fā)射器

發(fā)射器是世界上的第一種生物,依靠發(fā)射器才能繁衍出千奇百怪的粒子。那么發(fā)射器需要具備什么特征呢?

首先,它是屬于哪個(gè)世界的得搞清楚(因?yàn)檫@個(gè)世界可能不止一個(gè)世界)。

其次,就是發(fā)射器本身的狀態(tài):位置、自身體系內(nèi)的風(fēng)力、熱力,可以說:發(fā)射器就是一個(gè)世界里的小世界。

最后就是描述一下他的“基因”了,發(fā)射器的基因會(huì)影響到他們的后代(粒子)。我們賦予發(fā)射器越多的“基因”,那么他們的后代就會(huì)有更多的生物特征。具體看下面的良心注釋代碼吧~

  1. define(function (require, exports, module) { 
  2.    var Util = require('./Util'); 
  3.    var Grain = require('./Grain'); 
  4.  
  5.    /** 
  6.     * 發(fā)射器構(gòu)造函數(shù) 
  7.     * @param config 
  8.     *          id              身份標(biāo)識(shí)用于后續(xù)可視化編輯器的維護(hù) 
  9.     *          world           這個(gè)launcher的宿主 
  10.     * 
  11.     *          grainImage      粒子圖片 
  12.     *          grainList       粒子隊(duì)列 
  13.     *          grainLife       產(chǎn)生的粒子的生命 
  14.     *          grainLifeRange  粒子生命波動(dòng)范圍 
  15.     *          maxAliveCount   最大存活粒子數(shù)量 
  16.     * 
  17.     *          x               發(fā)射器位置x 
  18.     *          y               發(fā)射器位置y 
  19.     *          rangeX          發(fā)射器位置x波動(dòng)范圍 
  20.     *          rangeY          發(fā)射器位置y波動(dòng)范圍 
  21.     * 
  22.     *          sizeX           粒子橫向大小 
  23.     *          sizeY           粒子縱向大小 
  24.     *          sizeRange       粒子大小波動(dòng)范圍 
  25.     * 
  26.     *          mass            粒子質(zhì)量(暫時(shí)沒什么用) 
  27.     *          massRange       粒子質(zhì)量波動(dòng)范圍 
  28.     * 
  29.     *          heat            發(fā)射器自身體系的熱氣 
  30.     *          heatEnable      發(fā)射器自身體系的熱氣生效開關(guān) 
  31.     *          minHeat         隨機(jī)熱氣最小值 
  32.     *          maxHeat         隨機(jī)熱氣最小值 
  33.     * 
  34.     *          wind            發(fā)射器自身體系的風(fēng)力 
  35.     *          windEnable      發(fā)射器自身體系的風(fēng)力生效開關(guān) 
  36.     *          minWind         隨機(jī)風(fēng)力最小值 
  37.     *          maxWind         隨機(jī)風(fēng)力最小值 
  38.     * 
  39.     *          grainInfluencedByWorldWind      粒子受到世界風(fēng)力影響開關(guān) 
  40.     *          grainInfluencedByWorldHeat      粒子受到世界熱氣影響開關(guān) 
  41.     *          grainInfluencedByWorldGravity   粒子受到世界重力影響開關(guān) 
  42.     * 
  43.     *          grainInfluencedByLauncherWind   粒子受到發(fā)射器風(fēng)力影響開關(guān) 
  44.     *          grainInfluencedByLauncherHeat   粒子受到發(fā)射器熱氣影響開關(guān) 
  45.     * 
  46.     * @constructor 
  47.     */ 
  48.  
  49.    function Launcher(config) { 
  50.        //太長了,略去細(xì)節(jié) 
  51.    } 
  52.  
  53.    Launcher.prototype.updateLauncherStatus = function () {}; 
  54.    Launcher.prototype.swipeDeadGrain = function (grain_id) {}; 
  55.    Launcher.prototype.createGrain = function (count) {}; 
  56.    Launcher.prototype.paintGrain = function () {}; 
  57.  
  58.    module.exports = Launcher
  59.  
  60. }); 

發(fā)射器要負(fù)責(zé)生孩子啊,怎么生呢:

  1. Launcher.prototype.createGrain = function (count) { 
  2.        if (count + this.grainList.length <= this.maxAliveCount) { 
  3.            //新建了count個(gè)加上舊的還沒達(dá)到最大數(shù)額限制 
  4.        } else if (this.grainList.length >= this.maxAliveCount && 
  5.            count + this.grainList.length > this.maxAliveCount) { 
  6.            //光是舊的粒子數(shù)量還沒能達(dá)到最大限制 
  7.            //新建了count個(gè)加上舊的超過了最大數(shù)額限制 
  8.            count = this.maxAliveCount - this.grainList.length; 
  9.        } else { 
  10.            count = 0
  11.        } 
  12.        for (var i = 0; i < count; i++) { 
  13.            var _rd = Util.randomFloat(0, Math.PI * 2); 
  14.            var _grain = new Grain({/*粒子配置*/}); 
  15.            this.grainList.push(_grain); 
  16.        } 
  17.    }; 

生完孩子,孩子死掉了還得打掃……(好悲傷,怪內(nèi)存不夠用咯)

  1. Launcher.prototype.swipeDeadGrain = function (grain_id) { 
  2.     for (var i = 0; i < this.grainList.length; i++) { 
  3.         if (grain_id == this.grainList[i].id) { 
  4.             thisthis.grainList = this.grainList.remove(i);//remove是自己定義的一個(gè)Array方法 
  5.             this.createGrain(1); 
  6.             break; 
  7.         } 
  8.     } 
  9. }; 

生完孩子,還得把孩子放出來玩:

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

自己的內(nèi)部小世界也不要忘了維護(hù)呀~(跟外面的大世界差不多)

  1. Launcher.prototype.updateLauncherStatus = function () { 
  2.     if (this.grainInfluencedByLauncherWind) { 
  3.         this.wind = Util.randomFloat(this.minWind, this.maxWind); 
  4.     } 
  5.     if(this.grainInfluencedByLauncherHeat){ 
  6.         this.heat = Util.randomFloat(this.minHeat, this.maxHeat); 
  7.     } 
  8. }; 

好了,至此,我們完成了世界上第一種生物的打造,接下來就是他們的后代了(呼呼,上帝好累)

子子孫孫,無窮盡也

出來吧,小的們,你們才是世界的主角!

作為世界的主角,粒子們擁有各種自身的狀態(tài):位置、速度、大小、壽命長度、出生時(shí)間當(dāng)然必不可少

  1. define(function (require, exports, module) { 
  2.     var Util = require('./Util'); 
  3.  
  4.     /** 
  5.      * 粒子構(gòu)造函數(shù) 
  6.      * @param config 
  7.      *          id              唯一標(biāo)識(shí) 
  8.      *          world           世界宿主 
  9.      *          launcher        發(fā)射器宿主 
  10.      * 
  11.      *          x               位置x 
  12.      *          y               位置y 
  13.      *          vx              水平速度 
  14.      *          vy              垂直速度 
  15.      * 
  16.      *          sizeX           橫向大小 
  17.      *          sizeY           縱向大小 
  18.      * 
  19.      *          mass            質(zhì)量 
  20.      *          life            生命長度 
  21.      *          birthTime       出生時(shí)間 
  22.      * 
  23.      *          color_r 
  24.      *          color_g 
  25.      *          color_b 
  26.      *          alpha           透明度 
  27.      *          initAlpha       初始化時(shí)的透明度 
  28.      * 
  29.      *          influencedByWorldWind 
  30.      *          influencedByWorldHeat 
  31.      *          influencedByWorldGravity 
  32.      *          influencedByLauncherWind 
  33.      *          influencedByLauncherHeat 
  34.      * 
  35.      * @constructor 
  36.      */ 
  37.     function Grain(config) { 
  38.         //太長了,略去細(xì)節(jié) 
  39.     } 
  40.  
  41.     Grain.prototype.isDead = function () {}; 
  42.     Grain.prototype.calculate = function () {}; 
  43.     Grain.prototype.paint = function () {}; 
  44.     module.exports = Grain
  45. }); 

粒子們需要知道自己的下一刻是怎樣子的,這樣才能把自己在世界展現(xiàn)出來。對(duì)于運(yùn)動(dòng)狀態(tài),當(dāng)然都是初中物理的知識(shí)了:-)

  1. Grain.prototype.calculate = function () { 
  2.     //計(jì)算位置 
  3.     if (this.influencedByWorldGravity) { 
  4.         this.vy += this.world.gravity+Util.randomFloat(0,0.3*this.world.gravity); 
  5.     } 
  6.     if (this.influencedByWorldHeat && this.world.heatEnable) { 
  7.         this.vy -this.world.heat+Util.randomFloat(0,0.3*this.world.heat); 
  8.     } 
  9.     if (this.influencedByLauncherHeat && this.launcher.heatEnable) { 
  10.         this.vy -this.launcher.heat+Util.randomFloat(0,0.3*this.launcher.heat); 
  11.      } 
  12.      if (this.influencedByWorldWind && this.world.windEnable) { 
  13.          this.vx += this.world.wind+Util.randomFloat(0,0.3*this.world.wind); 
  14.      } 
  15.      if (this.influencedByLauncherWind && this.launcher.windEnable) { 
  16.         this.vx += this.launcher.wind+Util.randomFloat(0,0.3*this.launcher.wind); 
  17.     } 
  18.     this.y += this.vy; 
  19.     this.x += this.vx; 
  20.     thisthis.alpha = this.initAlpha * (1 - (this.world.time - this.birthTime) / this.life); 
  21.  
  22.     //TODO 計(jì)算顏色 和 其他 
  23.  
  24. }; 

粒子們?cè)趺粗雷约核懒藳]?

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

粒子們又該以怎樣的姿態(tài)把自己展現(xiàn)出來?

  1. Grain.prototype.paint = function () { 
  2.     if (this.isDead()) { 
  3.         this.launcher.swipeDeadGrain(this.id); 
  4.     } else { 
  5.         this.calculate(); 
  6.         this.world.context.save(); 
  7.         this.world.context.globalCompositeOperation = 'lighter'
  8.         thisthis.world.context.globalAlpha = this.alpha; 
  9.         this.world.context.drawImage(this.launcher.grainImage, this.x-(this.sizeX)/2, this.y-(this.sizeY)/2, this.sizeX, this.sizeY); 
  10.         this.world.context.restore(); 
  11.     } 
  12. }; 

嗟乎。

后續(xù)

后續(xù)希望能夠通過這個(gè)雛形,進(jìn)行擴(kuò)展,再造一個(gè)可視化編輯器供大家使用。

對(duì)了,代碼都在這:https://github.com/jation/CanvasGrain

 

責(zé)任編輯:倪明 來源: 騰訊Bugly
相關(guān)推薦

2016-04-27 09:49:16

用戶模型產(chǎn)品總結(jié)

2017-12-01 05:01:35

WiFi干擾無線網(wǎng)絡(luò)

2011-01-10 14:41:26

2011-05-03 15:59:00

黑盒打印機(jī)

2021-07-14 09:00:00

JavaFX開發(fā)應(yīng)用

2021-05-10 06:48:11

Python騰訊招聘

2023-06-05 13:07:38

2021-02-26 11:54:38

MyBatis 插件接口

2011-02-22 13:46:27

微軟SQL.NET

2021-12-28 08:38:26

Linux 中斷喚醒系統(tǒng)Linux 系統(tǒng)

2022-01-08 20:04:20

攔截系統(tǒng)調(diào)用

2023-04-26 12:46:43

DockerSpringKubernetes

2022-12-07 08:42:35

2022-07-27 08:16:22

搜索引擎Lucene

2022-03-14 14:47:21

HarmonyOS操作系統(tǒng)鴻蒙

2022-01-29 21:54:58

電商用戶數(shù)據(jù)

2011-02-22 14:36:40

ASP.NETmsdnC#

2020-10-28 14:03:22

NLP自然語言分詞

2020-04-14 10:20:12

MySQL數(shù)據(jù)庫死鎖

2021-08-04 08:55:02

Socket Java開發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)