有趣又逼真的水波交互动画

10,173 阅读2分钟

我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

介绍

本期将用 pixi.js 来完成一个水波交互动画,里面包括了水体扭曲和点击水波扩散的效果,主要利用了 pixi.js 库以及关于它的 filters 滤镜的使用。

演示

code.juejin.cn/pen/7142432…

正文

基础搭建

安装 pixi.js 库:

# NPM
npm i pixi.js -S
# Yarn 
yarn add pixi.js

引入并初始化:

import * as PIXI from "pixi.js"

const GAME_WIDTH = 1920;
const GAME_HEIGHT = 1080;

export default class Game {
    constructor(el) {
        return this.init(el)
    }
    init(el) {
        // 初始化
        this.app = new PIXI.Application({
            width: GAME_WIDTH,
            height: GAME_HEIGHT,
        });
        el.appendChild(this.app.view);
        this.loader = new PIXI.Loader();
        this.loader
            .add("bg", "assets/waterwave_0.jpg")
            .add("wave", "assets/waterwave_1.jpg")
            .load(this.render.bind(this))
        return this;
    }
    render(loader, resources) {
        // 渲染
    }
}

初始化就是创建 pixi.js 应用,然后把生成后的视图追加到要传入的 el 元素节点上。这里我们还要先加载两张图片,一张为水体背景,另一张为水体扭曲的错位图。加载完这两种图后,我们才会执行 render 方法进行渲染。

演示1.png

水体扭曲

import * as PIXI from "pixi.js"

const GAME_WIDTH = 1920;
const GAME_HEIGHT = 1080;

export default class Game {
    // ...
    render(loader, resources) {
        // 渲染
        this.resources = resources;
        const bg = PIXI.Sprite.from(resources.bg.texture);
        bg.anchor.set(0.5);
        bg.position.set(GAME_WIDTH / 2, GAME_HEIGHT / 2)
        bg.scale.set(.5, .5)

        this.container = new PIXI.Container();
        this.container.interactive = true;
        this.container.buttonMode = true;
        this.container.addChild(bg);
        this.app.stage.addChild(this.container);
    }
}

当然在做扭曲效果之前,我们先要把刚才加载好的水体背景绘制到界面上。

演示2.png

export default class Game {
    // ...
    render(loader, resources) {
        // ...
        this.container.filters = []
        this.displacementSprite = new PIXI.Sprite.from(resources.wave.texture)
        this.displacementSprite.texture.baseTexture.wrapMode = PIXI.WRAP_MODES.REPEAT;
        this.displacementSprite.scale.set(1);

        const displacementFilter = new PIXI.filters.DisplacementFilter(this.displacementSprite);
        this.container.addChild(this.displacementSprite);
        this.container.filters.push(displacementFilter)

        this.app.ticker.add(this.step.bind(this));
    }
    step() {
        const { displacementSprite } = this;
        displacementSprite.x += 1.7;
        if (displacementSprite.x >= displacementSprite.width) {
            displacementSprite.x = 0;
        }
    }
}

这里我们用了 pixi.js 内置的过滤器类 DisplacementFilter ,它的作用是做贴图置换,做纹理偏移。

先实例化一个 DisplacementFilter 类,把纹理错位图传入进去,然后把这个过滤器实例添加到 filters 数组里面,最后执行 tickerstep 方法里会连续不断的执行, 通过不停改变 x 轴坐标,来实现纹理偏移水体扭曲的效果。

演示3.gif

水波动画

我们还要再引入一个 pixi.js 的一个插件库 pixi-filters.js ,它可以处理很多的过滤器效果,水波就是其中一个。

import "./pixi-filters"
export default class Game {
    // ...
    render(loader, resources) {
        // ...
        this.shockwaveFilters = []
        this.container.on('pointerdown', (e) => {
            const { x, y } = e.data.global
            const shockwaveFilter = new PIXI.filters.ShockwaveFilter(
                [x, y],
                {
                    amplitude: 20 + 20 * Math.random(),  // 振幅
                    wavelength: 30 + 10 * Math.random(), // 波长
                    speed: 100 + 100 * Math.random(),    // 速度
                    radius: 80 * Math.random() + 120,    // 半径
                },
                0
            )
            this.shockwaveFilters.push(shockwaveFilter)
            this.container.filters.push(shockwaveFilter)
        });
    }
    step() {
        // ...
        const { shockwaveFilters } = this;
        for (let i = 0; i < shockwaveFilters.length; i++) {
            const filter = shockwaveFilters[i];
            filter.time += .008;
            if (filter.time >= 2) {
                shockwaveFilters.splice(i, 1)
                i--
            }
        }
    }
}

引入 pixi-filters.js 库后,我们就可以用它实例化一个 ShockwaveFilter 类来完成波纹效果,具体来说,就是在点击屏幕容器的时候,会实例化ShockwaveFilter 类,其中可以随机传入振幅、波长、速度、半径等参数,目的是为了让每次点击水波效果都有些许差异。再将这个过滤器添加一个单独的数组中保存,同时还要添加到 filters 中,在 step 方法不停执行的时候,我们就要遍历这个 shockwaveFilters 数组,将它的 time 属性累加就会产生水波效果动画了。但是别忘了,它执行完之后还要记得移除掉,避免占用内存空间。

演示4.gif