pixijs的filter实现局部点亮

500 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第19天,点击查看活动详情

一、前言

今天上pixijs官网,突然看了一个很有意思的效果,是给一个场景加上黑幕,然后有个小亮圈跟随鼠标移动。

下面是官网的例子截图,可以看到背景是一整张草地,然后我们鼠标移上去就可以看到其中一部分草,类似局部被点亮的效果。

image.png

于是乎我的脑海里浮现出可以用这个filter属性给大家整点活的想法。

下面我们就来试试看吧。

二、过程

1、准备好基本的元素

写法基于vue3+ts

首先还是需要我们的pixi容器,我们先创建一个400x400的application容器

// 创建我们的pixi容器
let app = new PIXI.Application({
  width: 400,         // default: 800 宽度
  height: 400,        // default: 600 高度
  antialias: true,    // default: false 反锯齿
  transparent: false, // default: false 透明度
  resolution: 1       // default: 1 分辨率
})

有了容器,我们还需要一个场景容器Container,先声明一下,便于后续sprite的添加和管理。

如果只有一个场景,其实也可以跳过这一步,后面直接把sprite加到application容器里就行。

let Game = new PIXI.Container()

然后我们使用定时器(也可以其他方式),让执行顺序变为异步,因为此处需要等到网页标签元素加载好之后,才能将我们的application容器挂载到页面里的元素上去,并在之中用loader加载一下图片资源,图片我放在public文件下面,等图片资源加载完之后,再调用setup函数,往场景里添加sprite和开启动画循环。

// 使用定时器变成异步等待标签加载完毕
setTimeout(() => {
  document.getElementById("pixijsjs")?.appendChild(app.view)
  app.loader
  .add([
    "stagebg.jpeg"
  ])
  .load(setup) // setup函数是关键
}, 400)

setup函数里,我们将场景容器Container加到application容器里,同时调用gameScene函数,来添加我们的主逻辑,还启用了循环函数,可以在play函数里,修改变量的值,达到动起来的效果。

let bg: PIXI.Sprite
let state: Function
function setup() {
  // 加载完图片后
  // 将场景加入到容器里
  app.stage.addChild(Game)
  // 游戏主场景的精灵
  gameScene()
  // 循环函数
  state = play
  app.ticker.add(delta => gameLoop(delta))
}
// 游戏循环函数
function gameLoop(delta: number){
  state(delta)
}
// 游戏循环函数--具体处理逻辑  ----1秒调用60次左右
function play(delta: number) {
   // 当status为true的时候,开启背景的旋转。
    if(status) {
        bg.rotation += 0.01
    }
}

2、gameScene函数

首先我们创建一个背景,用我们之前导入的stagebg.jpeg作为sprite的图片,大小是400x400,刚好填充满我们的pixi容器。

// 设置背景
  bg = new PIXI.Sprite(app.loader.resources["stagebg.jpeg"].texture)
  bg.x = 0
  bg.y = 0
  bg.width = 400
  bg.height = 400
  Game.addChild(bg)      
  

添加完之后是这样的效果。

image.png

然后是我们的重头戏filter属性

由于是光点,所以我们需要先设立一下半径和模糊大小

// 半径
const radius = 30
// 模糊大小
const blurSize = 10

接着我们用Graphics图层创建一个圆circles,这里的beginFill可以设置光点的背景色,但是经过filter之后,基本就是灯光的那种亮点,而没有本身的颜色。

const circle = new PIXI.Graphics()
    .beginFill(0xFF0000) // 红色
    .drawCircle(radius + blurSize, radius + blurSize, radius)
    .endFill()
circle.filters = [new PIXI.filters.BlurFilter(blurSize)]

给上面的circles准备一个合适的矩形盒子,用这盒子+圆circles一起生成一个texture,用来作为聚焦(亮点)时的背景。

const bounds = new PIXI.Rectangle(0, 0, (radius + blurSize) * 2, (radius + blurSize) * 2);
const texture = app.renderer.generateTexture(circle, PIXI.SCALE_MODES.NEAREST, 1, bounds);
const focus = new PIXI.Sprite(texture)

上述的focus时,需要加入到容器中,还需要成为bg的mask,不然黑屏的位置挡不住背景。

app.stage.addChild(focus)
bg.mask = focus

之后允许容器开启交互功能,并且挂载一个mousemove鼠标移动事件。第一个点是传入的鼠标需要减去focus的宽高的一半,第二个点是我设立里一个范围,当亮点范围在这个小矩形里,移除focus,屏幕变回原来不黑屏的样子,同时开启bg的旋转。

// 开启容器的交互
app.stage.interactive = true;
app.stage.on('mousemove', pointerMove);
function pointerMove(event: { data: { global: { x: number; y: number; }; }; }) {
    focus.position.x = event.data.global.x - focus.width / 2;
    focus.position.y = event.data.global.y - focus.height / 2;
    if(focus.position.x < 220 && focus.position.x > 180 && focus.position.y < 220 && focus.position.y > 180) {
      bg.mask = null
      app.stage.removeChild(focus)
      status = true
    }
}

3、来看下效果吧

可以看到原本的背景看不到了,鼠标划过的位置有小亮点,当我们的鼠标处于设立好的范围时,focus被移除,主背景开始旋转了起来。

filter属性.gif

三、小结

pixijs库有不少属性都很有趣,今天又体验了一个新的属性,大家伙可以去试试,相信一点点积累,一点点进步,大家最终都能成为大佬。

ps:我是地霊殿__三無,不想上班(狗头)。

微信图片_20221001074313.jpg