pixi.js——快速的2D sprite渲染引擎

521 阅读8分钟

Pixi.js

快速的2D sprite渲染引擎 官方文档 中文文档

安装

node

npm i pixi.js
import * as PIXI from 'pixi.js'

cdn js

<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.3/pixi.min.js"></script>

基本使用

引用成功

let type = PIXI.utils.isWebGLSupported ? 'webGL' : 'canvas';
PIXI.utils.sayHello(type);

全局对象

  • PIXI.Application 应用类
  • PIXI.utils 工具库
  • PIXI.Loader 加载器类
  • PIXI.BaseTexture 基础纹理类
  • PIXI.Texture 纹理类
  • PIXI.Sprite 精灵类
  • PIXI.AnimatedSprite 动画精灵类
  • PIXI.TilingSprite 叠加精灵类
  • PIXI.Text 文字类
  • PIXI.Graphics 图形类
  • PIXI.Container Container类

创建应用

应用会自动集成 renderer, ticker 和根 container

// 创建并编辑应用
let app = new PIXI.Application({
  width: 100,
  height: 100
});
document.body.appendChild(app.view); // app.view即canvas元素(画布)

// 更多的可覆盖的配置
let app2 = new PIXI.Application({
  width: 100,
  height: 100,
  antialias: true,  // 是否抗锯齿
  transparent: true, // 是否透明,默认为黑色
  resolution: 1 // 分辨率
  forceCanvas: true // 强制renderer使用canvasAPI
});
document.body.appendChild(app2.view);

// renderer渲染器 修改应用/重渲染画布
app.renderer.backgroundColor = 0xCC00FF; // 16进制颜色
app.renderer.autoResize = true; // 重设大小时自适应分辨率
app.render.resize(window.innerWidth, window.innerHeight);

纹理(Texture)

BaseTexture

每个纹理基于一个基础纹理,基础纹理创建与转换:

  • Image

    // 1. 创建Image实例
    var img = new Image();
    img.setAttribute("src", "/images/awards.png");
    // 2. 转纹理
    // 注:即使图片当前未加载完成,PIXI能在将来的图片加载完成时自动同步src值
    var baseTexture = new PIXI.BaseTexture(img);    // 纹理物料实例
    var texture = new PIXI.Texture(baseTexture);    // 纹理实例
    var sprite = new PIXI.Sprite(texture);
    app.stage.addChild(sprite);
    
  • canvas元素

    let basetexture = new BaseTexture.fromCanvas($canvas);     // 纹理物料fromCanvas实例
    let texture = new Texture(basetexture);     // 纹理实例
    

Texture

const texture = Texture.from('assets/image.png');
const sprite1 = new Sprite(texture);
const sprite2 = new Sprite(texture);

精灵(Sprite)

精灵: 作为stage容器的子项并绘制在view上的元素;纹理直接对精灵负责;

纹理: Pixi使用WebGL在GPU上渲染图像,图像无法直接被GPU渲染,需要经过图像 -> 纹理 -> 显示图像的过程;

纹理创建

  • 通过图片文件
  • 通过雪碧图
  • 通过纹理贴图

图片文件创建

  1. PIXI.Loader加载图像资源

    const loader = new PIXI.Loader();
    loader.add("url/path") // 允许传入多个文件路径的数组
        .load(setup);
    
  2. 图像的纹理,创建精灵并添加至舞台

    function setup(loader, resources){
        var texture = loader.resources["url/path"].texture;
        var sprites = new PIXI.Sprites(texture);
        app.stage.addChild(sprite);
    }
    

附:

  • loader.resources["url/path"].texture 获取图像资源的纹理对象外,通过PIXI.utils.TextureCache("url/path") 工具方法允许从纹理缓存中取出对应资源的纹理对象;
  • stage是一个pixi容器,不同于app.view(画布元素),stage用于容纳pixi其它对象的对象,对stage的操作也会反应在view上;
  • 移除精灵,即 app.stage.removeChild(sprite) 或隐藏精灵 sprite.visible = false

雪碧图创建

// 1. 加载雪碧图资源
const loader = new Loader().add({
  name: "sprite_game",
  url: "./images/sprite_game.png"
}).load(setup);

function setup(loader, resources){
  // 2. 创建纹理
  var texture = utils.TextureCache["sprite_game"];
  // 3. 创建矩形选区,裁剪纹理
  var rect = new Rectangle(96, 64, 32, 32);
  texture.frame = rect;
  // 4. 创建精灵并添加至舞台
  var sprite = new Sprite(texture);
  app.stage.addChild(sprite);
}

纹理贴图集创建

  // 1. 软件生成纹理贴图集json/png(此处使用TexturePacker工具)
  // 2. 加载纹理贴图集资源
  new Loader().add({
    name: "sprites",
    url: "./images/sprite.json"
  })
  .load(setup);

  function setup(loader, resources){
    // 3. 获取纹理贴图集中纹理各种方式
    // 3.1 从纹理贴图集textures 中获取
    var sprites = resources["sprites"].textures;
    var txure_dungeon = sprites["dungeon.png"];
    var txure_explorer = sprites["explorer.png"];
    var txure_treasure = sprites["treasure.png"];
    var txure_door = sprites["door.png"];

    // 游戏背景
    var dungeon = new Sprite(txure_dungeon);
    // 探索者——角色
    var explorer = new Sprite(txure_explorer);
    explorer.position.set(68, app.view.height / 2 - explorer.height / 2);
    // 宝物
    var treasure = new Sprite(txure_treasure);
    treasure.position.set(416, app.view.height / 2 - treasure.height / 2);
    // 门——入口
    var door = new Sprite(txure_door);
    door.position.set(32, 0);

    app.stage.addChild(dungeon, explorer, treasure, door);

    // 3.2  从纹理缓存中获取
    var txure_blob = utils.TextureCache["blob.png"];

    // 生成位置随机但不重叠的blob
    var blobNum = 6; // 生成blob数
    var diff = ((416 - 90) - 32 * 10) / 2; // 差值计算,横向可容量10个,居中差值为diff
    var baseX = 90 + diff; // 生成范围的基础X
    var baseY = 24; // 生成范围的基础Y
    var NumX = 10; // 横向格子数
    var NumY = 19; // 纵向格子数
    var randomPositionArr = randomInt([0, NumX - 1], [0, NumY - 1], blobNum); // 不重复的随机坐标的数组
    for(var i = 0; i < blobNum; i++){
      var blob = new Sprite(txure_blob);
      var x = baseX + randomPositionArr[i].x * blob.width;
      var y = baseY + randomPositionArr[i].y * blob.height;
      blob.position.set(x, y);
      app.stage.addChild(blob);
    }
  }

  // 不重复的随机坐标生成
  function randomInt(spaceX, spaceY, count){
    var arr = [];
    do{
      let offsetItem = {
        x: spaceX[0] + Math.floor(Math.random() * (spaceX[1] - spaceX[0] + 1)),
        y: spaceX[0] + Math.floor(Math.random() * (spaceY[1] - spaceY[0] + 1)),
      }
      var isRepeat = arr.some(item => item.x === offsetItem.x && item.y === offsetItem.y);
      if(!isRepeat) arr.push(offsetItem);
    }while(
      arr.length < count
    );
    return arr;
  }

注: 多个精灵添加到stage时,默认层级规则为新添加的覆盖已存在的sprite;

控制精灵

换肤

// 纹理更换
sprite.texture = newTexture;

定位

// 定位属性
sprite.x = 50;
sprite.y = 50;
// set方法
sprite.position.set(50, 50);

移动精灵

// 1. app.ticker(添加处理到每帧执行)
app.ticker.add(move);
function move(){
  explorer.x += 1.5;
  if(explorer.x >= 392) app.ticker.remove(move);
}
// 2. requestAnimationFrame
(function move(){
  explorer.x += 2;
  if(explorer.x <= 392) requestAnimationFrame(move);
})()
// 控制移动速度
explorer.vx = 1;
explorer.vy = 1;
app.ticker.add(move);
function move(){
  explorer.x += explorer.vx;
  explorer.y += explorer.vy;
  if(explorer.x >= 392) app.ticker.remove(move);
}
document.addEventListener("click", () => { explorer.vx = 2;explorer.vy = 2 });

大小

// 访问/获取宽高
sprite.width = 100;
sprite.height = 100;

缩放

// 缩放属性
sprite.scale.x = 0.5;
sprite.scale.y = 0.5;
// set方法
sprite.scale.set(2, 2);

层级

// 设置容器允许排序层级
container.sortableChildren = true;
// 精灵层叠
sprite.zIndex = -1;

旋转

sprite.rotation = Math.PI / 2; // 90°弧度值
sprite.angel = 90; // 角度

永远基于原点(默认左上角)旋转,通过设置anchor或pivot实现中心旋转;

anchor(锚点): 纹理基准点,默认为sprite原点(左上),修改锚点即修改纹理渲染参考点的相对位置;

// 锚点位置
sprite.anchor.x = 0.5; // 相对于原点左偏移50%
sprite.anchor.y = 0.5; // 相对于原点上偏移50%
// set方法
sprite.anchor.set(1, 1);

pivot(原点): sprite基准点,默认sprite的(x,y)点作为左上角,修改pivot可以修改坐标基准点的相对偏移位置;

sprite.pivot.set(sprite.width/2, sprite.height/2);

修改anchor即对纹理渲染加偏移量,sprite渲染偏移到精灵原点为中心处;

修改pivot位置是基准点改变,sprite原点位置即基准点来实现中心旋转;

动画精灵(AnimatedSprite)

创建动画

const Atlas = [0,1,2,3].map(index => {
  const frame = new Rectagle(index * 100, 0, 100, 100);
  return new PIXI.Texture(texture, frame);
});

const animatedSprite = PIXI.AnimatedSprite(Atlas);

控制动画

播放:animatedSprite.play()

停止:animatedSprite.stop()

速度缩放:animatedSprite.animationSpeed = 0.15(默认值1)

关闭循环:animatedSprite.loop = false

是否正在播放中:animatedSprite.playing === true

跳到指定帧数并播放:animatedSprite.gotoAndPlay(6)

跳到指定帧数并暂停:animatedSprite.gotoAndStop(3)

问题

帧图过长:pixi.js 对图片转GPU纹理存在尺寸限制,可通过多列排布单帧图避免宽/高超过限制

叠加精灵(TilingSprite)

创建叠加

// 纹理 精灵视口宽高
const tiling = new PIXI.TilingSprite(texture,width,height);

叠加位置

tiling.tilePosition.x -= 10;
tiling.tilePosition.y -= 10;
// 边界重置
if (tiling.tilePosition.x === -tiling.width) tiling.tilePosition.x = 0;

文字(Text)

// 绘制文字
const text = new Text("这是一段文字...");
text.style = {
  fontFamily: "Arial",
  fontSize: 24,
  fill: "red",
  wordWrap: true,  // 是否允许换行
  wordWrapWidth: 50,  // 换行时宽度值
  breakWords: true, // 单词是否可截断
  align: "center",
};
text.x = 0;
text.y = 50;
app.stage.addChild(text);

// 直接调整文字颜色
text.style.fill = "white";

图形(Graphics)

绘制形状

  // 绘制图形
  const graphics = new Graphics();
  graphics.beginFill(0x000080); // 开始填充
  graphics.lineStyle(1, 0xc0c0c0); // 绘制边框
  // 绘制动作
  // graphics.drawRect(0, 0, 61.8, 30); // 矩形
  // graphics.drawCircle(10, 10, 20);  // 圆形
  // graphics.drawEllipse(x, y, 61.8, 30); // 椭圆形
  // graphics.drawRoundedRect(0, 0, 61.8, 30, 10); // 圆角矩形
  // graphics.moveTo(0, 0); // 线条
  // graphics.lineTo(80, 50);
  // graphics.alpha = 0; // 转为透明层
  graphics.drawPolygon([-32, 64, 32, 64, 0, 0]); // 多边形
  // shadowMask.closePath();  // 自动闭合路径
  graphics.endFill(); // 结束填充
  graphics.x = 100;
  graphics.y = 100;

  app.stage.addChild(graphics);

纹理填充

const panel = new PIXI.Graphics();
// 通过矩阵调整纹理居中
const xOffset = -((texture.width - this.width) >> 1);
const matrix = new PIXI.Matrix();
matrix.tx = xOffset;
// 纹理填充
panel.beginTextureFill({ texture: panelTexture, matrix });
panel.drawRect(0, 0, options.width, options.height);
panel.endFill();

容器(Container)

将多个Sprite组合到Container分组,方便整体管理;

创建组合

let blobAndExplorer = new Container();
blobAndExplorer.addChild(blobSprite, explorerSprite);
blobAndExplorer.position.set(10, 10);
app.stage.addChild(blobAndExplorer);

Container 属性

console.log("blobAndExplorer成员", blobAndExplorer.children);
console.log("blobAndExplorer位置", blobAndExplorer.x, blobAndExplorer.y);
// toGlobal 将容器当做全局计算精灵相对位置
console.log(
  "toGlobal计算exolorer相对blobAndExplorer位置",
  blobAndExplorer.toGlobal(explorerSprite.position)
);

Sprite 相关属性

console.log("explorerSprite父容器", explorerSprite.parent);
console.log("explorerSprite位置", explorerSprite.x, explorerSprite.y);
console.log(
  "explorerSprite全局位置",
  explorerSprite.getGlobalPosition().x,
  explorerSprite.getGlobalPosition().y
);
// toLocal 基于精灵计算另一精灵位置
console.log(
  "toLocal计算exolorer相对blobSprite位置",
  blobSprite.toLocal(explorerSprite.position, blobSprite)
);

sprite 从一个 Container 放到新 Container 时,会先被旧 Container 中移除;

粒子容器

粒子容器上精灵位置直接在GPU上计算,来实现高性能的方式来组合精灵;当你需要大量的精灵或粒子时可以使用。

代价是精灵仅支持:xywidthheightscalepivotalphavisible属性,支持基本的对象转换(位置、比例、旋转)和一些高级功能,如着色;但不支持 masking, children, filters 等高级功能;

创建粒子容器

let superFastContaier = new ParticleContainer({
  maxSize, properties, batchSize, autoResize
});

区域

矩形选区:new Rectangle(x, y, width, height)

圆形选区:new Circle(x, y, radius)

圆角矩形:new RoundedRectangle(x, y, width, height, radius)

精灵边界区域:sprite.getBounds()

边界重叠检测:rectangle.intersets(rect)

事件

绑定

sprite.on('touchend', this.handle.bind(this))

可交互

sprite.interactive = true

加载器(Loader)

定义加载资源的名称

const loader = new PIXI.loader.add("avatar", "url/path/avatar.png")     // 定义该资源为变量avatar
    .load(callback);
// 访问
let texture = loader.resources.avatar.texture;

注: 对于常用的resources,定义名称是非常重要的;但变量名称并不能替代资源url作为资源唯一标识;

监听资源加载进度

let images_list = ["./images/yanhua.webp", "./images/paopao.webp"];
loader
  .add(images_list)
  .on("progress", (loader, resource) => {    // 监听progress事件,监听写法与jquery类似
    console.log(loader.progress + "%", resource.url); // loader进度与当前加载完成资源
  })
  .load((loader, resources) => {
    console.log("加载完成");
  });

Loader配置

Loader实例

const loader = new PIXI.Loader({
    "url/path", // 资源公共路径
    12          // 同时加载资源数,默认10
});
loader.onProgress.add(() => {}); // 每加载/错误时
loader.onError.add(() => {}); // 每错误时
loader.onLoad.add(() => {}); // 每加载时
loader.onComplete.add(() => {}); // 全部加载完成时

add方法

// 单个资源配置
    // 参数
    loader.add(name, url, options, callback).load();
    // 对象类型参数
    loader.add({name: "xxx", url: "url/path", ...options, onComplete(){ ... }}).load();
// 多资源配置
    loader.add([ "url/path1", "url/path2", "url/path2", "url/path3" ]).load();
    loader.add([ { ... }, { ... }, ... ]).load();

Loader.shared

返回1个全局共享loader,特性包括:

  • 任何地方任何时间新增加载资源(add)

  • PIXI.add(resources) 新增资源时会自动调用 load

  • 内置缓存,重复加载从缓存中加载

版本差异

资源加载

v4.x new Loader().add().load() 或者 pixi.loader.add().load()

v6.x 废弃pixi.loader,保留 new Loader 类实现

v7.x Assets.load(),内部基于 Assets.loader 对象,返回Promise实例

监听加载

v6.x 加载事件监听处理

  • loader.onStart.add(callback)

  • loader.onProgress.add(callback)

  • loader.onError.add(callback)

  • loader.onComplete.add(callback)

使用例子

PixiJS Examples

www.phaser-china.com/