跳至主要內容

pixi.js

ourandream大约 8 分钟front-end

pixi.js 是一个灵活的创作引擎.

base

pixi.js 使用 webgl 技术.

在安装好 pixi.js 的包后, 首先我们需要启动它:

let app = new PIXI.Application({ width: 640, height: 360 });

Application是一个助手类, 它会帮我们创建 renderer, stage, 并启动一个 ticker 用于更新.

然后我们需要将它创建的canvas添加到 dom:

document.body.appendChild(app.view);

然后我们创建一个Sprite, 它是加载图片并使用的一种常用方式. 注意使用之前必须加载, 在这里我们使用一个 method 来实现:

// Magically load the PNG asynchronously
let sprite = PIXI.Sprite.from("sample.png");

然后我们将Sprite加入Stage, Stage可以理解为场景的根容器, 它的所有内容会被加载城帧:

app.stage.addChild(sprite);

然后我们利用ticker添加动画. ticker会在每一帧调用被提供的 callback:

// Add a variable to count up the seconds our demo has been running
let elapsed = 0.0;
// Tell our application's ticker to run a new callback every frame, passing
// in the amount of time that has passed since the last tick
app.ticker.add((delta) => {
  // Add the time to our total elapsed time
  elapsed += delta;
  -- Update the sprite's X position based on the cosine of our elapsed time.  We divide
  // by 50 to slow the animation down a bit...
  sprite.x = 100.0 + Math.cos(elapsed / 50.0) * 100.0;
});

container

container用于收集一系列子对象, 它的产生耗费很低, 故通常我们建议设置多个container对渲染的对象进行分组, 这样既可以灵活控制渲染顺序, 也可以让项目添加功能时更加简单.

通常container会被用于存储被 mask(即仅在规定区域可见)的对象:

// Create a graphics object to define our mask
let mask = new PIXI.Graphics();
// Add the rectangular area to show
mask.beginFill(0xffffff);
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!
frame.addChild(maskContainer);

// Create contents for the masked container
let text = new PIXI.Text(
  "This text will scroll up and be masked, so you can see how masking works.  Lorem ipsum and all that.\n\n" +
    "You can put anything in the container and it will be masked!",
  {
    fontSize: 24,
    fill: 0x1010ff,
    wordWrap: true,
    wordWrapWidth: 180,
  }
);
text.x = 10;
maskContainer.addChild(text);

// Add a ticker callback to scroll the text up and down
let elapsed = 0.0;
app.ticker.add((delta) => {
  -- Update the text's y coordinate to scroll it
  elapsed += delta;
  text.y = 10 + -100.0 + Math.cos(elapsed / 50.0) * 100.0;
});

我们可以使用Graphics创建任意形状的 mask. 对与Sprite, 我们也可以使用 alpha chanel 作为 mask, 注意 canvas 不支持这点.

container的另一个作用是管理被filter作用的对象. filter是一种仅 webgl 支持的, 会对每一个像素起作用的效果(比如模糊和放大).

注意filter应该尽量少使用, 它对性能的影响很大.

Display Objects

Display Objects是一个基类, 表示任何可以被 pixi 渲染的东西. 通常使用的属性如下:

PROPERTYDESCRIPTION
position相对于父, 在 x 轴或 y 轴的位置, 以像素为单位,  也可以直接使用object.x / object.y
rotation角度, 以弧度为单位
angle角度, 以角度为单位
pivot旋转或子对象的原点.
alpha可见读, 0-1.
scale放大缩小范围, 可以单独设置 x, y 的放大缩小.
skewSkew transforms the object in x and y similar to the CSS skew() function, and is specified in radians
visible是否可见, 影响自身和子对象是否被更新和渲染.
renderable是否渲染, 影响自身是否被渲染, 不影响自身的更新和子对象的渲染.

Textures

texture代表一个用于显示在屏幕的像素资源(如 video, canvas 图形, svg 等).

首先我们需要加载资源, 一般我们使用loader, 它会异步加载资源, 并在加载完毕时发送事件. 通常我们会显示一个加载图片, 然后加载所有所需资源再进行渲染.

BaseTextures管理一个像素资源. 多个texture可使用同一个BaseTextures(由于缓存, 对于同个链接, 会返回同个BaseTextures, 节省了资源.

注意对于图片, 即使我们加载了textures, 我们依然需要将它上传到 cpu 进行解码我们可以使用  Prepareopen in new window插件, 在展现前完成所有工作.

当我们不需要某个资源时, 我们可以调用BaseTextures的 destroy()来清除. 当我们想去除所有资源时, 使用PIXI.utils.destroyTextureCache().

Graphics

Graphics是一个几何形状构建工具, 一个例子:

// Create a Graphics object, set a fill color, draw a rectangle
let obj = new PIXI.Graphics();
obj.beginFill(0xff0000);
obj.drawRect(0, 0, 200, 100);

// Add it to the stage to render
app.stage.addChild(obj);

档我们调用drawRect时, 并不是真的渲染了一个正方形, 而是将它Graphics的几何列表中.

可用的基本图形如下:

  • Line
  • Rect
  • RoundRect
  • Circle
  • Ellipse
  • Arc
  • Bezier and Quadratic Curve

除此之外, 我们还可以使用@pixi/graphics-extras扩充基础图形.

几何列表是可复用的:

// Create a master graphics object
let template = new PIXI.Graphics();
// Add a circle
template.drawCircle(100, 100, 50);

// Create 5 duplicate objects
for (let i = 0; i < 5; i++) {
  // Initialize the duplicate using our template's pre-built geometry
  let duplicate = new PIXI.Graphics(template.geometry);
}

但也正因为此, 我们不需要它时, 必须调用destroy().

Graphics除了按上面的直接渲染外, 也可以利用它的几何形状做 mask, 详见 container 章节.

Interaction

我们只需要简单地把DisplayObjectinteractive设为 true 即可开启互动.

监听事件:

let sprite = PIXI.Sprite.from("/some/texture.png");
sprite.on("pointerdown", (event) => {
  alert("clicked!");
});

一般我们使用Point事件, 它既支持鼠标也支持触控屏. 当然我们也可以指定特定的类型来进行一些特殊的操作.

一般事件的触发范围是一个正方形, 我们可以使用hitArea来自定义. 它支持  PIXI.Circle, PIXI.Rectangle, PIXI.RoundedRectangle, or PIXI.Polygon.

我们可以设置interactiveChildren使得点击测试跳过子对象提高性能.

除此之外还有一些要注意的点:

  • pixijs 的子对象触发事件不可以在父对象处理.

  • pixijs 不支持捕获事件, 所有不支持全局事件处理.

Render Loop

  • 调用 ticker 的 callback

  • 更新 scene graph

  • 渲染 scene graph

以上是基本的 render loop, 注意它也可以自定义.

注意刷新率由于种种原因很难控制, 不过我们可以设置 ticker 的minFPS  和  maxFPS来控制刷新率范围. 注意这种控制并不能保证效果, 最好在 callback 中添加一些处理作为预备.

Scene Graph

Scene Graph是一颗树, 保存所有可渲染的对象. 每个对象可以有自己的子对象. 每个对象的位置, 角度, 可见性, 透明度, 都是相对与父对象而言的. (比如父对象透明度 0.5, 子对象透明度设 0.5 最后结果是 0.25).

对象的最终计算出的位置, 角度等属性存在worldTransform, 透明度存在worldAlpha.

对象的渲染顺序是从根往下, 对于每一个节点的子节点从第一个子节点到最后一个字节点.

我们可以使用setChildIndex()重新安排子节点顺序, addChildAt()添加一个字节点到指定位置, sortableChildren自动排序子节点.

有时有些内容在可见范围外, 我们希望不渲染它以提升性能. 我们可以通过第三方插件实现, 当然也可以通过设置renderable属性自己实现.

对象的位置是相对与子对象的, 默认的根节点的位置原点是左上角.

获得某个对象的全局位置的位置:

// Get the global position of an object, relative to the top-left of the screen
let globalPos = obj.toGlobal(new PIXI.Point(0, 0));

对于网页, 我们还会多处一个叫Screen Coordinates的参考系, 即基于生成的 canvas 的左上角的参考系. 但 canvas 大小改变时, 渲染的参考系不会改变, 这会导致它们不匹配.

Sprites

Sprites一般代表一张图片, 它是displayobject, 故有很多可以设置的选项. 需要注意的如下:

Tinting: 给每一个像素点的颜色添加值, 如obj.tint = 0x00FF00让对象变为绿色调.

Blend modes: 每一个像素点颜色怎么相加, add将值加上(每一个 rgb 频道分别), multiply类是Tinting, screen覆盖下面的颜色.

Pivot vs Anchor: 两者都可指定旋转的原点. Pivotdisplayobject的属性, 它是相对于sprite左上角的原点便宜值, 以像素为单位. anchorsprite特有的属性, 以百分比为值, 如(0.5, 0.5). 注意改变上述两则都会改变它相对于父节点的位置.

Text

在 webgl 中生成文字意外的是一件困难的事情, 没有原生的方法可用. pixi.js 提供了两种方法:

Text Object: 它的原理是交给浏览器处理然后根据浏览器的数据生成图片.我们可以 text styleopen in new window轻松地改变样式. 当我们想使用 web fonts 时, 我们需要使用第三方库预加载它(如FontFaceObserver. 当它 scale 变大时, 它不会重新生成, 可能导致不清晰.

BitmapText: 使用 bitmap font 生成文字. 低耗费, 但需要加载字体文件.

根据一下点选择:

PIXI.Text

  • Static text
  • Small number of text objects
  • High fidelity text rendering (kerning e.g.)
  • Text layout (line & letter spacing)

PIXI.BitmapText

  • Dynamic text
  • Large number of text objects
  • Lower memory