三个文件,都比较长:
- Container:绘制对象的容器,不代表具体的物体,但是可以对其做一些蒙版和滤镜操作,类似于PS里的图层
- DisplayObject:可绘制对象的基类,包括Container,Graphics,Sprites等等
- Bounds:AABB的bounding box,一般图形库应该有的数据结构
所以Display包本质上定义了一些图形的基类和容器,相当于抽象的图形,大概率会有一系列图形操作和变换的方法。
Bounds
从最简单的Bounds入手。由于在three.js源码阅读中已经对渲染库的数学知识有一定了解了,这里直接忽略一些基本的数学操作,关注有特点的东西。
于是发现,没了。。。
Bounds本身就只有minX,minY,maxX,maxY等属性,再加上一个rectangle对象。
其余的就是各种各样的变换,以及和其它对象的运算求运算后bound的操作,基本都比较清晰。
DisplayObject
DisplayObject是其它所有可绘制对象的抽象类,具体见文档:https://pixijs.download/release/docs/PIXI.DisplayObject.html
DisplayObject的很大一部分便是其中包含的属性,以及在创建的时候constructor中为这些属性赋值。
一些属性比较直观:
- transform: 设置变换参数,包括位置,旋转,缩放等等,有一系列的getter/setter以及setTransform函数来设置与获取
- alpha:opacity设置
- worldAlpha:环境的alpha
- parent: Container,对象的父对象,有对应的setParent函数设置
- filterArea,filter,_enableFilters:为物体添加滤镜效果,设置滤镜的影响区域,滤镜就是WebGL的那些颜色混合属性
- isSprite: 判断是否是sprite,(有点奇怪,专门有个属性判断)
一些(抽象)方法也比较直观:
- setParent:设置对象的parent,parent对象直接添加一个child就好了
- removeChild:抽象方法,字面意思;
- render:抽象方法,字面意思,具体的对象应该有具体的实现
除此之外,DisplayObject还有一些事件,在进行一些行为的时候触发:
- added
- removed
- destroyed
destroy
destroy函数处理了与parent和listener的关系,同时释放自己的资源,并emit自己destroy的信息,同时,内部的_destroyed
属性用来标记object是否被销毁。这些步骤可以学习下:
destroy(_options?: IDestroyOptions|boolean): void
{
if (this.parent)
{
this.parent.removeChild(this);
}
this.emit('destroyed');
this.removeAllListeners();
this.transform = null;
this.parent = null;
// ...
this._destroyed = true;
}
可见性
Pixi用visible,renderable两个属性控制可见性。
两者有一定区别,visible: false类似于css中的display:none,renderable: false类似css中的visible: hidden。
另外,判断一个object在整个世界中可见的,需要其所有上层对象都是可见的,因此:
get worldVisible(): boolean
{
let item = this as DisplayObject;
do
{
if (!item.visible)
{
return false;
}
item = item.parent;
} while (item);
return true;
}
遮挡顺序
Pixi需要二维平面内物体的遮挡顺序,因此需要引入额外的信息,zIndex用以控制顺序,如果两个的zIndex相同,按照原始顺序排列。
控制遮挡顺序的属性包括:
- _zIndex: 字面意思,用来控制遮挡顺序
- _lastSortedIndex: 原始排序,用于在_zIndex相同的情况下进行排序
- sortDirty: children的排列顺序是否正确(在children更新index之后,顺序可能被打乱,后续需要重新排列)
Mask
Pixi支持用一个Object作为另一个物体的遮罩,也就是设置该物体的mask属性。而作为mask的对象,不会被渲染。Pixi内部在mask的setter中,配合_maskRefCount
来实现这一功能。
set mask(value: Container|MaskData|null)
{
// ...
if (this._mask)
{
const maskObject = ((this._mask as MaskData).maskObject || this._mask) as Container;
maskObject._maskRefCount--;
if (maskObject._maskRefCount === 0)
{
maskObject.renderable = true;
maskObject.isMask = false;
}
}
this._mask = value;
if (this._mask)
{
const maskObject = ((this._mask as MaskData).maskObject || this._mask) as Container;
if (maskObject._maskRefCount === 0)
{
maskObject.renderable = false;
maskObject.isMask = true;
}
maskObject._maskRefCount++;
}
}
_maskRefCount
记录了当前对象是否被用作mask,如果被用作mask(_maskRefCount != 0
)则不会被渲染,标记为是mask。而当取消作为mask之后,则更新_maskRefCount
。
tempDisplayObject
Pixi提供了一个默认的DisplayObject对象:tempDisplayObject
。
export class TemporaryDisplayObject extends DisplayObject
{
calculateBounds: () => null;
removeChild: (child: DisplayObject) => null;
render: (renderer: Renderer) => null;
sortDirty: boolean = null;
}
也就是把DisplayObject中abstract的属性补全了下,最小的实例对象。
这个对象一般作为对象的临时parent,用来进行一些以root为基准transform时使用。
有控制是否使用其作为parent的方法:
enableTempParent(): Container
{
const myParent = this.parent;
this.parent = this._tempDisplayObjectParent as Container;
return myParent;
}
disableTempParent(cacheParent: Container): void
{
this.parent = cacheParent;
}
mixin
把一些对象上的属性和方法附加到DisplayObject上,这里可以学习一下API,用Object.getOwnPropertyDescriptor
和Object.defineProperty
可以把各种配置直接复制过来。
static mixin(source: Dict<any>): void
{
// get all the enumerable property keys
const keys = Object.keys(source);
// loop through properties
for (let i = 0; i < keys.length; ++i)
{
const propertyName = keys[i];
// Set the property using the property descriptor - this works for accessors and normal value properties
Object.defineProperty(
DisplayObject.prototype,
propertyName,
Object.getOwnPropertyDescriptor(source, propertyName)
);
}
}
Transform
DisplayObject里有transform对象,同时暴露出许多相关的getter和setter可以设置transform中的位置,缩放等等,如setTransform
, updateTransform
等方法。
另外值得注意的是,在updateTransform
中,更新了_boundsID
,并重新计算了对象的worldAlpha
。
displayObjectUpdateTransform
关于displayObjectUpdateTransform
这个函数,相当于是为DisplayObject保留了自己的updateTransform
操作?子类overrideupdateTransform
函数的时候,依然在toLocal
, toGlobal
函数中可以使用DisplayObject中的updateTransform
吧。
只能这样解释了,不然说不通为啥毕竟只用updateTransform
就可以了。
TODO:不理解,需要后续看看
/**
* DisplayObject default updateTransform, does not update children of container.
* Will crash if there's no parent element.
*
* @memberof PIXI.DisplayObject#
* @method displayObjectUpdateTransform
*/
DisplayObject.prototype.displayObjectUpdateTransform = DisplayObject.prototype.updateTransform;
toLocal/toGlobal
功能比较好理解:
toGlobal
将当前object的局部坐标系下的点转化为全局的坐标toLocal
将全局的点,或者另一个object局部坐标系下的点转化为当前object局部坐标系下的坐标
以toGlobal
为例:
toGlobal<P extends IPointData = Point>(position: IPointData, point?: P, skipUpdate = false): P
{
if (!skipUpdate)
{
this._recursivePostUpdateTransform();
if (!this.parent)
{
this.parent = this._tempDisplayObjectParent as Container;
this.displayObjectUpdateTransform();
this.parent = null;
}
else
{
this.displayObjectUpdateTransform();
}
}
// don't need to update the lot
return this.worldTransform.apply<P>(position, point);
}
如果不要求更新object的transform,只要应用当前的worldTransform
即可。
如果要更新object的transform,则需要进行如下两步操作:
this._recursivePostUpdateTransform();
this.displayObjectUpdateTransform();
基本的功能就是递归更新transform,不过为什么最后还要执行displayObjectUpdateTransform
还是有点不理解。TODO!!
toLocal
的话,与toGlobal
刚好相反,应用逆矩阵即可。对于相对object的处理也比较简单,先转到global再到另一个的local即可:
if (from)
{
position = from.toGlobal(position, point, skipUpdate);
}
// ...
return this.worldTransform.applyInverse<P>(position, point);
Bounds
一些bounds相关的属性:
- _bounds: 包围盒,相对于世界坐标系来说
- _localBounds: local坐标系下的包围盒,相对于父元素的坐标系来说
- _boundsID: 控制bounds是否dirty,bounds可能会有计算和cache的过程(TODO)
- _boundsRect: _bounds对应的rectangle,也是一个cache,防止重新计算rect
- _localBoundsRect: _localBounds对应的rectangle
涉及的三个方法:
calculateBounds
抽象方法,每种object有自己计算bound的方式
getBounds
基本比较常规,主要做三件事情:
- 更新transform(如果有必要)
- 调用
calculateBounds
计算bounds(如果有必要) - 更新
_boundsRect
,并返回rect
getLocalBounds
getLocalBounds(rect?: Rectangle): Rectangle
{
// ...
const transformRef = this.transform;
const parentRef = this.parent;
this.parent = null;
this.transform = this._tempDisplayObjectParent.transform;
const worldBounds = this._bounds;
const worldBoundsID = this._boundsID;
this._bounds = this._localBounds;
const bounds = this.getBounds(false, rect);
this.parent = parentRef;
this.transform = transformRef;
this._bounds = worldBounds;
this._bounds.updateID += this._boundsID - worldBoundsID;// reflect side-effects
return bounds;
}
好家伙,getLocalBounds
的实现就太暴力了吧。
把object的parent替换掉,以它自己作为root,重新执行calculateBounds
,这样就得到了local的结果
难怪:
Do know that usage of
getLocalBounds
can corrupt the_bounds
of children (the whole subtree, actually). This is a known issue that has not been solved. See [getLocalBounds] for more details.
不过最后updateID
的操作还有点不太明白,为何如此设置,虽然是很细节的东西啦,就是为了标记children都已经是dirty的了。
Container
容器对象,可以用来整合对象,统一进行操作。
Container继承自DisplayObject,不过并非抽象类,可以直接创建一个Container。
看属性,没几个,主要是children相关的属性和操作。
属性
children
DisplayObject数组类型,用来存放children
parent
container自己的parent,也是Container类型
sortableChildren
sortDirty
当前顺序是否dirty,是否需要再下一次更新的时候sort
很简单啦,然后方法大概率也是操作相关的属性吧。
width/height
一个container有啥width/height呢?这里的设定还是比较有趣的
get width(): number
{
return this.scale.x * this.getLocalBounds().width;
}
set width(value: number)
{
const width = this.getLocalBounds().width;
if (width !== 0)
{
this.scale.x = value / width;
}
else
{
this.scale.x = 1;
}
this._width = value;
}
相当于是自己所有children的localBounds,乘以自己的scale,很合理
set的时候,自然不能挨个改children的size呀,所以只好改自己的scale了
另外,_width
和_height
属性好像没啥用吧,可能就是调试用的
方法
onChildrenChange
暴露出一个接口,可以让子类去override,定义当children change时候的行为。
addChild
可以支持多个参数,不过只看添加一个的部分:
// if the child has a parent then lets remove it as PixiJS objects can only exist in one place
if (child.parent)
{
child.parent.removeChild(child);
}
child.parent = this;
this.sortDirty = true;
// ensure child transform will be recalculated
child.transform._parentID = -1;
this.children.push(child);
// ensure bounds will be recalculated
this._boundsID++;
// TODO - lets either do all callbacks or all events.. not both!
this.onChildrenChange(this.children.length - 1);
this.emit('childAdded', child, this, this.children.length - 1);
child.emit('added', this);
基本就是,原parent的children里删掉,新parent里的增加,并添加child的parent关系
同时,通过对应counter标记transform和bounds为dirty
最后触发回调函数或添加child的事件
destroy
也没有什么特别好说的,基本上就是处理内部存储的信息,以及触发事件什么的
可以选择要不要destroy children
updateTransform
从上到下,更新自己的以及自己children的transform
同时谁当自己的worldAlpha
,其实在这里设置这个属性蛮尴尬的
calculateBounds
calculateBounds(): void
{
this._bounds.clear();
this._calculateBounds();
for (let i = 0; i < this.children.length; i++)
{
const child = this.children[i];
if (!child.visible || !child.renderable)
{
continue;
}
child.calculateBounds();
// TODO: filter+mask, need to mask both somehow
if (child._mask)
{
const maskObject = ((child._mask as MaskData).maskObject || child._mask) as Container;
maskObject.calculateBounds();
this._bounds.addBoundsMask(child._bounds, maskObject._bounds);
}
else if (child.filterArea)
{
this._bounds.addBoundsArea(child._bounds, child.filterArea);
}
else
{
this._bounds.addBounds(child._bounds);
}
}
this._bounds.updateID = this._boundsID;
}
自己的_calculateBounds
是空的,就不讨论了
依次计算children的bounds
不过这里考虑了mask和filter的存在,只计算child的有效区域
getLocalBounds
public getLocalBounds(rect?: Rectangle, skipChildrenUpdate = false): Rectangle
{
const result = super.getLocalBounds(rect);
if (!skipChildrenUpdate)
{
for (let i = 0, j = this.children.length; i < j; ++i)
{
const child = this.children[i];
if (child.visible)
{
child.updateTransform();
}
}
}
return result;
}
基本是调用DisplayObject的getLocalBounds
,不过这里要对所有的children执行updateTransform
,应该是在计算localBounds的时候破坏了children的transform
render
render(renderer: Renderer): void
{
// if the object is not visible or the alpha is 0 then no need to render this element
if (!this.visible || this.worldAlpha <= 0 || !this.renderable)
{
return;
}
// do a quick check to see if this element has a mask or a filter.
if (this._mask || (this.filters && this.filters.length))
{
this.renderAdvanced(renderer);
}
else
{
this._render(renderer);
// simple render children!
for (let i = 0, j = this.children.length; i < j; ++i)
{
this.children[i].render(renderer);
}
}
}
有必要好好看看,算是比较重要的方法了。
对于visible
, alpha
和renderable
进行一个初步的判断。
如果没有mask或者filter,直接调用自身的_render
函数和children的render
Container的_render
是空的,因为本身不需要画出什么东西,如果需要自定义的container,可能会加入一些东西。
如果有mask或filter,则需要更复杂的render:renderAdvanced
protected renderAdvanced(renderer: Renderer): void
{
const filters = this.filters;
const mask = this._mask as MaskData;
// push filter first as we need to ensure the stencil buffer is correct for any masking
if (filters)
{
if (!this._enabledFilters)
{
this._enabledFilters = [];
}
this._enabledFilters.length = 0;
for (let i = 0; i < filters.length; i++)
{
if (filters[i].enabled)
{
this._enabledFilters.push(filters[i]);
}
}
}
const flush = (filters && this._enabledFilters && this._enabledFilters.length)
|| (mask && (!mask.isMaskData
|| (mask.enabled && (mask.autoDetect || mask.type !== MASK_TYPES.NONE))));
if (flush)
{
renderer.batch.flush();
}
if (filters && this._enabledFilters && this._enabledFilters.length)
{
renderer.filter.push(this, this._enabledFilters);
}
if (mask)
{
renderer.mask.push(this, this._mask);
}
// add this object to the batch, only rendered if it has a texture.
this._render(renderer);
// now loop through the children and make sure they get rendered
for (let i = 0, j = this.children.length; i < j; i++)
{
this.children[i].render(renderer);
}
if (flush)
{
renderer.batch.flush();
}
if (mask)
{
renderer.mask.pop(this);
}
if (filters && this._enabledFilters && this._enabledFilters.length)
{
renderer.filter.pop();
}
}
这函数有亿点点麻烦呀。
其实仔细拆分下来也还好,首先整理下filter和mask,并判断两者是否至少存在一个,如果存在,没办法使用batch render。
紧接着就是给renderer设置mask和filter,交给renderer去做了,这里并不涉及细节。
绘制完之后,将添加给renderer的mask和filter再删掉。
TODO:不过有一点不明白,没啥render完之后,还要flush呢?此时不应该有一个flush的逆操作么?
这个可能要之后看core的时候再研究了。
其它的一些关于children操作的方法都比较常规,无非是获取,设置,交换位置等等
最后,Container的updateTransform
也是默认的,就搞不懂了
Container.prototype.containerUpdateTransform = Container.prototype.updateTransform;
TODO
displayObjectUpdateTransform
isSprite
属性getLocalBounds
里updateID
的更新renderAdvanced
中的第二个flush
操作