0. PixiJS 介绍
PixiJS是一个轻量级的2D渲染引擎,它能自动侦测使用WebGL还是Canvas来创建图形。开发者无需专门学习 WebGL 就能感受到强大的硬件加速的力量。
PixiJS 会帮助你用 JavaScript 或者其他 HTML5 技术来显示媒体,创建动画或管理交互式图像(精灵),从而制作一个游戏或应用。它拥有语义化简洁的 API 接口。比如支持纹理贴图集和为精灵提供了一些简单的动画系统。它也提供了一个完备的场景图,你可以在精灵图层里面创建另一个精灵,当然也可以让精灵响应你的鼠标或触摸事件。
要注意的是,虽然 PixiJS 非常适合制作小游戏,但它并不是一个游戏引擎,它的核心本质是尽可能快速有效地在屏幕上移动物体。这个库经常被用来制作HTML5游戏(微信小游戏)以及有复杂交互的H5活动页。
1.搭建环境
注意:本文使用pixi最新的v5版本,同时使用Parcel进行模块化打包pixi.js
v5版本默认使用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的属性,位于其中的子节点,都会受到影响。比如上面的例子,我们把rectangle
和rectangle2
分到了同一个组里,如果希望同时隐藏这两个元素,只需修改它们父级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.js,TweenMax,这里我们使用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场景小动画
打砖块游戏