场景图
🌐 Scene Graph
每一帧,PixiJS 都在更新然后渲染场景图。让我们来谈谈场景图中包含的内容,以及它如何影响你开发项目的方式。如果你以前开发过游戏,这一切应该都非常熟悉,但如果你来自 HTML 和 DOM,那么在我们进入你可以渲染的特定类型的对象之前,理解这些内容是值得的。
🌐 Every frame, PixiJS is updating and then rendering the scene graph. Let's talk about what's in the scene graph, and how it impacts how you develop your project. If you've built games before, this should all sound very familiar, but if you're coming from HTML and the DOM, it's worth understanding before we get into specific types of objects you can render.
场景图是一棵树
🌐 The Scene Graph Is a Tree
场景图的根节点是由应用维护的一个容器,并通过 app.stage 引用。当你将一个精灵或其他可渲染对象作为子对象添加到舞台时,它会被添加到场景图中,并且可以被渲染和交互。PixiJS 的 Containers 也可以有子对象,因此当你构建更复杂的场景时,最终会得到一个以应用舞台为根的父子关系树。
🌐 The scene graph's root node is a container maintained by the application, and referenced with app.stage. When you add a sprite or other renderable object as a child to the stage, it's added to the scene graph and will be rendered and interactable. PixiJS Containers can also have children, and so as you build more complex scenes, you will end up with a tree of parent-child relationships, rooted at the app's stage.
(一个探索你项目的有用工具是 Chrome 的 Pixi.js 开发者工具插件,它允许你在运行时实时查看和操作场景图!)
父级和子级
🌐 Parents and Children
当父对象移动时,其子对象也会移动。当父对象旋转时,其子对象也会旋转。隐藏父对象,子对象也会被隐藏。如果你有一个由多个精灵组成的游戏对象,你可以将它们收集到一个容器下,以便在世界中作为单个对象处理,一起移动和旋转。
🌐 When a parent moves, its children move as well. When a parent is rotated, its children are rotated too. Hide a parent, and the children will also be hidden. If you have a game object that's made up of multiple sprites, you can collect them under a container to treat them as a single object in the world, moving and rotating as one.
每一帧,PixiJS 会从根节点开始遍历场景图,通过所有子节点直到叶节点,以计算每个对象的最终位置、旋转、可见性、透明度等。如果父对象的 alpha 设置为 0.5(即 50% 透明),那么它的所有子对象也会以 50% 透明度开始。如果子对象随后被设置为 0.5 alpha,它不会是 50% 透明,而是 0.5 x 0.5 = 0.25 alpha,即 75% 透明。类似地,一个对象的位置是相对于其父对象的,所以如果父对象的 x 位置设置为 50 像素,而子对象的 x 位置设置为 100 像素,那么它将在屏幕上以 150 像素的偏移绘制,即 50 + 100。
🌐 Each frame, PixiJS runs through the scene graph from the root down through all the children to the leaves to calculate each object's final position, rotation, visibility, transparency, etc. If a parent's alpha is set to 0.5 (making it 50% transparent), all its children will start at 50% transparent as well. If a child is then set to 0.5 alpha, it won't be 50% transparent, it will be 0.5 x 0.5 = 0.25 alpha, or 75% transparent. Similarly, an object's position is relative to its parent, so if a parent is set to an x position of 50 pixels, and the child is set to an x position of 100 pixels, it will be drawn at a screen offset of 150 pixels, or 50 + 100.
这是一个例子。我们将创建三个精灵,每个精灵都是上一个的子对象,并为它们的位移、旋转、缩放和透明度设置动画。即使每个精灵的属性被设置为相同的值,父子链也会放大每一次变化:
🌐 Here's an example. We'll create three sprites, each a child of the last, and animate their position, rotation, scale and alpha. Even though each sprite's properties are set to the same values, the parent-child chain amplifies each change:
场景图中任何给定节点的累积平移、旋转、缩放和倾斜存储在对象的 worldTransform 属性中。类似地,累积的不透明度值存储在 worldAlpha 属性中。
渲染顺序
🌐 Render Order
所以我们有一棵要画的东西树。谁先被画出来?
🌐 So we have a tree of things to draw. Who gets drawn first?
PixiJS 从根节点向下渲染树。在每一层,当前对象先被渲染,然后按插入顺序渲染每个子节点。因此,第二个子节点会渲染在第一个子节点之上,第三个子节点会渲染在第二个子节点之上。
🌐 PixiJS renders the tree from the root down. At each level, the current object is rendered, then each child is rendered in order of insertion. So the second child is rendered on top of the first child, and the third over the second.
查看此示例,其中 A 下有两个父对象 A 和 D,以及两个子对象 B 和 C:
🌐 Check out this example, with two parent objects A & D, and two children B & C under A:
如果你想重新排序子对象,可以使用 setChildIndex()。要在父对象的列表中的指定位置添加子对象,请使用 addChildAt()。最后,你可以使用 sortableChildren 选项并为每个子对象设置 zIndex 属性来启用对象子对象的自动排序。
🌐 If you'd like to re-order a child object, you can use setChildIndex(). To add a child at a given point in a parent's list, use addChildAt(). Finally, you can enable automatic sorting of an object's children using the sortableChildren option combined with setting the zIndex property on each child.
RenderGroups
当你深入了解 PixiJS 时,你会遇到一个强大的功能,称为渲染组(Render Groups)。可以将渲染组视为场景图中的专用容器,它们本身就像迷你场景图。下面是你需要了解的,以便在项目中有效使用渲染组。如需更多信息,请查看 RenderGroups 概述
🌐 As you delve deeper into PixiJS, you'll encounter a powerful feature known as Render Groups. Think of Render Groups as specialized containers within your scene graph that act like mini scene graphs themselves. Here's what you need to know to effectively use Render Groups in your projects. For more info check out the RenderGroups overview
剔除
🌐 Culling
如果你正在构建一个场景中大量对象都在屏幕外的项目(比如一个横向滚动游戏),你会想要对这些对象进行_裁剪_。裁剪是评估一个对象(或其子对象)是否在屏幕上,如果不在,则关闭该对象的渲染的过程。如果你不裁剪屏幕外的对象,渲染器仍然会绘制它们,即使它们的像素最终没有显示在屏幕上。
🌐 If you're building a project where a large proportion of your scene objects are off-screen (say, a side-scrolling game), you will want to cull those objects. Culling is the process of evaluating if an object (or its children!) is on the screen, and if not, turning off rendering for it. If you don't cull off-screen objects, the renderer will still draw them, even though none of their pixels end up on the screen.
PixiJS 提供对视口裁剪的内置支持。要启用裁剪,请在你的对象上设置 cullable = true。你还可以将 cullableChildren 设置为 false,以允许 PixiJS 跳过递归裁剪函数,这可以提高性能。此外,你可以设置 cullArea 通过定义要裁剪的区域来进一步优化性能。
🌐 PixiJS provides built-in support for viewport culling. To enable culling, set cullable = true on your objects. You can also set cullableChildren to false to allow PixiJS to bypass the recursive culling function, which can improve performance. Additionally, you can set cullArea to further optimize performance by defining the area to be culled.
本地坐标与全局坐标
🌐 Local vs Global Coordinates
如果你向舞台添加一个精灵,默认情况下它会出现在屏幕的左上角。那是 PixiJS 使用的全局坐标空间的原点。如果你的所有对象都是舞台的子对象,那么那将是你唯一需要关心的坐标。但一旦引入容器和子对象,情况就会变得更复杂。位于 [50, 100] 的子对象是相对于它的父对象向右 50 像素、向下 100 像素的位置。
🌐 If you add a sprite to the stage, by default it will show up in the top left corner of the screen. That's the origin of the global coordinate space used by PixiJS. If all your objects were children of the stage, that's the only coordinates you'd need to worry about. But once you introduce containers and children, things get more complicated. A child object at [50, 100] is 50 pixels right and 100 pixels down from its parent.
我们将这两种坐标系统称为“全局”和“局部”坐标。当你在一个对象上使用 position.set(x, y) 时,你总是在使用局部坐标,相对于对象的父对象。
🌐 We call these two coordinate systems "global" and "local" coordinates. When you use position.set(x, y) on an object, you're always working in local coordinates, relative to the object's parent.
问题是,很多时候你想知道一个对象的全局位置。例如,如果你想剔除屏幕外的对象以节省渲染时间,你需要知道给定的子对象是否在视图矩形之外。
🌐 The problem is, there are many times when you want to know the global position of an object. For example, if you want to cull offscreen objects to save render time, you need to know if a given child is outside the view rectangle.
要从局部坐标转换到全局坐标,你可以使用 toGlobal() 函数。下面是一个示例用法:
🌐 To convert from local to global coordinates, you use the toGlobal() function. Here's a sample usage:
// Get the global position of an object, relative to the top-left of the screen
let globalPos = obj.toGlobal(new Point(0, 0));
这段代码将把 globalPos 设置为子对象的全局坐标,相对于全局坐标系中的 [0, 0]。
🌐 This snippet will set globalPos to be the global coordinates for the child object, relative to [0, 0] in the global coordinate system.
全局坐标与屏幕坐标
🌐 Global vs Screen Coordinates
当你的项目与主操作系统或浏览器协作时,会涉及第三种坐标系统——“屏幕”坐标(也称为“视口”坐标)。屏幕坐标表示相对于 PixiJS 渲染的画布元素左上角的位置。像 DOM 和原生鼠标点击事件这样的内容都在屏幕空间中工作。
🌐 When your project is working with the host operating system or browser, there is a third coordinate system that comes into play - "screen" coordinates (aka "viewport" coordinates). Screen coordinates represent position relative to the top-left of the canvas element that PixiJS is rendering into. Things like the DOM and native mouse click events work in screen space.
现在,在许多情况下,屏幕空间等同于世界空间。如果画布的大小与创建 Application 时指定的渲染视图大小相同,则属于这种情况。默认情况下,情况通常如此——例如,你会创建一个 800x600 的应用窗口并将其添加到你的 HTML 页面中,并且它将保持这个大小。世界坐标中的 100 像素将等于屏幕空间中的 100 像素。但是!通常会将渲染视图拉伸以填充屏幕,或者以较低的分辨率渲染并进行放大以提高速度。在这种情况下,画布元素的屏幕尺寸会发生改变(例如通过 CSS),但底层的渲染视图不会,从而导致世界坐标与屏幕坐标之间不匹配。
🌐 Now, in many cases, screen space is equivalent to world space. This is the case if the size of the canvas is the same as the size of the render view specified when you create you Application. By default, this will be the case - you'll create for example an 800x600 application window and add it to your HTML page, and it will stay that size. 100 pixels in world coordinates will equal 100 pixels in screen space. BUT! It is common to stretch the rendered view to have it fill the screen, or to render at a lower resolution and up-scale for speed. In that case, the screen size of the canvas element will change (e.g. via CSS), but the underlying render view will not, resulting in a mis-match between world coordinates and screen coordinates.