From 01f14ea645bf0c87d2991b9d4fed5686494e1a4a Mon Sep 17 00:00:00 2001 From: Mat Groves Date: Tue, 5 Mar 2024 17:49:04 +0000 Subject: [PATCH] Feat: v8 guides update (#74) * Restructuring: Move Tutorial and Playground into the Docusaurus Docs Structure * Fix main container styling in coding mode for full-screen * Chore: Website Restructuring * Updated content generation scripts * Minor fixes * Chore: Website Versioning * Chore: Create v7.x Snapshot + Make Working Docs the Pre-Release v8 * Versions page fixes * Tweaked Sandpack Dependencies Config * Hotfix: Restructure Source Examples and Tutorial * Type hotfix * Pre-Release v8 Examples Update * Added back parcel-bundler for all pixi versions * More examples updated * More examples update * Updated/Added/Removed more examples * Reverted conditioned babel core dev dependency * Pre-update Shader examples * Revert Ticker.shared back to app.ticker * Lint fix * Chore: Upgrade Working Docs to use PixiJS v8.0.0-rc.1 * Remove v8.0.0-rc (old) content * Simplify source versioning for examples and tutorials * Bump up to v8.0.0-rc.2 * Type fix * Add ghtoken ignore * Fix Versioned Links * Upgrade to RC4 * Chore: Website Versioning Scripts (#52) * Chore: Website Versioning * Versions page fixes * Add `Working with Content` Section on README (#54) * Added Working with Content Section on README * Quick adjustment --------- Co-authored-by: Baz Utsahajit * Chore: Create v7.x Snapshot + Make Working Docs the Pre-Release v8 (#53) * Chore: Create v7.x Snapshot + Make Working Docs the Pre-Release v8 * Tweaked Sandpack Dependencies Config * Hotfix: Restructure Source Examples and Tutorial (#55) * Hotfix: Restructure Source Examples and Tutorial * Type hotfix * Added back parcel-bundler for all pixi versions * Reverted conditioned babel core dev dependency * Chore: Upgrade Working Docs to use PixiJS v8.0.0-rc.2 and Simplify examples and tutorial source versioning (#60) * Chore: Upgrade Working Docs to use PixiJS v8.0.0-rc.1 * Simplify source versioning for examples and tutorials * Bump up to v8.0.0-rc.2 * Type fix * Add ghtoken ignore * Fix Versioned Links * Upgrade to RC4 * More Assorted fixes --------- Co-authored-by: Baz Utsahajit --------- Co-authored-by: Baz Utsahajit --------- Co-authored-by: Baz Utsahajit --------- Co-authored-by: Baz Utsahajit * Upgrade to RC4 * No footer on example pages * Update v7.x versioned docs * Use semver to pick the latest version instead of relying on the latest tag * Update Versions Page + Add devs and unmaintained versions * More readme update * Allowing user to customize pixi version label * Add the ability to manage generic snapshots * add v8 migration guide * Combine gitignores into the root one * Updated Mesh and Shaders Section * Remove Nested Boundary with Projection Example * Remove 7.3.2 * Update generate example docs script to fix sidebar names * Add ability to add extra packages to the playground * Split out Triangle example into multiple files * Update examples config to allow multiple files * Adapt syntax highlighting on tab switches * Extract multi-file handling into a useCodeSource function * Split long examples into multiple files * Allow active file override with '$' * Editor tutorial code change fix * Allow extra packages on tutorial + add babel plugin * Resolve conflict * Fix editor styling and persisting playground changes * Updated editor now keep persisted changes on all visible files + Fix tabs overflowing * Tabs style update and fixes * Upgrade to RC5 * Remove unused types and functions * Fix tab scroll * Fix texture url * Lint Fix * Remove ShaderToy Filter Render Texture Example * Update Instanced Geometry + Multipass Mesh Codes * Bump up to RC6 * Uncomment in instanced geometry * Fix uniform float type * fix up the custom shaders for gl * Upgrade to RC7 * Updated v7.x to latest * add more examples for v8 * v8 launch post * remove unused images * PixiJS not Pixi * missed one * update based on feedback * added final migration for v8 * typo * typos * typo * final tweak * guide updates * update * many fixes * added final migration for v8 (#73) * added final migration for v8 * typo * typos * typo * final tweak * feedback * undo versions * remove blog * update current --------- Co-authored-by: Baz Utsahajit Co-authored-by: bbazukun123 Co-authored-by: Zyie <24736175+Zyie@users.noreply.github.com> --- blog/2024-03-01-pixi-v8-launches.md | 6 +- docs/guides/advanced/render-groups.md | 45 +++++ docs/guides/basics/architecture-overview.md | 16 +- docs/guides/basics/getting-started.md | 30 +-- docs/guides/basics/render-loop.md | 2 +- docs/guides/basics/scene-graph.md | 65 ++++--- docs/guides/basics/what-pixijs-is.md | 8 +- docs/guides/components/assets.md | 174 ++++++++++++++---- docs/guides/components/containers.md | 80 +++++--- docs/guides/components/display-object.md | 21 --- docs/guides/components/graphics.md | 126 +++++++++---- docs/guides/components/interaction.md | 16 +- docs/guides/components/sprite-sheets.md | 10 +- docs/guides/components/sprites.md | 10 +- docs/guides/components/text.md | 60 +++--- docs/guides/components/textures.md | 48 +++-- docs/guides/production/performance-tips.md | 15 +- docusaurus.config.js | 1 + scripts/update-global-version-configs.js | 16 +- sidebars.js | 14 +- .../Homepage/ClosingSection/index.tsx | 2 +- src/components/Homepage/HeroHeader/index.tsx | 2 +- 22 files changed, 491 insertions(+), 276 deletions(-) create mode 100644 docs/guides/advanced/render-groups.md delete mode 100644 docs/guides/components/display-object.md diff --git a/blog/2024-03-01-pixi-v8-launches.md b/blog/2024-03-01-pixi-v8-launches.md index a2eab24ab..891a30bd0 100644 --- a/blog/2024-03-01-pixi-v8-launches.md +++ b/blog/2024-03-01-pixi-v8-launches.md @@ -39,8 +39,8 @@ We're incredibly proud of PixiJS v8 and eager to share the improvements and new ## 🔗 Quick links - The new Docs for v8 can be found here [LINK TO DOCS] -- [Migration](/next/guides/migrations/v8) -- [Examples](/next/examples) +- [Migration](/guides/migrations/v8) +- [Examples](/examples) - [Open Games](https://github.com/pixijs/open-games) --- @@ -197,7 +197,7 @@ myContainer.blendMode = 'color-burn` // easy! ``` -For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the [migration guide](/next/guides/migrations/v8), or why not jump in and play with some [examples](next/examples/graphics/simple). +For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the [migration guide](/guides/migrations/v8), or why not jump in and play with some [examples](examples/graphics/simple). #### 📝 Text Upgrades diff --git a/docs/guides/advanced/render-groups.md b/docs/guides/advanced/render-groups.md new file mode 100644 index 000000000..c5a16b62a --- /dev/null +++ b/docs/guides/advanced/render-groups.md @@ -0,0 +1,45 @@ +# Render Groups + +## Understanding RenderGroups in PixiJS + +As you delve deeper into PixiJS, especially with version 8, you'll encounter a powerful feature known as RenderGroups. Think of RenderGroups 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: + +### What Are Render Groups? + +Render Groups are essentially containers that PixiJS treats as self-contained scene graphs. When you assign parts of your scene to a Render Group, you're telling PixiJS to manage these objects together as a unit. This management includes monitoring for changes and preparing a set of render instructions specifically for the group. This is a powerful tool for optimizing your rendering process. + +### Why Use Render Groups? + +The main advantage of using Render Groups lies in their optimization capabilities. They allow for certain calculations, like transformations (position, scale, rotation), tint, and alpha adjustments, to be offloaded to the GPU. This means that operations like moving or adjusting the Render Group can be done with minimal CPU impact, making your application more performance-efficient. + +In practice, you're utilizing Render Groups even without explicit awareness. The root element you pass to the render function in PixiJS is automatically converted into a RenderGroup as this is where its render instructions will be stored. Though you also have the option to explicitly create additional RenderGroups as needed to further optimize your project. + +This feature is particularly beneficial for: + +- **Static Content:** For content that doesn't change often, a Render Group can significantly reduce the computational load on the CPU. In this case static refers to the scene graph structure, not that actual values of the PixiJS elements inside it (eg position, scale of things). +- **Distinct Scene Parts:** You can separate your scene into logical parts, such as the game world and the HUD (Heads-Up Display). Each part can be optimized individually, leading to overall better performance. + +### Examples + +```ts +const myGameWorld = new Container({ + isRenderGroup:true +}) + +const myHud = new Container({ + isRenderGroup:true +}) + +scene.addChild(myGameWorld, myHud) + +renderer.render(scene) // this action will actually convert the scene to a render group under the hood +``` + +Check out the [container example] (../../examples/basic/container). + +### Best Practices + +- **Don't Overuse:** While Render Groups are powerful, using too many can actually degrade performance. The goal is to find a balance that optimizes rendering without overwhelming the system with too many separate groups. Make sure to profile when using them. The majority of the time you won't need do use them at all! +- **Strategic Grouping:** Consider what parts of your scene change together and which parts remain static. Grouping dynamic elements separately from static elements can lead to performance gains. + +By understanding and utilizing Render Groups, you can take full advantage of PixiJS's rendering capabilities, making your applications smoother and more efficient. This feature represents a powerful tool in the optimization toolkit offered by PixiJS, enabling developers to create rich, interactive scenes that run smoothly across different devices. diff --git a/docs/guides/basics/architecture-overview.md b/docs/guides/basics/architecture-overview.md index f74a2ceb7..806cf34e0 100644 --- a/docs/guides/basics/architecture-overview.md +++ b/docs/guides/basics/architecture-overview.md @@ -8,18 +8,16 @@ Before we get into how the code is layed out, let's talk about where it lives. ## The Components -PixiJS is a modular rendering engine. Each task required for generating, updating and displaying content is broken out into its own component. Not only does this make the code cleaner, it allows for greater extensibility. Additionally, with the use of the [PixiJS Customize tool](https://pixijs.io/customize/), it's possible to build a custom PixiJS file containing only the subset of features your project needs, saving download size. - Here's a list of the major components that make up PixiJS. Note that this list isn't exhaustive. Additionally, don't worry too much about how each component works. The goal here is to give you a feel for what's under the hood as we start exploring the engine. ### Major Components | Component | Description | | --- | --- | -| **Renderer** `@pixi/core` | The core of the PixiJS system is the renderer, which displays the scene graph and draws it to the screen. The default renderer for PixiJS is based on WebGL under the hood. | -| **Container** `@pixi/display` | Main display object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See [Scene Graph](scene-graph) for more details. | -| **Loader** `@pixi/loader` | The loader system provides tools for asynchronously loading resources such as images and audio files. | -| **Ticker** `@pixi/ticker` | Tickers provide periodic callbacks based on a clock. Your game update logic will generally be run in response to a tick once per frame. You can have multiple tickers in use at one time. | -| **Application** `@pixi/app` | The Application is a simple helper that wraps a Loader, Ticker and Renderer into a single, convenient easy-to-use object. Great for getting started quickly, prototyping and building simple projects. | -| **Interaction** `@pixi/interaction` | PixiJS supports both touch and mouse-based interaction - making objects clickable, firing hover events, etc. | -| **Accessibility** `@pixi/accessibility` | Woven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility. | +| **Renderer** | The core of the PixiJS system is the renderer, which displays the scene graph and draws it to the screen. PixiJS will automatically determine whether to provide you the WebGPU or WebGL renderer under the hood. | +| **Container** | Main scene object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See [Scene Graph](scene-graph) for more details. | +| **Assets** | The Asset system provides tools for asynchronously loading resources such as images and audio files. | +| **Ticker** | Tickers provide periodic callbacks based on a clock. Your game update logic will generally be run in response to a tick once per frame. You can have multiple tickers in use at one time. | +| **Application** | The Application is a simple helper that wraps a Loader, Ticker and Renderer into a single, convenient easy-to-use object. Great for getting started quickly, prototyping and building simple projects. | +| **Events** | PixiJS supports pointer-based interaction - making objects clickable, firing hover events, etc. | +| **Accessibility** | Woven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility. | diff --git a/docs/guides/basics/getting-started.md b/docs/guides/basics/getting-started.md index a58391bb2..2bf35c5b3 100644 --- a/docs/guides/basics/getting-started.md +++ b/docs/guides/basics/getting-started.md @@ -31,7 +31,7 @@ OK! With those notes out of the way, let's get started. There are only a few s * Create an HTML file * Serve the file with a web server * Load the PixiJS library -* Create an [Application](https://pixijs.download/release/docs/PIXI.Application.html) +* Create an [Application](https://pixijs.download/release/docs/app.Application.html) * Add the generated view to the DOM * Add an image to the stage * Write an update loop @@ -81,27 +81,28 @@ Loading the library doesn't do much good if we don't *use* it, so the next step ```html ``` -What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. [Application](https://pixijs.download/release/docs/PIXI.Application.html) is a helper class that simplifies working with PixiJS. It creates the renderer, creates the stage, and starts a ticker for updating. In production, you'll almost certainly want to do these steps yourself for added customization and control - we'll cover doing so in a later guide. For now, the Application class is a perfect way to start playing with PixiJS without worrying about the details. +What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. [Application](https://pixijs.download/release/docs/app.Application.html) is a helper class that simplifies working with PixiJS. It creates the renderer, creates the stage, and starts a ticker for updating. In production, you'll almost certainly want to do these steps yourself for added customization and control - we'll cover doing so in a later guide. For now, the Application class is a perfect way to start playing with PixiJS without worrying about the details. The `Application` class also has a method `init` that will initialize the application with the given options. This method is asynchronous, so we use the `await` keyword to wait for it to complete. This is because PixiJS uses WebGPU or WebGL under the hood, and the former API asynchronous. -### Adding the View to the DOM +### Adding the Canvas to the DOM When the PIXI.Application class creates the renderer, it builds a Canvas element that it will render *to*. In order to see what we draw with PixiJS, we need to add this Canvas element to the web page's DOM. Append the following line to your page's script block: ```javascript - document.body.appendChild(app.view); + document.body.appendChild(app.canvas); ``` -This takes the view created by the application (the Canvas element) and adds it to the body of your page. +This takes the canvas created by the application (the Canvas element) and adds it to the body of your page. ### Creating a Sprite So far all we've been doing is prep work. We haven't actually told PixiJS to draw anything. Let's fix that by adding an image to be displayed. -There are a number of ways to draw images in PixiJS, but the simplest is by using a [Sprite](https://pixijs.download/release/docs/PIXI.Sprite.html). We'll get into the details of how the scene graph works in a later guide, but for now all you need to know is that PixiJS renders a hierarchy of [DisplayObjects](https://pixijs.download/release/docs/PIXI.DisplayObject.html). A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth. +There are a number of ways to draw images in PixiJS, but the simplest is by using a [Sprite](https://pixijs.download/release/docs/scene.Sprite.html). We'll get into the details of how the scene graph works in a later guide, but for now all you need to know is that PixiJS renders a hierarchy of [Containers](https://pixijs.download/release/docs/scene.Container.html). A Sprite is a type of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth. Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. We'll talk a lot more about resource loading in later guides. For now, we can use a helper method on the PIXI.Sprite class to handle the image loading for us: @@ -114,7 +115,7 @@ Before PixiJS can render an image, it needs to be loaded. Just like in any web ### Adding the Sprite to the Stage -Finally, we need to add our new sprite to the stage. The stage is simply a [Container](https://pixijs.download/release/docs/PIXI.Container.html) that is the root of the scene graph. Every child of the stage container will be rendered every frame. By adding our sprite to the stage, we tell PixiJS's renderer we want to draw it. +Finally, we need to add our new sprite to the stage. The stage is simply a [Container](https://pixijs.download/release/docs/scene.Container.html) that is the root of the scene graph. Every child of the stage container will be rendered every frame. By adding our sprite to the stage, we tell PixiJS's renderer we want to draw it. ```javascript app.stage.addChild(sprite); @@ -129,9 +130,9 @@ While you _can_ use PixiJS for static content, for most projects you'll want to let elapsed = 0.0; // Tell our application's ticker to run a new callback every frame, passing // in the amount of time that has passed since the last tick - app.ticker.add((delta) => { + app.ticker.add((ticker) => { // Add the time to our total elapsed time - elapsed += delta; + elapsed += ticker.deltaTime; // Update the sprite's X position based on the cosine of our elapsed time. We divide // by 50 to slow the animation down a bit... sprite.x = 100.0 + Math.cos(elapsed/50.0) * 100.0; @@ -155,8 +156,9 @@ Here's the whole thing in one place. Check your file and make sure it matches i diff --git a/docs/guides/basics/render-loop.md b/docs/guides/basics/render-loop.md index 938d4d0b1..b17b96710 100644 --- a/docs/guides/basics/render-loop.md +++ b/docs/guides/basics/render-loop.md @@ -28,7 +28,7 @@ A note about frame rates. The render loop can't be run infinitely fast - drawin -In cases where you want to adjust that behavior, you can set the `minFPS` and `maxFPS` attributes on a Ticker to give PixiJS hints as to the range of tick speeds you want to support. Just be aware that due to the complex environment, your project cannot _guarantee_ a given FPS. Use the passed `delta` value in your ticker callbacks to scale any animations to ensure smooth playback. +In cases where you want to adjust that behavior, you can set the `minFPS` and `maxFPS` attributes on a Ticker to give PixiJS hints as to the range of tick speeds you want to support. Just be aware that due to the complex environment, your project cannot _guarantee_ a given FPS. Use the passed `ticker.deltaTime` value in your ticker callbacks to scale any animations to ensure smooth playback. ## Custom Render Loops diff --git a/docs/guides/basics/scene-graph.md b/docs/guides/basics/scene-graph.md index 9e4370353..e0c237f11 100644 --- a/docs/guides/basics/scene-graph.md +++ b/docs/guides/basics/scene-graph.md @@ -4,7 +4,7 @@ Every frame, PixiJS is updating and then rendering the scene graph. Let's talk ## The Scene Graph Is a Tree -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. Most PixiJS objects 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. +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. (A helpful tool for exploring your project is the [Pixi.js devtools plugin](https://chrome.google.com/webstore/detail/pixijs-devtools/aamddddknhcagpehecnhphigffljadon) for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!) @@ -18,26 +18,34 @@ Each frame, PixiJS runs through the scene graph from the root down through all t 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: -```javascript +```ts // Create the application helper and add its render target to the page -const app = new PIXI.Application({ width: 640, height: 360 }); -document.body.appendChild(app.view); +const app = new Application(); +await app.init({ width: 640, height: 360 }) +document.body.appendChild(app.canvas); // Add a container to center our sprite stack on the page -const container = new PIXI.Container(); -container.x = app.screen.width / 2; -container.y = app.screen.height / 2; +const container = new Container({ + x:app.screen.width / 2, + y:app.screen.height / 2; +}); + app.stage.addChild(container); +// load the texture +await Assets.load('assets/images/sample.png'); + // Create the 3 sprites, each a child of the last const sprites = []; let parent = container; for (let i = 0; i < 3; i++) { - let sprite = PIXI.Sprite.from('assets/images/sample.png'); + let wrapper = new Container(); + let sprite = Sprite.from('assets/images/sample.png'); sprite.anchor.set(0.5); - parent.addChild(sprite); - sprites.push(sprite); - parent = sprite; + wrapper.addChild(sprite); + parent.addChild(wrapper); + sprites.push(wrapper); + parent = wrapper; } // Set all sprite's properties to the same value, animated over time @@ -71,27 +79,36 @@ Check out this example, with two parent objects A & D, and two children B & C un ```javascript // Create the application helper and add its render target to the page -const app = new PIXI.Application({ width: 640, height: 360 }); -document.body.appendChild(app.view); +const app = new Application(); +await app.init({ width: 640, height: 360 }) +document.body.appendChild(app.canvas); // Label showing scene graph hierarchy -const label = new PIXI.Text('Scene Graph:\n\napp.stage\n ┗ A\n ┗ B\n ┗ C\n ┗ D', {fill: '#ffffff'}); -label.position = {x: 300, y: 100}; +const label = new Text({ + text:'Scene Graph:\n\napp.stage\n ┗ A\n ┗ B\n ┗ C\n ┗ D', + style:{fill: '#ffffff'}, + position: {x: 300, y: 100} +}); + app.stage.addChild(label); // Helper function to create a block of color with a letter const letters = []; function addLetter(letter, parent, color, pos) { - const bg = new PIXI.Sprite(PIXI.Texture.WHITE); + const bg = new Sprite(Texture.WHITE); bg.width = 100; bg.height = 100; bg.tint = color; - const text = new PIXI.Text(letter, {fill: "#ffffff"}); + const text = new Text({ + text:letter, + style:{fill: "#ffffff"} + }); + text.anchor.set(0.5); text.position = {x: 50, y: 50}; - const container = new PIXI.Container(); + const container = new Container(); container.position = pos; container.visible = false; container.addChild(bg, text); @@ -120,9 +137,13 @@ app.ticker.add((delta) => { 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 + +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](../advanced/render-groups) + ## Culling -If you're building a project where a large proportion of your DisplayObject's 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. +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 doesn't provide built-in support for viewport culling, but you can find 3rd party plugins that might fit your needs. Alternately, if you'd like to build your own culling system, simply run your objects during each tick and set `renderable` to false on any object that doesn't need to be drawn. @@ -138,15 +159,15 @@ To convert from local to global coordinates, you use the `toGlobal()` function. ```javascript // Get the global position of an object, relative to the top-left of the screen -let globalPos = obj.toGlobal(new PIXI.Point(0,0)); +let globalPos = obj.toGlobal(new Point(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 -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. +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. -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 PIXI.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. +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. diff --git a/docs/guides/basics/what-pixijs-is.md b/docs/guides/basics/what-pixijs-is.md index a11c20d51..6bf9305f7 100644 --- a/docs/guides/basics/what-pixijs-is.md +++ b/docs/guides/basics/what-pixijs-is.md @@ -8,15 +8,15 @@ Here's what else you get with PixiJS: ## PixiJS Is ... Fast -One of the major features that distinguishes PixiJS from other web-based rendering solutions is *speed*. From the ground up, the render pipeline has been built to get the most performance possible out of your users' browsers. Automatic sprite and geometry batching, careful use of WebGL resources, a tight scene graph - no matter your application, speed is valuable, and PixiJS has it to spare. +One of the major features that distinguishes PixiJS from other web-based rendering solutions is *speed*. From the ground up, the render pipeline has been built to get the most performance possible out of your users' browsers. Automatic sprite and geometry batching, careful use of GPU resources, a tight scene graph - no matter your application, speed is valuable, and PixiJS has it to spare. ## ... More Than Just Sprites -Drawing images on a page can be handled with HTML5 and the DOM, so why use PixiJS? Beyond performance, the answer is that PixiJS goes well beyond simple images. Draw trails and tracks with [SimpleRope](https://pixijs.download/release/docs/PIXI.SimpleRope.html). Draw polygons, lines, circles and other primitives with [Graphics](https://pixijs.download/release/docs/PIXI.Graphics.html). [Text](https://pixijs.download/release/docs/PIXI.Text.html) provides full text rendering support that's just as performant as sprites. And even when drawing simple images, PixiJS natively supports spritesheets for efficient loading and ease of development. +Drawing images on a page can be handled with HTML5 and the DOM, so why use PixiJS? Beyond performance, the answer is that PixiJS goes well beyond simple images. Draw trails and tracks with [MeshRope](https://pixijs.download/release/docs/scene.MeshRope.html). Draw polygons, lines, circles and other primitives with [Graphics](https://pixijs.download/release/docs/scene.Graphics.html). [Text](https://pixijs.download/release/docs/scene.Text.html) provides full text rendering support that's just as performant as sprites. And even when drawing simple images, PixiJS natively supports spritesheets for efficient loading and ease of development. -## ... WebGL Native +## ... Hardware accelerated -WebGL is the JavaScript API for accessing users' GPUs for fast rendering and advanced effects. PixiJS leverages WebGL to display thousands of moving sprites efficiently even on mobile devices. But using WebGL offers more than just speed. By using the [Filter](https://pixijs.download/release/docs/PIXI.Filter.html) class, you can write shader programs (or use pre-built ones!) to achieve displacement maps, blurring, and other advanced visual effects that cannot be accomplished with just the DOM or Canvas APIs. +JavaScript has two APIs for handling hardware acceleration for graphical rendering: WebGL and the more modern WebGPU. Both essentially offer a JavaScript API for accessing users' GPUs for fast rendering and advanced effects. PixiJS leverages them to efficiently display thousands of moving sprites, even on mobile devices. However, using WebGL and WebGPU offers more than just speed. By using the [Filter](https://pixijs.download/release/docs/filters.Filter.html) class, you can write shader programs (or use pre-built ones!) to achieve displacement maps, blurring, and other advanced visual effects that cannot be accomplished with just the DOM or Canvas APIs. ## ... Open Source diff --git a/docs/guides/components/assets.md b/docs/guides/components/assets.md index ecd24388d..625c9507c 100644 --- a/docs/guides/components/assets.md +++ b/docs/guides/components/assets.md @@ -1,27 +1,55 @@ # Assets ## The Assets package -The Assets package is a modern replacement for the old `PIXI.Loader` class. It is a promise-based resource management solution that will download, cache and parse your assets into something you can use. The downloads can be simultaneous and in the background, meaning faster startup times for your app, the cache ensures that you never download the same asset twice and the extensible parser system allows you to easily extend and customize the process to your needs. +The Assets package is a modern replacement for the old `Loader` class. It is a promise-based resource management solution that will download, cache and parse your assets into something you can use. The downloads can be simultaneous and in the background, meaning faster startup times for your app, the cache ensures that you never download the same asset twice and the extensible parser system allows you to easily extend and customize the process to your needs. ## Getting started -The `@pixi/assets` package doesn't come bundled with PixiJS in version 6.x and must be added externally, however it will become integrated with version 7. The class that does all the heavy lifting is called `AssetsClass` but you don't need to create your own instance since you will find one ready to use in `PIXI.Assets`. -This package relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser [doesn't support promises](https://caniuse.com/promises) you should look into [polyfilling them](https://github.com/zloirock/core-js#ecmascript-promise). +`Assets` relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser [doesn't support promises](https://caniuse.com/promises) you should look into [polyfilling them](https://github.com/zloirock/core-js#ecmascript-promise). ## Making our first Assets Promise -To quickly use the `PIXI.Assets` instance, you just need to call `PIXI.Assets.load` and pass in an asset. This will return a promise that when resolved will yield the value you seek. +To quickly use the `Assets` instance, you just need to call `Assets.load` and pass in an asset. This will return a promise that when resolved will yield the value you seek. In this example, we will load a texture and then turn it into a sprite. -
+```ts +import { Application, Assets, Sprite } from 'pixi.js'; + +// Create a new application +const app = new Application(); + +// Initialize the application +await app.init({ background: '#1099bb', resizeTo: window }); + +// Append the application canvas to the document body +document.body.appendChild(app.canvas); + +// Start loading right away and create a promise +const texturePromise = Assets.load('https://pixijs.com/assets/bunny.png'); + +// When the promise resolves, we have the texture! +texturePromise.then((resolvedTexture) => +{ + // create a new Sprite from the resolved loaded Texture + const bunny = Sprite.from(resolvedTexture); + + // center the sprite's anchor point + bunny.anchor.set(0.5); + + // move the sprite to the center of the screen + bunny.x = app.screen.width / 2; + bunny.y = app.screen.height / 2; + + app.stage.addChild(bunny); +}); +``` One very important thing to keep in mind while using `Assets` is that all requests are cached and if the URL is the same, the promise returned will also be the same. To show it in code: ```js -promise1 = PIXI.Assets.load('bunny.png') -promise2 = PIXI.Assets.load('bunny.png') - -//promise1 === promise2 +promise1 = Assets.load('bunny.png') +promise2 = Assets.load('bunny.png') +// promise1 === promise2 ``` Out of the box, the following assets types can be loaded without the need for external plugins: @@ -71,37 +99,76 @@ This function now wraps the return value in a promise and allows us to use the ` See this example: -
+```ts +// Create a new application +const app = new Application(); +// Initialize the application +await app.init({ background: '#1099bb', resizeTo: window }); +// Append the application canvas to the document body +document.body.appendChild(app.canvas); +const texture = await Assets.load('https://pixijs.com/assets/bunny.png'); +// Create a new Sprite from the awaited loaded Texture +const bunny = Sprite.from(texture); +// Center the sprite's anchor point +bunny.anchor.set(0.5); +// Move the sprite to the center of the screen +bunny.x = app.screen.width / 2; +bunny.y = app.screen.height / 2; +app.stage.addChild(bunny); +``` The `texture` variable now is not a promise but the resolved texture that resulted after waiting for this promise to resolve. ```js -const texture = await PIXI.Assets.load('examples/assets/bunny.png'); +const texture = await Assets.load('examples/assets/bunny.png'); ``` This allows us to write more readable code without falling into callback hell and to better think when our program halts and yields. ## Loading multiple assets -We can add assets to the cache and then load them all simultaneously by using `PIXI.Assets.add(...)` and then calling `PIXI.Assets.load(...)` with all the keys you want to have loaded. +We can add assets to the cache and then load them all simultaneously by using `Assets.add(...)` and then calling `Assets.load(...)` with all the keys you want to have loaded. See the following example: -
+```ts +// Append the application canvas to the document body +document.body.appendChild(app.canvas); +// Add the assets to load +Assets.add({ alias: 'flowerTop', src: 'https://pixijs.com/assets/flowerTop.png' }); +Assets.add({ alias: 'eggHead', src: 'https://pixijs.com/assets/eggHead.png' }); +// Load the assets and get a resolved promise once both are loaded +const texturesPromise = Assets.load(['flowerTop', 'eggHead']); // => Promise<{flowerTop: Texture, eggHead: Texture}> +// When the promise resolves, we have the texture! +texturesPromise.then((textures) => +{ + // Create a new Sprite from the resolved loaded Textures + const flower = Sprite.from(textures.flowerTop); + flower.anchor.set(0.5); + flower.x = app.screen.width * 0.25; + flower.y = app.screen.height / 2; + app.stage.addChild(flower); + const egg = Sprite.from(textures.eggHead); + egg.anchor.set(0.5); + egg.x = app.screen.width * 0.75; + egg.y = app.screen.height / 2; + app.stage.addChild(egg); +}); +``` However, if you want to take full advantage of `@pixi/Assets` you should use bundles. -Bundles are just a way to group assets together and can be added manually by calling `PIXI.Assets.addBundle(...)`/`PIXI.Assets.loadBundle(...)`. +Bundles are just a way to group assets together and can be added manually by calling `Assets.addBundle(...)`/`Assets.loadBundle(...)`. ```js - PIXI.Assets.addBundle('animals', { + Assets.addBundle('animals', { bunny: 'bunny.png', chicken: 'chicken.png', thumper: 'thumper.png', }); - const assets = await PIXI.Assets.loadBundle('animals'); + const assets = await Assets.loadBundle('animals'); ``` -However, the best way to handle bundles is to use a manifest and call `PIXI.Assets.init({manifest})` with said manifest (or even better, an URL pointing to it). +However, the best way to handle bundles is to use a manifest and call `Assets.init({manifest})` with said manifest (or even better, an URL pointing to it). Splitting our assets into bundles that correspond to screens or stages of our app will come in handy for loading in the background while the user is using the app instead of locking them in a single monolithic loading screen. ```json @@ -111,12 +178,12 @@ Splitting our assets into bundles that correspond to screens or stages of our ap "name":"load-screen", "assets":[ { - "name":"background", - "srcs":"sunset.png" + "alias":"background", + "src":"sunset.png" }, { - "name":"bar", - "srcs":"load-bar.{png,webp}" + "alias":"bar", + "src":"load-bar.{png,webp}" } ] }, @@ -124,12 +191,12 @@ Splitting our assets into bundles that correspond to screens or stages of our ap "name":"game-screen", "assets":[ { - "name":"character", - "srcs":"robot.png" + "alias":"character", + "src":"robot.png" }, { - "name":"enemy", - "srcs":"bad-guy.png" + "alias":"enemy", + "src":"bad-guy.png" } ] } @@ -137,7 +204,7 @@ Splitting our assets into bundles that correspond to screens or stages of our ap } ``` ```js -PIXI.Assets.init({manifest: "path/manifest.json"}); +Assets.init({manifest: "path/manifest.json"}); ``` Beware that **you can only call `init` once**. @@ -146,16 +213,61 @@ Remember there is no downside in repeating URLs since they will all be cached, s ## Background loading -The old approach to loading was to use `PIXI.Loader` to load all your assets at the beginning of your app, but users are less patient now and want content to be instantly available so the practices are moving towards loading the bare minimum needed to show the user some content and, while they are interacting with that, we keep loading the following content in the background. +The old approach to loading was to use `Loader` to load all your assets at the beginning of your app, but users are less patient now and want content to be instantly available so the practices are moving towards loading the bare minimum needed to show the user some content and, while they are interacting with that, we keep loading the following content in the background. -Luckily, `@pixi/assets` has us covered with a system that allows us to load everything in the background and in case we need some assets right now, bump them to the top of the queue so we can minimize loading times. +Luckily, `Assets` has us covered with a system that allows us to load everything in the background and in case we need some assets right now, bump them to the top of the queue so we can minimize loading times. -To achieve this, we have the methods `PIXI.Assets.backgroundLoad(...)` and `PIXI.Assets.backgroundLoadBundle(...)` that will passively begin to load these assets in the background. So when you finally come to loading them you will get a promise that resolves to the loaded assets immediately. +To achieve this, we have the methods `Assets.backgroundLoad(...)` and `Assets.backgroundLoadBundle(...)` that will passively begin to load these assets in the background. So when you finally come to loading them you will get a promise that resolves to the loaded assets immediately. -When you finally need the assets to show, you call the usual `PIXI.Assets.load(...)` or `PIXI.Assets.loadBundle(...)` and you will get the corresponding promise. +When you finally need the assets to show, you call the usual `Assets.load(...)` or `Assets.loadBundle(...)` and you will get the corresponding promise. The best way to do this is using bundles, see the following example: -
+```ts +import { Application, Assets, Sprite } from 'pixi.js'; + +// Create a new application +const app = new Application(); + +async function init() +{ + // Initialize the application + await app.init({ background: '#1099bb', resizeTo: window }); + + // Append the application canvas to the document body + document.body.appendChild(app.canvas); + + // Manifest example + const manifestExample = { + bundles: [ + { + name: 'load-screen', + assets: [ + { + alias: 'flowerTop', + src: 'https://pixijs.com/assets/flowerTop.png', + }, + ], + }, + { + name: 'game-screen', + assets: [ + { + alias: 'eggHead', + src: 'https://pixijs.com/assets/eggHead.png', + }, + ], + }, + ], + }; + + await Assets.init({ manifest: manifestExample }); + + // Bundles can be loaded in the background too! + Assets.backgroundLoadBundle(['load-screen', 'game-screen']); +} + +init(); +``` We create one bundle for each screen our game will have and set them all to start downloading at the beginning of our app. If the user progresses slowly enough in our app then they should never get to see a loading screen after the first one! diff --git a/docs/guides/components/containers.md b/docs/guides/components/containers.md index 9839140c0..d7a99713d 100644 --- a/docs/guides/components/containers.md +++ b/docs/guides/components/containers.md @@ -1,10 +1,26 @@ # Containers -The [Container](https://pixijs.download/release/docs/PIXI.Container.html) class provides a simple display object that does what its name implies - collect a set of child objects together. But beyond grouping objects, containers have a few uses that you should be aware of. +The [Container](https://pixijs.download/release/docs/scene.Container.html) class provides a simple display object that does what its name implies - collect a set of child objects together. But beyond grouping objects, containers have a few uses that you should be aware of. + +## Commonly Used Attributes + +The most common attributes you'll use when laying out and animating content in PixiJS are provided by the Container class: + +| Property | Description | +| --- | --- | +| **position** | X- and Y-position are given in pixels and change the position of the object relative to its parent, also available directly as `object.x` / `object.y` | +| **rotation** | Rotation is specified in radians, and turns an object clockwise (0.0 - 2 * Math.PI) | +| **angle** | Angle is an alias for rotation that is specified in degrees instead of radians (0.0 - 360.0) | +| **pivot** | Point the object rotates around, in pixels - also sets origin for child objects | +| **alpha** | Opacity from 0.0 (fully transparent) to 1.0 (fully opaque), inherited by children | +| **scale** | Scale is specified as a percent with 1.0 being 100% or actual-size, and can be set independently for the x and y axis | +| **skew** | Skew transforms the object in x and y similar to the CSS skew() function, and is specified in radians | +| **visible** | Whether the object is visible or not, as a boolean value - prevents updating and rendering object and children | +| **renderable** | Whether the object should be rendered - when `false`, object will still be updated, but won't be rendered, doesn't affect children | ## Containers as Groups -Almost every type of display object is also derived from Container - even Sprites! This means that in many cases you can create a parent-child hierarchy with the objects you want to render. +Almost every type of display object is also derived from Container! This means that in many cases you can create a parent-child hierarchy with the objects you want to render. However, it's a good idea _not_ to do this. Standalone Container objects are **very** cheap to render, and having a proper hierarchy of Container objects, each containing one or more renderable objects, provides flexibility in rendering order. It also future-proofs your code, as when you need to add an additional object to a branch of the tree, your animation logic doesn't need to change - just drop the new object into the proper Container, and your logic moves the Container with no changes to your code. @@ -18,28 +34,30 @@ Another common use for Container objects is as hosts for masked content. "Maski Think of a pop-up window. It has a frame made of one or more Sprites, then has a scrollable content area that hides content outside the frame. A Container plus a mask makes that scrollable area easy to implement. Add the Container, set its `mask` property to a Graphics object with a rect, and add the text, image, etc. content you want to display as children of that masked Container. Any content that extends beyond the rectangular mask will simply not be drawn. Move the contents of the Container to scroll as desired. -```javascript +```ts // Create the application helper and add its render target to the page -let app = new PIXI.Application({ width: 640, height: 360 }); +let app = new Application({ width: 640, height: 360 }); document.body.appendChild(app.view); // Create window frame -let frame = new PIXI.Graphics(); -frame.beginFill(0x666666); -frame.lineStyle({ color: 0xffffff, width: 4, alignment: 0 }); -frame.drawRect(0, 0, 208, 208); -frame.position.set(320 - 104, 180 - 104); +let frame = new Graphics({ + x:320 - 104, + y:180 - 104 +}) +.rect(0, 0, 208, 208) +.fill(0x666666) +.stroke({ color: 0xffffff, width: 4, alignment: 0 }) + app.stage.addChild(frame); // Create a graphics object to define our mask -let mask = new PIXI.Graphics(); +let mask = new Graphics() // Add the rectangular area to show -mask.beginFill(0xffffff); -mask.drawRect(0,0,200,200); -mask.endFill(); + .rect(0,0,200,200) + .fill(0xffffff); // Add container that will hold our masked content -let maskContainer = new PIXI.Container(); +let maskContainer = new Container(); // Set the mask to use our graphics object from above maskContainer.mask = mask; // Add the mask as a child, so that the mask is positioned relative to its parent @@ -50,22 +68,23 @@ maskContainer.position.set(4,4); frame.addChild(maskContainer); // Create contents for the masked container -let text = new PIXI.Text( - 'This text will scroll up and be masked, so you can see how masking works. Lorem ipsum and all that.\n\n' + +let text = new Text({ + text:'This text will scroll up and be masked, so you can see how masking works. Lorem ipsum and all that.\n\n' + 'You can put anything in the container and it will be masked!', - { + style{ fontSize: 24, fill: 0x1010ff, wordWrap: true, wordWrapWidth: 180 - } -); -text.x = 10; + }, + x:10 +}); + maskContainer.addChild(text); // Add a ticker callback to scroll the text up and down let elapsed = 0.0; -app.ticker.add((delta) => { +app.ticker.add(({delta}) => { // Update the text's y coordinate to scroll it elapsed += delta; text.y = 10 + -100.0 + Math.cos(elapsed/50.0) * 100.0; @@ -74,23 +93,24 @@ app.ticker.add((delta) => { There are two types of masks supported by PixiJS: -Use a [Graphics](https://pixijs.download/release/docs/PIXI.Graphics.html) object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing +Use a [Graphics](https://pixijs.download/release/docs/scene.Graphics.html) object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing -Sprite: Use the alpha channel from a [Sprite](https://pixijs.download/release/docs/PIXI.Sprite.html) as your mask, providing anti-aliased edging - _not_ supported on the Canvas renderer +Sprite: Use the alpha channel from a [Sprite](https://pixijs.download/release/docs/scene.Sprite.html) as your mask, providing anti-aliased edging - _not_ supported on the Canvas renderer ## Filtering -Another common use for Container objects is as hosts for filtered content. Filters are an advanced, WebGL-only feature that allows PixiJS to perform per-pixel effects like blurring and displacements. By setting a filter on a Container, the area of the screen the Container encompasses will be processed by the filter after the Container's contents have been rendered. +Another common use for Container objects is as hosts for filtered content. Filters are an advanced, WebGL/WebGPU-only feature that allows PixiJS to perform per-pixel effects like blurring and displacements. By setting a filter on a Container, the area of the screen the Container encompasses will be processed by the filter after the Container's contents have been rendered. Below are list of filters available by default in PixiJS. There is, however, a community repository with [many more filters](https://github.com/pixijs/filters). | Filter | Description | | --- | --- | -| AlphaFilter: `@pixi/filter-alpha` | Similar to setting `alpha` property, but flattens the Container instead of applying to children individually. | -| BlurFilter: `@pixi/filter-blur` | Apply a blur effect | -| ColorMatrixFilter: `@pixi/filter-color-matrix` | A color matrix is a flexible way to apply more complex tints or color transforms (e.g., sepia tone). | -| DisplacementFilter: `@pixi/filter-displacement` | Displacement maps create visual offset pixels, for instance creating a wavy water effect. | -| FXAAFilter: `@pixi/filter-fxaa` | Basic FXAA (Fast Approximate Anti-Aliasing) to create smoothing effect. | -| NoiseFilter: `@pixi/filter-noise` | Create random noise (e.g., grain effect). | +| AlphaFilter | Similar to setting `alpha` property, but flattens the Container instead of applying to children individually. | +| BlurFilter | Apply a blur effect | +| ColorMatrixFilter | A color matrix is a flexible way to apply more complex tints or color transforms (e.g., sepia tone). | +| DisplacementFilter | Displacement maps create visual offset pixels, for instance creating a wavy water effect. | +| NoiseFilter | Create random noise (e.g., grain effect). | + +Under the hood, each Filter we offer out of the box is written in both glsl (for WebGL) and wgsl (for WebGPU). This means all filters should work on both renderers. _**Important:** Filters should be use somewhat sparingly. They can slow performance and increase memory if used too often in a scene._ diff --git a/docs/guides/components/display-object.md b/docs/guides/components/display-object.md deleted file mode 100644 index dd2b90d7b..000000000 --- a/docs/guides/components/display-object.md +++ /dev/null @@ -1,21 +0,0 @@ -# Display Objects - -[DisplayObject](https://pixijs.download/release/docs/PIXI.DisplayObject.html) is the core class for anything that can be rendered by the engine. It's the base class for sprites, text, complex graphics, containers, etc., and provides much of the common functionality for those objects. As you're learning PixiJS, it's important to [read through the documentation for this class](https://pixijs.download/release/docs/PIXI.DisplayObject.html) to understand how to move, scale, rotate and compose the visual elements of your project. - -Be aware that you won't use DisplayObject directly - you'll use its functions and attributes in derived classes. - -## Commonly Used Attributes - -The most common attributes you'll use when laying out and animating content in PixiJS are provided by the DisplayObject class: - -| Property | Description | -| --- | --- | -| **position** | X- and Y-position are given in pixels and change the position of the object relative to its parent, also available directly as `object.x` / `object.y` | -| **rotation** | Rotation is specified in radians, and turns an object clockwise (0.0 - 2 * Math.PI) | -| **angle** | Angle is an alias for rotation that is specified in degrees instead of radians (0.0 - 360.0) | -| **pivot** | Point the object rotates around, in pixels - also sets origin for child objects | -| **alpha** | Opacity from 0.0 (fully transparent) to 1.0 (fully opaque), inherited by children | -| **scale** | Scale is specified as a percent with 1.0 being 100% or actual-size, and can be set independently for the x and y axis | -| **skew** | Skew transforms the object in x and y similar to the CSS skew() function, and is specified in radians | -| **visible** | Whether the object is visible or not, as a boolean value - prevents updating and rendering object and children | -| **renderable** | Whether the object should be rendered - when `false`, object will still be updated, but won't be rendered, doesn't affect children | diff --git a/docs/guides/components/graphics.md b/docs/guides/components/graphics.md index 236e11946..857d823ac 100644 --- a/docs/guides/components/graphics.md +++ b/docs/guides/components/graphics.md @@ -1,38 +1,38 @@ # Graphics -[Graphics](https://pixijs.download/release/docs/PIXI.Graphics.html) is a complex and much misunderstood tool in the PixiJS toolbox. At first glance, it looks like a tool for drawing shapes. And it is! But it can also be used to generate masks. How does that work? +[Graphics](https://pixijs.download/release/docs/scene.Graphics.html) is a complex and much misunderstood tool in the PixiJS toolbox. At first glance, it looks like a tool for drawing shapes. And it is! But it can also be used to generate masks. How does that work? -In this guide, we're going to de-mystify the Graphics object, starting with how to think about what it does. +In this guide, we're going to de-mystify the `Graphics` object, starting with how to think about what it does. Check out the [graphics example code](../../examples/graphics/simple). ## Graphics Is About Building - Not Drawing -First-time users of the PIXI.Graphics class often struggle with how it works. Let's look at an example snippet that creates a Graphics object and draws a rectangle: +First-time users of the `Graphics` class often struggle with how it works. Let's look at an example snippet that creates a `Graphics` object and draws a rectangle: ```javascript -// Create a Graphics object, set a fill color, draw a rectangle -let obj = new PIXI.Graphics(); -obj.beginFill(0xff0000); -obj.drawRect(0, 0, 200, 100); +// Create a Graphics object, draw a rectangle and fill it +let obj = new Graphics() + .rect(0, 0, 200, 100) + .fill(0xff0000); // Add it to the stage to render app.stage.addChild(obj); ``` -That code will work - you'll end up with a red rectangle on the screen. But it's pretty confusing when you start to think about it. Why am I drawing a rectangle when *constructing* the object? Isn't drawing something a one-time action? How does the rectangle get drawn the *second* frame? And it gets even weirder when you create a Graphics object with a bunch of drawThis and drawThat calls, and then you use it as a *mask*. What??? +That code will work - you'll end up with a red rectangle on the screen. But it's pretty confusing when you start to think about it. Why am I drawing a rectangle when *constructing* the object? Isn't drawing something a one-time action? How does the rectangle get drawn the *second* frame? And it gets even weirder when you create a `Graphics` object with a bunch of drawThis and drawThat calls, and then you use it as a *mask*. What??? -The problem is that the function names are centered around *drawing*, which is an action that puts pixels on the screen. But in spite of that, the Graphics object is really about *building*. +The problem is that the function names are centered around *drawing*, which is an action that puts pixels on the screen. But in spite of that, the `Graphics` object is really about *building*. -Let's look a bit deeper at that `drawRect()` call. When you call `drawRect()`, PixiJS doesn't actually draw anything. Instead, it stores the rectangle you "drew" into a list of geometry for later use. If you then add the Graphics object to the scene, the renderer will come along, and ask the Graphics object to render itself. At that point, your rectangle actually gets drawn - along with any other shapes, lines, etc. that you've added to the geometry list. +Let's look a bit deeper at that `rect()` call. When you call `rect()`, PixiJS doesn't actually draw anything. Instead, it stores the rectangle you "drew" into a list of geometry for later use. If you then add the `Graphics` object to the scene, the renderer will come along, and ask the `Graphics` object to render itself. At that point, your rectangle actually gets drawn - along with any other shapes, lines, etc. that you've added to the geometry list. -Once you understand what's going on, things start to make a lot more sense. When you use a Graphics object as a mask, for example, the masking system uses that list of graphics primitives in the geometry list to constrain which pixels make it to the screen. There's no drawing involved. +Once you understand what's going on, things start to make a lot more sense. When you use a `Graphics` object as a mask, for example, the masking system uses that list of graphics primitives in the geometry list to constrain which pixels make it to the screen. There's no drawing involved. -That's why it helps to think of the Graphics class not as a drawing tool, but as a geometry building tool. +That's why it helps to think of the `Graphics` class not as a drawing tool, but as a geometry building tool. ## Types of Primitives -There are a lot of functions in the PIXI.Graphics class, but as a quick orientation, here's the list of basic primitives you can add: +There are a lot of functions in the `Graphics` class, but as a quick orientation, here's the list of basic primitives you can add: * Line * Rect @@ -42,7 +42,7 @@ There are a lot of functions in the PIXI.Graphics class, but as a quick orientat * Arc * Bezier and Quadratic Curve -In addition, the Graphics Extras package (`@pixi/graphics-extras`) optionally includes the following complex primitives: +In addition, you have access to the following complex primitives: * Torus * Chamfer Rect @@ -51,53 +51,111 @@ In addition, the Graphics Extras package (`@pixi/graphics-extras`) optionally in * Star * Rounded Polygon -## The Geometry List +There is also support for svg. But due to the nature of how PixiJS renders holes (it favours performance) Some complex hole shapes may render incorrectly. But for the majority of shapes, this will do the trick! -Inside every Graphics object is a GraphicsGeometry object. The [GraphicsGeometry](https://pixijs.download/release/docs/PIXI.GraphicsGeometry.html) class manages the list of geometry primitives created by the Graphics parent object. For the most part, you will not work directly with this object. The owning Graphics object creates and manages it. However, there are two related cases where you *do* work with the list. + ```ts + let mySvg = new Graphics() + .svg('M 100 350 q 150 -300 300 0'); +``` -First, you can re-use geometry from one Graphics object in another. No matter whether you're re-drawing the same shape over and over, or re-using it as a mask over and over, it's more efficient to share identical GraphicsGeometry. You can do this like so: +## The GraphicsContext -```javascript -// Create a master graphics object -let template = new PIXI.Graphics(); -// Add a circle -template.drawCircle(100, 100, 50); +Understanding the relationship between Sprites and their shared Texture can help grasp the concept of a `GraphicsContext`. Just as multiple Sprites can utilize a single Texture, saving memory by not duplicating pixel data, a GraphicsContext can be shared across multiple Graphics objects. + +This sharing of a `GraphicsContext` means that the intensive task of converting graphics instructions into GPU-ready geometry is done once, and the results are reused, much like textures. Consider the difference in efficiency between these approaches: + +Creating individual circles without sharing a context: +```ts +// Create 5 circles +for (let i = 0; i < 5; i++) { + let circle = new Graphics() + .circle(100, 100, 50) + .fill('red'); +} +``` +Versus sharing a GraphicsContext: +```ts +// Create a master Graphicscontext +let circleContext = new GraphicsContext() + .circle(100, 100, 50) + .fill('red') // Create 5 duplicate objects for (let i = 0; i < 5; i++) { - // Initialize the duplicate using our template's pre-built geometry - let duplicate = new PIXI.Graphics(template.geometry); + // Initialize the duplicate using our circleContext + let duplicate = new Graphics(circleContext); } ``` -This leads to the second time you need to be aware of the underlying GraphicsGeometry object - avoiding memory leaks. Because Graphics objects can share geometry, you *must* call `destroy()` when you no longer need them. Failure to do so will prevent the GraphicsGeometry object it owns from being properly de-referenced, and will lead to memory leaks. +Now, this might not be a huge deal for circles and squares, but when you are using SVGs, it becomes quite important to not have to rebuild each time and instead share a `GraphicsContext`. It's recommended for maximum performance to create your contexts upfront and reuse them, just like textures! -## Graphics For Display +```ts +let circleContext = new GraphicsContext() + .circle(100, 100, 50) + .fill('red') + +let rectangleContext = new GraphicsContext() + .rect(0, 0, 50, 50) + .fill('red') + +let frames = [circleContext, rectangleContext]; +let frameIndex = 0; + +const graphics = new Graphics(frames[frameIndex]); -OK, so now that we've covered how the PIXI.Graphics class works, let's look at how you use it. The most obvious use of a Graphics object is to draw dynamically generated shapes to the screen. +// animate from square to circle: + +function update() +{ + // swap the context - this is a very cheap operation! + // much cheaper than clearing it each frame. + graphics.context = frames[frameIndex++%frames.length]; +} +``` + +If you don't explicitly pass a `GraphicsContext` when creating a `Graphics` object, then internally, it will have its own context, accessible via `myGraphics.context`. The [GraphicsContext](https://pixijs.download/release/docs/scene.GraphicsContext.html) class manages the list of geometry primitives created by the Graphics parent object. Graphics functions are literally passed through to the internal contexts: + +```ts +let circleGraphics = new Graphics() + .circle(100, 100, 50) + .fill('red') +``` +same as: +```ts +let circleGraphics = new Graphics() + +circleGraphics.context + .circle(100, 100, 50) + .fill('red') +``` + +Calling `Graphics.destroy()` will destroy the graphics. If a context was passed to it via the constructor then it will leave the destruction the that context to you. However if the context is internally created (the default), when destroyed the Graphics object will destroy its internal `GraphicsContext`. + +## Graphics For Display -Doing so is simple. Create the object, call the various builder functions to add your custom primitives, then add the object to the scene graph. Each frame, the renderer will come along, ask the Graphics object to render itself, and each primitive, with associated line and fill styles, will be drawn to the screen. +OK, so now that we've covered how the `Graphics` class works, let's look at how you use it. The most obvious use of a `Graphics` object is to draw dynamically generated shapes to the screen. +Doing so is simple. Create the object, call the various builder functions to add your custom primitives, then add the object to the scene graph. Each frame, the renderer will come along, ask the `Graphics` object to render itself, and each primitive, with associated line and fill styles, will be drawn to the screen. ## Graphics as a Mask -You can also use a Graphics object as a complex mask. To do so, build your object and primitives as usual. Next create a PIXI.Container object that will contain the masked content, and set its `mask` property to your Graphics object. The children of the container will now be clipped to only show through inside the geometry you've created. This technique works for both WebGL and Canvas-based rendering. +You can also use a Graphics object as a complex mask. To do so, build your object and primitives as usual. Next create a `Container` object that will contain the masked content, and set its `mask` property to your Graphics object. The children of the container will now be clipped to only show through inside the geometry you've created. This technique works for both WebGL and Canvas-based rendering. Check out the [masking example code](../../examples/graphics/simple). ## Caveats and Gotchas -The Graphics class is a complex beast, and so there are a number of things to be aware of when using it. +The `Graphics` class is a complex beast, and so there are a number of things to be aware of when using it. -**Memory Leaks**: The first has already been mentioned - call `destroy()` on any Graphics object you no longer need to avoid memory leaks. +**Memory Leaks**: Call `destroy()` on any `Graphics` object you no longer need to avoid memory leaks. **Holes**: Holes you create have to be completely contained in the shape or else it may not be able to triangulate correctly. -**Changing Geometry**: If you want to change the shape of a Graphics object, you don't need to delete and recreate it. Instead you can use the `clear()` function to reset the contents of the geometry list, then add new primitives as desired. Be careful of performance when doing this every frame. +**Changing Geometry**: If you want to change the shape of a `Graphics` object, you don't need to delete and recreate it. Instead you can use the `clear()` function to reset the contents of the geometry list, then add new primitives as desired. Be careful of performance when doing this every frame. -**Performance**: Graphics objects are generally quite performant. However, if you build highly complex geometry, you may pass the threshold that permits batching during rendering, which can negatively impact performance. It's better for batching to use many Graphics objects instead of a single Graphics with many shapes. +**Performance**: `Graphics` objects are generally quite performant. However, if you build highly complex geometry, you may pass the threshold that permits batching during rendering, which can negatively impact performance. It's better for batching to use many `Graphics` objects instead of a single `Graphics` with many shapes. -**Transparency**: Because the Graphics object renders its primitives sequentially, be careful when using blend modes or partial transparency with overlapping geometry. Blend modes like `ADD` and `MULTIPLY` will work *on each primitive*, not on the final composite image. Similarly, partially transparent Graphics objects will show primitives overlapping. To apply transparency or blend modes to a single flattened surface, consider using AlphaFilter or RenderTexture. +**Transparency**: Because the `Graphics` object renders its primitives sequentially, be careful when using blend modes or partial transparency with overlapping geometry. Blend modes like `ADD` and `MULTIPLY` will work *on each primitive*, not on the final composite image. Similarly, partially transparent `Graphics` objects will show primitives overlapping. To apply transparency or blend modes to a single flattened surface, consider using AlphaFilter or RenderTexture. -That is why we have both Textures and BaseTextures - to allow sprite sheets, animations, button states, etc to be loaded as a single image, while only displaying the part of the master image that is needed. +That is why we have both Textures and TextureSource - to allow sprite sheets, animations, button states, etc to be loaded as a single image, while only displaying the part of the master image that is needed. ## Loading Textures -We will discuss resource loading in a later guide, but one of the most common issues new users face when building a PixiJS project is how best to load their textures. Using `PIXI.Texture.from()` as we do in our demo snippets will work, but will result in pop-in as each texture is loaded while your objects are already being rendered in the scene graph. +We will discuss resource loading in a later guide, but one of the most common issues new users face when building a PixiJS project is how best to load their textures. -Instead, here's a quick cheat sheet of one good solution: +here's a quick cheat sheet of one good solution: 1. Show a loading image -2. Create a Loader -3. Run all texture-based objects, add their textures to the loader -4. Start the loader, and optionally update your loading image based on progress callbacks -5. On loader completion, run all objects and use `PIXI.Texture.from()` to pull the loaded textures out of the texture cache -6. Prepare your textures (optional - see below) -7. Hide your loading image, start rendering your scene graph +2. Use [Assets](assets.md) to ensure that all textures are loaded +3. optionally update your loading image based on progress callbacks +4. On loader completion, run all objects and use `Texture.from()` to pull the loaded textures out of the texture cache +5. Prepare your textures (optional - see below) +6. Hide your loading image, start rendering your scene graph Using this workflow ensures that your textures are pre-loaded, to prevent pop-in, and is relatively easy to code. -Regarding preparing textures: Even after you've loaded your textures, the images still need to be pushed to the GPU and decoded. Doing this for a large number of source images can be slow and cause lag spikes when your project first loads. To solve this, you can use the [Prepare](https://pixijs.download/release/docs/PIXI.Prepare.html) plugin, which allows you to pre-load textures in a final step before displaying your project. +Regarding preparing textures: Even after you've loaded your textures, the images still need to be pushed to the GPU and decoded. Doing this for a large number of source images can be slow and cause lag spikes when your project first loads. To solve this, you can use the [Prepare](https://pixijs.download/release/docs/rendering.PrepareSystem.html) plugin, which allows you to pre-load textures in a final step before displaying your project. ## Unloading Textures Once you're done with a Texture, you may wish to free up the memory (both WebGL-managed buffers and browser-based) that it uses. To do so, you should call `destroy()` on the BaseTexture that owns the data. Remember that Textures don't manage pixel data! -This is a particularly good idea for short-lived imagery like cut-scenes that are large and will only be used once. If you want to remove *all* textures and wipe the slate clean, you can use the `PIXI.utils.destroyTextureCache()` function. +This is a particularly good idea for short-lived imagery like cut-scenes that are large and will only be used once. If a texture is destroyed that was loaded via `Assets` then the assets class will automatically remove it from the cache for you. ## Beyond Images As we alluded to above, you can make a Texture out of more than just images: -Video: Pass an HTML5 `