关于 pixijs-math
pixijs-math 是面向 PixiJS 开发者的数学技能,介绍坐标、变换与几何操作。内容涵盖核心数学类型及需单独导入的扩展方法。
主要功能
- 01点与可观察点操作
- 02仿射矩阵变换
- 03坐标空间转换
- 04形状检测与边界
- 05math-extras 向量及矩形扩展
适用场景
- 01计算场景节点的变换
- 02检测指针是否位于形状内
- 03获取矩形交集或并集
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ pixijs/ pixijs-skills --skill pixijs-math安装后在 Claude Code 中输入/pixijs-math确认已加载。
详细介绍
PixiJS 提供轻量级数学基础类型(Point、Matrix、形状类),供库中各处用于变换、命中测试和坐标转换。导入 pixi.js/math-extras 可添加向量运算(add、dot、magnitude、reflect)以及 Rectangle 的相交和并集辅助方法。
快速开始
const parent = new Container();
parent.position.set(100, 100);
parent.scale.set(2);
app.stage.addChild(parent);
const child = new Container();
child.position.set(50, 50);
parent.addChild(child);
const globalPt = child.toGlobal(new Point(0, 0));
const m = new Matrix()
.translate(100, 50)
.rotate(Math.PI / 4)
.scale(2, 2);
const world = m.apply(new Point(10, 20));
const hitArea = new Rectangle(0, 0, 200, 100);
console.log(hitArea.contains(50, 50));
相关技能: pixijs-scene-container(变换属性)、pixijs-events(hitArea 的用法)、pixijs-scene-core-concepts(使用 Rectangle 进行剔除)。
核心模式
Point 和 ObservablePoint
Point 是简单的 {x, y} 值类型。ObservablePoint 会在 x 或 y 发生变化时触发回调;Container 的 position、scale、pivot、origin 和 skew 内部均使用它。
import { Point } from "pixi.js";
const p = new Point(10, 20);
p.set(30, 40); // set both
p.set(50); // x=50, y=50
const clone = p.clone();
console.log(p.equals(clone)); // true
p.copyFrom({ x: 1, y: 2 }); // accepts any PointData
// Point.shared: temporary point, reset to (0,0) on each access
const temp = Point.shared;
temp.set(100, 200);
// do not store a reference to Point.shared
Container 的 position、scale、pivot、origin 和 skew 等属性都是 ObservablePoint。在这些属性上设置 .x 或 .y 会自动触发变换重新计算。
import { Container } from "pixi.js";
const obj = new Container();
obj.position.set(100, 200); // triggers observer -> marks transform dirty
obj.position.x = 150; // also triggers observer
Matrix(2D 仿射变换)
Matrix 表示一个 3x3 仿射变换:| a c tx | b d ty | 0 0 1 |。它支持 translate、scale、rotate、append、prepend、invert 和 decompose。
import { Matrix, Point } from "pixi.js";
// Build a transform
const m = new Matrix()
.translate(100, 50)
.rotate(Math.PI / 4)
.scale(2, 2);
// Transform a point (local -> parent space)
const local = new Point(10, 20);
const world = m.apply(local);
// Inverse transform (parent -> local space)
const backToLocal = m.applyInverse(world);
// Combine matrices
const a = new Matrix().translate(50, 0);
const b = new Matrix().rotate(Math.PI / 2);
a.append(b); // a = a * b
// Decompose into position/scale/rotation/skew
const transform = {
position: new Point(),
scale: new Point(),
pivot: new Point(),
skew: new Point(),
rotation: 0,
};
m.decompose(transform);
console.log(transform.rotation); // ~0.785 (PI/4)
// A mirrored matrix (one axis flipped, axes still perpendicular) decomposes into
// rotation + negative scale.x with zero skew. scale.y is never negative: a y-axis
// flip comes back as scale.x = -1 plus a half turn. Either way the parts rebuild the matrix.
const mirrored = new Matrix().rotate(Math.PI / 6).scale(-1, 1);
mirrored.decompose(transform);
transform.scale.x; // -1
transform.skew.x; // 0
// Shared temporary matrix (reset on each access)
const temp = Matrix.shared;
// IDENTITY is read-only reference
const isDefault = m.equals(Matrix.IDENTITY);
通过 Container 进行坐标变换
Container 提供 toGlobal、toLocal 和 getGlobalPosition,用于坐标转换。
import { Container, Point } from "pixi.js";
const parent = new Container();
parent.position.set(100, 100);
parent.scale.set(2);
const child = new Container();
child.position.set(50, 50);
parent.addChild(child);
// Local point in child's space -> global (world) space
const globalPt = child.toGlobal(new Point(0, 0));
// globalPt = { x: 200, y: 200 } (100 + 50*2, 100 + 50*2)
// Global point -> child's local space
const localPt = child.toLocal(new Point(200, 200));
// localPt = { x: 0, y: 0 }
// Convert between two containers
const other = new Container();
other.position.set(300, 300);
const ptInOther = child.toLocal(new Point(10, 10), other);
形状和命中测试
Rectangle、Circle、Ellipse、Polygon、RoundedRectangle 和 Triangle 均实现了 contains(x, y),用于测试点是否位于形状内;还提供 getBounds(out?) 和 strokeContains(x, y, width, alignment?)(Triangle 会忽略 alignment,始终测试居中的描边)。可将它们用作容器的 hitArea,以定义自定义交互区域。
import { Rectangle, Circle, Ellipse, Polygon, Container } from "pixi.js";
const rect = new Rectangle(0, 0, 200, 100);
rect.contains(50, 50); // true
rect.contains(300, 50); // false
rect.left; // 0
rect.right; // 200
rect.top; // 0
rect.bottom; // 100
rect.isEmpty(); // false (Rectangle.EMPTY returns a fresh empty rect)
// Native Rectangle-to-Rectangle methods (no math-extras needed)
const other = new Rectangle(50, 50, 100, 100);
rect.containsRect(other); // true if `other` is fully inside `rect`
rect.containsRect(new Rectangle(100, 0, 100, 100)); // true: a flush right edge counts
rect.containsRect(rect.clone()); // true: identical rects contain each other
rect.intersects(other); // boolean: do they overlap at all?
rect.intersects(other, matrix); // overlap after transforming `other`
// Stroke hit testing (alignment: 1 = inner, 0.5 = centered, 0 = outer)
rect.strokeContains(0, 50, 4); // true if (0,50) lies on a 4px centered stroke
const circle = new Circle(100, 100, 50);
circle.strokeContains(150, 100, 4, 1); // inner-aligned stroke check
// A stroke that reaches past the shape's centre covers the whole interior; there is no hole
new Ellipse(0, 0, 10, 10).strokeContains(0, 0, 40, 1); // true
// getBounds works on every shape (returns a Rectangle, accepts an out param)
const bounds = circle.getBounds();
const reused = new Rectangle();
new Polygon([0, 0, 100, 0, 50, 100]).getBounds(reused);
// Use as hit area for interaction
const button = new Container();
button.hitArea = new Rectangle(0, 0, 200, 50);
button.eventMode = "static";
button.on("pointerdown", () => {
/* clicked */
});
不要将原生 Rectangle.intersects(other)(返回 boolean)与 math-extras 的 intersection(other)(返回描述重叠区域的 Rectangle)混淆。
Rectangle 布局辅助方法
Rectangle 提供了可变辅助方法,常用于 UI/布局、边界聚合和像素对齐。所有方法都会返回 this,以便链式调用。
import { Rectangle } from "pixi.js";
const r = new Rectangle(10, 10, 100, 50);
r.pad(5); // grow on all sides: x=5, y=5, w=110, h=60
r.pad(10, 4); // separate horizontal/vertical padding
r.scale(2); // multiply x, y, width, height by 2
// fit shrinks `this` to lie inside another rect (clipping)
const viewport = new Rectangle(0, 0, 200, 200);
new Rectangle(150, 150, 200, 200).fit(viewport); // -> 150, 150, 50, 50
// enlarge expands `this` to include another rect (bounds aggregation)
const total = new Rectangle();
items.forEach((item) =>
total.enlarge(new Rectangle().copyFromBounds(item.getBounds())),
);
// ceil snaps to a pixel grid (resolution: 1 = whole pixels, 2 = half pixels)
new Rectangle(10.2, 10.6, 100.8, 100.4).ceil();
// copy a Container/Mesh bounds object straight into a Rectangle
new Rectangle().copyFromBounds(container.getBounds());
Polygon
Polygon 支持四种构造函数参数格式:扁平数字数组、类点对象数组,或将上述任一种作为展开参数传入。
import { Polygon, Point } from "pixi.js";
new Polygon([0, 0, 100, 0, 50, 100]); // flat numbers
new Polygon([new Point(0, 0), new Point(100, 0), new Point(50, 100)]); // PointData[]
new Polygon(0, 0, 100, 0, 50, 100); // spread numbers
new Polygon(new Point(0, 0), new Point(100, 0), new Point(50, 100)); // spread points
const poly = new Polygon([0, 0, 100, 0, 100, 100, 0, 100]);
poly.points; // [0, 0, 100, 0, 100, 100, 0, 100] (mutable flat array)
poly.closePath; // true by default; false produces an open path
poly.startX; // 0 - first vertex
poly.lastX; // 0 - last vertex (lastY for y)
poly.isClockwise(); // shoelace winding test (useful for SVG hole detection)
// Polygon-in-polygon containment for hole detection
const outer = new Polygon([0, 0, 100, 0, 100, 100, 0, 100]);
const hole = new Polygon([25, 25, 75, 25, 75, 75, 25, 75]);
outer.containsPolygon(hole); // true
// Stroke hit test follows the drawn stroke: alignment 1 = inside, 0.5 = centered,
// 0 = outside, independent of winding order
outer.strokeContains(-5, 50, 20); // true (centered stroke extends 10px outward)
outer.strokeContains(-5, 50, 20, 1); // false (inner stroke stays inside)
outer.strokeContains(5, 50, 20, 0); // false (outer stroke stays outside)
常量
import { DEG_TO_RAD, RAD_TO_DEG, PI_2 } from "pixi.js";
const angle = 45 * DEG_TO_RAD; // 0.785...
const degrees = angle * RAD_TO_DEG; // 45
const fullCircle = PI_2; // Math.PI * 2
类型
PointData- 最简{x, y}接口,大多数 API 都接受此类型。为只需读取坐标的参数标注类型时可使用它。PointLike- 在PointData的基础上扩展了set()、copyFrom()、copyTo()、equals()。Point和ObservablePoint均实现此接口。Size-{ width, height }接口,用于渲染器/画布 API。SHAPE_PRIMITIVE- 字符串字面量联合类型:'rectangle' | 'circle' | 'ellipse' | 'polygon' | 'roundedRectangle' | 'triangle'。所有形状都提供type,因此你无需使用instanceof就能进行分支判断。
import type { PointData } from "pixi.js";
function distance(a: PointData, b: PointData): number {
const dx = a.x - b.x;
const dy = a.y - b.y;
return Math.sqrt(dx * dx + dy * dy);
}
math-extras(副作用导入)
import 'pixi.js/math-extras' 通过原型扩展为 Point、ObservablePoint 和 Rectangle 添加方法。默认包中不包含这些方法。
import "pixi.js/math-extras";
import { Point } from "pixi.js";
Point / ObservablePoint 向量方法
所有方法都接受可选的 out 参数,以避免分配内存。不传入 out 时会返回一个新的 Point。
const a = new Point(3, 4);
const b = new Point(1, 2);
// Arithmetic
const sum = a.add(b); // Point(4, 6)
const diff = a.subtract(b); // Point(2, 2)
const prod = a.multiply(b); // Point(3, 8) - component-wise
const scaled = a.multiplyScalar(2); // Point(6, 8)
// Dot and cross product
const dot = a.dot(b); // 11
const cross = a.cross(b); // 2 (z-component of 3D cross)
// Length
const len = a.magnitude(); // 5
const lenSq = a.magnitudeSquared(); // 25 (faster for comparisons)
// Normalize to unit vector
const unit = a.normalize(); // Point(0.6, 0.8)
// Projection and reflection
const proj = a.project(b); // project a onto b
const refl = a.reflect(new Point(0, 1)); // reflect across normal
// Rotation
const rotated = a.rotate(Math.PI / 2); // rotate 90 degrees
// Reuse existing point to avoid allocation
const out = new Point();
a.add(b, out); // result written to out
Rectangle 扩展方法
containsRect 和 intersects 是 Rectangle 的原生方法(见上文)。math-extras 添加了 equals、intersection(返回重叠矩形)和 union:
import "pixi.js/math-extras";
import { Rectangle } from "pixi.js";
const r1 = new Rectangle(0, 0, 100, 100);
const r2 = new Rectangle(50, 50, 100, 100);
r1.equals(r2); // false
const overlap = r1.intersection(r2); // Rectangle(50, 50, 50, 50)
const envelope = r1.union(r2); // Rectangle(0, 0, 150, 150)
// Optional out parameter
const out = new Rectangle();
r1.intersection(r2, out);
几何工具函数
这些函数从 pixi.js/math-extras 导出,不属于主 pixi.js 入口。
import {
floatEqual,
lineIntersection,
segmentIntersection,
} from "pixi.js/math-extras";
import { Point } from "pixi.js";
// Epsilon-based float comparison (default epsilon: Number.EPSILON)
floatEqual(0.1 + 0.2, 0.3, 1e-10); // true with reasonable epsilon
floatEqual(1.0, 1.001, 0.01); // true (custom epsilon)
// Unbounded line intersection (returns {x: NaN, y: NaN} if parallel)
const hit = lineIntersection(
new Point(0, 0),
new Point(10, 10), // line A
new Point(10, 0),
new Point(0, 10), // line B
); // Point(5, 5)
if (isNaN(hit.x)) {
/* lines are parallel */
}
// Bounded segment intersection (returns {x: NaN, y: NaN} if segments don't cross)
const segHit = segmentIntersection(
new Point(0, 0),
new Point(10, 10),
new Point(10, 0),
new Point(0, 10),
); // Point(5, 5)
if (isNaN(segHit.x)) {
/* segments don't intersect */
}
常见错误
高优先级:从 @pixi/math 导入
错误:
import { Point } from "@pixi/math";
正确:
import { Point } from "pixi.js";
v8 使用单一的 pixi.js 包。@pixi/math、@pixi/core 等所有子包均已移除。
中等:修改 ObservablePoint 时未触发观察者
错误:
// Replacing the reference loses observation
let pos = container.position;
pos = new Point(100, 200); // container.position unchanged
正确:
// Mutate in place to trigger the observer
container.position.set(100, 200);
// or
container.position.x = 100;
container.position.y = 200;
// or copy from another point
container.position.copyFrom(new Point(100, 200));
容器的位置、缩放、轴心、原点和倾斜都是 ObservablePoint。设置它们的 .x 或 .y 会触发容器的变换更新。重新赋值变量引用不会修改容器。请始终通过 .set()、.copyFrom(),或直接为原对象的属性赋值来修改现有的 ObservablePoint。
中等:未导入 math-extras,无法使用扩展方法
错误:
import { Point } from "pixi.js";
const p = new Point(1, 2);
p.add(new Point(3, 4)); // TypeError: p.add is not a function
正确:
import "pixi.js/math-extras";
import { Point } from "pixi.js";
const p = new Point(1, 2);
const sum = p.add(new Point(3, 4)); // works
扩展数学工具(Point 上的 add、subtract、multiply、magnitude、normalize、dot、cross 等方法,以及形状上的相交方法)需要显式执行 import 'pixi.js/math-extras'。这些工具不包含在默认包中。
中等:存储对共享/临时对象的引用
错误:
const myPoint = Point.shared;
myPoint.set(100, 200);
// ... later ...
console.log(myPoint.x); // 0 (reset on next access)
正确:
const myPoint = new Point();
myPoint.copyFrom(Point.shared.set(100, 200));
// or just
const myPoint = new Point(100, 200);
每次访问 Point.shared 和 Matrix.shared 时,它们都会重置为零值/单位矩阵。它们用于单个表达式中的一次性计算。切勿存储对它们的引用。