本篇文章将应用动效工厂从0-1去搭建并生成各种各样的粒子动画, 利用可视化编辑的能力,而你通过繁难的操作就能够实现, 上面我就带你一步一步如何去实现一个粒子Action ,精确的来说去依据一个业务实现一个自定义的action。
读完本篇文章你能够理解到上面这几点:
- 如何实现一个自定义action
- 如何应用动效工厂
动效工厂
在实现自定义action 之前, 我先简略的介绍下咱们这个我的项目的背景。什么是动效工厂?
咱们团队用的动效场景比拟多,所以积淀了一些动效,同时为了下次在遇到能够更好的复用。于是咱们能反对多少动效,或者有哪些动效能够被复用,就能够在动效工厂的动效库里去找。
这时候就会有同学说这些货色都不满足需要、不通用,于是就有了接下来要讲的的自定义action,也就是实现一个动效库没有的动画,而后再提交到动效库里,前面有相似的动效,他人也就能够复用了。
粒子动画
这里的话我带大家实现一个粒子动画, 首先还是解释什么是粒子动画????
粒子动画是由在肯定范畴内随机生成的大量粒子产生静止而组成的动画,被宽泛使用于模仿天气零碎、烟雾光效等方面。在电商平台的微型游戏化场景中,粒子动画次要 用于出现在能量收集、金币收集时的特效。
这里我给大家筹备了两段咱们业务场景中应用的例子:
下面的粒子还是不够多,并且给粒子 贴上对应的贴图,再以某种动画的去展现,从而达到视觉上感觉十分爽的状态,就是有一种好多的感觉!
创立粒子类
其实无论什么样的粒子成果,你光看成果你都不晓得如何剖析这一个动画。其实剖析动画,咱们能够先看一个粒子是怎么变动的。咱们看上面这张图:
粒子A在画布上,从A地位通过肯定的动画 可能是线性变动, 也有可能是抛物线,或者是咱们的三阶贝塞尔曲线变动。 然而万变不离其宗, 实质上都x 和 y 的变动, 然而x 从 x0 运行到 x1 , y方向 从y0 静止 y1, 那么其实对应以后这个一个粒子来说 也就是须要有vx 和vy 这两个属性。这样一个粒子最根本的属性 其实对于根本的动画曾经满足了。 咱们写上面这段代码:
class Particle { constructor(x = 0, y = 0) { this.x = x this.y = x this.radius = 1 this.vx = 0 this.vy = 0 this.color = 'hsla(197,100%,50%,80%)' } draw(ctx) { ctx.save() ctx.fillStyle = this.color ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); ctx.restore() } move() { this.x += this.vx this.y += this.vy } }
这样其实咱们就能结构出以后粒子 所须要的个性。然而这时候咱们还是须要一个粒子manager去治理所有的粒子,次要是用来
- 管制粒子的数量
- 生成多个类型的粒子
- 执行渲染操作
- 管制粒子的 x 方向 速度 和 y方向的速度 在哪一个区间
其实这些就是所谓的对立治理,代码就不展现了。其实下面所谓的这些操作 就是形成了粒子action 的一些配置
ACTION编写
咱们关上到这个目录
这外面有咱们动效工厂 所有的action,比方我这里其实就是新建一个 particle-action.ts
这里先看下 一个action最根本的构造是如何实现的。
就是actionConfig 这里的话 其实 就是对应到动效工厂 可视化视图的哪些属性,咱们会有一些默认属性, 也就是每个action 固定的一些属性,然而同时也会有放弃非凡的状况。而后你在导出一个Action 函数
咱们看下这里是如何写的,这里其实都是固定写法。然而我还是想讲的明确点:
首先action必定是作用在视图上的, 在动效工厂里其实就是 对应的View
而后就是一些缓动 动画, 的一些参数,咱们看下代码
export const particleAction: Action<particleActionConfig> = ( view: View, { timing, color, radius, duration, ...config }, ) => { let ctx: CanvasRenderingContext2D | null return (time: number) => { const progress = useProgress(time, timing, duration, config) useOnce(() => { ctx = view.makeCanvas().getContext('2d') }) if (ctx) { view.addLayer(ctx.canvas) } } }
这里用到了 2个 独立的钩子
useOnce 和 useProgress
这里要不得不提了,为啥要用钩子, 最次要的目标是不便action在调用的时候和frame联动(这里借鉴了React hooks 的思维)
设置或者获取以后frame的duration, 我简略给大家介绍几个
useCache
缓存变量,放弃action的执行过程
useState
保留和批改action的状态
useOnce
在Frame的整个生命周期中只会被执行一次。
useFinish
在Frame被移除的时候执行
useContext
获取frame的以后的wind。
useProgress
获取以后的进度是多少
useOnce
其实有点相似于单例,咱们只创立一次,因为下面的函数是每一帧都进行了调用, 所以为了防止反复创立所存在的问题
我这里简略的说一下,这行代码
ctx = view.makeCanvas().getContext('2d')
其实view 只是咱们形象进去的货色,无论你做什么操作,也就是渲染画面, 都是依赖canvas。 所以view 上都makeCanvas 实例的办法,前面所有的动作,都在这个canvas 下来渲染, 从而造成动画。
2d 其实是一种最简略 也是最罕用的办法, 然而canvas 不仅仅只有 2d 还有webgl 和 webgl2。
其实也就是说白了 咱们这个货色前面扩大 3d 能力 是OK的 ,其实我也做了一些尝试, 然而因为业务场景 不是特地多,也是本人展现。
我这里能够给大家分享一下我用咱们动效工厂 3D 做出的动画。
好了言归正传咱们持续写咱们的粒子action
咱们看下这张图:
一个粒子在固定的view 挪动,如果挪动边界,其实也就是这时候速度该怎么变动呢,其实也就是所谓的碰撞检测
我这里其实就是有一个弹力系数, 当粒子静止到边界 也就是粒子的vx 方向 和 vy 方向 变成相同方向,如果没有开启碰撞检测的话, 其实就是去扭转 粒子的地位, 设置粒子的 x 和 y 值 在对应的地位。看上面这张图
我这只是一个方向的。 代码如下:
// 碰撞检测
checkWall(isWallCollisionTest = true, bounce = 1, W = 1000, H = 1000) { if (isWallCollisionTest) { if (this.x + this.radius > W) { this.x = W - this.radius; this.vx *= -bounce; } else if (this.x - this.radius < 0) { this.x = this.radius; this.vx *= -bounce; } if (this.y + this.radius > H) { this.y = H - this.radius; this.vy *= -bounce; } else if (this.y - this.radius < 0) { this.y = this.radius; this.vy *= -bounce; } } else { if (this.x - this.radius > W) { this.x = 0; } else if (this.x + this.radius < 0) { this.x = W; } if (this.y - this.radius > H) { this.y = 0; } else if (this.y + this.radius < 0) { this.y = H; } } }
咱们我设置100 的粒子 咱们看下 碰撞检测的成果:
这里的卡顿是录制视频的时候卡顿和咱们其余没关系的。canvas 粒子数量不够多的时候是不会卡顿的。
文字粒子
而后咱们在以后的粒子上进行了裁减,裁减下文字粒子。这里其实简略说下,canvas整个画布其实就是像素点,咱们能够实现原理其实很简略,Canvas中有个getImageData的办法,能够失去一个矩形范畴所有像素点数据。那么咱们就试试来获取一个文字的形态吧。
data属性返回一个 Uint8ClampedArray,它能够被应用作为查看初始像素数据。每个像素用4个1bytes值(依照红,绿,蓝和通明值的程序; 这就是"RGBA"格局) 来代表。每个色彩值部份用0至255来代表。每个部份被调配到一个在数组内间断的索引,左上角像素的红色部份在数组的索引0地位。像素从左到右被解决,而后往下,遍历整个数组
我这里应用的画布大小是 1000 * 1000, 用坐标系来示意就是x轴1000,y轴1000
其实就是RGBA(255,255,255,0) 这四个相似的数字示意一个像素,那10001000这个画布用Uint8ClampedArray数组示意,总共由多少个元素呢? 就是1000 1000 * 4 个元素
第一步,用 measureText 的办法来计算出文字适当的尺寸和地位。
const canvas = document.createElement('canvas') const ctx = canvas.getContext('2d'); canvas.setAttribute('width', 1000); canvas.setAttribute('height', 1000); ctx.clearRect(0, 0, 1000, 1000) ctx.font = 'bold 10p Arial' const measure = ctx.measureText(this.text) const size = 0.15 const fSize = Math.min(1000 * size * 10 / 7, 1000 * size * 10 / measure.width); // 10像素字体行高 lineHeight=7 magic ctx.font = `${fSize}px Arial`; let left = (1000 - measure.width) / 2 - measure.width / 2; const bottom = (1000 + fSize / 10 * 7) / 2; ctx.fillText(this.text, left, bottom) const imageData = ctx.getImageData(0, 0, 1000, 1000).data for (let x = 0; x < 1000; x += 4) { for (let y = 0; y < 1000; y += 4) { const fontIndex = (x + y * 1000) * 4 + 3; if (imageData[fontIndex] > 0) { this.points.push({ x, y }) } } }
其实这里 咱们就取得像素, 取得文字所组成的像素点。而后咱们就能够渲染了, 这里咱们看下上面这个视频:
为什么用动效工厂??
这些你都不须要去从新写,你只有会用咱们的动效工厂、简略配置就能够生成下面的粒子动画。
在页面上配置
你能够在这个页面进行配置, 配置以后action对应的属性,而后这货色 和咱们的dsl 有关系, 这个属性的命名肯定要留神。 我会在前面文章进行一步一步分析。
剖析动画
上图文字粒子动画从动画的角度就是4个关键帧, 而后到一步就进行切换。 那其实对应到咱们动效工厂其实就是的时间轴是这个样子的
我第一个关键帧对应的动画参数,以及继续的工夫;而后第一帧完结完,播放第二幁。
那么这样对应的视图其实也就是 4个view ,因为视图和 action一一对应的, 或者是一对多的关系。
也就是根节点 咱们的root 上面有4个view如图:
而后咱们抉择任意视图, 点击+ 号 抉择 粒子弹框如下:
抉择粒子,而后底下进度条就会呈现、点击,就能够在动画属性编辑。
咱们设置文字的参数,文字粒子的大小。
如图:
其实前面几个都是同样的操作,惟一不同的就是展现的文字不同,还有一个就是提早action执行,第二个提早1000秒第三个提早2000以此类推。
当你进度条看下这个样子你就会明确了,高深莫测
而后点击播放按钮就能够预览动画了:
HOW TO USE
这个其实非常简单,点击左下角的下载按钮,会生成一个ts文件
这个ts文件就是包含以后视图以及资源view的各种信息,不过你不须要思考。
间接装置咱们的sdk
yarn install @wind/core
而后调用
import { play } from '@wind/core' play(animation(), {container: canvas 节点})
这样就能够应用了!
最初
大家有问题的能够在评论区交换哦!
文/Fly
关注得物技术,做最潮技术人!