基于PixiJs的下雨动画实现
1. 安装引入
在游戏官网或活动脚手架中使用lerna命令为某个项目安装pixi.js依赖:
lerna add pixi.js --scope=module-name
然后在项目中按需导入所要调用的类即可,例如:
import { Application, Sprite, Graphics } from 'pixi.js'
2. 实现过程
1.1 创建舞台对象
使用Application类创建根容器,并把根容器放到画布中显示。由于一般用作背景动画,因此将容器大小设置为与浏览器的文档显示区域等大。
// html
<canvas ref="rainCanvas" />
//js
RAIN_APP = new Application({
view: this.$refs.rainCanvas,
backgroundAlpha: 0.5,
width: window.innerWidth,
height: window.innerHeight,
antialias: true,
resolution: 1
})
1.2 设置动画循环
依赖canvas的动画需要不断循环执行loop函数,把图像绘制在画布上,以此实现动画。在pixi.js中可通过ticker.add方法,让application实例化对象将render函数添加到渲染队列中,此后每次执行loop是都会促发一个render函数的执行,以此实现画布以每秒60帧的速率进行刷新。
PIXI_APP.ticker.add(delta => gameLoop(delta))
1.3 随机生成雨滴
使用Sprite类每次批量生成若干个雨水精灵,使用Sprite.from方法加载图片作为纹理,并给每个雨水精灵都设置独特的位置、位移、缩放和旋转属性,最后使用stage.addChild方法将其逐一加入舞台中。
function createRainArr () {
const a = angle[0] + myAngle.get() * (angle[1] - angle[0]) / 100 //入场角度
const s = speed[0] + mySpeed.get() * (speed[1] - speed[0]) / 100 //入场风速
if (rainArr.length < maxNum) {
if (Math.random() < drop_chance) { //用随机值模拟降雨概率情况
for (let i = 0; i < numLevel; i++) {
let position_X = 0
const edge = Math.tan((270 - a) * eachAnger) * PIXI_APP.renderer.height
if (edge >= 0) {
position_X = Math.random() * (PIXI_APP.renderer.width + edge)
} else {
position_X = Math.random() * (PIXI_APP.renderer.width - edge) + edge
}
const drop = Sprite.from(require('../images/HardRain.png'))
drop.x = position_X //入场x坐标
drop.y = 0 //入场y坐标
drop.vx = s * Math.cos(a * eachAnger) //水平方向的位移分速度
drop.vy = -s * Math.sin(a * eachAnger) //垂直方向的位移分速度
drop.scale.set(0.2, 0.2)
drop.rotation = -a * eachAnger
rainArr.push(drop)
PIXI_APP.stage.addChild(drop)
}
}
}
}
1.4 雨滴下落轨迹绘制
每次动画循环时,对已在容器内的雨水精灵的坐标进行重新计算并绘制,并判断雨滴是否已经掉落至页面底部,若是则应当生成水花,使用stage.removeChild方法将该精灵移除舞台。与此同时,每次循环都应准备生成新的水滴下落。
function gameLoop (delta) {
for (let i = 0; i < rainArr.length; i++) {
updatePosition(rainArr[i])
if (rainArr[i].y > PIXI_APP.renderer.height) {
if (hasBounce) {
let n = Math.round(4 + Math.random() * 4)
while (n--) {
createBouncesArr(rainArr[i].x, PIXI_APP.renderer.height)
}
}
PIXI_APP.stage.removeChild(rainArr[i])
rainArr.splice(i, 1)
}
}
createRainArr()
...
}
function updatePosition (sprite) {
sprite.vy += gravity //加上重力影响
sprite.x += sprite.vx
sprite.y += sprite.vy
}
1.5 随机生成水花
使用Graphics类在画布上绘制一个个随机的圆点来模拟水花,使用stage.addChild方法将其逐一加入舞台中,每个水花粒子都有其独特的位置属性和位移属性。
function createBouncesArr (pos_x, pos_y) {
const bounce = new Graphics()
bounce.beginFill(0xFFFFFF)
bounce.drawCircle(pos_x, pos_y, 0.6 + Math.random() * 2.4)
bounce.endFill()
const dist = Math.random() * 7 //溅起距离
const angle = Math.PI + Math.random() * Math.PI //反弹角度
bounce.vx = Math.cos(angle) * dist * 0.3 //水平方向的位移分速度
bounce.vy = Math.sin(angle) * dist * 0.3 //垂直方向的位移分速度
bouncesArr.push(bounce)
PIXI_APP.stage.addChild(bounce)
}
1.6 水花反弹轨迹绘制
每次动画循环时,对已在容器内的水花粒子的坐标进行重新计算并绘制,并判断该水花是否已经再次掉落至页面底部,若是则应当使用stage.removeChild方法将该粒子移除舞台。
function gameLoop (delta) {
......
if (hasBounce) {
for (let i = 0; i < bouncesArr.length; i++) {
updatePosition(bouncesArr[i])
if (bouncesArr[i].y >= PIXI_APP.renderer.height) {
PIXI_APP.stage.removeChild(bouncesArr[i])
bouncesArr.splice(i, 1)
}
}
}
}
1.7 页面缩放自适应
在window.onresize事件中监听窗口变化,一方面使用resize方法重置舞台大小,另一方面重设外层画布的大小,均与文档区域大小保持一致。
window.onresize = () => {
this.SIZE = {
w: document.documentElement.clientWidth,
h: document.documentElement.clientHeight
}
RAIN_APP.resize(this.SIZE.w, this.SIZE.h)
this.$refs.rainCanvas.style.width = this.SIZE.w + 'px'
this.$refs.rainCanvas.style.height = this.SIZE.h + 'px'
}
3. 参数说明
为了方便其他业务复用,将上述实现代码整合放入了一个Js文件中,以插件的形式可供调用,提供的功能参数如下:
参数项 | 参数描述 | 默认值 |
---|---|---|
speed | 以数组形式传入最小和最大风速 | [10, 100] |
gravity | 雨滴垂直方向的下落速度(模拟重力) | 0.163 |
maxNum | 画布上的雨滴数量上限 | 1000 |
numLevel | 每次随机生成的雨滴数量 | 10 |
drop_chance | 降雨概率,判断每次是否会生成雨滴,可控制雨滴出现的频率 | 0.1 |
hasBounce | 雨滴落地是否有回弹效果 | true |
4. 实现效果
本文中尽可能真实的模拟了下雨过程,主要亮点包括:
- 雨水的下落方向和速度不是一成不变的,可随机在预设范围内波动;
- 雨水触底后模拟了水花四溅的效果。
页面效果如下图:
转载自:https://juejin.cn/post/7355779183148826651