PixiJS核心概念及簡單上手

小平果118發表於2020-10-19

0. PixiJS 介紹

PixiJS是一個輕量級的2D渲染引擎,它能自動偵測使用WebGL還是Canvas來建立圖形。開發者無需專門學習 WebGL 就能感受到強大的硬體加速的力量。


PixiJS 會幫助你用 JavaScript 或者其他 HTML5 技術來顯示媒體,建立動畫或管理互動式影像(精靈),從而製作一個遊戲或應用。它擁有語義化簡潔的 API 介面。比如支援紋理貼圖集和為精靈提供了一些簡單的動畫系統。它也提供了一個完備的場景圖,你可以在精靈圖層裡面建立另一個精靈,當然也可以讓精靈響應你的滑鼠或觸控事件。


要注意的是,雖然 PixiJS 非常適合製作小遊戲但它並不是一個遊戲引擎,它的核心本質是儘可能快速有效地在螢幕上移動物體。這個庫經常被用來製作HTML5遊戲(微信小遊戲)以及有複雜互動的H5活動頁。

1.搭建環境

注意:本文使用pixi最新的v5版本,同時使用Parcel進行模組化打包
pixi.jsv5版本預設使用webgl渲染,如果希望可以回退到canvas,需要使用pixi.js-legacy,詳情見issue
專案初始化

mkdir learn-pixi
cd learn-pixi
npm init -y


安裝依賴

npm i pixi.js -save
npm i parcel-bundler -save-dev


根目錄建立index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>learn-pixi</title>
</head>
<body>
  <script src="./src/index.js"></script>
</body>
</html>


根目錄建立src目錄,新建src/index.js

alert('pixi');

修改package.json

"scripts": {
  "dev": "parcel index.html -p 8080",
  "build": "parcel build index.html"
}


執行npm run dev,訪問 http://localhost:8080/ 即可看到效果

2.快速開始

import { Application } from 'pixi.js';

const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
// app.view就是個canvas元素,掛載到頁面上
document.body.appendChild(app.view);


頁面上就出現了一個300*300的藍色矩形,矩形是由pixi.js建立的canvas渲染的。

我們可以繼續建立新的圖形,然後渲染到canvas裡

import { Application, Graphics } from 'pixi.js';
const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);

// 建立一個半徑為32px的圓
const circle = new Graphics();
circle.beginFill(0xfb6a8f);
circle.drawCircle(0, 0, 32);
circle.endFill();
circle.x = 130;
circle.y = 130;

// 新增到app.stage裡,從而可以渲染出來
app.stage.addChild(circle);


我們還可以渲染圖片

import { Application, Sprite } from 'pixi.js';

const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);
// 建立一個圖片精靈
const avatar = new Sprite.from('http://anata.me/img/avatar.jpg');
// 圖片寬高縮放0.5
avatar.scale.set(0.5, 0.5);
app.stage.addChild(avatar);





我們讓這個圖片精靈變得可以互動:點選圖片後,圖片透明度變成0.5

const avatar = new Sprite.from('avatar.jpg');
avatar.scale.set(0.5, 0.5);
avatar.x = 100;
avatar.y = 100;
// 可互動
avatar.interactive = true;
// 監聽事件
avatar.on('click', () => {
   // 透明度
   avatar.alpha= 0.5;
})
app.stage.addChild(avatar);


我們還能讓圖片一直旋轉

const avatar = new Sprite.from('avatar.jpg');
avatar.scale.set(0.5, 0.5);
avatar.x = 150;
avatar.y = 150;
// 修改旋轉中心為圖片中心
avatar.anchor.set(0.5, 0.5)
app.stage.addChild(avatar);
app.ticker.add(() => {
  // 每秒呼叫該方法60次(60幀動畫)
  avatar.rotation += 0.01;
})


3.基本概念

3.0 Application應用

使用 PixiJS ,我們首先應該建立一個 Pixi 應用,使用 PIXI.Application() 方法可以 new 一個,這個方法可以傳入一個物件引數,這個物件中,可以設定 Pixi 應用的寬、高、是否透明,等一些屬性,具體所有可以設定的屬性可以到 Pixi 的文件裡看


在使用 PIXI.Application() 方法時,如果你沒有給傳入的引數物件設定 view 屬性,它會自動建立一個canvas元素,建立出來的 canvas 元素就在 Pixi 應用的 view 屬性中。


pixi有幾個重要的Class:

  • Container (容器)
  • **Renderer **(渲染器)
  • Sprite (精靈)
  • Loader (資源載入器)
  • Texture(紋理)
  • Ticker (計時器)
const app = new Application({ // 建立一個Pixi 應用
  width: 300,
  height: 300
});
// 把 Pixi 應用中建立出來的 canvas 新增到頁面上
document.body.appendChild(app.view);

// 在離開頁面時需要手動清理記憶體,否則無法釋放WebGL記憶體
app.destroy(true)

**

3.1 Container 容器

容器是用來裝載多個顯示物件的(類比div), 它可以用 PIXI.Container() 方法來建立,而我們建立的 Pixi 應用的 stage 屬性(類比document.body)就是一個容器物件,它被當作根容器使用,它將包裹所有你想用 Pixi 顯示的東西。


app.stage是一個Container的例項,作為最底層的舞臺(stage),所有要渲染的圖形都應放在它的內部。

const app = new Application({
  width: 300,
  height: 300
});
// 新增不同的圖形, 類似於我們appendChild
app.stage.addChild(circle1);
app.stage.addChild(circle2);


我們也可以建立自己的Container,自定義的Container通常用來分組

import { Application, Container, Graphics } from 'pixi.js';

const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});

const myContainer = new Container();
// 相對於根節點偏移
myContainer.position.set(40, 40);

const rectangle = new Graphics();
rectangle.beginFill(0x000000);
rectangle.drawRect(0, 0, 64, 64);
rectangle.endFill();

const rectangle2 = new Graphics();
rectangle2.beginFill(0xFFFFFF);
rectangle2.drawRect(0, 0, 64, 64);
rectangle2.endFill();
rectangle2.position.set(20, 20);

myContainer.addChild(rectangle);
myContainer.addChild(rectangle2);

// 自定義Container最後需要新增到app.stage
app.stage.addChild(myContainer);
document.body.appendChild(app.view);




分組的好處在於,修改container的屬性,位於其中的子節點,都會受到影響。比如上面的例子,我們把rectanglerectangle2分到了同一個組裡,如果希望同時隱藏這兩個元素,只需修改它們父級container的透明度即可。

// 父級透明,則子級也透明
myContainer.alpha = 0;


一種常見的做法是,我們建立一個最頂層的rootContainer,之後所有的內容,都新增到rootContainer裡。而rootContainer作為頂級元素,可以進行一些縮放來適配不同的解析度:

const rootContainer = new Container();
app.stage.addChild(rootContainer);
// 相對於設計稿750px進行縮放(豎屏狀態)
const screenScaleRito = window.innerWidth / 750; // 橫屏則用innerHeight
rootContainer.scale.set(screenScaleRito, screenScaleRito);

這種方法類似我們前端的rem佈局

3.2 Renderer 渲染器

app.renderer是一個Renderer的例項,如果你希望重新渲染頁面,就需要使用它, 例如重置畫布大小後,需要重新渲染頁面元素。可以呼叫render方法重新渲染畫布元素。

// CanvasRenderer, WebGLRenderer
const renderer = new PIXI.autoDetectRenderer(256, 256, {
            view: document.getElementById('app')
        }
);

// 建立 Stage 
const stage = new PIXI.Container();
// 用 Render 去渲染 Stage
renderer.render(stage);

// 把畫布重新渲染為500*500大小
app.renderer.resize(500, 500);
// 渲染一個容器
const container = new Container();
app.renderer.render(container);

3.3 Sprite 精靈

Sprite精靈是可以放在容器裡的互動式影像。精靈是你能用程式碼控制影像的基礎。你能夠控制他們的位置,大小,和許多其他有用的屬性來產生互動和動畫。 建立一個精靈需要用 PIXI.Sprite() 方法。

const avatar = new Sprite.from('avatar.jpg');

// 和普通的圖形一樣可以設定各種屬性
avatar.width = 100;
avatar.height = 200;
avatar.position.set(20, 30);
avatar.scale.set(2, 2);

載入圖片通常需要耗費一定的時間,因此我們常常使用Loader來預載入圖片,當圖片全部載入成功後,才渲染出來。
sprite 具有zIndex屬性,當容器的sortableChildren屬性為true時容器中的所有子元素按照zIndex大小排序。

3.4 Loader 載入器

import { Application, Sprite, Loader } from 'pixi.js';

const loader = Loader.shared;// Loader.shared內建的單例loader
const loader = new Loader();// 也可以使用自定義的loader

const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);

loader
  .add('bili', 'http://pic.deepred5.com/bilibili.jpg')
  .add('avatar', 'http://anata.me/img/avatar.jpg')
  .load(setup)

// 監聽載入事件
loader.onProgress.add((loader) => {
  console.log(loader.progress);
});

// 啟動函式setup
function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  
  const avatar = new Sprite(
    loader.resources["avatar"].texture
  );
  avatar.width = 50;
  avatar.height = 50;
  avatar.position.set(50, 50);
  app.stage.addChild(bili);
  app.stage.addChild(avatar);
}


通過add方法新增需要載入的圖片,所有圖片載入完成後,load方法會呼叫傳入的setup回撥函式,這時就可以把圖片精靈加入到app.stage裡。onProgress事件可以監聽載入的進度,通過這個方法,可以很方便的製作進度條動畫。

3.5 Texture 紋理

因為 Pixi 用 WebGL 和 GPU 去渲染影像,所以影像需要轉化成 GPU 可以處理的格式。可以被 GPU 處理的影像被稱作紋理 。在你讓精靈顯示圖片之前,需要將普通的圖片轉化成 WebGL 紋理。為了讓所有工作執行的快速有效率,Pixi使用 紋理快取 來儲存和引用所有你的精靈需要的紋理。紋理的名稱字串就是影像的地址。這意味著如果你有從"cat.png" 載入的影像,你可以在紋理快取PIXI.utils.TextureCache["cat.png"]中這樣找到它。


前端有時會把多張圖片合併成一張雪碧圖,通過設定background-position來顯示不同的圖片。pixi.js也有類似的技術,我們可以利用Texture Packer軟體,把多張圖片合併成一張圖片,合併的同時,軟體會生成一份json配置檔案,記錄了每張圖片的相對位置,具體教程見這裡

import { Application, Container, Sprite, Graphics, Loader, Spritesheet } from 'pixi.js';
import spriteJson from './assets/treasureHunter.json'; // myjson記錄了每張圖片的相對位置
import imgSprite from './assets/treasureHunter.png';// mypng裡面有多張圖片

const loader = Loader.shared;
const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);

loader.add('imgSprite', imgSprite).load(setup)

function setup() {
  const texture = loader.resources["imgSprite"].texture.baseTexture;
  const sheet = new Spritesheet(texture, spriteJson);
  sheet.parse((textures) => {
    // imgSprite裡面的一張叫treasure.png的圖片
    const treasure = new Sprite(textures["treasure.png"]);
    treasure.position.set(0, 0);
    // imgSprite裡面的一張叫blob.png的圖片
    const blob = new Sprite(textures["blob.png"]);
    blob.position.set(100, 100);
    
    app.stage.addChild(treasure);
    app.stage.addChild(blob);
  });
}
// 銷燬紋理 texture.destroy()
// 重新整理紋理 texture.update()

3.6 Ticker 幀率更新函式

Ticker有點類似前端的requestAnimationFrame,當瀏覽器的顯示頻率重新整理的時候,此函式會被執行,因此常常用來製作幀動畫。app.ticker就是一個Ticker例項。

import { Application, Sprite, Loader } from 'pixi.js';

const loader = Loader.shared;
const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);
loader
  .add('bili', 'bilibili.jpg')                      
  .load(setup)

function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  app.stage.addChild(bili);
  app.ticker.add(() => {
    if (bili.x <= 200) {
      bili.x += 1;
    }
  })
}


我們也可以使用requestAnimationFrame實現這個效果

function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  app.stage.addChild(bili);
  
  function move() {
    if (bili.x <= 200) {
      bili.x += 1;
      requestAnimationFrame(move)
    }
  }
  requestAnimationFrame(move)
}

3.7 Graphic 幾何圖形

graphics主要用於繪製原始形狀(如線條,圓形和矩形)以及他們的上色和填充。

const graphics = new PIXI.Graphics()
// 線框 
graphics.lineStyle(2, 0x0000FF, 1) graphics.drawRect(50, 250, 100, 100)
// 四邊形 
graphics.drawRect(50, 50, 100, 100)
// 圓形 
graphics.drawCircle(100, 250, 50)
// 橢圓 
graphics.drawEllipse(600, 250, 80, 50)
// 圓角矩形 
graphics.drawRoundedRect(50, 440, 100, 100, 16)
// 星星 
graphics.drawStar(360, 370, 5, 50)
// 多邊形 
graphics.drawPolygon(path)
// 貝塞爾曲線 
graphics.bezierCurveTo(100, 240, 200, 200, 240, 100)
// 圓弧 
graphics.arc(300, 100, 50, Math.PI, 2 * Math.PI)
app.stage.addChild(graphics)


清除圖形 用graphics.clear()方法

text 文字

// 基礎文字 
const basicText = new PIXI.Text('6666666')
// 自定義文字樣式 
const style = new PIXI.TextStyle({    
  fontFamily: 'Arial',    
  fontSize: 36,    
  fontStyle: 'italic',    
  fontWeight: 'bold',    
  fill: ['#ffffff', '#00ff99'],    
  stroke: '#4a1850',    
  strokeThickness: 5,    
  dropShadow: true,    
  dropShadowColor: '#000000',    
  dropShadowBlur: 4,    
  dropShadowAngle: Math.PI / 6,    
  dropShadowDistance: 6,    
  wordWrap: true,    
  wordWrapWidth: 440
}) 
const richText = new PIXI.Text('6666666', style) 
app.stage.addChild(richText)

互動

pixi.js中的事件互動可同時相容移動端和pc端,在繫結事件之前需要開啟互動模式

graphics.interactive = true


在pc端需要出現cursor: pointer的效果

graphics.buttonMode = true

繫結事件在文件中都有詳細描述(click, tap,mousedown, mousemove……),下邊簡單寫一下

graphics.on('click', onClick)
function onClick() {    
  graphics.scale.x *= 1.25    
  graphics.scale.y *= 1.25 
}
  • 拖拽功能
const texture = PIXI.Texture.from('examples/assets/bunny.png') 
const bunny = new PIXI.Sprite(texture)
bunny    
  .on('pointerdown', onDragStart)    
  .on('pointerup', onDragEnd)    
  .on('pointerupoutside', onDragEnd)    
  .on('pointermove', onDragMove)     
function onDragStart(event) {    
  this.data = event.data    
  this.alpha = 0.5    
  this.dragging = true 
}
function onDragEnd() {    
  this.alpha = 1    
  this.dragging = false    
  this.data = null 
}
function onDragMove() {    
  if (this.dragging) {        
    const newPosition = this.data.getLocalPosition(this.parent)        
    this.x = newPosition.x        
    this.y = newPosition.y    
  } 
}

4. 補間動畫

Ticker可以實現簡單的動畫,但如果我們希望實現一些複雜效果,則需要自己編寫很多程式碼,這時就可以選擇一個相容pixi的動畫庫。市面上比較常見的動畫庫有:Tween.jsTweenMax,這裡我們使用TweenMax來演示效果。


安裝動畫庫

npm i gsap
import { Application, Sprite, Loader } from 'pixi.js';
import { TweenMax } from 'gsap/all';
const loader = Loader.shared;
const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);

loader
  .add('bili', 'bilibili.jpg')
  .load(setup)

function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  app.stage.addChild(bili);
  // 1s內x和y軸移動100
  TweenMax.to(bili, 1, { x: 100, y: 100 });
}


TweenMax還提供了一個PixiPlugin,可以一次修改多個pixi屬性

import { Application, Sprite, Loader } from 'pixi.js';
import * as PIXI from 'pixi.js';
import gsap, { TweenMax, PixiPlugin } from 'gsap/all';

gsap.registerPlugin(PixiPlugin);// 註冊外掛
PixiPlugin.registerPIXI(PIXI);

const loader = Loader.shared;
const app = new Application({
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});
document.body.appendChild(app.view);
loader
  .add('bili', 'http://pic.deepred5.com/bilibili.jpg')
  .load(setup)
function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  app.stage.addChild(bili);
  // 一次修改多個屬性
  TweenMax.to(bili, 1, { pixi: { scaleX: 1.2, scaleY: 1.2, skewX: 10, rotation: 20 } });
}


5. 自定義的Application

我們通常使用Pixi提供的Application方法來建立一個應用,它能自動建立renderer,ticker 和container。但其實,我們可以自己來建立這些物件。

import { Container, Renderer, Sprite, Loader, Ticker } from 'pixi.js';
import { TweenMax } from 'gsap/all';

const renderer = new Renderer({ // 自定義render
  width: 300,
  height: 300,
  antialias: true,
  transparent: false,
  resolution: 1,
  backgroundColor: 0x1d9ce0
});

const stage = new Container();// 自定義container
const loader = new Loader(); 
const ticker = new Ticker();

// 每次螢幕重新整理重新渲染,否則只會渲染第一幀
ticker.add(() => {
  renderer.render(stage);
});

ticker.start(); // 開始執行ticker,一定要呼叫這個方法,註冊的回撥函式才會被執行!!!
document.body.appendChild(renderer.view);
loader
  .add('bili', 'http://pic.deepred5.com/bilibili.jpg')
  .load(setup)
function setup() {
  const bili = new Sprite(
    loader.resources["bili"].texture
  );
  bili.width = 50;
  bili.height = 50;
  stage.addChild(bili);
  // 動畫效果
  ticker.add(() => {
    if (bili.x <= 200) {
      bili.x += 2;
    }
  });
  TweenMax.to(bili, 1, { y: 100, delay: 3 });
}

其實PIXI.Application的底層就是幫我們簡化了上述的操作。

6. 感受一下

下面這些是用 PixiJS 實現的一些例子,你可以點開看看。
cavalier challenge
Run Pixie Run
Filters Demo
WASTE INVADERS
Storm Brewing
H5場景小動畫
打磚塊遊戲

參考

主要參考:https://juejin.im/post/6844904020939636744

Pixi教程中文版
Pixi wiki

相關文章