pixi.js
[toc]
文档:https://pixijs.download/release/docs/PIXI.Application.html
起步
下载: pnpm i pixi.js
引入: import * as PIXI from 'pixi.js'
在创建一些特殊图形时可能还需要使用到
@pixi/graphics-extras
# 创建应用
const app = new PIXI.Application(options);
这将创建一个pixi应用(画布),随后可以在页面加载完毕后,通过appendChild(app.view)
的方式添加到页面中去.
在创建时有一个参数:options
,可以用来对应用进行画布设置:
- Width,height: 画布的大小;
- antialias: boolean 使得字体的边界和几何图形更加圆滑**[推荐true]**;
- transparent: boolean 将整个Canvas标签的透明度进行了设置**[建议false]**;
- resolution: number 让不同分辨率和像素密度运行的简单 [推荐1];
- resizeTo: window 自动跟随浏览器的窗口大小变化;
# 修改画布
修改画布的方法都在app.renderer
中,例如,修改画布的颜色 大小等.
- 颜色:
app.renderer.backgroundColor
- 大小:
app.renderer.view.width/height
/app.renderer.resisze(width,height)
- 以及一些其他的样式改变:
app.renderer.view.style
# 删除画布
app.destroy()
: 删除画布,若传入参数为true,则删除整个canvas.
# 舞台
拥有了画布之后,我们需要在画布上创建舞台 => app.stage
.
舞台是一个pixi应用的根容器,所有放进去的东西都会被渲染到canvas中.
容器
app.stage
舞台相当于一个最大的容器,除此之外,还可以创建一些其他的容器类.容器用于将精灵进行分组,从而可以进行统一/分别控制这些精灵们.
# 创建一个容器
const container = new PIXI.Container(); //设置其位置 container.x = 100; container.y = 100;
container有其独立的位置.
==注意!==
当你创建了一个sprite和多个不同的container,尝试将该sprite放入这些不同的container时,sprite永远只会出现在你最后放入的container里面.这是因为创建的sprite在页面上是唯一存在的.
因此,如果想要在多个container里面包含多个sprite,请创建多个sprite.或许,直接使用sprite作为一个容器也未尝不可.毕竟 sprite是继承于container的.
# 容器常用的属性和方法
-
width/height
: 容器的宽高. -
x/y
: 容器的位置. -
position
: 容器的位置.是一个对象 {x:...,y:...}. 或者使用position.set(x,y); -
rotation
: 容器旋转弧度. -
angle
: 容器旋转角度. rotation和angle中间有一个改变,另一个也会随之改变. -
scale
: 容器的缩放程度. container.scale.set(x:number,y:number): x/y方向的缩放程度. -
skew
: 容器的扭转程度. container.skew.set(x:number,y:number) : 同上. -
container.addChild(...child[])
: 向容器中添加一个子容器**[可以是一个数组,需要...运算符]**. -
container.removeChild(...child[])
: 从容器中删除子容器**[可以是一个数组,需要...运算符]**. -
...
# Particle Container
Particle Container 的出现是为了设计一个快速的容器,为了达到快速度,他牺牲了一些东西.包括:
- 不能有子容器
- 没有滤镜和Mask
- 单一纹理
Particle Container 里的包含的精灵的纹理必须来自同一个,否则会乱码.
精灵
在 Pixi.js
里,加载图片资源需要做以下操作:
- 加载图片纹理
- 创建精灵对象,将图片纹理与精灵对象进行连接.
- 将精灵添加到画布中.
# 精灵
精灵是用代码控制图像的基础.
pixi创建精灵的方式有三种:
-
用一个单图像文件进行创建.
-
用一个雪碧图进行创建.
-
从一个纹理贴图集中进行创建.
-
纹理 : 可以被GPU处理的图像被称为纹理.
-
纹理贴图集 : 就是用JSON定义了图像大小和位置的雪碧图.
-
在让精灵显示图片之前,需要将普通的图片转化为WebGL纹理,Pixi使用纹理缓存来存储和引用你精灵需要用到的纹理. 如果你有一张图片位于:
"/image/cat.png"
,那么可以在纹理缓存中这样找到他:PIXI.utils.TextureCache["images/cat.png"];
-
精灵加载方式:
-
-1- 先构建纹理缓存,然后用纹理缓存创建精灵[不推荐]let texture = PIXI.utils.TextureCache["images/cat.png"]; let sprite = new PIXI.Sprite(texture);
-
-2- 直接加载一个图像并转化为纹理,创建精灵[已弃用]PIXI.loader.add("images/cat.png").load(setup); //setup函数是当loader加载完图片后执行的函数 const setup = () => { //加载完图像,为加载图像创建一个精灵/创建一个精灵连接loader的resources对象 let sprite = new PIXI.Sprite(PIXI.loader.resouces["images/cat.png"].texture); }
PIXI.loader.add()可以进行链式调用以加载一系列图像.
PIXI.loader.add("images/cat.png").add("images/dog.png")..add("images/pig.png")...
当然也可以通过传参传数组进行加载多个图像.
PIXI.loader.add(["images/cat.png","images/dog.png","images/pig.png"])...
-
-3- 新版加载纹理并创建精灵的方法[推荐]
//加载图片作为纹理 不需要new const texture = PIXI.Texture.from("images/cat.png",{width:...,heght:...}(纹理尺寸可选)); //或者使用加载资产(加载资产可以加载任意类型的资产,此处假设为纹理) const texture = await Assets.load("images/cat.png"); //根据纹理创建精灵 const sprite = new PIXI.Sprite(texture); //将精灵添加到舞台中 app.stage.addChild(sprite);
精灵其实也继承于 container, 因此可以将其看作是一个container来进行使用.
精灵的一些属性/方法 (继承于container):
- width/height:...
- x/y...
anchor
: 设置精灵的中心位置(默认为左上角). sprite.anchor.set(x,y): x,y:分别表示左右/上下(0-1)tint
: 为精灵着色. 默认为白色: sprite.tint = 0XFFFFFFF;
# 精灵表(雪碧图)裁剪
精灵图裁剪广泛应用于雪碧图中.(雪碧图中有排列规则的精灵,利用裁剪可以将里面的精灵裁剪出来形成一个独立的精灵)
//加载雪碧图纹理,其中width height用来确定该雪碧图的大小 const texture = Texture.from("@/../..",{width:..,height:..}); //用于裁剪雪碧图的方块(可以是其他图形),[x,y]表示开始裁剪的位置(左上角),width,height表示裁剪的宽高 const rect = new Rectangle(x,y,width,height); //将上述裁剪规则运用到texture纹理中. texture.frame = rect; const sprite = new Sprite(texture);
使用上述方法一个一个加载精灵显得有些麻烦,因此我们可以借助第三方工具进行加载精灵.
# 使用纹理贴图集(精灵表)
在线雪碧图分割: https://image.rustwindy.com/image-tool/sprite
使用TexturePacker软件将若干个精灵组成雪碧图,并导出JSON数据记录每个精灵在雪碧图中的所在位置.
//创建纹理贴图集 const spriteSheet = new Spritesheet(BaseTexture.from(marioPng), marioData); //等待生成所有纹理 await spriteSheet.parse(); //根据marioData 生成不同的精灵 const marioBase = new Sprite(spriteSheet.textures['stanBy.png']); const marioRun = new Sprite(spriteSheet.textures['walk.png']);
基础线
# 画线步骤
pixi.js画线有以下几步:[需要严格按照顺序执行]
- 实例化
Graphics
类. - 设置填充或描边颜色.
- 画线操作.
- 将图形添加到舞台/容器中.
//实例化Graphics类 const graphics = new PIXI.Graphics(); //设置填充颜色或描边颜色 graphics.lineStyle(1,"red",1); //("线宽","颜色","透明度"); //画线操作 graphics.moveTo...; //添加到舞台中 app.stage.addChild(graphics);
# 画线
moveTo()
: 传入起始坐标.lineTo()
: 绘制到的下一个点坐标.lineTo可以有多个,表示折线的顶点.closePath()
:可选使用.将折线的终点与起点连接.
# 画圆弧
方法1:
arc(cx, cy, radius, startAngle, endAngle, anticlockwise)
:
-
cx
和cy
是圆心坐标。 -
radius
是圆弧半径。 -
startAngle
圆弧的开始角度(以弧度表示)。 -
endAngle
圆弧的结束角度(以弧度表示)。 -
anticlockwise
绘制方向,true
表示逆时针方向绘制,false
表示顺时针。默认值为false
。
方法2: 需配合moveTo进行使用.moveTo用来确定原点.
arcTo(x1, y1, x2, y2, radius)
:
x1
和y1
是弧线的起始点坐标。x2
和y2
是弧线的终点坐标。radius
是弧线的半径。
# 画贝塞尔曲线
二次贝塞尔曲线: 在使用 quadraticCurveTo()
前,需要使用 moveTo()
定义曲线的起点。
quadraticCurveTo(cpX, cpY, toX, toY)
:
cpX
和cpY
是第一个控制点。toX
和toY
是第二个控制点。
三次贝塞尔曲线:
bezierCurveTo(cpX, cpY, cpX2, cpY2, toX, toY)
基础图形
# 创建图形步骤
pixi.js创建图像有以下几步:[需要严格按照顺序执行]
- 实例化
Graphics
类. - 设置填充或描边颜色.
- 创建图形.
- 执行绘制操作.
- 将图形添加到舞台/容器中.
//实例化Graphics类 const graphics = new PIXI.Graphics(); //设置填充颜色或描边颜色 graphics.beginFill("red",1); //("颜色","透明度"); //创建图形操作 graphics.draw...; //结束设置,执行绘制操作 graphics.endFill(); //添加到舞台中 app.stage.addChild(graphics);
# 矩形
drawRect(x, y, width, height)
:
x
和y
是矩形左上角坐标的位置,这个位置是相对于画布而言的。width
和height
是矩形的宽高。
# 圆角矩形
drawRoundedRect(x, y, width, height, radius)
:
x
、y
、width
和height
都跟矩形Rect()
一样。radius
参数可以控制四个角的圆角半径。
# 倒角矩形
drawChamferRect(x, y, width, height, chamfer)
: @pixi/graphics-extras
- 参数效果同圆角矩阵.
# 倒圆角矩形
DrawFilletRect(x,y,width,height,fillet)
: @pixi/graphics-extras
- 参数效果同圆角矩阵. fillet为负数时,将会凹进去.
# 圆形
drawCircle(x, y, radius)
:
x
和y
是圆心的位置。radius
是圆的半径。
# 椭圆
drawEllipse(x, y, width, height)
:
x
和y
是椭圆的圆心位置。width
是椭圆的宽度,height
是椭圆的高度。
# 多边形
drawPolygon([x1,y1,x2,y2,x3,y3,x4,y4...])
:
-
drawPolygon()
方法可以绘制多边形,该方法接收1个参数,这个参数是一个数值型数组,用来表示多边形顶点坐标。 -
drawPolygon()
方法会自动将起始点和结束点连接起来,形成一个封闭的图形。
# 正多边形
drawRegularPolygon(x, y, radius, sides, rotation)
: @pixi/graphics-extras
x
和y
是多边形的中心点坐标。radius
是多边形的半径,也就是中心点到各个点的距离。sides
是多边形的边数,最小值是3。rotation
是多边形的旋转弧度,默认值是0。单位是弧度.
# 圆角正多边形
drawRoundedPolygon(x, y, radius, sides, corner, rotation)
: @pixi/graphics-extras
corner
是每个角的圆角半径,必传.- 其余同正多边形.
# 环形
drawTorus(x, y, innerRadius, outerRadius, startArc, endArc)
: @pixi/graphics-extras
x
和 y
是环形的圆心坐标。
innerRadius
是内圆半径。
outerRadius
是外圆半径。
startArc
环形开始位置,默认是0,单位是弧度。
endArc
环形结束位置,默认是 Math.PI * 2
,单位是弧度。
# 星形
drawStar(x, y, points, radius, innerRadius, rotation)
: @pixi/graphics-extras
-
x
和y
是星形的中心坐标。 -
points
代表点数,也就是星星有多少个角是由这个参数控制的,这个值必须大于1。 -
radius
代表整个星星的半径. -
innerRadius
: 代表内圆填充部分的半径. -
rotation
: 代表旋转弧度.
# 绘制任何形状
drawShape(path)
:
- path是通过对应图形的构造函数生成的图形对象.
const path = new PIXI.Rectangle(0, 0, 100, 100, 10); graphics.drawShape(path);
文本
创建文本的方法为:
new PIXI.Text(text, style, canvas)
:
- text:文本内容
- style:文本样式,是一个对象.具体请查看
样式设置 > 文本样式设置
- canvas:?
# BitMapText
BitMapText相比于Text 更快,但是也更有限. 它使用BitmapFont来绘制文本.这意味着改变你的文本只是改变屏幕上显示的精灵.缺点是需要完整的Unicode支持.
样式设置
# 图形/折线样式设置
图形/折线填充:
beginFill(color, alpha)
:
color
: 要填充的颜色.alpha
: 透明通道。0-1之间的值表示不同程度的半透明。
在绘制图形时,通常需要使用beginFill()
方法定义填充样式,然后使用drawRect()
、drawCircle()
等方法绘制形状,最后调用endFill()
方法结束填充。
使用 endFill()
可以结束上一个 beginFill()
方法定义的填充样式。如果没有调用endFill()
方法,则填充样式会一直应用到后续的所有形状上,直到定义新的填充样式。
图形/折线描边:
lineStyle(width, color, alpha, alignment, native)
:
-
width
: 描边宽度,默认值是 0 -
color
: 描边颜色,默认值是 0 -
alpha
: 描边的不透明度,默认值是 1 -
alignment
: 描边的对齐方式,取值:0 = inner
内部,0.5 = middle
中间,1 = outer
外部,默认值是 0. -
native
: 表示是否使用LINES
而不是TRIANGLE_STRIP
来绘制线条。默认值是false
# 文本样式设置
new PIXI.Text(text,style,canvas)
:
style: {
- fontFamily: 字体
- fontSize: 字号
- fontWeight: 字粗细
- fill: 填充色 => 单个颜色 或者 颜色数组[代表渐变]
- fillGradientType:渐变方向 [PIXI.TEXT_GRADIENT.LINEAR_VERTICAL,xxx.LINEAR_HORIZONTAL]
- stroke: 描边颜色
- strokeThickness: 描边宽度,默认是0.
- letterSpacing: 字距.
//文本阴影
- dropShadow: 是否开启文字阴影.
- dropShadowColor: 阴影颜色.
- dropShadowBlur: 模糊程度.
- dropShadowAngle: 阴影角度.
- dropShadowDistance: 阴影距离.
}
文本纹理:
-
创建文字
-
加载图片
-
将图片设置为文本的遮罩层
//创建文字 const text = new PIXI.Text(...); // 加载图片 const bunny = PIXI.Sprite.from('./pexels-alexander-grey.jpg'); // 设置图片宽高 bunny.width = app.screen.width; bunny.height = app.screen.height; //创建文本遮罩层 bunny.mask = text;
# 图片/精灵 样式设置
-
宽高
const texture = PIXI.Texture.from('./dinosaur.png'); const sprite = new PIXI.Sprite(texture); // 设置精灵宽高 sprite.width = 100 sprite.height = 100
-
位置
// 设置精灵位置 sprite.x = 100 sprite.y = 100
-
旋转
//旋转:相对于图形原点(左上角) sprite.rotation = 45 * Math.PI / 180;
-
可见性
//true为现实 false为隐藏 sprite.visible = false;
-
透明度
// 设置透明度 sprite.alpha = 0.6
-
修改图形原点(轴心点)
rectangle.pivot.set(x, y); //修改该属性会影响图形的样式/动画效果.
# 滤镜
创建滤镜: 滤镜可以放到所有容器和精灵上.
PIXI.js上所有滤镜:https://github.com/pixijs/filters
new PIXI.BlurFilter()
:
//创建滤镜 const blurfilter = new PIXI.BlurFilter(); //滤镜设置-模糊 blurfilter.blur = 10; //滤镜设置-透明 blurfilter.alpha = 2; //将滤镜添加到精灵身上 sprite.filters = [blurfilter];
添加滤镜的方式是添加到精灵的filter数组里面,因此可以组合多个滤镜添加到精灵上.
事件
所有Pixi.js支持的事件:
https://pixijs.download/release/docs/PIXI.BitmapText.html#added
- 添加事件监听:
.on()
; - 添加一次性的事件监听:
.once()
; - 删除事件监听:
.off()
; - 在合适的时机触发一次事件监听:
emit()
; - 删除所有事件监听:
removeAllListeners()
;
# 鼠标/触摸事件
PixiJS有一个功能,每当你在屏幕上点击或移动鼠标时,它都会检查你在哪个对象上,不管该对象在显示列表中有多深,并让它知道发生了一个鼠标。
鼠标/触摸的触发事件有:
仅鼠标 仅触摸 鼠标+触摸 click
tap
pointertap
mousedown
touchstart
pointerdown
mouseup
touchend
pointerup
mousemove
touchmove
pointermove
事件监听的基本步骤是:
-
将事件监听器挂载到你要监听的对象身上,例如
mario.on('click',(e)=>{...},mario); //mario是事件触发时调用的对象
e里面都有什么? => https://pixijs.download/dev/docs/PIXI.FederatedPointerEvent.html
-
开启活动模式
mario.eventMode = 'dynamic';
活动模式有以下几种:
none: 忽略所有交互,包括该对象的子级上.
passive: 不触发事件,忽略自身和非交互性的子级的触发,但是可交互的子级仍旧可以触发.
auto: 不触发事件,但是如果父级是交互式的,则触发.
static: 触发事件.
dynamic: 触发事件,并且会接收模拟交互事件.
# 键盘事件
键盘事件就是document的键盘事件,只不过在函数触发时,可能会根据需要进行**fun().bind(mario)**的执行.
$ 碰撞检测
对于一个矩形来说,我们可以获得它的:
top = y; bottom = y + height; left = x; right = x + width;
那么,对于两个矩形来说,如何判断是否碰撞了呢?
碰撞算法:
对于两个矩阵,我们可以取
rightMost_Left
: 最右边的left.leftMost_Right
: 最左边的right.bottomMost_Top
: 最下边的top.topMost_Bottom
: 最上边的bottom.
那么,如果 最右边的left > 最左边的right && 最下边的top > 最上边的bottom, 则没有发生碰撞,否则就发生了碰撞.
对于一个精灵,我们如何检测呢?
- 我们可以将精灵显示为矩形 =>
sprite.getBounds()
, 就可以利用碰撞检测算法进行碰撞检测了.
声音
加载并播放声音:
//加载声音 const sound = Sound.from("@/..."); //播放 sound.play(); //停止 sound.stop(); //暂停 sound.pause(); //继续 sound.resume(); //音量 sound.volume = 10; //静音 sound.mute = true; //循环 sound.loop = true;
更多声音相关信息请访问PIXI.JS官方文档.
动画
pixi.js中的动画分三类:
- 动画精灵: 由多个纹理组成的逐帧动画.
- Ticker: 可以自动更新场景,并在每个帧之间执行指定的代码.
- Tweens: 告诉物体需要在多少秒内到达某个状态.
- 骨骼动画: ...
# 动画精灵的使用方式
-
首先我们需要在TexturePacker中进行生成雪碧图,在生成雪碧图的时候,需要将连续的动画帧进行统一命名,例如: 'mario-walk-01','mario-walk-02','mario-walk-03'... 这样TexturePacker就可以识别到动画,并在输出的时候json文件自动添加animation属性,用来辅助创建动画精灵.
-
然后我们开始加载纹理.
const spriteSheet = new Spritesheet(BaseTexture.from(marioPng), marioData); await spriteSheet.parse();
-
创建一个动画精灵.
const marioWalk = new AnimationSprite(spriteSheet.animation(['mario-walk'])); // * 除此之外,如果你想自定义动画(不使用Texture创建的动画),这里也有一些方法(不推荐) //首先,将所有与动画有关的纹理取出来. const marioWalk1 = spriteSheet.texture['mario-walk-01']; const marioWalk2 = spriteSheet.texture['mario-walk-02']; //创建动画精灵 const marioWalk = new AnimationSprite([marioWalk1,marioWalk2]);
-
进行动画精灵的一些设置.
marioWalk.position.set(100,100); //动画播放速度 marioWalk.animationSpeed = 0.1; //默认1 //是否自动更新动画 marioWalk.autoUpdate = true; //默认true //动画精灵的当前帧/起始帧 marioWalk.currentFrame = 0; //默认0 //动画精灵是否循环播放 marioWalk.loop = true; //默认true //开始播放动画 marioWalk.play(); //结束动画播放 marioWalk.stop();
动画精灵的一些其他属性/方法: https://pixijs.download/dev/docs/PIXI.AnimatedSprite.html
# Ticker的使用方式
Pixi.js
提供了一个处理循环的对象ticker
,它是Pixi.js
的核心组件之一。这个对象可以帮助我们创建各种类型的动画效果,例如移动、旋转、缩放等。ticker
可以自动更新场景,并在每个帧之间执行我们指定的代码。
使用:
... graphics.drawRect(0,0,100,100); ... const ticker = Ticker.shared; //添加动画 ticker.add((delta)=>{ graphics.x += delta * 0.1; }) //动画速度 ticker.speed = 1; //默认1 //是否自动开始动画 ticker.autoStart = false; //默认true //开始动画 ticker.start(); //结束动画 ticker.stop();
ticker
是一个用来处理循环的对象,它负责在每一帧更新和重新渲染画布。delta
是一个与时间相关的因子,通常用于处理动画循环。delta
是上一帧和当前帧之间经过的时间的比例值。这个值可以用于确保动画在不同性能和速度的设备上尽可能保持一致的表现。
Ticker的一些其他属性/方法: https://pixijs.download/dev/docs/PIXI.Ticker.html
# Tweens使用方式 需要额外导包: "pnpm i tweedle.js";
Tweens致力于哪些精灵/图像 在一定时间内从一个状态转换到另一个状态时的动画而不必关心其转换的中间过程.(这和css 的animation很像)
使用:
import { Tween, Group } from "tweedle.js"; //创建一个Tweens,参数为需要改变的状态 const tweens = new Tweens(mario.position); //要到达的在状态以及到达该状态时经过的时间 tweens.to({x:100,y:100},1000).start(); //使其状态不断更新 const tweenLoop = () => { Group.shared.update(); requestAnimationFrame(tweenLoop); }; tweenLoop();
Tween的一些其他属性和方法: https://miltoncandelero.github.io/tweedle.js/
粒子
pixi在线粒子编辑器: https://pixijs.io/pixi-particles-editor/
使用粒子需要导包: npm install pixi-particles
使用粒子编辑器编辑完毕之后,可以下载一个json文件,然后放入Emitter构造函数.
Recipes:预加载资产
# 预加载资产
我们可以列一个资产的清单,然后用Assets.load()进行资产的加载.
为什么这样是可行的? 因为资产一旦被加载过了,后续再次使用的时候将直接从缓存中获取,而不需要再次加载.
构建清单:
import type { ResolverManifest } from "pixi.js"; export const manifest:ResolverManifest = { bundles: [ //加载纹理 { name : "bundleName", assets: { "Clampy the clamp" : "./clampy.png", "another image" : "./monster.png", } }, //加载声音 { name : "another bundle", assets: { "whistle" : "./whistle.mp3", } }, //加载精灵表 { name : "bundleName", assets: { "mario-walk": "./yourSpritesheetUrl.json", // 不要加载整个png文件,只需要加载.json文件即可,但是要确保png文件就在json文件旁边. } }, ] }
使用清单进行预加载:
import { mainfest } from "..."; await Assets.init({mainfest:mainfest}); const bundleIds = manifest.bundles.map(bundle => bundle.name); await Assets.loadBundle(bundleIds); //进行其他操作.
设置进度条:
在Assets.loadBundle()函数中,可以从传第二个参数:
... await Assets.loadBundle(bundleIds,(progressRatio:number)=>{ console.log(progressRatio); //progressRatio => [0-1]表示加载进度. }); ...
加载资源完毕后如何使用
//加载纹理 Texture.from("the name in manifest : such as 'Clampy the clamp'") Sprite.from("the name in manifest : such as 'Clampy the clamp'"); //加载声音 import { sound } from "pixi.js"; //小写的sound sound.play("the name in manifest : such as 'Clampy the clamp'");