最新文章专题视频专题问答1问答10问答100问答1000问答2000关键字专题1关键字专题50关键字专题500关键字专题1500TAG最新视频文章推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37视频文章20视频文章30视频文章40视频文章50视频文章60 视频文章70视频文章80视频文章90视频文章100视频文章120视频文章140 视频2关键字专题关键字专题tag2tag3文章专题文章专题2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章专题3
当前位置: 首页 - 科技 - 知识百科 - 正文

使用HTML5canvas实现一个简单的粒子引擎代码实例

来源:动视网 责编:小采 时间:2020-11-27 15:10:54
文档

使用HTML5canvas实现一个简单的粒子引擎代码实例

使用HTML5canvas实现一个简单的粒子引擎代码实例:前言好吧,说是粒子引擎还是大言不惭而标题党了,离真正的粒子引擎还有点远。废话少说,先看demo本文将教会你做一个简单的canvas粒子制造器(下称引擎)。世界观这个简单的引擎里需要有三种元素:世界(World)、发射器(Launcher)、粒子(Grain)。总
推荐度:
导读使用HTML5canvas实现一个简单的粒子引擎代码实例:前言好吧,说是粒子引擎还是大言不惭而标题党了,离真正的粒子引擎还有点远。废话少说,先看demo本文将教会你做一个简单的canvas粒子制造器(下称引擎)。世界观这个简单的引擎里需要有三种元素:世界(World)、发射器(Launcher)、粒子(Grain)。总


前言

好吧,说是“粒子引擎”还是大言不惭而标题党了,离真正的粒子引擎还有点远。废话少说,先看demo

本文将教会你做一个简单的canvas粒子制造器(下称引擎)。

世界观

这个简单的引擎里需要有三种元素:世界(World)、发射器(Launcher)、粒子(Grain)。总得来说就是:发射器存在于世界之中,发射器制造粒子,世界和发射器都会影响粒子的状态,每个粒子在经过世界和发射器的影响之后,计算出下一刻的位置,把自己画出来。

世界(World)

所谓“世界”,就是全局影响那些存在于这这个“世界”的粒子的环境。一个粒子如果选择存在于这个“世界”里,那么这个粒子将会受到这个“世界”的影响。

发射器(Launcher)

用来发射粒子的单位。他们能控制粒子生成的粒子的各种属性。作为粒子们的爹妈,发射器能够控制粒子的出生属性:出生的位置、出生的大小、寿命、是否受到“World”的影响、是否受到"Launcher"本身的影响等等……

除此之外,发射器本身还要把自己生出来的已经死去的粒子清扫掉。

粒子(Grain)

最小基本单位,就是每一个骚动的个体。每一个个体都拥有自己的位置、大小、寿命、是否受到同名度的影响等属性,这样才能在canvas上每时每刻准确描绘出他们的形态。

粒子绘制主逻辑

上面就是粒子绘制的主要逻辑。

我们先来看看世界需要什么。

创造一个世界

不知道为什么我理所当然得会想到世界应该有重力加速度。但是光有重力加速度不能表现出很多花样,于是这里我给他增加了另外两种影响因素:热气和风。重力加速度和热气他们的方向是垂直的,风影响方向是水平的,有了这三个东西,我们就能让粒子动得很风骚了。

一些状态(比如粒子的存亡)的维护需要有时间标志,那么我们把时间也加入到世界里吧,这样方便后期做时间暂停、逆流的效果。

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('./Util');
 var Grain = require('./Grain');

 /**
 * 发射器构造函数
 * @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;

});

发射器要负责生孩子啊,怎么生呢:

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

生完孩子,孩子死掉了还得打扫……(好悲伤,怪内存不够用咯)

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

生完孩子,还得把孩子放出来玩:

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

自己的内部小世界也不要忘了维护呀~(跟外面的大世界差不多)

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

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

子子孙孙,无穷尽也

出来吧,小的们,你们才是世界的主角!

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

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

 /**
 * 粒子构造函数
 * @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 = '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();
 }
};

嗟乎。

文档

使用HTML5canvas实现一个简单的粒子引擎代码实例

使用HTML5canvas实现一个简单的粒子引擎代码实例:前言好吧,说是粒子引擎还是大言不惭而标题党了,离真正的粒子引擎还有点远。废话少说,先看demo本文将教会你做一个简单的canvas粒子制造器(下称引擎)。世界观这个简单的引擎里需要有三种元素:世界(World)、发射器(Launcher)、粒子(Grain)。总
推荐度:
标签: 实现 简单的 代码
  • 热门焦点

最新推荐

猜你喜欢

热门推荐

专题
Top