mcpField
P
pixijs-scene-container

pixijs-scene-container

github.com/pixijs开发3586.0k 次安装4 周前更新
代码仓库

关于 pixijs-scene-container

面向 PixiJS 开发者的技能,介绍如何用 Container 组织 v8 场景图,涵盖子对象管理、变换、排序、边界、事件和生命周期操作。

主要功能

  1. 01组织场景对象并应用子树变换
  2. 02管理子对象顺序、插入和重新挂接
  3. 03介绍变换、边界与坐标转换
  4. 04讲解排序和渲染组
  5. 05说明事件、渲染回调与销毁

适用场景

  1. 01组合精灵及其他叶对象
  2. 02组织场景层级与绘制顺序
  3. 03在局部和全局坐标间转换点
  4. 04移除或销毁场景子树

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pixijs/pixijs-skills --skill pixijs-scene-container

安装后在 Claude Code 中输入/pixijs-scene-container确认已加载。

详细介绍

Container 是 PixiJS v8 场景图中的通用节点。它包含子对象,并对整个子树应用变换、透明度、色调和混合模式。你创建的每个显示对象,要么是用于构建分支的 Container,要么是嵌套在其中的叶节点(Sprite、Graphics、Text、Mesh)。

假设你熟悉 pixijs-scene-core-concepts。

快速入门

const group = new Container({
  label: "hero-group",
  x: 200,
  y: 150,
  sortableChildren: true,
});

const body = new Sprite(await Assets.load("body.png"));
const head = new Sprite(await Assets.load("head.png"));
head.position.set(0, -40);
head.zIndex = 1;

group.addChild(body, head);
group.pivot.set(group.width / 2, group.height / 2);
group.scale.set(1.5);

app.stage.addChild(group);

相关技能: pixijs-scene-core-concepts(场景图的思维模型、遮罩、图层、渲染组)、pixijs-scene-sprite / pixijs-scene-graphics / pixijs-scene-text / pixijs-scene-mesh(放入容器的叶对象)、pixijs-events(eventMode、命中测试)、pixijs-math(Matrix、toGlobal/toLocal 详情)、pixijs-performance(cacheAsTexture、剔除、渲染组)。

核心模式

构造函数选项
const container = new Container({
  label: "world",
  x: 100,
  y: 50,
  scale: 2,
  rotation: Math.PI / 4,
  alpha: 0.8,
  visible: true,
  tint: 0xffaa00,
  blendMode: "add",
  sortableChildren: true,
  isRenderGroup: true,
  origin: { x: 0, y: 0 },
  boundsArea: new Rectangle(0, 0, 1920, 1080),
});

所有 Container 选项(position、scale、tint、label、filters、zIndex 等)在这里也都有效——参见 skills/pixijs-scene-core-concepts/references/constructor-options.md。

Container 构造函数使用 assignWithIgnore 将选项对象中的每个字段批量复制到实例上,但 children、parent 和 effects 除外。Container 的任何公共属性都是有效的构造函数选项:cullable、cullArea、mask、filterArea、eventMode、hitArea 等。上面的选项块列出了最常用的选项;完整列表请参见上述共享参考文档。

isRenderGroup: true 会将容器提升为独立的渲染组,使其变换在 GPU 上应用,而不是由 CPU 逐个子对象重新计算。将它用于稳定的子树(大型静态世界、UI 图层)。不要过度使用;大多数场景不需要显式渲染组,渲染组过多会降低性能。提升前请先进行性能分析。参见 pixijs-scene-core-concepts/references/scene-management.md。

sortableChildren: true 会使子对象在下一次渲染时按 zIndex 重新排序。参见下文的 zIndex。

origin 是 v8 中的一等变换辅助属性:一个 ObservablePoint,可作为旋转/缩放中心,而不会移动容器。pivot 会在父级空间中偏移局部原点的投影(因此更改它会使对象移位);而 origin 不改变位置,只会围绕指定的局部点进行旋转/缩放。它接受 PointData、单个数字(同时应用于两个轴),也可以通过 container.origin.set(x, y) 实时设置。在同一个容器上同时设置 pivot 和 origin 会产生叠加效果,不建议这样做;请选择其中一个。

boundsArea 会强制 getBounds() 返回固定矩形,而不是递归测量子对象;对于包含数百个开销低且边界可预测的子对象的容器,这能提升性能。

cullable 和 cullArea 是有效的构造函数选项(assignWithIgnore 会像复制其他字段一样复制它们),但它们记录在 pixijs-performance 中,因为剔除设置属于性能方面,而非场景图方面。

叶节点与容器
const parent = new Container();
const sprite = new Sprite(texture);

parent.addChild(sprite);

只有 Container(以及旨在容纳子对象的子类,如 RenderLayer)才应拥有子对象。按照 PixiJS v8 的惯例,Sprite、Graphics、Text、Mesh、ParticleContainer 粒子和 DOMContainer 内容都是叶节点。需要对对象分组时,请将它们包在 Container 中:让容器负责布局逻辑,并让叶节点只保存视觉数据。向叶节点添加子对象会记录弃用警告,计划在未来改为直接报错。

添加和移除子项
const parent = new Container();

parent.addChild(a, b, c);
parent.addChildAt(d, 0);
parent.swapChildren(a, b);
parent.setChildIndex(c, 0);

parent.removeChild(b);
parent.removeChildAt(0);
parent.removeChildren();

parent.removeChildren(0, 2);

addChild 接受任意数量的子项,并返回第一个子项。子项按数组顺序渲染:索引 0 的子项最先绘制(位于后方),最后一个索引的子项最后绘制(位于前方)。addChildAt 在指定索引处插入子项;setChildIndex 移动现有子项;swapChildren 交换两个子项的位置。

removeChildren(beginIndex?, endIndex?) 移除一个切片并返回被移除的数组。

如果调用 addChildAt 时,子项已经属于同一个容器,它会静默地将子项移动到新索引。不会触发 added / childAdded / removed / childRemoved 事件,因为父子关系并未改变。只有当子项来自不同的父项(或没有父项)时,才会触发事件。

如果要在重新设置父项时保留世界变换(使子项不会在视觉上跳动),请使用 reparentChild / reparentChildAt。如果要在原位替换子项并复制旧子项的局部变换,请使用 replaceChild。

变换属性
const obj = new Container();

obj.x = 100;
obj.y = 200;
obj.position.set(100, 200);

obj.scale.set(2);
obj.scale = 2;

obj.rotation = Math.PI / 4;
obj.angle = 45;

obj.pivot.set(50, 50);
obj.skew.set(0.1, 0.2);

obj.alpha = 0.5;
obj.tint = 0xff0000;
obj.visible = false;
obj.renderable = false;
  • position、scale、pivot、skew 是 ObservablePoint。赋值 scale = 2 有效,并会设置两个轴。
  • rotation 的单位是弧度;angle 的单位是度;两者是保持同步的别名。
  • pivot 设置局部空间中的一个点,该点映射到父空间中的 position;更改它会同时移动和旋转容器。
  • alpha 和 tint 会沿子项层级逐级相乘。blendMode 应用于此容器的绘制指令。
  • visible = false 会跳过渲染和变换更新。renderable = false 会跳过渲染,但仍会更新变换(当需要在不绘制的情况下调用 getBounds() 或进行命中测试时使用)。
zIndex 和 sortableChildren
const world = new Container({ sortableChildren: true });

const ground = new Sprite(groundTexture);
ground.zIndex = 0;

const player = new Sprite(playerTexture);
player.zIndex = 10;

const ui = new Sprite(uiTexture);
ui.zIndex = 100;

world.addChild(ground, player, ui);

当 sortableChildren 为 true 时,容器会在下一次渲染前按 zIndex 对子项重新排序。更改任何子项的 zIndex 都会自动将父项标记为需要排序。只对确实需要排序的对象启用排序;关闭 sortableChildren 开销更低。如果需要完全手动控制,请在更改 zIndex 值后自行调用 container.sortChildren()。

如果需要一种与层级结构解耦的渲染顺序控制方式(子项保留其逻辑父项以进行变换,但以不同的 z 顺序渲染),请使用 RenderLayer。请参阅 pixijs-scene-core-concepts/references/scene-management.md。

边界和坐标转换
const bounds = container.getBounds();
console.log(bounds.x, bounds.y, bounds.width, bounds.height);

const rect = container.getBounds().rectangle;

const local = new Point(10, 20);
const world = container.toGlobal(local);
const backToLocal = container.toLocal(world);

const selfPos = container.getGlobalPosition();

getBounds() 返回一个 Bounds 对象(而不是 Rectangle);它提供 x、y、width、height,以及一个 .rectangle getter,供需要实际 Rectangle 的 API 使用。其签名为 getBounds(skipUpdate?: boolean, bounds?: Bounds)——将第一个参数设为 true 可跳过强制变换更新;将可选的 Bounds 实例作为第二个参数传入,可避免分配新实例。

toGlobal(point) 将此容器局部空间中的点转换为场景根空间中的点。toLocal(point, from?) 将另一个容器局部空间中的点(如果省略 from,则为全局空间中的点)转换到此容器的局部空间。getGlobalPosition() 是 parent.toGlobal(this._position) 的简写。

尺寸设置
const sprite = new Sprite(texture);

sprite.setSize(200, 100);
const { width, height } = sprite.getSize();

setSize 会调整 scale,使容器的边界符合指定的像素尺寸,并一次性完成操作。分别设置 .width 和 .height 也可行,但每次赋值都会单独重新计算边界;同时更改两个轴时,优先使用 setSize。

容器事件
const parent = new Container();

parent.on("childAdded", (child, container, index) => {
  console.log("added at", index, child.label);
});

parent.on("childRemoved", (child, container, index) => {
  console.log("removed from", index);
});

const child = new Container();
child.on("added", (newParent) => console.log("entered", newParent.label));
child.on("removed", (oldParent) => console.log("left", oldParent.label));
child.on("visibleChanged", (visible) => console.log("visible:", visible));
child.on("destroyed", (destroyed) => console.log("gone", destroyed.label));

parent.addChild(child);
事件触发时机参数
childAdded父容器接收到子对象时(child, container, index)
childRemoved父容器失去子对象时(child, container, index)
added子对象被添加时(parent)
removed子对象被移除时(parent)
destroyed容器被销毁时(container)
visibleChanged容器的 visible 值发生变化时(visible)

这些事件由 Container 的 EventEmitter 端发出;请勿将它们与 pixijs-events 的指针事件混淆。

destroyed 会在内部清理完成后、移除监听器之前触发,因此处理函数运行时,position、scale、pivot、origin、skew 和 parent 都已设为 null,且 children 已清空(长度为 0,但数组引用本身未设为 null)。请在调用 destroy() 之前 从容器中保存所需数据,而不要在处理函数中保存。

使用 onRender 进行逐帧更新
const container = new Container();

container.onRender = (renderer) => {
  container.rotation += 0.01;
};

container.onRender = null;

渲染容器时,onRender 会在每一帧运行,并接收当前活动的 Renderer。可用它执行与特定容器相关的轻量动画或逐帧更新。在 v7 中,这种做法是通过重写 updateTransform 实现的;而在 v8 中,updateTransform 不再每帧运行。将 onRender = null 可移除回调。

查找并从父容器中移除
const player = world.getChildByLabel("player");
const enemies = world.getChildrenByLabel(/enemy-\d+/, true);

const bounds = hud.getLocalBounds();

oldSprite.removeFromParent();
  • getChildByLabel(label, deep?) — 按字符串或 RegExp 查找第一个匹配项。传入 true 可进行递归搜索。
  • getChildrenByLabel(label, deep?, out?) — 查找所有匹配项。可选传入可重复使用的输出数组。
  • getLocalBounds() — 返回此容器自身坐标空间内的边界,不考虑父级变换。用于自包含的布局计算时,比 getBounds() 开销更低。
  • removeFromParent() — 将 this 从当前父容器中移除(如果已没有父容器,则不执行任何操作)。
销毁
container.destroy();

container.destroy({
  children: true,
  texture: true,
  textureSource: true,
});

console.log(container.destroyed);

默认情况下,destroy() 会将此容器从父容器中移除,并清理自身状态。传入 { children: true } 可递归销毁所有后代;通常销毁整个子树时会这样调用。texture: true 和 textureSource: true 还会销毁叶子子对象引用的 GPU 资源(适用于纹理仅为这些精灵加载的情况)。如果启用了 cacheAsTexture,请在销毁前通过 container.cacheAsTexture(false) 将其禁用。

常见错误

[CRITICAL] 向叶子场景对象添加子对象

错误:

const sprite = new Sprite(texture);
const overlay = new Sprite(overlayTexture);
sprite.addChild(overlay);

正确:

const group = new Container();
const sprite = new Sprite(texture);
const overlay = new Sprite(overlayTexture);
group.addChild(sprite, overlay);

Sprite、Graphics、Text 和 Mesh 都是叶子对象。向它们添加子对象现在会记录弃用警告,未来版本中将会报错。需要分组时,请始终使用 Container 包裹它们。

[HIGH] 误以为 getBounds() 会返回 Rectangle

错误:

const rect = container.getBounds();
rect.contains(x, y); // TypeError: contains is not a function

正确:

const rect = container.getBounds().rectangle;
rect.contains(x, y);

const bounds = container.getBounds();
console.log(bounds.width, bounds.height);

在 v8 中,getBounds() 返回 Bounds 实例。它的基本 getter(x、y、width、height)可以正常使用;但对于 .contains() 等 Rectangle 专属方法,或需要传入 Rectangle 的 API,请读取 .rectangle 属性。

[HIGH] 使用 cacheAsBitmap 而非 cacheAsTexture

错误:

container.cacheAsBitmap = true;

正确:

container.cacheAsTexture(true);

v7 中的 cacheAsBitmap 属性在 v8 中更名为 cacheAsTexture() 方法。调用 destroy() 之前,请始终通过 cacheAsTexture(false) 将其禁用。

[MEDIUM] 使用 container.name 而非 container.label

在 v8 中,name 更名为 label。旧属性仍可作为已弃用的别名使用;在 v8 中,使用 getChildByLabel 按名称查找子对象。

[MEDIUM] 在同一个容器上同时设置 pivot 和 origin

Pivot 会在父级空间中移动局部原点的投影(改变旋转中心的同时,也会使对象移动)。Origin 会改变旋转/缩放中心,但不会造成位移。在同一个容器上同时设置两者会产生意外的叠加效果;请选择其中一个。

API 参考

评论

支持 Markdown