pixi疑难杂症汇总

这段时间在忙一个七夕活动游戏的项目,技术栈基于gsap + pixi.js + ts + vue

因为算是第一次用pixi和gsap来做项目,踩了不少坑。特此记录整理一下。

gsap挂载pixi.js插件

import gsap, { PixiPlugin } from 'gsap/all';

gsap.registerPlugin(PixiPlugin);
PixiPlugin.registerPIXI(PIXI);

loader加载音频、gif等特殊资源

加载音频

pixijs 在苹果手机上面的微信浏览器里面用loader无法加载音频文件

加载图片很容易,但是有时候加载音频文件会卡住不动。

网上大部分能搜到的都是在pixi的 v4.0.0+ 版本中,通过修改loadtype来解决

    PIXI.loaders.Resource.setExtensionLoadType( 'mp3', PIXI.loaders.Resource.LOAD_TYPE.XHR );
    PIXI.loaders.Resource.setExtensionXhrType( 'mp3', PIXI.loaders.Resource.XHR_RESPONSE_TYPE.BLOB );

在 v5.0.0+ 版本中,原理是一样的,只是修改方法不一样,可以直接在load.add方法中来添加参数

// npm i @pixi/gif
import { Loader, LoaderResource } from 'pixi.js';

const loader = Loader.shared;

loader.add(
        {
          loadType: LoaderResource.LOAD_TYPE.XHR,
          url: 'assets/audio.mp3'
        })
      .load(setup)

function setup() {
  ...
}

加载gif

gif图片也可以用来当作精灵来使用

import { Loader, LoaderResource, Sprite } from 'pixi.js';
import { AnimatedGIF, AnimatedGIFLoader } from '@pixi/gif';
Loader.registerPlugin(AnimatedGIFLoader);


const loader = Loader.shared;
const gifSprites: Record<string, AnimatedGIF> = {} as any;

loader.add(
        {
          key: 'gif',
          url: 'assets/gg.gif'
        })
      .load((_, res) => {
        gifSprites['gif'] = res['gif'].animation;
      })

mask遮罩导致点击事件失效

当元素的遮罩mask设置透明度为0时,元素的绑定事件会失效。将透明度置为1即可

// Create a graphics object to define our mask
let mask = new PIXI.Graphics();
// Add the rectangular area to show
mask.beginFill(0xffffff, 1); // 第二个参数为透明度,若设置为0,则maskContainer的点击事件不会生效
mask.drawRect(0,0,200,200);
mask.endFill();

// Add container that will hold our masked content
let maskContainer = new PIXI.Container();
// Set the mask to use our graphics object from above
maskContainer.mask = mask;
// Add the mask as a child, so that the mask is positioned relative to its parent
maskContainer.addChild(mask);
// Offset by the window's frame width
maskContainer.position.set(4,4);
// And add the container to the window!
this.app.stage.addChild(maskContainer);

maskContainer.interactive = true
maskContainer.on('pointerdown', () => {
  console.log('pointerdown');
})

Container的背景色设置和阴影设置

坦白来说,Container并不能设置背景色和阴影,因为它本来只是作为一系列元素的集合,并不是真正div元素

等价方法就是创建一个Graphics元素,宽高设置为Container的宽高,来模仿背景色。

阴影设置也是手动绘制图形,放在Container下方来模拟。

绘制自定义图形

目前官方的绘制函数,有圆形、矩形、多边形、椭圆

可以自定义想要的绘制函数

  const words = new Graphics()
  this.words.beginFill(0x000000);
  // 传入Graphics元素,进行手动绘制
  this.roundRect(words, 0, 0, 100, 50, 9, 'right');
  this.words.endFill(); 

 function roundRect(ctx, x, y, w, h, r, direction) {
    // 画圆角 ctx、x起点、y起点、w宽度、y高度、r圆角半径, direction圆角朝向left/right

    // 绘制左上角圆弧 Math.PI = 180度
    // 圆心x起点、圆心y起点、半径、以3点钟方向顺时针旋转后确认的起始弧度、以3点钟方向顺时针旋转后确认的终止弧度
    ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);

    // 绘制border-top
    // 移动起点位置 x终点、y终点
    ctx.moveTo(x + r, y);
    // 画一条线 x终点、y终点
    ctx.lineTo(x + w - r, y);
    // 绘制右上角圆弧
    ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
    if (direction == 'right') {
      // 三角上的间距
      ctx.lineTo(x + w, y + h / 2 - 10);
      // 绘制三角
      ctx.lineTo(x + w + 10, y + h / 2);
      ctx.lineTo(x + w, y + h / 2 + 10);
    }

    // 绘制border-right
    ctx.lineTo(x + w, y + h - r);

    // 绘制右下角圆弧
    ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5);

    // 绘制border-bottom
    ctx.lineTo(x + r, y + h);
    // ctx.lineTo(x, y + h - r)

    // 绘制左下角圆弧
    ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI);

    // 绘制border-left
    if (direction == 'left') {
      // 三角下的间距
      ctx.lineTo(x, y + h / 2 + 10);
      // 绘制三角
      ctx.lineTo(x - 10, y + h / 2);
      ctx.lineTo(x, y + h / 2 - 10);
    }
    ctx.lineTo(x, y + r);
  }

直接采用相对路径加载图片失败

当你采用下面的方法来直接load图片资源的话,会报错「Error: Failed to load element using: IMG」

loader
    .add("a", "logo.png")
    .add("b", "bbb.png")
    .load(handleLoadComplete);

function handleLoadComplete() {
    let texture = loader.resources.a.texture;
    img = new PIXI.Sprite(texture);
    img.anchor.x = 0.5;
    img.anchor.y = 0.5;
    app.stage.addChild(img);

    app.ticker.add(animate);
};

function animate() {
    img.x = app.renderer.screen.width / 2;
    img.y = app.renderer.screen.height / 2;
    img.rotation += 0.1;
}

原因是图像的路径是相对于项目根目录指定的,而不是相对于编译版本。由于项目是动态构建的,为了找出图像的相对路径(以及哈希等等),不需要手动指定路径,只需导入图像:

可以使用import来引入正确的路径,或者使用require函数来加载(推荐)

import logoImagePath from './logo.png';
import bbbImagePath from './bbb.png';

loader
      .add("a", logoImagePath)
      .add("b", bbbImagePath)

// 或者

loader
    .add("a", require("logo.png"))
    .add("b", require("bbb.png"))
    .load(handleLoadComplete);

pixi实现3D类旋转

因为pixi是一个2D的动画引擎,实现在平面上的动效是比较方便的,例如平移、旋转等。

不过实现3D的旋转,只需要将scale值设置为负数即可

// 这种办法需要和gsap结合使用。
gsap.to(
        this.container,
        {
          duration: 0.9,
          pixi: { scaleX: -0.5 }
        }
      )

pixi精灵图加载方法

网上能够搜到的精灵图加载方法,基本上都是直接loader加载json文件,就可以拿到纹理了。

const app = new PIXI.Application();
document.body.appendChild(app.view);

app.loader
    .add('examples/assets/spritesheet/fighter.json')
    .load(onAssetsLoaded);

function onAssetsLoaded() {
  ...
}

但是我写demo来实现动画精灵的时候,这种方法无法正确添加纹理缓存。(pixi版本v6+)

因为webpack会编译我们的json文件,这样loader加载的时候就不是常量类型了。建议可以放在public文件夹下面,或者你利用脚手架设置的webpack不编译的地方。

如果无法改变文件夹位置,也可以使用下面这张方法,此时可以采用绕过loader的方法来加载,直接创建Spritesheet实例

import { Loader } from 'pixi.js';
import * as PIXI from "pixi.js";

const app = new PIXI.Application();
document.body.appendChild(app.view);

Loader.shared.add('spritesheet', require('./assets/dnf.png')).load(setup)

function setup(loader, resources) {
  // console.log(PIXI.utils.TextureCache, resources['spritesheet']);
  let id

  const texture = loader.resources["spritesheet"].texture.baseTexture;
  const sheet = new PIXI.Spritesheet(texture, jsonPath);
  sheet.parse((textures) => {
    id = textures;
  });

  // 
  // let id = resources['spritesheet'].textures;

    //创建纹理数组
    let frames = [
        id["dnf0.png"],
        id["dnf1.png"],
        id["dnf2.png"],
        id["dnf3.png"]
    ];
    //创建动画精灵
    let pixie = new PIXI.AnimatedSprite(frames);
    //设置动画精灵的速度
    pixie.animationSpeed = 0.1;

    //把动画精灵添加到舞台
    app.stage.addChild(pixie);
    //播放动画精灵
    pixie.play();
}

本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。