Published on

PixiJS 源码揭秘 - 3. Scene 与 Container 模块详解

Authors
  • avatar
    Name
    青雲
    Twitter

友情提示,本文篇幅较长,且多为代码细节解释。若你对细节不感兴趣,可直接跳转到第三章查看类图,了解 Container 类的整体架构;或者在有需要时,找到对应方法查看详细解释。Container 类是其他显示元素的基类,它为各种显示对象提供了基础的功能和属性框架。

一、引言

PixiJS 的 Scene 模块在图形渲染中起着关键作用,它提供了管理和组织显示对象的功能,让开发者能够轻松构建复杂场景图。其中包含 Sprite、Graphics 和 Text 等显示对象类,可高效渲染图形和文本。通过层次化对象结构,实现对显示对象的添加、删除和排序,提供灵活的场景管理能力。

在实际应用中,Scene 模块用于创建和管理显示对象、处理事件与交互、实现层次化渲染以及优化渲染性能。

其中,Container 类作为 Scene 中所有显示对象的基础类,具有重要地位。它为其他可渲染对象提供了组织和管理的框架,就像舞台上的导演,安排各个角色的位置、大小和层级关系,使整个场景有条不紊地呈现。Container 能够将多个显示对象组合成更复杂的结构,方便开发者对多个对象进行统一管理,同时还提供丰富的属性和方法,增强了 Scene 模块的灵活性和可扩展性。

二、Container 在 Scene 中的角色

与 Scene 的关系

Container 类对 PixiJS 的 Scene 模块整体架构影响深远,同时 Container 自身具有几个关键作用:

Container 的关键作用:

  1. 对象分组:Container可以包含其他显示对象,形成一个树状结构。这种分层结构便于管理复杂的场景。在 Scene 中,不同类型的显示对象如图片、文本、图形等可以通过 Container进行有效的组织,使得场景的构建更加清晰和有条理。
  2. 子节点管理:通过 addChildremoveChild等方法,可以方便地向 Container添加或移除子节点。这使得开发者在构建场景时能够动态地调整显示对象的组合,满足不同的需求。例如,在游戏中可以根据不同的场景状态添加或移除特定的角色或道具。
  3. 层级控制:通过 children数组和相关的排序方法,可以灵活地控制显示对象的渲染顺序。在 Scene 中,正确的渲染顺序对于呈现复杂的视觉效果至关重要。Container的层级控制功能允许开发者精确地安排各个显示对象的前后关系,确保重要的元素能够优先显示。
  4. 统一的属性管理:Container提供了位置、缩放、旋转、透明度等属性,这些属性能够传递给所有的子节点,实现统一的变换和渲染。在 Scene 中,这一功能大大简化了对复杂场景的管理。开发者可以通过调整 Container的属性,一次性地影响其中的所有子节点,提高了开发效率。

对架构的影响:

  1. 结构层次化:提供树状层次结构,便于管理复杂场景。所有显示对象归属于容器,可递归遍历和管理。
    • Scene 模块通过 Container实现了对显示对象的层次化组织,使得场景的构建更加清晰和高效。例如,在游戏开发中,不同层次的场景元素可以分别用不同的 Container来管理,如背景层、角色层、UI 层等。这种分层结构便于管理复杂的场景,与 Container的对象分组作用紧密相关。
  2. 渲染优化:使用 Container可进行分组渲染,减少渲染层次和调用次数,提高渲染性能。还能结合批处理技术进一步优化。
    • Container在 Scene 中的分组渲染功能,使得渲染过程更加高效。通过将相关的显示对象组合在一个 Container中,可以减少不必要的渲染操作,提高整体性能。
  3. 属性传递和继承:Container中的变换和属性会传递给子节点,方便在父容器中进行统一修改,简化对复杂场景的控制和调整。
    • 在 Scene 中,Container的属性传递机制使得开发者可以轻松地对一组显示对象进行统一的变换和渲染。例如,通过调整父容器的位置、缩放、旋转等属性,可以同时影响其中的所有子节点,实现了统一的属性管理,正如 Scene 模块中 Container提供位置、缩放、旋转、透明度等属性并传递给子节点的作用。
  4. 事件处理:通过 Container,事件的冒泡和捕获机制得以实现。事件可以在显示对象之间传播,无需单独处理每个对象的事件逻辑。
    • Container在 Scene 中的事件处理机制,使得交互操作更加便捷。当一个显示对象触发了一个事件,这个事件可以通过 Container的层次结构向上或向下传播,从而实现更加灵活的事件处理,对应 Scene 模块的事件处理与交互功能。

Container 的基本结构概述

Container目录下包含多个重要的子目录和主要文件,共同构成其功能体系。

  1. bounds子目录:
    • utils子目录中的 matrixAndBoundsPool.ts文件可能用于管理矩阵和边界的内存池,提高资源复用效率。
    • Bounds.ts以及各种 bounds 获取方法文件,如 getFastGlobalBounds.tsgetGlobalBounds.tsgetLocalBounds.tsgetRenderableBounds.ts,用于确定显示对象的边界范围,在场景布局和碰撞检测等方面起着关键作用。
  2. container-mixins子目录:
    • childrenHelperMixin.ts提供子节点辅助功能,方便对 Container中的子节点进行添加、删除和查询等操作。
    • effectsMixin.ts处理显示对象的效果,为场景增添视觉特效。
    • findMixin.ts实现查找功能,能够快速定位特定的显示对象。
    • measureMixin.ts用于测量显示对象的尺寸等属性。
    • onRenderMixin.ts在渲染过程中执行特定的操作。
    • sortMixin.ts进行子节点的排序,控制显示顺序。
    • toLocalGlobalMixin.ts实现坐标转换,方便在不同坐标系之间切换。
  3. utils子目录:
    • assignWithIgnore.ts可能用于忽略特定属性的赋值操作。
    • buildInstructions.ts构建指令,用于指导显示对象的渲染过程。
    • checkChildrenDidChange.ts检查子节点是否发生变化,以便及时更新场景。
    • clearList.ts清理列表,释放内存资源。
    • collectRenderGroups.ts收集渲染组,提高渲染效率。
    • definedProps.ts定义属性,规范显示对象的属性设置。
    • executeInstructions.ts执行指令,实现渲染逻辑。
    • mixColors.tsmixHexColors.tsmultiplyHexColors.ts用于颜色处理,丰富场景的色彩表现。
    • updateLocalTransform.tsupdateRenderGroupTransforms.tsupdateWorldTransform.ts分别更新局部、渲染组和世界坐标变换,确保显示对象在场景中的正确位置和姿态。
    • validateRenderables.ts验证可渲染对象,保证渲染质量。
  4. 核心文件:
    • Container.tsContainer类的主要实现文件,定义了 Container的基本属性和方法,是构建场景的基础。
    • RenderContainer.tsRenderGroup.tsRenderGroupPipe.tsRenderGroupSystem.ts共同构成了渲染组的管理体系,提高场景的渲染效率和性能。
    • destroyTypes.ts定义了销毁类型,方便资源的清理和管理。

三、Container 的具体功能解析

核心类解析

Container.ts

Container是 PixiJS 中一个重要的显示对象容器类,它可以容纳其他显示对象(如SpriteGraphics等),并提供了丰富的功能,包括变换(位置、旋转、缩放、倾斜等)、颜色调整、混合模式设置、可见性控制以及与渲染组相关的操作。

关键属性及解释

1. 容器标识和渲染组相关属性
public readonly uid: number = uid('renderable');
// the render group this container owns
public renderGroup: RenderGroup = null;
// the render group this container belongs to
public parentRenderGroup: RenderGroup = null;
// the index of the container in the render group
public parentRenderGroupIndex: number = 0;
// set to true if the container has changed. It is reset once the changes have been applied
public didChange = false;
// same as above, but for the renderable
public didViewUpdate = false;
// how deep is the container relative to its render group..
public relativeRenderGroupDepth = 0;
  • uid:为容器生成的唯一标识。
  • renderGroupparentRenderGroup分别表示容器拥有的渲染组和所属的渲染组。
  • parentRenderGroupIndex是容器在所属渲染组中的索引。
  • didChangedidViewUpdate用于标记容器是否发生了变化。
  • relativeRenderGroupDepth表示容器相对于其渲染组的深度。
2. 变换相关属性
// used by the transform system to check if a container needs to be updated that frame
public updateTick = -1;
// Current transform of the object based on local factors: position, scale, other stuff.
public localTransform: Matrix = new Matrix();
// The relative group transform is a transform relative to the render group it belongs too.
public relativeGroupTransform: Matrix = new Matrix();
// The group transform is a transform relative to the render group it belongs too.
public groupTransform: Matrix = this.relativeGroupTransform;
// the global transform taking into account the render group and all parents
private _worldTransform: Matrix;
// transform data..
public _position: ObservablePoint = new ObservablePoint(this, 0, 0);
public _scale: ObservablePoint = defaultScale;
public _pivot: ObservablePoint = defaultPivot;
public _skew: ObservablePoint = defaultSkew;
public _cx = 1;
public _sx = 0;
public _cy = 0;
public _sy = 1;
private _rotation = 0;
  • updateTick:用于变换系统检查容器在当前帧是否需要更新。
  • localTransformrelativeGroupTransformgroupTransform分别表示基于本地因素的当前变换、相对于所属渲染组的相对变换和相对于所属渲染组的变换(如果容器是渲染组,则为恒等矩阵)。
  • _worldTransform是考虑渲染组和所有父容器的全局变换。
  • _position_scale_pivot_skew分别表示位置、缩放、枢轴和倾斜的可观察点。
  • _cx_sx_cy_sy以及_rotation用于内部计算变换。
3. 颜色相关属性
// color stored as ABGR
public localColor = 0xFFFFFF;
public localAlpha = 1;
public groupAlpha = 1; // A
public groupColor = 0xFFFFFF; // BGR
public groupColorAlpha = 0xFFFFFFFF; // ABGR
  • localColorlocalAlpha表示本地颜色和不透明度。
  • groupAlphagroupColorgroupColorAlpha表示相对于渲染组的颜色和不透明度。
4. 混合模式相关属性
// @internal
// @ignore
public localBlendMode: BLEND_MODES = 'inherit';
// @internal
// @ignore
public groupBlendMode: BLEND_MODES = 'normal';
  • localBlendMode表示本地混合模式。
  • groupBlendMode表示相对于渲染组的混合模式。
5. 可见性相关属性
// This property holds three bits: culled, visible, renderable
public localDisplayStatus = 0b111; // 0b11 | 0b10 | 0b01 | 0b00
// @internal
// @ignore
public globalDisplayStatus = 0b111; // 0b11 | 0b10 | 0b01 | 0b00
  • localDisplayStatusglobalDisplayStatus用三位二进制表示是否被剔除、是否可见以及是否可渲染。
6. 其他属性
public children: C[] = [];
public parent: Container = null;
// used internally for changing up the render order.. mainly for masks and filters
public includeInBuild = true;
public measurable = true;
public isSimple = true;
public boundsArea: Rectangle;
public _didContainerChangeTick = 0;
public _didViewChangeTick = 0;
public readonly renderPipeId: string;
private _didLocalTransformChangeId = -1;
  • children是容器的子元素数组。
  • parent表示容器的父容器。
  • includeInBuildmeasurableisSimple用于内部渲染控制。
  • boundsArea是可选的容器边界区域,用于优化渲染。
  • _didContainerChangeTick_didViewChangeTick用于跟踪容器和视图的变化。
  • renderPipeId是渲染管道的标识。
  • _didLocalTransformChangeId用于跟踪本地变换的变化。

关键方法及解释

1. 构造函数
constructor(options: ContainerOptions<C> = {}) {
    super();

    this.effects = [];
    assignWithIgnore(this, options, {
        children: true,
        parent: true,
        effects: true,
    });

    options.children?.forEach((child) => this.addChild(child));
    options.parent?.addChild(this);
}

构造函数,初始化一个Container实例,并根据传入的options对象来设置实例的属性,同时处理子元素和父容器的添加操作

2. 添加子元素方法
public addChild<U extends C[]>(...children: U): U[0] {
    //...
}

addChild 方法负责将子对象添加到当前容器,并处理相关的父子关系、渲染组、排序和事件通知等逻辑,使得子对象能够正确地在容器中显示和交互。具体来说有如下逻辑:

  • 如果传入多个子对象,逐一调用 addChild 方法将它们添加到容器中,并返回第一个子对象。
  • 如果传入的是单个子对象(数组中的第一个子对象),获取该子对象。
    • 如果子对象的父容器已经是当前容器,只需重新排列顺序,并返回子对象。
    • 如果子对象有不同的父容器,首先从原始父容器中移除该子对象。
    • 将子对象添加到当前容器:
      • 将子对象添加到当前容器的 children 数组中。
      • 如果容器启用了按 zIndex 排序,将其标记为需要排序。
      • 更新子对象的父容器引用。
      • 更新子对象的状态标志。
  • 如果当前容器或其父容器是渲染组,将该子对象添加到该渲染组中。
  • 触发 childAdded 和 added 事件,通知其他系统子对象已被添加。
  • 更新更改计数器以反映状态修改。
  • 如果子对象的 zIndex 非零,标记其深度需要重新排序。
3. 移除子元素方法
public removeChild<U extends C[]>(...children: U): U[0] {
    //...
}

removeChild 方法负责从当前容器中移除一个或多个子对象,并处理相关的父子关系、渲染组和事件通知等逻辑。具体逻辑如下:

  • 如果传入多个子对象,逐一调用 removeChild 方法将它们从容器中移除,并返回第一个子对象。
  • 获取传入的单个子对象(数组中的第一个子对象)。
    • 在子对象数组 children 中查找该子对象的索引。
    • 如果子对象存在于当前容器中,执行以下操作:
      • 更新视图更改计数器。
      • 从子对象数组 children 中移除该子对象。
    • 如果当前容器或其父容器是渲染组,将该子对象从渲染组中移除。
    • 将子对象的父容器引用置为 null
    • 触发 childRemovedremoved 事件。
    • 返回删除的子对象。
4. 更新方法
public _onUpdate(point?: ObservablePoint) {
    //...
}

主要作用是处理容器的更新逻辑,包括更新倾斜度相关的变换(倾斜即CSS skew()函数)、增加容器变化计数、设置容器状态为已变化,并通知父级渲染组容器的状态变化。

5. 设置渲染组方法
set isRenderGroup(value: boolean) {
    if (!!this.renderGroup === value) return;
    if (value) {
        this.enableRenderGroup();
    } else {
        this.disableRenderGroup();
    }
}
public enableRenderGroup(): void {
    if (this.renderGroup) return;
        const parentRenderGroup = this.parentRenderGroup;
        parentRenderGroup?.removeChild(this);
        this.renderGroup = BigPool.get(RenderGroup, this);
        this.groupTransform = Matrix.IDENTITY;
        parentRenderGroup?.addChild(this);
        this._updateIsSimple();
}
public disableRenderGroup(): void {
    if (!this.renderGroup) return;
        const parentRenderGroup = this.parentRenderGroup;
        parentRenderGroup?.removeChild(this);
        BigPool.return(this.renderGroup);
        this.renderGroup = null;
        this.groupTransform = this.relativeGroupTransform;
        parentRenderGroup?.addChild(this);
        this._updateIsSimple();
}

设置容器是否为渲染组。渲染组是一种在图形渲染中使用的技术,它允许将一组相关的图形元素作为一个整体进行渲染,这样可以提高渲染效率和性能。

  • enableRenderGroup()
    • 检查是否已经存在渲染组,如果存在则直接返回。
    • 获取当前容器的父渲染组。
    • 从父渲染组中移除当前容器。
    • 从对象池(BigPool)中获取一个新的渲染组,并将其赋给当前容器。
    • 将容器的组变换矩阵设置为单位矩阵,因为其变换将由GPU处理。
    • 将当前容器重新添加到父渲染组中(如果存在)。
    • 更新容器的简单性状态。
  • disableRenderGroup()
    • 检查是否不存在渲染组,如果不存在则直接返回。
    • 获取当前容器的父渲染组。
    • 从父渲染组中移除当前容器。
    • 将当前容器的渲染组归还给对象池(BigPool)。
    • 将容器的渲染组设置为null,并将组变换矩阵恢复为相对组变换矩阵。
    • 将当前容器重新添加到父渲染组中(如果存在)。
    • 更新容器的简单性状态。
6. 获取世界变换方法
get worldTransform() {
    // 如果this._worldTransform为null或undefined,则将其赋值为new Matrix()。
    // 这样做的好处是只有在第一次访问worldTransform属性时才会创建Matrix对象,从而提高性能。
    this._worldTransform ||= new Matrix();
    //...
    return this._worldTransform;
}

这段代码定义了一个名为worldTransform的属性,它返回一个Matrix对象,表示当前容器在世界坐标系中的变换矩阵。这个属性的实现使用了懒加载(lazy loading)的技术,即只有在第一次访问时才会创建Matrix对象,并且会缓存这个对象以避免重复创建。

export class Matrix {
  // ...
  /**
   * @param a - x scale
   * @param b - y skew
   * @param c - x skew
   * @param d - y scale
   * @param tx - x translation
   * @param ty - y translation
   */
  constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
    this.a = a
    this.b = b
    this.c = c
    this.d = d
    this.tx = tx
    this.ty = ty
  }
  // ...
}
7. 变换相关的属性设置方法
// x 和 y 属性是 position.x 和 position.y 的别名
get x(): number { return this._position.x; }
set x(value: number) { this._position.x = value; }
get y(): number { return this._position.y; }
set y(value: number) { this._position.y = value; }
// position 属性
get position(): ObservablePoint { return this._position; }
set position(value: PointData) { this._position.copyFrom(value); }
// rotation 属性是弧度表示的旋转角度,angle 属性是角度表示的旋转角度
get rotation(): number { return this._rotation; }
set rotation(value: number) {
    if (this._rotation!== value) {
        this._rotation = value;
        this._onUpdate(this._skew);
    }
}
get angle(): number { return this.rotation * RAD_TO_DEG; }
set angle(value: number) { this.rotation = value * DEG_TO_RAD; }
// pivot 属性是枢轴点
get pivot(): ObservablePoint {
    if (this._pivot === defaultPivot) {
        this._pivot = new ObservablePoint(this, 0, 0);
    }
    return this._pivot;
}
set pivot(value: PointData | number) {
    if (this._pivot === defaultPivot) {
        this._pivot = new ObservablePoint(this, 0, 0);
    }
    typeof value === 'number'? this._pivot.set(value) : this._pivot.copyFrom(value);
}
// skew 属性是倾斜因子
get skew(): ObservablePoint {
    if (this._skew === defaultSkew) {
        this._skew = new ObservablePoint(this, 0, 0);
    }
    return this._skew;
}
set skew(value: PointData) {
    if (this._skew === defaultSkew) {
        this._skew = new ObservablePoint(this, 0, 0);
    }
    this._skew.copyFrom(value);
}
// scale 属性是缩放因子
get scale(): ObservablePoint {
    if (this._scale === defaultScale) {
        this._scale = new ObservablePoint(this, 1, 1);
    }
    return this._scale;
}
set scale(value: PointData | number) {
    if (this._scale === defaultScale) {
        this._scale = new ObservablePoint(this, 0, 0);
    }
    typeof value === 'number'? this._scale.set(value) : this._scale.copyFrom(value);
}
// width 和 height 属性通过设置缩放来实现
get width(): number { return Math.abs(this.scale.x * this.getLocalBounds().width); }
set width(value: number) {
    const localWidth = this.getLocalBounds().width;
    this._setWidth(value, localWidth);
}
get height(): number { return Math.abs(this.scale.y * this.getLocalBounds().height); }
set height(value: number) {
    const localHeight = this.getLocalBounds().height;
    this._setHeight(value, localHeight);
}
// getSize 和 setSize 方法用于获取和设置容器的大小
public getSize(out?: Size): Size {
    //...
}
public setSize(value: number | Optional<Size, 'height'>, height?: number) {
    //...
}

这些方法用于获取和设置容器的位置、旋转、枢轴、倾斜、缩放、宽度和高度等变换属性。

8. 更新变换方法
public updateTransform(opts: Partial<UpdateTransformOptions>): this {
    //...
    return this;
}
public setFromMatrix(matrix: Matrix): void {
    matrix.decompose(this);
}
public updateLocalTransform(): void {
    //...
}

updateTransform方法接受部分变换选项并更新容器的变换。setFromMatrix方法使用给定的矩阵更新容器的变换。updateLocalTransform方法更新本地变换。

9. 颜色相关的属性设置方法
set alpha(value: number) {
    //...
}
get alpha(): number { return this.localAlpha; }
set tint(value: ColorSource) {
    //...
}
get tint(): number {
    const bgr = this.localColor;
    // convert bgr to rgb..
    return ((bgr & 0xFF) << 16) + (bgr & 0xFF00) + ((bgr >> 16) & 0xFF);
}

设置和获取容器的不透明度(alpha属性)和颜色(tint属性)。

10. 混合模式相关的属性设置方法
set blendMode(value: BLEND_MODES) {
    //...
}
get blendMode(): BLEND_MODES { return this.localBlendMode; }

设置图形对象的混合模式(blend mode),混合模式决定了如何将图形对象的颜色与背景颜色进行混合。

11. 可见性相关的属性设置方法
get visible() {
    return!!(this.localDisplayStatus & 0b010);
}
set visible(value: boolean) {
    //...
}
get culled() {
    return!(this.localDisplayStatus & 0b100);
}
set culled(value: boolean) {
    //...
}
get renderable() {
    return!!(this.localDisplayStatus & 0b001);
}
set renderable(value: boolean) {
    //...
}
get isRenderable(): boolean {
    return (this.localDisplayStatus === 0b111 && this.groupAlpha > 0);
}
  • 获取和设置容器的可见性(visible属性),
  • 获取和设置是否被剔除(culled属性)
    • 剔除是图形渲染中的一个概念,指的是在某些情况下,由于对象距离相机过远、被其他物体遮挡或其他原因,不需要将该对象渲染到屏幕上。
  • 获取和设置是否可渲染(renderable属性)
  • isRenderable属性是一个综合判断的结果,它取决于两个条件:一是容器的本地显示状态(由 localDisplayStatus表示)是否为全有效状态(即 0b111,意味着未被剔除、可见且可渲染);二是容器相对于渲染组的不透明度(groupAlpha)是否大于 0。只有同时满足这两个条件,isRenderable才为true,表示容器可以被渲染。
12. 销毁方法

销毁容器,移除所有内部引用和监听器,并可选地销毁子元素。

public destroy(options: DestroyOptions = false): void {
    // 如果容器已经被销毁,则直接返回。
    if (this.destroyed) return;
    this.destroyed = true;

    // 快速移除所有子元素并存储在 oldChildren 中。
    const oldChildren = this.removeChildren(0, this.children.length);

    // 从父容器中移除自身,并将父容器引用设置为 null。
    this.removeFromParent();
    this.parent = null;
    // 将与遮罩和滤镜相关的效果设置为 null,释放资源。
    this._maskEffect = null;
    this._filterEffect = null;
    // 将 effects 属性设置为 null,释放可能占用的资源。
    this.effects = null;
    // 将位置、缩放、枢轴和倾斜等属性设置为 null,释放相关资源。
    this._position = null;
    this._scale = null;
    this._pivot = null;
    this._skew = null;

    // 触发 destroyed 事件,通知其他对象容器已被销毁。
    this.emit('destroyed', this);

    // 移除容器上注册的所有监听器,防止内存泄漏和不必要的事件触发。
    this.removeAllListeners();

    // 根据 options 判断是否销毁子元素。
    const destroyChildren = typeof options === 'boolean'? options : options?.children;
    if (destroyChildren) {
        // 如果决定销毁子元素,则遍历所有子元素并调用它们的 destroy 方法。
        for (let i = 0; i < oldChildren.length; ++i) {
            oldChildren[i].destroy(options);
        }
    }
    // 如果容器有渲染组,则调用渲染组的 destroy 方法,并将容器的渲染组引用设置为 null。
    this.renderGroup?.destroy();
    this.renderGroup = null;
}

混合方法

Container.mixin(childrenHelperMixin) // 提供了子对象管理的功能,如添加、删除、获取和排序子对象的方法。
Container.mixin(toLocalGlobalMixin) // 提供了全局与本地坐标转换的方法。
Container.mixin(onRenderMixin) // 提供了自定义渲染回调功能,使得容器能够在渲染时执行特定的逻辑。
Container.mixin(measureMixin) // 提供了尺寸计算和边界获取的方法。
Container.mixin(effectsMixin) // 提供了特效管理的功能,如遮罩和滤镜。
Container.mixin(findMixin) // 提供了通过标签查找子对象的方法。
Container.mixin(sortMixin) // 提供了子对象排序的功能。
Container.mixin(cullingMixin) // 提供了剔除功能,用于提高渲染性能。

通过mixin将多个混入模块的属性和方法添加到Container类中,从而扩展 Container 类的功能。每个 mixin 都包含了一组方法和属性,这些方法和属性可以使 Container 类具备额外的功能,而不用继承或修改 Container 本身。使用 mixin 是一种动态增强类功能的常用模式。

RenderGroup.ts

RenderGroup 类负责生成用于渲染根容器及其子容器的指令集。当容器或其子对象发生更改时,它会更新或重新生成指令集。

类定义与成员变量

RenderGroup 类实现了 Instruction 接口,并定义了一系列成员变量,用于管理渲染逻辑和状态。

export class RenderGroup implements Instruction {
  public renderPipeId = 'renderGroup'
  public root: Container = null

  public canBundle = false

  public renderGroupParent: RenderGroup = null
  public renderGroupChildren: RenderGroup[] = []

  public worldTransform: Matrix = new Matrix()
  public worldColorAlpha = 0xffffffff
  public worldColor = 0xffffff
  public worldAlpha = 1

  public readonly childrenToUpdate: Record<number, { list: Container[]; index: number }> =
    Object.create(null)
  public updateTick = 0

  public readonly childrenRenderablesToUpdate: { list: Container[]; index: number } = {
    list: [],
    index: 0,
  }

  public structureDidChange = true

  public instructionSet: InstructionSet = new InstructionSet()

  private readonly _onRenderContainers: Container[] = []
}
  • renderPipeId:标识渲染管道的ID。
  • root:根容器。
  • canBundle:标识是否可以打包处理。
  • renderGroupParentrenderGroupChildren:用于管理嵌套的渲染组。
  • worldTransform, worldColorAlpha, worldColor, worldAlpha: 世界变换矩阵和颜色数据。
  • childrenToUpdate, childrenRenderablesToUpdate: 存储需要更新的子对象。
  • structureDidChange:标识结构是否变化。
  • instructionSet:指令集。
  • _onRenderContainers:存储具有自定义渲染逻辑的容器。

方法定义

初始化方法 init
public init(root: Container)
{
    this.root = root;

    if (root._onRender) this.addOnRender(root);

    root.didChange = true;

    const children = root.children;

    for (let i = 0; i < children.length; i++)
    {
        this.addChild(children[i]);
    }
}
  • 初始化根容器。
  • 如果根容器具有 _onRender 回调,则将其添加到渲染列表。
  • 标记根容器为已更改,并递归添加所有子对象。
重置方法 reset
public reset()
{
    // ...
}
  • 重置渲染组的状态,清空子渲染组和更新列表,重置根容器引用和更新标记。
添加子渲染组方法 addRenderGroupChild
public addRenderGroupChild(renderGroupChild: RenderGroup)
{
    // ...
}
  • 添加子渲染组,并处理父子关系的引用更新。
递归添加子对象 addChild
public addChild(child: Container)
{
    // ...
}
  • 递归将子对象及其所有子层级对象添加到渲染组中。
删除子对象 removeChild
public removeChild(child: Container)
{
    // ...
}
  • 递归地从渲染组中删除子对象及其所有子层级对象。
标记子对象更新 onChildUpdate
public onChildUpdate(child: Container)
{
    let childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth];

    if (!childrenToUpdate)
    {
        childrenToUpdate = this.childrenToUpdate[child.relativeRenderGroupDepth] = {
            index: 0,
            list: [],
        };
    }

    childrenToUpdate.list[childrenToUpdate.index++] = child;
}
  • 根据子对象的层级深度标记其需要更新。
更新可渲染对象 updateRenderable
public updateRenderable(container: Container)
{
    if (container.globalDisplayStatus < 0b111) return;

    container.didViewUpdate = false;
    this.instructionSet.renderPipes[container.renderPipeId].updateRenderable(container);
}
  • 根据子对象的渲染状态更新其渲染指令。
自定义渲染回调管理
public addOnRender(container: Container)
{
    this._onRenderContainers.push(container);
}

public removeOnRender(container: Container)
{
    this._onRenderContainers.splice(this._onRenderContainers.indexOf(container), 1);
}

public runOnRender()
{
    for (let i = 0; i < this._onRenderContainers.length; i++)
    {
        this._onRenderContainers[i]._onRender();
    }
}
  • 管理具有自定义渲染逻辑的子对象。
销毁方法 destroy
public destroy()
{
    this.renderGroupParent = null;
    this.root = null;
    (this.childrenRenderablesToUpdate as any) = null;
    (this.childrenToUpdate as any) = null;
    (this.renderGroupChildren as any) = null;
    (this._onRenderContainers as any) = null;
    this.instructionSet = null;
}
  • 销毁渲染组,清理所有引用和状态数据。

RenderContainer.ts

RenderContainer 类是一个允许自定义渲染逻辑的容器类。通过该类,用户可以定义自己的渲染方法,这种方法可以是 WebGL 渲染、WebGPU 渲染或 Canvas 渲染,根据需要来定义。你可以通过继承该类并重写 render 方法,或者通过构造函数传入渲染函数来实现自定义渲染。

接口定义

export interface RenderContainerOptions extends ContainerOptions {
  render?: RenderFunction
  containsPoint?: (point: Point) => boolean
  addBounds?: (bounds: BoundsData) => void
}

RenderContainerOptions 接口继承自 ContainerOptions,增加了以下选项:

  • render:用于自定义渲染函数。
  • containsPoint:用于检测自定义逻辑是否包含某个点。
  • addBounds:用于添加对象的边界。

类定义与方法

export class RenderContainer extends Container implements View, Instruction {
  public batched = false
  public roundPixels: boolean
  public _roundPixels: 0 | 1
  public _lastUsed = 0
  public _lastInstructionTick = -1
  public bounds = new Bounds()
  public containsPoint: (point: Point) => boolean
  public addBounds: (bounds: Bounds) => void
  public canBundle = false
  public readonly renderPipeId: string = 'customRender'

  constructor(options: RenderContainerOptions | RenderFunction) {
    if (typeof options === 'function') {
      options = { render: options }
    }

    const { render, ...rest } = options

    super({
      label: 'RenderContainer',
      ...rest,
    })

    if (render) this.render = render

    this.containsPoint = options.containsPoint ?? (() => false)
    this.addBounds = options.addBounds ?? (() => false)
  }

  public render(_renderer: Renderer): void {
    // override me!
  }
}

属性解析

  • batched:标志是否已经批处理。
  • roundPixels:是否需要对x/y位置进行四舍五入处理。
  • _roundPixels:内部使用的四舍五入标志。
  • _lastUsed_lastInstructionTick:用于跟踪渲染状态的属性。
  • boundsBounds 实例,用于表示边界。
  • containsPoint:用于检测点是否在对象内部。
  • addBounds:用于添加对象的边界。
  • canBundle:标志是否可以捆绑在一起进行渲染。
  • renderPipeId:渲染管道ID,默认值为 'customRender'

构造函数解析

constructor(options: RenderContainerOptions | RenderFunction)
{
    if (typeof options === 'function')
    {
        options = { render: options };
    }

    const { render, ...rest } = options;

    super({
        label: 'RenderContainer',
        ...rest,
    });

    if (render) this.render = render;

    this.containsPoint = options.containsPoint ?? (() => false);
    this.addBounds = options.addBounds ?? (() => false);
}

构造函数支持两种方式初始化 RenderContainer

  1. 通过子类继承并重写 render 方法。
  2. 通过传入自定义渲染函数。

render 方法

public render(_renderer: Renderer): void
{
    // override me!
}

render 方法是一个易于重写的函数,用户可以在该方法中定义自己的渲染逻辑。

示例用法

代码注释中提供了一个简单示例,说明如何使用 RenderContainer 实现自定义渲染:

import { RenderContainer } from 'pixi.js'

// 通过子类继承实现
class MyRenderContainer extends RenderContainer {
  render(renderer) {
    renderer.clear({
      clearColor: 'green', // 渲染此项时将屏幕清除为绿色
    })
  }
}

// 通过传入渲染函数实现
const renderContainer = new RenderContainer((renderer) => {
  renderer.clear({
    clearColor: 'green', // 渲染此项时将屏幕清除为绿色
  })
})

container-mixins

container-mixinsContainer 混入多种功能模块,以扩展其在处理子元素、变换、测量、效果、查找、排序、本地与全局转换以及剔除等方面的能力。

childrenHelperMixin.ts

childrenHelperMixin 是一个用于增强 Container 功能的 mixin,提供了一系列管理容器子对象的方法。这包括添加、删除、获取和重新排列子对象等。通过这个 mixin,我们可以方便地操作 Container 中的子对象结构。

属性和方法解释

  1. **allowChildren**属性:表示容器是否允许添加子元素。默认为true
  2. **addChild**方法:这个方法用于向容器中添加一个或多个子元素,并将它们添加到容器的末尾。它通过循环调用addChildAt方法,将每个子元素添加到容器的子元素数组中,并设置相应的属性和触发事件。最后,返回第一个子元素。
  3. **removeChildren**方法:这个方法用于从容器中移除指定范围内的子元素。它首先确定要移除的子元素范围,然后将这些子元素从容器的子元素数组中移除,并将它们的父容器设置为null。如果容器有渲染组,还会通知渲染组移除这些子元素。最后,触发相应的事件通知其他对象子元素被移除。
  4. **removeChildAt**方法:这个方法通过给定的索引位置移除一个子元素。它首先调用getChildAt方法获取指定索引位置的子元素,然后调用removeChild方法将其从容器中移除。
  5. **getChildAt**方法:这个方法用于获取指定索引位置的子元素。如果索引超出范围,则抛出错误。
  6. **setChildIndex**方法:这个方法用于改变一个已有子元素在容器中的位置。它首先检查给定的索引是否在合法范围内,然后通过调用getChildIndex方法检查子元素是否存在于容器中,最后调用addChildAt方法将子元素添加到指定的索引位置。
  7. **getChildIndex**方法:这个方法用于获取一个子元素在容器中的索引位置。如果子元素不存在于容器中,则抛出错误。
  8. **addChildAt**方法:这个方法用于在指定索引位置向容器中添加一个子元素。它首先检查索引是否合法,如果子元素已经有父容器,并且不是当前容器或者位置不同,会从原来的父容器中移除。然后将子元素添加到当前容器的子元素数组中,并设置相应的属性和触发事件。如果容器有渲染组,还会通知渲染组添加这个子元素。
  9. **swapChildren**方法:这个方法用于交换容器中两个子元素的位置。它首先检查两个子元素是否相同,如果相同则不进行任何操作。然后获取两个子元素在容器中的索引位置,并在子元素数组中交换它们的位置。如果容器有渲染组,会设置渲染组的结构发生了变化。最后,增加容器的变化标记。
  10. **removeFromParent**方法:这个方法用于将当前容器从其父容器中移除。它通过调用父容器的removeChild方法来实现。
  11. **reparentChild**方法:这个方法用于将一个或多个子元素父级重新化为当前容器。如果只有一个子元素,会调用reparentChildAt方法将其添加到当前容器的末尾。如果有多个子元素,会遍历每个子元素并调用reparentChildAt方法将它们添加到当前容器的末尾。
  12. **reparentChildAt**方法:这个方法用于将一个子元素父级重新化为当前容器,并保持其在世界坐标系中的变换不变。如果子元素已经是当前容器的子元素,会调用setChildIndex方法将其移动到指定的索引位置。如果子元素不是当前容器的子元素,会先克隆子元素的世界变换矩阵,然后将子元素从原来的父容器中移除,并添加到当前容器的指定索引位置。最后,通过计算新的变换矩阵来保持子元素在世界坐标系中的变换不变。

effectsMixin.ts

EffectsMixin 是一个混入 (mixin),用于在 Container 类上添加特效管理功能。通过这个 mixin,你可以方便地为显示对象添加滤镜和遮罩效果。

关键属性及解释

  • _maskEffect:存储与遮罩效果相关的对象。
  • _filterEffect:存储与滤镜效果相关的对象。
  • filterArea:表示滤镜应用的区域,通常是一个矩形。
  • effects:存储其他效果的数组。

关键方法及解释

1. addEffect方法
addEffect(effect: Effect)
{
    const index = this.effects.indexOf(effect);

    if (index!== -1) return; // already exists!

    this.effects.push(effect);

    this.effects.sort((a, b) => a.priority - b.priority);

    const renderGroup = this.renderGroup || this.parentRenderGroup;

    if (renderGroup)
    {
        renderGroup.structureDidChange = true;
    }

    this._updateIsSimple();
}

这个方法用于向容器添加一个效果。首先检查效果是否已经存在于效果数组中,如果存在则直接返回。如果不存在,则将效果添加到数组中,并根据效果的优先级进行排序。然后通知渲染组结构发生了变化,并更新容器的简单性标志。

2. removeEffect方法
removeEffect(effect: Effect)
{
    const index = this.effects.indexOf(effect);

    if (index === -1) return; // already exists!

    this.effects.splice(index, 1);

    if (this.parentRenderGroup)
    {
        this.parentRenderGroup.structureDidChange = true;
    }

    this._updateIsSimple();
}

这个方法用于从容器中移除一个效果。首先检查效果是否存在于效果数组中,如果不存在则直接返回。如果存在,则从数组中移除该效果,并通知父渲染组结构发生了变化,同时更新容器的简单性标志。

3. mask属性的 setter 和 getter
set mask(value: number | Container | null)
{
    const effect = this._maskEffect;

    if (effect?.mask === value) return;

    if (effect)
    {
        this.removeEffect(effect);

        MaskEffectManager.returnMaskEffect(effect);

        this._maskEffect = null;
    }

    if (value === null || value === undefined) return;

    this._maskEffect = MaskEffectManager.getMaskEffect(value);

    this.addEffect(this._maskEffect);
}

get mask(): unknown
{
    return this._maskEffect?.mask;
}

mask属性用于设置容器的遮罩。如果设置的值与当前遮罩相同,则不进行任何操作。如果有当前遮罩,则先从效果数组中移除,并将其返回给遮罩效果管理器。如果设置的值不为nullundefined,则从遮罩效果管理器获取新的遮罩效果,并添加到效果数组中。getter方法返回当前遮罩的对象。

4. filters属性的 setter 和 getter
set filters(value: Filter | Filter[] | null | undefined)
{
    if (!Array.isArray(value) && value) value = [value];

    const effect = this._filterEffect ||= new FilterEffect();

    // Ignore the Filter type
    value = value as Filter[] | null | undefined;

    const hasFilters = value?.length > 0;
    const hadFilters = effect.filters?.length > 0;

    const didChange = hasFilters!== hadFilters;

    // Clone the filters array so we don't freeze the user-input
    value = Array.isArray(value)? value.slice(0) : value;

    // Ensure filters are immutable via filters getter
    effect.filters = Object.freeze(value);

    if (didChange)
    {
        if (hasFilters)
        {
            this.addEffect(effect);
        }
        else
        {
            this.removeEffect(effect);

            // sets the empty array...
            effect.filters = value?? null;
        }
    }
}

get filters(): readonly Filter[]
{
    return this._filterEffect?.filters;
}

filters属性用于设置容器的滤镜。如果传入的值不是数组且不为nullundefined,则将其转换为数组。然后检查是否有滤镜以及之前是否有滤镜,判断是否发生了变化。如果发生了变化,根据是否有滤镜决定添加或移除滤镜效果,并确保滤镜数组不可变。getter方法返回当前的滤镜数组。

5. filterArea属性的 setter 和 getter
set filterArea(value: Rectangle)
{
    this._filterEffect ||= new FilterEffect();

    this._filterEffect.filterArea = value;
}

get filterArea(): Rectangle
{
    return this._filterEffect?.filterArea;
}

filterArea属性用于设置滤镜应用的区域。如果没有滤镜效果对象,则创建一个新的滤镜效果对象。然后设置滤镜效果的区域。getter方法返回当前的滤镜区域。

findMixin.ts

findMixin 是一个用于增强 Container 功能的方法集合,提供了在容器中查找子对象的方法。通过这个 mixin,你可以方便地通过标签或名称来查找特定的子对象,支持深度搜索和正则表达式匹配。

关键属性及解释

  • label:容器的实例标签。
  • name(已废弃):容器的实例名称,从 8.0.0 版本开始不建议使用,应使用label代替。

关键方法及解释

1. getChildByName方法
getChildByName(name: string, deep = false): Container | null
{
    return this.getChildByLabel(name, deep);
}

这个方法已废弃,从 8.0.0 版本开始不建议使用。它实际上是调用getChildByLabel方法,根据给定的名称查找子容器。如果deeptrue,则递归查找。

2. getChildByLabel方法
getChildByLabel(label: string | RegExp, deep = false): Container | null
{
    const children = this.children;

    for (let i = 0; i < children.length; i++)
    {
        const child = children[i];

        if (child.label === label || (label instanceof RegExp && label.test(child.label))) return child;
    }

    if (deep)
    {
        for (let i = 0; i < children.length; i++)
        {
            const child = children[i];
            const found = child.getChildByLabel(label, true);

            if (found)
            {
                return found;
            }
        }
    }

    return null;
}

这个方法用于查找容器中具有指定标签的第一个子容器。它首先遍历当前容器的子容器,如果子容器的标签与给定的标签相等,或者给定的标签是正则表达式且子容器的标签匹配该正则表达式,则返回该子容器。如果deeptrue,则递归地在子容器中继续查找。

3. getChildrenByLabel方法
getChildrenByLabel(label: string | RegExp, deep = false, out = []): Container[]
{
    const children = this.children;

    for (let i = 0; i < children.length; i++)
    {
        const child = children[i];

        if (child.label === label || (label instanceof RegExp && label.test(child.label)))
        {
            out.push(child);
        }
    }

    if (deep)
    {
        for (let i = 0; i < children.length; i++)
        {
            children[i].getChildrenByLabel(label, true, out);
        }
    }

    return out;
}

这个方法用于查找容器中具有指定标签的所有子容器。它首先遍历当前容器的子容器,如果子容器的标签与给定的标签相等,或者给定的标签是正则表达式且子容器的标签匹配该正则表达式,则将该子容器添加到输出数组中。如果deeptrue,则递归地在子容器中继续查找,并将找到的子容器添加到输出数组中。

measureMixin.ts

measureMixin 是一个混入 (mixin),用于在 Container 类上添加测量和边界计算相关的功能。通过这个 mixin,你可以方便地获取容器的本地和全局边界,以及设置容器的宽度和高度。

关键属性及解释

  • _localBoundsCacheId:用于标识本地边界缓存的唯一 ID,初始值为 -1。
  • _localBoundsCacheData:存储本地边界缓存数据的对象,包括数据数组、索引、是否发生变化标志和本地边界的Bounds对象。

关键方法及解释

1. _setWidth方法
_setWidth(value: number, localWidth: number)
{
    const sign = Math.sign(this.scale.x) || 1;

    if (localWidth!== 0)
    {
        this.scale.x = (value / localWidth) * sign;
    }
    else
    {
        this.scale.x = sign;
    }
}

这个方法用于设置容器的宽度。它根据当前的缩放因子和给定的宽度值来计算新的缩放因子x分量。如果本地宽度不为 0,则新的缩放因子x分量为给定宽度值除以本地宽度再乘以当前缩放因子x分量的符号;如果本地宽度为 0,则缩放因子x分量设置为符号。

2. _setHeight方法
_setHeight(value: number, localHeight: number)
{
    const sign = Math.sign(this.scale.y) || 1;

    if (localHeight!== 0)
    {
        this.scale.y = (value / localHeight) * sign;
    }
    else
    {
        this.scale.y = sign;
    }
}

这个方法用于设置容器的高度。与_setWidth方法类似,它根据当前的缩放因子和给定的高度值来计算新的缩放因子y分量。如果本地高度不为 0,则新的缩放因子y分量为给定高度值除以本地高度再乘以当前缩放因子y分量的符号;如果本地高度为 0,则缩放因子y分量设置为符号。

3. getLocalBounds方法
getLocalBounds(): Bounds
{
    if (!this._localBoundsCacheData)
    {
        this._localBoundsCacheData = {
            data: [],
            index: 1,
            didChange: false,
            localBounds: new Bounds()
        };
    }

    const localBoundsCacheData = this._localBoundsCacheData;

    localBoundsCacheData.index = 1;
    localBoundsCacheData.didChange = false;

    if (localBoundsCacheData.data[0]!== this._didViewChangeTick)
    {
        localBoundsCacheData.didChange = true;
        localBoundsCacheData.data[0] = this._didViewChangeTick;
    }

    checkChildrenDidChange(this, localBoundsCacheData);

    if (localBoundsCacheData.didChange)
    {
        getLocalBounds(this, localBoundsCacheData.localBounds, tempMatrix);
    }

    return localBoundsCacheData.localBounds;
}

这个方法用于获取容器的本地边界。如果没有本地边界缓存数据,则创建一个新的缓存对象。然后,它检查缓存数据是否需要更新,如果缓存数据中的标记与容器的视图更新标记不同,则设置变化标志为true并更新缓存数据中的标记。接着,检查子元素是否发生变化,如果有变化则更新本地边界。最后,返回本地边界的Bounds对象。

4. getBounds方法
getBounds(skipUpdate?: boolean, bounds?: Bounds): Bounds
{
    return getGlobalBounds(this, skipUpdate, bounds || new Bounds());
}

这个方法用于获取容器的全局边界。它调用getGlobalBounds方法来计算全局边界,并返回一个Bounds对象。如果提供了可选的bounds参数,则使用该参数存储结果;如果没有提供,则创建一个新的Bounds对象。

onRenderMixin.ts

onRenderMixin 是一个混入 (mixin),用于在 Container 类上添加自定义渲染回调功能。通过这个 mixin,你可以为容器添加每帧渲染时需要运行的自定义逻辑。

方法解析

  1. _onRender** 属性**:
_onRender: null,
  • 存储自定义的渲染回调函数,可以为 null。
  1. onRender** 的 setter 方法**:
set onRender(func: () => void)
{
    const renderGroup = this.renderGroup || this.parentRenderGroup;

    if (!func)
    {
        if (this._onRender)
        {
            renderGroup?.removeOnRender(this);
        }

        this._onRender = null;

        return;
    }

    if (!this._onRender)
    {
        renderGroup?.addOnRender(this);
    }

    this._onRender = func;
}
  • 设置自定义渲染回调函数,同时处理该回调函数是否需要添加或移除到渲染组。
  • 延迟初始化:仅在需要时初始化 renderGroup 的添加或移除操作,避免不必要的性能开销。
  1. onRender** 的 getter 方法**:
get onRender(): () => void
{
    return this._onRender;
}
  • 获取自定义的渲染回调函数。

代码注释中提供了一个简单示例,说明如何使用 onRenderMixin 来添加自定义渲染逻辑:

/**
 * This callback is used when the container is rendered. This is where you should add your custom
 * logic that is needed to be run every frame.
 *
 * In v7 many users used `updateTransform` for this, however the way v8 renders objects is different
 * and "updateTransform" is no longer called every frame
 * @example
 * const container = new Container();
 * container.onRender = () => {
 *    container.rotation += 0.01;
 * };
 * @memberof scene.Container#
 */

这个示例展示了如何添加每帧更新容器旋转角度的逻辑。

sortMixin.ts

sortMixin 是一个混入(mixin),用于在 Container 类上添加子对象排序功能。通过这个 mixin,你可以根据 zIndex 属性动态排序子对象,确保渲染顺序的正确性。

关键属性及解释

  • _zIndex:存储容器的 zIndex,用于确定容器在渲染顺序中的位置。
  • sortDirty:标记容器的子元素是否需要在下次渲染时进行排序。如果为true,则表示需要排序。
  • sortableChildren:表示容器是否开启了对子元素的自动排序功能。如果为true,则容器会在下次渲染时或手动调用sortChildren方法时,根据子元素的zIndex值进行排序。

关键方法及解释

1. zIndex属性的 setter 和 getter
get zIndex()
{
    return this._zIndex;
}

set zIndex(value: number)
{
    if (this._zIndex === value) return;

    this._zIndex = value;

    this.depthOfChildModified();
}

zIndex属性用于获取或设置容器的 zIndex 值。当设置新的 zIndex 值时,如果与当前值相同,则不进行任何操作。如果不同,则更新_zIndex属性,并调用depthOfChildModified方法。

2. depthOfChildModified方法
depthOfChildModified()
{
  if (this.parent) {
    this.parent.sortableChildren = true
    this.parent.sortDirty = true
  }

  if (this.parentRenderGroup) {
    this.parentRenderGroup.structureDidChange = true
  }
}

这个方法在容器的子元素的深度(zIndex)发生变化时被调用。如果容器有父容器,则将父容器的sortableChildren属性设置为true,并将sortDirty属性设置为true,表示父容器的子元素需要进行排序。如果容器有父渲染组,则将渲染组的结构标记为发生了变化。

3. sortChildren方法
sortChildren()
{
  if (!this.sortDirty) return

  this.sortDirty = false

  this.children.sort(sortChildren)
}

这个方法用于对容器的子元素进行排序。如果sortDirtyfalse,则表示子元素不需要排序,直接返回。如果需要排序,则将sortDirty设置为false,并使用自定义的排序函数sortChildren对子元素数组进行排序。

toLocalGlobalMixin.ts

toLocalGlobalMixin 是一个用于增强 Container 功能的 mixin,提供了容器坐标系的全局和局部转换功能。通过这个 mixin,你可以方便地在全局和局部坐标系之间进行转换。

关键接口及解释

  • ToLocalGlobalMixin接口定义了三个方法:getGlobalPosition用于获取容器的全局位置;toGlobal将本地坐标转换为全局坐标;toLocal将全局坐标转换为相对于另一个容器的本地坐标。

关键方法及解释

getGlobalPosition方法
getGlobalPosition(point: Point = new Point(), skipUpdate = false): Point
{
    if (this.parent)
    {
        this.parent.toGlobal(this._position, point, skipUpdate);
    }
    else
    {
        point.x = this._position.x;
        point.y = this._position.y;
    }

    return point;
}

这个方法用于获取容器的全局位置。如果容器有父容器,则调用父容器的toGlobal方法将容器的本地位置转换为全局位置,并将结果存储在给定的点对象中。如果没有父容器,则直接将容器的本地位置赋值给点对象。最后返回点对象。

toGlobal方法
toGlobal<P extends PointData = Point>(position: PointData, point?: P, skipUpdate = false): P
{
    if (!skipUpdate)
    {
        this.updateLocalTransform();

        const globalMatrix = updateTransformBackwards(this, new Matrix());

        globalMatrix.append(this.localTransform);

        return globalMatrix.apply<P>(position, point);
    }

    // simply apply the matrix..
    return this.worldTransform.apply<P>(position, point);
}

这个方法用于将本地坐标转换为全局坐标。如果skipUpdatefalse,则先更新容器的本地变换,然后通过updateTransformBackwards方法获取一个全局矩阵,该矩阵与容器的本地变换矩阵相乘,得到最终的全局变换矩阵。最后,使用全局变换矩阵将给定的本地坐标转换为全局坐标,并存储在给定的点对象中。如果skipUpdatetrue,则直接使用容器的世界变换矩阵将本地坐标转换为全局坐标。

toLocal方法
toLocal<P extends PointData = Point>(position: PointData, from?: Container, point?: P, skipUpdate?: boolean): P
{
    if (from)
    {
        position = from.toGlobal(position, point, skipUpdate);
    }

    if (!skipUpdate)
    {
        this.updateLocalTransform();

        const globalMatrix = updateTransformBackwards(this, new Matrix());

        globalMatrix.append(this.localTransform);

        return globalMatrix.applyInverse<P>(position, point);
    }

    // simply apply the matrix..
    return this.worldTransform.applyInverse<P>(position, point);
}

这个方法用于将全局坐标转换为相对于另一个容器的本地坐标。如果提供了from参数,则先将全局坐标转换为给定容器的全局坐标。然后,如果skipUpdatefalse,则更新容器的本地变换,获取全局矩阵,并使用全局矩阵的逆矩阵将全局坐标转换为本地坐标。如果skipUpdatetrue,则直接使用容器的世界变换矩阵的逆矩阵将全局坐标转换为本地坐标。

矩阵相关知识点补充

在坐标转换中的应用

根据 matrix 定义,其中,参数 a 代表 x 方向的缩放,b 表示 y 方向的倾斜,c 为 x 方向的倾斜,d 是 y 方向的缩放,tx 是 x 方向的平移,ty 为 y 方向的平移。

矩阵相乘在坐标转换中的示例

假设我们有一个二维图形场景,其中有一个点的本地坐标为(2, 3),我们想要将其转换为全局坐标。假设有一个容器的变换矩阵A表示了一定的平移、旋转和缩放操作:

A = | 2 0 5 |

| 0 3 4 |

| 0 0 1 |

  1. 首先,将点的本地坐标表示为齐次坐标(2, 3, 1)
  2. 然后,通过矩阵相乘进行坐标转换。新的全局坐标为:

| 2 0 5 | | 2 | | 2*2 + 0*3 + 5*1 | | 9 |

| 0 3 4 | * | 3 | = | 0*2 + 3*3 + 4*1 | = |13 |

| 0 0 1 | | 1 | | 0*2 + 0*3 + 1*1 | | 1 |

所以,点的全局坐标为(9, 13)

二、逆矩阵在坐标转换中的示例

继续上面的例子,现在我们有一个全局坐标(9, 13),想要将其转换回本地坐标。

  1. 首先,计算矩阵A的逆矩阵A⁻¹。假设通过伴随矩阵法或初等变换法求出A⁻¹为:

| 0.5 0 -2.5 |

| 0 0.33 -1.33 |

| 0 0 1 |

  1. 然后,将全局坐标表示为齐次坐标(9, 13, 1)
  2. 通过矩阵相乘进行坐标转换。新的本地坐标为:

| 0.5 0 -2.5 | | 9 | | 0.5*9 + 0*13 - 2.5*1 | | 2 |

| 0 0.33 -1.33 | * | 13 | = | 0*9 + 0.33*13 - 1.33*1 | = | 3 |

| 0 0 1 | | 1 | | 0*9 + 0*13 + 1*1 | | 1 |

所以,点的本地坐标为(2, 3),与我们最初的本地坐标一致。

utils

utils 目录下,我们有一系列辅助函数,这些函数旨在简化和优化各种操作。以下是对这些方法的详细解析。

1. assignWithIgnore

目的

将一个对象的属性赋值到另一个对象中,可以通过一个可选的忽略属性对象来指定某些属性不进行赋值。

代码解析

export function assignWithIgnore<T extends Record<string, any>>(
  target: T,
  options: T,
  ignore: Record<string, boolean> = {}
) {
  // 使用for...in循环遍历源对象的属性。
  for (const key in options) {
    // 检查属性是否在忽略列表中且源对象的属性值不为undefined时,将属性值赋给目标对象。
    if (!ignore[key] && options[key] !== undefined) {
      target[key] = options[key]
    }
  }
}
  • 灵活性:提供了 ignore 选项用于精细控制,不会覆盖被忽略的属性。
  • 安全性:只复制已定义的属性,避免 undefined 给目标对象带来意外的键值对。

2. buildInstructions

目的

为渲染组构建指令集。

代码解析

export function buildInstructions(renderGroup: RenderGroup, renderer: Renderer) {
  const root = renderGroup.root
  const instructionSet = renderGroup.instructionSet

  instructionSet.reset()

  const renderPipes = renderer.renderPipes as RenderPipes

  renderPipes.batch.buildStart(instructionSet)
  renderPipes.blendMode.buildStart()
  renderPipes.colorMask.buildStart()

  if (root.sortableChildren) {
    root.sortChildren()
  }

  collectAllRenderablesAdvanced(root, instructionSet, renderer, true)

  renderPipes.batch.buildEnd(instructionSet)
  renderPipes.blendMode.buildEnd(instructionSet)
}
  • 重置指令集。
  • 依次调用各种渲染管道的buildStart方法。
  • 如果根容器有可排序的子元素,则进行排序。
  • 收集所有可渲染对象并添加到指令集中。
  • 最后调用各种渲染管道的buildEnd方法。

3. collectAllRenderables

目的

收集容器中的所有可渲染对象。

代码解析

export function collectAllRenderables(
  container: Container,
  instructionSet: InstructionSet,
  renderer: Renderer
): void {
  if (container.globalDisplayStatus < 0b111 || !container.includeInBuild) return

  if (container.sortableChildren) {
    container.sortChildren()
  }

  if (container.isSimple) {
    collectAllRenderablesSimple(container, instructionSet, renderer)
  } else {
    collectAllRenderablesAdvanced(container, instructionSet, renderer, false)
  }
}
  • 条件检查:先检查对象的可见性和包含标志,避免不必要的计算。
  • 分治设计:根据对象的复杂度选择不同的收集方法,优化性能。

4. checkChildrenDidChange

目的

检查容器的子元素是否有变化。

代码解析

export function checkChildrenDidChange(
  container: Container,
  previousData: {
    data: number[]
    index: number
    didChange: boolean
  }
) {
  const children = container.children

  for (let i = 0; i < children.length; i++) {
    const child = children[i]

    const uid = child.uid
    const didChange =
      ((child._didViewChangeTick & 0xffff) << 16) | (child._didContainerChangeTick & 0xffff)

    const index = previousData.index

    if (previousData.data[index] !== uid || previousData.data[index + 1] !== didChange) {
      previousData.data[previousData.index] = uid
      previousData.data[previousData.index + 1] = didChange

      previousData.didChange = true
    }

    previousData.index = index + 2

    if (child.children.length) {
      checkChildrenDidChange(child, previousData)
    }
  }

  return previousData.didChange
}
  • 遍历容器的子元素,比较子元素的唯一标识和变化标识与之前存储的数据是否不同,如果不同则更新数据并设置变化标志。
  • 如果子元素还有子元素,则递归地调用该方法进行检查。

5. clearList

目的

从指定索引开始清空数组中的元素。

代码解析

export function clearList(list: Array<unknown>, index?: number) {
  index ||= 0

  for (let j = index; j < list.length; j++) {
    if (list[j]) {
      list[j] = null
    } else {
      break
    }
  }
}
  • 高效实现:直接在数组中操作,避免创建新的对象。
  • 异常处理:通过 ||= 运算符确保 index 默认值为 0。

6. collectRenderGroups

目的

收集所有子渲染组。

代码解析

export function collectRenderGroups(renderGroup: RenderGroup, out: RenderGroup[] = []) {
  out.push(renderGroup)

  for (let i = 0; i < renderGroup.renderGroupChildren.length; i++) {
    collectRenderGroups(renderGroup.renderGroupChildren[i], out)
  }

  return out
}
  • 递归收集:通过递归确保所有渲染组及其子渲染组都被收集到。
  • 灵活输出:允许传入输出数组,避免重复创建对象,提高性能。

7. definedProps

目的

返回一个新对象,只包含输入对象中具有定义值的属性。

代码解析

export function definedProps<T extends Record<string, any>>(obj: T): T {
  const result: Partial<T> = {}

  for (const key in obj) {
    if (obj[key] !== undefined) {
      result[key] = obj[key]
    }
  }

  return result as T
}
  • 创建一个新的空对象。
  • 遍历输入对象的属性,如果属性值不为undefined,则将其添加到新对象中。

8. executeInstructions

目的

执行渲染组的指令集。

代码解析

export function executeInstructions(renderGroup: RenderGroup, renderer: RenderPipes) {
  const instructionSet = renderGroup.instructionSet
  const instructions = instructionSet.instructions

  for (let i = 0; i < instructionSet.instructionSize; i++) {
    const instruction = instructions[i]

    ;(renderer[instruction.renderPipeId as keyof RenderPipes] as InstructionPipe<any>).execute(
      instruction
    )
  }
}
  • 遍历指令集中的指令,根据指令的渲染管道 ID 调用相应的渲染管道的execute方法执行指令。

9. mixColorsmixStandardAnd32BitColors

目的

进行颜色混合操作。

代码解析

export function mixColors(localBGRColor: number, parentBGRColor: number) {
  if (localBGRColor === WHITE_BGR || parentBGRColor === WHITE_BGR) {
    return localBGRColor + parentBGRColor - WHITE_BGR
  }

  return mixHexColors(localBGRColor, parentBGRColor, 0.5)
}
export function mixStandardAnd32BitColors(
  localColorRGB: number,
  localAlpha: number,
  parentColor: number
) {
  const parentAlpha = ((parentColor >> 24) & 0xff) / 255

  const globalAlpha = localAlpha * parentAlpha * 255

  // flip rgb to bgr
  const localBGRColor =
    ((localColorRGB & 0xff) << 16) + (localColorRGB & 0xff00) + ((localColorRGB >> 16) & 0xff)

  const parentBGRColor = parentColor & 0x00ffffff

  let sharedBGRColor: number

  if (localBGRColor === WHITE_BGR || parentBGRColor === WHITE_BGR) {
    sharedBGRColor = localBGRColor + parentBGRColor - WHITE_BGR
  } else {
    sharedBGRColor = mixHexColors(localBGRColor, parentBGRColor, 0.5)
  }

  return sharedBGRColor + (globalAlpha << 24)
}
  • mixColors方法:根据传入的颜色值判断是否为白色,如果是白色则进行特殊处理,否则调用mixHexColors方法进行混合。
  • mixStandardAnd32BitColors方法:将本地颜色和父颜色进行混合,先处理透明度,再进行颜色混合。
  • mixHexColors方法:将两个十六进制颜色值按照给定的比例进行混合。

10. updateLocalTransform

目的

更新本地变换矩阵。

代码解析

export function updateLocalTransform(lt: Matrix, container: Container): void {
  const scale = container._scale
  const pivot = container._pivot
  const position = container._position

  const sx = scale._x
  const sy = scale._y

  const px = pivot._x
  const py = pivot._y

  // 根据容器的属性值计算矩阵值
  lt.a = container._cx * sx
  lt.b = container._sx * sx
  lt.c = container._cy * sy
  lt.d = container._sy * sy

  lt.tx = position._x - (px * lt.a + py * lt.c)
  lt.ty = position._y - (px * lt.b + py * lt.d)
}
  • 根据容器的缩放、枢轴和位置等属性计算变换矩阵的各个值。

11. updateRenderGroupTransforms

目的

更新渲染组的变换。

代码解析

export function updateRenderGroupTransforms(
  renderGroup: RenderGroup,
  updateChildRenderGroups = false
) {
  updateRenderGroupTransform(renderGroup)

  const childrenToUpdate = renderGroup.childrenToUpdate

  const updateTick = renderGroup.updateTick++

  for (const j in childrenToUpdate) {
    const renderGroupDepth = Number(j)
    const childrenAtDepth = childrenToUpdate[j]
    const list = childrenAtDepth.list
    const index = childrenAtDepth.index

    for (let i = 0; i < index; i++) {
      const child = list[i]

      if (
        child.parentRenderGroup === renderGroup &&
        child.relativeRenderGroupDepth === renderGroupDepth
      ) {
        updateTransformAndChildren(child, updateTick, 0)
      }
    }

    clearList(list, index)
    childrenAtDepth.index = 0
  }

  if (updateChildRenderGroups) {
    for (let i = 0; i < renderGroup.renderGroupChildren.length; i++) {
      updateRenderGroupTransforms(renderGroup.renderGroupChildren[i], updateChildRenderGroups)
    }
  }
}
  • 先更新当前渲染组的变换。
  • 遍历需要更新的子容器列表,根据条件更新子容器的变换和子元素。
  • 如果需要更新子渲染组,则递归地调用该方法更新子渲染组。

12. updateRenderGroupTransform

目的

更新单个渲染组的变换。

代码解析

export function updateRenderGroupTransform(renderGroup: RenderGroup) {
  const root = renderGroup.root
  let worldAlpha

  if (renderGroup.renderGroupParent) {
    const renderGroupParent = renderGroup.renderGroupParent

    renderGroup.worldTransform.appendFrom(
      root.relativeGroupTransform,
      renderGroupParent.worldTransform
    )

    renderGroup.worldColor = mixColors(root.groupColor, renderGroupParent.worldColor)

    worldAlpha = root.groupAlpha * renderGroupParent.worldAlpha
  } else {
    renderGroup.worldTransform.copyFrom(root.localTransform)
    renderGroup.worldColor = root.localColor
    worldAlpha = root.localAlpha
  }

  worldAlpha = worldAlpha < 0 ? 0 : worldAlpha > 1 ? 1 : worldAlpha
  renderGroup.worldAlpha = worldAlpha
  renderGroup.worldColorAlpha = renderGroup.worldColor + (((worldAlpha * 255) | 0) << 24)
}
  • 如果渲染组有父渲染组,则将根容器的相对组变换与父渲染组的世界变换进行组合,计算世界颜色和透明度,并更新世界颜色透明度。
  • 如果没有父渲染组,则直接将根容器的本地变换作为世界变换,将本地颜色作为世界颜色,计算世界透明度并更新世界颜色透明度。

13. updateTransformAndChildren

目的

更新容器及其子对象的变换。

代码解析

export function updateTransformAndChildren(
  container: Container,
  updateTick: number,
  updateFlags: number
) {
  if (updateTick === container.updateTick) return
  container.updateTick = updateTick

  container.didChange = false

  const localTransform = container.localTransform

  container.updateLocalTransform()

  const parent = container.parent

  if (parent && !parent.renderGroup) {
    updateFlags = updateFlags | container._updateFlags

    container.relativeGroupTransform.appendFrom(localTransform, parent.relativeGroupTransform)

    if (updateFlags & UPDATE_BLEND_COLOR_VISIBLE) {
      updateColorBlendVisibility(container, parent, updateFlags)
    }
  } else {
    updateFlags = container._updateFlags
    container.relativeGroupTransform.copyFrom(localTransform)

    if (updateFlags & UPDATE_BLEND_COLOR_VISIBLE) {
      updateColorBlendVisibility(container, tempContainer, updateFlags)
    }
  }

  if (!container.renderGroup) {
    const children = container.children
    const length = children.length

    for (let i = 0; i < length; i++) {
      updateTransformAndChildren(children[i], updateTick, updateFlags)
    }

    const renderGroup = container.parentRenderGroup

    if (container.renderPipeId && !renderGroup.structureDidChange) {
      renderGroup.updateRenderable(container)
    }
  }
}
  • 如果更新标记与容器的更新标记相同,则直接返回。
  • 更新容器的本地变换。
  • 如果容器有父容器且父容器不是渲染组,则将容器的相对组变换与父容器的相对组变换进行组合,并根据更新标记更新颜色、混合模式和可见性。
  • 如果容器没有父容器或父容器是渲染组,则直接将本地变换作为相对组变换,并根据更新标记更新颜色、混合模式和可见性。
  • 如果容器不是渲染组,则递归地更新子容器的变换和子元素。
  • 如果容器有渲染管道 ID 且渲染组的结构没有变化,则更新渲染组中的可渲染对象。

14. updateColorBlendVisibility

目的

更新颜色、混合模式和可见性。

代码解析

function updateColorBlendVisibility(
  container: Container,
  parent: Container,
  updateFlags: number
): void {
  if (updateFlags & UPDATE_COLOR) {
    container.groupColor = mixColors(container.localColor, parent.groupColor)

    let groupAlpha = container.localAlpha * parent.groupAlpha
    groupAlpha = groupAlpha < 0 ? 0 : groupAlpha > 1 ? 1 : groupAlpha

    container.groupAlpha = groupAlpha
    container.groupColorAlpha = container.groupColor + (((groupAlpha * 255) | 0) << 24)
  }

  if (updateFlags & UPDATE_BLEND) {
    container.groupBlendMode =
      container.localBlendMode === 'inherit' ? parent.groupBlendMode : container.localBlendMode
  }

  if (updateFlags & UPDATE_VISIBLE) {
    container.globalDisplayStatus = container.localDisplayStatus & parent.globalDisplayStatus
  }

  container._updateFlags = 0
}
  • 批量更新:同时处理颜色、混合模式和可见性,提高更新效率。
  • 渐变处理:对颜色和透明度进行混合处理,确保视觉效果的一致性。

15. updateWorldTransform

目的

更新容器的世界变换矩阵。

代码解析

export function updateWorldTransform(local: Matrix, parent: Matrix, world: Matrix): void {
  const lta = local.a
  const ltb = local.b
  const ltc = local.c
  const ltd = local.d
  const lttx = local.tx
  const ltty = local.ty

  const pta = parent.a
  const ptb = parent.b
  const ptc = parent.c
  const ptd = parent.d

  world.a = lta * pta + ltb * ptc
  world.b = lta * ptb + ltb * ptd
  world.c = ltc * pta + ltd * ptc
  world.d = ltc * ptb + ltd * ptd
  world.tx = lttx * pta + ltty * ptc + parent.tx
  world.ty = lttx * ptb + ltty * ptd + parent.ty
}
  • 矩阵乘法:通过矩阵乘法计算世界变换矩阵,确保变换的准确性和一致性。

16. validateRenderables

目的

验证渲染组中的可渲染对象。

代码解析

export function validateRenderables(renderGroup: RenderGroup, renderPipes: RenderPipes): boolean {
  const { list, index } = renderGroup.childrenRenderablesToUpdate
  let rebuildRequired = false

  for (let i = 0; i < index; i++) {
    const container = list[i]
    const renderable = container
    const pipe = renderPipes[renderable.renderPipeId as keyof RenderPipes] as RenderPipe<any>

    rebuildRequired = pipe.validateRenderable(container)

    if (rebuildRequired) {
      break
    }
  }

  renderGroup.structureDidChange = rebuildRequired

  return rebuildRequired
}
  • 遍历需要更新的可渲染对象列表,调用相应渲染管道的validateRenderable方法进行验证。
  • 如果有任何一个可渲染对象需要重建,则设置重建标志。

bounds

AABB(Axis-Aligned Bounding Box,轴对齐包围盒)是计算机图形学和游戏开发中常用的一种包围盒类型。AABB 是一种矩形框,在二维情况下通常用于快速判断两个对象是否有可能发生碰撞。在三维情况下,AABB 成为一个轴对齐的立方体。

AABB 的主要特点是它的边与坐标轴对齐。这简化了很多几何计算,因为不需要进行复杂的旋转或其他变换。Bounds 类正是围绕 AABB 包围盒设计的,提供了各种方法来处理几何变换和边界计算。

类属性和构造函数

Bounds 类的属性和构造函数如下:

export class Bounds {
  /** @default Infinity */
  public minX = Infinity

  /** @default Infinity */
  public minY = Infinity

  /** @default -Infinity */
  public maxX = -Infinity

  /** @default -Infinity */
  public maxY = -Infinity

  public matrix = defaultMatrix

  private _rectangle: Rectangle

  constructor(minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity) {
    this.minX = minX
    this.minY = minY
    this.maxX = maxX
    this.maxY = maxY
  }
}
  • 构造函数用于初始化 Bounds 对象的边界值。
  • minX, minY, maxX, maxY: 这些属性定义了 AABB 包围盒的边界,分别表示最小和最大 X 和 Y 坐标值。初始值分别为正无穷和负无穷,用于确保在更新边界时可以正确地调整值。
  • matrix: 这是一个默认的变换矩阵,用于边界计算,可以应用到包围盒的边界上,默认值是一个单位矩阵。
  • _rectangle: 这是一个缓存属性,用于生成边界矩形表示。

方法解析

(1)基本状态检查方法

  • isEmpty()方法用于检查包围盒是否为空,即判断最小 x 坐标是否大于最大 x 坐标或者最小 y 坐标是否大于最大 y 坐标。

(2)获取包围矩形方法

  • rectangle属性是一个 getter 方法,用于获取包围盒对应的Rectangle对象。如果内部的_rectangle不存在,则创建一个新的Rectangle对象。如果包围盒为空,则将Rectangle的尺寸设置为 0,否则根据包围盒的坐标设置Rectangle的属性。

(3)清空和设置方法

  • clear()方法用于清空包围盒,将最小坐标重置为正无穷,最大坐标重置为负无穷,并重置变换矩阵。
  • set(x0, y0, x1, y1)方法用于直接设置包围盒的坐标范围。

(4)添加边界方法

  • addFrame(x0, y0, x1, y1, matrix?)方法用于添加一个由四个坐标定义的矩形框架到包围盒中。首先,根据提供的变换矩阵(如果未提供则使用内部的默认矩阵)对四个顶点进行变换,然后更新包围盒的最小和最大坐标以包含这些变换后的顶点。
  • addRect(rect, matrix?)方法用于添加一个Rectangle对象到包围盒中,实际上是调用addFrame方法,将Rectangle的坐标转换为框架的四个坐标进行添加。
  • addBounds(bounds, matrix?)方法用于添加另一个BoundsData对象表示的包围盒到当前包围盒中,同样使用变换矩阵进行变换后添加。
  • addBoundsMask(mask)方法用于将当前包围盒与另一个Bounds对象进行掩码操作,即取两个包围盒的交集范围更新当前包围盒。
  • addVertexData(vertexData, beginOffset, endOffset, matrix?)方法用于添加一组顶点数据到包围盒中,遍历顶点数据,根据变换矩阵对每个顶点进行变换,然后更新包围盒以包含这些变换后的顶点。

(5)变换和调整方法

  • applyMatrix(matrix)方法用于将当前包围盒的坐标根据给定的变换矩阵进行变换,更新包围盒的最小和最大坐标。
  • fit(rect)方法用于调整包围盒以包含给定的Rectangle对象,比较当前包围盒的坐标与Rectangle的坐标,更新包围盒以确保完全包含Rectangle
  • fitBounds(left, right, top, bottom)方法与fit类似,用于调整包围盒以包含给定的边界范围。
  • pad(paddingX, paddingY?)方法用于在包围盒的各个方向上增加一定的填充量,通过调整最小和最大坐标实现。
  • ceil()方法用于对包围盒的坐标进行向上取整。
  • clone()方法用于创建一个当前包围盒的副本。
  • scale(x, y?)方法用于按给定的比例因子缩放包围盒的坐标。

(6)属性访问方法

  • xywidthheightleftrighttopbottom是一些属性访问器方法,用于方便地获取和设置包围盒的不同属性。例如,x属性可以获取包围盒的最小 x 坐标,也可以通过设置x属性来调整包围盒的位置。

(7)其他方法

  • isPositive属性用于检查包围盒的宽度和高度是否都为正数,表示包围盒是否有实际的尺寸。
  • isValid属性用于检查包围盒是否有效,即最小坐标是否不再是无穷大。
  • containsPoint(x, y)方法用于检查给定的坐标点是否在包围盒内部。
  • toString()方法用于返回包围盒的字符串表示,包含包围盒的坐标和尺寸信息。

相关函数的原理和机制

1. getFastGlobalBounds函数

  • 这个函数用于快速获取一个容器的全局包围盒。它首先清空输出的包围盒,然后通过递归调用_getGlobalBoundsRecursive函数来收集容器及其子容器的边界信息。如果最终得到的包围盒无效,则将其设置为一个默认的包围盒(0, 0, 0, 0)。如果容器不属于任何渲染组,则将包围盒应用容器的父渲染组的世界变换矩阵;如果容器属于渲染组,则将包围盒应用渲染组的局部变换矩阵。

2. _getGlobalBoundsRecursive函数

  • 这是一个内部递归函数,用于收集容器及其子容器的边界信息。如果容器不可见或不可测量,或者其本地显示状态不是完全可见(0b111),则直接返回。如果容器有效果列表或属于渲染组,则创建一个临时的局部包围盒用于收集边界信息,以避免影响全局包围盒。如果容器有boundsArea属性,则将其添加到包围盒中;如果容器有渲染管道 ID,则将其视图边界添加到包围盒中,并递归地处理子容器。如果容器有效果列表,则对每个效果进行处理,根据效果的addBounds方法添加边界信息,并在处理完所有效果后将局部包围盒添加到全局包围盒中。如果容器属于渲染组,则将局部包围盒添加到全局包围盒中,并释放临时包围盒到对象池。

3. getGlobalBounds函数

  • 这个函数用于获取一个容器的全局包围盒。它首先清空输出的包围盒,然后根据容器的父容器情况获取父变换矩阵。如果需要更新变换矩阵,则通过递归回溯的方式更新父变换矩阵,并从对象池中获取一个临时矩阵用于存储世界变换矩阵。然后,通过调用_getGlobalBounds函数收集容器及其子容器的边界信息,并在处理完成后释放临时矩阵到对象池。如果最终得到的包围盒无效,则将其设置为一个默认的包围盒(0, 0, 0, 0)。

4. _getGlobalBounds函数

  • 这是一个内部函数,用于收集容器及其子容器的边界信息。如果容器不可见或不可测量,则直接返回。如果需要保存边界信息(容器有效果列表),则创建一个临时的局部包围盒用于收集边界信息,以避免影响全局包围盒。如果容器有boundsArea属性,则将其添加到包围盒中;如果容器可渲染,则调用其addBounds方法添加边界信息,并递归地处理子容器。如果需要保存边界信息,则对每个效果进行处理,添加边界信息,并将局部包围盒添加到全局包围盒中,最后释放临时包围盒到对象池。如果不需要保存边界信息,则直接将子容器的边界信息添加到全局包围盒中。

5. updateTransformBackwards函数

  • 这个函数用于从一个容器开始递归地向上更新父容器的局部变换矩阵,直到到达根容器。它首先检查容器是否有父容器,如果有,则继续递归地更新父容器的变换矩阵,然后更新当前容器的局部变换矩阵,并将其与父变换矩阵进行组合。最后返回更新后的父变换矩阵。

6. getLocalBounds函数

  • 这个函数用于获取一个容器的局部包围盒。它首先清空输出的包围盒,然后根据是否是根容器的情况获取或创建相对变换矩阵。接着,通过调用_getLocalBounds函数收集容器及其子容器的边界信息。如果最终得到的包围盒无效,则将其设置为一个默认的包围盒(0, 0, 0, 0)。

7. _getLocalBounds函数

  • 这是一个内部函数,用于收集容器及其子容器的局部边界信息。如果不是根容器且容器不可见或不可测量,则直接返回。首先更新容器的局部变换矩阵,并创建相对变换矩阵,将局部变换矩阵与父变换矩阵进行组合。如果需要保存边界信息(容器有效果列表),则创建一个临时的局部包围盒用于收集边界信息,以避免影响全局包围盒。如果容器有boundsArea属性,则将其添加到包围盒中;如果容器可渲染,则调用其addBounds方法添加边界信息,并递归地处理子容器。如果需要保存边界信息,则对每个效果进行处理,添加边界信息,并将局部包围盒添加到全局包围盒中,最后释放临时包围盒到对象池。

8. getParent函数

  • 这个函数用于获取一个容器的父容器的变换矩阵。它首先检查容器是否有父容器,如果没有,则输出警告信息并返回。如果父容器不是根容器,则递归地调用自身获取父容器的变换矩阵,然后更新当前父容器的局部变换矩阵,并将其与传入的变换矩阵进行组合。

9. getGlobalRenderableBounds函数

  • 这个函数用于获取一组可渲染对象的全局包围盒。它首先清空输出的包围盒,然后遍历可渲染对象列表,对于每个可见的可渲染对象,将其世界变换矩阵设置为包围盒的矩阵,调用其addBounds方法添加边界信息,最后恢复包围盒的原始矩阵并返回包围盒。

总结

在 PixiJS 的 Scene 模块关键组件中,Container 类发挥了重要作用。其核心功能包括对象分组、子节点管理、层级控制和统一属性管理等,极大地增强了场景的灵活性和可扩展性。通过复杂的层次结构和变换传递,它方便了开发者对显示对象的管理和组织,提升了渲染性能。

RenderContainer 类允许自定义渲染逻辑,通过传递渲染函数或继承并重写 render 方法,开发者可以灵活定义自己的渲染方法。结合 container-mixins 目录下的各种混入模块,如 childrenHelperMixineffectsMixin 等,Container 类及其子类具备强大的功能扩展能力,从容应对多样化的场景需求。

另外,Bounds 类及相关函数确保了显示对象边界的精确计算和管理,通过 AABB 算法及变换矩阵优化渲染和碰撞检测。utils目录下的辅助函数通过多种优化策略,简化了渲染、边界计算、颜色处理等操作。

总之,Container 及其相关类与方法的设计,使得 Scene 模块在管理和组织显示对象、处理事件与交互、实现层次化渲染及优化性能方面表现出色,成为 PixiJS 构建复杂交互场景的坚固基石。其灵活性和高效性为开发者带来了极大的便捷和性能提升。