diff --git a/404.html b/404.html index f7e349189..59a323512 100644 --- a/404.html +++ b/404.html @@ -1,5 +1,5 @@ - + @@ -9,13 +9,13 @@ - - + +
-
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- - +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+ + \ No newline at end of file diff --git a/7.x/branding.html b/7.x/branding.html index fa490f800..2ed462871 100644 --- a/7.x/branding.html +++ b/7.x/branding.html @@ -9,12 +9,12 @@ - - + +
-
Skip to main content

Branding

Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.

This is the banner that is displayed at the top of our README.

PixiJS Banner

We recommend using the Logo in places where the audience may not be familiar with PixiJS.

Logo (Dark)

Download: SVG +

Version: v7.x

Branding

Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.

This is the banner that is displayed at the top of our README.

PixiJS Banner

We recommend using the Logo in places where the audience may not be familiar with PixiJS.

Logo (Dark)

Download: SVG PNG

PixiJS Logo Full Dark

Logo (Dark, Transparent)

Download: SVG PNG

PixiJS Logo Full Dark

Logo (Pink)

Download: SVG PNG

PixiJS Logo Full Light

Logo (Pink, Transparent)

Download: SVG @@ -22,7 +22,7 @@ PNG

PixiJS Logo Full Dark

Mark (Pink)

Download: SVG PNG

PixiJS Logo Mark Dark

Mark (Light)

Download: SVG PNG

PixiJS Logo Mark Light

- - + + \ No newline at end of file diff --git a/7.x/examples.html b/7.x/examples.html index 059cabddd..849cc2a4a 100644 --- a/7.x/examples.html +++ b/7.x/examples.html @@ -9,13 +9,13 @@ - - + + - - +
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/collision-detection.html b/7.x/examples/advanced/collision-detection.html index d7c1eee57..6c0b598f1 100644 --- a/7.x/examples/advanced/collision-detection.html +++ b/7.x/examples/advanced/collision-detection.html @@ -9,13 +9,13 @@ - - + + - - +
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/mouse-trail.html b/7.x/examples/advanced/mouse-trail.html index 0b9ca597f..9409e6ba7 100644 --- a/7.x/examples/advanced/mouse-trail.html +++ b/7.x/examples/advanced/mouse-trail.html @@ -9,13 +9,13 @@ - - + + - - +
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/scratch-card.html b/7.x/examples/advanced/scratch-card.html index 06617c5a2..113911b62 100644 --- a/7.x/examples/advanced/scratch-card.html +++ b/7.x/examples/advanced/scratch-card.html @@ -9,13 +9,13 @@ - - + + - - +
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/screen-shot.html b/7.x/examples/advanced/screen-shot.html index dff587e9d..6741a96fb 100644 --- a/7.x/examples/advanced/screen-shot.html +++ b/7.x/examples/advanced/screen-shot.html @@ -9,13 +9,13 @@ - - + + - - +
Skip to main content
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/slots.html b/7.x/examples/advanced/slots.html index 8b3b3b5e5..b953f1c7a 100644 --- a/7.x/examples/advanced/slots.html +++ b/7.x/examples/advanced/slots.html @@ -9,13 +9,13 @@ - - + + - - +
Skip to main content
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/spinners.html b/7.x/examples/advanced/spinners.html index 8ae28ec10..10c170408 100644 --- a/7.x/examples/advanced/spinners.html +++ b/7.x/examples/advanced/spinners.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
+ + \ No newline at end of file diff --git a/7.x/examples/advanced/star-warp.html b/7.x/examples/advanced/star-warp.html index 38cc1d6d2..f70dbea71 100644 --- a/7.x/examples/advanced/star-warp.html +++ b/7.x/examples/advanced/star-warp.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Star Warp

+ + \ No newline at end of file diff --git a/7.x/examples/assets/async.html b/7.x/examples/assets/async.html index 196211c7f..12d52932b 100644 --- a/7.x/examples/assets/async.html +++ b/7.x/examples/assets/async.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Async

+ + \ No newline at end of file diff --git a/7.x/examples/assets/background.html b/7.x/examples/assets/background.html index f35188d03..59d53e6dc 100644 --- a/7.x/examples/assets/background.html +++ b/7.x/examples/assets/background.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Background

+ + \ No newline at end of file diff --git a/7.x/examples/assets/bundle.html b/7.x/examples/assets/bundle.html index 50cc9dae2..04dfa64d8 100644 --- a/7.x/examples/assets/bundle.html +++ b/7.x/examples/assets/bundle.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Bundle

+ + \ No newline at end of file diff --git a/7.x/examples/assets/multiple.html b/7.x/examples/assets/multiple.html index dd1e9eb23..08ebd6716 100644 --- a/7.x/examples/assets/multiple.html +++ b/7.x/examples/assets/multiple.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Multiple

+ + \ No newline at end of file diff --git a/7.x/examples/assets/promise.html b/7.x/examples/assets/promise.html index 74e734c3b..e1045fe26 100644 --- a/7.x/examples/assets/promise.html +++ b/7.x/examples/assets/promise.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Promise

+ + \ No newline at end of file diff --git a/7.x/examples/basic/blend-modes.html b/7.x/examples/basic/blend-modes.html index 9a2b7081a..15248ef93 100644 --- a/7.x/examples/basic/blend-modes.html +++ b/7.x/examples/basic/blend-modes.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Blend Modes

+ + \ No newline at end of file diff --git a/7.x/examples/basic/cache-as-bitmap.html b/7.x/examples/basic/cache-as-bitmap.html index 473684fb3..ec6bcb6ae 100644 --- a/7.x/examples/basic/cache-as-bitmap.html +++ b/7.x/examples/basic/cache-as-bitmap.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Cache As Bitmap

+ + \ No newline at end of file diff --git a/7.x/examples/basic/container.html b/7.x/examples/basic/container.html index a4c1311ee..4e4760f05 100644 --- a/7.x/examples/basic/container.html +++ b/7.x/examples/basic/container.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Container

+ + \ No newline at end of file diff --git a/7.x/examples/basic/particle-container.html b/7.x/examples/basic/particle-container.html index 3043075d7..19ca3b63b 100644 --- a/7.x/examples/basic/particle-container.html +++ b/7.x/examples/basic/particle-container.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Particle Container

+ + \ No newline at end of file diff --git a/7.x/examples/basic/simple-plane.html b/7.x/examples/basic/simple-plane.html index 05f36f88b..d56680171 100644 --- a/7.x/examples/basic/simple-plane.html +++ b/7.x/examples/basic/simple-plane.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Simple Plane

+ + \ No newline at end of file diff --git a/7.x/examples/basic/tinting.html b/7.x/examples/basic/tinting.html index 13e4312c5..c10ffbc84 100644 --- a/7.x/examples/basic/tinting.html +++ b/7.x/examples/basic/tinting.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
+ + \ No newline at end of file diff --git a/7.x/examples/basic/transparent-background.html b/7.x/examples/basic/transparent-background.html index 47eb46660..42c64bba4 100644 --- a/7.x/examples/basic/transparent-background.html +++ b/7.x/examples/basic/transparent-background.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Transparent Background

+ + \ No newline at end of file diff --git a/7.x/examples/events/click.html b/7.x/examples/events/click.html index 9bd10d592..bdfea2f87 100644 --- a/7.x/examples/events/click.html +++ b/7.x/examples/events/click.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Click

+ + \ No newline at end of file diff --git a/7.x/examples/events/custom-hitarea.html b/7.x/examples/events/custom-hitarea.html index 88160b973..6cd0d1631 100644 --- a/7.x/examples/events/custom-hitarea.html +++ b/7.x/examples/events/custom-hitarea.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Custom Hitarea

+ + \ No newline at end of file diff --git a/7.x/examples/events/custom-mouse-icon.html b/7.x/examples/events/custom-mouse-icon.html index b045b3750..d04c758de 100644 --- a/7.x/examples/events/custom-mouse-icon.html +++ b/7.x/examples/events/custom-mouse-icon.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Custom Mouse Icon

+ + \ No newline at end of file diff --git a/7.x/examples/events/dragging.html b/7.x/examples/events/dragging.html index b75e26654..c696e8273 100644 --- a/7.x/examples/events/dragging.html +++ b/7.x/examples/events/dragging.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Dragging

+ + \ No newline at end of file diff --git a/7.x/examples/events/interactivity.html b/7.x/examples/events/interactivity.html index 73545deee..bd5a2f9e9 100644 --- a/7.x/examples/events/interactivity.html +++ b/7.x/examples/events/interactivity.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Interactivity

+ + \ No newline at end of file diff --git a/7.x/examples/events/logger.html b/7.x/examples/events/logger.html index 1881970ef..25d7ae618 100644 --- a/7.x/examples/events/logger.html +++ b/7.x/examples/events/logger.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
+ + \ No newline at end of file diff --git a/7.x/examples/events/nested-boundary-with-projection.html b/7.x/examples/events/nested-boundary-with-projection.html index b80285ba0..3f150187c 100644 --- a/7.x/examples/events/nested-boundary-with-projection.html +++ b/7.x/examples/events/nested-boundary-with-projection.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Nested Boundary With Projection

+ + \ No newline at end of file diff --git a/7.x/examples/events/pointer-tracker.html b/7.x/examples/events/pointer-tracker.html index 37f06d358..517ffe37c 100644 --- a/7.x/examples/events/pointer-tracker.html +++ b/7.x/examples/events/pointer-tracker.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Pointer Tracker

+ + \ No newline at end of file diff --git a/7.x/examples/events/slider.html b/7.x/examples/events/slider.html index cf18a2ae0..04b90b1b8 100644 --- a/7.x/examples/events/slider.html +++ b/7.x/examples/events/slider.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Slider

+ + \ No newline at end of file diff --git a/7.x/examples/filters-advanced/custom.html b/7.x/examples/filters-advanced/custom.html index e03ca3274..0460cb202 100644 --- a/7.x/examples/filters-advanced/custom.html +++ b/7.x/examples/filters-advanced/custom.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Custom

+ + \ No newline at end of file diff --git a/7.x/examples/filters-advanced/mouse-blending.html b/7.x/examples/filters-advanced/mouse-blending.html index 93a8f2fd6..c4e69e2c9 100644 --- a/7.x/examples/filters-advanced/mouse-blending.html +++ b/7.x/examples/filters-advanced/mouse-blending.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Mouse Blending

+ + \ No newline at end of file diff --git a/7.x/examples/filters-advanced/shader-toy-filter-render-texture.html b/7.x/examples/filters-advanced/shader-toy-filter-render-texture.html index 43a14107f..e66d6014b 100644 --- a/7.x/examples/filters-advanced/shader-toy-filter-render-texture.html +++ b/7.x/examples/filters-advanced/shader-toy-filter-render-texture.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Shader Toy Filter Render Texture

+ + \ No newline at end of file diff --git a/7.x/examples/filters-basic/blur.html b/7.x/examples/filters-basic/blur.html index 753890024..1caa648d4 100644 --- a/7.x/examples/filters-basic/blur.html +++ b/7.x/examples/filters-basic/blur.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Blur

+ + \ No newline at end of file diff --git a/7.x/examples/filters-basic/color-matrix.html b/7.x/examples/filters-basic/color-matrix.html index b421c5d5c..59c1c83f4 100644 --- a/7.x/examples/filters-basic/color-matrix.html +++ b/7.x/examples/filters-basic/color-matrix.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Color Matrix

+ + \ No newline at end of file diff --git a/7.x/examples/filters-basic/displacement-map-crawlies.html b/7.x/examples/filters-basic/displacement-map-crawlies.html index a1aaa77e3..32413f5fb 100644 --- a/7.x/examples/filters-basic/displacement-map-crawlies.html +++ b/7.x/examples/filters-basic/displacement-map-crawlies.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Displacement Map Crawlies

+ + \ No newline at end of file diff --git a/7.x/examples/filters-basic/displacement-map-flag.html b/7.x/examples/filters-basic/displacement-map-flag.html index fc5df2b31..0c76b2b8f 100644 --- a/7.x/examples/filters-basic/displacement-map-flag.html +++ b/7.x/examples/filters-basic/displacement-map-flag.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Displacement Map Flag

+ + \ No newline at end of file diff --git a/7.x/examples/graphics/advanced.html b/7.x/examples/graphics/advanced.html index 483130a7a..c7c6c2bf4 100644 --- a/7.x/examples/graphics/advanced.html +++ b/7.x/examples/graphics/advanced.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Advanced

+ + \ No newline at end of file diff --git a/7.x/examples/graphics/dynamic.html b/7.x/examples/graphics/dynamic.html index 1905bbed8..23f1f0508 100644 --- a/7.x/examples/graphics/dynamic.html +++ b/7.x/examples/graphics/dynamic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Dynamic

+ + \ No newline at end of file diff --git a/7.x/examples/graphics/simple.html b/7.x/examples/graphics/simple.html index 56b06dd56..2cbec60b7 100644 --- a/7.x/examples/graphics/simple.html +++ b/7.x/examples/graphics/simple.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Simple

+ + \ No newline at end of file diff --git a/7.x/examples/masks/filter.html b/7.x/examples/masks/filter.html index e9137f6d5..9308335a9 100644 --- a/7.x/examples/masks/filter.html +++ b/7.x/examples/masks/filter.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Filter

+ + \ No newline at end of file diff --git a/7.x/examples/masks/graphics.html b/7.x/examples/masks/graphics.html index b870822b1..3f908b508 100644 --- a/7.x/examples/masks/graphics.html +++ b/7.x/examples/masks/graphics.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Graphics

+ + \ No newline at end of file diff --git a/7.x/examples/masks/sprite.html b/7.x/examples/masks/sprite.html index 6bd81a0ec..c9d0f2f82 100644 --- a/7.x/examples/masks/sprite.html +++ b/7.x/examples/masks/sprite.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Sprite

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/instanced-geometry.html b/7.x/examples/mesh-and-shaders/instanced-geometry.html index ed4cdf13e..a9b82eb47 100644 --- a/7.x/examples/mesh-and-shaders/instanced-geometry.html +++ b/7.x/examples/mesh-and-shaders/instanced-geometry.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Instanced Geometry

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/interleaving-geometry.html b/7.x/examples/mesh-and-shaders/interleaving-geometry.html index 803d97f8e..056295a95 100644 --- a/7.x/examples/mesh-and-shaders/interleaving-geometry.html +++ b/7.x/examples/mesh-and-shaders/interleaving-geometry.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Interleaving Geometry

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/merging-geometry.html b/7.x/examples/mesh-and-shaders/merging-geometry.html index 20c260a92..a1213a01c 100644 --- a/7.x/examples/mesh-and-shaders/merging-geometry.html +++ b/7.x/examples/mesh-and-shaders/merging-geometry.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Merging Geometry

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh.html b/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh.html index 1188951d0..4dc3cc481 100644 --- a/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh.html +++ b/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Multi Pass Shader Generated Mesh

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/shader-toy-mesh.html b/7.x/examples/mesh-and-shaders/shader-toy-mesh.html index 2d7827aa4..7b23540e2 100644 --- a/7.x/examples/mesh-and-shaders/shader-toy-mesh.html +++ b/7.x/examples/mesh-and-shaders/shader-toy-mesh.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Shader Toy Mesh

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/shared-shader.html b/7.x/examples/mesh-and-shaders/shared-shader.html index b4bcd4033..56b675d50 100644 --- a/7.x/examples/mesh-and-shaders/shared-shader.html +++ b/7.x/examples/mesh-and-shaders/shared-shader.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Shared Shader

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/sharing-geometry.html b/7.x/examples/mesh-and-shaders/sharing-geometry.html index 9561bf1b9..7ff1189d8 100644 --- a/7.x/examples/mesh-and-shaders/sharing-geometry.html +++ b/7.x/examples/mesh-and-shaders/sharing-geometry.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Sharing Geometry

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/textured-mesh-advanced.html b/7.x/examples/mesh-and-shaders/textured-mesh-advanced.html index 04b919d56..152686cb7 100644 --- a/7.x/examples/mesh-and-shaders/textured-mesh-advanced.html +++ b/7.x/examples/mesh-and-shaders/textured-mesh-advanced.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Textured Mesh Advanced

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/textured-mesh-basic.html b/7.x/examples/mesh-and-shaders/textured-mesh-basic.html index 76a20da44..4bb24abe2 100644 --- a/7.x/examples/mesh-and-shaders/textured-mesh-basic.html +++ b/7.x/examples/mesh-and-shaders/textured-mesh-basic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Textured Mesh Basic

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/triangle-color.html b/7.x/examples/mesh-and-shaders/triangle-color.html index d36b7f5c5..553bf45df 100644 --- a/7.x/examples/mesh-and-shaders/triangle-color.html +++ b/7.x/examples/mesh-and-shaders/triangle-color.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Triangle Color

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/triangle-textured.html b/7.x/examples/mesh-and-shaders/triangle-textured.html index fcece4470..0a87251b4 100644 --- a/7.x/examples/mesh-and-shaders/triangle-textured.html +++ b/7.x/examples/mesh-and-shaders/triangle-textured.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Triangle Textured

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/triangle.html b/7.x/examples/mesh-and-shaders/triangle.html index 24daa1644..d5d7c76a3 100644 --- a/7.x/examples/mesh-and-shaders/triangle.html +++ b/7.x/examples/mesh-and-shaders/triangle.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Triangle

+ + \ No newline at end of file diff --git a/7.x/examples/mesh-and-shaders/uniforms.html b/7.x/examples/mesh-and-shaders/uniforms.html index 6b21341c8..2ca5c70eb 100644 --- a/7.x/examples/mesh-and-shaders/uniforms.html +++ b/7.x/examples/mesh-and-shaders/uniforms.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Uniforms

+ + \ No newline at end of file diff --git a/7.x/examples/offscreen-canvas/basic.html b/7.x/examples/offscreen-canvas/basic.html index 798710ba8..4ad4c84e4 100644 --- a/7.x/examples/offscreen-canvas/basic.html +++ b/7.x/examples/offscreen-canvas/basic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Basic

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/animated-sprite-animation-speed.html b/7.x/examples/sprite/animated-sprite-animation-speed.html index 3269f1a3c..f1c385c91 100644 --- a/7.x/examples/sprite/animated-sprite-animation-speed.html +++ b/7.x/examples/sprite/animated-sprite-animation-speed.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Animated Sprite Animation Speed

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/animated-sprite-explosion.html b/7.x/examples/sprite/animated-sprite-explosion.html index 88d4fbbc8..9d990227a 100644 --- a/7.x/examples/sprite/animated-sprite-explosion.html +++ b/7.x/examples/sprite/animated-sprite-explosion.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Animated Sprite Explosion

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/animated-sprite-jet.html b/7.x/examples/sprite/animated-sprite-jet.html index 070b5b932..f0c997939 100644 --- a/7.x/examples/sprite/animated-sprite-jet.html +++ b/7.x/examples/sprite/animated-sprite-jet.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Animated Sprite Jet

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/basic.html b/7.x/examples/sprite/basic.html index 97f26852a..e1cfa7201 100644 --- a/7.x/examples/sprite/basic.html +++ b/7.x/examples/sprite/basic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Basic

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/texture-swap.html b/7.x/examples/sprite/texture-swap.html index 2706af686..74bdc2516 100644 --- a/7.x/examples/sprite/texture-swap.html +++ b/7.x/examples/sprite/texture-swap.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Texture Swap

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/tiling-sprite.html b/7.x/examples/sprite/tiling-sprite.html index 62ff8121c..4c8702150 100644 --- a/7.x/examples/sprite/tiling-sprite.html +++ b/7.x/examples/sprite/tiling-sprite.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Tiling Sprite

+ + \ No newline at end of file diff --git a/7.x/examples/sprite/video.html b/7.x/examples/sprite/video.html index d2cf2c58f..0084044bf 100644 --- a/7.x/examples/sprite/video.html +++ b/7.x/examples/sprite/video.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Video

+ + \ No newline at end of file diff --git a/7.x/examples/text/bitmap-text.html b/7.x/examples/text/bitmap-text.html index dc6ce3908..ceb3ab38a 100644 --- a/7.x/examples/text/bitmap-text.html +++ b/7.x/examples/text/bitmap-text.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Bitmap Text

+ + \ No newline at end of file diff --git a/7.x/examples/text/from-font.html b/7.x/examples/text/from-font.html index 38fb03d70..394483b4f 100644 --- a/7.x/examples/text/from-font.html +++ b/7.x/examples/text/from-font.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

From Font

+ + \ No newline at end of file diff --git a/7.x/examples/text/pixi-text.html b/7.x/examples/text/pixi-text.html index f619cad32..97b549ec0 100644 --- a/7.x/examples/text/pixi-text.html +++ b/7.x/examples/text/pixi-text.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Pixi Text

+ + \ No newline at end of file diff --git a/7.x/examples/text/web-font.html b/7.x/examples/text/web-font.html index 1a5644705..0db02f991 100644 --- a/7.x/examples/text/web-font.html +++ b/7.x/examples/text/web-font.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Web Font

+ + \ No newline at end of file diff --git a/7.x/examples/textures/gradient-basic.html b/7.x/examples/textures/gradient-basic.html index d3ce39a2a..7b1be554c 100644 --- a/7.x/examples/textures/gradient-basic.html +++ b/7.x/examples/textures/gradient-basic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Gradient Basic

+ + \ No newline at end of file diff --git a/7.x/examples/textures/gradient-resource.html b/7.x/examples/textures/gradient-resource.html index 7ba8289dc..05c9f2f67 100644 --- a/7.x/examples/textures/gradient-resource.html +++ b/7.x/examples/textures/gradient-resource.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Gradient Resource

+ + \ No newline at end of file diff --git a/7.x/examples/textures/render-texture-advanced.html b/7.x/examples/textures/render-texture-advanced.html index 6706bf37f..0da1e2757 100644 --- a/7.x/examples/textures/render-texture-advanced.html +++ b/7.x/examples/textures/render-texture-advanced.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Render Texture Advanced

+ + \ No newline at end of file diff --git a/7.x/examples/textures/render-texture-basic.html b/7.x/examples/textures/render-texture-basic.html index e76ffe6ea..391a3ec6e 100644 --- a/7.x/examples/textures/render-texture-basic.html +++ b/7.x/examples/textures/render-texture-basic.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Render Texture Basic

+ + \ No newline at end of file diff --git a/7.x/examples/textures/texture-rotate.html b/7.x/examples/textures/texture-rotate.html index ef4238f54..8eb01dc1f 100644 --- a/7.x/examples/textures/texture-rotate.html +++ b/7.x/examples/textures/texture-rotate.html @@ -9,13 +9,13 @@ - - + +
-
Skip to main content
- - +
Skip to main content
Version: v7.x

Texture Rotate

+ + \ No newline at end of file diff --git a/7.x/faq.html b/7.x/faq.html index 15b5e3ff7..58dbc522b 100644 --- a/7.x/faq.html +++ b/7.x/faq.html @@ -9,12 +9,12 @@ - - + +
-
Skip to main content

FAQ

What is PixiJS for?

Everything! Pixi.js is a rendering library that will allow you to create rich, +

Version: v7.x

FAQ

What is PixiJS for?

Everything! Pixi.js is a rendering library that will allow you to create rich, interactive graphic experiences, cross-platform applications, and games without having to dive into the WebGL API or grapple with the intricacies of browser and device compatibility. Killer performance with a clean API, means not only will @@ -22,7 +22,7 @@ are what make it possible to push PixiJS further, faster. Contributions allow us to commission the PixiJS developer community to accelerate feature development and create more in-depth documentation. Support Us by making a contribution via Open Collective. Go on! It will be a massive help AND make you feel good about yourself, win win ;)

Where do I get it?

Visit our GitHub page to download the very latest version of PixiJS. This is the most up-to-date resource for PixiJS and should always be your first port of call to make sure you are using the latest version. Just click the 'Download' link in the navigation.

How do I get started?

Right here! Take a look through the Resources section for a wealth of information including documentation, forums, tutorials and the Goodboy blog.

Why should I use PixiJS?

Because you care about speed. PixiJS' #1 mantra has always been speed. We really do feel the need! We do everything we can to make PixiJS as streamlined, efficient and fast as possible, whilst balancing it with offering as many crucial and valuable features as we can.

Is PixiJS a game engine?

No. PixiJS is what we've come to think of as a "creation engine". Whilst it is extremely good for making games, the core essence of PixiJS is simply moving things around on screens as quickly and efficiently as possible. It does of course happen that it is absolutely brilliant for making games though!

Who makes PixiJS?

Outside of the highly active PixiJS community, it is primarily maintained by Mat Groves, Technical Partner of our creative agency Goodboy Digital. One of the huge advantages of creating PixiJS within the framework of a working agency is that it means its features are always driven by genuine industry demands and critically are always trialled "in anger" in our cutting-edge games, sites and apps.

I found a bug. What should I do?

Two things - lets us know via the PixiJS GitHub community and even better yet, if you know how, post a fix! Our Community is stronger in numbers so we're always keen to welcome new contributors into the team to help us shape what PixiJS becomes next.

- - + + \ No newline at end of file diff --git a/7.x/guides.html b/7.x/guides.html index 6be70b3f6..6bbad5083 100644 --- a/7.x/guides.html +++ b/7.x/guides.html @@ -9,13 +9,13 @@ - - + +
-

Welcome

PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.

About The Guides

If you're new to PixiJS, we suggest you start with the Basics and read through them in order (a good place to start is Getting Started). While PixiJS has a mature API and solid documentation, the guides go over many common issues and questions that developers new to the system encounter.

Other Resources

As you explore the guides, you may find these resources valuable:

- - +
Version: v7.x

Welcome

PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.

About The Guides

If you're new to PixiJS, we suggest you start with the Basics and read through them in order (a good place to start is Getting Started). While PixiJS has a mature API and solid documentation, the guides go over many common issues and questions that developers new to the system encounter.

Other Resources

As you explore the guides, you may find these resources valuable:

+ + \ No newline at end of file diff --git a/7.x/guides/basics/architecture-overview.html b/7.x/guides/basics/architecture-overview.html index f442c5fe7..2bf091730 100644 --- a/7.x/guides/basics/architecture-overview.html +++ b/7.x/guides/basics/architecture-overview.html @@ -9,13 +9,13 @@ - - + +
-

Architecture Overview

OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.

The Code

Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on GitHub. Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called TypeScript, which enables type-checking in JavaScript via a pre-compile step.

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, 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

ComponentDescription
Renderer @pixi/coreThe 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/displayMain display object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See Scene Graph for more details.
Loader @pixi/loaderThe loader system provides tools for asynchronously loading resources such as images and audio files.
Ticker @pixi/tickerTickers 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/appThe 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/interactionPixiJS supports both touch and mouse-based interaction - making objects clickable, firing hover events, etc.
Accessibility @pixi/accessibilityWoven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility.
- - +
Version: v7.x

Architecture Overview

OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.

The Code

Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on GitHub. Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called TypeScript, which enables type-checking in JavaScript via a pre-compile step.

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, 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

ComponentDescription
Renderer @pixi/coreThe 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/displayMain display object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See Scene Graph for more details.
Loader @pixi/loaderThe loader system provides tools for asynchronously loading resources such as images and audio files.
Ticker @pixi/tickerTickers 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/appThe 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/interactionPixiJS supports both touch and mouse-based interaction - making objects clickable, firing hover events, etc.
Accessibility @pixi/accessibilityWoven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility.
+ + \ No newline at end of file diff --git a/7.x/guides/basics/getting-started.html b/7.x/guides/basics/getting-started.html index d1eaccc38..9593afd42 100644 --- a/7.x/guides/basics/getting-started.html +++ b/7.x/guides/basics/getting-started.html @@ -9,16 +9,16 @@ - - + +
-

Getting Started

In this section we're going to build the simplest possible PixiJS application. In doing so, we'll walk through the basics of how to build and serve the code.

Advanced Users

A quick note before we start: this guide is aimed at beginning PixiJS developers who have minimal +

Version: v7.x

Getting Started

In this section we're going to build the simplest possible PixiJS application. In doing so, we'll walk through the basics of how to build and serve the code.

Advanced Users

A quick note before we start: this guide is aimed at beginning PixiJS developers who have minimal experience developing JavaScript-based applications. If you are a coding veteran, you may find that the level of detail here is not helpful. If that's the case, you may want to skim this guide, then jump into how to work with PixiJS and packers like webpack and npm.

A Note About JavaScript

One final note. The JavaScript universe is currently in transition from old-school JavaScript (ES5) to the newer ES6 flavor:

// ES5
var x = 5;
setTimeout(function() { alert(x); }, 1000);
// ES6
const x = 5;
setTimeout(() => alert(x), 1000);

ES6 brings a number of major advantages in terms of clearer syntax, better variable scoping, native class support, etc. By now, all major browsers support it. Given this, our examples in these guides will use ES6. This doesn't mean you can't use PixiJS with ES5 programs! Just mentally substitute "var" for "let/const", expand the shorter function-passing syntax, and everything will run just fine.

Components of a PixiJS Application

OK! With those notes out of the way, let's get started. There are only a few steps required to write a PixiJS application:

  • Create an HTML file
  • Serve the file with a web server
  • Load the PixiJS library
  • Create an Application
  • Add the generated view to the DOM
  • Add an image to the stage
  • Write an update loop

Let's walk through them together.

The HTML File

PixiJS is a JavaScript library that runs in a web page. So the first thing we're going to need is some HTML in a file. In a real PixiJS application, you might want to embed your display within a complex existing page, or you might want your display area to fill the whole page. For this demo, we'll build an empty page to start:

<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello PixiJS</h1>
</body>
</html>

Create a new folder named pixi-test, then copy and paste this HTML into a new file in the pixi-test folder named index.html.

Serving the File

You will need to run a web server to develop locally with PixiJS. Web browsers prevent loading local files (such as images and audio files) on locally loaded web pages. If you just double-click your new HTML file, you'll get an error when you try to add a sprite to the PixiJS stage.

Running a web server sounds complex and difficult, but it turns out there are a number of simple web servers that will serve this purpose. For this guide, we're going to be working with Mongoose, but you could just as easily use XAMPP or the http-server Node.js package to serve your files.

To start serving your page with Mongoose, go to the Mongoose download page and download the free server for your operating system. Mongoose defaults to serving the files in the folder it's run in, so copy the downloaded executable into the folder you created in the prior step (pixi-test). Double-click the executable, tell your operating system that you trust the file to run, and you'll have a running web server, serving your new folder.

Test that everything is working by opening your browser of choice and entering http://127.0.0.1:8080 in the location bar. (Mongoose by default serves files on port 8080.) You should see "Hello PixiJS" and nothing else. If you get an error at this step, it means you didn't name your file index.html or you mis-configured your web server.

Loading PixiJS

OK, so we have a web page, and we're serving it. But it's empty. The next step is to actually load the PixiJS library. If we were building a real application, we'd want to download a target version of PixiJS from the Pixi Github repo so that our version wouldn't change on us. But for this sample application, we'll just use the CDN version of PixiJS. Add this line to the <head> section of your index.html file:

<script src="https://pixijs.download/release/pixi.js"></script>

This will include a non-minified version of the latest version of PixiJS when your page loads, ready to be used. We use the non-minified version because we're in development. In production, you'd want to use pixi.min.js instead, which is compressed for faster download and excludes assertions and deprecation warnings that can help when building your project, but take longer to download and run.

Creating an Application

Loading the library doesn't do much good if we don't use it, so the next step is to start up PixiJS. Start by replacing the line <h1>Hello PixiJS</h1> with a script tag like so:

<script>
let app = new PIXI.Application({ width: 640, height: 360 });
</script>

What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. Application 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.

Adding the View 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:

  document.body.appendChild(app.view);

This takes the view 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. 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. A Sprite is a type of DisplayObject 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:

  // Magically load the PNG asynchronously
let sprite = PIXI.Sprite.from('sample.png');

Download the sample PNG here, and save it into your pixi-test directory next to your index.html.

Adding the Sprite to the Stage

Finally, we need to add our new sprite to the stage. The stage is simply a Container 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.

  app.stage.addChild(sprite);

Writing an Update Loop

While you can use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ticker. A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:

  // Add a variable to count up the seconds our demo has been running
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) => {
// Add the time to our total elapsed time
elapsed += delta;
// 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;
});

All you need to do is to call app.ticker.add(...), pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations.

Putting It All Together

That's it! The simplest PixiJS project!

Here's the whole thing in one place. Check your file and make sure it matches if you're getting errors.

<!doctype html>
<html>
<head>
<script src="https://pixijs.download/release/pixi.min.js"></script>
</head>
<body>
<script>
// Create the application helper and add its render target to the page
let app = new PIXI.Application({ width: 640, height: 360 });
document.body.appendChild(app.view);

// Create the sprite and add it to the stage
let sprite = PIXI.Sprite.from('sample.png');
app.stage.addChild(sprite);

// Add a ticker callback to move the sprite back and forth
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta;
sprite.x = 100.0 + Math.cos(elapsed/50.0) * 100.0;
});
</script>
</body>
</html>

Once you have things working, the next thing to do is to read through the rest of the Basics guides to dig into how all this works in much greater depth.

- - + + \ No newline at end of file diff --git a/7.x/guides/basics/render-loop.html b/7.x/guides/basics/render-loop.html index 6903573e2..fe7f7008f 100644 --- a/7.x/guides/basics/render-loop.html +++ b/7.x/guides/basics/render-loop.html @@ -9,13 +9,13 @@ - - + +
-

Render Loop

Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.

The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering.

Let's walk through what happens each frame of the render loop. There are three main steps.

Running Ticker Callbacks

The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering.

Updating the Scene Graph

We'll talk a lot more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing.

Rendering the Scene Graph

Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (app.stage), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization.

Frame Rates

A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue.

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.

Custom Render Loops

What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach.

- - +
Version: v7.x

Render Loop

Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.

The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering.

Let's walk through what happens each frame of the render loop. There are three main steps.

Running Ticker Callbacks

The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering.

Updating the Scene Graph

We'll talk a lot more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing.

Rendering the Scene Graph

Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (app.stage), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization.

Frame Rates

A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue.

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.

Custom Render Loops

What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach.

+ + \ No newline at end of file diff --git a/7.x/guides/basics/scene-graph.html b/7.x/guides/basics/scene-graph.html index 5051bd4d4..b3eaadf9b 100644 --- a/7.x/guides/basics/scene-graph.html +++ b/7.x/guides/basics/scene-graph.html @@ -9,13 +9,13 @@ - - + +
-

Scene Graph

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

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.

(A helpful tool for exploring your project is the Pixi.js devtools plugin for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)

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.

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:

// 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);

// 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;
app.stage.addChild(container);

// 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');
sprite.anchor.set(0.5);
parent.addChild(sprite);
sprites.push(sprite);
parent = sprite;
}

// Set all sprite's properties to the same value, animated over time
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60;
const amount = Math.sin(elapsed);
const scale = 1.0 + 0.25 * amount;
const alpha = 0.75 + 0.25 * amount;
const angle = 40 * amount;
const x = 75 * amount;
for (let i = 0; i < sprites.length; i++) {
const sprite = sprites[i];
sprite.scale.set(scale);
sprite.alpha = alpha;
sprite.angle = angle;
sprite.x = x;
}
});

The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's worldTransform property. Similarly, the cumulative alpha value is stored in the worldAlpha property.

Render Order

So we have a tree of things to draw. Who gets drawn first?

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.

Check out this example, with two parent objects A & D, and two children B & C under A:

// 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);

// 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};
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);
bg.width = 100;
bg.height = 100;
bg.tint = color;

const text = new PIXI.Text(letter, {fill: "#ffffff"});
text.anchor.set(0.5);
text.position = {x: 50, y: 50};

const container = new PIXI.Container();
container.position = pos;
container.visible = false;
container.addChild(bg, text);
parent.addChild(container);

letters.push(container);
return container;
}

// Define 4 letters
let a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});
let b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});
let c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});
let d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});

// Display them over time, in order
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60.0;
if (elapsed >= letters.length) { elapsed = 0.0; }
for (let i = 0; i < letters.length; i ++) {
letters[i].visible = elapsed >= i;
}
});

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.

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.

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.

Local vs Global Coordinates

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.

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.

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 PIXI.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.

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.

- - +
Version: v7.x

Scene Graph

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

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.

(A helpful tool for exploring your project is the Pixi.js devtools plugin for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)

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.

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:

// 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);

// 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;
app.stage.addChild(container);

// 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');
sprite.anchor.set(0.5);
parent.addChild(sprite);
sprites.push(sprite);
parent = sprite;
}

// Set all sprite's properties to the same value, animated over time
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60;
const amount = Math.sin(elapsed);
const scale = 1.0 + 0.25 * amount;
const alpha = 0.75 + 0.25 * amount;
const angle = 40 * amount;
const x = 75 * amount;
for (let i = 0; i < sprites.length; i++) {
const sprite = sprites[i];
sprite.scale.set(scale);
sprite.alpha = alpha;
sprite.angle = angle;
sprite.x = x;
}
});

The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's worldTransform property. Similarly, the cumulative alpha value is stored in the worldAlpha property.

Render Order

So we have a tree of things to draw. Who gets drawn first?

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.

Check out this example, with two parent objects A & D, and two children B & C under A:

// 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);

// 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};
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);
bg.width = 100;
bg.height = 100;
bg.tint = color;

const text = new PIXI.Text(letter, {fill: "#ffffff"});
text.anchor.set(0.5);
text.position = {x: 50, y: 50};

const container = new PIXI.Container();
container.position = pos;
container.visible = false;
container.addChild(bg, text);
parent.addChild(container);

letters.push(container);
return container;
}

// Define 4 letters
let a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});
let b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});
let c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});
let d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});

// Display them over time, in order
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60.0;
if (elapsed >= letters.length) { elapsed = 0.0; }
for (let i = 0; i < letters.length; i ++) {
letters[i].visible = elapsed >= i;
}
});

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.

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.

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.

Local vs Global Coordinates

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.

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.

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 PIXI.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.

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.

+ + \ No newline at end of file diff --git a/7.x/guides/basics/what-pixijs-is-not.html b/7.x/guides/basics/what-pixijs-is-not.html index bc3cc32df..920f30c15 100644 --- a/7.x/guides/basics/what-pixijs-is-not.html +++ b/7.x/guides/basics/what-pixijs-is-not.html @@ -9,13 +9,13 @@ - - + +
-

What PixiJS Is Not

While PixiJS can do many things, there are things it can't do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you're about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.

PixiJS Is Not ... A Framework

PixiJS is a rendering engine, and it supports additional features such as interaction management that are commonly needed when using a render engine. But it is not a framework like Unity or Phaser. Frameworks are designed to do all the things you'd need to do when building a game - user settings management, music playback, object scripting, art pipeline management... the list goes on. PixiJS is designed to do one thing really well - render graphical content. This lets us focus on keeping up with new technology, and makes downloading PixiJS blazingly fast.

... A 3D Renderer

PixiJS is built for 2D. Platformers, adventure games, interactive ads, custom data visualization... all good. But if you want to render 3D models, you might want to check out babylon.js or three.js.

... A Mobile App

If you're looking to build mobile games, you can do it with PixiJS, but you'll need to use a deployment system like Apache Cordova if you want access to native bindings. We don't provide access to the camera, location services, notifications, etc.

... A UI Library

Building a truly generic UI system is a huge challenge, as anyone who has worked with Unity's UI tools can attest. We've chosen to avoid the complexity to stay true to our core focus on speed. While you can certainly build your own UI using PixiJS's scene graph and interaction manager, we don't ship with a UI library out of the box.

... A Data Store

There are many techniques and technologies that you can use to store settings, scores, and other data. Cookies, Web Storage, server-based storage... there are many solutions, each with advantages and disadvantages. You can use any of them with PixiJS, but we don't provide tools to do so.

... An Audio Library

At least, not out of the box. Again, web audio technology is a constantly evolving challenge, with constantly changing rules and requirements across many browsers. There are a number of dedicated web audio libraries (such as Howler.js that can be used with PixiJS to play sound effects and music. Alternatively, the PixiJS Sound plugin is designed to work well with PixiJS.

... A Development Environment

There are a number of tools that are useful for building 2D art and games that you might expect to be a part of PixiJS, but we're a rendering engine, not a development environment. Packing sprite sheets, processing images, building mipmaps or Retina-ready sprites - there are great standalone tools for this type of tooling. Where appropriate throughout the guides, we'll point you to tools that may be useful.

So Is PixiJS Right For Me?

Only you know! If you're looking for a tightly focused, fast and efficient rendering engine for your next web-based project, PixiJS is likely a great fit.

If you need a full game development framework, with native bindings and a rich UI library, you may want to explore other options.

Or you may not. It can be faster and easier to build just the subset of a full framework that your project needs than it can be to digest a monolithic API with bells and whistles you don't need. There are hundreds of complex, rich games and visual projects that use PixiJS for rendering, with plugins or custom code to add the UI and sound effects. There are benefits to both approaches. Regardless, we hope you have a better feel for what PixiJS can (and cannot!) offer your project.

- - +
Version: v7.x

What PixiJS Is Not

While PixiJS can do many things, there are things it can't do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you're about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.

PixiJS Is Not ... A Framework

PixiJS is a rendering engine, and it supports additional features such as interaction management that are commonly needed when using a render engine. But it is not a framework like Unity or Phaser. Frameworks are designed to do all the things you'd need to do when building a game - user settings management, music playback, object scripting, art pipeline management... the list goes on. PixiJS is designed to do one thing really well - render graphical content. This lets us focus on keeping up with new technology, and makes downloading PixiJS blazingly fast.

... A 3D Renderer

PixiJS is built for 2D. Platformers, adventure games, interactive ads, custom data visualization... all good. But if you want to render 3D models, you might want to check out babylon.js or three.js.

... A Mobile App

If you're looking to build mobile games, you can do it with PixiJS, but you'll need to use a deployment system like Apache Cordova if you want access to native bindings. We don't provide access to the camera, location services, notifications, etc.

... A UI Library

Building a truly generic UI system is a huge challenge, as anyone who has worked with Unity's UI tools can attest. We've chosen to avoid the complexity to stay true to our core focus on speed. While you can certainly build your own UI using PixiJS's scene graph and interaction manager, we don't ship with a UI library out of the box.

... A Data Store

There are many techniques and technologies that you can use to store settings, scores, and other data. Cookies, Web Storage, server-based storage... there are many solutions, each with advantages and disadvantages. You can use any of them with PixiJS, but we don't provide tools to do so.

... An Audio Library

At least, not out of the box. Again, web audio technology is a constantly evolving challenge, with constantly changing rules and requirements across many browsers. There are a number of dedicated web audio libraries (such as Howler.js that can be used with PixiJS to play sound effects and music. Alternatively, the PixiJS Sound plugin is designed to work well with PixiJS.

... A Development Environment

There are a number of tools that are useful for building 2D art and games that you might expect to be a part of PixiJS, but we're a rendering engine, not a development environment. Packing sprite sheets, processing images, building mipmaps or Retina-ready sprites - there are great standalone tools for this type of tooling. Where appropriate throughout the guides, we'll point you to tools that may be useful.

So Is PixiJS Right For Me?

Only you know! If you're looking for a tightly focused, fast and efficient rendering engine for your next web-based project, PixiJS is likely a great fit.

If you need a full game development framework, with native bindings and a rich UI library, you may want to explore other options.

Or you may not. It can be faster and easier to build just the subset of a full framework that your project needs than it can be to digest a monolithic API with bells and whistles you don't need. There are hundreds of complex, rich games and visual projects that use PixiJS for rendering, with plugins or custom code to add the UI and sound effects. There are benefits to both approaches. Regardless, we hope you have a better feel for what PixiJS can (and cannot!) offer your project.

+ + \ No newline at end of file diff --git a/7.x/guides/basics/what-pixijs-is.html b/7.x/guides/basics/what-pixijs-is.html index c97eec539..2b27a38b6 100644 --- a/7.x/guides/basics/what-pixijs-is.html +++ b/7.x/guides/basics/what-pixijs-is.html @@ -9,13 +9,13 @@ - - + +
-

What PixiJS Is

So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.

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.

... 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. Draw polygons, lines, circles and other primitives with Graphics. Text 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

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 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

Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and hosted on GitHub for issue tracking and ease of access.

... Extensible

Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs.

... Easy to Deploy

Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock.

- - +
Version: v7.x

What PixiJS Is

So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.

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.

... 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. Draw polygons, lines, circles and other primitives with Graphics. Text 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

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 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

Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and hosted on GitHub for issue tracking and ease of access.

... Extensible

Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs.

... Easy to Deploy

Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock.

+ + \ No newline at end of file diff --git a/7.x/guides/components/assets.html b/7.x/guides/components/assets.html index 1d1bb26dc..d17f37c57 100644 --- a/7.x/guides/components/assets.html +++ b/7.x/guides/components/assets.html @@ -9,12 +9,12 @@ - - + +
-

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.

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. +

Version: v7.x

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.

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 you should look into polyfilling them.

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. In this example, we will load a texture and then turn it into a sprite.

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:

promise1 = PIXI.Assets.load('bunny.png')
promise2 = PIXI.Assets.load('bunny.png')

//promise1 === promise2

Out of the box, the following assets types can be loaded without the need for external plugins:

  • Textures (avif, webp, png, jpg, gif)
  • Sprite sheets (json)
  • Bitmap fonts (xml, fnt, txt)
  • Web fonts (ttf, woff, woff2)
  • Json files (json)
  • Text files (txt)

More types can be added fairly easily by creating additional loader parsers.

Warning about solved promises

When an asset is downloaded, it is cached as a promise inside the Assets instance and if you try to download it again you will get a reference to the already resolved promise. @@ -23,7 +23,7 @@ See the following example:

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(...).

  PIXI.Assets.addBundle('animals', {
bunny: 'bunny.png',
chicken: 'chicken.png',
thumper: 'thumper.png',
});

const assets = await PIXI.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). 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.

{
"bundles":[
{
"name":"load-screen",
"assets":[
{
"name":"background",
"srcs":"sunset.png"
},
{
"name":"bar",
"srcs":"load-bar.{png,webp}"
}
]
},
{
"name":"game-screen",
"assets":[
{
"name":"character",
"srcs":"robot.png"
},
{
"name":"enemy",
"srcs":"bad-guy.png"
}
]
}
]
}
PIXI.Assets.init({manifest: "path/manifest.json"});

Beware that you can only call init once.

Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!

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.

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.

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.

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.

The best way to do this is using bundles, see the following example:

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!

- - + + \ No newline at end of file diff --git a/7.x/guides/components/containers.html b/7.x/guides/components/containers.html index 8ba37230b..f8df4f092 100644 --- a/7.x/guides/components/containers.html +++ b/7.x/guides/components/containers.html @@ -9,13 +9,13 @@ - - + +
-

Containers

The Container 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.

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.

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.

So that's the primary use for Containers - as groups of renderable objects in a hierarchy.

Check out the container example code.

Masking

Another common use for Container objects is as hosts for masked content. "Masking" is a technique where parts of your scene graph are only visible within a given area.

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.

// Create the application helper and add its render target to the page
let app = new PIXI.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);
app.stage.addChild(frame);

// Create a graphics object to define our mask
let mask = new PIXI.Graphics();
// Add the rectangular area to show
mask.beginFill(0xffffff);
mask.drawRect(0,0,200,200);
mask.endFill();

// Add container that will hold our masked content
let maskContainer = new PIXI.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
maskContainer.addChild(mask);
// Offset by the window's frame width
maskContainer.position.set(4,4);
// And add the container to the window!
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' +
'You can put anything in the container and it will be masked!',
{
fontSize: 24,
fill: 0x1010ff,
wordWrap: true,
wordWrapWidth: 180
}
);
text.x = 10;
maskContainer.addChild(text);

// Add a ticker callback to scroll the text up and down
let elapsed = 0.0;
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;
});

There are two types of masks supported by PixiJS:

Use a Graphics object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing

Sprite: Use the alpha channel from a Sprite 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.

Below are list of filters available by default in PixiJS. There is, however, a community repository with many more filters.

FilterDescription
AlphaFilter: @pixi/filter-alphaSimilar to setting alpha property, but flattens the Container instead of applying to children individually.
BlurFilter: @pixi/filter-blurApply a blur effect
ColorMatrixFilter: @pixi/filter-color-matrixA color matrix is a flexible way to apply more complex tints or color transforms (e.g., sepia tone).
DisplacementFilter: @pixi/filter-displacementDisplacement maps create visual offset pixels, for instance creating a wavy water effect.
FXAAFilter: @pixi/filter-fxaaBasic FXAA (Fast Approximate Anti-Aliasing) to create smoothing effect.
NoiseFilter: @pixi/filter-noiseCreate random noise (e.g., grain effect).

Important: Filters should be use somewhat sparingly. They can slow performance and increase memory if used too often in a scene.

- - +
Version: v7.x

Containers

The Container 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.

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.

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.

So that's the primary use for Containers - as groups of renderable objects in a hierarchy.

Check out the container example code.

Masking

Another common use for Container objects is as hosts for masked content. "Masking" is a technique where parts of your scene graph are only visible within a given area.

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.

// Create the application helper and add its render target to the page
let app = new PIXI.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);
app.stage.addChild(frame);

// Create a graphics object to define our mask
let mask = new PIXI.Graphics();
// Add the rectangular area to show
mask.beginFill(0xffffff);
mask.drawRect(0,0,200,200);
mask.endFill();

// Add container that will hold our masked content
let maskContainer = new PIXI.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
maskContainer.addChild(mask);
// Offset by the window's frame width
maskContainer.position.set(4,4);
// And add the container to the window!
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' +
'You can put anything in the container and it will be masked!',
{
fontSize: 24,
fill: 0x1010ff,
wordWrap: true,
wordWrapWidth: 180
}
);
text.x = 10;
maskContainer.addChild(text);

// Add a ticker callback to scroll the text up and down
let elapsed = 0.0;
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;
});

There are two types of masks supported by PixiJS:

Use a Graphics object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing

Sprite: Use the alpha channel from a Sprite 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.

Below are list of filters available by default in PixiJS. There is, however, a community repository with many more filters.

FilterDescription
AlphaFilter: @pixi/filter-alphaSimilar to setting alpha property, but flattens the Container instead of applying to children individually.
BlurFilter: @pixi/filter-blurApply a blur effect
ColorMatrixFilter: @pixi/filter-color-matrixA color matrix is a flexible way to apply more complex tints or color transforms (e.g., sepia tone).
DisplacementFilter: @pixi/filter-displacementDisplacement maps create visual offset pixels, for instance creating a wavy water effect.
FXAAFilter: @pixi/filter-fxaaBasic FXAA (Fast Approximate Anti-Aliasing) to create smoothing effect.
NoiseFilter: @pixi/filter-noiseCreate random noise (e.g., grain effect).

Important: Filters should be use somewhat sparingly. They can slow performance and increase memory if used too often in a scene.

+ + \ No newline at end of file diff --git a/7.x/guides/components/display-object.html b/7.x/guides/components/display-object.html index bad8359dc..29c5da64b 100644 --- a/7.x/guides/components/display-object.html +++ b/7.x/guides/components/display-object.html @@ -9,13 +9,13 @@ - - + +
-

Display Objects

DisplayObject 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 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:

PropertyDescription
positionX- 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
rotationRotation is specified in radians, and turns an object clockwise (0.0 - 2 * Math.PI)
angleAngle is an alias for rotation that is specified in degrees instead of radians (0.0 - 360.0)
pivotPoint the object rotates around, in pixels - also sets origin for child objects
alphaOpacity from 0.0 (fully transparent) to 1.0 (fully opaque), inherited by children
scaleScale is specified as a percent with 1.0 being 100% or actual-size, and can be set independently for the x and y axis
skewSkew transforms the object in x and y similar to the CSS skew() function, and is specified in radians
visibleWhether the object is visible or not, as a boolean value - prevents updating and rendering object and children
renderableWhether the object should be rendered - when false, object will still be updated, but won't be rendered, doesn't affect children
- - +
Version: v7.x

Display Objects

DisplayObject 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 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:

PropertyDescription
positionX- 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
rotationRotation is specified in radians, and turns an object clockwise (0.0 - 2 * Math.PI)
angleAngle is an alias for rotation that is specified in degrees instead of radians (0.0 - 360.0)
pivotPoint the object rotates around, in pixels - also sets origin for child objects
alphaOpacity from 0.0 (fully transparent) to 1.0 (fully opaque), inherited by children
scaleScale is specified as a percent with 1.0 being 100% or actual-size, and can be set independently for the x and y axis
skewSkew transforms the object in x and y similar to the CSS skew() function, and is specified in radians
visibleWhether the object is visible or not, as a boolean value - prevents updating and rendering object and children
renderableWhether the object should be rendered - when false, object will still be updated, but won't be rendered, doesn't affect children
+ + \ No newline at end of file diff --git a/7.x/guides/components/graphics.html b/7.x/guides/components/graphics.html index fb75303a9..e93773dad 100644 --- a/7.x/guides/components/graphics.html +++ b/7.x/guides/components/graphics.html @@ -9,13 +9,13 @@ - - + +
-

Graphics

Graphics 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.

Check out the graphics example code.

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:

// 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);

// 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???

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.

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.

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:

  • Line
  • Rect
  • RoundRect
  • Circle
  • Ellipse
  • Arc
  • Bezier and Quadratic Curve

In addition, the Graphics Extras package (@pixi/graphics-extras) optionally includes the following complex primitives:

  • Torus
  • Chamfer Rect
  • Fillet Rect
  • Regular Polygon
  • Star
  • Rounded Polygon

The Geometry List

Inside every Graphics object is a GraphicsGeometry object. The GraphicsGeometry 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.

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:

// Create a master graphics object
let template = new PIXI.Graphics();
// Add a circle
template.drawCircle(100, 100, 50);

// 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);
}

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.

Graphics For Display

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.

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.

Check out the masking example code.

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.

Memory Leaks: The first has already been mentioned - 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.

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.

- - +
Version: v7.x

Graphics

Graphics 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.

Check out the graphics example code.

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:

// 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);

// 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???

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.

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.

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:

  • Line
  • Rect
  • RoundRect
  • Circle
  • Ellipse
  • Arc
  • Bezier and Quadratic Curve

In addition, the Graphics Extras package (@pixi/graphics-extras) optionally includes the following complex primitives:

  • Torus
  • Chamfer Rect
  • Fillet Rect
  • Regular Polygon
  • Star
  • Rounded Polygon

The Geometry List

Inside every Graphics object is a GraphicsGeometry object. The GraphicsGeometry 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.

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:

// Create a master graphics object
let template = new PIXI.Graphics();
// Add a circle
template.drawCircle(100, 100, 50);

// 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);
}

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.

Graphics For Display

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.

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.

Check out the masking example code.

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.

Memory Leaks: The first has already been mentioned - 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.

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.

+ + \ No newline at end of file diff --git a/7.x/guides/components/interaction.html b/7.x/guides/components/interaction.html index 42a80ef8b..ca774c9e7 100644 --- a/7.x/guides/components/interaction.html +++ b/7.x/guides/components/interaction.html @@ -9,13 +9,13 @@ - - + +
-

Interaction

PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.

Event Modes

The new event-based system that replaced InteractionManager from v6 has expanded the definition of what a DisplayObject means to be interactive. With this we have introduced eventMode which allows you to control how an object responds to interaction events. This is similar to the interactive property in v6 but with more options.

eventModeDescription
noneIgnores all interaction events, similar to CSS's pointer-events: none, good optimization for non-interactive children
passiveDoes not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. If you want to be compatible with v6, set this as your default eventMode (see options in Renderer, Application, etc)
autoDoes not emit events and but is hit tested if parent is interactive. Same as interactive = false in v7
staticEmit events and is hit tested. Same as interaction = true in v7, useful for objects like buttons that do not move.
dynamicEmits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.

Event Types

PixiJS supports the following event types:

Event TypeDescription
pointercancelFired when a pointer device button is released outside the display object that initially registered a pointerdown.
pointerdownFired when a pointer device button is pressed on the display object.
pointerenterFired when a pointer device enters the display object.
pointerleaveFired when a pointer device leaves the display object.
pointermoveFired when a pointer device is moved while over the display object.
globalpointermoveFired when a pointer device is moved, regardless of hit-testing the current object.
pointeroutFired when a pointer device is moved off the display object.
pointeroverFired when a pointer device is moved onto the display object.
pointertapFired when a pointer device is tapped twice on the display object.
pointerupFired when a pointer device button is released over the display object.
pointerupoutsideFired when a pointer device button is released outside the display object that initially registered a pointerdown.
mousedown Fired when a mouse button is pressed on the display object.
mouseenterFired when the mouse cursor enters the display object.
mouseleaveFired when the mouse cursor leaves the display object.
mousemove Fired when the mouse cursor is moved while over the display object.
globalmousemoveFired when a mouse is moved, regardless of hit-testing the current object.
mouseout Fired when the mouse cursor is moved off the display object.
mouseover Fired when the mouse cursor is moved onto the display object.
mouseup Fired when a mouse button is released over the display object.
mouseupoutside Fired when a mouse button is released outside the display object that initially registered a mousedown.
click Fired when a mouse button is clicked (pressed and released) over the display object.
touchcancel Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchend Fired when a touch point is removed from the display object.
touchendoutside Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchmove Fired when a touch point is moved along the display object.
globaltouchmoveFired when a touch point is moved, regardless of hit-testing the current object.
touchstart Fired when a touch point is placed on the display object.
tap Fired when a touch point is tapped twice on the display object.
wheel Fired when a mouse wheel is spun over the display object.
rightclick Fired when a right mouse button is clicked (pressed and released) over the display object.
rightdown Fired when a right mouse button is pressed on the display object.
rightup Fired when a right mouse button is released over the display object.
rightupoutside Fired when a right mouse button is released outside the display object that initially registered a rightdown.

Enabling Interaction

Any DisplayObject-derived object (Sprite, Container, etc.) can become interactive simply by setting its eventMode property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior.

Check out the interaction example code.

To respond to clicks and taps, bind to the events fired on the object, like so:

let sprite = PIXI.Sprite.from('/some/texture.png');
sprite.on('pointerdown', (event) => { alert('clicked!'); });
sprite.eventMode = 'static';

Check out the DisplayObject for the list of interaction events supported.

Checking if Object is Interactive

You can check if an object is interactive by calling the isInteractive property. This will return true if eventMode is set to static or dynamic.

if (sprite.isInteractive()) {
// sprite is interactive
}

Use Pointer Events

PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for both.

What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with either mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events.

Optimization

Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named interactiveChildren. If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to false and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set background.interactiveChildren = false for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain.

The EventSystem can also be customised to be more performant:

const app = new PIXI.Application({
/**
* by default we use `auto` for backwards compatibility.
* However `passive` is more performant and will be used by default in the future,
*/
eventMode: 'passive',
eventFeatures: {
move: true,
/** disables the global move events which can be very expensive in large scenes */
globalMove: false,
click: true,
wheel: true,
}
});
- - +
Version: v7.x

Interaction

PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.

Event Modes

The new event-based system that replaced InteractionManager from v6 has expanded the definition of what a DisplayObject means to be interactive. With this we have introduced eventMode which allows you to control how an object responds to interaction events. This is similar to the interactive property in v6 but with more options.

eventModeDescription
noneIgnores all interaction events, similar to CSS's pointer-events: none, good optimization for non-interactive children
passiveDoes not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. If you want to be compatible with v6, set this as your default eventMode (see options in Renderer, Application, etc)
autoDoes not emit events and but is hit tested if parent is interactive. Same as interactive = false in v7
staticEmit events and is hit tested. Same as interaction = true in v7, useful for objects like buttons that do not move.
dynamicEmits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.

Event Types

PixiJS supports the following event types:

Event TypeDescription
pointercancelFired when a pointer device button is released outside the display object that initially registered a pointerdown.
pointerdownFired when a pointer device button is pressed on the display object.
pointerenterFired when a pointer device enters the display object.
pointerleaveFired when a pointer device leaves the display object.
pointermoveFired when a pointer device is moved while over the display object.
globalpointermoveFired when a pointer device is moved, regardless of hit-testing the current object.
pointeroutFired when a pointer device is moved off the display object.
pointeroverFired when a pointer device is moved onto the display object.
pointertapFired when a pointer device is tapped twice on the display object.
pointerupFired when a pointer device button is released over the display object.
pointerupoutsideFired when a pointer device button is released outside the display object that initially registered a pointerdown.
mousedown Fired when a mouse button is pressed on the display object.
mouseenterFired when the mouse cursor enters the display object.
mouseleaveFired when the mouse cursor leaves the display object.
mousemove Fired when the mouse cursor is moved while over the display object.
globalmousemoveFired when a mouse is moved, regardless of hit-testing the current object.
mouseout Fired when the mouse cursor is moved off the display object.
mouseover Fired when the mouse cursor is moved onto the display object.
mouseup Fired when a mouse button is released over the display object.
mouseupoutside Fired when a mouse button is released outside the display object that initially registered a mousedown.
click Fired when a mouse button is clicked (pressed and released) over the display object.
touchcancel Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchend Fired when a touch point is removed from the display object.
touchendoutside Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchmove Fired when a touch point is moved along the display object.
globaltouchmoveFired when a touch point is moved, regardless of hit-testing the current object.
touchstart Fired when a touch point is placed on the display object.
tap Fired when a touch point is tapped twice on the display object.
wheel Fired when a mouse wheel is spun over the display object.
rightclick Fired when a right mouse button is clicked (pressed and released) over the display object.
rightdown Fired when a right mouse button is pressed on the display object.
rightup Fired when a right mouse button is released over the display object.
rightupoutside Fired when a right mouse button is released outside the display object that initially registered a rightdown.

Enabling Interaction

Any DisplayObject-derived object (Sprite, Container, etc.) can become interactive simply by setting its eventMode property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior.

Check out the interaction example code.

To respond to clicks and taps, bind to the events fired on the object, like so:

let sprite = PIXI.Sprite.from('/some/texture.png');
sprite.on('pointerdown', (event) => { alert('clicked!'); });
sprite.eventMode = 'static';

Check out the DisplayObject for the list of interaction events supported.

Checking if Object is Interactive

You can check if an object is interactive by calling the isInteractive property. This will return true if eventMode is set to static or dynamic.

if (sprite.isInteractive()) {
// sprite is interactive
}

Use Pointer Events

PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for both.

What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with either mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events.

Optimization

Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named interactiveChildren. If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to false and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set background.interactiveChildren = false for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain.

The EventSystem can also be customised to be more performant:

const app = new PIXI.Application({
/**
* by default we use `auto` for backwards compatibility.
* However `passive` is more performant and will be used by default in the future,
*/
eventMode: 'passive',
eventFeatures: {
move: true,
/** disables the global move events which can be very expensive in large scenes */
globalMove: false,
click: true,
wheel: true,
}
});
+ + \ No newline at end of file diff --git a/7.x/guides/components/sprite-sheets.html b/7.x/guides/components/sprite-sheets.html index 7e7a664dc..0ed5d9d59 100644 --- a/7.x/guides/components/sprite-sheets.html +++ b/7.x/guides/components/sprite-sheets.html @@ -9,13 +9,13 @@ - - + +
-

Spritesheets

Now that you understand basic sprites, it's time to talk about a better way to create them - the Spritesheet class.

A Spritesheet is a media format for more efficiently downloading and rendering Sprites. While somewhat more complex to create and use, they are a key tool in optimizing your project.

Anatomy of a Spritesheet

The basic idea of a spritesheet is to pack a series of images together into a single image, track where each source image ends up, and use that combined image as a shared BaseTexture for the resulting Sprites.

The first step is to collect the images you want to combine. The sprite packer then collects the images, and creates a new combined image.

As this image is being created, the tool building it keeps track of the location of the rectangle where each source image is stored. It then writes out a JSON file with that information.

These two files, in combination, can be passed into a SpriteSheet constructor. The SpriteSheet object then parses the JSON, and creates a series of Texture objects, one for each source image, setting the source rectangle for each based on the JSON data. Each texture uses the same shared BaseTexture as its source.

Doubly Efficient

SpriteSheets help your project in two ways.

First, by speeding up the loading process. While downloading a SpriteSheet's texture requires moving the same (or even slightly more!) number of bytes, they're grouped into a single file. This means that the user's browser can request and download far fewer files for the same number of Sprites. The number of files itself is a key driver of download speed, because each request requires a round-trip to the webserver, and browsers are limited to how many files they can download simultaneously. Converting a project from individual source images to shared sprite sheets can cut your download time in half, at no cost in quality.

Second, by improving batch rendering. WebGL rendering speed scales roughly with the number of draw calls made. Batching multiple Sprites, etc. into a single draw call is the main secret to how PixiJS can run so blazingly fast. Maximizing batching is a complex topic, but when multiple Sprites all share a common BaseTexture, it makes it more likely that they can be batched together and rendered in a single call.

Creating SpriteSheets

You can use a 3rd party tool to assemble your sprite sheet files. Here are two that may fit your needs:

ShoeBox: ShoeBox is a free, Adobe AIR-based sprite packing utility that is great for small projects or learning how SpriteSheets work.

TexturePacker: TexturePacker is a more polished tool that supports advanced features and workflows. A free version is available which has all the necessary features for packing spritesheets for PixiJS. It's a good fit for larger projects and professional game development, or projects that need more complex tile mapping features.

Spritesheet data can also be created manually or programmatically, and supplied to a new AnimatedSprite. This may be an easier option if your sprites are already contained in a single image.

// Create object to store sprite sheet data
const atlasData = {
frames: {
enemy1: {
frame: { x: 0, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
enemy2: {
frame: { x: 32, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
},
meta: {
image: 'images/spritesheet.png',
format: 'RGBA8888',
size: { w: 128, h: 32 },
scale: 1
},
animations: {
enemy: ['enemy1','enemy2'] //array of frames by name
}
}


// Create the SpriteSheet from data and image
const spritesheet = new PIXI.Spritesheet(
PIXI.BaseTexture.from(atlasData.meta.image),
atlasData
);

// Generate all the Textures asynchronously
await spritesheet.parse();

// spritesheet is ready to use!
const anim = new PIXI.AnimatedSprite(spritesheet.animations.enemy);

// set the animation speed
anim.animationSpeed = 0.1666;
// play the animation on a loop
anim.play();
// add it to the stage to render
app.stage.addChild(anim);
- - +
Version: v7.x

Spritesheets

Now that you understand basic sprites, it's time to talk about a better way to create them - the Spritesheet class.

A Spritesheet is a media format for more efficiently downloading and rendering Sprites. While somewhat more complex to create and use, they are a key tool in optimizing your project.

Anatomy of a Spritesheet

The basic idea of a spritesheet is to pack a series of images together into a single image, track where each source image ends up, and use that combined image as a shared BaseTexture for the resulting Sprites.

The first step is to collect the images you want to combine. The sprite packer then collects the images, and creates a new combined image.

As this image is being created, the tool building it keeps track of the location of the rectangle where each source image is stored. It then writes out a JSON file with that information.

These two files, in combination, can be passed into a SpriteSheet constructor. The SpriteSheet object then parses the JSON, and creates a series of Texture objects, one for each source image, setting the source rectangle for each based on the JSON data. Each texture uses the same shared BaseTexture as its source.

Doubly Efficient

SpriteSheets help your project in two ways.

First, by speeding up the loading process. While downloading a SpriteSheet's texture requires moving the same (or even slightly more!) number of bytes, they're grouped into a single file. This means that the user's browser can request and download far fewer files for the same number of Sprites. The number of files itself is a key driver of download speed, because each request requires a round-trip to the webserver, and browsers are limited to how many files they can download simultaneously. Converting a project from individual source images to shared sprite sheets can cut your download time in half, at no cost in quality.

Second, by improving batch rendering. WebGL rendering speed scales roughly with the number of draw calls made. Batching multiple Sprites, etc. into a single draw call is the main secret to how PixiJS can run so blazingly fast. Maximizing batching is a complex topic, but when multiple Sprites all share a common BaseTexture, it makes it more likely that they can be batched together and rendered in a single call.

Creating SpriteSheets

You can use a 3rd party tool to assemble your sprite sheet files. Here are two that may fit your needs:

ShoeBox: ShoeBox is a free, Adobe AIR-based sprite packing utility that is great for small projects or learning how SpriteSheets work.

TexturePacker: TexturePacker is a more polished tool that supports advanced features and workflows. A free version is available which has all the necessary features for packing spritesheets for PixiJS. It's a good fit for larger projects and professional game development, or projects that need more complex tile mapping features.

Spritesheet data can also be created manually or programmatically, and supplied to a new AnimatedSprite. This may be an easier option if your sprites are already contained in a single image.

// Create object to store sprite sheet data
const atlasData = {
frames: {
enemy1: {
frame: { x: 0, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
enemy2: {
frame: { x: 32, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
},
meta: {
image: 'images/spritesheet.png',
format: 'RGBA8888',
size: { w: 128, h: 32 },
scale: 1
},
animations: {
enemy: ['enemy1','enemy2'] //array of frames by name
}
}


// Create the SpriteSheet from data and image
const spritesheet = new PIXI.Spritesheet(
PIXI.BaseTexture.from(atlasData.meta.image),
atlasData
);

// Generate all the Textures asynchronously
await spritesheet.parse();

// spritesheet is ready to use!
const anim = new PIXI.AnimatedSprite(spritesheet.animations.enemy);

// set the animation speed
anim.animationSpeed = 0.1666;
// play the animation on a loop
anim.play();
// add it to the stage to render
app.stage.addChild(anim);
+ + \ No newline at end of file diff --git a/7.x/guides/components/sprites.html b/7.x/guides/components/sprites.html index e0cc3c902..0ebffe0b6 100644 --- a/7.x/guides/components/sprites.html +++ b/7.x/guides/components/sprites.html @@ -9,13 +9,13 @@ - - + +
-

Sprites

Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.

Creating Sprites

To create a Sprite, all you need is a Texture (check out the Texture guide). Load a PNG's URL using the PIXI.Loader class, then call PIXI.Sprite.from(url) and you're all set. As a convenience during prototyping, you can pass a non-loaded URL to from() and PixiJS will handle it, but your sprite will "pop in" after it loads if you don't pre-load your textures.

Check out the sprite example code.

Using Sprites

In our DisplayObject guide, we learned about the DisplayObject class and the various properties it defines. Since Sprite objects are also display objects, you can move a sprite, rotate it, and update any other display property.

Alpha, Tint and Blend Modes

Alpha is a standard display object property. You can use it to fade sprites into the scene by animating each sprite's alpha from 0.0 to 1.0 over a period of time.

Tinting allows you multiply the color value of every pixel by a single color. For example, if you had a dungeon game, you might show a character's poison status by setting obj.tint = 0x00FF00, which would give a green tint to the character.

Blend modes change how pixel colors are added to the screen when rendering. The three main modes are add, which adds each pixel's RGB channels to whatever is under your sprite (useful for glows and lighting), multiply which works like tint, but on a per-pixel basis, and screen, which overlays the pixels, brightening whatever is underneath them.

Scale vs Width & Height

One common area of confusion when working with sprites lies in scaling and dimensions. The PIXI.DisplayObject class allows you to set the x and y scale for any object. Sprites, being DisplayObjects, also support scaling. In addition, however, Sprites support explicit width and height attributes that can be used to achieve the same effect, but are in pixels instead of a percentage. This works because a Sprite object owns a Texture, which has an explicit width and height. When you set a Sprite's width, internally PixiJS converts that width into a percentage of the underlying texture's width and updates the object's x-scale. So width and height are really just convenience methods for changing scale, based on pixel dimensions rather than percentages.

Pivot vs Anchor

If you add a sprite to your stage and rotate it, it will by default rotate around the top-left corner of the image. In some cases, this is what you want. In many cases, however, what you want is for the sprite to rotate around the center of the image it contains, or around an arbitrary point.

There are two ways to achieve this: pivots and anchors

An object's pivot is an offset, expressed in pixels, from the top-left corner of the Sprite. It defaults to (0, 0). If you have a Sprite whose texture is 100px x 50px, and want to set the pivot point to the center of the image, you'd set your pivot to (50, 25) - half the width, and half the height. Note that pivots can be set outside of the image, meaning the pivot may be less than zero or greater than the width/height. This can be useful in setting up complex animation hierarchies, for example. Every DisplayObject has a pivot.

An anchor, in contrast, is only available for Sprites. Anchors are specified in percentages, from 0.0 to 1.0, in each dimension. To rotate around the center point of a texture using anchors, you'd set your Sprite's anchor to (0.5, 0.5) - 50% in width and height. While less common, anchors can also be outside the standard 0.0 - 1.0 range.

The nice thing about anchors is that they are resolution and dimension agnostic. If you set your Sprite to be anchored in the middle then later change the size of the texture, your object will still rotate correctly. If you had instead set a pivot using pixel-based calculations, changing the texture size would require changing your pivot point.

So, generally speaking, you'll want to use anchors when working with Sprites.

One final note: unlike CSS, where setting the transform-origin of the image doesn't move it, in PixiJS setting an anchor or pivot will move your object on the screen. In other words, setting an anchor or pivot affects not just the rotation origin, but also the position of the sprite relative to its parent.

- - +
Version: v7.x

Sprites

Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.

Creating Sprites

To create a Sprite, all you need is a Texture (check out the Texture guide). Load a PNG's URL using the PIXI.Loader class, then call PIXI.Sprite.from(url) and you're all set. As a convenience during prototyping, you can pass a non-loaded URL to from() and PixiJS will handle it, but your sprite will "pop in" after it loads if you don't pre-load your textures.

Check out the sprite example code.

Using Sprites

In our DisplayObject guide, we learned about the DisplayObject class and the various properties it defines. Since Sprite objects are also display objects, you can move a sprite, rotate it, and update any other display property.

Alpha, Tint and Blend Modes

Alpha is a standard display object property. You can use it to fade sprites into the scene by animating each sprite's alpha from 0.0 to 1.0 over a period of time.

Tinting allows you multiply the color value of every pixel by a single color. For example, if you had a dungeon game, you might show a character's poison status by setting obj.tint = 0x00FF00, which would give a green tint to the character.

Blend modes change how pixel colors are added to the screen when rendering. The three main modes are add, which adds each pixel's RGB channels to whatever is under your sprite (useful for glows and lighting), multiply which works like tint, but on a per-pixel basis, and screen, which overlays the pixels, brightening whatever is underneath them.

Scale vs Width & Height

One common area of confusion when working with sprites lies in scaling and dimensions. The PIXI.DisplayObject class allows you to set the x and y scale for any object. Sprites, being DisplayObjects, also support scaling. In addition, however, Sprites support explicit width and height attributes that can be used to achieve the same effect, but are in pixels instead of a percentage. This works because a Sprite object owns a Texture, which has an explicit width and height. When you set a Sprite's width, internally PixiJS converts that width into a percentage of the underlying texture's width and updates the object's x-scale. So width and height are really just convenience methods for changing scale, based on pixel dimensions rather than percentages.

Pivot vs Anchor

If you add a sprite to your stage and rotate it, it will by default rotate around the top-left corner of the image. In some cases, this is what you want. In many cases, however, what you want is for the sprite to rotate around the center of the image it contains, or around an arbitrary point.

There are two ways to achieve this: pivots and anchors

An object's pivot is an offset, expressed in pixels, from the top-left corner of the Sprite. It defaults to (0, 0). If you have a Sprite whose texture is 100px x 50px, and want to set the pivot point to the center of the image, you'd set your pivot to (50, 25) - half the width, and half the height. Note that pivots can be set outside of the image, meaning the pivot may be less than zero or greater than the width/height. This can be useful in setting up complex animation hierarchies, for example. Every DisplayObject has a pivot.

An anchor, in contrast, is only available for Sprites. Anchors are specified in percentages, from 0.0 to 1.0, in each dimension. To rotate around the center point of a texture using anchors, you'd set your Sprite's anchor to (0.5, 0.5) - 50% in width and height. While less common, anchors can also be outside the standard 0.0 - 1.0 range.

The nice thing about anchors is that they are resolution and dimension agnostic. If you set your Sprite to be anchored in the middle then later change the size of the texture, your object will still rotate correctly. If you had instead set a pivot using pixel-based calculations, changing the texture size would require changing your pivot point.

So, generally speaking, you'll want to use anchors when working with Sprites.

One final note: unlike CSS, where setting the transform-origin of the image doesn't move it, in PixiJS setting an anchor or pivot will move your object on the screen. In other words, setting an anchor or pivot affects not just the rotation origin, but also the position of the sprite relative to its parent.

+ + \ No newline at end of file diff --git a/7.x/guides/components/text.html b/7.x/guides/components/text.html index e507d54f1..93f6f3ce4 100644 --- a/7.x/guides/components/text.html +++ b/7.x/guides/components/text.html @@ -9,13 +9,13 @@ - - + +
-

Text

Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.

Let's dig into how this works.

There Are Two Kinds of Text

Because of the challenges of working with text in WebGL, PixiJS provides two very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance.

The Text Object

In order to draw text to the screen, you use a Text object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high.

So when working with PIXI.Text objects, there are two sets of options - standard display object options like position, rotation, etc that work after the text is rasterized internally, and text style options that are used while rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled.

Check out the text example code.

Text Styles

There are a lot of text style options available (see TextStyle), but they break down into 5 main groups:

Font: fontFamily to select the webfont to use, fontSize to specify the size of the text to draw, along with options for font weight, style and variant.

Appearance: Set the color with fill or add a stroke outline, including options for gradient fills.

Drop-Shadows: Set a drop-shadow with dropShadow, with a host of related options to specify offset, blur, opacity, etc.

Layout: Enable with wordWrap and wordWrapWidth, and then customize the lineHeight and align or letterSpacing

Utilities: Add padding or trim extra space to deal with funky font families if needed.

To interactively test out feature of Text Style, check out this tool.

Loading and Using Fonts

In order for PixiJS to build a PIXI.Text object, you'll need to make sure that the font you want to use is loaded by the browser. Unfortunately, at the time of writing, the PIXI.Loader system does not support loading font files, so you'll need to use a 3rd party font loader to ensure that any custom web fonts you want to use are pre-loaded. It's not enough to add an @font-face declaration in your project's CSS because browsers will happily render text using a fallback font while your custom font loads.

Any javascript library that can load a web font will work, you just want something that will delay starting your project until the font has been fully loaded by the browser.

One such library is FontFaceObserver. Here's a simple example that shows how to use it to ensure the web font "Short Stack" is loaded before your app starts. First, we need a font-face declaration in CSS:

@font-face {
font-family: Short Stack;
src: url(short-stack.woff2) format('woff2'),
url(short-stack.woff) format('woff');
}

Now that the browser knows what our font is and how to find the source files, it's time to use the library to load them:

// Create the loader
let font = new FontFaceObserver('Short Stack', {});
// Start loading the font
font.load().then(() => {
// Successful load, start up your PixiJS app as usual
let app = new PIXI.Application({ width: 640, height: 360 });
document.body.appendChild(app.view);
// ... etc ...

}, () => {
// Failed load, log the error or display a message to the user
alert('Unable to load required font!');
});

Caveats and Gotchas

While PixiJS does make working with text easy, there are a few things you need to watch out for.

First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a PIXI.BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes.

Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp.

BitmapText

In addition to the standard PIXI.Text approach to adding text to your project, PixiJS also supports bitmap fonts. Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image.

The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture.

Check out the bitmap text example code.

BitmapFont

  • 3rd party solutions
  • BitmapFont.from auto-generation

Selecting the Right Approach

PIXI.Text

  • Static text
  • Small number of text objects
  • High fidelity text rendering (kerning e.g.)
  • Text layout (line & letter spacing)

PIXI.BitmapText

  • Dynamic text
  • Large number of text objects
  • Lower memory
- - +
Version: v7.x

Text

Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.

Let's dig into how this works.

There Are Two Kinds of Text

Because of the challenges of working with text in WebGL, PixiJS provides two very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance.

The Text Object

In order to draw text to the screen, you use a Text object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high.

So when working with PIXI.Text objects, there are two sets of options - standard display object options like position, rotation, etc that work after the text is rasterized internally, and text style options that are used while rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled.

Check out the text example code.

Text Styles

There are a lot of text style options available (see TextStyle), but they break down into 5 main groups:

Font: fontFamily to select the webfont to use, fontSize to specify the size of the text to draw, along with options for font weight, style and variant.

Appearance: Set the color with fill or add a stroke outline, including options for gradient fills.

Drop-Shadows: Set a drop-shadow with dropShadow, with a host of related options to specify offset, blur, opacity, etc.

Layout: Enable with wordWrap and wordWrapWidth, and then customize the lineHeight and align or letterSpacing

Utilities: Add padding or trim extra space to deal with funky font families if needed.

To interactively test out feature of Text Style, check out this tool.

Loading and Using Fonts

In order for PixiJS to build a PIXI.Text object, you'll need to make sure that the font you want to use is loaded by the browser. Unfortunately, at the time of writing, the PIXI.Loader system does not support loading font files, so you'll need to use a 3rd party font loader to ensure that any custom web fonts you want to use are pre-loaded. It's not enough to add an @font-face declaration in your project's CSS because browsers will happily render text using a fallback font while your custom font loads.

Any javascript library that can load a web font will work, you just want something that will delay starting your project until the font has been fully loaded by the browser.

One such library is FontFaceObserver. Here's a simple example that shows how to use it to ensure the web font "Short Stack" is loaded before your app starts. First, we need a font-face declaration in CSS:

@font-face {
font-family: Short Stack;
src: url(short-stack.woff2) format('woff2'),
url(short-stack.woff) format('woff');
}

Now that the browser knows what our font is and how to find the source files, it's time to use the library to load them:

// Create the loader
let font = new FontFaceObserver('Short Stack', {});
// Start loading the font
font.load().then(() => {
// Successful load, start up your PixiJS app as usual
let app = new PIXI.Application({ width: 640, height: 360 });
document.body.appendChild(app.view);
// ... etc ...

}, () => {
// Failed load, log the error or display a message to the user
alert('Unable to load required font!');
});

Caveats and Gotchas

While PixiJS does make working with text easy, there are a few things you need to watch out for.

First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a PIXI.BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes.

Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp.

BitmapText

In addition to the standard PIXI.Text approach to adding text to your project, PixiJS also supports bitmap fonts. Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image.

The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture.

Check out the bitmap text example code.

BitmapFont

  • 3rd party solutions
  • BitmapFont.from auto-generation

Selecting the Right Approach

PIXI.Text

  • Static text
  • Small number of text objects
  • High fidelity text rendering (kerning e.g.)
  • Text layout (line & letter spacing)

PIXI.BitmapText

  • Dynamic text
  • Large number of text objects
  • Lower memory
+ + \ No newline at end of file diff --git a/7.x/guides/components/textures.html b/7.x/guides/components/textures.html index 4483c1e0a..3eb6fb1fe 100644 --- a/7.x/guides/components/textures.html +++ b/7.x/guides/components/textures.html @@ -9,13 +9,13 @@ - - + +
-

Textures

We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures.

In PixiJS, textures are one of the core resources used by display objects. A texture, broadly speaking, represents a source of pixels to be used to fill in an area on the screen. The simplest example is a sprite - a rectangle that is completely filled with a single texture. But things can get much more complex.

Life-cycle of a Texture

Let's examine how textures really work, by following the path your image data travels on its way to the screen.

Here's the flow we're going to follow: Source Image > Loader > BaseTexture > Texture

Serving the Image

To start with, you have the image you want to display. The first step is to make it available on your server. This may seem obvious, but if you're coming to PixiJS from other game development systems, it's worth remembering that everything has to be loaded over the network. If you're developing locally, please be aware that you must use a webserver to test, or your images won't load due to how browsers treat local file security.

Loading the Image

To work with the image, the first step is to pull the image file from your webserver into the user's web browser. To do this, we can use PIXI.Texture.from(), which works for quick demos, but in production you'll use the Loader class. A Loader wraps and manages using an <IMG> element to tell the browser to fetch the image, and then notifies you when that has been completed. This process is asynchronous - you request the load, then time passes, then an event fires to let you know the load is completed. We'll go into the loader in a lot more depth in a later guide.

BaseTextures Own the Data

Once the Loader has done its work, the loaded <IMG> element contains the pixel data we need. But to use it to render something, PixiJS has to take that raw image file and upload it to the GPU. This brings us to the real workhorse of the texture system - the BaseTexture class. Each BaseTexture manages a single pixel source - usually an image, but can also be a Canvas or Video element. BaseTextures allow PixiJS to convert the image to pixels and use those pixels in rendering. In addition, it also contains settings that control how the texture data is rendered, such as the wrap mode (for UV coordinates outside the 0.0-1.0 range) and scale mode (used when scaling a texture).

BaseTextures are automatically cached, so that calling PIXI.Texture.from() repeatedly for the same URL returns the same BaseTexture each time. Destroying a BaseTexture frees the image data associated with it.

Textures are a View on BaseTextures

So finally, we get to the PIXI.Texture class itself! At this point, you may be wondering what the Texture object does. After all, the BaseTexture manages the pixels and render settings. And the answer is, it doesn't do very much. Textures are light-weight views on an underlying BaseTexture. Their main attribute is the source rectangle within the BaseTexture from which to pull.

If all PixiJS drew were sprites, that would be pretty redundant. But consider SpriteSheets. A SpriteSheet is a single image that contains multiple sprite images arranged within. In a Spritesheet object, a single BaseTexture is referenced by a set of Textures, one for each source image in the original sprite sheet. By sharing a single BaseTexture, the browser only downloads one file, and our batching renderer can blaze through drawing sprites since they all share the same underlying pixel data. The SpriteSheet's Textures pull out just the rectangle of pixels needed by each sprite.

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.

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.

Instead, 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

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 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.

Beyond Images

As we alluded to above, you can make a Texture out of more than just images:

Video: Pass an HTML5 <VIDEO> element to PIXI.BaseTexture.from() to allow you to display video in your project. Since it's a texture, you can tint it, add filters, or even apply it to custom geometry.

Canvas: Similarly, you can wrap an HTML5 <CANVAS> element in a BaseTexture to let you use canvas's drawing methods to dynamically create a texture.

SVG: Pass in an <SVG> element or load a .svg URL, and PixiJS will attempt to rasterize it. For highly network-constrained projects, this can allow for beautiful graphics with minimal network load times.

RenderTexture: A more advanced (but very powerful!) feature is to build a Texture from a RenderTexture. This can allow for building complex geometry using a Geometry object, then baking that geometry down to a simple texture.

Each of these texture sources has caveats and nuances that we can't cover in this guide, but they should give you a feeling for the power of PixiJS's texture system.

Check out the render texture example code.

- - +
Version: v7.x

Textures

We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures.

In PixiJS, textures are one of the core resources used by display objects. A texture, broadly speaking, represents a source of pixels to be used to fill in an area on the screen. The simplest example is a sprite - a rectangle that is completely filled with a single texture. But things can get much more complex.

Life-cycle of a Texture

Let's examine how textures really work, by following the path your image data travels on its way to the screen.

Here's the flow we're going to follow: Source Image > Loader > BaseTexture > Texture

Serving the Image

To start with, you have the image you want to display. The first step is to make it available on your server. This may seem obvious, but if you're coming to PixiJS from other game development systems, it's worth remembering that everything has to be loaded over the network. If you're developing locally, please be aware that you must use a webserver to test, or your images won't load due to how browsers treat local file security.

Loading the Image

To work with the image, the first step is to pull the image file from your webserver into the user's web browser. To do this, we can use PIXI.Texture.from(), which works for quick demos, but in production you'll use the Loader class. A Loader wraps and manages using an <IMG> element to tell the browser to fetch the image, and then notifies you when that has been completed. This process is asynchronous - you request the load, then time passes, then an event fires to let you know the load is completed. We'll go into the loader in a lot more depth in a later guide.

BaseTextures Own the Data

Once the Loader has done its work, the loaded <IMG> element contains the pixel data we need. But to use it to render something, PixiJS has to take that raw image file and upload it to the GPU. This brings us to the real workhorse of the texture system - the BaseTexture class. Each BaseTexture manages a single pixel source - usually an image, but can also be a Canvas or Video element. BaseTextures allow PixiJS to convert the image to pixels and use those pixels in rendering. In addition, it also contains settings that control how the texture data is rendered, such as the wrap mode (for UV coordinates outside the 0.0-1.0 range) and scale mode (used when scaling a texture).

BaseTextures are automatically cached, so that calling PIXI.Texture.from() repeatedly for the same URL returns the same BaseTexture each time. Destroying a BaseTexture frees the image data associated with it.

Textures are a View on BaseTextures

So finally, we get to the PIXI.Texture class itself! At this point, you may be wondering what the Texture object does. After all, the BaseTexture manages the pixels and render settings. And the answer is, it doesn't do very much. Textures are light-weight views on an underlying BaseTexture. Their main attribute is the source rectangle within the BaseTexture from which to pull.

If all PixiJS drew were sprites, that would be pretty redundant. But consider SpriteSheets. A SpriteSheet is a single image that contains multiple sprite images arranged within. In a Spritesheet object, a single BaseTexture is referenced by a set of Textures, one for each source image in the original sprite sheet. By sharing a single BaseTexture, the browser only downloads one file, and our batching renderer can blaze through drawing sprites since they all share the same underlying pixel data. The SpriteSheet's Textures pull out just the rectangle of pixels needed by each sprite.

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.

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.

Instead, 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

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 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.

Beyond Images

As we alluded to above, you can make a Texture out of more than just images:

Video: Pass an HTML5 <VIDEO> element to PIXI.BaseTexture.from() to allow you to display video in your project. Since it's a texture, you can tint it, add filters, or even apply it to custom geometry.

Canvas: Similarly, you can wrap an HTML5 <CANVAS> element in a BaseTexture to let you use canvas's drawing methods to dynamically create a texture.

SVG: Pass in an <SVG> element or load a .svg URL, and PixiJS will attempt to rasterize it. For highly network-constrained projects, this can allow for beautiful graphics with minimal network load times.

RenderTexture: A more advanced (but very powerful!) feature is to build a Texture from a RenderTexture. This can allow for building complex geometry using a Geometry object, then baking that geometry down to a simple texture.

Each of these texture sources has caveats and nuances that we can't cover in this guide, but they should give you a feeling for the power of PixiJS's texture system.

Check out the render texture example code.

+ + \ No newline at end of file diff --git a/7.x/guides/migrations/upgrading.html b/7.x/guides/migrations/upgrading.html index 8f10f3757..db4f1f613 100644 --- a/7.x/guides/migrations/upgrading.html +++ b/7.x/guides/migrations/upgrading.html @@ -9,13 +9,13 @@ - - + +
-

Upgrading PixiJS

PixiJS uses a lot of peerDependencies internally to define the relationship between packages. This has created unpredictable errors because of how npm resolves peers when bumping/upgrading (e.g., #8382, #8268, #8144, #7209).

When you're upgrading using npm please completely uninstall instead of just changing the version in your package.json:

npm uninstall pixi.js
npm install pixi.js

If you are using any PixiJS community plugins, please make sure to uninstall those too:

npm uninstall pixi.js @pixi/particle-emitter @pixi/sound
npm install pixi.js @pixi/particle-emitter @pixi/sound
- - +
Version: v7.x

Upgrading PixiJS

PixiJS uses a lot of peerDependencies internally to define the relationship between packages. This has created unpredictable errors because of how npm resolves peers when bumping/upgrading (e.g., #8382, #8268, #8144, #7209).

When you're upgrading using npm please completely uninstall instead of just changing the version in your package.json:

npm uninstall pixi.js
npm install pixi.js

If you are using any PixiJS community plugins, please make sure to uninstall those too:

npm uninstall pixi.js @pixi/particle-emitter @pixi/sound
npm install pixi.js @pixi/particle-emitter @pixi/sound
+ + \ No newline at end of file diff --git a/7.x/guides/migrations/v5.html b/7.x/guides/migrations/v5.html index 8686a7fc2..f763f40de 100644 --- a/7.x/guides/migrations/v5.html +++ b/7.x/guides/migrations/v5.html @@ -9,13 +9,13 @@ - - + +
-

v5 Migration Guide

This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we've try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.

🚧 API Changes

Making WebGL First-Class

PixiJS v5 has made WebGL the first-class renderer and made CanvasRenderer to be second-class. Functionally, there's not much that changed from v4, but there are a bunch of subtle internal naming changes which could trip-up some developers upgrading to v5. For instance:

  • WebGLRenderer becomes Renderer
  • renderWebGL becomes render (in DisplayObject, Sprite, Container, etc)
  • _renderWebGL becomes _render (in DisplayObject, Container, etc)

If you created a plugin or project that previously used render on a Container (see #5510), this will probably cause your project to not render correctly. Please consider renaming your user-defined render to something else. In most other cases, you'll get a deprecation warning trying to invoke WebGL-related classes or methods, e.g., new PIXI.WebGLRenderer().

Renderer Parameters

Specifying options as a third parameter in Renderer constructor is officially dropped (same with PIXI.Application, PIXI.autoDetectRenderer & PIXI.CanvasRenderer). In v4 we supported two function signatures, but in v5 we dropped width, height, options signature. Please add width and height to options.

const renderer = new PIXI.Renderer(800, 600, { transparent: true }); // bad
const renderer = new PIXI.Renderer({ width: 800, height: 600, transparent: true }); // good
  • Note: Adding transparent: true in Renderer or Application constructor options might help with strange artifacts on some devices, but it might reduce FPS. It's much better than preserveDrawingBuffer: true.

  • If you need the v4 default behavior of resizing the canvas using css pixels, add autoDensity: true to the options.

Not everything went to params. To enable WebGL1 even if WebGL2 is available, use

PIXI.settings.PREFER_ENV = PIXI.ENV.WEBGL;

Mesh, Plane, Rope

PixiJS v5 introduces a new class called PIXI.Mesh. This allows overriding the default shader and the ability to add more attributes to geometry. For example, you can add colors to vertices.

The old v4 Mesh class has moved from PIXI.mesh.Mesh to PIXI.SimpleMesh, it extends PIXI.Mesh.

PIXI.mesh.Rope, PIXI.mesh.Plane, PIXI.mesh.NineSlicePlane have moved to PIXI.SimpleRope, PIXI.SimplePlane and PIXI.NineSlicePlane respectively.

If you used custom shaders or generated meshes in v4, you might be impacted by these changes in v5.

PIXI.SimpleMesh fields vertices, uvs, indices are wrapped inside mesh.geometry attribute buffers. For example, this is how access to buffers provided through mesh.uvBuffer property:

get uvBuffer()
{
return this.geometry.buffers[1];
}

The indices property shortcut is also missing, but you can access the data inside mesh.geometry.indexBuffer.

You can override buffer data, and notify it that data was changed, in this case buffer will be uploaded to GPU lazily. Previously in v4 mesh had several flags that indicated which attributes have to be updated and their names confused people.

Graphics Holes

Drawing holes in Graphics was very limited in v4. This only supported non-Shape drawing, like using lineTo, bezierCurveTo, etc. In v5, we improved the hole API by supporting shapes. Unfortunately, there's no deprecation strategy to support the v4 API. For instance, in v4:

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.moveTo(0, 0)
.lineTo(100, 0)
.lineTo(100, 100)
.lineTo(0, 100)
.moveTo(10, 10)
.lineTo(90, 10)
.lineTo(90, 90)
.lineTo(10, 90)
.addHole();

Live example in v4.x

In v5, Graphics has simplified and the API changed from addHole to beginHole and endHole.

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.drawRect(0, 0, 100, 100)
.beginHole()
.drawCircle(50, 50, 30)
.endHole();

Live example in dev

Filter Padding

In v4 filters had a default padding of 4 and in v5 this has been changed to a default of 0. This can cause some filters to look broken when used. To fix this issue simply add some padding to the filters you create.

// Glow filter from https://github.com/pixijs/pixi-filters
const filter = new PIXI.filters.GlowFilter();
filter.padding = 4;

Some filters, like BlurFilter, automatically calculate the padding so changes may not be necessary.

Filter Default Vertex Shader

We reorganized all uniforms dedicated to coordinate system transforms, and renamed them. If your filter doesn't work anymore, check if you use default vertex shader. In that case, you can use old v4 vertex shader code.

All changes are explained in [[Creating Filters|v5-Creating-filters]]

Enable Mipmapping for RenderTexture

Previously, you may have ended up with code like this in v4 (specifically if you saw Ivan's comment/JSFiddle):

const renderer = PIXI.autoDetectRenderer();
renderer.bindTexture(baseRenderTex, false, 0);
const glTex = baseRenderTex._glTextures[renderer.CONTEXT_UID];
glTex.enableMipmap(); // this is what actually generates mipmaps in WebGL
glTex.enableLinearScaling(); // this is what tells WebGL to USE those mipmaps

In v5, this code is no longer needed.

BaseTexture Resources

One of the newest features in v5 is that we decoupled all the asset-specific functionality from BaseTexture. We created a new system called "resources" and each BaseTexture now has a resource that wraps some specific asset type. For instance: VideoResource, SVGResource, ImageResource, CanvasResource. In the future, we hope to be able to add other resource types. If there were asset-specific methods or properties being called before, these will probably be on baseTexture.resource.

Also, we removed all of the from* methods from BaseTexture, so you just can call BaseTexture.from and pass in whatever resource. Please see docs for more information about from.

const canvas = document.createElement('canvas');
const baseTexture = PIXI.BaseTexture.from(canvas);

That API also allows to use pure WebGL and 2d context calls, see the gradient example.

BaseTexture.source

Has been moved to baseTexture.resource.source, moved into resource corresponding to the baseTexture. baseTexture.resource does not exist for RenderTexture, and source does not exist for resources that dont have source.

Graphics Interaction

If you use transparent interactive graphics trick, make sure that you use specify alpha=0 for all element, not for its parts. How PixiJS deals with shapes that have alpha=0 is considered undefined behaviour. We might change it back, but we have no guarantees about it.

graphics.beginFill(0xffffff, 0.0); //bad
graphics.alpha = 0; //good

📦 Publishing Changes

Canvas Becomes Legacy

Since WebGL and WebGL2 are now first-class, we have removed the canvas-based fallback from the default pixi.js package. If you need CanvasRenderer, you should switch to use pixi.js-legacy instead.

import * as PIXI from "pixi.js";
// Will NOT return CanvasRenderer because canvas-based
// functionality was removed from "pixi.js"
const renderer = PIXI.autoDetectRenderer(); // return PIXI.Renderer or throws error

Instead, use the legacy bundle to have access to the canvas rendering.

import * as PIXI from "pixi.js-legacy";
const renderer = PIXI.autoDetectRenderer(); // returns PIXI.Renderer or PIXI.CanvasRenderer

Bundling Changes

If you're using Rollup, Parcel or another bundler to add PixiJS into your project there are a few subtle changes when moving to v5. Namely, the global PIXI object is no longer created automatically. This was removed from bundling for two purpose: 1) to improve tree-shaking for bundlers, and 2) for security purpose by protecting PIXI.

This is no longer a valid way to import:

import "pixi.js";
const renderer = PIXI.autoDetectRenderer(); // INVALID! No more global.PIXI!

Instead, you should import as a namespace or individual elements:

import * as PIXI from "pixi.js";
const renderer = PIXI.autoDetectRenderer();

// or even better:
import { autoDetectRenderer } from "pixi.js";
const renderer = autoDetectRenderer();

Lastly, some 3rd-party plugins maybe expecting window.PIXI, so you might have to explicitly expose the global like this, however this is not recommended.

import * as PIXI from 'pixi.js';
window.PIXI = PIXI; // some bundlers might prefer "global" instead of "window"

Webpack

When Webpack and 3rd-party plugins, like pixi-spine, you might have difficulties building the global PIXI object resulting in a runtime error ReferenceError: PIXI is not defined. Usually this can be resolved by using Webpack shimming globals.

For instance, here's your import code:

import * as PIXI from 'pixi.js';
import 'pixi-spine'; // or other plugins that need global 'PIXI' to be defined first

Add a plugins section to your webpack.config.js to let know Webpack that the global PIXI variable make reference to pixi.js module. For instance:

const webpack = require('webpack');

module.exports = {
entry: '...',
output: {
...
},
plugins: [
new webpack.ProvidePlugin({
PIXI: 'pixi.js'
})
]
}
- - +
Version: v7.x

v5 Migration Guide

This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we've try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.

🚧 API Changes

Making WebGL First-Class

PixiJS v5 has made WebGL the first-class renderer and made CanvasRenderer to be second-class. Functionally, there's not much that changed from v4, but there are a bunch of subtle internal naming changes which could trip-up some developers upgrading to v5. For instance:

  • WebGLRenderer becomes Renderer
  • renderWebGL becomes render (in DisplayObject, Sprite, Container, etc)
  • _renderWebGL becomes _render (in DisplayObject, Container, etc)

If you created a plugin or project that previously used render on a Container (see #5510), this will probably cause your project to not render correctly. Please consider renaming your user-defined render to something else. In most other cases, you'll get a deprecation warning trying to invoke WebGL-related classes or methods, e.g., new PIXI.WebGLRenderer().

Renderer Parameters

Specifying options as a third parameter in Renderer constructor is officially dropped (same with PIXI.Application, PIXI.autoDetectRenderer & PIXI.CanvasRenderer). In v4 we supported two function signatures, but in v5 we dropped width, height, options signature. Please add width and height to options.

const renderer = new PIXI.Renderer(800, 600, { transparent: true }); // bad
const renderer = new PIXI.Renderer({ width: 800, height: 600, transparent: true }); // good
  • Note: Adding transparent: true in Renderer or Application constructor options might help with strange artifacts on some devices, but it might reduce FPS. It's much better than preserveDrawingBuffer: true.

  • If you need the v4 default behavior of resizing the canvas using css pixels, add autoDensity: true to the options.

Not everything went to params. To enable WebGL1 even if WebGL2 is available, use

PIXI.settings.PREFER_ENV = PIXI.ENV.WEBGL;

Mesh, Plane, Rope

PixiJS v5 introduces a new class called PIXI.Mesh. This allows overriding the default shader and the ability to add more attributes to geometry. For example, you can add colors to vertices.

The old v4 Mesh class has moved from PIXI.mesh.Mesh to PIXI.SimpleMesh, it extends PIXI.Mesh.

PIXI.mesh.Rope, PIXI.mesh.Plane, PIXI.mesh.NineSlicePlane have moved to PIXI.SimpleRope, PIXI.SimplePlane and PIXI.NineSlicePlane respectively.

If you used custom shaders or generated meshes in v4, you might be impacted by these changes in v5.

PIXI.SimpleMesh fields vertices, uvs, indices are wrapped inside mesh.geometry attribute buffers. For example, this is how access to buffers provided through mesh.uvBuffer property:

get uvBuffer()
{
return this.geometry.buffers[1];
}

The indices property shortcut is also missing, but you can access the data inside mesh.geometry.indexBuffer.

You can override buffer data, and notify it that data was changed, in this case buffer will be uploaded to GPU lazily. Previously in v4 mesh had several flags that indicated which attributes have to be updated and their names confused people.

Graphics Holes

Drawing holes in Graphics was very limited in v4. This only supported non-Shape drawing, like using lineTo, bezierCurveTo, etc. In v5, we improved the hole API by supporting shapes. Unfortunately, there's no deprecation strategy to support the v4 API. For instance, in v4:

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.moveTo(0, 0)
.lineTo(100, 0)
.lineTo(100, 100)
.lineTo(0, 100)
.moveTo(10, 10)
.lineTo(90, 10)
.lineTo(90, 90)
.lineTo(10, 90)
.addHole();

Live example in v4.x

In v5, Graphics has simplified and the API changed from addHole to beginHole and endHole.

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.drawRect(0, 0, 100, 100)
.beginHole()
.drawCircle(50, 50, 30)
.endHole();

Live example in dev

Filter Padding

In v4 filters had a default padding of 4 and in v5 this has been changed to a default of 0. This can cause some filters to look broken when used. To fix this issue simply add some padding to the filters you create.

// Glow filter from https://github.com/pixijs/pixi-filters
const filter = new PIXI.filters.GlowFilter();
filter.padding = 4;

Some filters, like BlurFilter, automatically calculate the padding so changes may not be necessary.

Filter Default Vertex Shader

We reorganized all uniforms dedicated to coordinate system transforms, and renamed them. If your filter doesn't work anymore, check if you use default vertex shader. In that case, you can use old v4 vertex shader code.

All changes are explained in [[Creating Filters|v5-Creating-filters]]

Enable Mipmapping for RenderTexture

Previously, you may have ended up with code like this in v4 (specifically if you saw Ivan's comment/JSFiddle):

const renderer = PIXI.autoDetectRenderer();
renderer.bindTexture(baseRenderTex, false, 0);
const glTex = baseRenderTex._glTextures[renderer.CONTEXT_UID];
glTex.enableMipmap(); // this is what actually generates mipmaps in WebGL
glTex.enableLinearScaling(); // this is what tells WebGL to USE those mipmaps

In v5, this code is no longer needed.

BaseTexture Resources

One of the newest features in v5 is that we decoupled all the asset-specific functionality from BaseTexture. We created a new system called "resources" and each BaseTexture now has a resource that wraps some specific asset type. For instance: VideoResource, SVGResource, ImageResource, CanvasResource. In the future, we hope to be able to add other resource types. If there were asset-specific methods or properties being called before, these will probably be on baseTexture.resource.

Also, we removed all of the from* methods from BaseTexture, so you just can call BaseTexture.from and pass in whatever resource. Please see docs for more information about from.

const canvas = document.createElement('canvas');
const baseTexture = PIXI.BaseTexture.from(canvas);

That API also allows to use pure WebGL and 2d context calls, see the gradient example.

BaseTexture.source

Has been moved to baseTexture.resource.source, moved into resource corresponding to the baseTexture. baseTexture.resource does not exist for RenderTexture, and source does not exist for resources that dont have source.

Graphics Interaction

If you use transparent interactive graphics trick, make sure that you use specify alpha=0 for all element, not for its parts. How PixiJS deals with shapes that have alpha=0 is considered undefined behaviour. We might change it back, but we have no guarantees about it.

graphics.beginFill(0xffffff, 0.0); //bad
graphics.alpha = 0; //good

📦 Publishing Changes

Canvas Becomes Legacy

Since WebGL and WebGL2 are now first-class, we have removed the canvas-based fallback from the default pixi.js package. If you need CanvasRenderer, you should switch to use pixi.js-legacy instead.

import * as PIXI from "pixi.js";
// Will NOT return CanvasRenderer because canvas-based
// functionality was removed from "pixi.js"
const renderer = PIXI.autoDetectRenderer(); // return PIXI.Renderer or throws error

Instead, use the legacy bundle to have access to the canvas rendering.

import * as PIXI from "pixi.js-legacy";
const renderer = PIXI.autoDetectRenderer(); // returns PIXI.Renderer or PIXI.CanvasRenderer

Bundling Changes

If you're using Rollup, Parcel or another bundler to add PixiJS into your project there are a few subtle changes when moving to v5. Namely, the global PIXI object is no longer created automatically. This was removed from bundling for two purpose: 1) to improve tree-shaking for bundlers, and 2) for security purpose by protecting PIXI.

This is no longer a valid way to import:

import "pixi.js";
const renderer = PIXI.autoDetectRenderer(); // INVALID! No more global.PIXI!

Instead, you should import as a namespace or individual elements:

import * as PIXI from "pixi.js";
const renderer = PIXI.autoDetectRenderer();

// or even better:
import { autoDetectRenderer } from "pixi.js";
const renderer = autoDetectRenderer();

Lastly, some 3rd-party plugins maybe expecting window.PIXI, so you might have to explicitly expose the global like this, however this is not recommended.

import * as PIXI from 'pixi.js';
window.PIXI = PIXI; // some bundlers might prefer "global" instead of "window"

Webpack

When Webpack and 3rd-party plugins, like pixi-spine, you might have difficulties building the global PIXI object resulting in a runtime error ReferenceError: PIXI is not defined. Usually this can be resolved by using Webpack shimming globals.

For instance, here's your import code:

import * as PIXI from 'pixi.js';
import 'pixi-spine'; // or other plugins that need global 'PIXI' to be defined first

Add a plugins section to your webpack.config.js to let know Webpack that the global PIXI variable make reference to pixi.js module. For instance:

const webpack = require('webpack');

module.exports = {
entry: '...',
output: {
...
},
plugins: [
new webpack.ProvidePlugin({
PIXI: 'pixi.js'
})
]
}
+ + \ No newline at end of file diff --git a/7.x/guides/migrations/v6.html b/7.x/guides/migrations/v6.html index f66debcca..fedb8d9ab 100644 --- a/7.x/guides/migrations/v6.html +++ b/7.x/guides/migrations/v6.html @@ -9,13 +9,13 @@ - - + +
-

v6 Migration Guide

PixiJS 6 comes with few surface-level breaking changes. This document is not complete.

Typings

If you're using TypeScript, make sure the follow is added to your tsconfig.json:

{
"compilerOptions": {
"moduleResolution": "node",
// Required for importing 3rd-party dependencies like EventEmitter3
"esModuleInterop": true
}
}

Mesh Internals

If you ever overrode Mesh._renderDefault to take into account more uniforms like this: v5 Reference

if (shader.program.uniformData.translationMatrix)
{
shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
}

Remove the if, leave the contents, otherwise you might not get correct sync uniform for translationMatrix, or even worse - get null pointer. v6 Reference.

shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
- - +
Version: v7.x

v6 Migration Guide

PixiJS 6 comes with few surface-level breaking changes. This document is not complete.

Typings

If you're using TypeScript, make sure the follow is added to your tsconfig.json:

{
"compilerOptions": {
"moduleResolution": "node",
// Required for importing 3rd-party dependencies like EventEmitter3
"esModuleInterop": true
}
}

Mesh Internals

If you ever overrode Mesh._renderDefault to take into account more uniforms like this: v5 Reference

if (shader.program.uniformData.translationMatrix)
{
shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
}

Remove the if, leave the contents, otherwise you might not get correct sync uniform for translationMatrix, or even worse - get null pointer. v6 Reference.

shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
+ + \ No newline at end of file diff --git a/7.x/guides/migrations/v7.html b/7.x/guides/migrations/v7.html index bf8a67175..a4e68f3ed 100644 --- a/7.x/guides/migrations/v7.html +++ b/7.x/guides/migrations/v7.html @@ -9,15 +9,15 @@ - - + +
-

v7 Migration Guide

First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn't really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.

👋 Dropping Internet Explorer

Microsoft officially ended support for IE, so we decided to follow. It simplified many of our modernizations since IE was an outliner from Safari/Chrome/Firefox/Edge and mobile browsers. If you need support for IE, please consider using Babel or some other trans-piling tool.

🗑️ Remove Polyfills

We removed the bundled polyfills such as requestAnimationFrame and Promise. These things are widely available in browsers now. If projects require them, developers should include the polyfills they need for backward-compatibility. Please check out polyfill.io.

💬 Output ES2020 (modules) and ES2017 (browser)

PixiJS historically only published ES5 (no classes!). A new output standard allows us to use ES2017 features that previously we couldn't use (e.g., String.prototype.startsWith, Array.prototype.contains, etc). Not only does it make the code more readable, but the output looks nicer as well. For modules we are outputting ES2020, which contains syntax like nullish coalescing (??). If your project needs to have backward compatibility, you can use Babel to transpile or polyfill.

🐭 Replaces InteractionManager with EventSystem

InteractionManager was getting complex and difficult to maintain. Few core team members understood the code. We decided to move to FederatedEvents, which is concise, better aligned with the DOM, and supports things like bubbling. The good news, is you shouldn't have to change code, as it is largely a drop-in replacement. We added addEventListener and removeEventListener APIs to DisplayObject which have the same DOM signature and can be used instead of on and off.

📦 Replaces Loader with Assets

Similarly, we've been wanting to remove the Loader because of its legacy approach (e.g., XMLHttpRequest). This was forked from resource-loader that has been with PixiJS for a long time. The original design inspiration for Loader was driven largely by Flash/AS3, which now seem dated. There were a few things we wanted out of a new iteration: static loading, loading with Workers, background loading, Promise-based, fewer layers of caching. Here's a quick example of how this will change:

import { Loader, Sprite } from 'pixi.js';

const loader = new Loader();
loader.add('background', 'path/to/assets/background.jpg');
loader.load((loader, resources) => {
const image = Sprite.from(resources.background.texture);
});

Now becomes:

import { Assets, Sprite } from 'pixi.js';

const texture = await Assets.load('path/to/assets/background.jpg');
const image = Sprite.from(texture);

🤝 Abandon the use of peerDependencies

PixiJS heavily uses peerDependencies in the package.json within each package. This design choice has plagued Pixi with many issues. It's a breaking change to remove, so now was a good time. We have decided to completely remove peerDependencies, instead opting for nothing. This should make installing and upgrading pixi.js much easier. We are working on updating our tooling for composing a custom version with packages. Edit: As of 7.2.0, we have reverted this change to keep compatibility with some module-based CDNs.

👂 Other Changes

  • Browser builds have been removed for all packages, with the exception of pixi.js and pixi.js-legacy.
  • Removes Graphics.nextRoundedRectBehavior this is now the default behavior
  • Removes Text.nextLineHeightBehavior this is now the default behavior
  • AbstractBatchRenderer and BatchPluginFactory has been removed. Either extends BatchRenderer or use setShaderGenerator on the default BatchRenderer, (e.g., renderer.plugins.batch)
  • BatchRenderer is installed by default in @pixi/core, no need to Renderer.registerPlugin('batch', BatchRenderer) anymore

Exports from @pixi/core

The @pixi/core package now depends and re-exports the following packages.

  • @pixi/math
  • @pixi/contants
  • @pixi/utils
  • @pixi/runner
  • @pixi/settings
  • @pixi/ticker

While some packages will still work when installed directly, others will not, since by installing them alongside @pixi/core you will be effectively importing two copies of the same code.  +

Version: v7.x

v7 Migration Guide

First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn't really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.

👋 Dropping Internet Explorer

Microsoft officially ended support for IE, so we decided to follow. It simplified many of our modernizations since IE was an outliner from Safari/Chrome/Firefox/Edge and mobile browsers. If you need support for IE, please consider using Babel or some other trans-piling tool.

🗑️ Remove Polyfills

We removed the bundled polyfills such as requestAnimationFrame and Promise. These things are widely available in browsers now. If projects require them, developers should include the polyfills they need for backward-compatibility. Please check out polyfill.io.

💬 Output ES2020 (modules) and ES2017 (browser)

PixiJS historically only published ES5 (no classes!). A new output standard allows us to use ES2017 features that previously we couldn't use (e.g., String.prototype.startsWith, Array.prototype.contains, etc). Not only does it make the code more readable, but the output looks nicer as well. For modules we are outputting ES2020, which contains syntax like nullish coalescing (??). If your project needs to have backward compatibility, you can use Babel to transpile or polyfill.

🐭 Replaces InteractionManager with EventSystem

InteractionManager was getting complex and difficult to maintain. Few core team members understood the code. We decided to move to FederatedEvents, which is concise, better aligned with the DOM, and supports things like bubbling. The good news, is you shouldn't have to change code, as it is largely a drop-in replacement. We added addEventListener and removeEventListener APIs to DisplayObject which have the same DOM signature and can be used instead of on and off.

📦 Replaces Loader with Assets

Similarly, we've been wanting to remove the Loader because of its legacy approach (e.g., XMLHttpRequest). This was forked from resource-loader that has been with PixiJS for a long time. The original design inspiration for Loader was driven largely by Flash/AS3, which now seem dated. There were a few things we wanted out of a new iteration: static loading, loading with Workers, background loading, Promise-based, fewer layers of caching. Here's a quick example of how this will change:

import { Loader, Sprite } from 'pixi.js';

const loader = new Loader();
loader.add('background', 'path/to/assets/background.jpg');
loader.load((loader, resources) => {
const image = Sprite.from(resources.background.texture);
});

Now becomes:

import { Assets, Sprite } from 'pixi.js';

const texture = await Assets.load('path/to/assets/background.jpg');
const image = Sprite.from(texture);

🤝 Abandon the use of peerDependencies

PixiJS heavily uses peerDependencies in the package.json within each package. This design choice has plagued Pixi with many issues. It's a breaking change to remove, so now was a good time. We have decided to completely remove peerDependencies, instead opting for nothing. This should make installing and upgrading pixi.js much easier. We are working on updating our tooling for composing a custom version with packages. Edit: As of 7.2.0, we have reverted this change to keep compatibility with some module-based CDNs.

👂 Other Changes

  • Browser builds have been removed for all packages, with the exception of pixi.js and pixi.js-legacy.
  • Removes Graphics.nextRoundedRectBehavior this is now the default behavior
  • Removes Text.nextLineHeightBehavior this is now the default behavior
  • AbstractBatchRenderer and BatchPluginFactory has been removed. Either extends BatchRenderer or use setShaderGenerator on the default BatchRenderer, (e.g., renderer.plugins.batch)
  • BatchRenderer is installed by default in @pixi/core, no need to Renderer.registerPlugin('batch', BatchRenderer) anymore

Exports from @pixi/core

The @pixi/core package now depends and re-exports the following packages.

  • @pixi/math
  • @pixi/contants
  • @pixi/utils
  • @pixi/runner
  • @pixi/settings
  • @pixi/ticker

While some packages will still work when installed directly, others will not, since by installing them alongside @pixi/core you will be effectively importing two copies of the same code.  This will lead to errors where changing settings from @pixi/settings doesn't do anything since @pixi/core has its own version of that package. It is recommended that you uninstall these from your project and use @pixi/core instead.

import { Rectangle } from '@pixi/math';
import { settings } from '@pixi/settings';
import { ALPHA_MODES } from '@pixi/constants';
import { string2hex } from '@pixi/utils';

Now becomes:

import { Rectangle, settings, ALPHA_MODES, utils } from '@pixi/core';

const { string2hex } = utils;

Extract and Prepare Systems

Extract and prepare plugins have been converted to Renderer "systems".

renderer.plugins.extract
renderer.plugins.prepare

Now becomes:

renderer.extract
renderer.prepare

Extensions Self-Install

Extensions now install themselves, so you should only need to import the class in order to use. For example, in v6:

import { AccessibilityManager } from '@pixi/accessibility';
import { extensions } from '@pixi/core';
extensions.add(AccessibilityManager);

Now becomes:

import '@pixi/accessibility';

Using hitTest with Events

With the new events system, one of the common APIs that changed is `hitTest.

import {Application} from 'pixi.js';

const app = new Application();
app.renderer.plugins.interaction.hitTest({x, y});

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const boundary = new EventBoundary(app.stage);
boundary.hitTest(x, y);

New Async Extract Methods

The following methods are now async and return a Promise.

  • CanvasExtract.base64()
  • CanvasExtract.image()
  • Extract.base64()
  • Extract.image()
import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = app.renderer.extract.base64();

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = await app.renderer.extract.base64();

Interactive Move Events

Interaction events in PixiJS now behave like the DOM in v7. This was intentional to align around behavior that would be familiar with developers, but obviously impacts the behavior with pointermove, mousemove, and touchmove.

Like the DOM, move events are now local. This means that if you are outside the bounds of the object, you will not receive a move event. Generally, you should consider adding move events to the stage or parent instead of the DisplayObject itself.

Working example: https://jsfiddle.net/bigtimebuddy/spnv4wm6/

Interactive Property Handlers are Removed

Property-based handlers were removed from events. This was a feature of the old InteractionManager. For instance:

sprite.pointertap = () => {
// handler the pointertap
};

Now becomes:

sprite.on('pointertap', () => {
// handler the pointertap
});

Property buttonMode has been removed

The property buttonMode was a convenience for toggling the cursor property between pointer and null. It has now been removed.

sprite.buttonMode = true;

Now becomes:

sprite.cursor = 'pointer';

If you would like to re-add this functionality, you can patch DisplayObject's prototype:

import { DisplayObject } from 'pixi.js';

Object.defineProperty(DisplayObject.prototype, 'buttonMode', {
get() { return this.cursor === 'pointer'; },
set(value) { this.cursor = value ? 'pointer' : null; },
});

☝️ Suggestions for Upgrading

If you're planning on transitioning your code from v6, it would be helpful to implement some of the more dramatic changes in v6 first before upgrading to v7:

import { InteractionManager, extensions, Application } from 'pixi.js';
import { EventSystem } from '@pixi/events';

// Uninstall interaction
extensions.remove(InteractionManager);

// Create the renderer or application
const app = new Application();

// Install events
app.renderer.addSystem(EventSystem, 'events');
  • Switch to the Assets package by installing @pixi/assets and swapping for Loader. For more information on implementing Assets, see this guide.
  • Set Graphics.nextRoundedRectBehavior = true, this uses arcs for corner radius instead of bezier curves.
  • Set Text.nextLineHeightBehavior = true, this defaults to the DOM-like behavior for line height.

🏗️ Plugin Supported

PluginCompatiblePlugin Version Supported
PixiJS Soundv5.0.0+
PixiJS HTMLTextv3.0.0+
PixiJS Filtersv5.0.0+
PixiJS GIFv2.0.0+
PixiJS Spinev4.0.0+
PixiJS Particle Emitterv5.0.8+
PixiJS Animate
PixiJS Layersv2.0.0+
PixiJS Lightsv4.0.0+
PixiJS Graphics Smoothv1.0.0+
PixiJS Tilemap
- - + + \ No newline at end of file diff --git a/7.x/guides/production/performance-tips.html b/7.x/guides/production/performance-tips.html index c4ff17870..3862258e1 100644 --- a/7.x/guides/production/performance-tips.html +++ b/7.x/guides/production/performance-tips.html @@ -9,13 +9,13 @@ - - + +
-

Performance Tips

General

  • Only optimize when you need to! PixiJS can handle a fair amount of content off the bat
  • Be mindful of the complexity of your scene. The more objects you add the slower things will end up
  • Order can help, for example sprite / graphic / sprite / graphic is slower than sprite / sprite / graphic / graphic
  • Some older mobile devices run things a little slower. Passing in the option useContextAlpha: false and antialias: false to the Renderer or Application can help with performance
  • Culling is disabled by default as it's often better to do this at an application level or set objects to be cullable = true. If you are GPU-bound it will improve performance; if you are CPU-bound it will degrade performance

Sprites

  • Use Spritesheets where possible to minimize total textures
  • Sprites can be batched with up to 16 different textures (dependent on hardware)
  • This is the fastest way to render content
  • On older devices use smaller low resolution textures
  • Add the extention @0.5x.png to the 50% scale-down spritesheet so PixiJS will visually-double them automatically
  • Draw order can be important

Graphics

  • Graphics objects are fastest when they are not modified constantly (not including the transform, alpha or tint!)
  • Graphics objects are batched when under a certain size (100 points or smaller)
  • Small Graphics objects are as fast as Sprites (rectangles, triangles)
  • Using 100s of graphics complex objects can be slow, in this instance use sprites (you can create a texture)

Texture

  • Textures are automatically managed by a Texture Garbage Collector
  • You can also manage them yourself by using texture.destroy()
  • If you plan to destroy more than one at once add a random delay to their destruction to remove freezing
  • Delay texture destroy if you plan to delete a lot of textures yourself

Text

  • Avoid changing it on every frame as this can be expensive (each time it draws to a canvas and then uploads to GPU)
  • Bitmap Text gives much better performance for dynamically changing text
  • Text resolution matches the renderer resolution, decrease resolution yourself by setting the resolution property, which can consume less memory

Masks

  • Masks can be expensive if too many are used: e.g., 100s of masks will really slow things down
  • Axis-aligned Rectangle masks are the fastest (as the use scissor rect)
  • Graphics masks are second fastest (as they use the stencil buffer)
  • Sprite masks are the third fastest (they uses filters). They are really expensive. Do not use too many in your scene!

Filters

  • Release memory: displayObject.filters = null
  • If you know the size of them: displayObject.filterArea = new PIXI.Rectangle(x,y,w,h). This can speed things up as it means the object does not need to be measured
  • Filters are expensive, using too many will start to slow things down!

BlendModes

  • Different blend modes will cause batches to break (de-optimize)
  • SceenSprite / NormalSprite / SceenSprite / NormalSprite would be 4 draw calls
  • SceenSprite / SceenSprite / NormalSprite / NormalSprite would be 2 draw calls

CacheAsBitmap

  • Setting to true turns an object into a Sprite by caching it as a Texture
  • It has a one time cost when it is activated as it draws the object to a Texture
  • Avoid changing this on elements frequently
  • If you have a complicated item that has lots of sprites / filters AND does not move then this will speed up rendering!
  • Do not need apply to sprites as they are already Textures
  • Do not use if the object where its children are constantly changing as this will slow things down

Events

  • If an object has no interactive children use interactiveChildren = false. The event system will then be able to avoid crawling through the object
  • Setting hitArea = new PIXI.Rectangle(x,y,w,h) as above should stop the event system from crawling through the object
- - +
Version: v7.x

Performance Tips

General

  • Only optimize when you need to! PixiJS can handle a fair amount of content off the bat
  • Be mindful of the complexity of your scene. The more objects you add the slower things will end up
  • Order can help, for example sprite / graphic / sprite / graphic is slower than sprite / sprite / graphic / graphic
  • Some older mobile devices run things a little slower. Passing in the option useContextAlpha: false and antialias: false to the Renderer or Application can help with performance
  • Culling is disabled by default as it's often better to do this at an application level or set objects to be cullable = true. If you are GPU-bound it will improve performance; if you are CPU-bound it will degrade performance

Sprites

  • Use Spritesheets where possible to minimize total textures
  • Sprites can be batched with up to 16 different textures (dependent on hardware)
  • This is the fastest way to render content
  • On older devices use smaller low resolution textures
  • Add the extention @0.5x.png to the 50% scale-down spritesheet so PixiJS will visually-double them automatically
  • Draw order can be important

Graphics

  • Graphics objects are fastest when they are not modified constantly (not including the transform, alpha or tint!)
  • Graphics objects are batched when under a certain size (100 points or smaller)
  • Small Graphics objects are as fast as Sprites (rectangles, triangles)
  • Using 100s of graphics complex objects can be slow, in this instance use sprites (you can create a texture)

Texture

  • Textures are automatically managed by a Texture Garbage Collector
  • You can also manage them yourself by using texture.destroy()
  • If you plan to destroy more than one at once add a random delay to their destruction to remove freezing
  • Delay texture destroy if you plan to delete a lot of textures yourself

Text

  • Avoid changing it on every frame as this can be expensive (each time it draws to a canvas and then uploads to GPU)
  • Bitmap Text gives much better performance for dynamically changing text
  • Text resolution matches the renderer resolution, decrease resolution yourself by setting the resolution property, which can consume less memory

Masks

  • Masks can be expensive if too many are used: e.g., 100s of masks will really slow things down
  • Axis-aligned Rectangle masks are the fastest (as the use scissor rect)
  • Graphics masks are second fastest (as they use the stencil buffer)
  • Sprite masks are the third fastest (they uses filters). They are really expensive. Do not use too many in your scene!

Filters

  • Release memory: displayObject.filters = null
  • If you know the size of them: displayObject.filterArea = new PIXI.Rectangle(x,y,w,h). This can speed things up as it means the object does not need to be measured
  • Filters are expensive, using too many will start to slow things down!

BlendModes

  • Different blend modes will cause batches to break (de-optimize)
  • SceenSprite / NormalSprite / SceenSprite / NormalSprite would be 4 draw calls
  • SceenSprite / SceenSprite / NormalSprite / NormalSprite would be 2 draw calls

CacheAsBitmap

  • Setting to true turns an object into a Sprite by caching it as a Texture
  • It has a one time cost when it is activated as it draws the object to a Texture
  • Avoid changing this on elements frequently
  • If you have a complicated item that has lots of sprites / filters AND does not move then this will speed up rendering!
  • Do not need apply to sprites as they are already Textures
  • Do not use if the object where its children are constantly changing as this will slow things down

Events

  • If an object has no interactive children use interactiveChildren = false. The event system will then be able to avoid crawling through the object
  • Setting hitArea = new PIXI.Rectangle(x,y,w,h) as above should stop the event system from crawling through the object
+ + \ No newline at end of file diff --git a/7.x/playground.html b/7.x/playground.html index f1a8e39b1..0f6f1ae75 100644 --- a/7.x/playground.html +++ b/7.x/playground.html @@ -9,13 +9,13 @@ - - + + - - +
Version: v7.x
+ + \ No newline at end of file diff --git a/7.x/search-index-blog.json b/7.x/search-index-blog.json new file mode 100644 index 000000000..c3d40df9e --- /dev/null +++ b/7.x/search-index-blog.json @@ -0,0 +1 @@ +[{"documents":[{"i":520,"t":"","u":"/blog/archive","b":["Blog"]},{"i":521,"t":"Introducing the PixiJS Universe!","u":"/blog/pixi-universe","b":["Blog"]},{"i":555,"t":"PixiJS v8 Launches! 🎉","u":"/blog/pixi-v8-launches","b":["Blog"]},{"i":591,"t":"PixiJS v8 Beta! 🎉","u":"/blog/pixi-v8-beta","b":["Blog"]}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/520",[]],["t/521",[0,1.161,1,0.344,2,1.161]],["t/555",[1,0.301,3,0.584,4,1.015,5,0.584]],["t/591",[1,0.301,3,0.584,5,0.584,6,1.015]]],"invertedIndex":[["",{"_index":5,"t":{"555":{"position":[[20,2]]},"591":{"position":[[16,2]]}}}],["beta",{"_index":6,"t":{"591":{"position":[[10,5]]}}}],["introducing",{"_index":0,"t":{"521":{"position":[[0,11]]}}}],["launches",{"_index":4,"t":{"555":{"position":[[10,9]]}}}],["pixijs",{"_index":1,"t":{"521":{"position":[[16,6]]},"555":{"position":[[0,6]]},"591":{"position":[[0,6]]}}}],["universe",{"_index":2,"t":{"521":{"position":[[23,9]]}}}],["v8",{"_index":3,"t":{"555":{"position":[[7,2]]},"591":{"position":[[7,2]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":523,"t":"Phase 1​","u":"/blog/pixi-universe","h":"#phase-1","p":521},{"i":525,"t":"PixiJS Website​","u":"/blog/pixi-universe","h":"#pixijs-website","p":521},{"i":527,"t":"PixiJS React​","u":"/blog/pixi-universe","h":"#pixijs-react","p":521},{"i":529,"t":"PixiJS Open Games​","u":"/blog/pixi-universe","h":"#pixijs-open-games","p":521},{"i":531,"t":"PixiJS UI & PixiJS Layout​","u":"/blog/pixi-universe","h":"#pixijs-ui--pixijs-layout","p":521},{"i":533,"t":"AssetPack​","u":"/blog/pixi-universe","h":"#assetpack","p":521},{"i":535,"t":"Phase 2​","u":"/blog/pixi-universe","h":"#phase-2","p":521},{"i":537,"t":"PixiJS JumpStart​","u":"/blog/pixi-universe","h":"#pixijs-jumpstart","p":521},{"i":539,"t":"PixiJS Dev Tools​","u":"/blog/pixi-universe","h":"#pixijs-dev-tools","p":521},{"i":541,"t":"Phase 3​","u":"/blog/pixi-universe","h":"#phase-3","p":521},{"i":543,"t":"Comet​","u":"/blog/pixi-universe","h":"#comet","p":521},{"i":545,"t":"PixiJS v8​","u":"/blog/pixi-universe","h":"#pixijs-v8","p":521},{"i":547,"t":"Phase 4​","u":"/blog/pixi-universe","h":"#phase-4","p":521},{"i":549,"t":"PixiJS 3D​","u":"/blog/pixi-universe","h":"#pixijs-3d","p":521},{"i":551,"t":"PixiJS Game Engine​","u":"/blog/pixi-universe","h":"#pixijs-game-engine","p":521},{"i":553,"t":"Conclusion","u":"/blog/pixi-universe","h":"#conclusion","p":521},{"i":557,"t":"🚀 Revolutionizing Web Graphics: Welcome to PixiJS v8","u":"/blog/pixi-v8-launches","h":"#-revolutionizing-web-graphics-welcome-to-pixijs-v8","p":555},{"i":559,"t":"🔗 Quick links","u":"/blog/pixi-v8-launches","h":"#-quick-links","p":555},{"i":561,"t":"🎁 Whats New?","u":"/blog/pixi-v8-launches","h":"#-whats-new","p":555},{"i":563,"t":"✨ We promise the Renderer will work","u":"/blog/pixi-v8-launches","h":"#-we-promise-the-renderer-will-work","p":555},{"i":565,"t":"🤝 What now? Get involved!","u":"/blog/pixi-v8-launches","h":"#-what-now-get-involved","p":555},{"i":567,"t":"📲 Keep in touch","u":"/blog/pixi-v8-launches","h":"#-keep-in-touch","p":555},{"i":593,"t":"1. 😍 Embracing WebGPU","u":"/blog/pixi-v8-beta","h":"#1--embracing-webgpu","p":591},{"i":595,"t":"2. 🚀 Turbocharging Performance","u":"/blog/pixi-v8-beta","h":"#2--turbocharging-performance","p":591},{"i":597,"t":"But Wait, There's More!","u":"/blog/pixi-v8-beta","h":"#but-wait-theres-more","p":591},{"i":599,"t":"Over to you!","u":"/blog/pixi-v8-beta","h":"#over-to-you","p":591},{"i":601,"t":"Steps to install:","u":"/blog/pixi-v8-beta","h":"#steps-to-install","p":591},{"i":603,"t":"Keep in touch!","u":"/blog/pixi-v8-beta","h":"#keep-in-touch","p":591}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/523",[0,2.069,1,2.722]],["t/525",[2,1.128,3,3.289]],["t/527",[2,1.128,4,3.289]],["t/529",[2,0.963,5,2.807,6,2.807]],["t/531",[2,1.117,7,2.17,8,0.818,9,2.17]],["t/533",[10,3.972]],["t/535",[0,2.069,11,2.722]],["t/537",[2,1.128,12,3.289]],["t/539",[2,0.963,13,2.807,14,2.807]],["t/541",[0,2.069,15,3.289]],["t/543",[16,3.972]],["t/545",[2,1.128,17,2.722]],["t/547",[0,2.069,18,3.289]],["t/549",[2,1.128,19,3.289]],["t/551",[2,0.963,20,2.807,21,2.807]],["t/553",[22,3.972]],["t/557",[2,0.607,8,0.666,17,1.464,23,1.769,24,1.769,25,1.769,26,1.769]],["t/559",[8,1.058,27,2.807,28,2.807]],["t/561",[8,1.058,29,2.807,30,2.807]],["t/563",[8,0.922,31,2.448,32,2.448,33,2.448]],["t/565",[8,1.058,34,2.807,35,2.807]],["t/567",[8,1.058,36,2.323,37,2.323]],["t/593",[1,2.026,8,0.922,38,2.448,39,2.448]],["t/595",[8,0.922,11,2.026,40,2.448,41,2.448]],["t/597",[42,2.807,43,2.807,44,2.807]],["t/599",[45,3.972]],["t/601",[46,3.289,47,3.289]],["t/603",[36,2.722,37,2.722]]],"invertedIndex":[["",{"_index":8,"t":{"531":{"position":[[10,1]]},"557":{"position":[[0,2]]},"559":{"position":[[0,2]]},"561":{"position":[[0,2]]},"563":{"position":[[0,1]]},"565":{"position":[[0,2]]},"567":{"position":[[0,2]]},"593":{"position":[[3,2]]},"595":{"position":[[3,2]]}}}],["1",{"_index":1,"t":{"523":{"position":[[6,2]]},"593":{"position":[[0,2]]}}}],["2",{"_index":11,"t":{"535":{"position":[[6,2]]},"595":{"position":[[0,2]]}}}],["3",{"_index":15,"t":{"541":{"position":[[6,2]]}}}],["3d",{"_index":19,"t":{"549":{"position":[[7,3]]}}}],["4",{"_index":18,"t":{"547":{"position":[[6,2]]}}}],["assetpack",{"_index":10,"t":{"533":{"position":[[0,10]]}}}],["comet",{"_index":16,"t":{"543":{"position":[[0,6]]}}}],["conclusion",{"_index":22,"t":{"553":{"position":[[0,10]]}}}],["dev",{"_index":13,"t":{"539":{"position":[[7,3]]}}}],["embracing",{"_index":38,"t":{"593":{"position":[[6,9]]}}}],["engine",{"_index":21,"t":{"551":{"position":[[12,7]]}}}],["game",{"_index":20,"t":{"551":{"position":[[7,4]]}}}],["games",{"_index":6,"t":{"529":{"position":[[12,6]]}}}],["graphics",{"_index":25,"t":{"557":{"position":[[23,9]]}}}],["install",{"_index":47,"t":{"601":{"position":[[9,8]]}}}],["involved",{"_index":35,"t":{"565":{"position":[[17,9]]}}}],["jumpstart",{"_index":12,"t":{"537":{"position":[[7,10]]}}}],["keep",{"_index":36,"t":{"567":{"position":[[3,4]]},"603":{"position":[[0,4]]}}}],["layout",{"_index":9,"t":{"531":{"position":[[19,7]]}}}],["links",{"_index":28,"t":{"559":{"position":[[9,5]]}}}],["more",{"_index":44,"t":{"597":{"position":[[18,5]]}}}],["new",{"_index":30,"t":{"561":{"position":[[9,4]]}}}],["now",{"_index":34,"t":{"565":{"position":[[8,4]]}}}],["open",{"_index":5,"t":{"529":{"position":[[7,4]]}}}],["over",{"_index":45,"t":{"599":{"position":[[0,4]]}}}],["performance",{"_index":41,"t":{"595":{"position":[[20,11]]}}}],["phase",{"_index":0,"t":{"523":{"position":[[0,5]]},"535":{"position":[[0,5]]},"541":{"position":[[0,5]]},"547":{"position":[[0,5]]}}}],["pixijs",{"_index":2,"t":{"525":{"position":[[0,6]]},"527":{"position":[[0,6]]},"529":{"position":[[0,6]]},"531":{"position":[[0,6],[12,6]]},"537":{"position":[[0,6]]},"539":{"position":[[0,6]]},"545":{"position":[[0,6]]},"549":{"position":[[0,6]]},"551":{"position":[[0,6]]},"557":{"position":[[44,6]]}}}],["promise",{"_index":31,"t":{"563":{"position":[[5,7]]}}}],["quick",{"_index":27,"t":{"559":{"position":[[3,5]]}}}],["react",{"_index":4,"t":{"527":{"position":[[7,6]]}}}],["renderer",{"_index":32,"t":{"563":{"position":[[17,8]]}}}],["revolutionizing",{"_index":23,"t":{"557":{"position":[[3,15]]}}}],["steps",{"_index":46,"t":{"601":{"position":[[0,5]]}}}],["there's",{"_index":43,"t":{"597":{"position":[[10,7]]}}}],["tools",{"_index":14,"t":{"539":{"position":[[11,6]]}}}],["touch",{"_index":37,"t":{"567":{"position":[[11,5]]},"603":{"position":[[8,6]]}}}],["turbocharging",{"_index":40,"t":{"595":{"position":[[6,13]]}}}],["ui",{"_index":7,"t":{"531":{"position":[[7,2]]}}}],["v8",{"_index":17,"t":{"545":{"position":[[7,3]]},"557":{"position":[[51,2]]}}}],["wait",{"_index":42,"t":{"597":{"position":[[4,5]]}}}],["web",{"_index":24,"t":{"557":{"position":[[19,3]]}}}],["webgpu",{"_index":39,"t":{"593":{"position":[[16,6]]}}}],["website",{"_index":3,"t":{"525":{"position":[[7,8]]}}}],["welcome",{"_index":26,"t":{"557":{"position":[[33,7]]}}}],["whats",{"_index":29,"t":{"561":{"position":[[3,5]]}}}],["work",{"_index":33,"t":{"563":{"position":[[31,4]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":522,"t":"We are excited to announce the launch of the PixiJS Universe, an initiative to further enhance the capabilities of PixiJS and make it even easier for developers to create amazing games and apps. For years, PixiJS has been the most popular 2D renderer for the web, but it was always \"just a rendering engine.\" The community has created many great tools to help developers build games and applications with PixiJS, but we lacked the manpower to maintain and improve these tools, causing fragmentation in the community. To address this, Playco has assembled a dedicated team of developers who will work on PixiJS full-time. Over the next 12 months, we have more than ten projects planned, and in this announcement, we would like to introduce some of them.","s":"Introducing the PixiJS Universe!","u":"/blog/pixi-universe","h":"","p":521},{"i":524,"t":"We have several new projects in the works, and we are releasing them in four phases over the year. We are currently nearing the end of phase 1, which has seen the release of several exciting new tools and libraries for PixiJS:","s":"Phase 1​","u":"/blog/pixi-universe","h":"#phase-1","p":521},{"i":526,"t":"First up we are excited to announce that we will be releasing a beta version of our new website for PixiJS. The current documentation has suffered from long-term organic growth over the years with little structure and as such much of the PixiJS documentation is fragmented across multiple sites. This new website will put everything you need in one place and be easily searchable. We are also planning on improving the documentation and adding more guides and examples, as well as considering translating the documentation into other languages. If you have any ideas on how to improve the site please let us know! Github Repo","s":"PixiJS Website​","u":"/blog/pixi-universe","h":"#pixijs-website","p":521},{"i":528,"t":"Next up is PixiJS React. A library that provides a way to use PixiJS in React applications. PixiJS React is a continuation of the react-pixi library created by Patrick Brouwer, which has been widely used by the PixiJS community in React projects. PixiJS has taken over maintenance of the library and is dedicated to improving the library's performance, documentation, and support for new features. The library provides an easy-to-use interface that makes it easy for developers to create React applications with PixiJS. The library integrates all the core features of PixiJS and allows you to create custom components for 3rd party libraries. Github Repo","s":"PixiJS React​","u":"/blog/pixi-universe","h":"#pixijs-react","p":521},{"i":530,"t":"We have now released a new repository called \"PixiJS Open Games\". This is a collection of open-source games that showcases how to use PixiJS to create games and is released under the MIT license. The purpose of these games is to provide professional examples of how to use PixiJS for game development. The first two games released are a match-3 game and a bubble shooter game. These games not only demonstrate how to use PixiJS for game development but also how to use other libraries such as PixiJS UI and AssetPack. We also have more games planned for release, which will demonstrate how to use PixiJS with other libraries such as PixiJS Layout and PixiJS React. PixiJS Open Games will hopefully be a valuable resource for game developers who want to learn how to use PixiJS for game development and will also be a great source of inspiration for developers looking to create their own games using PixiJS. The project is available on GitHub for anyone who wants to explore the code or contribute to the project. Github Repo Play Puzzling Potions Play Bubbo Bubbo","s":"PixiJS Open Games​","u":"/blog/pixi-universe","h":"#pixijs-open-games","p":521},{"i":532,"t":"PixiJS UI is a new library for developers who want to create beautiful and functional user interfaces with PixiJS. The library includes a range of components such as buttons, checkboxes, sliders, text inputs, scroll views, lists, radio buttons, and progress bars, which can be easily integrated into your game. These components are highly customizable, allowing you to tweak the appearance and behaviour of each element to fit your game's specific needs. PixiJS UI has been used in all of the open-source games, so feel free to check these out for real-world examples. PixiJS UI Github Repo PixiJS Layout is another library that can make your life as a developer easier. This library enables you to create responsive layouts using PixiJS, which means you can design interfaces that adapt to different screen sizes and aspect ratios. PixiJS Layout works well with PixiJS UI, allowing you to combine both libraries to create complex, dynamic interfaces that respond to user input and screen changes. With PixiJS Layout, you have the flexibility to create resizable layouts that can be adjusted to fit any screen size or device. This means that your game's interface can look great on everything from small mobile devices to large desktop displays. PixiJS Layout is still under development but will be ready in the next few weeks PixiJS Layout Github Repo","s":"PixiJS UI & PixiJS Layout​","u":"/blog/pixi-universe","h":"#pixijs-ui--pixijs-layout","p":521},{"i":534,"t":"Finally, for phase 1 we are announcing AssetPack. Asset management is an important part of developing applications, and the new AssetPack library aims to make this process easier. AssetPack is a framework-agnostic library that can be used with any framework, including PixiJS, ThreeJS, and Phaser. It provides a range of features that help developers manage their assets efficiently. The key feature of AssetPack is the ability to automatically generate new assets on the fly. For example, you can provide it with a folder of individual images and it will generate sprite sheets, which can significantly improve the performance of your application. It also provides plugins to generate mipmaps, convert fonts to different formats, convert audio to different formats, compress images, and minify JSON. These features help developers optimize their assets for faster loading times, better performance, and improved user experience. We will soon be releasing a new blog post that provides more details on how to use it. With its many useful features and framework-agnostic design. However, if you want to get stuck in today then check out the GitHub repo Github Repo","s":"AssetPack​","u":"/blog/pixi-universe","h":"#assetpack","p":521},{"i":536,"t":"Phase 2 will begin shortly and aims to make it easier to work with PixiJS applications.","s":"Phase 2​","u":"/blog/pixi-universe","h":"#phase-2","p":521},{"i":538,"t":"Jumpstart is a new CLI tool being developed by the PixiJS team to simplify the process of creating new PixiJS applications. With this tool, developers will no longer need to set up complicated tooling or worry about setting up different bundlers and frameworks. The tool will handle all the setup for you, allowing you to focus on building your application. Jumpstart will be similar to other CLI tools such as Create-React-App or Create-Vue, which have become popular in the front-end development community. The tool will provide templates for different bundlers and frameworks, including webpack, parcel, rollup, and more. This will make it easy for developers to get started with PixiJS regardless of their preferred tools and workflows. With Jumpstart, you'll be able to create a new PixiJS application in just a few minutes and start building right away.","s":"PixiJS JumpStart​","u":"/blog/pixi-universe","h":"#pixijs-jumpstart","p":521},{"i":540,"t":"PixiJS dev tools will be a browser extension that is planned to be released soon to help developers debug their PixiJS applications. This tool aims to make it easier for developers to understand the inner workings of PixiJS, optimize their code, and follow best practices. It is designed to help developers diagnose performance issues and visualize the resources that their applications are consuming. One of the key features of the PixiJS Dev Tools is its ability to help developers understand the complex process of batching in PixiJS. Batching is a technique used to optimize the rendering of multiple objects in the same draw call. This process can be complicated to understand, especially for new developers. PixiJS Dev Tools aims to make it easier to debug and optimize the rendering of objects. Overall, PixiJS Dev Tools will be a powerful tool that will make it easier for developers to build high-performance, visually stunning applications with PixiJS. By providing developers with a deeper understanding of the inner workings of PixiJS, this toolset will help developers optimize their code and create more efficient and engaging applications.","s":"PixiJS Dev Tools​","u":"/blog/pixi-universe","h":"#pixijs-dev-tools","p":521},{"i":542,"t":"Phase 3 is where our long-term projects start to be revealed. These are major changes to the PixiJS ecosystem that we are incredibly excited about","s":"Phase 3​","u":"/blog/pixi-universe","h":"#phase-3","p":521},{"i":544,"t":"Comet will be a new editor that aims to make it easier than ever to design and create games and applications with PixiJS. With its intuitive and user-friendly interface, the editor is designed to appeal to both designers and developers, allowing both groups to collaborate and work more efficiently. One of the standout features of Comet is the visual interface it provides for creating and editing scenes, sprites, animations, and more. This means that designers can create and edit complex scenes without ever having to write a single line of code. The editor provides a range of tools and options for creating sprites, animations, and other game elements, making it easy to get started with creating a game or application. In addition, developers will appreciate the runtime player feature, which allows them to easily recreate scenes in their own applications. This makes it easy to test and iterate on designs, ensuring that the final product is both functional and visually appealing. And with multi-user, real-time collaboration, Comet makes it easy for teams to work together, sharing assets and ideas and creating high-quality games and applications in record time.","s":"Comet​","u":"/blog/pixi-universe","h":"#comet","p":521},{"i":546,"t":"PixiJS v8 will be the next major release that represents a complete rewrite of PixiJS from the ground up. The development team has leveraged their extensive experience over many years to make improvements and optimizations to the core PixiJS engine. The new version of PixiJS is designed to be faster and more efficient, providing a significant improvement in rendering performance compared to v7 (currently sitting at x2) One of the most exciting features of PixiJS v8 is the inclusion of first-class support for WebGPU, which is a new graphics API that is being developed by major browser vendors. This will enable developers to take advantage of advanced GPU capabilities, which can significantly improve the performance of graphics-intensive applications. In addition to WebGPU support, the PixiJS team has also made a significant effort to optimize the engine for the canvas renderer, which will be available as a first-class option for developers looking to reduce bundle size. Overall, PixiJS v8 represents a major leap forward for us, developers can expect a much faster and more efficient engine that is better suited for building complex, graphics-intensive applications.","s":"PixiJS v8​","u":"/blog/pixi-universe","h":"#pixijs-v8","p":521},{"i":548,"t":"Phase 4 represents a leap into new territory for PixiJS as we look to delve deeper into areas outside of strictly 2D rendering.","s":"Phase 4​","u":"/blog/pixi-universe","h":"#phase-4","p":521},{"i":550,"t":"For years, Goodboy (now Playco) has had an internal 3D engine called Odie that was built on top of PixiJS. We are now planning to open-source it, which is exciting for those of you who want to seamlessly mix 2D and 3D content in your games or applications. With PixiJS 3D, you will no longer need to switch between engines or frameworks to incorporate 3D elements in your project. Although this is a long-term project, the team is making progress and plans to share more information later in the year. This release will greatly expand PixiJS's capabilities and give developers even more flexibility when building their applications.","s":"PixiJS 3D​","u":"/blog/pixi-universe","h":"#pixijs-3d","p":521},{"i":552,"t":"Finally, we are thrilled to announce that we will be working on a new library called PixiJS Game Engine. This game engine aims to provide everything you would expect from a 2D/3D game engine and will offer many features and tools to make game development easier and more efficient. Some of the features of PixiJS Game Engine will include support for physics engines, audio, input handling, asset loading and management, state management, animation and tweening, and more.","s":"PixiJS Game Engine​","u":"/blog/pixi-universe","h":"#pixijs-game-engine","p":521},{"i":554,"t":"We've shared a lot of exciting news about new projects and updates coming to the PixiJS community. There's a lot to look forward to in the upcoming months. We want to extend our sincere thanks to the PixiJS community, its contributors, and Playco for making all of this possible. We're excited to see what you'll create with these new tools and resources, and we look forward to continuing to support and grow the PixiJS ecosystem. Be sure to check out the GitHub links mentioned above and stay tuned for more updates on the PixiJS Universe!","s":"Conclusion","u":"/blog/pixi-universe","h":"#conclusion","p":521},{"i":556,"t":"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time.","s":"PixiJS v8 Launches! 🎉","u":"/blog/pixi-v8-launches","h":"","p":555},{"i":558,"t":"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration. Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture. With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code. Our vision for v8 was clear: Longevity: We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade. Innovation with WebGPU: Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out. Leveraging Modern JavaScript: The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful. Correcting Past Oversights: Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!). Boosting Performance: PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7. We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!","s":"🚀 Revolutionizing Web Graphics: Welcome to PixiJS v8","u":"/blog/pixi-v8-launches","h":"#-revolutionizing-web-graphics-welcome-to-pixijs-v8","p":555},{"i":560,"t":"The new Docs for v8 can be found here Migration Examples Open Games","s":"🔗 Quick links","u":"/blog/pixi-v8-launches","h":"#-quick-links","p":555},{"i":562,"t":"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above. 📈 New Performance Bar​ The performance of v8 is faster for both renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here: CPU = time spent by the CPU rendering a single frame GPU = time spent by the GPU rendering a single frame Bunny Situation V7 CPU V8 CPU CPU Dif V7 GPU V8 GPU GPU dif 100k sprites all moving ~50ms ~15ms 233% ~9ms ~2ms 350% 100k sprites not moving ~21ms ~0.12ms 17417% ~9ms ~0.5ms 1700% 100k sprites (changing scene structure) ~50ms ~24ms 108% ~9ms ~2ms 350% These benchmark numbers are based on the Bunnymark test that you can try yourself. v7 Bunnymark v8 Bunnymark - WebGPU v8 Bunnymark - WebGL Repo 🖥️ WebGPU Renderer​ We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements. 📦 New Package Structure​ No more \"lerna.\" PixiJS is now just one package with one import root: import {stuff} from ‘pixi.js’. This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported. Old: import { Sprite } from \"@pixi/sprite\"; import { Graphic } from \"@pixi/graphics\"; New: import { Sprite, Graphic } from \"pixi.js\";","s":"🎁 Whats New?","u":"/blog/pixi-v8-launches","h":"#-whats-new","p":555},{"i":564,"t":"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere! import { Application, autoDetectRenderer } from \"pixi.js\"; const app = new Application(); (async () => { await app.init({ // application options }); // or const renderer = await autoDetectRenderer({}); // WebGL or WebGPU // do pixi things })(); 🌟 Scene Upgrades​ The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance. const container = new Container({ isRenderGroup:true // this containers transform is now handled on the GPU! }) Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as: // will make all the children tinted red container.tint = 'red' // will make all the children have the add blend mode container.blendMode = 'add' Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer. const texture = RenderTexture.create({ width:100, height:100, antialias:true // easy as that }) We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics: ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend. It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns. import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!) myContainer.blendMode = 'color-burn` // easy! 🎨 Graphics Upgrades​ The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows: graphics .rect(50, 50, 100, 100) .fill('blue'); A GraphicsContext has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility. Support for SVG drawing has been added. For example: graphics.svg('M 100 350 q 150 -300 300 0'); Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs. The new GraphicsPath class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the buildGeometryFromPath function, opening up new possibilities for intricate and detailed graphic designs. const path = new GraphicsPath() .rect(-50, -50, 100, 100) // create geometry from the path: const geometry = buildGeometryFromPath({ path, }); const mesh = new Mesh({ geometry, texture: Texture.WHITE, }); For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the migration guide, or why not jump in and play with some examples. 📝 Text Upgrades​ Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard. BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs. const myText = new BitmapText({ text: 'hello im a bitmap font!', // font will be dynamically created style:{ fontFamily: 'Outfit', fontSize: 12, fill: 'red', } }) Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text. const myText = new Text({ text: 'hello im some fancy text', // font will be dynamically created! style:{ fontFamily: 'Outfit', fontSize: 12, fill: { texture, color:'red'} // same as graphics api fills stroke: { width:3, color:'blue' } // same as graphics api strokes } })","s":"✨ We promise the Renderer will work","u":"/blog/pixi-v8-launches","h":"#-we-promise-the-renderer-will-work","p":555},{"i":566,"t":"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on GitHub and Discord to any issues that arise, valuing your input to make PixiJS even better. A heartfelt thanks to our early adopters (everyone in here) for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you! A final big shout-out to PlayCo for their support in making this release a reality! Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help.","s":"🤝 What now? Get involved!","u":"/blog/pixi-v8-launches","h":"#-what-now-get-involved","p":555},{"i":568,"t":"To stay in the loop, we invite you to follow Doormat23 and PixiJS on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on Discord for direct engagement and real-time chit-chats.","s":"📲 Keep in touch","u":"/blog/pixi-v8-launches","h":"#-keep-in-touch","p":555},{"i":592,"t":"We're thrilled to offer an exclusive preview of the future of 2D web graphics with the Beta release of PixiJS v8. Although not yet finalized, this Beta iteration is packed with killer performance improvements and features we're eager for you to start playing with! Over the course of a decade—yes, you read that right, ten years!—we've implemented significant changes to the PixiJS engine. But the advancements in this new release are among the most monumental we've ever made! Two driving factors catalysed our approach to re-engineering the codebase and rendering pipeline in v8:","s":"PixiJS v8 Beta! 🎉","u":"/blog/pixi-v8-beta","h":"","p":591},{"i":594,"t":"The newcomer WebGPU offers a substantial performance improvement over its predecessor, WebGL. It propels web computations and graphics into a new era, providing a more efficient and robust API. Soon, it will be the go-to method for rendering most GPU-powered content on the web. This shift is reminiscent of PixiJS's initial launch. At that time, WebGL was new and only available in a handful of desktop browsers, while Canvas was ubiquitous. PixiJS's standout feature was its ability to first attempt rendering with WebGL and then fall back to Canvas as a Plan B. This approach allowed PixiJS content to immediately benefit as WebGL gained traction. Fast forward to today, and WebGL is now available on 95% of browsers. History is repeating itself with WebGPU, currently supported in only a few desktop browsers and roughly 27% of the market. However, it's only a matter of time before it becomes universally supported. PixiJS aims to execute the same fallback strategy, allowing you to always leverage the best technology available without needing to rewrite your code. This is precisely what version 8 achieves and will future proof everything we make for another ten years :D","s":"1. 😍 Embracing WebGPU","u":"/blog/pixi-v8-beta","h":"#1--embracing-webgpu","p":591},{"i":596,"t":"PixiJS has always been synonymous with speed and high-performance graphics. With v8, we've revisited our architecture to optimize both static and dynamic rendering. While v7 is fast, it operates as a somewhat ‘naïve’ renderer. v7 approach:​ Traverse the scene graph and make sure all the transforms are correct Traverse the scene graph a second time and do the following Build batches to render Upload the data to the GPU Draw the batch to the screen. v8 approach​ Update the transform of only things that changed Traverse the scene graph and construct a set of instructions. Upload all scene data to GPU in one go. Execute rendering based on the instructions. There are three key changes to this loop that give us a performance bump. First, we update only the elements that have changed. If nothing has moved, no code is executed, optimizing computational overhead. Second, if the scene graph remains unchanged in subsequent frames, we reuse the existing rendering instructions. This avoids the overhead of reconstructing these instructions for each frame. Third, if no elements in the scene change position, the data upload step (Step 3) is entirely skipped, thereby saving bandwidth and further reducing computational work. The net effect of these improvements? A decent performance leap across varying use-cases: CPU = time spent by the cpu rendering a single frame GPU = time spend by the gpu rendering a single frame Bunny Situation V7 CPU V8 CPU CPU Dif V7 GPU V8 GPU GPU dif 100k sprites all moving ~50ms ~15ms 233% ~9ms ~2ms 350% 100k sprites not moving ~21ms ~0.12ms 17417% ~9ms ~0.5ms 1700% 100k sprites (changing scene structure) ~50ms ~24ms 108% ~9ms ~2ms 350% These benchmark numbers are based on this Bunnymark test that you can try yourself! v7 Bunnymark v8 Bunnymark - WebGPU v8 Bunnymark - WebGL Repo Please have a play, you can fiddle with the parameters in the url to change the number of bunnies. Curious to see what numbers all of you get! Best of all, these improvements apply to WebGPU and the WebGL renderer. As with all of PixiJs’s party tricks, this all happens automatically :D","s":"2. 🚀 Turbocharging Performance","u":"/blog/pixi-v8-beta","h":"#2--turbocharging-performance","p":591},{"i":598,"t":"While the two key drivers behind this overhaul were performance and usability, we didn't stop there. We've seized this opportunity to enhance the API and introduce a plethora of new features to the engine—far too many to encapsulate in a single post! Stay tuned for upcoming blog posts where we'll delve deeper into these additional improvements and API refinements, empowering you to create even more remarkable projects. For a comprehensive overview of what's new, don't miss the release notes. As a crucial note, PixiJS v8 retains much of the familiar API despite undergoing significant internal updates. Our changes are geared toward making PixiJS more robust and user-friendly. When you encounter modifications, rest assured that the v7 methodology will continue to work—you'll simply see a deprecation warning, guiding you towards optimal practices.","s":"But Wait, There's More!","u":"/blog/pixi-v8-beta","h":"#but-wait-theres-more","p":591},{"i":600,"t":"As we progress toward the release candidate, now is the perfect time for you to dive in and explore v8. Your feedback at this stage is invaluable for fine-tuning our engine. We invite you to share your thoughts—the good, the bad, and the ugly—report bugs, and even contribute code. Together, we can elevate PixiJS to unprecedented heights. 👇 Don't wait—dive right in! Explore the PixiJS v8 Codebase on GitHub","s":"Over to you!","u":"/blog/pixi-v8-beta","h":"#over-to-you","p":591},{"i":602,"t":"via npm you can install the beta version like so: npm install pixi.js@prerelease-v8 then you can create the most appropriate renderer using the new autoDetectRenderer function: import { autoDetectRenderer } from \"pixi.js\"; async function init() { const renderer = await autoDetectRenderer({ // any settings }); // will return a WebGL or WebGPU renderer } Start experimenting with PixiJS v8 Beta today and join us in shaping the future of 2D web graphics! 🎉","s":"Steps to install:","u":"/blog/pixi-v8-beta","h":"#steps-to-install","p":591},{"i":604,"t":"\"To stay in the loop, we invite you to follow Doormat23 and PixiJS on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on Discord for direct engagement and real-time conversations.","s":"Keep in touch!","u":"/blog/pixi-v8-beta","h":"#keep-in-touch","p":591}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/522",[0,2.018,1,2.536,2,2.922,3,0.151,4,2.922,5,3.507,6,2.922,7,2.018,8,2.536,9,0.759,10,1.827,11,1.519,12,1.876,13,1.077,14,3.507,15,2.402,16,3.507,17,1.827,18,2.922,19,1.519,20,1.663,21,1.663,22,2.248,23,1.277,24,1.663,25,2.186,26,2.248,27,1.827,28,2.248,29,2.186,30,2.248,31,2.536,32,1.392,33,3.507,34,3.507,35,3.507,36,2.248,37,3.507,38,3.507,39,3.507,40,2.248,41,3.507,42,2.536,43,2.018,44,2.018,45,3.507,46,1.277,47,1.392,48,1.827,49,2.922,50,2.922,51,0.568,52,2.248,53,1.663,54,2.536,55,3.507,56,2.922]],["t/524",[3,0.119,29,2.119,47,1.773,53,2.119,57,5.372,58,0.698,59,3.723,60,3.231,61,4.469,62,3.723,63,3.723,64,2.865,65,4.469,66,3.723,67,2.571,68,3.723,69,3.723,70,1.773,71,2.328,72,2.865]],["t/526",[0,2.122,1,2.666,3,0.127,17,1.921,36,2.364,47,1.463,51,0.597,58,0.619,60,2.666,73,1.921,74,1.921,75,2.666,76,2.364,77,4.765,78,3.687,79,4.649,80,3.687,81,2.666,82,2.666,83,3.687,84,3.687,85,3.687,86,2.666,87,1.921,88,2.122,89,3.687,90,2.666,91,3.687,92,3.687,93,2.364,94,2.364,95,1.597,96,3.072,97,2.364,98,3.687,99,3.072,100,3.072,101,3.072,102,3.687,103,2.122,104,2.666,105,3.687,106,3.687,107,3.687,108,3.072,109,3.687,110,2.666,111,3.072,112,1.597,113,1.748]],["t/528",[3,0.158,12,1.283,13,1.421,25,1.671,26,2.259,32,1.836,42,2.548,47,1.399,48,1.836,53,1.671,58,0.458,72,2.259,74,1.836,79,2.936,100,2.936,112,1.527,113,1.671,114,4.227,115,3.651,116,3.346,117,3.524,118,2.966,119,3.524,120,2.936,121,3.524,122,3.524,123,3.524,124,2.028,125,2.936,126,3.524,127,3.524,128,1.179,129,1.836,130,1.836,131,2.966,132,2.548,133,2.936,134,3.524,135,2.259,136,2.548,137,3.524,138,2.936,139,3.524,140,2.936]],["t/530",[3,0.16,12,1.496,13,1.262,15,3.071,28,1.896,51,0.479,54,2.138,58,0.384,70,1.174,72,2.634,73,1.54,87,2.14,103,1.702,112,1.78,113,1.402,114,2.138,118,3.558,141,1.281,142,3.934,143,2.957,144,2.138,145,2.718,146,2.957,147,2.634,148,2.957,149,2.138,150,2.957,151,2.957,152,2.957,153,1.896,154,2.957,155,3.194,156,2.046,157,1.896,158,2.957,159,2.138,160,2.957,161,2.957,162,4.109,163,2.464,164,2.464,165,2.138,166,2.957,167,2.957,168,2.957,169,1.702,170,2.957,171,2.957,172,2.138,173,1.702,174,2.972,175,2.138,176,2.957,177,2.464,178,1.281,179,2.464,180,2.972,181,2.957,182,2.957,183,4.109]],["t/532",[3,0.156,9,0.555,11,1.111,12,0.934,13,1.471,15,1.336,28,1.645,48,1.336,58,0.333,59,2.137,72,1.645,87,1.336,93,1.645,97,1.645,103,1.476,104,1.855,112,1.609,113,1.761,115,3.069,124,1.476,132,1.855,138,3.095,145,1.476,147,1.645,149,1.855,155,1.476,156,1.111,163,3.989,165,3.675,169,1.476,173,1.476,184,2.137,185,2.137,186,2.138,187,4.368,188,2.566,189,1.645,190,3.715,191,2.566,192,2.566,193,2.137,194,2.566,195,2.566,196,2.566,197,2.566,198,2.566,199,1.855,200,2.566,201,1.855,202,2.566,203,2.566,204,1.935,205,2.566,206,2.566,207,2.566,208,2.137,209,2.566,210,3.715,211,3.715,212,2.566,213,1.855,214,2.566,215,2.566,216,1.645,217,1.336,218,1.645,219,1.855,220,1.645,221,2.566,222,2.566,223,2.137,224,2.566,225,3.715,226,2.382,227,1.855,228,2.137,229,1.855,230,3.639,231,2.566,232,2.566,233,2.566,234,2.566,235,1.645,236,1.645,237,2.137,238,2.566,239,1.855,240,1.111,241,1.855,242,2.566,243,2.566,244,1.855,245,2.566,246,1.855,247,2.566,248,2.566,249,2.566,250,2.137,251,2.137,252,2.566,253,2.566,254,1.855,255,1.855,256,2.566]],["t/534",[3,0.077,9,0.625,11,1.25,12,1.471,27,1.503,30,2.59,32,1.145,36,1.85,51,0.467,58,0.606,60,2.087,67,1.661,68,2.404,112,1.75,113,1.915,115,2.59,116,3.371,118,1.85,124,1.661,128,1.351,130,1.85,153,1.85,164,4.207,169,1.661,186,1.661,189,1.85,216,1.85,217,1.503,227,2.087,229,2.921,257,2.404,258,2.886,259,2.087,260,2.404,261,2.087,262,2.404,263,2.886,264,1.503,265,1.85,266,4.661,267,4.04,268,2.087,269,2.886,270,2.886,271,2.886,272,3.883,273,2.404,274,1.503,275,1.85,276,2.087,277,2.087,278,4.661,279,2.404,280,2.404,281,2.886,282,2.886,283,4.04,284,2.404,285,2.886,286,1.85,287,1.85,288,2.886,289,2.886,290,4.04,291,2.886,292,4.04,293,2.404,294,2.886,295,2.886,296,2.886,297,1.85,298,1.661,299,1.85,300,2.886,301,1.661,302,2.404,303,2.087,304,2.087,305,2.404,306,2.087,307,2.886,308,2.404,309,2.886,310,1.85]],["t/536",[3,0.128,9,1.045,11,2.092,32,1.917,44,2.779,67,2.779,264,2.516,311,4.83,312,4.83,313,3.493]],["t/538",[3,0.141,9,0.701,12,1.592,13,1.521,18,2.697,25,1.535,29,2.073,32,1.285,43,1.863,51,0.524,58,0.643,66,2.697,74,2.277,87,1.686,94,2.075,114,2.341,131,2.075,153,2.075,156,1.403,204,1.686,229,3.162,255,2.341,265,2.075,268,2.341,287,2.803,314,4.951,315,4.373,316,4.417,317,2.697,318,2.697,319,3.238,320,2.341,321,2.697,322,2.341,323,2.697,324,3.238,325,3.238,326,2.697,327,4.373,328,3.643,329,3.238,330,3.238,331,3.238,332,3.162,333,2.341,334,2.341,335,3.238,336,2.697,337,3.238,338,3.238,339,3.238,340,3.238,341,3.238,342,2.697,343,3.238,344,3.238,345,3.238,346,2.697,347,3.238,348,2.075,349,2.341,350,3.238]],["t/540",[3,0.156,9,1.009,11,2.019,12,2.101,13,0.886,23,1.471,29,2.394,30,3.237,31,2.087,32,2.004,51,0.467,54,2.087,58,0.375,90,2.087,95,1.25,124,1.661,128,1.351,130,1.145,142,2.404,178,1.75,236,1.85,264,2.104,265,2.59,274,1.503,276,2.087,297,3.237,304,2.087,316,3.366,323,2.404,351,5.05,352,2.404,353,2.886,354,4.04,355,4.661,356,4.04,357,4.04,358,1.85,359,1.85,360,2.404,361,1.85,362,2.886,363,2.404,364,2.886,365,2.404,366,2.886,367,4.04,368,2.886,369,3.366,370,2.087,371,2.404,372,2.886,373,2.886,374,2.404,375,2.087,376,2.087,377,2.087,378,2.886,379,2.087,380,2.087,381,2.886,382,2.886,383,1.85,384,2.404]],["t/542",[0,2.724,3,0.126,53,2.244,67,2.724,81,3.424,82,3.424,159,3.424,240,2.051,348,3.035,385,4.734,386,3.424,387,3.944,388,3.944]],["t/544",[3,0.076,9,0.617,11,1.235,12,1.459,13,1.231,15,2.087,29,1.352,32,1.838,44,2.305,46,1.459,51,0.648,58,0.371,95,1.235,97,1.828,108,2.375,116,2.897,130,1.132,131,2.969,132,2.897,133,3.337,136,2.062,155,2.305,178,1.235,185,2.375,186,2.305,189,1.828,204,1.485,218,1.828,226,1.828,227,2.062,235,2.969,236,1.828,264,1.485,272,2.375,273,2.375,275,1.828,287,1.828,320,3.632,342,2.375,361,1.828,376,2.062,377,2.062,389,4.631,390,4.631,391,2.375,392,2.062,393,2.851,394,4.006,395,2.375,396,2.851,397,2.375,398,2.851,399,2.851,400,3.858,401,2.568,402,4.006,403,2.851,404,2.062,405,2.851,406,2.851,407,1.641,408,2.851,409,2.062,410,2.062,411,1.235,412,2.375,413,2.851,414,2.851,415,2.851,416,2.851,417,1.828,418,2.851,419,2.375,420,2.375,421,2.375,422,2.851,423,2.851,424,2.851,425,2.851,426,2.851,427,2.062,428,2.375,429,2.851,430,2.851]],["t/546",[3,0.149,8,2.046,9,0.612,12,1.679,17,1.473,20,1.341,23,1.03,24,2.186,27,1.473,32,1.581,36,1.813,43,2.292,47,1.123,48,1.473,51,0.645,58,0.518,64,1.813,70,1.123,71,1.473,73,2.075,74,1.473,76,1.813,88,1.628,95,1.225,128,1.332,129,2.075,130,1.123,135,1.813,156,1.225,172,2.046,175,2.046,236,1.813,244,2.046,286,1.813,297,1.813,298,2.292,301,1.628,303,2.046,317,2.357,318,2.357,332,2.046,352,2.357,361,1.813,374,2.357,379,2.046,383,2.554,386,3.335,412,2.357,431,1.416,432,3.319,433,2.829,434,2.357,435,2.829,436,2.829,437,2.829,438,1.473,439,2.829,440,2.554,441,2.046,442,2.357,443,1.628,444,2.829,445,2.829,446,2.829,447,3.319,448,1.726,449,1.83,450,1.628,451,2.829,452,2.357,453,2.357,454,2.829,455,2.829,456,1.628,457,3.984,458,2.357,459,2.829,460,2.046,461,2.829,462,2.829,463,2.357,464,2.046,465,2.046,466,2.357,467,2.829]],["t/548",[3,0.124,19,2.024,23,1.701,58,0.607,67,2.689,246,3.379,380,3.379,432,3.893,464,3.379,468,4.673,469,4.673,470,3.893,471,3.893,472,4.673,473,4.673]],["t/550",[3,0.124,8,2.574,10,1.854,12,1.296,15,1.854,17,1.854,19,1.542,24,1.687,32,1.848,40,2.281,43,2.048,51,0.754,63,2.965,70,1.412,71,1.854,81,2.574,82,2.574,94,2.281,99,2.965,141,2.017,144,2.574,145,2.048,147,2.281,169,2.048,174,3.367,199,2.574,241,2.574,321,2.965,328,2.965,332,2.574,410,2.574,411,1.542,474,3.559,475,2.965,476,4.586,477,3.559,478,3.559,479,3.559,480,3.559,481,2.965,482,3.559,483,2.574,484,2.965,485,2.965,486,2.965,487,3.559,488,2.965,489,3.559,490,2.574,491,2.965,492,3.559,493,3.559,494,3.559,495,2.574,496,2.965]],["t/552",[1,2.781,3,0.13,9,0.832,11,1.666,24,2.687,27,2.003,29,1.823,51,0.792,58,0.5,93,2.465,115,2.465,129,2.003,130,1.943,144,2.781,153,2.465,155,3.368,156,1.666,239,2.781,257,3.204,259,2.781,260,4.078,264,2.003,293,3.204,299,2.465,383,2.465,466,3.204,486,3.204,497,2.781,498,3.846,499,3.846,500,2.781,501,3.204,502,3.846,503,3.846,504,3.846,505,3.846,506,3.846]],["t/554",[0,2.133,3,0.149,4,3.088,13,1.138,25,2.267,29,1.757,40,2.376,50,3.088,51,0.6,53,1.757,58,0.621,71,1.93,112,1.606,129,1.93,169,2.133,216,2.376,217,1.93,246,3.457,346,3.088,365,3.088,387,3.088,411,1.606,465,3.457,507,1.757,508,3.706,509,4.781,510,3.706,511,2.49,512,3.706,513,2.68,514,3.088,515,3.706,516,3.706,517,3.088,518,3.088,519,2.68,520,2.376,521,2.133,522,3.706,523,3.088,524,2.376,525,3.088,526,3.706,527,3.088,528,2.376,529,3.088]],["t/556",[3,0.145,9,0.748,13,1.062,19,1.497,21,2.58,46,1.259,51,0.739,53,1.639,95,1.497,96,2.879,155,1.989,156,1.497,184,2.879,254,2.499,298,1.989,310,2.215,375,2.499,411,1.497,431,1.571,448,1.497,449,1.372,507,1.639,519,2.499,530,2.879,531,2.879,532,2.879,533,3.456,534,2.944,535,3.456,536,3.456,537,2.879,538,2.879,539,2.879,540,3.456,541,2.879,542,3.456,543,2.215,544,2.215,545,3.456,546,3.456,547,2.879,548,2.879,549,2.499,550,2.879,551,3.456,552,2.879,553,3.456,554,3.456,555,3.456,556,3.456,557,2.499,558,1.989,559,2.879,560,2.879,561,3.456,562,2.879,563,2.879,564,3.456]],["t/558",[3,0.145,7,1.174,9,0.442,10,1.063,17,1.063,19,0.884,20,0.967,22,1.308,23,1.14,24,0.967,25,0.967,46,1.388,47,0.81,48,1.063,51,0.33,52,1.308,58,0.265,62,1.7,69,1.7,70,0.81,87,1.063,101,1.7,104,1.475,128,1.047,130,1.243,135,2.007,141,0.884,145,1.174,147,1.308,156,0.884,172,1.475,174,1.475,178,1.356,201,1.475,204,1.063,217,1.063,220,1.308,240,0.884,254,1.475,259,1.475,262,1.7,286,1.308,298,1.174,299,1.308,303,1.475,359,1.308,361,1.308,375,1.475,386,1.475,388,1.7,409,1.475,411,0.884,417,1.308,420,1.7,431,1.647,438,1.063,440,1.308,441,1.475,442,1.7,443,2.193,448,1.356,450,1.174,471,1.7,481,1.7,490,1.475,497,1.475,507,2.542,511,1.063,513,1.475,519,1.475,520,2.007,521,1.174,534,1.174,537,1.7,539,1.7,541,1.7,543,1.308,544,1.308,547,1.7,548,1.7,549,1.475,550,2.609,557,1.475,565,1.7,566,2.04,567,2.04,568,2.04,569,2.04,570,2.04,571,2.04,572,2.04,573,2.04,574,2.04,575,2.04,576,1.985,577,2.04,578,2.04,579,1.7,580,2.04,581,2.04,582,2.04,583,2.04,584,2.04,585,2.04,586,3.131,587,2.04,588,2.04,589,2.04,590,1.7,591,2.04,592,2.04,593,1.7,594,2.04,595,2.04,596,2.04,597,2.04,598,2.04,599,1.7,600,2.04,601,2.04,602,2.04,603,2.04,604,2.04,605,2.04,606,2.04,607,2.04,608,2.04,609,2.04,610,1.7,611,1.7,612,1.7,613,1.7,614,1.7,615,2.04,616,2.04,617,1.7,618,1.7,619,2.04,620,2.04,621,2.04,622,2.04,623,3.131,624,2.04,625,2.04,626,2.04,627,2.04,628,2.04,629,1.475,630,2.04,631,1.7,632,2.04,633,1.7,634,2.04,635,2.04,636,1.7,637,2.04,638,2.04,639,2.04,640,1.475,641,2.04,642,2.04,643,2.04,644,2.04,645,1.475,646,2.04,647,1.7,648,1.475,649,1.7,650,2.04,651,2.04,652,2.04,653,1.7]],["t/560",[15,2.533,58,0.632,103,2.798,145,2.798,431,1.494,648,3.516,654,4.863,655,4.863,656,3.516]],["t/562",[3,0.077,7,1.07,9,0.403,20,1.382,21,0.882,23,1.481,26,1.192,28,1.192,46,1.309,47,1.157,51,0.759,58,0.529,74,0.969,86,2.108,87,0.969,88,1.07,95,1.263,113,0.882,125,1.55,128,1.36,141,1.263,149,1.345,156,0.806,173,1.07,213,1.345,216,1.192,217,0.969,226,1.868,235,1.192,240,0.806,244,1.345,261,1.345,274,0.969,277,1.345,284,2.428,298,1.07,301,2.068,302,1.55,306,1.345,322,1.345,333,1.345,334,1.345,358,1.192,400,1.55,401,2.304,407,2.068,417,1.192,431,1.44,438,0.969,443,2.068,448,2.03,449,0.738,456,2.697,463,1.55,483,1.345,500,1.345,507,0.882,511,1.518,524,1.192,525,1.55,527,1.55,534,1.677,543,1.192,549,1.345,558,1.677,576,2.119,599,1.55,613,1.55,629,1.345,640,2.108,647,1.55,656,1.345,657,2.915,658,1.86,659,1.86,660,1.86,661,1.86,662,1.55,663,1.86,664,3.129,665,1.86,666,1.86,667,1.55,668,1.86,669,1.86,670,1.86,671,1.345,672,1.192,673,2.428,674,3.904,675,2.428,676,2.428,677,1.55,678,1.55,679,2.428,680,2.994,681,2.108,682,2.428,683,1.55,684,1.55,685,2.994,686,2.428,687,2.108,688,1.55,689,1.55,690,1.55,691,1.55,692,1.55,693,1.55,694,1.345,695,1.55,696,1.55,697,1.55,698,1.55,699,3.389,700,1.55,701,1.55,702,1.55,703,1.55,704,1.86,705,1.86,706,1.86,707,1.55,708,1.86,709,1.86,710,1.86,711,1.86,712,1.86,713,1.55,714,1.86,715,1.55,716,1.86,717,1.55,718,1.55,719,1.86,720,1.86,721,1.86,722,1.55,723,1.86,724,2.915,725,1.86,726,3.195,727,1.86,728,1.55,729,2.108,730,1.345,731,1.86,732,1.86,733,1.55,734,1.86,735,1.86,736,1.86,737,1.86,738,2.428,739,1.86]],["t/564",[7,0.612,9,0.525,11,0.46,13,0.327,19,0.795,20,1.368,23,0.669,26,1.177,27,0.554,48,0.554,49,1.53,51,0.392,58,0.571,64,0.681,74,0.554,88,0.612,90,0.769,94,1.177,95,1.05,97,0.681,103,0.612,110,0.769,120,0.886,124,1.057,128,0.614,129,1.262,131,1.554,135,0.681,136,1.328,141,1.831,157,1.177,165,1.328,173,1.395,178,0.46,180,0.769,186,1.057,189,0.681,193,3.658,201,0.769,204,0.554,213,0.769,219,0.769,220,0.681,223,0.886,226,1.177,228,0.886,240,0.46,241,0.769,250,0.886,261,0.769,265,1.554,268,0.769,275,0.681,279,0.886,280,0.886,286,0.681,287,1.554,299,1.177,301,0.612,308,0.886,320,0.769,326,0.886,333,1.753,334,0.769,336,0.886,369,0.886,370,2.086,377,0.769,384,0.886,391,0.886,392,0.769,395,0.886,401,0.681,407,0.612,409,0.769,411,0.46,419,1.53,428,0.886,431,0.327,447,0.886,448,1.05,449,1.799,450,1.875,452,0.886,453,0.886,456,1.057,460,0.769,476,0.886,484,0.886,485,0.886,491,0.886,501,0.886,513,0.769,524,0.681,534,1.057,576,0.956,611,2.019,617,1.53,618,0.886,631,0.886,633,0.886,636,0.886,648,0.769,649,0.886,653,0.886,662,0.886,664,3.487,671,1.328,681,0.769,687,0.769,694,0.769,703,0.886,715,1.53,717,2.404,718,2.019,722,0.886,726,1.328,728,0.886,729,0.769,730,0.769,733,0.886,738,0.886,740,1.063,741,1.836,742,1.063,743,1.063,744,1.063,745,1.063,746,1.063,747,1.063,748,1.063,749,1.063,750,1.063,751,1.063,752,1.063,753,1.53,754,1.063,755,1.063,756,1.53,757,3.522,758,0.886,759,1.53,760,1.063,761,0.769,762,2.885,763,1.063,764,2.424,765,1.063,766,1.836,767,1.063,768,1.063,769,1.063,770,1.836,771,1.063,772,1.063,773,1.836,774,1.063,775,1.063,776,0.886,777,1.063,778,1.063,779,1.063,780,1.063,781,1.063,782,1.063,783,0.681,784,2.424,785,1.063,786,0.886,787,1.063,788,1.063,789,1.063,790,1.063,791,0.886,792,1.063,793,1.836,794,2.424,795,1.836,796,1.063,797,2.424,798,1.063,799,1.063,800,1.063,801,3.564,802,1.836,803,1.063,804,1.836,805,1.063,806,1.063,807,1.063,808,1.063,809,1.063,810,1.063,811,1.836,812,1.063,813,1.063,814,1.063,815,1.063,816,1.063,817,1.063,818,1.063,819,1.063,820,1.063,821,1.063,822,1.063,823,1.063,824,1.063,825,1.063,826,1.063,827,1.063,828,1.063,829,1.063,830,1.063,831,1.063,832,1.063,833,1.063,834,1.063,835,1.063,836,1.063,837,1.063,838,1.063,839,1.063,840,1.063,841,1.063,842,1.063,843,1.063,844,1.063,845,1.063,846,1.063,847,1.063,848,1.063,849,1.063,850,1.063,851,1.063,852,1.063,853,1.063,854,1.063,855,1.063,856,2.424,857,1.063,858,1.063,859,1.063,860,1.063,861,2.424,862,3.258,863,1.063,864,1.836,865,1.063,866,1.063,867,1.063,868,1.063,869,1.063,870,1.063,871,1.063,872,1.836,873,1.063,874,1.063,875,2.885,876,1.063,877,1.063,878,1.836,879,1.836,880,1.063,881,1.063,882,1.063,883,2.424,884,2.885,885,1.836,886,0.886,887,1.063,888,1.063,889,1.063,890,2.424,891,1.063,892,1.063,893,1.063,894,1.063,895,1.063,896,1.063,897,1.063,898,1.063,899,0.886,900,1.063,901,1.063,902,1.063,903,1.063,904,1.063,905,1.063,906,1.063,907,2.424,908,1.063,909,1.063,910,1.063,911,1.063,912,0.886,913,1.063,914,1.836,915,1.063,916,1.063,917,1.063,918,1.836,919,1.063,920,1.836,921,1.836,922,2.424,923,1.836,924,1.836,925,1.836,926,1.836,927,2.424,928,2.424,929,1.063,930,1.063,931,1.063,932,1.836,933,1.063,934,1.063,935,1.063,936,1.063]],["t/566",[0,1.915,3,0.118,9,0.72,10,1.733,21,1.578,30,2.133,40,2.133,42,2.406,43,1.915,44,1.915,70,1.321,73,1.733,111,2.772,112,1.442,129,1.733,141,1.442,217,1.733,219,2.406,239,2.406,274,1.733,301,1.915,363,2.772,404,2.406,411,1.442,421,2.772,427,2.406,431,1.368,449,1.321,495,2.406,517,2.772,518,2.772,520,3.218,521,2.563,523,2.772,530,2.772,531,2.772,563,2.772,565,2.772,590,2.772,645,2.406,656,3.221,761,2.406,937,3.328,938,3.328,939,2.772,940,3.328,941,3.328,942,2.772,943,3.328,944,3.328,945,3.328,946,2.406,947,3.328,948,3.328,949,3.328,950,3.328,951,3.328,952,3.328,953,3.328,954,3.328,955,3.328,956,2.772,957,3.328,958,3.328,959,3.328,960,3.328,961,2.772,962,3.328,963,2.406,964,3.328,965,2.406]],["t/568",[3,0.117,25,2.08,46,1.597,51,0.71,71,2.285,218,2.812,313,3.172,358,2.812,511,2.285,528,2.812,672,2.812,946,3.172,963,3.172,966,3.172,967,3.654,968,3.654,969,3.654,970,3.172,971,3.654,972,3.654,973,3.172,974,3.654,975,3.654,976,4.387,977,4.387]],["t/592",[3,0.127,19,1.606,21,1.757,23,1.35,24,1.757,47,1.471,52,2.376,58,0.482,70,1.897,75,3.457,128,1.239,130,1.471,157,2.376,240,1.606,348,2.376,349,2.68,431,1.469,438,1.93,440,2.376,449,1.471,458,3.088,488,3.088,497,2.68,500,2.68,507,1.757,520,3.065,538,3.088,543,2.376,558,2.133,645,2.68,702,3.088,783,2.376,978,3.706,979,3.706,980,3.706,981,3.706,982,3.706,983,3.706,984,3.706,985,3.706,986,3.706,987,3.706,988,3.706,989,3.706,990,3.706,991,3.706,992,3.706,993,3.706,994,3.088,995,3.706]],["t/594",[2,2.32,3,0.105,9,0.603,17,1.451,21,1.868,22,1.785,23,1.435,46,1.435,47,1.105,51,0.451,52,1.785,58,0.512,64,1.785,73,1.451,76,1.785,93,1.785,128,0.931,141,1.206,175,3.306,178,1.206,204,1.451,220,1.785,251,3.282,255,2.014,264,1.451,275,1.785,276,2.014,304,2.014,310,1.785,359,1.785,370,2.014,379,2.014,383,1.785,397,2.32,404,2.014,434,2.32,441,2.014,448,1.707,449,1.105,450,1.603,456,1.603,460,2.849,465,2.014,483,2.849,495,2.849,534,1.603,544,1.785,558,1.603,576,2.732,579,2.32,593,2.32,610,2.32,753,2.32,783,1.785,965,2.014,996,2.785,997,2.785,998,2.785,999,2.785,1000,2.785,1001,2.785,1002,2.785,1003,2.785,1004,2.785,1005,2.785,1006,2.785,1007,2.785,1008,4.572,1009,2.785,1010,2.785,1011,2.785,1012,2.785,1013,2.785,1014,2.785,1015,2.785,1016,2.785,1017,2.785,1018,2.785,1019,2.32,1020,2.785,1021,2.785,1022,2.785,1023,3.94,1024,2.785,1025,2.785,1026,2.785,1027,2.785,1028,2.785,1029,2.785,1030,2.785,1031,2.32,1032,2.785,1033,2.785,1034,2.785,1035,2.785,1036,2.785,1037,2.785,1038,2.785,1039,2.785,1040,2.32]],["t/596",[3,0.052,6,1.626,9,0.422,20,1.434,22,1.251,23,1.645,31,1.411,44,1.123,46,1.349,73,1.016,86,1.411,95,0.845,110,1.411,113,0.925,118,1.251,128,1.239,140,1.626,159,1.411,178,0.845,180,1.411,208,1.626,230,1.626,235,1.251,237,1.626,240,0.845,274,1.016,277,1.411,297,1.251,322,1.411,359,1.251,371,1.626,376,1.411,401,2.375,407,1.741,410,2.188,417,1.251,431,1.468,438,1.576,443,2.6,448,1.311,449,0.774,456,2.87,464,1.411,496,1.626,507,0.925,521,1.123,524,1.251,557,2.188,576,1.576,612,1.626,614,1.626,629,1.411,640,1.411,664,1.741,667,1.626,671,1.411,672,1.251,673,2.521,674,3.764,675,1.626,676,3.087,677,1.626,678,1.626,679,2.521,680,3.087,681,2.188,682,2.521,683,1.626,684,1.626,685,3.087,686,2.521,687,2.188,688,1.626,689,1.626,690,1.626,691,1.626,692,1.626,693,1.626,694,3.606,695,1.626,696,1.626,697,1.626,698,2.521,699,3.478,700,1.626,701,1.626,713,1.626,730,2.188,761,1.411,776,1.626,783,1.939,786,1.626,791,1.626,912,1.626,965,1.411,1019,1.626,1031,1.626,1040,1.626,1041,1.951,1042,1.951,1043,1.951,1044,1.951,1045,1.951,1046,1.951,1047,3.706,1048,4.175,1049,1.951,1050,3.026,1051,1.951,1052,1.951,1053,3.706,1054,3.706,1055,3.026,1056,1.951,1057,4.175,1058,1.951,1059,1.951,1060,1.951,1061,1.951,1062,1.951,1063,1.951,1064,3.026,1065,3.026,1066,1.951,1067,1.951,1068,1.951,1069,1.951,1070,1.951,1071,1.951,1072,1.951,1073,1.951,1074,3.026,1075,1.951,1076,1.951,1077,1.951,1078,1.951,1079,1.951,1080,1.951,1081,1.951,1082,1.951,1083,1.951,1084,1.951,1085,1.951,1086,1.951,1087,1.951,1088,1.951,1089,1.951,1090,1.951,1091,1.951,1092,1.951,1093,1.951]],["t/598",[3,0.115,7,1.83,10,1.656,13,0.977,27,1.656,51,0.699,53,1.508,56,2.649,58,0.561,70,1.262,88,1.83,128,1.064,130,1.262,157,2.039,186,1.83,240,1.378,274,1.656,305,2.649,306,2.3,360,2.649,380,2.3,392,2.3,407,1.83,411,1.378,431,0.977,438,1.656,440,2.039,443,1.83,450,2.823,470,2.649,475,2.649,507,1.508,511,1.656,514,2.649,521,1.83,528,2.039,529,2.649,532,2.649,544,2.039,552,2.649,707,2.649,899,2.649,961,2.649,970,2.3,1094,3.18,1095,3.18,1096,3.18,1097,3.18,1098,3.18,1099,3.18,1100,3.18,1101,3.18,1102,3.18,1103,3.18,1104,3.18,1105,3.18,1106,3.18,1107,3.18,1108,3.18,1109,3.18,1110,2.649,1111,3.18,1112,3.18,1113,3.18,1114,3.18,1115,3.18,1116,3.18,1117,3.18,1118,3.18,1119,2.649,1120,3.18,1121,3.18,1122,3.18,1123,3.18,1124,3.18,1125,3.18,1126,3.18,1127,3.18,1128,3.18,1129,3.18,1130,3.18,1131,3.18]],["t/600",[3,0.134,10,2.093,24,1.905,46,1.463,70,1.594,112,1.741,141,1.741,177,4.192,178,1.741,179,3.347,199,2.905,349,2.905,427,2.905,431,1.546,490,2.905,559,3.347,560,3.347,562,3.347,664,2.312,939,3.347,942,3.347,956,3.347,966,2.905,994,3.347,1110,3.347,1119,3.347,1132,4.018,1133,4.018,1134,4.018,1135,4.018,1136,4.018,1137,4.018,1138,4.018,1139,4.018,1140,4.018,1141,4.018,1142,4.018]],["t/602",[3,0.1,13,1.156,19,1.631,20,2.528,21,1.785,58,0.489,75,3.492,76,2.413,173,2.166,310,2.413,348,2.413,431,1.483,448,1.631,449,1.494,558,2.166,576,1.961,664,3.562,726,2.722,729,2.722,756,4.442,757,3.136,758,3.136,759,3.136,886,4.023,973,2.722,1143,3.765,1144,4.829,1145,4.829,1146,3.765,1147,3.765,1148,3.765,1149,3.765,1150,3.765,1151,3.765,1152,3.765]],["t/604",[3,0.117,25,2.092,46,1.607,51,0.714,71,2.299,218,2.829,313,3.192,358,2.829,511,2.299,528,2.829,672,2.829,946,3.192,963,3.192,966,3.192,967,3.677,968,3.677,969,3.677,970,3.192,971,3.677,972,3.677,973,3.192,974,3.677,975,3.677,1153,4.414]]],"invertedIndex":[["",{"_index":664,"t":{"562":{"position":[[206,2],[510,1],[563,1],[1007,3],[1716,2],[1972,1],[1981,1],[2011,1],[2021,1],[2058,1],[2076,1]]},"564":{"position":[[452,1],[486,1],[514,1],[542,2],[545,2],[548,1],[567,2],[590,3],[594,2],[615,1],[647,2],[666,2],[684,5],[690,2],[1103,1],[1140,2],[1196,2],[1440,2],[1496,1],[1504,2],[1578,1],[1861,1],[1924,2],[1940,2],[2615,2],[2700,1],[2715,2],[2724,2],[3789,1],[3836,2],[3885,1],[3917,3],[3932,1],[3979,3],[4175,2],[4648,1],[4700,2],[4793,1],[4795,2],[5003,1],[5050,2],[5137,1],[5161,2],[5199,1],[5223,1],[5225,2],[5257,1],[5259,2]]},"596":{"position":[[1321,1],[1374,1]]},"600":{"position":[[340,2]]},"602":{"position":[[184,1],[205,1],[245,1],[262,1],[291,2],[307,3],[311,2],[353,1],[455,2]]}}}],["0",{"_index":873,"t":{"564":{"position":[[3338,4]]}}}],["0.12ms",{"_index":689,"t":{"562":{"position":[[758,7]]},"596":{"position":[[1569,7]]}}}],["0.5ms",{"_index":691,"t":{"562":{"position":[[778,6]]},"596":{"position":[[1589,6]]}}}],["1",{"_index":68,"t":{"524":{"position":[[141,2]]},"534":{"position":[[19,1]]}}}],["100",{"_index":862,"t":{"564":{"position":[[2975,4],[2980,4],[3315,3],[3826,4],[3831,4]]}}}],["100k",{"_index":680,"t":{"562":{"position":[[672,4],[728,4],[791,4]]},"596":{"position":[[1483,4],[1539,4],[1602,4]]}}}],["108",{"_index":696,"t":{"562":{"position":[[843,4]]},"596":{"position":[[1654,4]]}}}],["12",{"_index":49,"t":{"522":{"position":[[635,2]]},"564":{"position":[[4776,3],[5127,3]]}}}],["150",{"_index":871,"t":{"564":{"position":[[3325,3]]}}}],["15ms",{"_index":683,"t":{"562":{"position":[[702,5]]},"596":{"position":[[1513,5]]}}}],["1700",{"_index":692,"t":{"562":{"position":[[785,5]]},"596":{"position":[[1596,5]]}}}],["17417",{"_index":690,"t":{"562":{"position":[[766,6]]},"596":{"position":[[1577,6]]}}}],["2",{"_index":311,"t":{"536":{"position":[[6,1]]}}}],["21ms",{"_index":688,"t":{"562":{"position":[[752,5]]},"596":{"position":[[1563,5]]}}}],["233",{"_index":684,"t":{"562":{"position":[[708,4]]},"596":{"position":[[1519,4]]}}}],["24ms",{"_index":695,"t":{"562":{"position":[[837,5]]},"596":{"position":[[1648,5]]}}}],["27",{"_index":1025,"t":{"594":{"position":[[825,3]]}}}],["2d",{"_index":19,"t":{"522":{"position":[[239,2]]},"548":{"position":[[114,2]]},"550":{"position":[[208,2]]},"556":{"position":[[513,2]]},"558":{"position":[[2234,2]]},"564":{"position":[[845,2],[2864,2]]},"592":{"position":[[62,2]]},"602":{"position":[[438,2]]}}}],["2d/3d",{"_index":499,"t":{"552":{"position":[[173,5]]}}}],["2ms",{"_index":686,"t":{"562":{"position":[[718,4],[853,4]]},"596":{"position":[[1529,4],[1664,4]]}}}],["3",{"_index":159,"t":{"530":{"position":[[343,1]]},"542":{"position":[[6,1]]},"596":{"position":[[1137,2]]}}}],["300",{"_index":872,"t":{"564":{"position":[[3330,3],[3334,3]]}}}],["350",{"_index":687,"t":{"562":{"position":[[723,4],[858,4]]},"564":{"position":[[3319,3]]},"596":{"position":[[1534,4],[1669,4]]}}}],["3d",{"_index":476,"t":{"550":{"position":[[52,2],[215,2],[269,3],[352,2]]},"564":{"position":[[982,2]]}}}],["3rd",{"_index":139,"t":{"528":{"position":[[622,3]]}}}],["4",{"_index":468,"t":{"548":{"position":[[6,1]]}}}],["50",{"_index":861,"t":{"564":{"position":[[2971,3],[3817,3],[3822,3]]}}}],["50ms",{"_index":682,"t":{"562":{"position":[[696,5],[831,5]]},"596":{"position":[[1507,5],[1642,5]]}}}],["8",{"_index":1037,"t":{"594":{"position":[[1103,1]]}}}],["95",{"_index":1020,"t":{"594":{"position":[[704,3]]}}}],["9ms",{"_index":685,"t":{"562":{"position":[[713,4],[773,4],[848,4]]},"596":{"position":[[1524,4],[1584,4],[1659,4]]}}}],["ability",{"_index":276,"t":{"534":{"position":[[420,7]]},"540":{"position":[[457,7]]},"594":{"position":[[477,7]]}}}],["above",{"_index":527,"t":{"554":{"position":[[480,5]]},"562":{"position":[[199,6]]}}}],["accelerated",{"_index":772,"t":{"564":{"position":[[857,11]]}}}],["achieves",{"_index":1038,"t":{"594":{"position":[[1105,8]]}}}],["acknowledgment",{"_index":583,"t":{"558":{"position":[[525,14]]}}}],["adapt",{"_index":228,"t":{"532":{"position":[[782,5]]},"564":{"position":[[4054,5]]}}}],["add",{"_index":611,"t":{"558":{"position":[[1200,3]]},"564":{"position":[[1543,3],[1580,5],[4414,3]]}}}],["added",{"_index":811,"t":{"564":{"position":[[1956,5],[3279,6]]}}}],["adding",{"_index":101,"t":{"526":{"position":[[437,6]]},"558":{"position":[[911,6]]}}}],["addition",{"_index":412,"t":{"544":{"position":[[729,9]]},"546":{"position":[[763,8]]}}}],["additional",{"_index":1105,"t":{"598":{"position":[[322,10]]}}}],["address",{"_index":39,"t":{"522":{"position":[[520,7]]}}}],["addressed",{"_index":626,"t":{"558":{"position":[[1605,9]]}}}],["adjusted",{"_index":243,"t":{"532":{"position":[[1083,8]]}}}],["adopters",{"_index":951,"t":{"566":{"position":[[319,8]]}}}],["advanced",{"_index":455,"t":{"546":{"position":[[649,8]]}}}],["advancements",{"_index":543,"t":{"556":{"position":[[211,13]]},"558":{"position":[[1361,12]]},"562":{"position":[[1702,13]]},"592":{"position":[[398,12]]}}}],["advantage",{"_index":454,"t":{"546":{"position":[[636,9]]}}}],["advisable",{"_index":838,"t":{"564":{"position":[[2517,9]]}}}],["agnostic",{"_index":267,"t":{"534":{"position":[[205,8],[1061,8]]}}}],["aim",{"_index":597,"t":{"558":{"position":[[815,3]]}}}],["aims",{"_index":264,"t":{"534":{"position":[[146,4]]},"536":{"position":[[31,4]]},"540":{"position":[[143,4],[731,4]]},"544":{"position":[[32,4]]},"552":{"position":[[122,4]]},"594":{"position":[[928,4]]}}}],["allow",{"_index":898,"t":{"564":{"position":[[4219,5]]}}}],["allowed",{"_index":1014,"t":{"594":{"position":[[579,7]]}}}],["allowing",{"_index":204,"t":{"532":{"position":[[353,8],[874,8]]},"538":{"position":[[306,8]]},"544":{"position":[[237,8]]},"558":{"position":[[756,8]]},"564":{"position":[[3425,8]]},"594":{"position":[[972,8]]}}}],["allows",{"_index":136,"t":{"528":{"position":[[579,6]]},"544":{"position":[[800,6]]},"564":{"position":[[2019,6],[3582,6]]}}}],["alpha",{"_index":792,"t":{"564":{"position":[[1297,6]]}}}],["already",{"_index":638,"t":{"558":{"position":[[1832,7]]}}}],["alternatively",{"_index":972,"t":{"568":{"position":[[139,14]]},"604":{"position":[[140,14]]}}}],["although",{"_index":488,"t":{"550":{"position":[[381,8]]},"592":{"position":[[114,8]]}}}],["always",{"_index":22,"t":{"522":{"position":[[275,6]]},"558":{"position":[[553,6]]},"594":{"position":[[988,6]]},"596":{"position":[[11,6]]}}}],["amazing",{"_index":14,"t":{"522":{"position":[[171,7]]}}}],["animation",{"_index":505,"t":{"552":{"position":[[438,9]]}}}],["animations",{"_index":402,"t":{"544":{"position":[[416,11],[622,11]]}}}],["announce",{"_index":1,"t":{"522":{"position":[[18,8]]},"526":{"position":[[27,8]]},"552":{"position":[[28,8]]}}}],["announcement",{"_index":55,"t":{"522":{"position":[[698,13]]}}}],["announcing",{"_index":258,"t":{"534":{"position":[[28,10]]}}}],["another",{"_index":220,"t":{"532":{"position":[[608,7]]},"558":{"position":[[1068,7]]},"564":{"position":[[1199,7]]},"594":{"position":[[1159,7]]}}}],["antialias:true",{"_index":810,"t":{"564":{"position":[[1909,14]]}}}],["antialiasing",{"_index":802,"t":{"564":{"position":[[1614,12],[1680,12]]}}}],["anticipating",{"_index":606,"t":{"558":{"position":[[1016,12]]}}}],["anyone",{"_index":176,"t":{"530":{"position":[[947,6]]}}}],["api",{"_index":450,"t":{"546":{"position":[[546,3]]},"558":{"position":[[2093,3]]},"564":{"position":[[2759,3],[2875,4],[4085,4],[5181,3],[5245,3]]},"594":{"position":[[189,4]]},"598":{"position":[[146,3],[350,3],[555,3]]}}}],["app",{"_index":334,"t":{"538":{"position":[[424,3]]},"562":{"position":[[1905,3]]},"564":{"position":[[510,3]]}}}],["app.init",{"_index":760,"t":{"564":{"position":[[556,10]]}}}],["appeal",{"_index":393,"t":{"544":{"position":[[196,6]]}}}],["appealing",{"_index":423,"t":{"544":{"position":[[980,10]]}}}],["appearance",{"_index":206,"t":{"532":{"position":[[379,10]]}}}],["application",{"_index":287,"t":{"534":{"position":[[636,12]]},"538":{"position":[[345,12],[795,11]]},"544":{"position":[[713,12]]},"564":{"position":[[454,12],[520,14],[570,11]]}}}],["applications",{"_index":32,"t":{"522":{"position":[[387,12]]},"528":{"position":[[78,13],[494,12]]},"534":{"position":[[102,13]]},"536":{"position":[[74,13]]},"538":{"position":[[110,13]]},"540":{"position":[[119,13],[374,12],[937,12],[1141,13]]},"544":{"position":[[96,12],[851,13],[1146,12]]},"546":{"position":[[746,13],[1168,13]]},"550":{"position":[[243,13],[619,13]]}}}],["applied",{"_index":795,"t":{"564":{"position":[[1392,7],[4973,7]]}}}],["apply",{"_index":667,"t":{"562":{"position":[[356,6]]},"596":{"position":[[1994,5]]}}}],["applying",{"_index":805,"t":{"564":{"position":[[1772,8]]}}}],["appreciate",{"_index":413,"t":{"544":{"position":[[755,10]]}}}],["approach",{"_index":783,"t":{"564":{"position":[[1039,8]]},"592":{"position":[[512,8]]},"594":{"position":[[570,8]]},"596":{"position":[[230,10],[455,9]]}}}],["appropriate",{"_index":1147,"t":{"602":{"position":[[113,11]]}}}],["apps",{"_index":16,"t":{"522":{"position":[[189,5]]}}}],["architecture",{"_index":1043,"t":{"596":{"position":[[105,12]]}}}],["areas",{"_index":471,"t":{"548":{"position":[[88,5]]},"558":{"position":[[1725,5]]}}}],["arguably",{"_index":578,"t":{"558":{"position":[[384,8]]}}}],["arise",{"_index":947,"t":{"566":{"position":[[233,6]]}}}],["aspect",{"_index":232,"t":{"532":{"position":[[818,6]]}}}],["aspects",{"_index":586,"t":{"558":{"position":[[615,7],[1641,7]]}}}],["assembled",{"_index":41,"t":{"522":{"position":[[545,9]]}}}],["asset",{"_index":259,"t":{"534":{"position":[[50,5]]},"552":{"position":[[390,5]]},"558":{"position":[[306,5]]}}}],["assetpack",{"_index":164,"t":{"530":{"position":[[507,10]]},"534":{"position":[[39,10],[128,9],[180,9],[403,9]]}}}],["assets",{"_index":272,"t":{"534":{"position":[[364,6],[458,6],[847,6]]},"544":{"position":[[1093,6]]}}}],["assured",{"_index":1123,"t":{"598":{"position":[[722,7]]}}}],["async",{"_index":758,"t":{"564":{"position":[[535,6]]},"602":{"position":[[223,5]]}}}],["asynchronous",{"_index":741,"t":{"564":{"position":[[50,13],[374,12]]}}}],["attempt",{"_index":1010,"t":{"594":{"position":[[494,7]]}}}],["audio",{"_index":293,"t":{"534":{"position":[[739,5]]},"552":{"position":[[367,6]]}}}],["autodetectrenderer",{"_index":756,"t":{"564":{"position":[[467,18],[623,23]]},"602":{"position":[[148,18],[186,18],[270,20]]}}}],["automatically",{"_index":277,"t":{"534":{"position":[[431,13]]},"562":{"position":[[1238,13]]},"596":{"position":[[2089,13]]}}}],["available",{"_index":175,"t":{"530":{"position":[[923,9]]},"546":{"position":[[904,9]]},"594":{"position":[[370,9],[691,9],[1024,9]]}}}],["avoid",{"_index":840,"t":{"564":{"position":[[2550,5]]}}}],["avoids",{"_index":1070,"t":{"596":{"position":[[985,6]]}}}],["await",{"_index":759,"t":{"564":{"position":[[550,5],[617,5]]},"602":{"position":[[264,5]]}}}],["away",{"_index":350,"t":{"538":{"position":[[854,5]]}}}],["b",{"_index":1013,"t":{"594":{"position":[[562,2]]}}}],["back",{"_index":593,"t":{"558":{"position":[[733,4]]},"594":{"position":[[537,4]]}}}],["backend",{"_index":703,"t":{"562":{"position":[[1055,7]]},"564":{"position":[[215,7]]}}}],["bad",{"_index":1138,"t":{"600":{"position":[[225,4]]}}}],["bandwidth",{"_index":1078,"t":{"596":{"position":[[1176,9]]}}}],["bar",{"_index":665,"t":{"562":{"position":[[225,4]]}}}],["bars",{"_index":200,"t":{"532":{"position":[[258,5]]}}}],["based",{"_index":698,"t":{"562":{"position":[[891,5]]},"596":{"position":[[634,5],[1702,5]]}}}],["batch",{"_index":713,"t":{"562":{"position":[[1420,5]]},"596":{"position":[[431,5]]}}}],["batches",{"_index":1052,"t":{"596":{"position":[[377,7]]}}}],["batching",{"_index":367,"t":{"540":{"position":[[518,8],[538,8]]}}}],["beautiful",{"_index":184,"t":{"532":{"position":[[61,9]]},"556":{"position":[[630,9]]}}}],["become",{"_index":336,"t":{"538":{"position":[[454,6]]},"564":{"position":[[2788,6]]}}}],["becomes",{"_index":1029,"t":{"594":{"position":[[890,7]]}}}],["before",{"_index":1028,"t":{"594":{"position":[[880,6]]}}}],["begin",{"_index":312,"t":{"536":{"position":[[13,5]]}}}],["behaviour",{"_index":207,"t":{"532":{"position":[[394,9]]}}}],["behind",{"_index":1095,"t":{"598":{"position":[[26,6]]}}}],["being",{"_index":317,"t":{"538":{"position":[[28,5]]},"546":{"position":[[558,5]]}}}],["believe",{"_index":566,"t":{"558":{"position":[[13,7]]}}}],["below",{"_index":660,"t":{"562":{"position":[[82,5]]}}}],["benchmark",{"_index":697,"t":{"562":{"position":[[869,9]]},"596":{"position":[[1680,9]]}}}],["benefit",{"_index":1016,"t":{"594":{"position":[[617,7]]}}}],["best",{"_index":359,"t":{"540":{"position":[[257,4]]},"558":{"position":[[673,4]]},"594":{"position":[[1008,4]]},"596":{"position":[[1962,4]]}}}],["beta",{"_index":75,"t":{"526":{"position":[[64,4]]},"592":{"position":[[87,4],[147,4]]},"602":{"position":[[28,4],[390,4]]}}}],["better",{"_index":301,"t":{"534":{"position":[[880,6]]},"546":{"position":[[1113,6]]},"562":{"position":[[1104,6],[1492,6],[1878,6]]},"564":{"position":[[4229,6]]},"566":{"position":[[279,7]]}}}],["between",{"_index":485,"t":{"550":{"position":[[307,7]]},"564":{"position":[[4594,7]]}}}],["big",{"_index":958,"t":{"566":{"position":[[536,3]]}}}],["bigger",{"_index":595,"t":{"558":{"position":[[779,6]]}}}],["bitmap",{"_index":914,"t":{"564":{"position":[[4496,6],[4685,6]]}}}],["bitmapfonts",{"_index":902,"t":{"564":{"position":[[4317,11]]}}}],["bitmaptext",{"_index":919,"t":{"564":{"position":[[4654,12]]}}}],["blend",{"_index":717,"t":{"562":{"position":[[1462,5]]},"564":{"position":[[1235,5],[1411,5],[1547,5],[2602,5]]}}}],["blog",{"_index":305,"t":{"534":{"position":[[962,4]]},"598":{"position":[[275,4]]}}}],["board",{"_index":643,"t":{"558":{"position":[[1942,5]]}}}],["boosting",{"_index":637,"t":{"558":{"position":[[1800,8]]}}}],["both",{"_index":235,"t":{"532":{"position":[[898,4]]},"544":{"position":[[206,4],[246,4],[951,4]]},"562":{"position":[[266,4]]},"596":{"position":[[130,4]]}}}],["boundaries",{"_index":531,"t":{"556":{"position":[[22,10]]},"566":{"position":[[652,10]]}}}],["breaking",{"_index":646,"t":{"558":{"position":[[2084,8]]}}}],["breaks",{"_index":714,"t":{"562":{"position":[[1426,7]]}}}],["brouwer",{"_index":122,"t":{"528":{"position":[[168,8]]}}}],["browser",{"_index":352,"t":{"540":{"position":[[27,7]]},"546":{"position":[[583,7]]}}}],["browsers",{"_index":1008,"t":{"594":{"position":[[404,9],[711,9],[804,8]]}}}],["bubble",{"_index":160,"t":{"530":{"position":[[356,6]]}}}],["bubbo",{"_index":183,"t":{"530":{"position":[[1053,5],[1059,5]]}}}],["bugs",{"_index":1140,"t":{"600":{"position":[[250,5]]}}}],["build",{"_index":31,"t":{"522":{"position":[[371,5]]},"540":{"position":[[895,5]]},"596":{"position":[[371,5]]}}}],["buildgeometryfrompath",{"_index":885,"t":{"564":{"position":[[3673,21],[3887,23]]}}}],["building",{"_index":332,"t":{"538":{"position":[[331,8],[839,8]]},"546":{"position":[[1131,8]]},"550":{"position":[[604,8]]}}}],["built",{"_index":478,"t":{"550":{"position":[[83,5]]}}}],["bump",{"_index":1059,"t":{"596":{"position":[[729,5]]}}}],["bundle",{"_index":463,"t":{"546":{"position":[[971,6]]},"562":{"position":[[1931,6]]}}}],["bundlers",{"_index":327,"t":{"538":{"position":[[237,8],[555,8]]}}}],["bunnies",{"_index":1089,"t":{"596":{"position":[[1909,8]]}}}],["bunny",{"_index":677,"t":{"562":{"position":[[612,5]]},"596":{"position":[[1423,5]]}}}],["bunnymark",{"_index":699,"t":{"562":{"position":[[904,9],[949,9],[962,9],[984,9]]},"596":{"position":[[1716,9],[1761,9],[1774,9],[1796,9]]}}}],["burn",{"_index":849,"t":{"564":{"position":[[2709,5]]}}}],["buttons",{"_index":190,"t":{"532":{"position":[[166,8],[236,8]]}}}],["call",{"_index":372,"t":{"540":{"position":[[630,5]]}}}],["called",{"_index":144,"t":{"530":{"position":[[38,6]]},"550":{"position":[[62,6]]},"552":{"position":[[78,6]]}}}],["camera",{"_index":773,"t":{"564":{"position":[[869,7],[964,6]]}}}],["candidate",{"_index":1132,"t":{"600":{"position":[[34,10]]}}}],["canvas",{"_index":460,"t":{"546":{"position":[[873,6]]},"564":{"position":[[2857,6]]},"594":{"position":[[420,6],[545,6]]}}}],["capabilities",{"_index":8,"t":{"522":{"position":[[99,12]]},"546":{"position":[[662,13]]},"550":{"position":[[544,12]]}}}],["care",{"_index":669,"t":{"562":{"position":[[401,4]]}}}],["cases",{"_index":1083,"t":{"596":{"position":[[1310,6]]}}}],["catalysed",{"_index":991,"t":{"592":{"position":[[498,9]]}}}],["causing",{"_index":37,"t":{"522":{"position":[[477,7]]}}}],["celebrating",{"_index":536,"t":{"556":{"position":[[111,11]]}}}],["certain",{"_index":628,"t":{"558":{"position":[[1633,7]]}}}],["change",{"_index":730,"t":{"562":{"position":[[1848,6]]},"564":{"position":[[1216,6]]},"596":{"position":[[1093,6],[1888,6]]}}}],["changed",{"_index":1055,"t":{"596":{"position":[[506,7],[780,8]]}}}],["changer",{"_index":535,"t":{"556":{"position":[[102,8]]}}}],["changes",{"_index":240,"t":{"532":{"position":[[989,8]]},"542":{"position":[[78,7]]},"558":{"position":[[2097,8]]},"562":{"position":[[153,8]]},"564":{"position":[[2777,7]]},"592":{"position":[[360,7]]},"596":{"position":[[681,7]]},"598":{"position":[[612,7]]}}}],["changing",{"_index":693,"t":{"562":{"position":[[804,9]]},"596":{"position":[[1615,9]]}}}],["characters",{"_index":908,"t":{"564":{"position":[[4418,10]]}}}],["chats",{"_index":977,"t":{"568":{"position":[[241,6]]}}}],["check",{"_index":216,"t":{"532":{"position":[[528,5]]},"534":{"position":[[1126,5]]},"554":{"position":[[443,5]]},"562":{"position":[[478,5]]}}}],["checkboxes",{"_index":191,"t":{"532":{"position":[[175,11]]}}}],["cherry",{"_index":845,"t":{"564":{"position":[[2660,6]]}}}],["children",{"_index":794,"t":{"564":{"position":[[1364,8],[1461,8],[1525,8]]}}}],["chit",{"_index":976,"t":{"568":{"position":[[236,4]]}}}],["class",{"_index":447,"t":{"546":{"position":[[496,5],[925,5]]},"564":{"position":[[3491,5]]}}}],["classics",{"_index":814,"t":{"564":{"position":[[2098,9]]}}}],["cleaner",{"_index":621,"t":{"558":{"position":[[1504,7]]}}}],["clear",{"_index":603,"t":{"558":{"position":[[956,6]]}}}],["cli",{"_index":315,"t":{"538":{"position":[[19,3],[393,3]]}}}],["closely",{"_index":851,"t":{"564":{"position":[[2829,7]]}}}],["code",{"_index":178,"t":{"530":{"position":[[979,4]]},"540":{"position":[[240,5],[1097,4]]},"544":{"position":[[545,5]]},"558":{"position":[[630,4],[928,5]]},"564":{"position":[[146,4]]},"594":{"position":[[1066,5]]},"596":{"position":[[814,4]]},"600":{"position":[[276,5]]}}}],["codebase",{"_index":994,"t":{"592":{"position":[[543,8]]},"600":{"position":[[391,8]]}}}],["collaborate",{"_index":396,"t":{"544":{"position":[[261,11]]}}}],["collaboration",{"_index":425,"t":{"544":{"position":[[1022,14]]}}}],["collection",{"_index":146,"t":{"530":{"position":[[76,10]]}}}],["color",{"_index":848,"t":{"564":{"position":[[2702,6]]}}}],["color:'blue",{"_index":936,"t":{"564":{"position":[[5210,12]]}}}],["color:'red",{"_index":934,"t":{"564":{"position":[[5148,12]]}}}],["colorblend",{"_index":815,"t":{"564":{"position":[[2108,11]]}}}],["colorburnblend",{"_index":816,"t":{"564":{"position":[[2120,15]]}}}],["colordodgeblend",{"_index":817,"t":{"564":{"position":[[2136,16]]}}}],["combine",{"_index":234,"t":{"532":{"position":[[890,7]]}}}],["comet",{"_index":389,"t":{"544":{"position":[[0,5],[332,5],[1037,5]]}}}],["coming",{"_index":512,"t":{"554":{"position":[[67,6]]}}}],["committed",{"_index":943,"t":{"566":{"position":[[163,9]]}}}],["community",{"_index":25,"t":{"522":{"position":[[313,9],[506,10]]},"528":{"position":[[218,9]]},"538":{"position":[[498,10]]},"554":{"position":[[88,10],[207,10]]},"558":{"position":[[66,9]]},"568":{"position":[[179,9]]},"604":{"position":[[180,9]]}}}],["compared",{"_index":442,"t":{"546":{"position":[[382,8]]},"558":{"position":[[1948,8]]}}}],["compatibility",{"_index":651,"t":{"558":{"position":[[2151,13]]}}}],["compilation",{"_index":734,"t":{"562":{"position":[[1909,12]]}}}],["complete",{"_index":433,"t":{"546":{"position":[[59,8]]}}}],["complex",{"_index":236,"t":{"532":{"position":[[923,8]]},"540":{"position":[[499,7]]},"544":{"position":[[484,7]]},"546":{"position":[[1140,8]]}}}],["complexity",{"_index":630,"t":{"558":{"position":[[1669,10]]}}}],["complicated",{"_index":323,"t":{"538":{"position":[[181,11]]},"540":{"position":[[656,11]]}}}],["components",{"_index":138,"t":{"528":{"position":[[607,10]]},"532":{"position":[[147,10],[317,10]]}}}],["comprehensive",{"_index":1108,"t":{"598":{"position":[[429,13]]}}}],["compress",{"_index":294,"t":{"534":{"position":[[767,8]]}}}],["computational",{"_index":1064,"t":{"596":{"position":[[843,13],[1207,13]]}}}],["computations",{"_index":1000,"t":{"594":{"position":[[109,12]]}}}],["concept",{"_index":763,"t":{"564":{"position":[[713,7]]}}}],["conform",{"_index":929,"t":{"564":{"position":[[4825,7]]}}}],["considering",{"_index":105,"t":{"526":{"position":[[481,11]]}}}],["const",{"_index":757,"t":{"564":{"position":[[504,5],[600,5],[1087,5],[1847,5],[3778,5],[3870,5],[3921,5],[4635,5],[4990,5]]},"602":{"position":[[247,5]]}}}],["construct",{"_index":1056,"t":{"596":{"position":[[543,9]]}}}],["consuming",{"_index":366,"t":{"540":{"position":[[391,10]]}}}],["container",{"_index":784,"t":{"564":{"position":[[1093,9],[1109,11],[1341,10]]}}}],["container.blendmode",{"_index":800,"t":{"564":{"position":[[1558,19]]}}}],["container.tint",{"_index":798,"t":{"564":{"position":[[1481,14]]}}}],["containers",{"_index":766,"t":{"564":{"position":[[768,10],[1148,10]]}}}],["content",{"_index":483,"t":{"550":{"position":[[218,7]]},"562":{"position":[[1185,8]]},"594":{"position":[[259,7],[594,7]]}}}],["context",{"_index":854,"t":{"564":{"position":[[2867,7]]}}}],["continuation",{"_index":119,"t":{"528":{"position":[[110,12]]}}}],["continue",{"_index":961,"t":{"566":{"position":[[618,8]]},"598":{"position":[[759,8]]}}}],["continuing",{"_index":522,"t":{"554":{"position":[[379,10]]}}}],["contribute",{"_index":179,"t":{"530":{"position":[[987,10]]},"600":{"position":[[265,10]]}}}],["contributions",{"_index":940,"t":{"566":{"position":[[101,14]]}}}],["contributors",{"_index":518,"t":{"554":{"position":[[222,13]]},"566":{"position":[[394,12]]}}}],["conversations",{"_index":1153,"t":{"604":{"position":[[237,14]]}}}],["convert",{"_index":290,"t":{"534":{"position":[[695,7],[731,7]]}}}],["cool",{"_index":788,"t":{"564":{"position":[[1207,4]]}}}],["core",{"_index":135,"t":{"528":{"position":[[551,4]]},"546":{"position":[[230,4]]},"558":{"position":[[889,4],[1247,4]]},"564":{"position":[[2503,5]]}}}],["correct",{"_index":1049,"t":{"596":{"position":[[303,7]]}}}],["correcting",{"_index":622,"t":{"558":{"position":[[1531,10]]}}}],["course",{"_index":985,"t":{"592":{"position":[[274,6]]}}}],["covered",{"_index":659,"t":{"562":{"position":[[56,7]]}}}],["cpu",{"_index":674,"t":{"562":{"position":[[506,3],[530,3],[631,3],[638,3],[642,3],[1363,3]]},"596":{"position":[[1317,3],[1341,3],[1442,3],[1449,3],[1453,3]]}}}],["create",{"_index":13,"t":{"522":{"position":[[164,6]]},"528":{"position":[[481,6],[593,6]]},"530":{"position":[[144,6],[871,6]]},"532":{"position":[[54,6],[699,6],[916,6],[1046,6]]},"538":{"position":[[411,6],[431,6],[775,6]]},"540":{"position":[[1106,6]]},"544":{"position":[[79,6],[468,6]]},"554":{"position":[[313,6]]},"556":{"position":[[422,6]]},"564":{"position":[[3839,6]]},"598":{"position":[[385,6]]},"602":{"position":[[97,6]]}}}],["created",{"_index":26,"t":{"522":{"position":[[327,7]]},"528":{"position":[[149,7]]},"562":{"position":[[1094,7]]},"564":{"position":[[4728,7],[5078,8]]}}}],["creating",{"_index":320,"t":{"538":{"position":[[90,8]]},"544":{"position":[[378,8],[604,8],[694,8],[1114,8]]},"564":{"position":[[1823,8]]}}}],["creation",{"_index":804,"t":{"564":{"position":[[1735,8],[3597,8]]}}}],["crucial",{"_index":1113,"t":{"598":{"position":[[502,7]]}}}],["curious",{"_index":1090,"t":{"596":{"position":[[1918,7]]}}}],["current",{"_index":78,"t":{"526":{"position":[[112,7]]}}}],["currently",{"_index":64,"t":{"524":{"position":[[106,9]]},"546":{"position":[[397,10]]},"564":{"position":[[3386,9]]},"594":{"position":[[762,9]]}}}],["custom",{"_index":137,"t":{"528":{"position":[[600,6]]}}}],["customizable",{"_index":203,"t":{"532":{"position":[[339,13]]}}}],["cutting",{"_index":615,"t":{"558":{"position":[[1293,7]]}}}],["d",{"_index":1040,"t":{"594":{"position":[[1177,2]]},"596":{"position":[[2103,2]]}}}],["darkenblend",{"_index":818,"t":{"564":{"position":[[2153,12]]}}}],["data",{"_index":1054,"t":{"596":{"position":[[406,4],[593,4],[1114,4]]}}}],["debug",{"_index":354,"t":{"540":{"position":[[100,5],[757,5]]}}}],["decade",{"_index":537,"t":{"556":{"position":[[125,6]]},"558":{"position":[[1076,7]]}}}],["decade—yes",{"_index":986,"t":{"592":{"position":[[286,11]]}}}],["decent",{"_index":1081,"t":{"596":{"position":[[1267,6]]}}}],["dedicated",{"_index":42,"t":{"522":{"position":[[557,9]]},"528":{"position":[[303,9]]},"566":{"position":[[384,9]]}}}],["deeper",{"_index":380,"t":{"540":{"position":[[994,6]]},"548":{"position":[[76,6]]},"598":{"position":[[304,6]]}}}],["default",{"_index":916,"t":{"564":{"position":[[4549,7]]}}}],["delve",{"_index":470,"t":{"548":{"position":[[70,5]]},"598":{"position":[[298,5]]}}}],["demonstrate",{"_index":162,"t":{"530":{"position":[[398,11],[574,11]]}}}],["depending",{"_index":917,"t":{"564":{"position":[[4610,9]]}}}],["deprecation",{"_index":1127,"t":{"598":{"position":[[796,11]]}}}],["design",{"_index":227,"t":{"532":{"position":[[759,6]]},"534":{"position":[[1070,7]]},"544":{"position":[[68,6]]}}}],["designed",{"_index":361,"t":{"540":{"position":[[279,8]]},"544":{"position":[[184,8]]},"546":{"position":[[279,8]]},"558":{"position":[[977,8]]}}}],["designers",{"_index":394,"t":{"544":{"position":[[211,9],[454,9]]}}}],["designs",{"_index":419,"t":{"544":{"position":[[907,8]]},"564":{"position":[[3461,8],[3769,8]]}}}],["desktop",{"_index":251,"t":{"532":{"position":[[1228,7]]},"594":{"position":[[396,7],[796,7]]}}}],["despite",{"_index":1116,"t":{"598":{"position":[[559,7]]}}}],["destructuring",{"_index":620,"t":{"558":{"position":[[1467,13]]}}}],["detailed",{"_index":662,"t":{"562":{"position":[[123,8]]},"564":{"position":[[3752,8]]}}}],["details",{"_index":307,"t":{"534":{"position":[[991,7]]}}}],["dev",{"_index":351,"t":{"540":{"position":[[7,3],[440,3],[721,3],[818,3]]}}}],["developed",{"_index":318,"t":{"538":{"position":[[34,9]]},"546":{"position":[[564,9]]}}}],["developer",{"_index":222,"t":{"532":{"position":[[653,9]]}}}],["developers",{"_index":12,"t":{"522":{"position":[[150,10],[360,10],[575,10]]},"528":{"position":[[467,10]]},"530":{"position":[[730,10],[849,10]]},"532":{"position":[[31,10]]},"534":{"position":[[340,10],[821,10]]},"538":{"position":[[140,10],[652,10]]},"540":{"position":[[89,10],[170,10],[296,10],[473,10],[702,11],[881,10],[976,10],[1071,10]]},"544":{"position":[[225,11],[739,10]]},"546":{"position":[[617,10],[942,10],[1043,10]]},"550":{"position":[[566,10]]}}}],["developing",{"_index":263,"t":{"534":{"position":[[91,10]]}}}],["development",{"_index":156,"t":{"530":{"position":[[289,12],[437,11],[786,11]]},"532":{"position":[[1275,11]]},"538":{"position":[[486,11]]},"546":{"position":[[110,11]]},"552":{"position":[[243,11]]},"556":{"position":[[368,12]]},"558":{"position":[[1418,12]]},"562":{"position":[[1637,11]]}}}],["device",{"_index":245,"t":{"532":{"position":[[1118,7]]}}}],["devices",{"_index":249,"t":{"532":{"position":[[1211,7]]}}}],["diagnose",{"_index":362,"t":{"540":{"position":[[307,8]]}}}],["didn't",{"_index":1097,"t":{"598":{"position":[[82,6]]}}}],["dif",{"_index":679,"t":{"562":{"position":[[646,3],[668,3]]},"596":{"position":[[1457,3],[1479,3]]}}}],["differenceblend",{"_index":819,"t":{"564":{"position":[[2166,16]]}}}],["different",{"_index":229,"t":{"532":{"position":[[791,9]]},"534":{"position":[[712,9],[748,9]]},"538":{"position":[[227,9],[545,9]]}}}],["digital",{"_index":568,"t":{"558":{"position":[[120,7]]}}}],["direct",{"_index":975,"t":{"568":{"position":[[204,6]]},"604":{"position":[[205,6]]}}}],["discord",{"_index":946,"t":{"566":{"position":[[206,7]]},"568":{"position":[[192,7]]},"604":{"position":[[193,7]]}}}],["discuss",{"_index":658,"t":{"562":{"position":[[30,8]]}}}],["displays",{"_index":252,"t":{"532":{"position":[[1236,9]]}}}],["dive",{"_index":559,"t":{"556":{"position":[[537,4]]},"600":{"position":[[80,4]]}}}],["divideblend",{"_index":820,"t":{"564":{"position":[[2183,12]]}}}],["docs",{"_index":654,"t":{"560":{"position":[[8,4]]}}}],["documentation",{"_index":79,"t":{"526":{"position":[[120,13],[245,13],[419,13],[509,13]]},"528":{"position":[[353,14]]}}}],["don't",{"_index":1110,"t":{"598":{"position":[[467,5]]},"600":{"position":[[343,5]]}}}],["doormat23",{"_index":967,"t":{"568":{"position":[[45,9]]},"604":{"position":[[46,9]]}}}],["draw",{"_index":371,"t":{"540":{"position":[[625,4]]},"596":{"position":[[422,4]]}}}],["drawing",{"_index":856,"t":{"564":{"position":[[2894,7],[3262,7],[3509,7]]}}}],["drivers",{"_index":1094,"t":{"598":{"position":[[18,7]]}}}],["driving",{"_index":538,"t":{"556":{"position":[[135,7]]},"592":{"position":[[482,7]]}}}],["dropping",{"_index":554,"t":{"556":{"position":[[433,8]]}}}],["due",{"_index":719,"t":{"562":{"position":[[1511,3]]}}}],["during",{"_index":733,"t":{"562":{"position":[[1898,6]]},"564":{"position":[[1724,6]]}}}],["dynamic",{"_index":237,"t":{"532":{"position":[[932,7]]},"596":{"position":[[146,7]]}}}],["dynamically",{"_index":907,"t":{"564":{"position":[[4402,11],[4716,11],[5066,11]]}}}],["each",{"_index":208,"t":{"532":{"position":[[407,4]]},"596":{"position":[[1046,4]]}}}],["eager",{"_index":645,"t":{"558":{"position":[[2004,5]]},"566":{"position":[[57,5]]},"592":{"position":[[228,5]]}}}],["early",{"_index":950,"t":{"566":{"position":[[313,5]]}}}],["ease",{"_index":556,"t":{"556":{"position":[[463,5]]}}}],["easier",{"_index":11,"t":{"522":{"position":[[139,6]]},"532":{"position":[[663,7]]},"534":{"position":[[172,7]]},"536":{"position":[[47,6]]},"540":{"position":[[159,6],[747,6],[870,6]]},"544":{"position":[[48,6]]},"552":{"position":[[255,6]]},"564":{"position":[[4577,6]]}}}],["easily",{"_index":97,"t":{"526":{"position":[[362,6]]},"532":{"position":[[277,6]]},"544":{"position":[[815,6]]},"564":{"position":[[1327,6]]}}}],["easy",{"_index":131,"t":{"528":{"position":[[422,4],[458,4]]},"538":{"position":[[643,4]]},"544":{"position":[[669,4],[879,4],[1052,4]]},"564":{"position":[[1431,4],[1927,4],[2718,5]]}}}],["ecosystem",{"_index":387,"t":{"542":{"position":[[100,9]]},"554":{"position":[[421,10]]}}}],["edge",{"_index":616,"t":{"558":{"position":[[1301,4]]}}}],["edit",{"_index":403,"t":{"544":{"position":[[479,4]]}}}],["editing",{"_index":399,"t":{"544":{"position":[[391,7]]}}}],["editor",{"_index":390,"t":{"544":{"position":[[20,6],[174,6],[555,6]]}}}],["effect",{"_index":1080,"t":{"596":{"position":[[1235,6]]}}}],["efficiency",{"_index":867,"t":{"564":{"position":[[3218,10]]}}}],["efficient",{"_index":383,"t":{"540":{"position":[[1118,9]]},"546":{"position":[[310,10],[1088,9]]},"552":{"position":[[271,10]]},"594":{"position":[[168,9]]}}}],["efficiently",{"_index":273,"t":{"534":{"position":[[371,12]]},"544":{"position":[[287,12]]}}}],["effort",{"_index":459,"t":{"546":{"position":[[835,6]]}}}],["efforts",{"_index":955,"t":{"566":{"position":[[442,7]]}}}],["element",{"_index":209,"t":{"532":{"position":[[412,7]]}}}],["elements",{"_index":410,"t":{"544":{"position":[[649,9]]},"550":{"position":[[355,8]]},"596":{"position":[[761,8],[1071,8]]}}}],["elevate",{"_index":560,"t":{"556":{"position":[[563,7]]},"600":{"position":[[299,7]]}}}],["embracing",{"_index":609,"t":{"558":{"position":[[1108,9]]}}}],["emerge",{"_index":591,"t":{"558":{"position":[[701,6]]}}}],["empowering",{"_index":552,"t":{"556":{"position":[[404,10]]},"598":{"position":[[367,10]]}}}],["enable",{"_index":452,"t":{"546":{"position":[[610,6]]},"564":{"position":[[1665,6]]}}}],["enables",{"_index":223,"t":{"532":{"position":[[684,7]]},"564":{"position":[[3497,7]]}}}],["enabling",{"_index":765,"t":{"564":{"position":[[759,8]]}}}],["encapsulate",{"_index":1103,"t":{"598":{"position":[[221,11]]}}}],["encounter",{"_index":1120,"t":{"598":{"position":[[692,9]]}}}],["encountered",{"_index":585,"t":{"558":{"position":[[603,11]]}}}],["encounters",{"_index":710,"t":{"562":{"position":[[1328,10]]}}}],["end",{"_index":66,"t":{"524":{"position":[[128,3]]},"538":{"position":[[482,3]]}}}],["engagement",{"_index":963,"t":{"566":{"position":[[694,10]]},"568":{"position":[[211,10]]},"604":{"position":[[212,10]]}}}],["engaging",{"_index":384,"t":{"540":{"position":[[1132,8]]},"564":{"position":[[3452,8]]}}}],["engine",{"_index":24,"t":{"522":{"position":[[300,8]]},"546":{"position":[[242,7],[858,6],[1098,6]]},"550":{"position":[[55,6]]},"552":{"position":[[97,7],[115,6],[184,6],[318,6]]},"558":{"position":[[290,7]]},"592":{"position":[[382,7]]},"600":{"position":[[166,7]]}}}],["engineering",{"_index":993,"t":{"592":{"position":[[527,11]]}}}],["engines",{"_index":486,"t":{"550":{"position":[[315,7]]},"552":{"position":[[358,8]]}}}],["engine—far",{"_index":1102,"t":{"598":{"position":[[198,10]]}}}],["enhance",{"_index":7,"t":{"522":{"position":[[87,7]]},"558":{"position":[[838,7]]},"562":{"position":[[1594,7]]},"564":{"position":[[1066,7]]},"598":{"position":[[134,7]]}}}],["enhanced",{"_index":894,"t":{"564":{"position":[[4067,8]]}}}],["enhancing",{"_index":631,"t":{"558":{"position":[[1684,9]]},"564":{"position":[[3208,9]]}}}],["ensured",{"_index":650,"t":{"558":{"position":[[2143,7]]}}}],["ensuring",{"_index":420,"t":{"544":{"position":[[916,8]]},"558":{"position":[[1262,8]]}}}],["entirely",{"_index":1075,"t":{"596":{"position":[[1143,8]]}}}],["environment",{"_index":782,"t":{"564":{"position":[[985,11]]}}}],["era",{"_index":1001,"t":{"594":{"position":[[146,4]]}}}],["especially",{"_index":373,"t":{"540":{"position":[[683,10]]}}}],["essentially",{"_index":837,"t":{"564":{"position":[[2476,11]]}}}],["even",{"_index":10,"t":{"522":{"position":[[134,4]]},"550":{"position":[[577,4]]},"558":{"position":[[1888,4]]},"566":{"position":[[274,4]]},"598":{"position":[[392,4]]},"600":{"position":[[260,4]]}}}],["everyone",{"_index":952,"t":{"566":{"position":[[328,9]]}}}],["everything",{"_index":93,"t":{"526":{"position":[[322,10]]},"532":{"position":[[1182,10]]},"552":{"position":[[138,10]]},"594":{"position":[[1136,10]]}}}],["evolution",{"_index":964,"t":{"566":{"position":[[724,10]]}}}],["evolved",{"_index":570,"t":{"558":{"position":[[142,7]]}}}],["example",{"_index":280,"t":{"534":{"position":[[481,8]]},"564":{"position":[[3290,8]]}}}],["examples",{"_index":103,"t":{"526":{"position":[[460,9]]},"530":{"position":[[250,8]]},"532":{"position":[[559,9]]},"560":{"position":[[48,8]]},"564":{"position":[[4165,9]]}}}],["excited",{"_index":0,"t":{"522":{"position":[[7,7]]},"526":{"position":[[16,7]]},"542":{"position":[[133,7]]},"554":{"position":[[286,7]]},"566":{"position":[[745,7]]}}}],["exciting",{"_index":71,"t":{"524":{"position":[[182,8]]},"546":{"position":[[439,8]]},"550":{"position":[[155,8]]},"554":{"position":[[22,8]]},"568":{"position":[[113,8]]},"604":{"position":[[114,8]]}}}],["exclusionblend",{"_index":821,"t":{"564":{"position":[[2196,15]]}}}],["exclusive",{"_index":978,"t":{"592":{"position":[[27,9]]}}}],["execute",{"_index":1031,"t":{"594":{"position":[[936,7]]},"596":{"position":[[616,7]]}}}],["executed",{"_index":1062,"t":{"596":{"position":[[822,9]]}}}],["existing",{"_index":612,"t":{"558":{"position":[[1214,8]]},"596":{"position":[[947,8]]}}}],["expand",{"_index":494,"t":{"550":{"position":[[528,6]]}}}],["expect",{"_index":466,"t":{"546":{"position":[[1058,6]]},"552":{"position":[[159,6]]}}}],["expected",{"_index":721,"t":{"562":{"position":[[1582,8]]}}}],["experience",{"_index":303,"t":{"534":{"position":[[918,11]]},"546":{"position":[[157,10]]},"558":{"position":[[2205,10]]}}}],["experiences",{"_index":555,"t":{"556":{"position":[[446,11]]}}}],["experimenting",{"_index":1151,"t":{"602":{"position":[[361,13]]}}}],["exploration",{"_index":663,"t":{"562":{"position":[[132,11]]}}}],["explore",{"_index":177,"t":{"530":{"position":[[967,7]]},"600":{"position":[[92,7],[369,7]]}}}],["extend",{"_index":515,"t":{"554":{"position":[[167,6]]}}}],["extension",{"_index":353,"t":{"540":{"position":[[35,9]]}}}],["extensive",{"_index":437,"t":{"546":{"position":[[147,9]]}}}],["facilitates",{"_index":769,"t":{"564":{"position":[[826,11]]}}}],["factors",{"_index":990,"t":{"592":{"position":[[490,7]]}}}],["fall",{"_index":1011,"t":{"594":{"position":[[532,4]]}}}],["fallback",{"_index":1032,"t":{"594":{"position":[[953,8]]}}}],["familiar",{"_index":1115,"t":{"598":{"position":[[546,8]]}}}],["fancy",{"_index":933,"t":{"564":{"position":[[5037,5]]}}}],["fast",{"_index":1019,"t":{"594":{"position":[[651,4]]},"596":{"position":[[177,5]]}}}],["faster",{"_index":298,"t":{"534":{"position":[[858,6]]},"546":{"position":[[294,6],[1072,6]]},"556":{"position":[[235,7]]},"558":{"position":[[1924,6]]},"562":{"position":[[255,6]]}}}],["feature",{"_index":275,"t":{"534":{"position":[[392,7]]},"544":{"position":[[785,8]]},"564":{"position":[[3545,7]]},"594":{"position":[[461,7]]}}}],["features",{"_index":130,"t":{"528":{"position":[[388,9],[556,8]]},"534":{"position":[[321,8],[807,8],[1038,8]]},"540":{"position":[[417,8]]},"544":{"position":[[320,8]]},"546":{"position":[[448,8]]},"552":{"position":[[211,8],[294,8]]},"558":{"position":[[1446,8],[2044,8]]},"592":{"position":[[213,8]]},"598":{"position":[[182,8]]}}}],["feedback",{"_index":939,"t":{"566":{"position":[[88,8]]},"600":{"position":[[109,8]]}}}],["feel",{"_index":214,"t":{"532":{"position":[[515,4]]}}}],["felt",{"_index":634,"t":{"558":{"position":[[1734,4]]}}}],["few",{"_index":255,"t":{"532":{"position":[[1317,3]]},"538":{"position":[[817,3]]},"594":{"position":[[792,3]]}}}],["fiddle",{"_index":1085,"t":{"596":{"position":[[1847,6]]}}}],["fill",{"_index":875,"t":{"564":{"position":[[3352,4],[4780,5],[4937,4],[5131,5]]}}}],["fill('blue",{"_index":863,"t":{"564":{"position":[[2985,14]]}}}],["filling",{"_index":857,"t":{"564":{"position":[[2906,7]]}}}],["fills",{"_index":927,"t":{"564":{"position":[[4803,5],[4845,5],[5185,5]]}}}],["filter",{"_index":806,"t":{"564":{"position":[[1783,7]]}}}],["filters",{"_index":715,"t":{"562":{"position":[[1442,8]]},"564":{"position":[[2005,8],[2488,7]]}}}],["final",{"_index":421,"t":{"544":{"position":[[934,5]]},"566":{"position":[[530,5]]}}}],["finalized",{"_index":980,"t":{"592":{"position":[[131,10]]}}}],["finally",{"_index":257,"t":{"534":{"position":[[0,8]]},"552":{"position":[[0,8]]}}}],["fine",{"_index":1134,"t":{"600":{"position":[[150,4]]}}}],["first",{"_index":73,"t":{"526":{"position":[[0,5]]},"530":{"position":[[306,5]]},"546":{"position":[[490,5],[919,5]]},"566":{"position":[[23,5]]},"594":{"position":[[488,5]]},"596":{"position":[[735,6]]}}}],["firstly",{"_index":743,"t":{"564":{"position":[[90,8]]}}}],["fit",{"_index":210,"t":{"532":{"position":[[423,3],[1095,3]]}}}],["flexibility",{"_index":241,"t":{"532":{"position":[[1031,11]]},"550":{"position":[[587,11]]},"564":{"position":[[3233,12]]}}}],["fly",{"_index":279,"t":{"534":{"position":[[472,4]]},"564":{"position":[[4357,3]]}}}],["focus",{"_index":331,"t":{"538":{"position":[[322,5]]}}}],["folder",{"_index":281,"t":{"534":{"position":[[516,6]]}}}],["follow",{"_index":358,"t":{"540":{"position":[[250,6]]},"562":{"position":[[173,6]]},"568":{"position":[[38,6]]},"604":{"position":[[39,6]]}}}],["following",{"_index":1051,"t":{"596":{"position":[[361,9]]}}}],["follows",{"_index":859,"t":{"564":{"position":[[2943,8]]}}}],["font",{"_index":922,"t":{"564":{"position":[[4692,7],[4703,4],[5053,4]]}}}],["font's",{"_index":909,"t":{"564":{"position":[[4436,6]]}}}],["fontfamily",{"_index":924,"t":{"564":{"position":[[4744,11],[5095,11]]}}}],["fonts",{"_index":291,"t":{"534":{"position":[[703,5]]}}}],["fontsize",{"_index":926,"t":{"564":{"position":[[4766,9],[5117,9]]}}}],["formats",{"_index":292,"t":{"534":{"position":[[722,8],[758,8]]}}}],["forward",{"_index":465,"t":{"546":{"position":[[1027,7]]},"554":{"position":[[121,7],[368,7]]},"594":{"position":[[656,7]]}}}],["found",{"_index":655,"t":{"560":{"position":[[27,5]]}}}],["foundation",{"_index":599,"t":{"558":{"position":[[850,10]]},"562":{"position":[[1680,10]]}}}],["four",{"_index":61,"t":{"524":{"position":[[72,4]]}}}],["fragmentation",{"_index":38,"t":{"522":{"position":[[485,13]]}}}],["fragmented",{"_index":89,"t":{"526":{"position":[[262,10]]}}}],["frame",{"_index":676,"t":{"562":{"position":[[553,5],[606,5]]},"596":{"position":[[1051,6],[1364,5],[1417,5]]}}}],["frames",{"_index":1068,"t":{"596":{"position":[[926,7]]}}}],["framework",{"_index":266,"t":{"534":{"position":[[195,9],[248,10],[1051,9]]}}}],["frameworks",{"_index":328,"t":{"538":{"position":[[250,11],[568,11]]},"550":{"position":[[326,10]]}}}],["free",{"_index":215,"t":{"532":{"position":[[520,4]]}}}],["friendly",{"_index":392,"t":{"544":{"position":[[150,8]]},"564":{"position":[[2819,9]]},"598":{"position":[[673,9]]}}}],["front",{"_index":337,"t":{"538":{"position":[[476,5]]}}}],["full",{"_index":45,"t":{"522":{"position":[[610,4]]}}}],["fun",{"_index":930,"t":{"564":{"position":[[4920,3]]}}}],["function",{"_index":886,"t":{"564":{"position":[[3695,9]]},"602":{"position":[[167,9],[229,8]]}}}],["functional",{"_index":185,"t":{"532":{"position":[[75,10]]},"544":{"position":[[956,10]]}}}],["functionality",{"_index":632,"t":{"558":{"position":[[1694,14]]}}}],["further",{"_index":6,"t":{"522":{"position":[[79,7]]},"596":{"position":[[1190,7]]}}}],["future",{"_index":558,"t":{"556":{"position":[[503,6]]},"562":{"position":[[1166,6],[1695,6]]},"592":{"position":[[52,6]]},"594":{"position":[[1123,6]]},"602":{"position":[[428,6]]}}}],["gained",{"_index":1017,"t":{"594":{"position":[[634,6]]}}}],["game",{"_index":155,"t":{"530":{"position":[[284,4],[345,4],[371,5],[432,4],[725,4],[781,4]]},"532":{"position":[[305,5]]},"544":{"position":[[644,4],[705,4]]},"552":{"position":[[92,4],[110,4],[179,4],[238,4],[313,4]]},"556":{"position":[[97,4]]}}}],["game's",{"_index":211,"t":{"532":{"position":[[432,6],[1147,6]]}}}],["games",{"_index":15,"t":{"522":{"position":[[179,5],[377,5]]},"530":{"position":[[58,7],[102,5],[151,5],[217,5],[316,5],[383,5],[536,5],[677,5],[888,5]]},"532":{"position":[[505,6]]},"544":{"position":[[86,5],[1136,5]]},"550":{"position":[[234,5]]},"560":{"position":[[62,5]]}}}],["geared",{"_index":1118,"t":{"598":{"position":[[624,6]]}}}],["generate",{"_index":278,"t":{"534":{"position":[[445,8],[556,8],[677,8]]}}}],["generated",{"_index":903,"t":{"564":{"position":[[4340,9]]}}}],["geometry",{"_index":884,"t":{"564":{"position":[[3654,8],[3846,8],[3876,8],[3945,9]]}}}],["github",{"_index":112,"t":{"526":{"position":[[614,6]]},"528":{"position":[[643,6]]},"530":{"position":[[936,6],[1014,6]]},"532":{"position":[[579,6],[1341,6]]},"534":{"position":[[1140,6],[1152,6]]},"554":{"position":[[457,6]]},"566":{"position":[[195,6]]},"600":{"position":[[403,6]]}}}],["give",{"_index":496,"t":{"550":{"position":[[561,4]]},"596":{"position":[[707,4]]}}}],["glyphs",{"_index":910,"t":{"564":{"position":[[4443,6]]}}}],["go",{"_index":965,"t":{"566":{"position":[[773,2]]},"594":{"position":[[215,2]]},"596":{"position":[[612,3]]}}}],["good",{"_index":1137,"t":{"600":{"position":[[215,5]]}}}],["goodboy",{"_index":474,"t":{"550":{"position":[[11,7]]}}}],["gotten",{"_index":957,"t":{"566":{"position":[[503,6]]}}}],["gpu",{"_index":456,"t":{"546":{"position":[[658,3]]},"562":{"position":[[559,3],[583,3],[653,3],[660,3],[664,3],[1381,4]]},"564":{"position":[[790,3],[1191,4]]},"594":{"position":[[247,3]]},"596":{"position":[[418,3],[601,3],[1370,3],[1394,3],[1464,3],[1471,3],[1475,3]]}}}],["gradient",{"_index":874,"t":{"564":{"position":[[3343,8]]}}}],["gradients",{"_index":878,"t":{"564":{"position":[[3414,10],[4887,10]]}}}],["graph",{"_index":1048,"t":{"596":{"position":[[260,5],[330,5],[533,5],[888,5]]}}}],["graphic",{"_index":738,"t":{"562":{"position":[[2013,7],[2068,7]]},"564":{"position":[[3761,7]]}}}],["graphics",{"_index":449,"t":{"546":{"position":[[537,8],[727,8],[1149,8]]},"556":{"position":[[520,9]]},"562":{"position":[[1111,8]]},"564":{"position":[[2727,8],[2750,8],[2952,8],[3052,8],[3190,8],[4013,8],[4076,8],[4866,9],[4953,8],[5172,8],[5236,8]]},"566":{"position":[[670,8]]},"592":{"position":[[69,8]]},"594":{"position":[[126,8]]},"596":{"position":[[66,9]]},"602":{"position":[[445,9]]}}}],["graphics.svg('m",{"_index":869,"t":{"564":{"position":[[3299,15]]}}}],["graphicscontext",{"_index":864,"t":{"564":{"position":[[3002,15],[3142,15]]}}}],["graphicspath",{"_index":879,"t":{"564":{"position":[[3478,12],[3795,14]]}}}],["great",{"_index":28,"t":{"522":{"position":[[340,5]]},"530":{"position":[[817,5]]},"532":{"position":[[1173,5]]},"562":{"position":[[395,5]]}}}],["greater",{"_index":642,"t":{"558":{"position":[[1893,7]]}}}],["greatly",{"_index":493,"t":{"550":{"position":[[520,7]]}}}],["ground",{"_index":435,"t":{"546":{"position":[[95,6]]}}}],["groups",{"_index":395,"t":{"544":{"position":[[251,6]]},"564":{"position":[[731,6]]}}}],["grow",{"_index":523,"t":{"554":{"position":[[405,4]]},"566":{"position":[[73,4]]}}}],["growth",{"_index":84,"t":{"526":{"position":[[170,6]]}}}],["guarantee",{"_index":708,"t":{"562":{"position":[[1252,9]]}}}],["guidance",{"_index":893,"t":{"564":{"position":[[4035,8]]}}}],["guide",{"_index":649,"t":{"558":{"position":[[2133,5]]},"564":{"position":[[4120,6]]}}}],["guides",{"_index":102,"t":{"526":{"position":[[449,6]]}}}],["guiding",{"_index":1129,"t":{"598":{"position":[[817,7]]}}}],["handful",{"_index":1007,"t":{"594":{"position":[[385,7]]}}}],["handle",{"_index":329,"t":{"538":{"position":[[276,6]]}}}],["handled",{"_index":787,"t":{"564":{"position":[[1176,7]]}}}],["handling",{"_index":503,"t":{"552":{"position":[[380,9]]}}}],["happens",{"_index":1093,"t":{"596":{"position":[[2081,7]]}}}],["hard",{"_index":565,"t":{"558":{"position":[[5,4]]},"566":{"position":[[426,4]]}}}],["hardlightblend",{"_index":822,"t":{"564":{"position":[[2212,15]]}}}],["hardmixblend",{"_index":823,"t":{"564":{"position":[[2228,13]]}}}],["hardware",{"_index":771,"t":{"564":{"position":[[848,8]]}}}],["having",{"_index":405,"t":{"544":{"position":[[512,6]]}}}],["heartfelt",{"_index":949,"t":{"566":{"position":[[289,9]]}}}],["height:100",{"_index":809,"t":{"564":{"position":[[1897,11]]}}}],["heights",{"_index":562,"t":{"556":{"position":[[595,8]]},"600":{"position":[[331,8]]}}}],["hello",{"_index":920,"t":{"564":{"position":[[4673,6],[5022,6]]}}}],["help",{"_index":30,"t":{"522":{"position":[[355,4]]},"534":{"position":[[335,4],[816,4]]},"540":{"position":[[84,4],[291,4],[468,4],[1066,4]]},"566":{"position":[[786,5]]}}}],["here",{"_index":656,"t":{"560":{"position":[[33,4]]},"562":{"position":[[500,5]]},"566":{"position":[[341,5],[510,4]]}}}],["high",{"_index":376,"t":{"540":{"position":[[901,4]]},"544":{"position":[[1123,4]]},"596":{"position":[[49,4]]}}}],["highlights",{"_index":661,"t":{"562":{"position":[[100,11]]}}}],["highly",{"_index":202,"t":{"532":{"position":[[332,6]]}}}],["history",{"_index":1021,"t":{"594":{"position":[[721,7]]}}}],["hood",{"_index":705,"t":{"562":{"position":[[1139,4]]}}}],["hopefully",{"_index":166,"t":{"530":{"position":[[688,9]]}}}],["html",{"_index":853,"t":{"564":{"position":[[2852,4]]}}}],["htmltext",{"_index":900,"t":{"564":{"position":[[4287,8]]}}}],["ideal",{"_index":774,"t":{"564":{"position":[[877,5]]}}}],["ideas",{"_index":108,"t":{"526":{"position":[[561,5]]},"544":{"position":[[1104,5]]}}}],["identical",{"_index":915,"t":{"564":{"position":[[4518,9]]}}}],["identifying",{"_index":744,"t":{"564":{"position":[[99,11]]}}}],["im",{"_index":921,"t":{"564":{"position":[[4680,2],[5029,2]]}}}],["images",{"_index":283,"t":{"534":{"position":[[537,6],[776,7]]}}}],["immediately",{"_index":1015,"t":{"594":{"position":[[605,11]]}}}],["implemented",{"_index":702,"t":{"562":{"position":[[1034,11]]},"592":{"position":[[336,11]]}}}],["import",{"_index":726,"t":{"562":{"position":[[1799,6],[1812,6],[1965,6],[2004,6],[2051,6]]},"564":{"position":[[445,6],[2577,6]]},"602":{"position":[[177,6]]}}}],["important",{"_index":261,"t":{"534":{"position":[[73,9]]},"562":{"position":[[1199,9]]},"564":{"position":[[2440,9]]}}}],["imported",{"_index":735,"t":{"562":{"position":[[1950,9]]}}}],["improve",{"_index":36,"t":{"522":{"position":[[456,7]]},"526":{"position":[[577,7]]},"534":{"position":[[604,7]]},"546":{"position":[[700,7]]}}}],["improved",{"_index":302,"t":{"534":{"position":[[904,8]]},"562":{"position":[[1262,8]]}}}],["improvement",{"_index":441,"t":{"546":{"position":[[345,11]]},"558":{"position":[[569,12]]},"594":{"position":[[53,11]]}}}],["improvements",{"_index":438,"t":{"546":{"position":[[192,12]]},"558":{"position":[[2023,12]]},"562":{"position":[[343,12]]},"592":{"position":[[196,12]]},"596":{"position":[[1251,13],[1981,12]]},"598":{"position":[[333,12]]}}}],["improving",{"_index":100,"t":{"526":{"position":[[405,9]]},"528":{"position":[[316,9]]}}}],["include",{"_index":501,"t":{"552":{"position":[[330,7]]},"564":{"position":[[2631,7]]}}}],["includes",{"_index":188,"t":{"532":{"position":[[127,8]]}}}],["including",{"_index":268,"t":{"534":{"position":[[259,9]]},"538":{"position":[[580,9]]},"564":{"position":[[2080,9]]}}}],["inclusion",{"_index":446,"t":{"546":{"position":[[477,9]]}}}],["incorporate",{"_index":487,"t":{"550":{"position":[[340,11]]}}}],["incredibly",{"_index":388,"t":{"542":{"position":[[122,10]]},"558":{"position":[[1970,10]]}}}],["individual",{"_index":282,"t":{"534":{"position":[[526,10]]}}}],["information",{"_index":491,"t":{"550":{"position":[[471,11]]},"564":{"position":[[3992,11]]}}}],["inherited",{"_index":790,"t":{"564":{"position":[[1261,10]]}}}],["init",{"_index":1148,"t":{"602":{"position":[[238,6]]}}}],["initial",{"_index":1006,"t":{"594":{"position":[[317,7]]}}}],["initialization",{"_index":752,"t":{"564":{"position":[[336,14]]}}}],["initializing",{"_index":740,"t":{"564":{"position":[[5,12]]}}}],["initiative",{"_index":5,"t":{"522":{"position":[[65,10]]}}}],["inner",{"_index":356,"t":{"540":{"position":[[199,5],[1022,5]]}}}],["innovate",{"_index":962,"t":{"566":{"position":[[630,8]]}}}],["innovation",{"_index":539,"t":{"556":{"position":[[143,11]]},"558":{"position":[[1084,10]]}}}],["input",{"_index":239,"t":{"532":{"position":[[972,5]]},"552":{"position":[[374,5]]},"566":{"position":[[253,5]]}}}],["inputs",{"_index":194,"t":{"532":{"position":[[201,7]]}}}],["insights",{"_index":590,"t":{"558":{"position":[[692,8]]},"566":{"position":[[454,8]]}}}],["inspiration",{"_index":171,"t":{"530":{"position":[[833,11]]}}}],["install",{"_index":1145,"t":{"602":{"position":[[16,7],[54,7]]}}}],["installed",{"_index":904,"t":{"564":{"position":[[4364,9]]}}}],["instance",{"_index":855,"t":{"564":{"position":[[2884,9]]}}}],["instructions",{"_index":1057,"t":{"596":{"position":[[562,13],[647,13],[966,13],[1029,12]]}}}],["integrated",{"_index":201,"t":{"532":{"position":[[284,10]]},"558":{"position":[[1171,10]]},"564":{"position":[[4276,10]]}}}],["integrates",{"_index":134,"t":{"528":{"position":[[532,10]]}}}],["integration",{"_index":547,"t":{"556":{"position":[[301,11]]},"558":{"position":[[330,12]]}}}],["intensive",{"_index":457,"t":{"546":{"position":[[736,9],[1158,9]]}}}],["interface",{"_index":132,"t":{"528":{"position":[[434,9]]},"532":{"position":[[1154,9]]},"544":{"position":[[159,10],[352,9]]}}}],["interfaces",{"_index":187,"t":{"532":{"position":[[91,10],[766,10],[940,10]]}}}],["internal",{"_index":475,"t":{"550":{"position":[[43,8]]},"598":{"position":[[590,8]]}}}],["intricate",{"_index":889,"t":{"564":{"position":[[3738,9]]}}}],["introduce",{"_index":56,"t":{"522":{"position":[[729,9]]},"598":{"position":[[154,9]]}}}],["introduced",{"_index":764,"t":{"564":{"position":[[747,11],[3027,11],[3374,11]]}}}],["intuitive",{"_index":391,"t":{"544":{"position":[[131,9]]},"564":{"position":[[2800,9]]}}}],["invaluable",{"_index":956,"t":{"566":{"position":[[467,10]]},"600":{"position":[[135,10]]}}}],["invite",{"_index":966,"t":{"568":{"position":[[24,6]]},"600":{"position":[[177,6]]},"604":{"position":[[25,6]]}}}],["isrendergroup:true",{"_index":785,"t":{"564":{"position":[[1121,18]]}}}],["issues",{"_index":363,"t":{"540":{"position":[[328,6]]},"566":{"position":[[221,6]]}}}],["it's",{"_index":534,"t":{"556":{"position":[[90,4],[469,4],[494,4]]},"558":{"position":[[0,4]]},"562":{"position":[[1194,4],[1577,4]]},"564":{"position":[[2435,4],[2512,4]]},"594":{"position":[[853,4]]}}}],["iterate",{"_index":418,"t":{"544":{"position":[[896,7]]}}}],["iteration",{"_index":981,"t":{"592":{"position":[[152,9]]}}}],["itself",{"_index":753,"t":{"564":{"position":[[361,6],[1026,7]]},"594":{"position":[[742,6]]}}}],["javascript",{"_index":550,"t":{"556":{"position":[[344,10]]},"558":{"position":[[1345,11],[1377,10]]}}}],["jaw",{"_index":553,"t":{"556":{"position":[[429,3]]}}}],["join",{"_index":973,"t":{"568":{"position":[[162,4]]},"602":{"position":[[405,4]]},"604":{"position":[[163,4]]}}}],["json",{"_index":296,"t":{"534":{"position":[[795,5]]}}}],["jump",{"_index":896,"t":{"564":{"position":[[4138,4]]}}}],["jumpstart",{"_index":314,"t":{"538":{"position":[[0,9],[358,9],[746,10]]}}}],["key",{"_index":274,"t":{"534":{"position":[[388,3]]},"540":{"position":[[413,3]]},"562":{"position":[[96,3]]},"566":{"position":[[708,3]]},"596":{"position":[[677,3]]},"598":{"position":[[14,3]]}}}],["killer",{"_index":983,"t":{"592":{"position":[[177,6]]}}}],["know",{"_index":111,"t":{"526":{"position":[[608,5]]},"566":{"position":[[123,4]]}}}],["lacked",{"_index":33,"t":{"522":{"position":[[420,6]]}}}],["landed",{"_index":533,"t":{"556":{"position":[[78,7]]}}}],["landscape",{"_index":569,"t":{"558":{"position":[[128,9]]}}}],["languages",{"_index":107,"t":{"526":{"position":[[534,10]]}}}],["large",{"_index":250,"t":{"532":{"position":[[1222,5]]},"564":{"position":[[898,5]]}}}],["later",{"_index":492,"t":{"550":{"position":[[483,5]]}}}],["latest",{"_index":541,"t":{"556":{"position":[[190,6]]},"558":{"position":[[1122,6]]}}}],["launch",{"_index":2,"t":{"522":{"position":[[31,6]]},"594":{"position":[[325,7]]}}}],["layers",{"_index":601,"t":{"558":{"position":[[918,6]]}}}],["layout",{"_index":165,"t":{"530":{"position":[[640,6]]},"532":{"position":[[598,6],[840,6],[1010,7],[1253,6],[1334,6]]},"564":{"position":[[4486,6],[4535,6]]}}}],["layouts",{"_index":225,"t":{"532":{"position":[[717,7],[1063,7]]}}}],["leap",{"_index":464,"t":{"546":{"position":[[1022,4]]},"548":{"position":[[21,4]]},"596":{"position":[[1286,4]]}}}],["learn",{"_index":170,"t":{"530":{"position":[[753,5]]}}}],["lerna",{"_index":725,"t":{"562":{"position":[[1750,8]]}}}],["lessons",{"_index":625,"t":{"558":{"position":[[1581,8]]}}}],["let's",{"_index":563,"t":{"556":{"position":[[604,5]]},"566":{"position":[[612,5]]}}}],["level",{"_index":653,"t":{"558":{"position":[[2225,5]]},"564":{"position":[[2065,6]]}}}],["leverage",{"_index":1034,"t":{"594":{"position":[[995,8]]}}}],["leveraged",{"_index":436,"t":{"546":{"position":[[131,9]]}}}],["leveraging",{"_index":548,"t":{"556":{"position":[[326,10]]},"558":{"position":[[1327,10]]}}}],["lib",{"_index":844,"t":{"564":{"position":[[2651,4]]}}}],["libraries",{"_index":72,"t":{"524":{"position":[[205,9]]},"528":{"position":[[632,10]]},"530":{"position":[[475,9],[615,9]]},"532":{"position":[[903,9]]}}}],["library",{"_index":115,"t":{"528":{"position":[[27,7],[141,7],[288,7],[402,7],[524,7]]},"532":{"position":[[19,7],[119,7],[616,7],[676,7]]},"534":{"position":[[138,7],[214,7]]},"552":{"position":[[70,7]]}}}],["library's",{"_index":127,"t":{"528":{"position":[[330,9]]}}}],["license",{"_index":151,"t":{"530":{"position":[[187,8]]}}}],["life",{"_index":221,"t":{"532":{"position":[[643,4]]}}}],["lightenblend",{"_index":824,"t":{"564":{"position":[[2242,13]]}}}],["limitations",{"_index":711,"t":{"562":{"position":[[1344,11]]}}}],["limited",{"_index":876,"t":{"564":{"position":[[3396,7]]}}}],["limits",{"_index":954,"t":{"566":{"position":[[363,6]]}}}],["line",{"_index":408,"t":{"544":{"position":[[537,4]]}}}],["linear",{"_index":877,"t":{"564":{"position":[[3407,6]]}}}],["linearburnblend",{"_index":825,"t":{"564":{"position":[[2256,16]]}}}],["lineardodgeblend",{"_index":826,"t":{"564":{"position":[[2273,17]]}}}],["linearlightblend",{"_index":827,"t":{"564":{"position":[[2291,17]]}}}],["links",{"_index":525,"t":{"554":{"position":[[464,5]]},"562":{"position":[[184,5]]}}}],["lists",{"_index":197,"t":{"532":{"position":[[223,6]]}}}],["little",{"_index":85,"t":{"526":{"position":[[197,6]]}}}],["load",{"_index":749,"t":{"564":{"position":[[202,4]]}}}],["loaded",{"_index":747,"t":{"564":{"position":[[171,6]]}}}],["loading",{"_index":299,"t":{"534":{"position":[[865,7]]},"552":{"position":[[396,7]]},"558":{"position":[[312,7]]},"564":{"position":[[115,7],[268,7]]}}}],["long",{"_index":81,"t":{"526":{"position":[[152,4]]},"542":{"position":[[21,4]]},"550":{"position":[[400,4]]}}}],["longer",{"_index":321,"t":{"538":{"position":[[159,6]]},"550":{"position":[[285,6]]}}}],["longevity",{"_index":604,"t":{"558":{"position":[[963,10]]}}}],["look",{"_index":246,"t":{"532":{"position":[[1168,4]]},"548":{"position":[[62,4]]},"554":{"position":[[116,4],[363,4]]}}}],["looking",{"_index":172,"t":{"530":{"position":[[860,7]]},"546":{"position":[[953,7]]},"558":{"position":[[1771,8]]}}}],["loop",{"_index":672,"t":{"562":{"position":[[437,4]]},"568":{"position":[[15,5]]},"596":{"position":[[697,4]]},"604":{"position":[[16,5]]}}}],["lot",{"_index":509,"t":{"554":{"position":[[15,3],[109,3]]}}}],["luminosityblend",{"_index":828,"t":{"564":{"position":[[2309,16]]}}}],["made",{"_index":458,"t":{"546":{"position":[[816,4]]},"592":{"position":[[472,5]]}}}],["mainly",{"_index":668,"t":{"562":{"position":[[371,6]]}}}],["maintain",{"_index":35,"t":{"522":{"position":[[443,8]]}}}],["maintenance",{"_index":126,"t":{"528":{"position":[[269,11]]}}}],["major",{"_index":386,"t":{"542":{"position":[[72,5]]},"546":{"position":[[27,5],[577,5],[1016,5]]},"558":{"position":[[271,5]]}}}],["make",{"_index":9,"t":{"522":{"position":[[126,4]]},"532":{"position":[[633,4]]},"534":{"position":[[154,4]]},"536":{"position":[[39,4]]},"538":{"position":[[635,4]]},"540":{"position":[[151,4],[739,4],[862,4]]},"544":{"position":[[40,4]]},"546":{"position":[[187,4]]},"552":{"position":[[233,4]]},"556":{"position":[[610,4]]},"558":{"position":[[1496,4]]},"562":{"position":[[409,4]]},"564":{"position":[[1448,4],[1512,4],[2618,4]]},"566":{"position":[[262,4]]},"594":{"position":[[1150,4]]},"596":{"position":[[270,4]]}}}],["makes",{"_index":133,"t":{"528":{"position":[[449,5]]},"544":{"position":[[870,5],[1043,5]]}}}],["making",{"_index":411,"t":{"544":{"position":[[659,6]]},"550":{"position":[[431,6]]},"554":{"position":[[251,6]]},"556":{"position":[[225,6]]},"558":{"position":[[1914,6]]},"564":{"position":[[4567,6]]},"566":{"position":[[581,6]]},"598":{"position":[[638,6]]}}}],["manage",{"_index":271,"t":{"534":{"position":[[351,6]]}}}],["management",{"_index":260,"t":{"534":{"position":[[56,10]]},"552":{"position":[[408,11],[426,11]]}}}],["manpower",{"_index":34,"t":{"522":{"position":[[431,8]]}}}],["many",{"_index":27,"t":{"522":{"position":[[335,4]]},"534":{"position":[[1026,4]]},"546":{"position":[[173,4]]},"552":{"position":[[206,4]]},"564":{"position":[[3119,4]]},"598":{"position":[[213,4]]}}}],["market",{"_index":1026,"t":{"594":{"position":[[836,7]]}}}],["masks",{"_index":716,"t":{"562":{"position":[[1451,6]]}}}],["match",{"_index":158,"t":{"530":{"position":[[337,5]]}}}],["matter",{"_index":1027,"t":{"594":{"position":[[865,6]]}}}],["means",{"_index":226,"t":{"532":{"position":[[745,5],[1131,5]]},"544":{"position":[[443,5]]},"562":{"position":[[287,5],[1855,5]]},"564":{"position":[[1309,5],[4881,5]]}}}],["media",{"_index":969,"t":{"568":{"position":[[76,6]]},"604":{"position":[[77,6]]}}}],["memory",{"_index":913,"t":{"564":{"position":[[4474,7]]}}}],["mention",{"_index":836,"t":{"564":{"position":[[2453,7]]}}}],["mentioned",{"_index":526,"t":{"554":{"position":[[470,9]]}}}],["mesh",{"_index":883,"t":{"564":{"position":[[3649,4],[3927,4],[3938,6]]}}}],["method",{"_index":1002,"t":{"594":{"position":[[221,6]]}}}],["methodology",{"_index":1124,"t":{"598":{"position":[[742,11]]}}}],["migration",{"_index":648,"t":{"558":{"position":[[2123,9]]},"560":{"position":[[38,9]]},"564":{"position":[[4110,9]]}}}],["minify",{"_index":295,"t":{"534":{"position":[[788,6]]}}}],["minimize",{"_index":746,"t":{"564":{"position":[[154,8]]}}}],["minutes",{"_index":347,"t":{"538":{"position":[[821,7]]}}}],["mipmaps",{"_index":289,"t":{"534":{"position":[[686,8]]}}}],["miss",{"_index":1111,"t":{"598":{"position":[[473,4]]}}}],["mit",{"_index":150,"t":{"530":{"position":[[183,3]]}}}],["mix",{"_index":482,"t":{"550":{"position":[[204,3]]}}}],["mobile",{"_index":248,"t":{"532":{"position":[[1204,6]]}}}],["mode",{"_index":799,"t":{"564":{"position":[[1553,4]]}}}],["modern",{"_index":549,"t":{"556":{"position":[[337,6]]},"558":{"position":[[1338,6]]},"562":{"position":[[1527,6]]}}}],["modes",{"_index":718,"t":{"562":{"position":[[1468,6]]},"564":{"position":[[1241,5],[1417,6],[2608,6]]}}}],["modifications",{"_index":1121,"t":{"598":{"position":[[702,14]]}}}],["months",{"_index":50,"t":{"522":{"position":[[638,7]]},"554":{"position":[[148,7]]}}}],["monumental",{"_index":989,"t":{"592":{"position":[[450,10]]}}}],["more",{"_index":51,"t":{"522":{"position":[[654,4]]},"526":{"position":[[444,4]]},"530":{"position":[[531,4]]},"534":{"position":[[986,4]]},"538":{"position":[[619,5]]},"540":{"position":[[1113,4]]},"544":{"position":[[282,4],[432,5]]},"546":{"position":[[305,4],[1083,4]]},"550":{"position":[[466,4],[582,4]]},"552":{"position":[[266,4],[466,5]]},"554":{"position":[[505,4]]},"556":{"position":[[243,4],[625,4]]},"558":{"position":[[1516,4]]},"562":{"position":[[39,4],[118,4],[416,4],[1339,4],[1522,4],[1745,4]]},"564":{"position":[[2795,4],[3438,4],[3987,4]]},"568":{"position":[[108,4]]},"594":{"position":[[163,4]]},"598":{"position":[[397,4],[652,4]]},"604":{"position":[[109,4]]}}}],["moved",{"_index":1061,"t":{"596":{"position":[[804,6]]}}}],["moves",{"_index":781,"t":{"564":{"position":[[971,5]]}}}],["moving",{"_index":681,"t":{"562":{"position":[[689,6],[745,6]]},"564":{"position":[[1009,6]]},"596":{"position":[[1500,6],[1556,6]]}}}],["much",{"_index":88,"t":{"526":{"position":[[226,4]]},"546":{"position":[[1067,4]]},"562":{"position":[[1873,4]]},"564":{"position":[[1272,4]]},"598":{"position":[[534,4]]}}}],["multi",{"_index":424,"t":{"544":{"position":[[1000,5]]}}}],["multiple",{"_index":90,"t":{"526":{"position":[[280,8]]},"540":{"position":[[596,8]]},"564":{"position":[[3181,8]]}}}],["mycontainer.blendmode",{"_index":847,"t":{"564":{"position":[[2678,21]]}}}],["mytext",{"_index":918,"t":{"564":{"position":[[4641,6],[4996,6]]}}}],["navigating",{"_index":775,"t":{"564":{"position":[[887,10]]}}}],["naïve",{"_index":1046,"t":{"596":{"position":[[209,7]]}}}],["nearing",{"_index":65,"t":{"524":{"position":[[116,7]]}}}],["necessary",{"_index":745,"t":{"564":{"position":[[127,9]]}}}],["need",{"_index":94,"t":{"526":{"position":[[337,4]]},"538":{"position":[[166,4]]},"550":{"position":[[292,4]]},"564":{"position":[[402,4],[1657,4]]}}}],["needing",{"_index":1035,"t":{"594":{"position":[[1042,7]]}}}],["needs",{"_index":213,"t":{"532":{"position":[[448,6]]},"562":{"position":[[468,5]]},"564":{"position":[[4628,6]]}}}],["negationblend",{"_index":829,"t":{"564":{"position":[[2326,14]]}}}],["net",{"_index":1079,"t":{"596":{"position":[[1231,3]]}}}],["new",{"_index":58,"t":{"524":{"position":[[16,3],[191,3]]},"526":{"position":[[84,3],[301,3]]},"528":{"position":[[384,3]]},"530":{"position":[[23,3]]},"532":{"position":[[15,3]]},"534":{"position":[[124,3],[454,3],[958,3]]},"538":{"position":[[15,3],[99,3],[784,3]]},"540":{"position":[[698,3]]},"544":{"position":[[16,3]]},"546":{"position":[[254,3],[533,3]]},"548":{"position":[[31,3]]},"552":{"position":[[66,3]]},"554":{"position":[[42,3],[331,3]]},"558":{"position":[[2040,3]]},"560":{"position":[[4,3]]},"562":{"position":[[209,3],[1572,4],[1719,3],[2046,4]]},"564":{"position":[[516,3],[1105,3],[1212,3],[1676,3],[3474,3],[3716,3],[3791,3],[3934,3],[4650,3],[5005,3]]},"592":{"position":[[419,3]]},"594":{"position":[[142,3],[357,3]]},"598":{"position":[[178,3],[462,4]]},"602":{"position":[[144,3]]}}}],["newcomer",{"_index":996,"t":{"594":{"position":[[4,8]]}}}],["news",{"_index":510,"t":{"554":{"position":[[31,4]]}}}],["next",{"_index":48,"t":{"522":{"position":[[630,4]]},"528":{"position":[[0,4]]},"532":{"position":[[1312,4]]},"546":{"position":[[22,4]]},"558":{"position":[[2220,4]]},"564":{"position":[[2060,4]]}}}],["note",{"_index":707,"t":{"562":{"position":[[1212,4]]},"598":{"position":[[510,5]]}}}],["notes",{"_index":1112,"t":{"598":{"position":[[490,6]]}}}],["nothing",{"_index":1060,"t":{"596":{"position":[[792,7]]}}}],["now",{"_index":141,"t":{"530":{"position":[[8,3]]},"550":{"position":[[19,4],[114,3]]},"558":{"position":[[343,4]]},"562":{"position":[[1769,3],[1864,3]]},"564":{"position":[[46,3],[1172,3],[1231,3],[1323,3],[3162,3],[4333,3],[4562,4],[4821,3],[4966,3]]},"566":{"position":[[116,3]]},"594":{"position":[[687,3]]},"600":{"position":[[45,3]]}}}],["npm",{"_index":1144,"t":{"602":{"position":[[4,3],[50,3]]}}}],["number",{"_index":1088,"t":{"596":{"position":[[1899,6]]}}}],["numbers",{"_index":673,"t":{"562":{"position":[[492,7],[879,7]]},"596":{"position":[[1690,7],[1938,7]]}}}],["numerous",{"_index":657,"t":{"562":{"position":[[10,8],[1411,8]]}}}],["object",{"_index":619,"t":{"558":{"position":[[1460,6]]}}}],["objects",{"_index":369,"t":{"540":{"position":[[605,7],[793,8]]},"564":{"position":[[3199,8]]}}}],["odie",{"_index":477,"t":{"550":{"position":[[69,4]]}}}],["offer",{"_index":500,"t":{"552":{"position":[[200,5]]},"562":{"position":[[1486,5]]},"592":{"position":[[18,5]]}}}],["offers",{"_index":997,"t":{"594":{"position":[[20,6]]}}}],["old",{"_index":736,"t":{"562":{"position":[[1960,4]]}}}],["one",{"_index":95,"t":{"526":{"position":[[345,3]]},"540":{"position":[[402,3]]},"544":{"position":[[300,3]]},"546":{"position":[[423,3]]},"556":{"position":[[647,3]]},"562":{"position":[[1778,3],[1795,3]]},"564":{"position":[[211,3],[2672,5],[3088,3]]},"596":{"position":[[608,3]]}}}],["open",{"_index":145,"t":{"530":{"position":[[53,4],[90,4],[672,4]]},"532":{"position":[[493,4]]},"550":{"position":[[130,4]]},"558":{"position":[[54,4]]},"560":{"position":[[57,4]]}}}],["opening",{"_index":887,"t":{"564":{"position":[[3705,7]]}}}],["operates",{"_index":1044,"t":{"596":{"position":[[186,8]]}}}],["operations",{"_index":866,"t":{"564":{"position":[[3061,11]]}}}],["opportunity",{"_index":1100,"t":{"598":{"position":[[119,11]]}}}],["optimal",{"_index":1131,"t":{"598":{"position":[[837,7]]}}}],["optimizations",{"_index":439,"t":{"546":{"position":[[209,13]]}}}],["optimize",{"_index":297,"t":{"534":{"position":[[832,8]]},"540":{"position":[[225,8],[570,8],[767,8],[1082,8]]},"546":{"position":[[845,8]]},"596":{"position":[[121,8]]}}}],["optimizing",{"_index":1063,"t":{"596":{"position":[[832,10]]}}}],["option",{"_index":461,"t":{"546":{"position":[[931,6]]}}}],["options",{"_index":409,"t":{"544":{"position":[[592,7]]},"558":{"position":[[1485,7]]},"564":{"position":[[582,7]]}}}],["organic",{"_index":83,"t":{"526":{"position":[[162,7]]}}}],["out",{"_index":217,"t":{"532":{"position":[[540,3]]},"534":{"position":[[1132,3]]},"554":{"position":[[449,3]]},"558":{"position":[[1322,4]]},"562":{"position":[[484,3]]},"566":{"position":[[546,3]]}}}],["outfit",{"_index":925,"t":{"564":{"position":[[4756,9],[5107,9]]}}}],["outside",{"_index":472,"t":{"548":{"position":[[94,7]]}}}],["over",{"_index":47,"t":{"522":{"position":[[621,4]]},"524":{"position":[[84,4]]},"526":{"position":[[177,4]]},"528":{"position":[[264,4]]},"546":{"position":[[168,4]]},"558":{"position":[[582,4]]},"562":{"position":[[1283,4],[1611,4]]},"592":{"position":[[265,4]]},"594":{"position":[[65,4]]}}}],["overall",{"_index":374,"t":{"540":{"position":[[802,8]]},"546":{"position":[[984,8]]}}}],["overengineered",{"_index":635,"t":{"558":{"position":[[1744,14]]}}}],["overhaul",{"_index":1096,"t":{"598":{"position":[[38,8]]}}}],["overhauled",{"_index":574,"t":{"558":{"position":[[260,10]]}}}],["overhead",{"_index":1065,"t":{"596":{"position":[[857,9],[996,8]]}}}],["overlayblend",{"_index":830,"t":{"564":{"position":[[2341,13]]}}}],["oversights",{"_index":624,"t":{"558":{"position":[[1547,11]]}}}],["overuse",{"_index":839,"t":{"564":{"position":[[2534,7]]}}}],["overview",{"_index":1109,"t":{"598":{"position":[[443,8]]}}}],["package",{"_index":724,"t":{"562":{"position":[[1723,7],[1782,7]]}}}],["packed",{"_index":982,"t":{"592":{"position":[[165,6]]}}}],["panning",{"_index":779,"t":{"564":{"position":[[926,7]]}}}],["paradigm",{"_index":613,"t":{"558":{"position":[[1252,9]]},"562":{"position":[[1120,8]]}}}],["parameters",{"_index":1086,"t":{"596":{"position":[[1863,10]]}}}],["parcel",{"_index":340,"t":{"538":{"position":[[599,7]]}}}],["part",{"_index":262,"t":{"534":{"position":[[83,4]]},"558":{"position":[[42,4]]}}}],["particularly",{"_index":633,"t":{"558":{"position":[[1709,12]]},"564":{"position":[[3556,12]]}}}],["parts",{"_index":575,"t":{"558":{"position":[[277,5]]}}}],["party",{"_index":140,"t":{"528":{"position":[[626,5]]},"596":{"position":[[2058,5]]}}}],["past",{"_index":623,"t":{"558":{"position":[[1542,4],[1766,4]]}}}],["path",{"_index":890,"t":{"564":{"position":[[3784,4],[3864,5],[3911,5]]}}}],["paths",{"_index":881,"t":{"564":{"position":[[3609,5]]}}}],["patrick",{"_index":121,"t":{"528":{"position":[[160,7]]}}}],["perfect",{"_index":942,"t":{"566":{"position":[[144,8]]},"600":{"position":[[56,7]]}}}],["performance",{"_index":128,"t":{"528":{"position":[[340,12]]},"534":{"position":[[616,11],[887,12]]},"540":{"position":[[316,11],[906,12]]},"546":{"position":[[370,11],[712,11]]},"558":{"position":[[1809,12],[1901,12]]},"562":{"position":[[213,11],[234,11],[1271,11],[1499,11]]},"564":{"position":[[1074,12],[4236,11]]},"592":{"position":[[184,11]]},"594":{"position":[[41,11]]},"596":{"position":[[54,11],[717,11],[1274,11]]},"598":{"position":[[52,11]]}}}],["phase",{"_index":67,"t":{"524":{"position":[[135,5]]},"534":{"position":[[13,5]]},"536":{"position":[[0,5]]},"542":{"position":[[0,5]]},"548":{"position":[[0,5]]}}}],["phaser",{"_index":270,"t":{"534":{"position":[[290,7]]}}}],["phases",{"_index":62,"t":{"524":{"position":[[77,6]]},"558":{"position":[[1315,6]]}}}],["photoshop",{"_index":813,"t":{"564":{"position":[[1990,9]]}}}],["physics",{"_index":502,"t":{"552":{"position":[[350,7]]}}}],["pick",{"_index":846,"t":{"564":{"position":[[2667,4]]}}}],["picture",{"_index":596,"t":{"558":{"position":[[786,8]]}}}],["pinlightblend",{"_index":831,"t":{"564":{"position":[[2355,14]]}}}],["pipeline",{"_index":995,"t":{"592":{"position":[[566,8]]}}}],["pixi",{"_index":120,"t":{"528":{"position":[[136,4]]},"564":{"position":[[672,4]]}}}],["pixi(el",{"_index":564,"t":{"556":{"position":[[651,8]]}}}],["pixi.js",{"_index":729,"t":{"562":{"position":[[1832,10],[2083,10]]},"564":{"position":[[493,10]]},"602":{"position":[[212,10]]}}}],["pixi.js/advanced",{"_index":843,"t":{"564":{"position":[[2584,17]]}}}],["pixi.js@prerelease",{"_index":1146,"t":{"602":{"position":[[62,18]]}}}],["pixi/graphics",{"_index":739,"t":{"562":{"position":[[2028,17]]}}}],["pixi/sprite",{"_index":737,"t":{"562":{"position":[[1988,15]]}}}],["pixijs",{"_index":3,"t":{"522":{"position":[[45,6],[115,6],[206,6],[405,7],[603,6]]},"524":{"position":[[219,7]]},"526":{"position":[[100,7],[238,6]]},"528":{"position":[[11,6],[62,6],[92,6],[211,6],[247,6],[512,7],[568,6]]},"530":{"position":[[45,7],[134,6],[273,6],[421,6],[493,6],[597,6],[633,6],[651,6],[665,6],[770,6],[900,7]]},"532":{"position":[[0,6],[107,7],[455,6],[569,6],[591,6],[731,7],[833,6],[863,6],[1003,6],[1246,6],[1327,6]]},"534":{"position":[[269,7]]},"536":{"position":[[67,6]]},"538":{"position":[[51,6],[103,6],[683,6],[788,6]]},"540":{"position":[[0,6],[112,6],[217,7],[433,6],[530,7],[714,6],[811,6],[955,7],[1040,7]]},"542":{"position":[[93,6]]},"544":{"position":[[114,7]]},"546":{"position":[[0,6],[79,6],[235,6],[269,6],[460,6],[795,6],[993,6]]},"548":{"position":[[49,6]]},"550":{"position":[[99,7],[262,6]]},"552":{"position":[[85,6],[306,6]]},"554":{"position":[[81,6],[200,6],[414,6],[525,6]]},"556":{"position":[[64,6],[174,6],[381,6],[553,6]]},"558":{"position":[[26,6],[175,7],[373,6],[800,6],[864,7],[1271,6],[1652,7],[1822,6],[1990,6],[2252,6]]},"562":{"position":[[1315,6],[1759,6]]},"566":{"position":[[3,6],[267,6]]},"568":{"position":[[59,6]]},"592":{"position":[[103,6],[375,6]]},"594":{"position":[[587,6],[921,6]]},"596":{"position":[[0,6]]},"598":{"position":[[516,6],[645,6]]},"600":{"position":[[307,6],[381,6]]},"602":{"position":[[380,6]]},"604":{"position":[[60,6]]}}}],["pixijs's",{"_index":495,"t":{"550":{"position":[[535,8]]},"566":{"position":[[715,8]]},"594":{"position":[[308,8],[443,8]]}}}],["pixijs’s",{"_index":1091,"t":{"596":{"position":[[2049,8]]}}}],["place",{"_index":96,"t":{"526":{"position":[[349,5]]},"556":{"position":[[640,6]]}}}],["plan",{"_index":1012,"t":{"594":{"position":[[557,4]]}}}],["planned",{"_index":54,"t":{"522":{"position":[[677,8]]},"530":{"position":[[542,7]]},"540":{"position":[[53,7]]}}}],["planning",{"_index":99,"t":{"526":{"position":[[393,8]]},"550":{"position":[[118,8]]}}}],["plans",{"_index":489,"t":{"550":{"position":[[451,5]]}}}],["play",{"_index":180,"t":{"530":{"position":[[1026,4],[1048,4]]},"564":{"position":[[4150,4]]},"596":{"position":[[1833,5]]}}}],["playco",{"_index":40,"t":{"522":{"position":[[534,6]]},"550":{"position":[[24,7]]},"554":{"position":[[240,6]]},"566":{"position":[[553,6]]}}}],["player",{"_index":415,"t":{"544":{"position":[[778,6]]}}}],["playing",{"_index":984,"t":{"592":{"position":[[251,7]]}}}],["please",{"_index":110,"t":{"526":{"position":[[594,6]]},"564":{"position":[[4090,6]]},"596":{"position":[[1819,6]]}}}],["plethora",{"_index":1101,"t":{"598":{"position":[[166,8]]}}}],["plugins",{"_index":288,"t":{"534":{"position":[[666,7]]}}}],["point",{"_index":750,"t":{"564":{"position":[[259,5]]}}}],["popular",{"_index":18,"t":{"522":{"position":[[231,7]]},"538":{"position":[[461,7]]}}}],["position",{"_index":1073,"t":{"596":{"position":[[1100,9]]}}}],["possibilities",{"_index":888,"t":{"564":{"position":[[3720,13]]}}}],["possible",{"_index":519,"t":{"554":{"position":[[270,9]]},"556":{"position":[[43,8]]},"558":{"position":[[2182,9]]}}}],["post",{"_index":306,"t":{"534":{"position":[[967,4]]},"562":{"position":[[76,5]]},"598":{"position":[[245,5]]}}}],["posts",{"_index":1104,"t":{"598":{"position":[[280,5]]}}}],["potential",{"_index":841,"t":{"564":{"position":[[2556,9]]}}}],["potions",{"_index":182,"t":{"530":{"position":[[1040,7]]}}}],["powered",{"_index":1003,"t":{"594":{"position":[[251,7]]}}}],["powerful",{"_index":375,"t":{"540":{"position":[[838,8]]},"556":{"position":[[273,9]]},"558":{"position":[[1521,9]]}}}],["powering",{"_index":865,"t":{"564":{"position":[[3039,8]]}}}],["practices",{"_index":360,"t":{"540":{"position":[[262,10]]},"598":{"position":[[845,10]]}}}],["precisely",{"_index":1036,"t":{"594":{"position":[[1080,9]]}}}],["predecessor",{"_index":998,"t":{"594":{"position":[[74,12]]}}}],["prefer",{"_index":906,"t":{"564":{"position":[[4389,7]]}}}],["preferred",{"_index":344,"t":{"538":{"position":[[710,9]]}}}],["preview",{"_index":979,"t":{"592":{"position":[[37,7]]}}}],["problem",{"_index":594,"t":{"558":{"position":[[747,8]]}}}],["process",{"_index":265,"t":{"534":{"position":[[164,7]]},"538":{"position":[[79,7]]},"540":{"position":[[507,7],[641,7]]},"564":{"position":[[35,7],[387,8],[1806,7]]}}}],["product",{"_index":422,"t":{"544":{"position":[[940,7]]}}}],["professional",{"_index":154,"t":{"530":{"position":[[237,12]]}}}],["progress",{"_index":199,"t":{"532":{"position":[[249,8]]},"550":{"position":[[438,8]]},"600":{"position":[[6,8]]}}}],["project",{"_index":174,"t":{"530":{"position":[[912,7],[1005,8]]},"550":{"position":[[372,8],[410,8]]},"558":{"position":[[1565,7]]}}}],["projects",{"_index":53,"t":{"522":{"position":[[668,8]]},"524":{"position":[[20,8]]},"528":{"position":[[237,9]]},"542":{"position":[[31,8]]},"554":{"position":[[46,8]]},"556":{"position":[[576,8]]},"598":{"position":[[413,9]]}}}],["promise",{"_index":754,"t":{"564":{"position":[[417,7]]}}}],["proof",{"_index":1039,"t":{"594":{"position":[[1130,5]]}}}],["propels",{"_index":999,"t":{"594":{"position":[[97,7]]}}}],["property",{"_index":803,"t":{"564":{"position":[[1693,8]]}}}],["proud",{"_index":644,"t":{"558":{"position":[[1981,5]]}}}],["provide",{"_index":153,"t":{"530":{"position":[[229,7]]},"534":{"position":[[498,7]]},"538":{"position":[[523,7]]},"552":{"position":[[130,7]]}}}],["provided",{"_index":647,"t":{"558":{"position":[[2112,8]]},"562":{"position":[[190,8]]}}}],["provides",{"_index":116,"t":{"528":{"position":[[40,8],[410,8]]},"534":{"position":[[301,8],[657,8],[977,8]]},"544":{"position":[[365,8],[562,8]]}}}],["providing",{"_index":379,"t":{"540":{"position":[[966,9]]},"546":{"position":[[321,9]]},"594":{"position":[[151,9]]}}}],["purpose",{"_index":152,"t":{"530":{"position":[[200,7]]}}}],["purposes",{"_index":742,"t":{"564":{"position":[[80,9]]}}}],["push",{"_index":530,"t":{"556":{"position":[[13,4]]},"566":{"position":[[643,4]]}}}],["put",{"_index":92,"t":{"526":{"position":[[318,3]]}}}],["puzzling",{"_index":181,"t":{"530":{"position":[[1031,8]]}}}],["q",{"_index":870,"t":{"564":{"position":[[3323,1]]}}}],["quality",{"_index":429,"t":{"544":{"position":[[1128,7]]}}}],["quick",{"_index":944,"t":{"566":{"position":[[176,5]]}}}],["radio",{"_index":198,"t":{"532":{"position":[[230,5]]}}}],["range",{"_index":189,"t":{"532":{"position":[[138,5]]},"534":{"position":[[312,5]]},"544":{"position":[[573,5]]},"564":{"position":[[1981,5]]}}}],["ratios",{"_index":233,"t":{"532":{"position":[[825,7]]}}}],["re",{"_index":992,"t":{"592":{"position":[[524,2]]}}}],["react",{"_index":114,"t":{"528":{"position":[[18,6],[72,5],[99,5],[130,5],[231,5],[488,5]]},"530":{"position":[[658,6]]},"538":{"position":[[418,5]]}}}],["reactive",{"_index":670,"t":{"562":{"position":[[421,8]]}}}],["read",{"_index":987,"t":{"592":{"position":[[302,4]]}}}],["ready",{"_index":254,"t":{"532":{"position":[[1299,5]]},"556":{"position":[[4,5]]},"558":{"position":[[2196,5]]}}}],["real",{"_index":218,"t":{"532":{"position":[[548,4]]},"544":{"position":[[1012,4]]},"568":{"position":[[226,4]]},"604":{"position":[[227,4]]}}}],["reality",{"_index":960,"t":{"566":{"position":[[603,8]]}}}],["rearchitected",{"_index":627,"t":{"558":{"position":[[1619,13]]}}}],["reconstructing",{"_index":1071,"t":{"596":{"position":[[1008,14]]}}}],["record",{"_index":430,"t":{"544":{"position":[[1162,6]]}}}],["recreate",{"_index":416,"t":{"544":{"position":[[822,8]]}}}],["rect",{"_index":891,"t":{"564":{"position":[[3810,6]]}}}],["rect(50",{"_index":860,"t":{"564":{"position":[[2961,9]]}}}],["rectangle",{"_index":858,"t":{"564":{"position":[[2916,9]]}}}],["red",{"_index":797,"t":{"564":{"position":[[1477,3],[1498,5],[4786,6]]}}}],["reduce",{"_index":462,"t":{"546":{"position":[[964,6]]}}}],["reducing",{"_index":629,"t":{"558":{"position":[[1660,8]]},"562":{"position":[[1922,8]]},"596":{"position":[[1198,8]]}}}],["refer",{"_index":895,"t":{"564":{"position":[[4097,5]]}}}],["refine",{"_index":588,"t":{"558":{"position":[[654,7]]}}}],["refinements",{"_index":1106,"t":{"598":{"position":[[354,12]]}}}],["reflection",{"_index":580,"t":{"558":{"position":[[454,10]]}}}],["regardless",{"_index":343,"t":{"538":{"position":[[690,10]]}}}],["relatively",{"_index":720,"t":{"562":{"position":[[1561,10]]}}}],["release",{"_index":70,"t":{"524":{"position":[[163,7]]},"530":{"position":[[554,8]]},"546":{"position":[[33,7]]},"550":{"position":[[507,7]]},"558":{"position":[[432,7]]},"566":{"position":[[593,7]]},"592":{"position":[[92,7],[423,7]]},"598":{"position":[[482,7]]},"600":{"position":[[26,7]]}}}],["released",{"_index":142,"t":{"530":{"position":[[12,8],[164,8],[322,8]]},"540":{"position":[[67,8]]}}}],["releasing",{"_index":60,"t":{"524":{"position":[[54,9]]},"526":{"position":[[52,9]]},"534":{"position":[[946,9]]}}}],["relevant",{"_index":608,"t":{"558":{"position":[[1044,8]]}}}],["remain",{"_index":607,"t":{"558":{"position":[[1037,6]]}}}],["remains",{"_index":614,"t":{"558":{"position":[[1278,7]]},"596":{"position":[[894,7]]}}}],["remarkable",{"_index":1107,"t":{"598":{"position":[[402,10]]}}}],["reminiscent",{"_index":1005,"t":{"594":{"position":[[293,11]]}}}],["render",{"_index":671,"t":{"562":{"position":[[430,6]]},"564":{"position":[[724,6],[1749,6]]},"596":{"position":[[388,6]]}}}],["renderer",{"_index":20,"t":{"522":{"position":[[242,8]]},"546":{"position":[[880,9]]},"558":{"position":[[1229,8]]},"562":{"position":[[319,9],[1018,9]]},"564":{"position":[[20,9],[137,8],[606,8],[1837,9]]},"596":{"position":[[217,9],[2024,9]]},"602":{"position":[[125,8],[253,8],[344,8]]}}}],["renderers",{"_index":666,"t":{"562":{"position":[[271,10]]}}}],["rendering",{"_index":23,"t":{"522":{"position":[[290,9]]},"540":{"position":[[583,9],[780,9]]},"546":{"position":[[360,9]]},"548":{"position":[[117,10]]},"558":{"position":[[1132,9],[2237,9]]},"562":{"position":[[534,9],[587,9],[1067,10],[1537,10]]},"564":{"position":[[1586,9],[2043,9]]},"592":{"position":[[556,9]]},"594":{"position":[[232,9],[502,9]]},"596":{"position":[[154,10],[624,9],[956,9],[1345,9],[1398,9]]}}}],["rendertexture.create",{"_index":807,"t":{"564":{"position":[[1863,22]]}}}],["renowned",{"_index":639,"t":{"558":{"position":[[1840,8]]}}}],["repeating",{"_index":1022,"t":{"594":{"position":[[732,9]]}}}],["repo",{"_index":113,"t":{"526":{"position":[[621,4]]},"528":{"position":[[650,4]]},"530":{"position":[[1021,4]]},"532":{"position":[[586,4],[1348,4]]},"534":{"position":[[1147,4],[1159,4]]},"562":{"position":[[1002,4]]},"596":{"position":[[1814,4]]}}}],["repository",{"_index":143,"t":{"530":{"position":[[27,10]]}}}],["represents",{"_index":432,"t":{"546":{"position":[[46,10],[1003,10]]},"548":{"position":[[8,10]]}}}],["required",{"_index":911,"t":{"564":{"position":[[4454,9]]}}}],["resembling",{"_index":852,"t":{"564":{"position":[[2837,10]]}}}],["resizable",{"_index":242,"t":{"532":{"position":[[1053,9]]}}}],["resource",{"_index":168,"t":{"530":{"position":[[712,8]]}}}],["resources",{"_index":365,"t":{"540":{"position":[[353,9]]},"554":{"position":[[345,10]]}}}],["respond",{"_index":238,"t":{"532":{"position":[[956,7]]}}}],["responses",{"_index":945,"t":{"566":{"position":[[182,9]]}}}],["responsive",{"_index":224,"t":{"532":{"position":[[706,10]]}}}],["rest",{"_index":1122,"t":{"598":{"position":[[717,4]]}}}],["retains",{"_index":1114,"t":{"598":{"position":[[526,7]]}}}],["return",{"_index":1150,"t":{"602":{"position":[[319,6]]}}}],["reuse",{"_index":1069,"t":{"596":{"position":[[937,5]]}}}],["revealed",{"_index":385,"t":{"542":{"position":[[52,9]]}}}],["revisit",{"_index":598,"t":{"558":{"position":[[826,7]]}}}],["revisited",{"_index":1042,"t":{"596":{"position":[[91,9]]}}}],["rewrite",{"_index":434,"t":{"546":{"position":[[68,7]]},"594":{"position":[[1053,7]]}}}],["rich",{"_index":706,"t":{"562":{"position":[[1176,4]]}}}],["ridiculously",{"_index":545,"t":{"556":{"position":[[260,12]]}}}],["right",{"_index":349,"t":{"538":{"position":[[848,5]]},"592":{"position":[[312,6]]},"600":{"position":[[359,5]]}}}],["robust",{"_index":544,"t":{"556":{"position":[[248,7]]},"558":{"position":[[1057,6]]},"594":{"position":[[182,6]]},"598":{"position":[[657,6]]}}}],["rollup",{"_index":341,"t":{"538":{"position":[[607,7]]}}}],["room",{"_index":584,"t":{"558":{"position":[[560,4]]}}}],["root",{"_index":727,"t":{"562":{"position":[[1806,5]]}}}],["roughly",{"_index":1024,"t":{"594":{"position":[[817,7]]}}}],["runtime",{"_index":414,"t":{"544":{"position":[[770,7]]}}}],["same",{"_index":370,"t":{"540":{"position":[[620,4]]},"564":{"position":[[1402,4],[4840,4],[5164,4],[5228,4]]},"594":{"position":[[948,4]]}}}],["saturationblend",{"_index":832,"t":{"564":{"position":[[2370,16]]}}}],["saving",{"_index":912,"t":{"564":{"position":[[4464,6]]},"596":{"position":[[1169,6]]}}}],["scenarios",{"_index":709,"t":{"562":{"position":[[1301,10]]}}}],["scene",{"_index":694,"t":{"562":{"position":[[814,5]]},"564":{"position":[[693,5]]},"596":{"position":[[254,5],[324,5],[527,5],[587,5],[882,5],[1087,5],[1625,5]]}}}],["scenes",{"_index":400,"t":{"544":{"position":[[399,7],[492,6],[831,6]]},"562":{"position":[[1399,6]]}}}],["screen",{"_index":230,"t":{"532":{"position":[[801,6],[982,6],[1103,6]]},"596":{"position":[[444,7]]}}}],["scroll",{"_index":195,"t":{"532":{"position":[[209,6]]}}}],["seamless",{"_index":546,"t":{"556":{"position":[[292,8]]}}}],["seamlessly",{"_index":481,"t":{"550":{"position":[[193,10]]},"558":{"position":[[1160,10]]}}}],["searchable",{"_index":98,"t":{"526":{"position":[[369,11]]}}}],["second",{"_index":1050,"t":{"596":{"position":[[338,6],[867,7]]}}}],["secondly",{"_index":751,"t":{"564":{"position":[[322,9]]}}}],["see",{"_index":521,"t":{"554":{"position":[[297,3]]},"558":{"position":[[771,3]]},"566":{"position":[[66,3],[756,3]]},"596":{"position":[[1929,3]]},"598":{"position":[[790,3]]}}}],["seen",{"_index":69,"t":{"524":{"position":[[154,4]]},"558":{"position":[[189,4]]}}}],["seized",{"_index":1099,"t":{"598":{"position":[[107,6]]}}}],["served",{"_index":582,"t":{"558":{"position":[[502,6]]}}}],["serves",{"_index":722,"t":{"562":{"position":[[1662,6]]},"564":{"position":[[69,6]]}}}],["set",{"_index":322,"t":{"538":{"position":[[174,3]]},"562":{"position":[[1148,3]]},"596":{"position":[[555,3]]}}}],["setting",{"_index":326,"t":{"538":{"position":[[216,7]]},"564":{"position":[[1705,7]]}}}],["settings",{"_index":1149,"t":{"602":{"position":[[298,8]]}}}],["setup",{"_index":330,"t":{"538":{"position":[[291,5]]}}}],["several",{"_index":57,"t":{"524":{"position":[[8,7],[174,7]]}}}],["shaking",{"_index":732,"t":{"562":{"position":[[1890,7]]}}}],["shapes",{"_index":880,"t":{"564":{"position":[[3532,7]]}}}],["shaping",{"_index":1152,"t":{"602":{"position":[[416,7]]}}}],["share",{"_index":490,"t":{"550":{"position":[[460,5]]},"558":{"position":[[2013,5]]},"600":{"position":[[191,5]]}}}],["shared",{"_index":508,"t":{"554":{"position":[[6,6]]}}}],["sharing",{"_index":428,"t":{"544":{"position":[[1085,7]]},"564":{"position":[[3521,7]]}}}],["sheets",{"_index":285,"t":{"534":{"position":[[572,7]]}}}],["shift",{"_index":1004,"t":{"594":{"position":[[284,5]]}}}],["shooter",{"_index":161,"t":{"530":{"position":[[363,7]]}}}],["shortcomings",{"_index":581,"t":{"558":{"position":[[472,12]]}}}],["shortly",{"_index":313,"t":{"536":{"position":[[19,7]]},"568":{"position":[[130,8]]},"604":{"position":[[131,8]]}}}],["shout",{"_index":959,"t":{"566":{"position":[[540,5]]}}}],["showcases",{"_index":148,"t":{"530":{"position":[[113,9]]}}}],["side",{"_index":712,"t":{"562":{"position":[[1367,4]]}}}],["significant",{"_index":440,"t":{"546":{"position":[[333,11],[823,11]]},"558":{"position":[[194,11]]},"592":{"position":[[348,11]]},"598":{"position":[[578,11]]}}}],["significantly",{"_index":286,"t":{"534":{"position":[[590,13]]},"546":{"position":[[686,13]]},"558":{"position":[[1393,13]]},"564":{"position":[[1052,13]]}}}],["similar",{"_index":333,"t":{"538":{"position":[[376,7]]},"562":{"position":[[1622,7]]},"564":{"position":[[947,7],[1791,7],[3073,7]]}}}],["simplified",{"_index":617,"t":{"558":{"position":[[1407,10]]},"564":{"position":[[1636,11],[2929,10]]}}}],["simplify",{"_index":319,"t":{"538":{"position":[[66,8]]}}}],["simply",{"_index":1126,"t":{"598":{"position":[[783,6]]}}}],["sincere",{"_index":516,"t":{"554":{"position":[[178,7]]}}}],["single",{"_index":407,"t":{"544":{"position":[[530,6]]},"562":{"position":[[69,6],[546,6],[599,6]]},"564":{"position":[[3135,6]]},"596":{"position":[[1357,6],[1410,6]]},"598":{"position":[[238,6]]}}}],["site",{"_index":109,"t":{"526":{"position":[[589,4]]}}}],["sites",{"_index":91,"t":{"526":{"position":[[289,6]]}}}],["sitting",{"_index":444,"t":{"546":{"position":[[408,7]]}}}],["situation",{"_index":678,"t":{"562":{"position":[[618,9]]},"596":{"position":[[1429,9]]}}}],["size",{"_index":244,"t":{"532":{"position":[[1110,4]]},"546":{"position":[[978,5]]},"562":{"position":[[1938,4]]}}}],["sizes",{"_index":231,"t":{"532":{"position":[[808,5]]}}}],["skipped",{"_index":1076,"t":{"596":{"position":[[1152,8]]}}}],["sliders",{"_index":192,"t":{"532":{"position":[[187,8]]}}}],["slowdowns",{"_index":842,"t":{"564":{"position":[[2566,10]]}}}],["small",{"_index":247,"t":{"532":{"position":[[1198,5]]}}}],["smoother",{"_index":551,"t":{"556":{"position":[[359,8]]}}}],["social",{"_index":968,"t":{"568":{"position":[[69,6]]},"604":{"position":[[70,6]]}}}],["softlightblend",{"_index":833,"t":{"564":{"position":[[2387,15]]}}}],["solid",{"_index":723,"t":{"562":{"position":[[1674,5]]}}}],["solutions",{"_index":589,"t":{"558":{"position":[[678,9]]}}}],["somewhat",{"_index":1045,"t":{"596":{"position":[[200,8]]}}}],["somewhere",{"_index":755,"t":{"564":{"position":[[434,10]]}}}],["soon",{"_index":304,"t":{"534":{"position":[[938,4]]},"540":{"position":[[76,4]]},"594":{"position":[[194,5]]}}}],["source",{"_index":147,"t":{"530":{"position":[[95,6],[823,6]]},"532":{"position":[[498,6]]},"550":{"position":[[135,6]]},"558":{"position":[[59,6]]}}}],["specific",{"_index":212,"t":{"532":{"position":[[439,8]]}}}],["speed",{"_index":640,"t":{"558":{"position":[[1857,6]]},"562":{"position":[[337,5],[1605,5]]},"596":{"position":[[39,5]]}}}],["spend",{"_index":1084,"t":{"596":{"position":[[1381,5]]}}}],["spent",{"_index":675,"t":{"562":{"position":[[517,5],[570,5]]},"596":{"position":[[1328,5]]}}}],["sprite",{"_index":284,"t":{"534":{"position":[[565,6]]},"562":{"position":[[1974,6],[2060,7]]}}}],["sprites",{"_index":401,"t":{"544":{"position":[[407,8],[613,8]]},"562":{"position":[[677,7],[733,7],[796,7]]},"564":{"position":[[3124,8]]},"596":{"position":[[1488,7],[1544,7],[1607,7]]}}}],["stage",{"_index":1133,"t":{"600":{"position":[[126,5]]}}}],["stand",{"_index":605,"t":{"558":{"position":[[992,5]]}}}],["standard",{"_index":901,"t":{"564":{"position":[[4307,9]]}}}],["standout",{"_index":397,"t":{"544":{"position":[[311,8]]},"594":{"position":[[452,8]]}}}],["start",{"_index":348,"t":{"538":{"position":[[833,5]]},"542":{"position":[[40,5]]},"592":{"position":[[245,5]]},"602":{"position":[[355,5]]}}}],["started",{"_index":342,"t":{"538":{"position":[[670,7]]},"544":{"position":[[681,7]]}}}],["state",{"_index":504,"t":{"552":{"position":[[420,5]]}}}],["static",{"_index":776,"t":{"564":{"position":[[904,6]]},"596":{"position":[[135,6]]}}}],["stay",{"_index":528,"t":{"554":{"position":[[490,4]]},"568":{"position":[[3,4]]},"598":{"position":[[251,4]]},"604":{"position":[[4,4]]}}}],["step",{"_index":1074,"t":{"596":{"position":[[1126,4],[1131,5]]}}}],["stepped",{"_index":592,"t":{"558":{"position":[[725,7]]}}}],["steps",{"_index":938,"t":{"566":{"position":[[29,5]]}}}],["still",{"_index":253,"t":{"532":{"position":[[1263,5]]}}}],["stop",{"_index":1098,"t":{"598":{"position":[[89,4]]}}}],["strategy",{"_index":1033,"t":{"594":{"position":[[962,9]]}}}],["streamlining",{"_index":600,"t":{"558":{"position":[[872,12]]}}}],["strictly",{"_index":473,"t":{"548":{"position":[[105,8]]}}}],["stroke",{"_index":932,"t":{"564":{"position":[[4946,6],[5191,7]]}}}],["strokes",{"_index":928,"t":{"564":{"position":[[4813,7],[4855,7],[5249,7]]}}}],["structure",{"_index":86,"t":{"526":{"position":[[204,9]]},"562":{"position":[[820,10],[1731,10]]},"596":{"position":[[1631,10]]}}}],["stuck",{"_index":309,"t":{"534":{"position":[[1106,5]]}}}],["stuff",{"_index":728,"t":{"562":{"position":[[1819,7]]},"564":{"position":[[290,5]]}}}],["stunning",{"_index":378,"t":{"540":{"position":[[928,8]]}}}],["style",{"_index":923,"t":{"564":{"position":[[4736,7],[5087,7]]}}}],["subsequent",{"_index":1067,"t":{"596":{"position":[[915,10]]}}}],["substantial",{"_index":579,"t":{"558":{"position":[[402,11]]},"594":{"position":[[29,11]]}}}],["subtractblend",{"_index":834,"t":{"564":{"position":[[2403,14]]}}}],["such",{"_index":87,"t":{"526":{"position":[[221,4]]},"530":{"position":[[485,4],[625,4]]},"532":{"position":[[158,4]]},"538":{"position":[[403,4]]},"558":{"position":[[298,4]]},"562":{"position":[[1434,4]]}}}],["suffered",{"_index":80,"t":{"526":{"position":[[138,8]]}}}],["suited",{"_index":467,"t":{"546":{"position":[[1120,6]]}}}],["supercharged",{"_index":540,"t":{"556":{"position":[[161,12]]}}}],["support",{"_index":129,"t":{"528":{"position":[[372,7]]},"546":{"position":[[502,7],[782,8]]},"552":{"position":[[338,7]]},"554":{"position":[[393,7]]},"564":{"position":[[1962,7],[3246,7],[3357,7]]},"566":{"position":[[570,7]]}}}],["supported",{"_index":1023,"t":{"594":{"position":[[772,9],[910,10]]}}}],["sure",{"_index":524,"t":{"554":{"position":[[435,4]]},"562":{"position":[[165,4]]},"564":{"position":[[2623,4]]},"596":{"position":[[275,4]]}}}],["svg",{"_index":868,"t":{"564":{"position":[[3258,3]]}}}],["switch",{"_index":484,"t":{"550":{"position":[[300,6]]},"564":{"position":[[4587,6]]}}}],["synonymous",{"_index":1041,"t":{"596":{"position":[[23,10]]}}}],["take",{"_index":453,"t":{"546":{"position":[[631,4]]},"564":{"position":[[2033,4]]}}}],["taken",{"_index":125,"t":{"528":{"position":[[258,5]]},"562":{"position":[[389,5]]}}}],["takes",{"_index":937,"t":{"566":{"position":[[13,5]]}}}],["team",{"_index":43,"t":{"522":{"position":[[567,4]]},"538":{"position":[[58,4]]},"546":{"position":[[122,4],[802,4]]},"550":{"position":[[423,4]]},"566":{"position":[[411,4]]}}}],["teams",{"_index":426,"t":{"544":{"position":[[1061,5]]}}}],["technique",{"_index":368,"t":{"540":{"position":[[552,9]]}}}],["technological",{"_index":542,"t":{"556":{"position":[[197,13]]}}}],["technology",{"_index":610,"t":{"558":{"position":[[1142,11]]},"594":{"position":[[1013,10]]}}}],["templates",{"_index":338,"t":{"538":{"position":[[531,9]]}}}],["ten",{"_index":52,"t":{"522":{"position":[[664,3]]},"558":{"position":[[91,3]]},"592":{"position":[[319,3]]},"594":{"position":[[1167,3]]}}}],["term",{"_index":82,"t":{"526":{"position":[[157,4]]},"542":{"position":[[26,4]]},"550":{"position":[[405,4]]}}}],["territory",{"_index":469,"t":{"548":{"position":[[35,9]]}}}],["test",{"_index":417,"t":{"544":{"position":[[887,4]]},"558":{"position":[[1002,4]]},"562":{"position":[[914,4]]},"596":{"position":[[1726,4]]}}}],["testing",{"_index":953,"t":{"566":{"position":[[351,7]]}}}],["text",{"_index":193,"t":{"532":{"position":[[196,4]]},"564":{"position":[[4178,4],[4193,4],[4503,4],[4557,4],[4667,5],[4798,4],[4984,5],[5009,6],[5016,5],[5043,6]]}}}],["texture",{"_index":801,"t":{"564":{"position":[[1601,7],[1756,7],[1853,7],[3092,7],[3955,8],[5139,8]]}}}],["texture.white",{"_index":892,"t":{"564":{"position":[[3964,14]]}}}],["textures",{"_index":636,"t":{"558":{"position":[[1788,11]]},"564":{"position":[[4898,9]]}}}],["thanks",{"_index":517,"t":{"554":{"position":[[186,6]]},"566":{"position":[[299,6]]}}}],["there's",{"_index":513,"t":{"554":{"position":[[99,7]]},"558":{"position":[[545,7]]},"564":{"position":[[248,7]]}}}],["thereby",{"_index":1077,"t":{"596":{"position":[[1161,7]]}}}],["things",{"_index":761,"t":{"564":{"position":[[677,6]]},"566":{"position":[[128,6]]},"596":{"position":[[494,6]]}}}],["third",{"_index":1072,"t":{"596":{"position":[[1058,6]]}}}],["those",{"_index":480,"t":{"550":{"position":[[168,5]]}}}],["thoughts—the",{"_index":1136,"t":{"600":{"position":[[202,12]]}}}],["three",{"_index":1058,"t":{"596":{"position":[[671,5]]}}}],["threejs",{"_index":269,"t":{"534":{"position":[[277,8]]}}}],["thrilled",{"_index":497,"t":{"552":{"position":[[16,8]]},"558":{"position":[[354,8]]},"592":{"position":[[6,8]]}}}],["through",{"_index":778,"t":{"564":{"position":[[918,7]]}}}],["time",{"_index":46,"t":{"522":{"position":[[615,5]]},"544":{"position":[[1017,4],[1169,5]]},"556":{"position":[[665,5]]},"558":{"position":[[110,5],[587,5],[1010,5]]},"562":{"position":[[512,4],[565,4],[1616,5]]},"568":{"position":[[231,4]]},"594":{"position":[[341,5],[875,4]]},"596":{"position":[[345,4],[1323,4],[1376,4]]},"600":{"position":[[64,4]]},"604":{"position":[[232,4]]}}}],["times",{"_index":300,"t":{"534":{"position":[[873,6]]}}}],["tint",{"_index":793,"t":{"564":{"position":[[1334,4],[1387,4]]}}}],["tinted",{"_index":796,"t":{"564":{"position":[[1470,6]]}}}],["tints",{"_index":789,"t":{"564":{"position":[[1251,5]]}}}],["today",{"_index":310,"t":{"534":{"position":[[1115,5]]},"556":{"position":[[530,6]]},"594":{"position":[[667,6]]},"602":{"position":[[395,5]]}}}],["together",{"_index":427,"t":{"544":{"position":[[1075,9]]},"566":{"position":[[679,9]]},"600":{"position":[[282,9]]}}}],["tool",{"_index":316,"t":{"538":{"position":[[23,4],[134,5],[266,4],[513,4]]},"540":{"position":[[138,4],[847,4]]}}}],["tooling",{"_index":324,"t":{"538":{"position":[[193,7]]}}}],["tools",{"_index":29,"t":{"522":{"position":[[346,5],[470,6]]},"524":{"position":[[195,5]]},"538":{"position":[[397,5],[720,5]]},"540":{"position":[[11,5],[444,5],[725,5],[822,5]]},"544":{"position":[[582,5]]},"552":{"position":[[224,5]]},"554":{"position":[[335,5]]}}}],["toolset",{"_index":382,"t":{"540":{"position":[[1053,7]]}}}],["top",{"_index":479,"t":{"550":{"position":[[92,3]]}}}],["toward",{"_index":1119,"t":{"598":{"position":[[631,6]]},"600":{"position":[[15,6]]}}}],["towards",{"_index":1130,"t":{"598":{"position":[[829,7]]}}}],["traction",{"_index":1018,"t":{"594":{"position":[[641,9]]}}}],["transform",{"_index":786,"t":{"564":{"position":[[1159,9]]},"596":{"position":[[476,9]]}}}],["transformations",{"_index":768,"t":{"564":{"position":[[804,16]]}}}],["transformed",{"_index":882,"t":{"564":{"position":[[3632,11]]}}}],["transforms",{"_index":791,"t":{"564":{"position":[[1282,10]]},"596":{"position":[[288,10]]}}}],["transition",{"_index":572,"t":{"558":{"position":[[224,10]]}}}],["translating",{"_index":106,"t":{"526":{"position":[[493,11]]}}}],["traverse",{"_index":1047,"t":{"596":{"position":[[241,8],[311,8],[514,8]]}}}],["tree",{"_index":731,"t":{"562":{"position":[[1885,4]]}}}],["tremendously",{"_index":571,"t":{"558":{"position":[[150,13]]}}}],["tricks",{"_index":1092,"t":{"596":{"position":[[2064,7]]}}}],["true",{"_index":770,"t":{"564":{"position":[[840,4],[1719,4]]}}}],["try",{"_index":700,"t":{"562":{"position":[[932,3]]},"596":{"position":[[1744,3]]}}}],["tuned",{"_index":529,"t":{"554":{"position":[[495,5]]},"598":{"position":[[256,5]]}}}],["tuning",{"_index":1135,"t":{"600":{"position":[[155,6]]}}}],["tweak",{"_index":205,"t":{"532":{"position":[[369,5]]}}}],["tweening",{"_index":506,"t":{"552":{"position":[[452,9]]}}}],["two",{"_index":157,"t":{"530":{"position":[[312,3]]},"564":{"position":[[76,3],[4606,3]]},"592":{"position":[[478,3]]},"598":{"position":[[10,3]]}}}],["typescript",{"_index":573,"t":{"558":{"position":[[238,11]]}}}],["ubiquitous",{"_index":1009,"t":{"594":{"position":[[431,11]]}}}],["ugly—report",{"_index":1139,"t":{"600":{"position":[[238,11]]}}}],["ui",{"_index":163,"t":{"530":{"position":[[500,2]]},"532":{"position":[[7,2],[462,2],[576,2],[870,3]]}}}],["unchanged",{"_index":1066,"t":{"596":{"position":[[902,9]]}}}],["under",{"_index":149,"t":{"530":{"position":[[173,5]]},"532":{"position":[[1269,5]]},"562":{"position":[[1129,5]]}}}],["undergoing",{"_index":1117,"t":{"598":{"position":[[567,10]]}}}],["undergone",{"_index":850,"t":{"564":{"position":[[2767,9]]}}}],["understand",{"_index":355,"t":{"540":{"position":[[184,10],[484,10],[671,11]]}}}],["understanding",{"_index":381,"t":{"540":{"position":[[1001,13]]}}}],["universally",{"_index":1030,"t":{"594":{"position":[[898,11]]}}}],["universe",{"_index":4,"t":{"522":{"position":[[52,9]]},"554":{"position":[[532,9]]}}}],["unlocked",{"_index":641,"t":{"558":{"position":[[1879,8]]}}}],["unprecedented",{"_index":1141,"t":{"600":{"position":[[317,13]]}}}],["unseen",{"_index":561,"t":{"556":{"position":[[588,6]]}}}],["unveil",{"_index":577,"t":{"558":{"position":[[366,6]]}}}],["unveiling",{"_index":971,"t":{"568":{"position":[[98,9]]},"604":{"position":[[99,9]]}}}],["up",{"_index":74,"t":{"526":{"position":[[6,2]]},"528":{"position":[[5,2]]},"538":{"position":[[178,2],[224,2]]},"546":{"position":[[102,3]]},"562":{"position":[[1155,2]]},"564":{"position":[[3713,2]]}}}],["upcoming",{"_index":514,"t":{"554":{"position":[[139,8]]},"598":{"position":[[266,8]]}}}],["update",{"_index":557,"t":{"556":{"position":[[486,7]]},"558":{"position":[[414,6]]},"596":{"position":[[465,6],[745,6]]}}}],["updates",{"_index":511,"t":{"554":{"position":[[59,7],[510,7]]},"558":{"position":[[206,8]]},"562":{"position":[[19,7],[452,7]]},"568":{"position":[[122,7]]},"598":{"position":[[599,8]]},"604":{"position":[[123,7]]}}}],["upfront",{"_index":905,"t":{"564":{"position":[[4374,7]]}}}],["upgraded",{"_index":897,"t":{"564":{"position":[[4207,8]]}}}],["upgrades",{"_index":762,"t":{"564":{"position":[[699,9],[2736,9],[4022,8],[4183,9]]}}}],["upload",{"_index":1053,"t":{"596":{"position":[[395,6],[576,6],[1119,6]]}}}],["url",{"_index":1087,"t":{"596":{"position":[[1881,3]]}}}],["usability",{"_index":899,"t":{"564":{"position":[[4252,10]]},"598":{"position":[[68,10]]}}}],["use",{"_index":118,"t":{"528":{"position":[[58,3],[430,3]]},"530":{"position":[[130,3],[269,3],[417,3],[465,3],[593,3],[766,3]]},"534":{"position":[[1009,3]]},"596":{"position":[[1306,3]]}}}],["used",{"_index":124,"t":{"528":{"position":[[199,4]]},"532":{"position":[[474,4]]},"534":{"position":[[234,4]]},"540":{"position":[[562,4]]},"564":{"position":[[1814,4],[3107,4]]}}}],["useful",{"_index":308,"t":{"534":{"position":[[1031,6]]},"564":{"position":[[3569,6]]}}}],["user",{"_index":186,"t":{"532":{"position":[[86,4],[967,4]]},"534":{"position":[[913,4]]},"544":{"position":[[145,4],[1006,5]]},"564":{"position":[[233,4],[2814,4]]},"598":{"position":[[668,4]]}}}],["users",{"_index":748,"t":{"564":{"position":[[187,6]]}}}],["using",{"_index":173,"t":{"530":{"position":[[894,5]]},"532":{"position":[[725,5]]},"562":{"position":[[296,5]]},"564":{"position":[[241,6],[308,5],[3663,5]]},"602":{"position":[[134,5]]}}}],["utilize",{"_index":767,"t":{"564":{"position":[[782,7]]}}}],["utilized",{"_index":618,"t":{"558":{"position":[[1437,8]]},"564":{"position":[[3169,8]]}}}],["v7",{"_index":443,"t":{"546":{"position":[[394,2]]},"558":{"position":[[488,3],[1960,3],[2170,2]]},"562":{"position":[[628,2],[650,2],[946,2]]},"596":{"position":[[171,2],[227,2],[1439,2],[1461,2],[1758,2]]},"598":{"position":[[739,2]]}}}],["v8",{"_index":431,"t":{"546":{"position":[[7,2],[467,2],[1000,2]]},"556":{"position":[[71,2],[388,2],[560,2]]},"558":{"position":[[380,3],[807,3],[949,2],[986,2],[1501,2],[1595,3],[1869,3],[1997,2],[2259,3]]},"560":{"position":[[17,2]]},"562":{"position":[[249,2],[302,2],[635,2],[657,2],[959,2],[981,2]]},"564":{"position":[[4301,2]]},"566":{"position":[[10,2],[373,3]]},"592":{"position":[[110,3],[578,3]]},"596":{"position":[[81,3],[452,2],[1446,2],[1468,2],[1771,2],[1793,2]]},"598":{"position":[[523,2]]},"600":{"position":[[100,3],[388,2]]},"602":{"position":[[81,2],[387,2]]}}}],["valuable",{"_index":167,"t":{"530":{"position":[[703,8]]}}}],["valuing",{"_index":948,"t":{"566":{"position":[[240,7]]}}}],["varying",{"_index":1082,"t":{"596":{"position":[[1298,7]]}}}],["vendors",{"_index":451,"t":{"546":{"position":[[591,8]]}}}],["version",{"_index":76,"t":{"526":{"position":[[69,7]]},"546":{"position":[[258,7]]},"594":{"position":[[1095,7]]},"602":{"position":[[33,7]]}}}],["via",{"_index":1143,"t":{"602":{"position":[[0,3]]}}}],["vibrant",{"_index":974,"t":{"568":{"position":[[171,7]]},"604":{"position":[[172,7]]}}}],["views",{"_index":196,"t":{"532":{"position":[[216,6]]}}}],["vision",{"_index":602,"t":{"558":{"position":[[938,6]]}}}],["visual",{"_index":398,"t":{"544":{"position":[[345,6]]}}}],["visualize",{"_index":364,"t":{"540":{"position":[[339,9]]}}}],["visually",{"_index":377,"t":{"540":{"position":[[919,8]]},"544":{"position":[[971,8]]},"564":{"position":[[3443,8]]}}}],["vividlightblend",{"_index":835,"t":{"564":{"position":[[2418,16]]}}}],["vue",{"_index":335,"t":{"538":{"position":[[438,4]]}}}],["wait—dive",{"_index":1142,"t":{"600":{"position":[[349,9]]}}}],["want",{"_index":169,"t":{"530":{"position":[[745,4]]},"532":{"position":[[46,4]]},"534":{"position":[[1094,4]]},"550":{"position":[[185,4]]},"554":{"position":[[159,4]]}}}],["warning",{"_index":1128,"t":{"598":{"position":[[808,8]]}}}],["way",{"_index":117,"t":{"528":{"position":[[51,3]]}}}],["ways",{"_index":931,"t":{"564":{"position":[[4924,4]]}}}],["we'll",{"_index":970,"t":{"568":{"position":[[89,5]]},"598":{"position":[[292,5]]},"604":{"position":[[90,5]]}}}],["we're",{"_index":520,"t":{"554":{"position":[[280,5]]},"558":{"position":[[348,5],[1964,5]]},"566":{"position":[[51,5],[157,5],[739,5]]},"592":{"position":[[0,5],[222,5]]}}}],["we've",{"_index":507,"t":{"554":{"position":[[0,5]]},"556":{"position":[[155,5]]},"558":{"position":[[183,5],[254,5],[593,5],[719,5],[1154,5],[1431,5],[1599,5],[1873,5],[2106,5]]},"562":{"position":[[1028,5]]},"592":{"position":[[461,5]]},"596":{"position":[[85,5]]},"598":{"position":[[101,5]]}}}],["web",{"_index":21,"t":{"522":{"position":[[259,4]]},"556":{"position":[[59,4],[442,3],[516,3],[619,3]]},"562":{"position":[[1181,3]]},"566":{"position":[[666,3]]},"592":{"position":[[65,3]]},"594":{"position":[[105,3],[274,4]]},"602":{"position":[[441,3]]}}}],["webgl",{"_index":576,"t":{"558":{"position":[[324,5],[1223,5],[1309,5]]},"562":{"position":[[313,5],[996,5],[1288,5],[1652,6]]},"564":{"position":[[284,5],[650,5]]},"594":{"position":[[87,6],[347,5],[517,5],[628,5],[678,5]]},"596":{"position":[[1808,5],[2018,5]]},"602":{"position":[[328,5]]}}}],["webgpu",{"_index":448,"t":{"546":{"position":[[514,7],[775,6]]},"556":{"position":[[316,6]]},"558":{"position":[[1100,7],[1182,7]]},"562":{"position":[[974,6],[1011,6],[1048,6],[1222,6],[1475,6],[1551,6]]},"564":{"position":[[314,7],[354,6],[659,6]]},"594":{"position":[[13,6],[754,7]]},"596":{"position":[[1786,6],[2003,6]]},"602":{"position":[[337,6]]}}}],["webpack",{"_index":339,"t":{"538":{"position":[[590,8]]}}}],["website",{"_index":77,"t":{"526":{"position":[[88,7],[305,7]]}}}],["weeks",{"_index":256,"t":{"532":{"position":[[1321,5]]}}}],["well",{"_index":104,"t":{"526":{"position":[[473,4]]},"532":{"position":[[853,4]]},"558":{"position":[[512,5]]}}}],["what's",{"_index":532,"t":{"556":{"position":[[36,6]]},"598":{"position":[[455,6]]}}}],["wherever",{"_index":652,"t":{"558":{"position":[[2173,8]]}}}],["whilst",{"_index":704,"t":{"562":{"position":[[1078,6]]}}}],["whopping",{"_index":567,"t":{"558":{"position":[[82,8]]}}}],["wide",{"_index":812,"t":{"564":{"position":[[1976,4]]}}}],["widely",{"_index":123,"t":{"528":{"position":[[192,6]]}}}],["width:100",{"_index":808,"t":{"564":{"position":[[1886,10]]}}}],["width:3",{"_index":935,"t":{"564":{"position":[[5201,8]]}}}],["wished",{"_index":587,"t":{"558":{"position":[[638,6]]}}}],["without",{"_index":404,"t":{"544":{"position":[[499,7]]},"566":{"position":[[515,7]]},"594":{"position":[[1034,7]]}}}],["won't",{"_index":941,"t":{"566":{"position":[[135,5]]}}}],["work",{"_index":44,"t":{"522":{"position":[[595,4]]},"536":{"position":[[57,4]]},"544":{"position":[[277,4],[1070,4]]},"566":{"position":[[431,5]]},"596":{"position":[[1221,5]]}}}],["workflows",{"_index":345,"t":{"538":{"position":[[730,10]]}}}],["working",{"_index":498,"t":{"552":{"position":[[53,7]]}}}],["workings",{"_index":357,"t":{"540":{"position":[[205,8],[1028,8]]}}}],["works",{"_index":59,"t":{"524":{"position":[[36,6]]},"532":{"position":[[847,5]]}}}],["work—you'll",{"_index":1125,"t":{"598":{"position":[[771,11]]}}}],["world",{"_index":219,"t":{"532":{"position":[[553,5]]},"564":{"position":[[1020,5]]},"566":{"position":[[44,6]]}}}],["worlds",{"_index":777,"t":{"564":{"position":[[911,6]]}}}],["worry",{"_index":325,"t":{"538":{"position":[[204,5]]}}}],["write",{"_index":406,"t":{"544":{"position":[[522,5]]}}}],["x2",{"_index":445,"t":{"546":{"position":[[419,3]]}}}],["year",{"_index":63,"t":{"524":{"position":[[93,5]]},"550":{"position":[[496,5]]}}}],["years",{"_index":17,"t":{"522":{"position":[[199,6]]},"526":{"position":[[186,5]]},"546":{"position":[[178,5]]},"550":{"position":[[4,6]]},"558":{"position":[[95,6]]},"594":{"position":[[1171,5]]}}}],["years!—we've",{"_index":988,"t":{"592":{"position":[[323,12]]}}}],["you'll",{"_index":346,"t":{"538":{"position":[[757,6]]},"554":{"position":[[306,6]]}}}],["yourself",{"_index":701,"t":{"562":{"position":[[936,9]]},"596":{"position":[[1748,9]]}}}],["zooming",{"_index":780,"t":{"564":{"position":[[938,8]]}}}]],"pipeline":["stemmer"]}}] \ No newline at end of file diff --git a/7.x/search-index.json b/7.x/search-index.json new file mode 100644 index 000000000..3da73e477 --- /dev/null +++ b/7.x/search-index.json @@ -0,0 +1 @@ +[{"documents":[{"i":520,"t":"","u":"/blog/archive","b":["Blog"]},{"i":521,"t":"Introducing the PixiJS Universe!","u":"/blog/pixi-universe","b":["Blog"]},{"i":555,"t":"PixiJS v8 Launches! 🎉","u":"/blog/pixi-v8-launches","b":["Blog"]},{"i":569,"t":"Branding","u":"/7.x/branding","b":[]},{"i":591,"t":"PixiJS v8 Beta! 🎉","u":"/blog/pixi-v8-beta","b":["Blog"]},{"i":605,"t":"Collision Detection","u":"/7.x/examples/advanced/collision-detection","b":["Advanced"]},{"i":606,"t":"Examples","u":"/7.x/examples","b":[]},{"i":608,"t":"Scratch Card","u":"/7.x/examples/advanced/scratch-card","b":["Advanced"]},{"i":609,"t":"Mouse Trail","u":"/7.x/examples/advanced/mouse-trail","b":["Advanced"]},{"i":610,"t":"Screen Shot","u":"/7.x/examples/advanced/screen-shot","b":["Advanced"]},{"i":611,"t":"Slots","u":"/7.x/examples/advanced/slots","b":["Advanced"]},{"i":612,"t":"Star Warp","u":"/7.x/examples/advanced/star-warp","b":["Advanced"]},{"i":613,"t":"Spinners","u":"/7.x/examples/advanced/spinners","b":["Advanced"]},{"i":614,"t":"Async","u":"/7.x/examples/assets/async","b":["Assets"]},{"i":615,"t":"Multiple","u":"/7.x/examples/assets/multiple","b":["Assets"]},{"i":616,"t":"Background","u":"/7.x/examples/assets/background","b":["Assets"]},{"i":617,"t":"Promise","u":"/7.x/examples/assets/promise","b":["Assets"]},{"i":618,"t":"Bundle","u":"/7.x/examples/assets/bundle","b":["Assets"]},{"i":619,"t":"Blend Modes","u":"/7.x/examples/basic/blend-modes","b":["Basic"]},{"i":620,"t":"Cache As Bitmap","u":"/7.x/examples/basic/cache-as-bitmap","b":["Basic"]},{"i":621,"t":"Particle Container","u":"/7.x/examples/basic/particle-container","b":["Basic"]},{"i":622,"t":"Simple Plane","u":"/7.x/examples/basic/simple-plane","b":["Basic"]},{"i":623,"t":"Container","u":"/7.x/examples/basic/container","b":["Basic"]},{"i":624,"t":"Tinting","u":"/7.x/examples/basic/tinting","b":["Basic"]},{"i":625,"t":"Transparent Background","u":"/7.x/examples/basic/transparent-background","b":["Basic"]},{"i":626,"t":"Custom Mouse Icon","u":"/7.x/examples/events/custom-mouse-icon","b":["Events"]},{"i":627,"t":"Click","u":"/7.x/examples/events/click","b":["Events"]},{"i":628,"t":"Custom Hitarea","u":"/7.x/examples/events/custom-hitarea","b":["Events"]},{"i":629,"t":"Interactivity","u":"/7.x/examples/events/interactivity","b":["Events"]},{"i":630,"t":"Dragging","u":"/7.x/examples/events/dragging","b":["Events"]},{"i":631,"t":"Logger","u":"/7.x/examples/events/logger","b":["Events"]},{"i":632,"t":"Nested Boundary With Projection","u":"/7.x/examples/events/nested-boundary-with-projection","b":["Events"]},{"i":633,"t":"Pointer Tracker","u":"/7.x/examples/events/pointer-tracker","b":["Events"]},{"i":634,"t":"Custom","u":"/7.x/examples/filters-advanced/custom","b":["Filters Advanced"]},{"i":635,"t":"Mouse Blending","u":"/7.x/examples/filters-advanced/mouse-blending","b":["Filters Advanced"]},{"i":636,"t":"Slider","u":"/7.x/examples/events/slider","b":["Events"]},{"i":637,"t":"Shader Toy Filter Render Texture","u":"/7.x/examples/filters-advanced/shader-toy-filter-render-texture","b":["Filters Advanced"]},{"i":638,"t":"Displacement Map Crawlies","u":"/7.x/examples/filters-basic/displacement-map-crawlies","b":["Filters Basic"]},{"i":639,"t":"Color Matrix","u":"/7.x/examples/filters-basic/color-matrix","b":["Filters Basic"]},{"i":640,"t":"Blur","u":"/7.x/examples/filters-basic/blur","b":["Filters Basic"]},{"i":641,"t":"Displacement Map Flag","u":"/7.x/examples/filters-basic/displacement-map-flag","b":["Filters Basic"]},{"i":642,"t":"Advanced","u":"/7.x/examples/graphics/advanced","b":["Graphics"]},{"i":643,"t":"Simple","u":"/7.x/examples/graphics/simple","b":["Graphics"]},{"i":644,"t":"Graphics","u":"/7.x/examples/masks/graphics","b":["Masks"]},{"i":645,"t":"Dynamic","u":"/7.x/examples/graphics/dynamic","b":["Graphics"]},{"i":646,"t":"Filter","u":"/7.x/examples/masks/filter","b":["Masks"]},{"i":647,"t":"Instanced Geometry","u":"/7.x/examples/mesh-and-shaders/instanced-geometry","b":["Mesh And Shaders"]},{"i":648,"t":"Merging Geometry","u":"/7.x/examples/mesh-and-shaders/merging-geometry","b":["Mesh And Shaders"]},{"i":649,"t":"Sprite","u":"/7.x/examples/masks/sprite","b":["Masks"]},{"i":650,"t":"Multi Pass Shader Generated Mesh","u":"/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh","b":["Mesh And Shaders"]},{"i":651,"t":"Interleaving Geometry","u":"/7.x/examples/mesh-and-shaders/interleaving-geometry","b":["Mesh And Shaders"]},{"i":652,"t":"Shader Toy Mesh","u":"/7.x/examples/mesh-and-shaders/shader-toy-mesh","b":["Mesh And Shaders"]},{"i":653,"t":"Sharing Geometry","u":"/7.x/examples/mesh-and-shaders/sharing-geometry","b":["Mesh And Shaders"]},{"i":654,"t":"Textured Mesh Advanced","u":"/7.x/examples/mesh-and-shaders/textured-mesh-advanced","b":["Mesh And Shaders"]},{"i":655,"t":"Shared Shader","u":"/7.x/examples/mesh-and-shaders/shared-shader","b":["Mesh And Shaders"]},{"i":656,"t":"Triangle Color","u":"/7.x/examples/mesh-and-shaders/triangle-color","b":["Mesh And Shaders"]},{"i":657,"t":"Triangle","u":"/7.x/examples/mesh-and-shaders/triangle","b":["Mesh And Shaders"]},{"i":658,"t":"Textured Mesh Basic","u":"/7.x/examples/mesh-and-shaders/textured-mesh-basic","b":["Mesh And Shaders"]},{"i":659,"t":"Uniforms","u":"/7.x/examples/mesh-and-shaders/uniforms","b":["Mesh And Shaders"]},{"i":660,"t":"Basic","u":"/7.x/examples/offscreen-canvas/basic","b":["Offscreen Canvas"]},{"i":661,"t":"Animated Sprite Jet","u":"/7.x/examples/sprite/animated-sprite-jet","b":["Sprite"]},{"i":662,"t":"Animated Sprite Explosion","u":"/7.x/examples/sprite/animated-sprite-explosion","b":["Sprite"]},{"i":663,"t":"Triangle Textured","u":"/7.x/examples/mesh-and-shaders/triangle-textured","b":["Mesh And Shaders"]},{"i":664,"t":"Texture Swap","u":"/7.x/examples/sprite/texture-swap","b":["Sprite"]},{"i":665,"t":"Basic","u":"/7.x/examples/sprite/basic","b":["Sprite"]},{"i":666,"t":"Tiling Sprite","u":"/7.x/examples/sprite/tiling-sprite","b":["Sprite"]},{"i":667,"t":"Animated Sprite Animation Speed","u":"/7.x/examples/sprite/animated-sprite-animation-speed","b":["Sprite"]},{"i":668,"t":"Bitmap Text","u":"/7.x/examples/text/bitmap-text","b":["Text"]},{"i":669,"t":"From Font","u":"/7.x/examples/text/from-font","b":["Text"]},{"i":670,"t":"Video","u":"/7.x/examples/sprite/video","b":["Sprite"]},{"i":671,"t":"Pixi Text","u":"/7.x/examples/text/pixi-text","b":["Text"]},{"i":672,"t":"Gradient Basic","u":"/7.x/examples/textures/gradient-basic","b":["Textures"]},{"i":673,"t":"Gradient Resource","u":"/7.x/examples/textures/gradient-resource","b":["Textures"]},{"i":674,"t":"Web Font","u":"/7.x/examples/text/web-font","b":["Text"]},{"i":675,"t":"Render Texture Advanced","u":"/7.x/examples/textures/render-texture-advanced","b":["Textures"]},{"i":676,"t":"Render Texture Basic","u":"/7.x/examples/textures/render-texture-basic","b":["Textures"]},{"i":677,"t":"Texture Rotate","u":"/7.x/examples/textures/texture-rotate","b":["Textures"]},{"i":678,"t":"FAQ","u":"/7.x/faq","b":[]},{"i":695,"t":"Welcome","u":"/7.x/guides","b":[]},{"i":701,"t":"Architecture Overview","u":"/7.x/guides/basics/architecture-overview","b":["Basics"]},{"i":709,"t":"Render Loop","u":"/7.x/guides/basics/render-loop","b":["Basics"]},{"i":721,"t":"What PixiJS Is","u":"/7.x/guides/basics/what-pixijs-is","b":["Basics"]},{"i":735,"t":"What PixiJS Is Not","u":"/7.x/guides/basics/what-pixijs-is-not","b":["Basics"]},{"i":753,"t":"Getting Started","u":"/7.x/guides/basics/getting-started","b":["Basics"]},{"i":779,"t":"Containers","u":"/7.x/guides/components/containers","b":["Components"]},{"i":787,"t":"Assets","u":"/7.x/guides/components/assets","b":["Components"]},{"i":802,"t":"Scene Graph","u":"/7.x/guides/basics/scene-graph","b":["Basics"]},{"i":816,"t":"Interaction","u":"/7.x/guides/components/interaction","b":["Components"]},{"i":830,"t":"Spritesheets","u":"/7.x/guides/components/sprite-sheets","b":["Components"]},{"i":838,"t":"Sprites","u":"/7.x/guides/components/sprites","b":["Components"]},{"i":850,"t":"Display Objects","u":"/7.x/guides/components/display-object","b":["Components"]},{"i":854,"t":"Graphics","u":"/7.x/guides/components/graphics","b":["Components"]},{"i":868,"t":"Text","u":"/7.x/guides/components/text","b":["Components"]},{"i":886,"t":"v5 Migration Guide","u":"/7.x/guides/migrations/v5","b":["Migrations"]},{"i":916,"t":"Textures","u":"/7.x/guides/components/textures","b":["Components"]},{"i":934,"t":"v6 Migration Guide","u":"/7.x/guides/migrations/v6","b":["Migrations"]},{"i":940,"t":"v7 Migration Guide","u":"/7.x/guides/migrations/v7","b":["Migrations"]},{"i":976,"t":"Performance Tips","u":"/7.x/guides/production/performance-tips","b":["Production"]},{"i":997,"t":"","u":"/7.x/playground","b":["playground"]},{"i":998,"t":"Upgrading PixiJS","u":"/7.x/guides/migrations/upgrading","b":["Migrations"]},{"i":1000,"t":"Tutorials","u":"/7.x/tutorials","b":[]},{"i":1002,"t":"LOADING...","u":"/7.x/tutorials/getting-started","b":[]}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/520",[]],["t/521",[0,3.317,1,2.167,2,3.317]],["t/555",[1,1.838,3,2.474,4,2.814,5,2.474]],["t/569",[6,5.164]],["t/591",[1,1.838,3,2.474,5,2.474,7,2.814]],["t/605",[8,4.04,9,4.04]],["t/606",[10,5.164]],["t/608",[11,4.04,12,4.04]],["t/609",[13,3.23,14,4.04]],["t/610",[15,4.04,16,4.04]],["t/611",[17,5.164]],["t/612",[18,4.04,19,4.04]],["t/613",[20,5.164]],["t/614",[21,5.164]],["t/615",[22,5.164]],["t/616",[23,4.541]],["t/617",[24,5.164]],["t/618",[25,5.164]],["t/619",[26,4.04,27,4.04]],["t/620",[28,4.04,29,3.552]],["t/621",[30,4.04,31,3.552]],["t/622",[32,3.552,33,4.04]],["t/623",[31,4.541]],["t/624",[34,5.164]],["t/625",[23,3.552,35,4.04]],["t/626",[13,2.653,36,2.653,37,3.317]],["t/627",[38,5.164]],["t/628",[36,3.23,39,4.04]],["t/629",[40,5.164]],["t/630",[41,5.164]],["t/631",[42,5.164]],["t/632",[43,3.317,44,3.317,45,3.317]],["t/633",[46,4.04,47,4.04]],["t/634",[36,4.13]],["t/635",[13,3.23,48,4.04]],["t/636",[49,5.164]],["t/637",[50,1.809,51,2.148,52,2.148,53,1.809,54,1.693]],["t/638",[55,2.917,56,2.917,57,3.317]],["t/639",[58,3.552,59,4.04]],["t/640",[60,5.164]],["t/641",[55,2.917,56,2.917,61,3.317]],["t/642",[62,4.13]],["t/643",[32,4.541]],["t/644",[63,4.541]],["t/645",[64,5.164]],["t/646",[52,4.541]],["t/647",[65,4.04,66,2.99]],["t/648",[66,2.99,67,4.04]],["t/649",[68,3.578]],["t/650",[50,1.809,69,2.443,70,2.443,71,2.443,72,1.809]],["t/651",[66,2.99,73,4.04]],["t/652",[50,2.455,51,2.917,72,2.455]],["t/653",[66,2.99,74,4.04]],["t/654",[62,2.653,72,2.455,75,2.653]],["t/655",[50,2.99,76,4.04]],["t/656",[58,3.552,77,3.23]],["t/657",[77,4.13]],["t/658",[72,2.455,75,2.653,78,2.298]],["t/659",[79,5.164]],["t/660",[78,3.578]],["t/661",[68,2.298,80,2.653,81,3.317]],["t/662",[68,2.298,80,2.653,82,3.317]],["t/663",[75,3.23,77,3.23]],["t/664",[54,2.799,83,4.04]],["t/665",[78,3.578]],["t/666",[68,2.799,84,4.04]],["t/667",[68,1.949,80,2.25,85,2.814,86,2.814]],["t/668",[29,3.552,87,3.23]],["t/669",[88,4.541]],["t/670",[89,5.164]],["t/671",[87,3.23,90,4.04]],["t/672",[78,2.799,91,3.552]],["t/673",[91,3.552,92,4.04]],["t/674",[88,3.552,93,4.04]],["t/675",[53,2.455,54,2.298,62,2.653]],["t/676",[53,2.455,54,2.298,78,2.298]],["t/677",[54,2.799,94,4.04]],["t/678",[95,5.164]],["t/695",[96,5.164]],["t/701",[97,4.04,98,4.04]],["t/709",[53,2.99,99,4.04]],["t/721",[1,3.374]],["t/735",[1,3.374]],["t/753",[100,4.04,101,4.04]],["t/779",[102,5.164]],["t/787",[103,5.164]],["t/802",[104,4.04,105,4.04]],["t/816",[106,5.164]],["t/830",[107,5.164]],["t/838",[108,5.164]],["t/850",[109,4.04,110,4.04]],["t/854",[63,4.541]],["t/868",[87,4.13]],["t/886",[111,3.317,112,2.653,113,2.653]],["t/916",[114,5.164]],["t/934",[112,2.653,113,2.653,115,3.317]],["t/940",[112,2.653,113,2.653,116,3.317]],["t/976",[117,4.04,118,4.04]],["t/997",[]],["t/998",[1,2.639,119,4.04]],["t/1000",[120,5.164]],["t/1002",[121,5.164]]],"invertedIndex":[["",{"_index":5,"t":{"555":{"position":[[20,2]]},"591":{"position":[[16,2]]}}}],["advanced",{"_index":62,"t":{"642":{"position":[[0,8]]},"654":{"position":[[14,8]]},"675":{"position":[[15,8]]}}}],["animated",{"_index":80,"t":{"661":{"position":[[0,8]]},"662":{"position":[[0,8]]},"667":{"position":[[0,8]]}}}],["animation",{"_index":85,"t":{"667":{"position":[[16,9]]}}}],["architecture",{"_index":97,"t":{"701":{"position":[[0,12]]}}}],["assets",{"_index":103,"t":{"787":{"position":[[0,6]]}}}],["async",{"_index":21,"t":{"614":{"position":[[0,5]]}}}],["background",{"_index":23,"t":{"616":{"position":[[0,10]]},"625":{"position":[[12,10]]}}}],["basic",{"_index":78,"t":{"658":{"position":[[14,5]]},"660":{"position":[[0,5]]},"665":{"position":[[0,5]]},"672":{"position":[[9,5]]},"676":{"position":[[15,5]]}}}],["beta",{"_index":7,"t":{"591":{"position":[[10,5]]}}}],["bitmap",{"_index":29,"t":{"620":{"position":[[9,6]]},"668":{"position":[[0,6]]}}}],["blend",{"_index":26,"t":{"619":{"position":[[0,5]]}}}],["blending",{"_index":48,"t":{"635":{"position":[[6,8]]}}}],["blur",{"_index":60,"t":{"640":{"position":[[0,4]]}}}],["boundary",{"_index":44,"t":{"632":{"position":[[7,8]]}}}],["branding",{"_index":6,"t":{"569":{"position":[[0,8]]}}}],["bundle",{"_index":25,"t":{"618":{"position":[[0,6]]}}}],["cache",{"_index":28,"t":{"620":{"position":[[0,5]]}}}],["card",{"_index":12,"t":{"608":{"position":[[8,4]]}}}],["click",{"_index":38,"t":{"627":{"position":[[0,5]]}}}],["collision",{"_index":8,"t":{"605":{"position":[[0,9]]}}}],["color",{"_index":58,"t":{"639":{"position":[[0,5]]},"656":{"position":[[9,5]]}}}],["container",{"_index":31,"t":{"621":{"position":[[9,9]]},"623":{"position":[[0,9]]}}}],["containers",{"_index":102,"t":{"779":{"position":[[0,10]]}}}],["crawlies",{"_index":57,"t":{"638":{"position":[[17,8]]}}}],["custom",{"_index":36,"t":{"626":{"position":[[0,6]]},"628":{"position":[[0,6]]},"634":{"position":[[0,6]]}}}],["detection",{"_index":9,"t":{"605":{"position":[[10,9]]}}}],["displacement",{"_index":55,"t":{"638":{"position":[[0,12]]},"641":{"position":[[0,12]]}}}],["display",{"_index":109,"t":{"850":{"position":[[0,7]]}}}],["dragging",{"_index":41,"t":{"630":{"position":[[0,8]]}}}],["dynamic",{"_index":64,"t":{"645":{"position":[[0,7]]}}}],["examples",{"_index":10,"t":{"606":{"position":[[0,8]]}}}],["explosion",{"_index":82,"t":{"662":{"position":[[16,9]]}}}],["faq",{"_index":95,"t":{"678":{"position":[[0,3]]}}}],["filter",{"_index":52,"t":{"637":{"position":[[11,6]]},"646":{"position":[[0,6]]}}}],["flag",{"_index":61,"t":{"641":{"position":[[17,4]]}}}],["font",{"_index":88,"t":{"669":{"position":[[5,4]]},"674":{"position":[[4,4]]}}}],["generated",{"_index":71,"t":{"650":{"position":[[18,9]]}}}],["geometry",{"_index":66,"t":{"647":{"position":[[10,8]]},"648":{"position":[[8,8]]},"651":{"position":[[13,8]]},"653":{"position":[[8,8]]}}}],["getting",{"_index":100,"t":{"753":{"position":[[0,7]]}}}],["gradient",{"_index":91,"t":{"672":{"position":[[0,8]]},"673":{"position":[[0,8]]}}}],["graph",{"_index":105,"t":{"802":{"position":[[6,5]]}}}],["graphics",{"_index":63,"t":{"644":{"position":[[0,8]]},"854":{"position":[[0,8]]}}}],["guide",{"_index":113,"t":{"886":{"position":[[13,5]]},"934":{"position":[[13,5]]},"940":{"position":[[13,5]]}}}],["hitarea",{"_index":39,"t":{"628":{"position":[[7,7]]}}}],["icon",{"_index":37,"t":{"626":{"position":[[13,4]]}}}],["instanced",{"_index":65,"t":{"647":{"position":[[0,9]]}}}],["interaction",{"_index":106,"t":{"816":{"position":[[0,11]]}}}],["interactivity",{"_index":40,"t":{"629":{"position":[[0,13]]}}}],["interleaving",{"_index":73,"t":{"651":{"position":[[0,12]]}}}],["introducing",{"_index":0,"t":{"521":{"position":[[0,11]]}}}],["jet",{"_index":81,"t":{"661":{"position":[[16,3]]}}}],["launches",{"_index":4,"t":{"555":{"position":[[10,9]]}}}],["loading",{"_index":121,"t":{"1002":{"position":[[0,10]]}}}],["logger",{"_index":42,"t":{"631":{"position":[[0,6]]}}}],["loop",{"_index":99,"t":{"709":{"position":[[7,4]]}}}],["map",{"_index":56,"t":{"638":{"position":[[13,3]]},"641":{"position":[[13,3]]}}}],["matrix",{"_index":59,"t":{"639":{"position":[[6,6]]}}}],["merging",{"_index":67,"t":{"648":{"position":[[0,7]]}}}],["mesh",{"_index":72,"t":{"650":{"position":[[28,4]]},"652":{"position":[[11,4]]},"654":{"position":[[9,4]]},"658":{"position":[[9,4]]}}}],["migration",{"_index":112,"t":{"886":{"position":[[3,9]]},"934":{"position":[[3,9]]},"940":{"position":[[3,9]]}}}],["modes",{"_index":27,"t":{"619":{"position":[[6,5]]}}}],["mouse",{"_index":13,"t":{"609":{"position":[[0,5]]},"626":{"position":[[7,5]]},"635":{"position":[[0,5]]}}}],["multi",{"_index":69,"t":{"650":{"position":[[0,5]]}}}],["multiple",{"_index":22,"t":{"615":{"position":[[0,8]]}}}],["nested",{"_index":43,"t":{"632":{"position":[[0,6]]}}}],["objects",{"_index":110,"t":{"850":{"position":[[8,7]]}}}],["overview",{"_index":98,"t":{"701":{"position":[[13,8]]}}}],["particle",{"_index":30,"t":{"621":{"position":[[0,8]]}}}],["pass",{"_index":70,"t":{"650":{"position":[[6,4]]}}}],["performance",{"_index":117,"t":{"976":{"position":[[0,11]]}}}],["pixi",{"_index":90,"t":{"671":{"position":[[0,4]]}}}],["pixijs",{"_index":1,"t":{"521":{"position":[[16,6]]},"555":{"position":[[0,6]]},"591":{"position":[[0,6]]},"721":{"position":[[5,6]]},"735":{"position":[[5,6]]},"998":{"position":[[10,6]]}}}],["plane",{"_index":33,"t":{"622":{"position":[[7,5]]}}}],["pointer",{"_index":46,"t":{"633":{"position":[[0,7]]}}}],["projection",{"_index":45,"t":{"632":{"position":[[21,10]]}}}],["promise",{"_index":24,"t":{"617":{"position":[[0,7]]}}}],["render",{"_index":53,"t":{"637":{"position":[[18,6]]},"675":{"position":[[0,6]]},"676":{"position":[[0,6]]},"709":{"position":[[0,6]]}}}],["resource",{"_index":92,"t":{"673":{"position":[[9,8]]}}}],["rotate",{"_index":94,"t":{"677":{"position":[[8,6]]}}}],["scene",{"_index":104,"t":{"802":{"position":[[0,5]]}}}],["scratch",{"_index":11,"t":{"608":{"position":[[0,7]]}}}],["screen",{"_index":15,"t":{"610":{"position":[[0,6]]}}}],["shader",{"_index":50,"t":{"637":{"position":[[0,6]]},"650":{"position":[[11,6]]},"652":{"position":[[0,6]]},"655":{"position":[[7,6]]}}}],["shared",{"_index":76,"t":{"655":{"position":[[0,6]]}}}],["sharing",{"_index":74,"t":{"653":{"position":[[0,7]]}}}],["shot",{"_index":16,"t":{"610":{"position":[[7,4]]}}}],["simple",{"_index":32,"t":{"622":{"position":[[0,6]]},"643":{"position":[[0,6]]}}}],["slider",{"_index":49,"t":{"636":{"position":[[0,6]]}}}],["slots",{"_index":17,"t":{"611":{"position":[[0,5]]}}}],["speed",{"_index":86,"t":{"667":{"position":[[26,5]]}}}],["spinners",{"_index":20,"t":{"613":{"position":[[0,8]]}}}],["sprite",{"_index":68,"t":{"649":{"position":[[0,6]]},"661":{"position":[[9,6]]},"662":{"position":[[9,6]]},"666":{"position":[[7,6]]},"667":{"position":[[9,6]]}}}],["sprites",{"_index":108,"t":{"838":{"position":[[0,7]]}}}],["spritesheets",{"_index":107,"t":{"830":{"position":[[0,12]]}}}],["star",{"_index":18,"t":{"612":{"position":[[0,4]]}}}],["started",{"_index":101,"t":{"753":{"position":[[8,7]]}}}],["swap",{"_index":83,"t":{"664":{"position":[[8,4]]}}}],["text",{"_index":87,"t":{"668":{"position":[[7,4]]},"671":{"position":[[5,4]]},"868":{"position":[[0,4]]}}}],["texture",{"_index":54,"t":{"637":{"position":[[25,7]]},"664":{"position":[[0,7]]},"675":{"position":[[7,7]]},"676":{"position":[[7,7]]},"677":{"position":[[0,7]]}}}],["textured",{"_index":75,"t":{"654":{"position":[[0,8]]},"658":{"position":[[0,8]]},"663":{"position":[[9,8]]}}}],["textures",{"_index":114,"t":{"916":{"position":[[0,8]]}}}],["tiling",{"_index":84,"t":{"666":{"position":[[0,6]]}}}],["tinting",{"_index":34,"t":{"624":{"position":[[0,7]]}}}],["tips",{"_index":118,"t":{"976":{"position":[[12,4]]}}}],["toy",{"_index":51,"t":{"637":{"position":[[7,3]]},"652":{"position":[[7,3]]}}}],["tracker",{"_index":47,"t":{"633":{"position":[[8,7]]}}}],["trail",{"_index":14,"t":{"609":{"position":[[6,5]]}}}],["transparent",{"_index":35,"t":{"625":{"position":[[0,11]]}}}],["triangle",{"_index":77,"t":{"656":{"position":[[0,8]]},"657":{"position":[[0,8]]},"663":{"position":[[0,8]]}}}],["tutorials",{"_index":120,"t":{"1000":{"position":[[0,9]]}}}],["uniforms",{"_index":79,"t":{"659":{"position":[[0,8]]}}}],["universe",{"_index":2,"t":{"521":{"position":[[23,9]]}}}],["upgrading",{"_index":119,"t":{"998":{"position":[[0,9]]}}}],["v5",{"_index":111,"t":{"886":{"position":[[0,2]]}}}],["v6",{"_index":115,"t":{"934":{"position":[[0,2]]}}}],["v7",{"_index":116,"t":{"940":{"position":[[0,2]]}}}],["v8",{"_index":3,"t":{"555":{"position":[[7,2]]},"591":{"position":[[7,2]]}}}],["video",{"_index":89,"t":{"670":{"position":[[0,5]]}}}],["warp",{"_index":19,"t":{"612":{"position":[[5,4]]}}}],["web",{"_index":93,"t":{"674":{"position":[[0,3]]}}}],["welcome",{"_index":96,"t":{"695":{"position":[[0,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":523,"t":"Phase 1​","u":"/blog/pixi-universe","h":"#phase-1","p":521},{"i":525,"t":"PixiJS Website​","u":"/blog/pixi-universe","h":"#pixijs-website","p":521},{"i":527,"t":"PixiJS React​","u":"/blog/pixi-universe","h":"#pixijs-react","p":521},{"i":529,"t":"PixiJS Open Games​","u":"/blog/pixi-universe","h":"#pixijs-open-games","p":521},{"i":531,"t":"PixiJS UI & PixiJS Layout​","u":"/blog/pixi-universe","h":"#pixijs-ui--pixijs-layout","p":521},{"i":533,"t":"AssetPack​","u":"/blog/pixi-universe","h":"#assetpack","p":521},{"i":535,"t":"Phase 2​","u":"/blog/pixi-universe","h":"#phase-2","p":521},{"i":537,"t":"PixiJS JumpStart​","u":"/blog/pixi-universe","h":"#pixijs-jumpstart","p":521},{"i":539,"t":"PixiJS Dev Tools​","u":"/blog/pixi-universe","h":"#pixijs-dev-tools","p":521},{"i":541,"t":"Phase 3​","u":"/blog/pixi-universe","h":"#phase-3","p":521},{"i":543,"t":"Comet​","u":"/blog/pixi-universe","h":"#comet","p":521},{"i":545,"t":"PixiJS v8​","u":"/blog/pixi-universe","h":"#pixijs-v8","p":521},{"i":547,"t":"Phase 4​","u":"/blog/pixi-universe","h":"#phase-4","p":521},{"i":549,"t":"PixiJS 3D​","u":"/blog/pixi-universe","h":"#pixijs-3d","p":521},{"i":551,"t":"PixiJS Game Engine​","u":"/blog/pixi-universe","h":"#pixijs-game-engine","p":521},{"i":553,"t":"Conclusion","u":"/blog/pixi-universe","h":"#conclusion","p":521},{"i":557,"t":"🚀 Revolutionizing Web Graphics: Welcome to PixiJS v8","u":"/blog/pixi-v8-launches","h":"#-revolutionizing-web-graphics-welcome-to-pixijs-v8","p":555},{"i":559,"t":"🔗 Quick links","u":"/blog/pixi-v8-launches","h":"#-quick-links","p":555},{"i":561,"t":"🎁 Whats New?","u":"/blog/pixi-v8-launches","h":"#-whats-new","p":555},{"i":563,"t":"✨ We promise the Renderer will work","u":"/blog/pixi-v8-launches","h":"#-we-promise-the-renderer-will-work","p":555},{"i":565,"t":"🤝 What now? Get involved!","u":"/blog/pixi-v8-launches","h":"#-what-now-get-involved","p":555},{"i":567,"t":"📲 Keep in touch","u":"/blog/pixi-v8-launches","h":"#-keep-in-touch","p":555},{"i":571,"t":"Banner","u":"/7.x/branding","h":"#banner","p":569},{"i":573,"t":"Logo","u":"/7.x/branding","h":"#logo","p":569},{"i":575,"t":"Logo (Dark)","u":"/7.x/branding","h":"#logo-dark","p":569},{"i":577,"t":"Logo (Dark, Transparent)","u":"/7.x/branding","h":"#logo-dark-transparent","p":569},{"i":579,"t":"Logo (Pink)","u":"/7.x/branding","h":"#logo-pink","p":569},{"i":581,"t":"Logo (Pink, Transparent)","u":"/7.x/branding","h":"#logo-pink-transparent","p":569},{"i":583,"t":"Mark","u":"/7.x/branding","h":"#mark","p":569},{"i":585,"t":"Mark (Pink, Large)","u":"/7.x/branding","h":"#mark-pink-large","p":569},{"i":587,"t":"Mark (Pink)","u":"/7.x/branding","h":"#mark-pink","p":569},{"i":589,"t":"Mark (Light)","u":"/7.x/branding","h":"#mark-light","p":569},{"i":593,"t":"1. 😍 Embracing WebGPU","u":"/blog/pixi-v8-beta","h":"#1--embracing-webgpu","p":591},{"i":595,"t":"2. 🚀 Turbocharging Performance","u":"/blog/pixi-v8-beta","h":"#2--turbocharging-performance","p":591},{"i":597,"t":"But Wait, There's More!","u":"/blog/pixi-v8-beta","h":"#but-wait-theres-more","p":591},{"i":599,"t":"Over to you!","u":"/blog/pixi-v8-beta","h":"#over-to-you","p":591},{"i":601,"t":"Steps to install:","u":"/blog/pixi-v8-beta","h":"#steps-to-install","p":591},{"i":603,"t":"Keep in touch!","u":"/blog/pixi-v8-beta","h":"#keep-in-touch","p":591},{"i":679,"t":"What is PixiJS for?","u":"/7.x/faq","h":"#what-is-pixijs-for","p":678},{"i":681,"t":"Is PixiJS free?","u":"/7.x/faq","h":"#is-pixijs-free","p":678},{"i":683,"t":"Where do I get it?","u":"/7.x/faq","h":"#where-do-i-get-it","p":678},{"i":685,"t":"How do I get started?","u":"/7.x/faq","h":"#how-do-i-get-started","p":678},{"i":687,"t":"Why should I use PixiJS?","u":"/7.x/faq","h":"#why-should-i-use-pixijs","p":678},{"i":689,"t":"Is PixiJS a game engine?","u":"/7.x/faq","h":"#is-pixijs-a-game-engine","p":678},{"i":691,"t":"Who makes PixiJS?","u":"/7.x/faq","h":"#who-makes-pixijs","p":678},{"i":693,"t":"I found a bug. What should I do?","u":"/7.x/faq","h":"#i-found-a-bug-what-should-i-do","p":678},{"i":697,"t":"About The Guides","u":"/7.x/guides","h":"#about-the-guides","p":695},{"i":699,"t":"Other Resources","u":"/7.x/guides","h":"#other-resources","p":695},{"i":703,"t":"The Code","u":"/7.x/guides/basics/architecture-overview","h":"#the-code","p":701},{"i":705,"t":"The Components","u":"/7.x/guides/basics/architecture-overview","h":"#the-components","p":701},{"i":707,"t":"Major Components","u":"/7.x/guides/basics/architecture-overview","h":"#major-components","p":701},{"i":711,"t":"Running Ticker Callbacks","u":"/7.x/guides/basics/render-loop","h":"#running-ticker-callbacks","p":709},{"i":713,"t":"Updating the Scene Graph","u":"/7.x/guides/basics/render-loop","h":"#updating-the-scene-graph","p":709},{"i":715,"t":"Rendering the Scene Graph","u":"/7.x/guides/basics/render-loop","h":"#rendering-the-scene-graph","p":709},{"i":717,"t":"Frame Rates","u":"/7.x/guides/basics/render-loop","h":"#frame-rates","p":709},{"i":719,"t":"Custom Render Loops","u":"/7.x/guides/basics/render-loop","h":"#custom-render-loops","p":709},{"i":723,"t":"PixiJS Is ... Fast","u":"/7.x/guides/basics/what-pixijs-is","h":"#pixijs-is--fast","p":721},{"i":725,"t":"... More Than Just Sprites","u":"/7.x/guides/basics/what-pixijs-is","h":"#-more-than-just-sprites","p":721},{"i":727,"t":"... WebGL Native","u":"/7.x/guides/basics/what-pixijs-is","h":"#-webgl-native","p":721},{"i":729,"t":"... Open Source","u":"/7.x/guides/basics/what-pixijs-is","h":"#-open-source","p":721},{"i":731,"t":"... Extensible","u":"/7.x/guides/basics/what-pixijs-is","h":"#-extensible","p":721},{"i":733,"t":"... Easy to Deploy","u":"/7.x/guides/basics/what-pixijs-is","h":"#-easy-to-deploy","p":721},{"i":737,"t":"PixiJS Is Not ... A Framework","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#pixijs-is-not--a-framework","p":735},{"i":739,"t":"... A 3D Renderer","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-3d-renderer","p":735},{"i":741,"t":"... A Mobile App","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-mobile-app","p":735},{"i":743,"t":"... A UI Library","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-ui-library","p":735},{"i":745,"t":"... A Data Store","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-data-store","p":735},{"i":747,"t":"... An Audio Library","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-an-audio-library","p":735},{"i":749,"t":"... A Development Environment","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-development-environment","p":735},{"i":751,"t":"So Is PixiJS Right For Me?","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#so-is-pixijs-right-for-me","p":735},{"i":755,"t":"Advanced Users","u":"/7.x/guides/basics/getting-started","h":"#advanced-users","p":753},{"i":757,"t":"A Note About JavaScript","u":"/7.x/guides/basics/getting-started","h":"#a-note-about-javascript","p":753},{"i":759,"t":"Components of a PixiJS Application","u":"/7.x/guides/basics/getting-started","h":"#components-of-a-pixijs-application","p":753},{"i":761,"t":"The HTML File","u":"/7.x/guides/basics/getting-started","h":"#the-html-file","p":753},{"i":763,"t":"Serving the File","u":"/7.x/guides/basics/getting-started","h":"#serving-the-file","p":753},{"i":765,"t":"Loading PixiJS","u":"/7.x/guides/basics/getting-started","h":"#loading-pixijs","p":753},{"i":767,"t":"Creating an Application","u":"/7.x/guides/basics/getting-started","h":"#creating-an-application","p":753},{"i":769,"t":"Adding the View to the DOM","u":"/7.x/guides/basics/getting-started","h":"#adding-the-view-to-the-dom","p":753},{"i":771,"t":"Creating a Sprite","u":"/7.x/guides/basics/getting-started","h":"#creating-a-sprite","p":753},{"i":773,"t":"Adding the Sprite to the Stage","u":"/7.x/guides/basics/getting-started","h":"#adding-the-sprite-to-the-stage","p":753},{"i":775,"t":"Writing an Update Loop","u":"/7.x/guides/basics/getting-started","h":"#writing-an-update-loop","p":753},{"i":777,"t":"Putting It All Together","u":"/7.x/guides/basics/getting-started","h":"#putting-it-all-together","p":753},{"i":781,"t":"Containers as Groups","u":"/7.x/guides/components/containers","h":"#containers-as-groups","p":779},{"i":783,"t":"Masking","u":"/7.x/guides/components/containers","h":"#masking","p":779},{"i":785,"t":"Filtering","u":"/7.x/guides/components/containers","h":"#filtering","p":779},{"i":788,"t":"The Assets package","u":"/7.x/guides/components/assets","h":"#the-assets-package","p":787},{"i":790,"t":"Getting started","u":"/7.x/guides/components/assets","h":"#getting-started","p":787},{"i":792,"t":"Making our first Assets Promise","u":"/7.x/guides/components/assets","h":"#making-our-first-assets-promise","p":787},{"i":794,"t":"Warning about solved promises","u":"/7.x/guides/components/assets","h":"#warning-about-solved-promises","p":787},{"i":796,"t":"Using Async/Await","u":"/7.x/guides/components/assets","h":"#using-asyncawait","p":787},{"i":798,"t":"Loading multiple assets","u":"/7.x/guides/components/assets","h":"#loading-multiple-assets","p":787},{"i":800,"t":"Background loading","u":"/7.x/guides/components/assets","h":"#background-loading","p":787},{"i":804,"t":"The Scene Graph Is a Tree","u":"/7.x/guides/basics/scene-graph","h":"#the-scene-graph-is-a-tree","p":802},{"i":806,"t":"Parents and Children","u":"/7.x/guides/basics/scene-graph","h":"#parents-and-children","p":802},{"i":808,"t":"Render Order","u":"/7.x/guides/basics/scene-graph","h":"#render-order","p":802},{"i":810,"t":"Culling","u":"/7.x/guides/basics/scene-graph","h":"#culling","p":802},{"i":812,"t":"Local vs Global Coordinates","u":"/7.x/guides/basics/scene-graph","h":"#local-vs-global-coordinates","p":802},{"i":814,"t":"Global vs Screen Coordinates","u":"/7.x/guides/basics/scene-graph","h":"#global-vs-screen-coordinates","p":802},{"i":818,"t":"Event Modes","u":"/7.x/guides/components/interaction","h":"#event-modes","p":816},{"i":820,"t":"Event Types","u":"/7.x/guides/components/interaction","h":"#event-types","p":816},{"i":822,"t":"Enabling Interaction","u":"/7.x/guides/components/interaction","h":"#enabling-interaction","p":816},{"i":824,"t":"Checking if Object is Interactive","u":"/7.x/guides/components/interaction","h":"#checking-if-object-is-interactive","p":816},{"i":826,"t":"Use Pointer Events","u":"/7.x/guides/components/interaction","h":"#use-pointer-events","p":816},{"i":828,"t":"Optimization","u":"/7.x/guides/components/interaction","h":"#optimization","p":816},{"i":832,"t":"Anatomy of a Spritesheet","u":"/7.x/guides/components/sprite-sheets","h":"#anatomy-of-a-spritesheet","p":830},{"i":834,"t":"Doubly Efficient","u":"/7.x/guides/components/sprite-sheets","h":"#doubly-efficient","p":830},{"i":836,"t":"Creating SpriteSheets","u":"/7.x/guides/components/sprite-sheets","h":"#creating-spritesheets","p":830},{"i":840,"t":"Creating Sprites","u":"/7.x/guides/components/sprites","h":"#creating-sprites","p":838},{"i":842,"t":"Using Sprites","u":"/7.x/guides/components/sprites","h":"#using-sprites","p":838},{"i":844,"t":"Alpha, Tint and Blend Modes","u":"/7.x/guides/components/sprites","h":"#alpha-tint-and-blend-modes","p":838},{"i":846,"t":"Scale vs Width & Height","u":"/7.x/guides/components/sprites","h":"#scale-vs-width--height","p":838},{"i":848,"t":"Pivot vs Anchor","u":"/7.x/guides/components/sprites","h":"#pivot-vs-anchor","p":838},{"i":852,"t":"Commonly Used Attributes","u":"/7.x/guides/components/display-object","h":"#commonly-used-attributes","p":850},{"i":856,"t":"Graphics Is About Building - Not Drawing","u":"/7.x/guides/components/graphics","h":"#graphics-is-about-building---not-drawing","p":854},{"i":858,"t":"Types of Primitives","u":"/7.x/guides/components/graphics","h":"#types-of-primitives","p":854},{"i":860,"t":"The Geometry List","u":"/7.x/guides/components/graphics","h":"#the-geometry-list","p":854},{"i":862,"t":"Graphics For Display","u":"/7.x/guides/components/graphics","h":"#graphics-for-display","p":854},{"i":864,"t":"Graphics as a Mask","u":"/7.x/guides/components/graphics","h":"#graphics-as-a-mask","p":854},{"i":866,"t":"Caveats and Gotchas","u":"/7.x/guides/components/graphics","h":"#caveats-and-gotchas","p":854},{"i":870,"t":"There Are Two Kinds of Text","u":"/7.x/guides/components/text","h":"#there-are-two-kinds-of-text","p":868},{"i":872,"t":"The Text Object","u":"/7.x/guides/components/text","h":"#the-text-object","p":868},{"i":874,"t":"Text Styles","u":"/7.x/guides/components/text","h":"#text-styles","p":868},{"i":876,"t":"Loading and Using Fonts","u":"/7.x/guides/components/text","h":"#loading-and-using-fonts","p":868},{"i":878,"t":"Caveats and Gotchas","u":"/7.x/guides/components/text","h":"#caveats-and-gotchas","p":868},{"i":880,"t":"BitmapText","u":"/7.x/guides/components/text","h":"#bitmaptext","p":868},{"i":882,"t":"BitmapFont","u":"/7.x/guides/components/text","h":"#bitmapfont","p":868},{"i":884,"t":"Selecting the Right Approach","u":"/7.x/guides/components/text","h":"#selecting-the-right-approach","p":868},{"i":888,"t":"🚧 API Changes","u":"/7.x/guides/migrations/v5","h":"#-api-changes","p":886},{"i":889,"t":"Making WebGL First-Class","u":"/7.x/guides/migrations/v5","h":"#making-webgl-first-class","p":886},{"i":891,"t":"Renderer Parameters","u":"/7.x/guides/migrations/v5","h":"#renderer-parameters","p":886},{"i":893,"t":"Mesh, Plane, Rope","u":"/7.x/guides/migrations/v5","h":"#mesh-plane-rope","p":886},{"i":895,"t":"Graphics Holes","u":"/7.x/guides/migrations/v5","h":"#graphics-holes","p":886},{"i":897,"t":"Filter Padding","u":"/7.x/guides/migrations/v5","h":"#filter-padding","p":886},{"i":899,"t":"Filter Default Vertex Shader","u":"/7.x/guides/migrations/v5","h":"#filter-default-vertex-shader","p":886},{"i":901,"t":"Enable Mipmapping for RenderTexture","u":"/7.x/guides/migrations/v5","h":"#enable-mipmapping-for-rendertexture","p":886},{"i":903,"t":"BaseTexture Resources","u":"/7.x/guides/migrations/v5","h":"#basetexture-resources","p":886},{"i":905,"t":"BaseTexture.source","u":"/7.x/guides/migrations/v5","h":"#basetexturesource","p":886},{"i":907,"t":"Graphics Interaction","u":"/7.x/guides/migrations/v5","h":"#graphics-interaction","p":886},{"i":909,"t":"📦 Publishing Changes","u":"/7.x/guides/migrations/v5","h":"#-publishing-changes","p":886},{"i":910,"t":"Canvas Becomes Legacy","u":"/7.x/guides/migrations/v5","h":"#canvas-becomes-legacy","p":886},{"i":912,"t":"Bundling Changes","u":"/7.x/guides/migrations/v5","h":"#bundling-changes","p":886},{"i":914,"t":"Webpack","u":"/7.x/guides/migrations/v5","h":"#webpack","p":886},{"i":918,"t":"Life-cycle of a Texture","u":"/7.x/guides/components/textures","h":"#life-cycle-of-a-texture","p":916},{"i":920,"t":"Serving the Image","u":"/7.x/guides/components/textures","h":"#serving-the-image","p":916},{"i":922,"t":"Loading the Image","u":"/7.x/guides/components/textures","h":"#loading-the-image","p":916},{"i":924,"t":"BaseTextures Own the Data","u":"/7.x/guides/components/textures","h":"#basetextures-own-the-data","p":916},{"i":926,"t":"Textures are a View on BaseTextures","u":"/7.x/guides/components/textures","h":"#textures-are-a-view-on-basetextures","p":916},{"i":928,"t":"Loading Textures","u":"/7.x/guides/components/textures","h":"#loading-textures","p":916},{"i":930,"t":"Unloading Textures","u":"/7.x/guides/components/textures","h":"#unloading-textures","p":916},{"i":932,"t":"Beyond Images","u":"/7.x/guides/components/textures","h":"#beyond-images","p":916},{"i":936,"t":"Typings","u":"/7.x/guides/migrations/v6","h":"#typings","p":934},{"i":938,"t":"Mesh Internals","u":"/7.x/guides/migrations/v6","h":"#mesh-internals","p":934},{"i":942,"t":"👋 Dropping Internet Explorer","u":"/7.x/guides/migrations/v7","h":"#-dropping-internet-explorer","p":940},{"i":944,"t":"🗑️ Remove Polyfills","u":"/7.x/guides/migrations/v7","h":"#️-remove-polyfills","p":940},{"i":946,"t":"💬 Output ES2020 (modules) and ES2017 (browser)","u":"/7.x/guides/migrations/v7","h":"#-output-es2020-modules-and-es2017-browser","p":940},{"i":948,"t":"🐭 Replaces InteractionManager with EventSystem","u":"/7.x/guides/migrations/v7","h":"#-replaces-interactionmanager-with-eventsystem","p":940},{"i":950,"t":"📦 Replaces Loader with Assets","u":"/7.x/guides/migrations/v7","h":"#-replaces-loader-with-assets","p":940},{"i":952,"t":"🤝 Abandon the use of peerDependencies","u":"/7.x/guides/migrations/v7","h":"#-abandon-the-use-of-peerdependencies","p":940},{"i":954,"t":"👂 Other Changes","u":"/7.x/guides/migrations/v7","h":"#-other-changes","p":940},{"i":956,"t":"Exports from @pixi/core","u":"/7.x/guides/migrations/v7","h":"#exports-from-pixicore","p":940},{"i":958,"t":"Extract and Prepare Systems","u":"/7.x/guides/migrations/v7","h":"#extract-and-prepare-systems","p":940},{"i":960,"t":"Extensions Self-Install","u":"/7.x/guides/migrations/v7","h":"#extensions-self-install","p":940},{"i":962,"t":"Using hitTest with Events","u":"/7.x/guides/migrations/v7","h":"#using-hittest-with-events","p":940},{"i":964,"t":"New Async Extract Methods","u":"/7.x/guides/migrations/v7","h":"#new-async-extract-methods","p":940},{"i":966,"t":"Interactive Move Events","u":"/7.x/guides/migrations/v7","h":"#interactive-move-events","p":940},{"i":968,"t":"Interactive Property Handlers are Removed","u":"/7.x/guides/migrations/v7","h":"#interactive-property-handlers-are-removed","p":940},{"i":970,"t":"Property buttonMode has been removed","u":"/7.x/guides/migrations/v7","h":"#property-buttonmode-has-been-removed","p":940},{"i":972,"t":"☝️ Suggestions for Upgrading","u":"/7.x/guides/migrations/v7","h":"#️-suggestions-for-upgrading","p":940},{"i":974,"t":"🏗️ Plugin Supported","u":"/7.x/guides/migrations/v7","h":"#️-plugin-supported","p":940},{"i":977,"t":"General","u":"/7.x/guides/production/performance-tips","h":"#general","p":976},{"i":979,"t":"Sprites","u":"/7.x/guides/production/performance-tips","h":"#sprites","p":976},{"i":981,"t":"Graphics","u":"/7.x/guides/production/performance-tips","h":"#graphics","p":976},{"i":983,"t":"Texture","u":"/7.x/guides/production/performance-tips","h":"#texture","p":976},{"i":985,"t":"Text","u":"/7.x/guides/production/performance-tips","h":"#text","p":976},{"i":987,"t":"Masks","u":"/7.x/guides/production/performance-tips","h":"#masks","p":976},{"i":989,"t":"Filters","u":"/7.x/guides/production/performance-tips","h":"#filters","p":976},{"i":991,"t":"BlendModes","u":"/7.x/guides/production/performance-tips","h":"#blendmodes","p":976},{"i":993,"t":"CacheAsBitmap","u":"/7.x/guides/production/performance-tips","h":"#cacheasbitmap","p":976},{"i":995,"t":"Events","u":"/7.x/guides/production/performance-tips","h":"#events","p":976}],"index":{"version":"2.3.9","fields":["t"],"fieldVectors":[["t/523",[0,3.95,1,4.579]],["t/525",[2,2.326,3,5.126]],["t/527",[2,2.326,4,5.126]],["t/529",[2,1.965,5,3.869,6,4.331]],["t/531",[2,2.283,7,2.953,8,1.141,9,3.305]],["t/533",[10,6.279]],["t/535",[0,3.95,11,4.579]],["t/537",[2,2.326,12,5.126]],["t/539",[2,1.965,13,4.331,14,4.331]],["t/541",[0,3.95,15,5.126]],["t/543",[16,6.279]],["t/545",[2,2.326,17,4.579]],["t/547",[0,3.95,18,5.126]],["t/549",[2,2.326,19,4.579]],["t/551",[2,1.965,20,3.869,21,3.869]],["t/553",[22,6.279]],["t/557",[2,1.213,8,0.922,17,2.387,23,2.673,24,2.673,25,1.774,26,2.673]],["t/559",[8,1.494,27,4.331,28,4.331]],["t/561",[8,1.494,29,4.331,30,3.869]],["t/563",[8,1.294,31,3.349,32,3.086,33,3.749]],["t/565",[8,1.494,34,4.331,35,4.331]],["t/567",[8,1.494,36,3.869,37,3.869]],["t/571",[38,6.279]],["t/573",[39,4.575]],["t/575",[39,3.735,40,4.579]],["t/577",[39,3.156,40,3.869,41,3.869]],["t/579",[39,3.735,42,3.95]],["t/581",[39,3.156,41,3.869,42,3.337]],["t/583",[43,4.838]],["t/585",[42,3.337,43,3.337,44,4.331]],["t/587",[42,3.95,43,3.95]],["t/589",[43,3.95,45,5.126]],["t/593",[1,3.349,8,1.294,46,3.749,47,3.749]],["t/595",[8,1.294,11,3.349,48,3.749,49,3.749]],["t/597",[50,4.331,51,4.331,52,3.869]],["t/599",[53,6.279]],["t/601",[54,5.126,55,4.579]],["t/603",[36,4.579,37,4.579]],["t/679",[2,2.85]],["t/681",[2,2.326,56,5.126]],["t/683",[]],["t/685",[57,5.609]],["t/687",[2,2.326,58,4.219]],["t/689",[2,1.965,20,3.869,21,3.869]],["t/691",[2,2.326,59,5.126]],["t/693",[60,5.126,61,5.126]],["t/697",[62,6.279]],["t/699",[63,5.609]],["t/703",[64,6.279]],["t/705",[65,5.168]],["t/707",[65,4.219,66,5.126]],["t/711",[67,4.331,68,4.331,69,4.331]],["t/713",[70,4.331,71,3.565,72,3.565]],["t/715",[71,3.565,72,3.565,73,4.331]],["t/717",[74,5.126,75,5.126]],["t/719",[76,4.331,77,3.869,78,4.331]],["t/723",[2,1.965,8,1.494,79,4.331]],["t/725",[8,1.494,52,3.869,80,3.337]],["t/727",[8,1.494,81,3.869,82,4.331]],["t/729",[5,3.869,8,1.494,83,4.331]],["t/731",[8,1.769,84,5.126]],["t/733",[8,1.494,85,4.331,86,4.331]],["t/737",[2,1.965,8,1.494,87,4.331]],["t/739",[8,1.494,19,3.869,32,3.565]],["t/741",[8,1.494,88,4.331,89,4.331]],["t/743",[7,3.869,8,1.494,90,3.869]],["t/745",[8,1.494,91,3.869,92,4.331]],["t/747",[8,1.494,90,3.869,93,4.331]],["t/749",[8,1.494,94,4.331,95,4.331]],["t/751",[2,2.326,96,4.579]],["t/755",[97,5.126,98,5.126]],["t/757",[99,5.126,100,5.126]],["t/759",[2,1.965,65,3.565,101,3.869]],["t/761",[102,5.126,103,4.579]],["t/763",[103,4.579,104,4.579]],["t/765",[2,2.326,105,3.556]],["t/767",[101,4.579,106,3.95]],["t/769",[107,3.869,108,3.869,109,4.331]],["t/771",[106,3.95,110,4.579]],["t/773",[107,3.869,110,3.869,111,4.331]],["t/775",[112,4.331,113,4.331,114,4.331]],["t/777",[115,5.126,116,5.126]],["t/781",[117,5.126,118,5.126]],["t/783",[119,6.279]],["t/785",[120,6.279]],["t/788",[121,3.95,122,5.126]],["t/790",[57,4.579,123,5.126]],["t/792",[31,3.349,121,2.889,124,3.349,125,3.349]],["t/794",[126,4.331,127,4.331,128,4.331]],["t/796",[129,3.95,130,5.126]],["t/798",[105,3.005,121,3.337,131,4.331]],["t/800",[105,3.556,132,5.126]],["t/804",[71,3.565,72,3.565,133,4.331]],["t/806",[134,5.126,135,5.126]],["t/808",[77,4.579,136,5.126]],["t/810",[137,6.279]],["t/812",[138,3.749,139,2.889,140,3.349,141,3.349]],["t/814",[139,2.889,140,3.349,141,3.349,142,3.749]],["t/818",[143,4.579,144,4.579]],["t/820",[143,4.579,145,4.579]],["t/822",[146,5.126,147,4.579]],["t/824",[148,4.331,149,3.869,150,3.565]],["t/826",[58,3.565,151,4.331,152,3.337]],["t/828",[153,6.279]],["t/832",[154,5.126,155,5.126]],["t/834",[156,5.126,157,5.126]],["t/836",[106,3.95,158,5.126]],["t/840",[80,3.95,106,3.95]],["t/842",[80,3.95,129,3.95]],["t/844",[144,3.349,159,3.749,160,3.749,161,3.749]],["t/846",[8,1.141,139,2.547,162,3.305,163,3.305,164,3.305]],["t/848",[139,3.337,165,4.331,166,4.331]],["t/852",[167,4.331,168,4.331,169,4.331]],["t/856",[25,2.875,170,4.331,171,4.331]],["t/858",[145,4.579,172,5.126]],["t/860",[173,5.126,174,5.126]],["t/862",[25,3.403,175,5.126]],["t/864",[25,3.403,176,5.126]],["t/866",[177,4.579,178,4.579]],["t/870",[179,4.331,180,4.331,181,3.337]],["t/872",[149,4.579,181,3.95]],["t/874",[181,3.95,182,5.126]],["t/876",[105,3.005,129,3.337,183,4.331]],["t/878",[177,4.579,178,4.579]],["t/880",[184,6.279]],["t/882",[185,6.279]],["t/884",[96,3.869,186,4.331,187,4.331]],["t/888",[8,1.494,188,4.331,189,3.337]],["t/889",[81,3.349,124,3.349,125,3.349,190,3.749]],["t/891",[32,4.219,191,5.126]],["t/893",[192,3.869,193,4.331,194,4.331]],["t/895",[25,3.403,195,5.126]],["t/897",[196,4.579,197,5.126]],["t/899",[196,3.349,198,3.749,199,3.749,200,3.749]],["t/901",[201,4.331,202,4.331,203,4.331]],["t/903",[63,4.579,204,5.126]],["t/905",[205,6.279]],["t/907",[25,3.403,147,4.579]],["t/909",[8,1.494,189,3.337,206,4.331]],["t/910",[207,4.331,208,4.331,209,4.331]],["t/912",[189,3.95,210,5.126]],["t/914",[211,6.279]],["t/918",[212,4.331,213,4.331,214,3.869]],["t/920",[104,4.579,215,4.579]],["t/922",[105,3.556,215,4.579]],["t/924",[91,4.579,216,4.579]],["t/926",[108,3.869,216,3.869,217,3.565]],["t/928",[105,3.556,217,4.219]],["t/930",[217,4.219,218,5.126]],["t/932",[219,5.126,220,5.126]],["t/936",[221,6.279]],["t/938",[192,4.579,222,5.126]],["t/942",[8,1.294,223,3.749,224,3.749,225,3.749]],["t/944",[8,1.494,226,4.331,227,4.331]],["t/946",[8,1.02,228,2.955,229,2.955,230,2.955,231,2.955,232,2.955]],["t/948",[8,1.294,233,3.349,234,3.749,235,3.749]],["t/950",[8,1.294,121,2.889,233,3.349,236,3.749]],["t/952",[8,1.294,58,3.086,237,3.749,238,3.749]],["t/954",[8,1.769,189,3.95]],["t/956",[239,5.126,240,5.126]],["t/958",[241,3.869,242,4.331,243,4.331]],["t/960",[55,3.869,244,4.331,245,4.331]],["t/962",[129,3.337,152,3.337,246,4.331]],["t/964",[30,3.349,241,3.349,247,3.749,248,3.749]],["t/966",[150,3.565,152,3.337,249,4.331]],["t/968",[150,3.086,250,3.349,251,3.749,252,3.349]],["t/970",[250,3.869,252,3.869,253,4.331]],["t/972",[8,1.494,254,4.331,255,4.331]],["t/974",[8,1.494,256,4.331,257,4.331]],["t/977",[258,6.279]],["t/979",[80,4.838]],["t/981",[25,4.168]],["t/983",[214,5.609]],["t/985",[181,4.838]],["t/987",[259,6.279]],["t/989",[260,6.279]],["t/991",[261,6.279]],["t/993",[262,6.279]],["t/995",[152,4.838]]],"invertedIndex":[["",{"_index":8,"t":{"531":{"position":[[10,1]]},"557":{"position":[[0,2]]},"559":{"position":[[0,2]]},"561":{"position":[[0,2]]},"563":{"position":[[0,1]]},"565":{"position":[[0,2]]},"567":{"position":[[0,2]]},"593":{"position":[[3,2]]},"595":{"position":[[3,2]]},"723":{"position":[[10,3]]},"725":{"position":[[0,3]]},"727":{"position":[[0,3]]},"729":{"position":[[0,3]]},"731":{"position":[[0,3]]},"733":{"position":[[0,3]]},"737":{"position":[[14,3]]},"739":{"position":[[0,3]]},"741":{"position":[[0,3]]},"743":{"position":[[0,3]]},"745":{"position":[[0,3]]},"747":{"position":[[0,3]]},"749":{"position":[[0,3]]},"846":{"position":[[15,1]]},"888":{"position":[[0,2]]},"909":{"position":[[0,2]]},"942":{"position":[[0,2]]},"944":{"position":[[0,3]]},"946":{"position":[[0,2]]},"948":{"position":[[0,2]]},"950":{"position":[[0,2]]},"952":{"position":[[0,2]]},"954":{"position":[[0,2]]},"972":{"position":[[0,2]]},"974":{"position":[[0,3]]}}}],["1",{"_index":1,"t":{"523":{"position":[[6,2]]},"593":{"position":[[0,2]]}}}],["2",{"_index":11,"t":{"535":{"position":[[6,2]]},"595":{"position":[[0,2]]}}}],["3",{"_index":15,"t":{"541":{"position":[[6,2]]}}}],["3d",{"_index":19,"t":{"549":{"position":[[7,3]]},"739":{"position":[[6,2]]}}}],["4",{"_index":18,"t":{"547":{"position":[[6,2]]}}}],["abandon",{"_index":237,"t":{"952":{"position":[[3,7]]}}}],["adding",{"_index":107,"t":{"769":{"position":[[0,6]]},"773":{"position":[[0,6]]}}}],["advanced",{"_index":97,"t":{"755":{"position":[[0,8]]}}}],["alpha",{"_index":159,"t":{"844":{"position":[[0,6]]}}}],["anatomy",{"_index":154,"t":{"832":{"position":[[0,7]]}}}],["anchor",{"_index":166,"t":{"848":{"position":[[9,6]]}}}],["api",{"_index":188,"t":{"888":{"position":[[3,3]]}}}],["app",{"_index":89,"t":{"741":{"position":[[13,3]]}}}],["application",{"_index":101,"t":{"759":{"position":[[23,11]]},"767":{"position":[[12,11]]}}}],["approach",{"_index":187,"t":{"884":{"position":[[20,8]]}}}],["assetpack",{"_index":10,"t":{"533":{"position":[[0,10]]}}}],["assets",{"_index":121,"t":{"788":{"position":[[4,6]]},"792":{"position":[[17,6]]},"798":{"position":[[17,6]]},"950":{"position":[[24,6]]}}}],["async",{"_index":247,"t":{"964":{"position":[[4,5]]}}}],["async/await",{"_index":130,"t":{"796":{"position":[[6,11]]}}}],["attributes",{"_index":169,"t":{"852":{"position":[[14,10]]}}}],["audio",{"_index":93,"t":{"747":{"position":[[7,5]]}}}],["background",{"_index":132,"t":{"800":{"position":[[0,10]]}}}],["banner",{"_index":38,"t":{"571":{"position":[[0,6]]}}}],["basetexture",{"_index":204,"t":{"903":{"position":[[0,11]]}}}],["basetexture.source",{"_index":205,"t":{"905":{"position":[[0,18]]}}}],["basetextures",{"_index":216,"t":{"924":{"position":[[0,12]]},"926":{"position":[[23,12]]}}}],["becomes",{"_index":208,"t":{"910":{"position":[[7,7]]}}}],["beyond",{"_index":219,"t":{"932":{"position":[[0,6]]}}}],["bitmapfont",{"_index":185,"t":{"882":{"position":[[0,10]]}}}],["bitmaptext",{"_index":184,"t":{"880":{"position":[[0,10]]}}}],["blend",{"_index":161,"t":{"844":{"position":[[16,5]]}}}],["blendmodes",{"_index":261,"t":{"991":{"position":[[0,10]]}}}],["browser",{"_index":232,"t":{"946":{"position":[[38,9]]}}}],["bug",{"_index":61,"t":{"693":{"position":[[10,4]]}}}],["building",{"_index":170,"t":{"856":{"position":[[18,8]]}}}],["bundling",{"_index":210,"t":{"912":{"position":[[0,8]]}}}],["buttonmode",{"_index":253,"t":{"970":{"position":[[9,10]]}}}],["cacheasbitmap",{"_index":262,"t":{"993":{"position":[[0,13]]}}}],["callbacks",{"_index":69,"t":{"711":{"position":[[15,9]]}}}],["canvas",{"_index":207,"t":{"910":{"position":[[0,6]]}}}],["caveats",{"_index":177,"t":{"866":{"position":[[0,7]]},"878":{"position":[[0,7]]}}}],["changes",{"_index":189,"t":{"888":{"position":[[7,7]]},"909":{"position":[[14,7]]},"912":{"position":[[9,7]]},"954":{"position":[[9,7]]}}}],["checking",{"_index":148,"t":{"824":{"position":[[0,8]]}}}],["children",{"_index":135,"t":{"806":{"position":[[12,8]]}}}],["class",{"_index":190,"t":{"889":{"position":[[19,5]]}}}],["code",{"_index":64,"t":{"703":{"position":[[4,4]]}}}],["comet",{"_index":16,"t":{"543":{"position":[[0,6]]}}}],["commonly",{"_index":167,"t":{"852":{"position":[[0,8]]}}}],["components",{"_index":65,"t":{"705":{"position":[[4,10]]},"707":{"position":[[6,10]]},"759":{"position":[[0,10]]}}}],["conclusion",{"_index":22,"t":{"553":{"position":[[0,10]]}}}],["containers",{"_index":117,"t":{"781":{"position":[[0,10]]}}}],["coordinates",{"_index":141,"t":{"812":{"position":[[16,11]]},"814":{"position":[[17,11]]}}}],["creating",{"_index":106,"t":{"767":{"position":[[0,8]]},"771":{"position":[[0,8]]},"836":{"position":[[0,8]]},"840":{"position":[[0,8]]}}}],["culling",{"_index":137,"t":{"810":{"position":[[0,7]]}}}],["custom",{"_index":76,"t":{"719":{"position":[[0,6]]}}}],["cycle",{"_index":213,"t":{"918":{"position":[[5,5]]}}}],["dark",{"_index":40,"t":{"575":{"position":[[5,6]]},"577":{"position":[[5,6]]}}}],["data",{"_index":91,"t":{"745":{"position":[[6,4]]},"924":{"position":[[21,4]]}}}],["default",{"_index":198,"t":{"899":{"position":[[7,7]]}}}],["deploy",{"_index":86,"t":{"733":{"position":[[12,6]]}}}],["dev",{"_index":13,"t":{"539":{"position":[[7,3]]}}}],["development",{"_index":94,"t":{"749":{"position":[[6,11]]}}}],["display",{"_index":175,"t":{"862":{"position":[[13,7]]}}}],["dom",{"_index":109,"t":{"769":{"position":[[23,3]]}}}],["doubly",{"_index":156,"t":{"834":{"position":[[0,6]]}}}],["drawing",{"_index":171,"t":{"856":{"position":[[33,7]]}}}],["dropping",{"_index":223,"t":{"942":{"position":[[3,8]]}}}],["easy",{"_index":85,"t":{"733":{"position":[[4,4]]}}}],["efficient",{"_index":157,"t":{"834":{"position":[[7,9]]}}}],["embracing",{"_index":46,"t":{"593":{"position":[[6,9]]}}}],["enable",{"_index":201,"t":{"901":{"position":[[0,6]]}}}],["enabling",{"_index":146,"t":{"822":{"position":[[0,8]]}}}],["engine",{"_index":21,"t":{"551":{"position":[[12,7]]},"689":{"position":[[17,7]]}}}],["environment",{"_index":95,"t":{"749":{"position":[[18,11]]}}}],["es2017",{"_index":231,"t":{"946":{"position":[[31,6]]}}}],["es2020",{"_index":229,"t":{"946":{"position":[[10,6]]}}}],["event",{"_index":143,"t":{"818":{"position":[[0,5]]},"820":{"position":[[0,5]]}}}],["events",{"_index":152,"t":{"826":{"position":[[12,6]]},"962":{"position":[[19,6]]},"966":{"position":[[17,6]]},"995":{"position":[[0,6]]}}}],["eventsystem",{"_index":235,"t":{"948":{"position":[[36,11]]}}}],["explorer",{"_index":225,"t":{"942":{"position":[[21,8]]}}}],["exports",{"_index":239,"t":{"956":{"position":[[0,7]]}}}],["extensible",{"_index":84,"t":{"731":{"position":[[4,10]]}}}],["extensions",{"_index":244,"t":{"960":{"position":[[0,10]]}}}],["extract",{"_index":241,"t":{"958":{"position":[[0,7]]},"964":{"position":[[10,7]]}}}],["fast",{"_index":79,"t":{"723":{"position":[[14,4]]}}}],["file",{"_index":103,"t":{"761":{"position":[[9,4]]},"763":{"position":[[12,4]]}}}],["filter",{"_index":196,"t":{"897":{"position":[[0,6]]},"899":{"position":[[0,6]]}}}],["filtering",{"_index":120,"t":{"785":{"position":[[0,9]]}}}],["filters",{"_index":260,"t":{"989":{"position":[[0,7]]}}}],["first",{"_index":125,"t":{"792":{"position":[[11,5]]},"889":{"position":[[13,5]]}}}],["fonts",{"_index":183,"t":{"876":{"position":[[18,5]]}}}],["found",{"_index":60,"t":{"693":{"position":[[2,5]]}}}],["frame",{"_index":74,"t":{"717":{"position":[[0,5]]}}}],["framework",{"_index":87,"t":{"737":{"position":[[20,9]]}}}],["free",{"_index":56,"t":{"681":{"position":[[10,5]]}}}],["game",{"_index":20,"t":{"551":{"position":[[7,4]]},"689":{"position":[[12,4]]}}}],["games",{"_index":6,"t":{"529":{"position":[[12,6]]}}}],["general",{"_index":258,"t":{"977":{"position":[[0,7]]}}}],["geometry",{"_index":173,"t":{"860":{"position":[[4,8]]}}}],["getting",{"_index":123,"t":{"790":{"position":[[0,7]]}}}],["global",{"_index":140,"t":{"812":{"position":[[9,6]]},"814":{"position":[[0,6]]}}}],["gotchas",{"_index":178,"t":{"866":{"position":[[12,7]]},"878":{"position":[[12,7]]}}}],["graph",{"_index":72,"t":{"713":{"position":[[19,5]]},"715":{"position":[[20,5]]},"804":{"position":[[10,5]]}}}],["graphics",{"_index":25,"t":{"557":{"position":[[23,9]]},"856":{"position":[[0,8]]},"862":{"position":[[0,8]]},"864":{"position":[[0,8]]},"895":{"position":[[0,8]]},"907":{"position":[[0,8]]},"981":{"position":[[0,8]]}}}],["groups",{"_index":118,"t":{"781":{"position":[[14,6]]}}}],["guides",{"_index":62,"t":{"697":{"position":[[10,6]]}}}],["handlers",{"_index":251,"t":{"968":{"position":[[21,8]]}}}],["height",{"_index":164,"t":{"846":{"position":[[17,6]]}}}],["hittest",{"_index":246,"t":{"962":{"position":[[6,7]]}}}],["holes",{"_index":195,"t":{"895":{"position":[[9,5]]}}}],["html",{"_index":102,"t":{"761":{"position":[[4,4]]}}}],["image",{"_index":215,"t":{"920":{"position":[[12,5]]},"922":{"position":[[12,5]]}}}],["images",{"_index":220,"t":{"932":{"position":[[7,6]]}}}],["install",{"_index":55,"t":{"601":{"position":[[9,8]]},"960":{"position":[[16,7]]}}}],["interaction",{"_index":147,"t":{"822":{"position":[[9,11]]},"907":{"position":[[9,11]]}}}],["interactionmanager",{"_index":234,"t":{"948":{"position":[[12,18]]}}}],["interactive",{"_index":150,"t":{"824":{"position":[[22,11]]},"966":{"position":[[0,11]]},"968":{"position":[[0,11]]}}}],["internals",{"_index":222,"t":{"938":{"position":[[5,9]]}}}],["internet",{"_index":224,"t":{"942":{"position":[[12,8]]}}}],["involved",{"_index":35,"t":{"565":{"position":[[17,9]]}}}],["javascript",{"_index":100,"t":{"757":{"position":[[13,10]]}}}],["jumpstart",{"_index":12,"t":{"537":{"position":[[7,10]]}}}],["keep",{"_index":36,"t":{"567":{"position":[[3,4]]},"603":{"position":[[0,4]]}}}],["kinds",{"_index":180,"t":{"870":{"position":[[14,5]]}}}],["large",{"_index":44,"t":{"585":{"position":[[12,6]]}}}],["layout",{"_index":9,"t":{"531":{"position":[[19,7]]}}}],["legacy",{"_index":209,"t":{"910":{"position":[[15,6]]}}}],["library",{"_index":90,"t":{"743":{"position":[[9,7]]},"747":{"position":[[13,7]]}}}],["life",{"_index":212,"t":{"918":{"position":[[0,4]]}}}],["light",{"_index":45,"t":{"589":{"position":[[5,7]]}}}],["links",{"_index":28,"t":{"559":{"position":[[9,5]]}}}],["list",{"_index":174,"t":{"860":{"position":[[13,4]]}}}],["loader",{"_index":236,"t":{"950":{"position":[[12,6]]}}}],["loading",{"_index":105,"t":{"765":{"position":[[0,7]]},"798":{"position":[[0,7]]},"800":{"position":[[11,7]]},"876":{"position":[[0,7]]},"922":{"position":[[0,7]]},"928":{"position":[[0,7]]}}}],["local",{"_index":138,"t":{"812":{"position":[[0,5]]}}}],["logo",{"_index":39,"t":{"573":{"position":[[0,4]]},"575":{"position":[[0,4]]},"577":{"position":[[0,4]]},"579":{"position":[[0,4]]},"581":{"position":[[0,4]]}}}],["loop",{"_index":114,"t":{"775":{"position":[[18,4]]}}}],["loops",{"_index":78,"t":{"719":{"position":[[14,5]]}}}],["major",{"_index":66,"t":{"707":{"position":[[0,5]]}}}],["makes",{"_index":59,"t":{"691":{"position":[[4,5]]}}}],["making",{"_index":124,"t":{"792":{"position":[[0,6]]},"889":{"position":[[0,6]]}}}],["mark",{"_index":43,"t":{"583":{"position":[[0,4]]},"585":{"position":[[0,4]]},"587":{"position":[[0,4]]},"589":{"position":[[0,4]]}}}],["mask",{"_index":176,"t":{"864":{"position":[[14,4]]}}}],["masking",{"_index":119,"t":{"783":{"position":[[0,7]]}}}],["masks",{"_index":259,"t":{"987":{"position":[[0,5]]}}}],["mesh",{"_index":192,"t":{"893":{"position":[[0,5]]},"938":{"position":[[0,4]]}}}],["methods",{"_index":248,"t":{"964":{"position":[[18,7]]}}}],["mipmapping",{"_index":202,"t":{"901":{"position":[[7,10]]}}}],["mobile",{"_index":88,"t":{"741":{"position":[[6,6]]}}}],["modes",{"_index":144,"t":{"818":{"position":[[6,5]]},"844":{"position":[[22,5]]}}}],["modules",{"_index":230,"t":{"946":{"position":[[17,9]]}}}],["more",{"_index":52,"t":{"597":{"position":[[18,5]]},"725":{"position":[[4,4]]}}}],["move",{"_index":249,"t":{"966":{"position":[[12,4]]}}}],["multiple",{"_index":131,"t":{"798":{"position":[[8,8]]}}}],["native",{"_index":82,"t":{"727":{"position":[[10,6]]}}}],["new",{"_index":30,"t":{"561":{"position":[[9,4]]},"964":{"position":[[0,3]]}}}],["note",{"_index":99,"t":{"757":{"position":[[2,4]]}}}],["now",{"_index":34,"t":{"565":{"position":[[8,4]]}}}],["object",{"_index":149,"t":{"824":{"position":[[12,6]]},"872":{"position":[[9,6]]}}}],["open",{"_index":5,"t":{"529":{"position":[[7,4]]},"729":{"position":[[4,4]]}}}],["optimization",{"_index":153,"t":{"828":{"position":[[0,12]]}}}],["order",{"_index":136,"t":{"808":{"position":[[7,5]]}}}],["output",{"_index":228,"t":{"946":{"position":[[3,6]]}}}],["over",{"_index":53,"t":{"599":{"position":[[0,4]]}}}],["package",{"_index":122,"t":{"788":{"position":[[11,7]]}}}],["padding",{"_index":197,"t":{"897":{"position":[[7,7]]}}}],["parameters",{"_index":191,"t":{"891":{"position":[[9,10]]}}}],["parents",{"_index":134,"t":{"806":{"position":[[0,7]]}}}],["peerdependencies",{"_index":238,"t":{"952":{"position":[[22,16]]}}}],["performance",{"_index":49,"t":{"595":{"position":[[20,11]]}}}],["phase",{"_index":0,"t":{"523":{"position":[[0,5]]},"535":{"position":[[0,5]]},"541":{"position":[[0,5]]},"547":{"position":[[0,5]]}}}],["pink",{"_index":42,"t":{"579":{"position":[[5,6]]},"581":{"position":[[5,6]]},"585":{"position":[[5,6]]},"587":{"position":[[5,6]]}}}],["pivot",{"_index":165,"t":{"848":{"position":[[0,5]]}}}],["pixi/core",{"_index":240,"t":{"956":{"position":[[13,10]]}}}],["pixijs",{"_index":2,"t":{"525":{"position":[[0,6]]},"527":{"position":[[0,6]]},"529":{"position":[[0,6]]},"531":{"position":[[0,6],[12,6]]},"537":{"position":[[0,6]]},"539":{"position":[[0,6]]},"545":{"position":[[0,6]]},"549":{"position":[[0,6]]},"551":{"position":[[0,6]]},"557":{"position":[[44,6]]},"679":{"position":[[8,6]]},"681":{"position":[[3,6]]},"687":{"position":[[17,7]]},"689":{"position":[[3,6]]},"691":{"position":[[10,7]]},"723":{"position":[[0,6]]},"737":{"position":[[0,6]]},"751":{"position":[[6,6]]},"759":{"position":[[16,6]]},"765":{"position":[[8,6]]}}}],["plane",{"_index":193,"t":{"893":{"position":[[6,6]]}}}],["plugin",{"_index":256,"t":{"974":{"position":[[4,6]]}}}],["pointer",{"_index":151,"t":{"826":{"position":[[4,7]]}}}],["polyfills",{"_index":227,"t":{"944":{"position":[[11,9]]}}}],["prepare",{"_index":242,"t":{"958":{"position":[[12,7]]}}}],["primitives",{"_index":172,"t":{"858":{"position":[[9,10]]}}}],["promise",{"_index":31,"t":{"563":{"position":[[5,7]]},"792":{"position":[[24,7]]}}}],["promises",{"_index":128,"t":{"794":{"position":[[21,8]]}}}],["property",{"_index":250,"t":{"968":{"position":[[12,8]]},"970":{"position":[[0,8]]}}}],["publishing",{"_index":206,"t":{"909":{"position":[[3,10]]}}}],["putting",{"_index":115,"t":{"777":{"position":[[0,7]]}}}],["quick",{"_index":27,"t":{"559":{"position":[[3,5]]}}}],["rates",{"_index":75,"t":{"717":{"position":[[6,5]]}}}],["react",{"_index":4,"t":{"527":{"position":[[7,6]]}}}],["remove",{"_index":226,"t":{"944":{"position":[[4,6]]}}}],["removed",{"_index":252,"t":{"968":{"position":[[34,7]]},"970":{"position":[[29,7]]}}}],["render",{"_index":77,"t":{"719":{"position":[[7,6]]},"808":{"position":[[0,6]]}}}],["renderer",{"_index":32,"t":{"563":{"position":[[17,8]]},"739":{"position":[[9,8]]},"891":{"position":[[0,8]]}}}],["rendering",{"_index":73,"t":{"715":{"position":[[0,9]]}}}],["rendertexture",{"_index":203,"t":{"901":{"position":[[22,13]]}}}],["replaces",{"_index":233,"t":{"948":{"position":[[3,8]]},"950":{"position":[[3,8]]}}}],["resources",{"_index":63,"t":{"699":{"position":[[6,9]]},"903":{"position":[[12,9]]}}}],["revolutionizing",{"_index":23,"t":{"557":{"position":[[3,15]]}}}],["right",{"_index":96,"t":{"751":{"position":[[13,5]]},"884":{"position":[[14,5]]}}}],["rope",{"_index":194,"t":{"893":{"position":[[13,4]]}}}],["running",{"_index":67,"t":{"711":{"position":[[0,7]]}}}],["scale",{"_index":162,"t":{"846":{"position":[[0,5]]}}}],["scene",{"_index":71,"t":{"713":{"position":[[13,5]]},"715":{"position":[[14,5]]},"804":{"position":[[4,5]]}}}],["screen",{"_index":142,"t":{"814":{"position":[[10,6]]}}}],["selecting",{"_index":186,"t":{"884":{"position":[[0,9]]}}}],["self",{"_index":245,"t":{"960":{"position":[[11,4]]}}}],["serving",{"_index":104,"t":{"763":{"position":[[0,7]]},"920":{"position":[[0,7]]}}}],["shader",{"_index":200,"t":{"899":{"position":[[22,6]]}}}],["solved",{"_index":127,"t":{"794":{"position":[[14,6]]}}}],["source",{"_index":83,"t":{"729":{"position":[[9,6]]}}}],["sprite",{"_index":110,"t":{"771":{"position":[[11,6]]},"773":{"position":[[11,6]]}}}],["sprites",{"_index":80,"t":{"725":{"position":[[19,7]]},"840":{"position":[[9,7]]},"842":{"position":[[6,7]]},"979":{"position":[[0,7]]}}}],["spritesheet",{"_index":155,"t":{"832":{"position":[[13,11]]}}}],["spritesheets",{"_index":158,"t":{"836":{"position":[[9,12]]}}}],["stage",{"_index":111,"t":{"773":{"position":[[25,5]]}}}],["started",{"_index":57,"t":{"685":{"position":[[13,8]]},"790":{"position":[[8,7]]}}}],["steps",{"_index":54,"t":{"601":{"position":[[0,5]]}}}],["store",{"_index":92,"t":{"745":{"position":[[11,5]]}}}],["styles",{"_index":182,"t":{"874":{"position":[[5,6]]}}}],["suggestions",{"_index":254,"t":{"972":{"position":[[3,11]]}}}],["supported",{"_index":257,"t":{"974":{"position":[[11,9]]}}}],["systems",{"_index":243,"t":{"958":{"position":[[20,7]]}}}],["text",{"_index":181,"t":{"870":{"position":[[23,4]]},"872":{"position":[[4,4]]},"874":{"position":[[0,4]]},"985":{"position":[[0,4]]}}}],["texture",{"_index":214,"t":{"918":{"position":[[16,7]]},"983":{"position":[[0,7]]}}}],["textures",{"_index":217,"t":{"926":{"position":[[0,8]]},"928":{"position":[[8,8]]},"930":{"position":[[10,8]]}}}],["there's",{"_index":51,"t":{"597":{"position":[[10,7]]}}}],["ticker",{"_index":68,"t":{"711":{"position":[[8,6]]}}}],["tint",{"_index":160,"t":{"844":{"position":[[7,4]]}}}],["together",{"_index":116,"t":{"777":{"position":[[15,8]]}}}],["tools",{"_index":14,"t":{"539":{"position":[[11,6]]}}}],["touch",{"_index":37,"t":{"567":{"position":[[11,5]]},"603":{"position":[[8,6]]}}}],["transparent",{"_index":41,"t":{"577":{"position":[[12,12]]},"581":{"position":[[12,12]]}}}],["tree",{"_index":133,"t":{"804":{"position":[[21,4]]}}}],["turbocharging",{"_index":48,"t":{"595":{"position":[[6,13]]}}}],["two",{"_index":179,"t":{"870":{"position":[[10,3]]}}}],["types",{"_index":145,"t":{"820":{"position":[[6,5]]},"858":{"position":[[0,5]]}}}],["typings",{"_index":221,"t":{"936":{"position":[[0,7]]}}}],["ui",{"_index":7,"t":{"531":{"position":[[7,2]]},"743":{"position":[[6,2]]}}}],["unloading",{"_index":218,"t":{"930":{"position":[[0,9]]}}}],["update",{"_index":113,"t":{"775":{"position":[[11,6]]}}}],["updating",{"_index":70,"t":{"713":{"position":[[0,8]]}}}],["upgrading",{"_index":255,"t":{"972":{"position":[[19,9]]}}}],["use",{"_index":58,"t":{"687":{"position":[[13,3]]},"826":{"position":[[0,3]]},"952":{"position":[[15,3]]}}}],["used",{"_index":168,"t":{"852":{"position":[[9,4]]}}}],["users",{"_index":98,"t":{"755":{"position":[[9,5]]}}}],["using",{"_index":129,"t":{"796":{"position":[[0,5]]},"842":{"position":[[0,5]]},"876":{"position":[[12,5]]},"962":{"position":[[0,5]]}}}],["v8",{"_index":17,"t":{"545":{"position":[[7,3]]},"557":{"position":[[51,2]]}}}],["vertex",{"_index":199,"t":{"899":{"position":[[15,6]]}}}],["view",{"_index":108,"t":{"769":{"position":[[11,4]]},"926":{"position":[[15,4]]}}}],["vs",{"_index":139,"t":{"812":{"position":[[6,2]]},"814":{"position":[[7,2]]},"846":{"position":[[6,2]]},"848":{"position":[[6,2]]}}}],["wait",{"_index":50,"t":{"597":{"position":[[4,5]]}}}],["warning",{"_index":126,"t":{"794":{"position":[[0,7]]}}}],["web",{"_index":24,"t":{"557":{"position":[[19,3]]}}}],["webgl",{"_index":81,"t":{"727":{"position":[[4,5]]},"889":{"position":[[7,5]]}}}],["webgpu",{"_index":47,"t":{"593":{"position":[[16,6]]}}}],["webpack",{"_index":211,"t":{"914":{"position":[[0,7]]}}}],["website",{"_index":3,"t":{"525":{"position":[[7,8]]}}}],["welcome",{"_index":26,"t":{"557":{"position":[[33,7]]}}}],["whats",{"_index":29,"t":{"561":{"position":[[3,5]]}}}],["width",{"_index":163,"t":{"846":{"position":[[9,5]]}}}],["work",{"_index":33,"t":{"563":{"position":[[31,4]]}}}],["writing",{"_index":112,"t":{"775":{"position":[[0,7]]}}}]],"pipeline":["stemmer"]}},{"documents":[{"i":522,"t":"We are excited to announce the launch of the PixiJS Universe, an initiative to further enhance the capabilities of PixiJS and make it even easier for developers to create amazing games and apps. For years, PixiJS has been the most popular 2D renderer for the web, but it was always \"just a rendering engine.\" The community has created many great tools to help developers build games and applications with PixiJS, but we lacked the manpower to maintain and improve these tools, causing fragmentation in the community. To address this, Playco has assembled a dedicated team of developers who will work on PixiJS full-time. Over the next 12 months, we have more than ten projects planned, and in this announcement, we would like to introduce some of them.","s":"Introducing the PixiJS Universe!","u":"/blog/pixi-universe","h":"","p":521},{"i":524,"t":"We have several new projects in the works, and we are releasing them in four phases over the year. We are currently nearing the end of phase 1, which has seen the release of several exciting new tools and libraries for PixiJS:","s":"Phase 1​","u":"/blog/pixi-universe","h":"#phase-1","p":521},{"i":526,"t":"First up we are excited to announce that we will be releasing a beta version of our new website for PixiJS. The current documentation has suffered from long-term organic growth over the years with little structure and as such much of the PixiJS documentation is fragmented across multiple sites. This new website will put everything you need in one place and be easily searchable. We are also planning on improving the documentation and adding more guides and examples, as well as considering translating the documentation into other languages. If you have any ideas on how to improve the site please let us know! Github Repo","s":"PixiJS Website​","u":"/blog/pixi-universe","h":"#pixijs-website","p":521},{"i":528,"t":"Next up is PixiJS React. A library that provides a way to use PixiJS in React applications. PixiJS React is a continuation of the react-pixi library created by Patrick Brouwer, which has been widely used by the PixiJS community in React projects. PixiJS has taken over maintenance of the library and is dedicated to improving the library's performance, documentation, and support for new features. The library provides an easy-to-use interface that makes it easy for developers to create React applications with PixiJS. The library integrates all the core features of PixiJS and allows you to create custom components for 3rd party libraries. Github Repo","s":"PixiJS React​","u":"/blog/pixi-universe","h":"#pixijs-react","p":521},{"i":530,"t":"We have now released a new repository called \"PixiJS Open Games\". This is a collection of open-source games that showcases how to use PixiJS to create games and is released under the MIT license. The purpose of these games is to provide professional examples of how to use PixiJS for game development. The first two games released are a match-3 game and a bubble shooter game. These games not only demonstrate how to use PixiJS for game development but also how to use other libraries such as PixiJS UI and AssetPack. We also have more games planned for release, which will demonstrate how to use PixiJS with other libraries such as PixiJS Layout and PixiJS React. PixiJS Open Games will hopefully be a valuable resource for game developers who want to learn how to use PixiJS for game development and will also be a great source of inspiration for developers looking to create their own games using PixiJS. The project is available on GitHub for anyone who wants to explore the code or contribute to the project. Github Repo Play Puzzling Potions Play Bubbo Bubbo","s":"PixiJS Open Games​","u":"/blog/pixi-universe","h":"#pixijs-open-games","p":521},{"i":532,"t":"PixiJS UI is a new library for developers who want to create beautiful and functional user interfaces with PixiJS. The library includes a range of components such as buttons, checkboxes, sliders, text inputs, scroll views, lists, radio buttons, and progress bars, which can be easily integrated into your game. These components are highly customizable, allowing you to tweak the appearance and behaviour of each element to fit your game's specific needs. PixiJS UI has been used in all of the open-source games, so feel free to check these out for real-world examples. PixiJS UI Github Repo PixiJS Layout is another library that can make your life as a developer easier. This library enables you to create responsive layouts using PixiJS, which means you can design interfaces that adapt to different screen sizes and aspect ratios. PixiJS Layout works well with PixiJS UI, allowing you to combine both libraries to create complex, dynamic interfaces that respond to user input and screen changes. With PixiJS Layout, you have the flexibility to create resizable layouts that can be adjusted to fit any screen size or device. This means that your game's interface can look great on everything from small mobile devices to large desktop displays. PixiJS Layout is still under development but will be ready in the next few weeks PixiJS Layout Github Repo","s":"PixiJS UI & PixiJS Layout​","u":"/blog/pixi-universe","h":"#pixijs-ui--pixijs-layout","p":521},{"i":534,"t":"Finally, for phase 1 we are announcing AssetPack. Asset management is an important part of developing applications, and the new AssetPack library aims to make this process easier. AssetPack is a framework-agnostic library that can be used with any framework, including PixiJS, ThreeJS, and Phaser. It provides a range of features that help developers manage their assets efficiently. The key feature of AssetPack is the ability to automatically generate new assets on the fly. For example, you can provide it with a folder of individual images and it will generate sprite sheets, which can significantly improve the performance of your application. It also provides plugins to generate mipmaps, convert fonts to different formats, convert audio to different formats, compress images, and minify JSON. These features help developers optimize their assets for faster loading times, better performance, and improved user experience. We will soon be releasing a new blog post that provides more details on how to use it. With its many useful features and framework-agnostic design. However, if you want to get stuck in today then check out the GitHub repo Github Repo","s":"AssetPack​","u":"/blog/pixi-universe","h":"#assetpack","p":521},{"i":536,"t":"Phase 2 will begin shortly and aims to make it easier to work with PixiJS applications.","s":"Phase 2​","u":"/blog/pixi-universe","h":"#phase-2","p":521},{"i":538,"t":"Jumpstart is a new CLI tool being developed by the PixiJS team to simplify the process of creating new PixiJS applications. With this tool, developers will no longer need to set up complicated tooling or worry about setting up different bundlers and frameworks. The tool will handle all the setup for you, allowing you to focus on building your application. Jumpstart will be similar to other CLI tools such as Create-React-App or Create-Vue, which have become popular in the front-end development community. The tool will provide templates for different bundlers and frameworks, including webpack, parcel, rollup, and more. This will make it easy for developers to get started with PixiJS regardless of their preferred tools and workflows. With Jumpstart, you'll be able to create a new PixiJS application in just a few minutes and start building right away.","s":"PixiJS JumpStart​","u":"/blog/pixi-universe","h":"#pixijs-jumpstart","p":521},{"i":540,"t":"PixiJS dev tools will be a browser extension that is planned to be released soon to help developers debug their PixiJS applications. This tool aims to make it easier for developers to understand the inner workings of PixiJS, optimize their code, and follow best practices. It is designed to help developers diagnose performance issues and visualize the resources that their applications are consuming. One of the key features of the PixiJS Dev Tools is its ability to help developers understand the complex process of batching in PixiJS. Batching is a technique used to optimize the rendering of multiple objects in the same draw call. This process can be complicated to understand, especially for new developers. PixiJS Dev Tools aims to make it easier to debug and optimize the rendering of objects. Overall, PixiJS Dev Tools will be a powerful tool that will make it easier for developers to build high-performance, visually stunning applications with PixiJS. By providing developers with a deeper understanding of the inner workings of PixiJS, this toolset will help developers optimize their code and create more efficient and engaging applications.","s":"PixiJS Dev Tools​","u":"/blog/pixi-universe","h":"#pixijs-dev-tools","p":521},{"i":542,"t":"Phase 3 is where our long-term projects start to be revealed. These are major changes to the PixiJS ecosystem that we are incredibly excited about","s":"Phase 3​","u":"/blog/pixi-universe","h":"#phase-3","p":521},{"i":544,"t":"Comet will be a new editor that aims to make it easier than ever to design and create games and applications with PixiJS. With its intuitive and user-friendly interface, the editor is designed to appeal to both designers and developers, allowing both groups to collaborate and work more efficiently. One of the standout features of Comet is the visual interface it provides for creating and editing scenes, sprites, animations, and more. This means that designers can create and edit complex scenes without ever having to write a single line of code. The editor provides a range of tools and options for creating sprites, animations, and other game elements, making it easy to get started with creating a game or application. In addition, developers will appreciate the runtime player feature, which allows them to easily recreate scenes in their own applications. This makes it easy to test and iterate on designs, ensuring that the final product is both functional and visually appealing. And with multi-user, real-time collaboration, Comet makes it easy for teams to work together, sharing assets and ideas and creating high-quality games and applications in record time.","s":"Comet​","u":"/blog/pixi-universe","h":"#comet","p":521},{"i":546,"t":"PixiJS v8 will be the next major release that represents a complete rewrite of PixiJS from the ground up. The development team has leveraged their extensive experience over many years to make improvements and optimizations to the core PixiJS engine. The new version of PixiJS is designed to be faster and more efficient, providing a significant improvement in rendering performance compared to v7 (currently sitting at x2) One of the most exciting features of PixiJS v8 is the inclusion of first-class support for WebGPU, which is a new graphics API that is being developed by major browser vendors. This will enable developers to take advantage of advanced GPU capabilities, which can significantly improve the performance of graphics-intensive applications. In addition to WebGPU support, the PixiJS team has also made a significant effort to optimize the engine for the canvas renderer, which will be available as a first-class option for developers looking to reduce bundle size. Overall, PixiJS v8 represents a major leap forward for us, developers can expect a much faster and more efficient engine that is better suited for building complex, graphics-intensive applications.","s":"PixiJS v8​","u":"/blog/pixi-universe","h":"#pixijs-v8","p":521},{"i":548,"t":"Phase 4 represents a leap into new territory for PixiJS as we look to delve deeper into areas outside of strictly 2D rendering.","s":"Phase 4​","u":"/blog/pixi-universe","h":"#phase-4","p":521},{"i":550,"t":"For years, Goodboy (now Playco) has had an internal 3D engine called Odie that was built on top of PixiJS. We are now planning to open-source it, which is exciting for those of you who want to seamlessly mix 2D and 3D content in your games or applications. With PixiJS 3D, you will no longer need to switch between engines or frameworks to incorporate 3D elements in your project. Although this is a long-term project, the team is making progress and plans to share more information later in the year. This release will greatly expand PixiJS's capabilities and give developers even more flexibility when building their applications.","s":"PixiJS 3D​","u":"/blog/pixi-universe","h":"#pixijs-3d","p":521},{"i":552,"t":"Finally, we are thrilled to announce that we will be working on a new library called PixiJS Game Engine. This game engine aims to provide everything you would expect from a 2D/3D game engine and will offer many features and tools to make game development easier and more efficient. Some of the features of PixiJS Game Engine will include support for physics engines, audio, input handling, asset loading and management, state management, animation and tweening, and more.","s":"PixiJS Game Engine​","u":"/blog/pixi-universe","h":"#pixijs-game-engine","p":521},{"i":554,"t":"We've shared a lot of exciting news about new projects and updates coming to the PixiJS community. There's a lot to look forward to in the upcoming months. We want to extend our sincere thanks to the PixiJS community, its contributors, and Playco for making all of this possible. We're excited to see what you'll create with these new tools and resources, and we look forward to continuing to support and grow the PixiJS ecosystem. Be sure to check out the GitHub links mentioned above and stay tuned for more updates on the PixiJS Universe!","s":"Conclusion","u":"/blog/pixi-universe","h":"#conclusion","p":521},{"i":556,"t":"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time.","s":"PixiJS v8 Launches! 🎉","u":"/blog/pixi-v8-launches","h":"","p":555},{"i":558,"t":"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration. Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture. With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code. Our vision for v8 was clear: Longevity: We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade. Innovation with WebGPU: Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out. Leveraging Modern JavaScript: The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful. Correcting Past Oversights: Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!). Boosting Performance: PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7. We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!","s":"🚀 Revolutionizing Web Graphics: Welcome to PixiJS v8","u":"/blog/pixi-v8-launches","h":"#-revolutionizing-web-graphics-welcome-to-pixijs-v8","p":555},{"i":560,"t":"The new Docs for v8 can be found here Migration Examples Open Games","s":"🔗 Quick links","u":"/blog/pixi-v8-launches","h":"#-quick-links","p":555},{"i":562,"t":"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above. 📈 New Performance Bar​ The performance of v8 is faster for both renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here: CPU = time spent by the CPU rendering a single frame GPU = time spent by the GPU rendering a single frame Bunny Situation V7 CPU V8 CPU CPU Dif V7 GPU V8 GPU GPU dif 100k sprites all moving ~50ms ~15ms 233% ~9ms ~2ms 350% 100k sprites not moving ~21ms ~0.12ms 17417% ~9ms ~0.5ms 1700% 100k sprites (changing scene structure) ~50ms ~24ms 108% ~9ms ~2ms 350% These benchmark numbers are based on the Bunnymark test that you can try yourself. v7 Bunnymark v8 Bunnymark - WebGPU v8 Bunnymark - WebGL Repo 🖥️ WebGPU Renderer​ We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements. 📦 New Package Structure​ No more \"lerna.\" PixiJS is now just one package with one import root: import {stuff} from ‘pixi.js’. This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported. Old: import { Sprite } from \"@pixi/sprite\"; import { Graphic } from \"@pixi/graphics\"; New: import { Sprite, Graphic } from \"pixi.js\";","s":"🎁 Whats New?","u":"/blog/pixi-v8-launches","h":"#-whats-new","p":555},{"i":564,"t":"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere! import { Application, autoDetectRenderer } from \"pixi.js\"; const app = new Application(); (async () => { await app.init({ // application options }); // or const renderer = await autoDetectRenderer({}); // WebGL or WebGPU // do pixi things })(); 🌟 Scene Upgrades​ The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance. const container = new Container({ isRenderGroup:true // this containers transform is now handled on the GPU! }) Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as: // will make all the children tinted red container.tint = 'red' // will make all the children have the add blend mode container.blendMode = 'add' Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer. const texture = RenderTexture.create({ width:100, height:100, antialias:true // easy as that }) We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics: ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend. It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns. import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!) myContainer.blendMode = 'color-burn` // easy! 🎨 Graphics Upgrades​ The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows: graphics .rect(50, 50, 100, 100) .fill('blue'); A GraphicsContext has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility. Support for SVG drawing has been added. For example: graphics.svg('M 100 350 q 150 -300 300 0'); Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs. The new GraphicsPath class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the buildGeometryFromPath function, opening up new possibilities for intricate and detailed graphic designs. const path = new GraphicsPath() .rect(-50, -50, 100, 100) // create geometry from the path: const geometry = buildGeometryFromPath({ path, }); const mesh = new Mesh({ geometry, texture: Texture.WHITE, }); For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the migration guide, or why not jump in and play with some examples. 📝 Text Upgrades​ Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard. BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs. const myText = new BitmapText({ text: 'hello im a bitmap font!', // font will be dynamically created style:{ fontFamily: 'Outfit', fontSize: 12, fill: 'red', } }) Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text. const myText = new Text({ text: 'hello im some fancy text', // font will be dynamically created! style:{ fontFamily: 'Outfit', fontSize: 12, fill: { texture, color:'red'} // same as graphics api fills stroke: { width:3, color:'blue' } // same as graphics api strokes } })","s":"✨ We promise the Renderer will work","u":"/blog/pixi-v8-launches","h":"#-we-promise-the-renderer-will-work","p":555},{"i":566,"t":"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on GitHub and Discord to any issues that arise, valuing your input to make PixiJS even better. A heartfelt thanks to our early adopters (everyone in here) for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you! A final big shout-out to PlayCo for their support in making this release a reality! Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help.","s":"🤝 What now? Get involved!","u":"/blog/pixi-v8-launches","h":"#-what-now-get-involved","p":555},{"i":568,"t":"To stay in the loop, we invite you to follow Doormat23 and PixiJS on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on Discord for direct engagement and real-time chit-chats.","s":"📲 Keep in touch","u":"/blog/pixi-v8-launches","h":"#-keep-in-touch","p":555},{"i":570,"t":"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.","s":"Branding","u":"/7.x/branding","h":"","p":569},{"i":572,"t":"This is the banner that is displayed at the top of our README.","s":"Banner","u":"/7.x/branding","h":"#banner","p":569},{"i":574,"t":"We recommend using the Logo in places where the audience may not be familiar with PixiJS.","s":"Logo","u":"/7.x/branding","h":"#logo","p":569},{"i":576,"t":"Download: SVG PNG","s":"Logo (Dark)","u":"/7.x/branding","h":"#logo-dark","p":569},{"i":578,"t":"Download: SVG PNG","s":"Logo (Dark, Transparent)","u":"/7.x/branding","h":"#logo-dark-transparent","p":569},{"i":580,"t":"Download: SVG PNG","s":"Logo (Pink)","u":"/7.x/branding","h":"#logo-pink","p":569},{"i":582,"t":"Download: SVG PNG","s":"Logo (Pink, Transparent)","u":"/7.x/branding","h":"#logo-pink-transparent","p":569},{"i":584,"t":"We recommend using the Mark in places where the audience is someone familiar with the ecosystem, such as PixiJS Discord users, plugin authors, social media followers.","s":"Mark","u":"/7.x/branding","h":"#mark","p":569},{"i":586,"t":"512px x 512px Download: SVG PNG","s":"Mark (Pink, Large)","u":"/7.x/branding","h":"#mark-pink-large","p":569},{"i":588,"t":"Download: SVG PNG","s":"Mark (Pink)","u":"/7.x/branding","h":"#mark-pink","p":569},{"i":590,"t":"Download: SVG PNG","s":"Mark (Light)","u":"/7.x/branding","h":"#mark-light","p":569},{"i":592,"t":"We're thrilled to offer an exclusive preview of the future of 2D web graphics with the Beta release of PixiJS v8. Although not yet finalized, this Beta iteration is packed with killer performance improvements and features we're eager for you to start playing with! Over the course of a decade—yes, you read that right, ten years!—we've implemented significant changes to the PixiJS engine. But the advancements in this new release are among the most monumental we've ever made! Two driving factors catalysed our approach to re-engineering the codebase and rendering pipeline in v8:","s":"PixiJS v8 Beta! 🎉","u":"/blog/pixi-v8-beta","h":"","p":591},{"i":594,"t":"The newcomer WebGPU offers a substantial performance improvement over its predecessor, WebGL. It propels web computations and graphics into a new era, providing a more efficient and robust API. Soon, it will be the go-to method for rendering most GPU-powered content on the web. This shift is reminiscent of PixiJS's initial launch. At that time, WebGL was new and only available in a handful of desktop browsers, while Canvas was ubiquitous. PixiJS's standout feature was its ability to first attempt rendering with WebGL and then fall back to Canvas as a Plan B. This approach allowed PixiJS content to immediately benefit as WebGL gained traction. Fast forward to today, and WebGL is now available on 95% of browsers. History is repeating itself with WebGPU, currently supported in only a few desktop browsers and roughly 27% of the market. However, it's only a matter of time before it becomes universally supported. PixiJS aims to execute the same fallback strategy, allowing you to always leverage the best technology available without needing to rewrite your code. This is precisely what version 8 achieves and will future proof everything we make for another ten years :D","s":"1. 😍 Embracing WebGPU","u":"/blog/pixi-v8-beta","h":"#1--embracing-webgpu","p":591},{"i":596,"t":"PixiJS has always been synonymous with speed and high-performance graphics. With v8, we've revisited our architecture to optimize both static and dynamic rendering. While v7 is fast, it operates as a somewhat ‘naïve’ renderer. v7 approach:​ Traverse the scene graph and make sure all the transforms are correct Traverse the scene graph a second time and do the following Build batches to render Upload the data to the GPU Draw the batch to the screen. v8 approach​ Update the transform of only things that changed Traverse the scene graph and construct a set of instructions. Upload all scene data to GPU in one go. Execute rendering based on the instructions. There are three key changes to this loop that give us a performance bump. First, we update only the elements that have changed. If nothing has moved, no code is executed, optimizing computational overhead. Second, if the scene graph remains unchanged in subsequent frames, we reuse the existing rendering instructions. This avoids the overhead of reconstructing these instructions for each frame. Third, if no elements in the scene change position, the data upload step (Step 3) is entirely skipped, thereby saving bandwidth and further reducing computational work. The net effect of these improvements? A decent performance leap across varying use-cases: CPU = time spent by the cpu rendering a single frame GPU = time spend by the gpu rendering a single frame Bunny Situation V7 CPU V8 CPU CPU Dif V7 GPU V8 GPU GPU dif 100k sprites all moving ~50ms ~15ms 233% ~9ms ~2ms 350% 100k sprites not moving ~21ms ~0.12ms 17417% ~9ms ~0.5ms 1700% 100k sprites (changing scene structure) ~50ms ~24ms 108% ~9ms ~2ms 350% These benchmark numbers are based on this Bunnymark test that you can try yourself! v7 Bunnymark v8 Bunnymark - WebGPU v8 Bunnymark - WebGL Repo Please have a play, you can fiddle with the parameters in the url to change the number of bunnies. Curious to see what numbers all of you get! Best of all, these improvements apply to WebGPU and the WebGL renderer. As with all of PixiJs’s party tricks, this all happens automatically :D","s":"2. 🚀 Turbocharging Performance","u":"/blog/pixi-v8-beta","h":"#2--turbocharging-performance","p":591},{"i":598,"t":"While the two key drivers behind this overhaul were performance and usability, we didn't stop there. We've seized this opportunity to enhance the API and introduce a plethora of new features to the engine—far too many to encapsulate in a single post! Stay tuned for upcoming blog posts where we'll delve deeper into these additional improvements and API refinements, empowering you to create even more remarkable projects. For a comprehensive overview of what's new, don't miss the release notes. As a crucial note, PixiJS v8 retains much of the familiar API despite undergoing significant internal updates. Our changes are geared toward making PixiJS more robust and user-friendly. When you encounter modifications, rest assured that the v7 methodology will continue to work—you'll simply see a deprecation warning, guiding you towards optimal practices.","s":"But Wait, There's More!","u":"/blog/pixi-v8-beta","h":"#but-wait-theres-more","p":591},{"i":600,"t":"As we progress toward the release candidate, now is the perfect time for you to dive in and explore v8. Your feedback at this stage is invaluable for fine-tuning our engine. We invite you to share your thoughts—the good, the bad, and the ugly—report bugs, and even contribute code. Together, we can elevate PixiJS to unprecedented heights. 👇 Don't wait—dive right in! Explore the PixiJS v8 Codebase on GitHub","s":"Over to you!","u":"/blog/pixi-v8-beta","h":"#over-to-you","p":591},{"i":602,"t":"via npm you can install the beta version like so: npm install pixi.js@prerelease-v8 then you can create the most appropriate renderer using the new autoDetectRenderer function: import { autoDetectRenderer } from \"pixi.js\"; async function init() { const renderer = await autoDetectRenderer({ // any settings }); // will return a WebGL or WebGPU renderer } Start experimenting with PixiJS v8 Beta today and join us in shaping the future of 2D web graphics! 🎉","s":"Steps to install:","u":"/blog/pixi-v8-beta","h":"#steps-to-install","p":591},{"i":604,"t":"\"To stay in the loop, we invite you to follow Doormat23 and PixiJS on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on Discord for direct engagement and real-time conversations.","s":"Keep in touch!","u":"/blog/pixi-v8-beta","h":"#keep-in-touch","p":591},{"i":607,"t":"Welcome to the PixiJS Examples page! Here you can find a variety of demos and code snippets to help you get started with PixiJS. Check out some of our featured examples below: Basic Container Blend Modes Tiling Sprite Animated Sprite Text Graphics","s":"Examples","u":"/7.x/examples","h":"","p":606},{"i":680,"t":"Everything! Pixi.js is a rendering library that will allow you to create rich, interactive graphic experiences, cross-platform applications, and games without having to dive into the WebGL API or grapple with the intricacies of browser and device compatibility. Killer performance with a clean API, means not only will your content be better - but also faster to build!","s":"What is PixiJS for?","u":"/7.x/faq","h":"#what-is-pixijs-for","p":678},{"i":682,"t":"PixiJS is and always will be free and Open Source. That said, financial contributions are what make it possible to push PixiJS further, faster. Contributions allow us to commission the PixiJS developer community to accelerate feature development and create more in-depth documentation. Support Us by making a contribution via Open Collective. Go on! It will be a massive help AND make you feel good about yourself, win win ;)","s":"Is PixiJS free?","u":"/7.x/faq","h":"#is-pixijs-free","p":678},{"i":684,"t":"Visit our GitHub page to download the very latest version of PixiJS. This is the most up-to-date resource for PixiJS and should always be your first port of call to make sure you are using the latest version. Just click the 'Download' link in the navigation.","s":"Where do I get it?","u":"/7.x/faq","h":"#where-do-i-get-it","p":678},{"i":686,"t":"Right here! Take a look through the Resources section for a wealth of information including documentation, forums, tutorials and the Goodboy blog.","s":"How do I get started?","u":"/7.x/faq","h":"#how-do-i-get-started","p":678},{"i":688,"t":"Because you care about speed. PixiJS' #1 mantra has always been speed. We really do feel the need! We do everything we can to make PixiJS as streamlined, efficient and fast as possible, whilst balancing it with offering as many crucial and valuable features as we can.","s":"Why should I use PixiJS?","u":"/7.x/faq","h":"#why-should-i-use-pixijs","p":678},{"i":690,"t":"No. PixiJS is what we've come to think of as a \"creation engine\". Whilst it is extremely good for making games, the core essence of PixiJS is simply moving things around on screens as quickly and efficiently as possible. It does of course happen that it is absolutely brilliant for making games though!","s":"Is PixiJS a game engine?","u":"/7.x/faq","h":"#is-pixijs-a-game-engine","p":678},{"i":692,"t":"Outside of the highly active PixiJS community, it is primarily maintained by Mat Groves, Technical Partner of our creative agency Goodboy Digital. One of the huge advantages of creating PixiJS within the framework of a working agency is that it means its features are always driven by genuine industry demands and critically are always trialled \"in anger\" in our cutting-edge games, sites and apps.","s":"Who makes PixiJS?","u":"/7.x/faq","h":"#who-makes-pixijs","p":678},{"i":694,"t":"Two things - lets us know via the PixiJS GitHub community and even better yet, if you know how, post a fix! Our Community is stronger in numbers so we're always keen to welcome new contributors into the team to help us shape what PixiJS becomes next.","s":"I found a bug. What should I do?","u":"/7.x/faq","h":"#i-found-a-bug-what-should-i-do","p":678},{"i":696,"t":"PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.","s":"Welcome","u":"/7.x/guides","h":"","p":695},{"i":698,"t":"If you're new to PixiJS, we suggest you start with the Basics and read through them in order (a good place to start is Getting Started). While PixiJS has a mature API and solid documentation, the guides go over many common issues and questions that developers new to the system encounter.","s":"About The Guides","u":"/7.x/guides","h":"#about-the-guides","p":695},{"i":700,"t":"As you explore the guides, you may find these resources valuable: PixiJS API documentation PixiJS Github repo","s":"Other Resources","u":"/7.x/guides","h":"#other-resources","p":695},{"i":702,"t":"OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.","s":"Architecture Overview","u":"/7.x/guides/basics/architecture-overview","h":"","p":701},{"i":704,"t":"Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on GitHub. Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called TypeScript, which enables type-checking in JavaScript via a pre-compile step.","s":"The Code","u":"/7.x/guides/basics/architecture-overview","h":"#the-code","p":701},{"i":706,"t":"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, 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.","s":"The Components","u":"/7.x/guides/basics/architecture-overview","h":"#the-components","p":701},{"i":708,"t":"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 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.","s":"Major Components","u":"/7.x/guides/basics/architecture-overview","h":"#major-components","p":701},{"i":710,"t":"Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop. The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering. Let's walk through what happens each frame of the render loop. There are three main steps.","s":"Render Loop","u":"/7.x/guides/basics/render-loop","h":"","p":709},{"i":712,"t":"The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering.","s":"Running Ticker Callbacks","u":"/7.x/guides/basics/render-loop","h":"#running-ticker-callbacks","p":709},{"i":714,"t":"We'll talk a lot more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing.","s":"Updating the Scene Graph","u":"/7.x/guides/basics/render-loop","h":"#updating-the-scene-graph","p":709},{"i":716,"t":"Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (app.stage), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization.","s":"Rendering the Scene Graph","u":"/7.x/guides/basics/render-loop","h":"#rendering-the-scene-graph","p":709},{"i":718,"t":"A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue. 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.","s":"Frame Rates","u":"/7.x/guides/basics/render-loop","h":"#frame-rates","p":709},{"i":720,"t":"What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach.","s":"Custom Render Loops","u":"/7.x/guides/basics/render-loop","h":"#custom-render-loops","p":709},{"i":722,"t":"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications. Here's what else you get with PixiJS:","s":"What PixiJS Is","u":"/7.x/guides/basics/what-pixijs-is","h":"","p":721},{"i":724,"t":"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.","s":"PixiJS Is ... Fast","u":"/7.x/guides/basics/what-pixijs-is","h":"#pixijs-is--fast","p":721},{"i":726,"t":"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. Draw polygons, lines, circles and other primitives with Graphics. Text 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.","s":"... More Than Just Sprites","u":"/7.x/guides/basics/what-pixijs-is","h":"#-more-than-just-sprites","p":721},{"i":728,"t":"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 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.","s":"... WebGL Native","u":"/7.x/guides/basics/what-pixijs-is","h":"#-webgl-native","p":721},{"i":730,"t":"Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and hosted on GitHub for issue tracking and ease of access.","s":"... Open Source","u":"/7.x/guides/basics/what-pixijs-is","h":"#-open-source","p":721},{"i":732,"t":"Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs.","s":"... Extensible","u":"/7.x/guides/basics/what-pixijs-is","h":"#-extensible","p":721},{"i":734,"t":"Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock.","s":"... Easy to Deploy","u":"/7.x/guides/basics/what-pixijs-is","h":"#-easy-to-deploy","p":721},{"i":736,"t":"While PixiJS can do many things, there are things it can't do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you're about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.","s":"What PixiJS Is Not","u":"/7.x/guides/basics/what-pixijs-is-not","h":"","p":735},{"i":738,"t":"PixiJS is a rendering engine, and it supports additional features such as interaction management that are commonly needed when using a render engine. But it is not a framework like Unity or Phaser. Frameworks are designed to do all the things you'd need to do when building a game - user settings management, music playback, object scripting, art pipeline management... the list goes on. PixiJS is designed to do one thing really well - render graphical content. This lets us focus on keeping up with new technology, and makes downloading PixiJS blazingly fast.","s":"PixiJS Is Not ... A Framework","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#pixijs-is-not--a-framework","p":735},{"i":740,"t":"PixiJS is built for 2D. Platformers, adventure games, interactive ads, custom data visualization... all good. But if you want to render 3D models, you might want to check out babylon.js or three.js.","s":"... A 3D Renderer","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-3d-renderer","p":735},{"i":742,"t":"If you're looking to build mobile games, you can do it with PixiJS, but you'll need to use a deployment system like Apache Cordova if you want access to native bindings. We don't provide access to the camera, location services, notifications, etc.","s":"... A Mobile App","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-mobile-app","p":735},{"i":744,"t":"Building a truly generic UI system is a huge challenge, as anyone who has worked with Unity's UI tools can attest. We've chosen to avoid the complexity to stay true to our core focus on speed. While you can certainly build your own UI using PixiJS's scene graph and interaction manager, we don't ship with a UI library out of the box.","s":"... A UI Library","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-ui-library","p":735},{"i":746,"t":"There are many techniques and technologies that you can use to store settings, scores, and other data. Cookies, Web Storage, server-based storage... there are many solutions, each with advantages and disadvantages. You can use any of them with PixiJS, but we don't provide tools to do so.","s":"... A Data Store","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-data-store","p":735},{"i":748,"t":"At least, not out of the box. Again, web audio technology is a constantly evolving challenge, with constantly changing rules and requirements across many browsers. There are a number of dedicated web audio libraries (such as Howler.js that can be used with PixiJS to play sound effects and music. Alternatively, the PixiJS Sound plugin is designed to work well with PixiJS.","s":"... An Audio Library","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-an-audio-library","p":735},{"i":750,"t":"There are a number of tools that are useful for building 2D art and games that you might expect to be a part of PixiJS, but we're a rendering engine, not a development environment. Packing sprite sheets, processing images, building mipmaps or Retina-ready sprites - there are great standalone tools for this type of tooling. Where appropriate throughout the guides, we'll point you to tools that may be useful.","s":"... A Development Environment","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#-a-development-environment","p":735},{"i":752,"t":"Only you know! If you're looking for a tightly focused, fast and efficient rendering engine for your next web-based project, PixiJS is likely a great fit. If you need a full game development framework, with native bindings and a rich UI library, you may want to explore other options. Or you may not. It can be faster and easier to build just the subset of a full framework that your project needs than it can be to digest a monolithic API with bells and whistles you don't need. There are hundreds of complex, rich games and visual projects that use PixiJS for rendering, with plugins or custom code to add the UI and sound effects. There are benefits to both approaches. Regardless, we hope you have a better feel for what PixiJS can (and cannot!) offer your project.","s":"So Is PixiJS Right For Me?","u":"/7.x/guides/basics/what-pixijs-is-not","h":"#so-is-pixijs-right-for-me","p":735},{"i":754,"t":"In this section we're going to build the simplest possible PixiJS application. In doing so, we'll walk through the basics of how to build and serve the code.","s":"Getting Started","u":"/7.x/guides/basics/getting-started","h":"","p":753},{"i":756,"t":"A quick note before we start: this guide is aimed at beginning PixiJS developers who have minimal experience developing JavaScript-based applications. If you are a coding veteran, you may find that the level of detail here is not helpful. If that's the case, you may want to skim this guide, then jump into how to work with PixiJS and packers like webpack and npm.","s":"Advanced Users","u":"/7.x/guides/basics/getting-started","h":"#advanced-users","p":753},{"i":758,"t":"One final note. The JavaScript universe is currently in transition from old-school JavaScript (ES5) to the newer ES6 flavor: // ES5 var x = 5; setTimeout(function() { alert(x); }, 1000); // ES6 const x = 5; setTimeout(() => alert(x), 1000); ES6 brings a number of major advantages in terms of clearer syntax, better variable scoping, native class support, etc. By now, all major browsers support it. Given this, our examples in these guides will use ES6. This doesn't mean you can't use PixiJS with ES5 programs! Just mentally substitute \"var\" for \"let/const\", expand the shorter function-passing syntax, and everything will run just fine.","s":"A Note About JavaScript","u":"/7.x/guides/basics/getting-started","h":"#a-note-about-javascript","p":753},{"i":760,"t":"OK! With those notes out of the way, let's get started. There are only a few steps required to write a PixiJS application: Create an HTML file Serve the file with a web server Load the PixiJS library Create an Application Add the generated view to the DOM Add an image to the stage Write an update loop Let's walk through them together.","s":"Components of a PixiJS Application","u":"/7.x/guides/basics/getting-started","h":"#components-of-a-pixijs-application","p":753},{"i":762,"t":"PixiJS is a JavaScript library that runs in a web page. So the first thing we're going to need is some HTML in a file. In a real PixiJS application, you might want to embed your display within a complex existing page, or you might want your display area to fill the whole page. For this demo, we'll build an empty page to start:

Hello PixiJS

Create a new folder named pixi-test, then copy and paste this HTML into a new file in the pixi-test folder named index.html.","s":"The HTML File","u":"/7.x/guides/basics/getting-started","h":"#the-html-file","p":753},{"i":764,"t":"You will need to run a web server to develop locally with PixiJS. Web browsers prevent loading local files (such as images and audio files) on locally loaded web pages. If you just double-click your new HTML file, you'll get an error when you try to add a sprite to the PixiJS stage. Running a web server sounds complex and difficult, but it turns out there are a number of simple web servers that will serve this purpose. For this guide, we're going to be working with Mongoose, but you could just as easily use XAMPP or the http-server Node.js package to serve your files. To start serving your page with Mongoose, go to the Mongoose download page and download the free server for your operating system. Mongoose defaults to serving the files in the folder it's run in, so copy the downloaded executable into the folder you created in the prior step (pixi-test). Double-click the executable, tell your operating system that you trust the file to run, and you'll have a running web server, serving your new folder. Test that everything is working by opening your browser of choice and entering http://127.0.0.1:8080 in the location bar. (Mongoose by default serves files on port 8080.) You should see \"Hello PixiJS\" and nothing else. If you get an error at this step, it means you didn't name your file index.html or you mis-configured your web server.","s":"Serving the File","u":"/7.x/guides/basics/getting-started","h":"#serving-the-file","p":753},{"i":766,"t":"OK, so we have a web page, and we're serving it. But it's empty. The next step is to actually load the PixiJS library. If we were building a real application, we'd want to download a target version of PixiJS from the Pixi Github repo so that our version wouldn't change on us. But for this sample application, we'll just use the CDN version of PixiJS. Add this line to the section of your index.html file: This will include a non-minified version of the latest version of PixiJS when your page loads, ready to be used. We use the non-minified version because we're in development. In production, you'd want to use pixi.min.js instead, which is compressed for faster download and excludes assertions and deprecation warnings that can help when building your project, but take longer to download and run.","s":"Loading PixiJS","u":"/7.x/guides/basics/getting-started","h":"#loading-pixijs","p":753},{"i":768,"t":"Loading the library doesn't do much good if we don't use it, so the next step is to start up PixiJS. Start by replacing the line

Hello PixiJS

with a script tag like so: What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. Application 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.","s":"Creating an Application","u":"/7.x/guides/basics/getting-started","h":"#creating-an-application","p":753},{"i":770,"t":"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: document.body.appendChild(app.view); This takes the view created by the application (the Canvas element) and adds it to the body of your page.","s":"Adding the View to the DOM","u":"/7.x/guides/basics/getting-started","h":"#adding-the-view-to-the-dom","p":753},{"i":772,"t":"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. 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. A Sprite is a type of DisplayObject 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: // Magically load the PNG asynchronously let sprite = PIXI.Sprite.from('sample.png'); Download the sample PNG here, and save it into your pixi-test directory next to your index.html.","s":"Creating a Sprite","u":"/7.x/guides/basics/getting-started","h":"#creating-a-sprite","p":753},{"i":774,"t":"Finally, we need to add our new sprite to the stage. The stage is simply a Container 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. app.stage.addChild(sprite);","s":"Adding the Sprite to the Stage","u":"/7.x/guides/basics/getting-started","h":"#adding-the-sprite-to-the-stage","p":753},{"i":776,"t":"While you can use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ticker. A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block: // Add a variable to count up the seconds our demo has been running 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) => { // Add the time to our total elapsed time elapsed += delta; // 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; }); All you need to do is to call app.ticker.add(...), pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations.","s":"Writing an Update Loop","u":"/7.x/guides/basics/getting-started","h":"#writing-an-update-loop","p":753},{"i":778,"t":"That's it! The simplest PixiJS project! Here's the whole thing in one place. Check your file and make sure it matches if you're getting errors. Once you have things working, the next thing to do is to read through the rest of the Basics guides to dig into how all this works in much greater depth.","s":"Putting It All Together","u":"/7.x/guides/basics/getting-started","h":"#putting-it-all-together","p":753},{"i":780,"t":"The Container 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.","s":"Containers","u":"/7.x/guides/components/containers","h":"","p":779},{"i":782,"t":"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. 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. So that's the primary use for Containers - as groups of renderable objects in a hierarchy. Check out the container example code.","s":"Containers as Groups","u":"/7.x/guides/components/containers","h":"#containers-as-groups","p":779},{"i":784,"t":"Another common use for Container objects is as hosts for masked content. \"Masking\" is a technique where parts of your scene graph are only visible within a given area. 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. // Create the application helper and add its render target to the page let app = new PIXI.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); app.stage.addChild(frame); // Create a graphics object to define our mask let mask = new PIXI.Graphics(); // Add the rectangular area to show mask.beginFill(0xffffff); mask.drawRect(0,0,200,200); mask.endFill(); // Add container that will hold our masked content let maskContainer = new PIXI.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 maskContainer.addChild(mask); // Offset by the window's frame width maskContainer.position.set(4,4); // And add the container to the window! 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' + 'You can put anything in the container and it will be masked!', { fontSize: 24, fill: 0x1010ff, wordWrap: true, wordWrapWidth: 180 } ); text.x = 10; maskContainer.addChild(text); // Add a ticker callback to scroll the text up and down let elapsed = 0.0; 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; }); There are two types of masks supported by PixiJS: Use a Graphics object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing Sprite: Use the alpha channel from a Sprite as your mask, providing anti-aliased edging - not supported on the Canvas renderer","s":"Masking","u":"/7.x/guides/components/containers","h":"#masking","p":779},{"i":786,"t":"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. Below are list of filters available by default in PixiJS. There is, however, a community repository with many more 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). Important: Filters should be use somewhat sparingly. They can slow performance and increase memory if used too often in a scene.","s":"Filtering","u":"/7.x/guides/components/containers","h":"#filtering","p":779},{"i":789,"t":"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.","s":"The Assets package","u":"/7.x/guides/components/assets","h":"#the-assets-package","p":787},{"i":791,"t":"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 you should look into polyfilling them.","s":"Getting started","u":"/7.x/guides/components/assets","h":"#getting-started","p":787},{"i":793,"t":"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. In this example, we will load a texture and then turn it into a sprite. 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: promise1 = PIXI.Assets.load('bunny.png') promise2 = PIXI.Assets.load('bunny.png') //promise1 === promise2 Out of the box, the following assets types can be loaded without the need for external plugins: Textures (avif, webp, png, jpg, gif) Sprite sheets (json) Bitmap fonts (xml, fnt, txt) Web fonts (ttf, woff, woff2) Json files (json) Text files (txt) More types can be added fairly easily by creating additional loader parsers.","s":"Making our first Assets Promise","u":"/7.x/guides/components/assets","h":"#making-our-first-assets-promise","p":787},{"i":795,"t":"When an asset is downloaded, it is cached as a promise inside the Assets instance and if you try to download it again you will get a reference to the already resolved promise. However promise handlers .then(...)/.catch(...)/.finally(...) are always asynchronous, this means that even if a promise was already resolved the code below the .then(...)/.catch(...)/.finally(...) will execute before the code inside them. See this example: console.log(1); alreadyResolvedPromise.then(() => console.log(2)); console.log(3); // Console output: // 1 // 3 // 2 To learn more about why this happens you will need to learn about Microtasks, however, using async functions should mitigate this problem.","s":"Warning about solved promises","u":"/7.x/guides/components/assets","h":"#warning-about-solved-promises","p":787},{"i":797,"t":"There is a way to work with promises that is more intuitive and easier to read: async/await. To use it we first need to create a function/method and mark it as async. async function test() { // ... } This function now wraps the return value in a promise and allows us to use the await keyword before a promise to halt the execution of the code until it is resolved and gives us the value. See this example: The texture variable now is not a promise but the resolved texture that resulted after waiting for this promise to resolve. const texture = await PIXI.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.","s":"Using Async/Await","u":"/7.x/guides/components/assets","h":"#using-asyncawait","p":787},{"i":799,"t":"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. See the following example: 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(...). PIXI.Assets.addBundle('animals', { bunny: 'bunny.png', chicken: 'chicken.png', thumper: 'thumper.png', }); const assets = await PIXI.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). 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. { \"bundles\":[ { \"name\":\"load-screen\", \"assets\":[ { \"name\":\"background\", \"srcs\":\"sunset.png\" }, { \"name\":\"bar\", \"srcs\":\"load-bar.{png,webp}\" } ] }, { \"name\":\"game-screen\", \"assets\":[ { \"name\":\"character\", \"srcs\":\"robot.png\" }, { \"name\":\"enemy\", \"srcs\":\"bad-guy.png\" } ] } ] } PIXI.Assets.init({manifest: \"path/manifest.json\"}); Beware that you can only call init once. Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!","s":"Loading multiple assets","u":"/7.x/guides/components/assets","h":"#loading-multiple-assets","p":787},{"i":801,"t":"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. 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. 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. 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. The best way to do this is using bundles, see the following example: 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!","s":"Background loading","u":"/7.x/guides/components/assets","h":"#background-loading","p":787},{"i":803,"t":"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.","s":"Scene Graph","u":"/7.x/guides/basics/scene-graph","h":"","p":802},{"i":805,"t":"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. (A helpful tool for exploring your project is the Pixi.js devtools plugin for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)","s":"The Scene Graph Is a Tree","u":"/7.x/guides/basics/scene-graph","h":"#the-scene-graph-is-a-tree","p":802},{"i":807,"t":"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. 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: // 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); // 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; app.stage.addChild(container); // 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'); sprite.anchor.set(0.5); parent.addChild(sprite); sprites.push(sprite); parent = sprite; } // Set all sprite's properties to the same value, animated over time let elapsed = 0.0; app.ticker.add((delta) => { elapsed += delta / 60; const amount = Math.sin(elapsed); const scale = 1.0 + 0.25 * amount; const alpha = 0.75 + 0.25 * amount; const angle = 40 * amount; const x = 75 * amount; for (let i = 0; i < sprites.length; i++) { const sprite = sprites[i]; sprite.scale.set(scale); sprite.alpha = alpha; sprite.angle = angle; sprite.x = x; } }); The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's worldTransform property. Similarly, the cumulative alpha value is stored in the worldAlpha property.","s":"Parents and Children","u":"/7.x/guides/basics/scene-graph","h":"#parents-and-children","p":802},{"i":809,"t":"So we have a tree of things to draw. Who gets drawn first? 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. Check out this example, with two parent objects A & D, and two children B & C under A: // 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); // 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}; 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); bg.width = 100; bg.height = 100; bg.tint = color; const text = new PIXI.Text(letter, {fill: \"#ffffff\"}); text.anchor.set(0.5); text.position = {x: 50, y: 50}; const container = new PIXI.Container(); container.position = pos; container.visible = false; container.addChild(bg, text); parent.addChild(container); letters.push(container); return container; } // Define 4 letters let a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100}); let b = addLetter('B', a, 0x00ff00, {x: 20, y: 20}); let c = addLetter('C', a, 0x0000ff, {x: 20, y: 40}); let d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100}); // Display them over time, in order let elapsed = 0.0; app.ticker.add((delta) => { elapsed += delta / 60.0; if (elapsed >= letters.length) { elapsed = 0.0; } for (let i = 0; i < letters.length; i ++) { letters[i].visible = elapsed >= i; } }); 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.","s":"Render Order","u":"/7.x/guides/basics/scene-graph","h":"#render-order","p":802},{"i":811,"t":"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. 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.","s":"Culling","u":"/7.x/guides/basics/scene-graph","h":"#culling","p":802},{"i":813,"t":"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. 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. 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 PIXI.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.","s":"Local vs Global Coordinates","u":"/7.x/guides/basics/scene-graph","h":"#local-vs-global-coordinates","p":802},{"i":815,"t":"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.","s":"Global vs Screen Coordinates","u":"/7.x/guides/basics/scene-graph","h":"#global-vs-screen-coordinates","p":802},{"i":817,"t":"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.","s":"Interaction","u":"/7.x/guides/components/interaction","h":"","p":816},{"i":819,"t":"The new event-based system that replaced InteractionManager from v6 has expanded the definition of what a DisplayObject means to be interactive. With this we have introduced eventMode which allows you to control how an object responds to interaction events. This is similar to the interactive property in v6 but with more options. eventMode Description none Ignores all interaction events, similar to CSS's pointer-events: none, good optimization for non-interactive children passive Does not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. If you want to be compatible with v6, set this as your default eventMode (see options in Renderer, Application, etc) auto Does not emit events and but is hit tested if parent is interactive. Same as interactive = false in v7 static Emit events and is hit tested. Same as interaction = true in v7, useful for objects like buttons that do not move. dynamic Emits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.","s":"Event Modes","u":"/7.x/guides/components/interaction","h":"#event-modes","p":816},{"i":821,"t":"PixiJS supports the following event types: Event Type Description pointercancel Fired when a pointer device button is released outside the display object that initially registered a pointerdown. pointerdown Fired when a pointer device button is pressed on the display object. pointerenter Fired when a pointer device enters the display object. pointerleave Fired when a pointer device leaves the display object. pointermove Fired when a pointer device is moved while over the display object. globalpointermove Fired when a pointer device is moved, regardless of hit-testing the current object. pointerout Fired when a pointer device is moved off the display object. pointerover Fired when a pointer device is moved onto the display object. pointertap Fired when a pointer device is tapped twice on the display object. pointerup Fired when a pointer device button is released over the display object. pointerupoutside Fired when a pointer device button is released outside the display object that initially registered a pointerdown. mousedown Fired when a mouse button is pressed on the display object. mouseenter Fired when the mouse cursor enters the display object. mouseleave Fired when the mouse cursor leaves the display object. mousemove Fired when the mouse cursor is moved while over the display object. globalmousemove Fired when a mouse is moved, regardless of hit-testing the current object. mouseout Fired when the mouse cursor is moved off the display object. mouseover Fired when the mouse cursor is moved onto the display object. mouseup Fired when a mouse button is released over the display object. mouseupoutside Fired when a mouse button is released outside the display object that initially registered a mousedown. click Fired when a mouse button is clicked (pressed and released) over the display object. touchcancel Fired when a touch point is removed outside of the display object that initially registered a touchstart. touchend Fired when a touch point is removed from the display object. touchendoutside Fired when a touch point is removed outside of the display object that initially registered a touchstart. touchmove Fired when a touch point is moved along the display object. globaltouchmove Fired when a touch point is moved, regardless of hit-testing the current object. touchstart Fired when a touch point is placed on the display object. tap Fired when a touch point is tapped twice on the display object. wheel Fired when a mouse wheel is spun over the display object. rightclick Fired when a right mouse button is clicked (pressed and released) over the display object. rightdown Fired when a right mouse button is pressed on the display object. rightup Fired when a right mouse button is released over the display object. rightupoutside Fired when a right mouse button is released outside the display object that initially registered a rightdown.","s":"Event Types","u":"/7.x/guides/components/interaction","h":"#event-types","p":816},{"i":823,"t":"Any DisplayObject-derived object (Sprite, Container, etc.) can become interactive simply by setting its eventMode property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior. Check out the interaction example code. To respond to clicks and taps, bind to the events fired on the object, like so: let sprite = PIXI.Sprite.from('/some/texture.png'); sprite.on('pointerdown', (event) => { alert('clicked!'); }); sprite.eventMode = 'static'; Check out the DisplayObject for the list of interaction events supported.","s":"Enabling Interaction","u":"/7.x/guides/components/interaction","h":"#enabling-interaction","p":816},{"i":825,"t":"You can check if an object is interactive by calling the isInteractive property. This will return true if eventMode is set to static or dynamic. if (sprite.isInteractive()) { // sprite is interactive }","s":"Checking if Object is Interactive","u":"/7.x/guides/components/interaction","h":"#checking-if-object-is-interactive","p":816},{"i":827,"t":"PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for both. What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with either mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events.","s":"Use Pointer Events","u":"/7.x/guides/components/interaction","h":"#use-pointer-events","p":816},{"i":829,"t":"Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named interactiveChildren. If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to false and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set background.interactiveChildren = false for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain. The EventSystem can also be customised to be more performant: const app = new PIXI.Application({ /** * by default we use `auto` for backwards compatibility. * However `passive` is more performant and will be used by default in the future, */ eventMode: 'passive', eventFeatures: { move: true, /** disables the global move events which can be very expensive in large scenes */ globalMove: false, click: true, wheel: true, } });","s":"Optimization","u":"/7.x/guides/components/interaction","h":"#optimization","p":816},{"i":831,"t":"Now that you understand basic sprites, it's time to talk about a better way to create them - the Spritesheet class. A Spritesheet is a media format for more efficiently downloading and rendering Sprites. While somewhat more complex to create and use, they are a key tool in optimizing your project.","s":"Spritesheets","u":"/7.x/guides/components/sprite-sheets","h":"","p":830},{"i":833,"t":"The basic idea of a spritesheet is to pack a series of images together into a single image, track where each source image ends up, and use that combined image as a shared BaseTexture for the resulting Sprites. The first step is to collect the images you want to combine. The sprite packer then collects the images, and creates a new combined image. As this image is being created, the tool building it keeps track of the location of the rectangle where each source image is stored. It then writes out a JSON file with that information. These two files, in combination, can be passed into a SpriteSheet constructor. The SpriteSheet object then parses the JSON, and creates a series of Texture objects, one for each source image, setting the source rectangle for each based on the JSON data. Each texture uses the same shared BaseTexture as its source.","s":"Anatomy of a Spritesheet","u":"/7.x/guides/components/sprite-sheets","h":"#anatomy-of-a-spritesheet","p":830},{"i":835,"t":"SpriteSheets help your project in two ways. First, by speeding up the loading process. While downloading a SpriteSheet's texture requires moving the same (or even slightly more!) number of bytes, they're grouped into a single file. This means that the user's browser can request and download far fewer files for the same number of Sprites. The number of files itself is a key driver of download speed, because each request requires a round-trip to the webserver, and browsers are limited to how many files they can download simultaneously. Converting a project from individual source images to shared sprite sheets can cut your download time in half, at no cost in quality. Second, by improving batch rendering. WebGL rendering speed scales roughly with the number of draw calls made. Batching multiple Sprites, etc. into a single draw call is the main secret to how PixiJS can run so blazingly fast. Maximizing batching is a complex topic, but when multiple Sprites all share a common BaseTexture, it makes it more likely that they can be batched together and rendered in a single call.","s":"Doubly Efficient","u":"/7.x/guides/components/sprite-sheets","h":"#doubly-efficient","p":830},{"i":837,"t":"You can use a 3rd party tool to assemble your sprite sheet files. Here are two that may fit your needs: ShoeBox: ShoeBox is a free, Adobe AIR-based sprite packing utility that is great for small projects or learning how SpriteSheets work. TexturePacker: TexturePacker is a more polished tool that supports advanced features and workflows. A free version is available which has all the necessary features for packing spritesheets for PixiJS. It's a good fit for larger projects and professional game development, or projects that need more complex tile mapping features. Spritesheet data can also be created manually or programmatically, and supplied to a new AnimatedSprite. This may be an easier option if your sprites are already contained in a single image. // Create object to store sprite sheet data const atlasData = { frames: { enemy1: { frame: { x: 0, y:0, w:32, h:32 }, sourceSize: { w: 32, h: 32 }, spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 } }, enemy2: { frame: { x: 32, y:0, w:32, h:32 }, sourceSize: { w: 32, h: 32 }, spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 } }, }, meta: { image: 'images/spritesheet.png', format: 'RGBA8888', size: { w: 128, h: 32 }, scale: 1 }, animations: { enemy: ['enemy1','enemy2'] //array of frames by name } } // Create the SpriteSheet from data and image const spritesheet = new PIXI.Spritesheet( PIXI.BaseTexture.from(atlasData.meta.image), atlasData ); // Generate all the Textures asynchronously await spritesheet.parse(); // spritesheet is ready to use! const anim = new PIXI.AnimatedSprite(spritesheet.animations.enemy); // set the animation speed anim.animationSpeed = 0.1666; // play the animation on a loop anim.play(); // add it to the stage to render app.stage.addChild(anim);","s":"Creating SpriteSheets","u":"/7.x/guides/components/sprite-sheets","h":"#creating-spritesheets","p":830},{"i":839,"t":"Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.","s":"Sprites","u":"/7.x/guides/components/sprites","h":"","p":838},{"i":841,"t":"To create a Sprite, all you need is a Texture (check out the Texture guide). Load a PNG's URL using the PIXI.Loader class, then call PIXI.Sprite.from(url) and you're all set. As a convenience during prototyping, you can pass a non-loaded URL to from() and PixiJS will handle it, but your sprite will \"pop in\" after it loads if you don't pre-load your textures. Check out the sprite example code.","s":"Creating Sprites","u":"/7.x/guides/components/sprites","h":"#creating-sprites","p":838},{"i":843,"t":"In our DisplayObject guide, we learned about the DisplayObject class and the various properties it defines. Since Sprite objects are also display objects, you can move a sprite, rotate it, and update any other display property.","s":"Using Sprites","u":"/7.x/guides/components/sprites","h":"#using-sprites","p":838},{"i":845,"t":"Alpha is a standard display object property. You can use it to fade sprites into the scene by animating each sprite's alpha from 0.0 to 1.0 over a period of time. Tinting allows you multiply the color value of every pixel by a single color. For example, if you had a dungeon game, you might show a character's poison status by setting obj.tint = 0x00FF00, which would give a green tint to the character. Blend modes change how pixel colors are added to the screen when rendering. The three main modes are add, which adds each pixel's RGB channels to whatever is under your sprite (useful for glows and lighting), multiply which works like tint, but on a per-pixel basis, and screen, which overlays the pixels, brightening whatever is underneath them.","s":"Alpha, Tint and Blend Modes","u":"/7.x/guides/components/sprites","h":"#alpha-tint-and-blend-modes","p":838},{"i":847,"t":"One common area of confusion when working with sprites lies in scaling and dimensions. The PIXI.DisplayObject class allows you to set the x and y scale for any object. Sprites, being DisplayObjects, also support scaling. In addition, however, Sprites support explicit width and height attributes that can be used to achieve the same effect, but are in pixels instead of a percentage. This works because a Sprite object owns a Texture, which has an explicit width and height. When you set a Sprite's width, internally PixiJS converts that width into a percentage of the underlying texture's width and updates the object's x-scale. So width and height are really just convenience methods for changing scale, based on pixel dimensions rather than percentages.","s":"Scale vs Width & Height","u":"/7.x/guides/components/sprites","h":"#scale-vs-width--height","p":838},{"i":849,"t":"If you add a sprite to your stage and rotate it, it will by default rotate around the top-left corner of the image. In some cases, this is what you want. In many cases, however, what you want is for the sprite to rotate around the center of the image it contains, or around an arbitrary point. There are two ways to achieve this: pivots and anchors An object's pivot is an offset, expressed in pixels, from the top-left corner of the Sprite. It defaults to (0, 0). If you have a Sprite whose texture is 100px x 50px, and want to set the pivot point to the center of the image, you'd set your pivot to (50, 25) - half the width, and half the height. Note that pivots can be set outside of the image, meaning the pivot may be less than zero or greater than the width/height. This can be useful in setting up complex animation hierarchies, for example. Every DisplayObject has a pivot. An anchor, in contrast, is only available for Sprites. Anchors are specified in percentages, from 0.0 to 1.0, in each dimension. To rotate around the center point of a texture using anchors, you'd set your Sprite's anchor to (0.5, 0.5) - 50% in width and height. While less common, anchors can also be outside the standard 0.0 - 1.0 range. The nice thing about anchors is that they are resolution and dimension agnostic. If you set your Sprite to be anchored in the middle then later change the size of the texture, your object will still rotate correctly. If you had instead set a pivot using pixel-based calculations, changing the texture size would require changing your pivot point. So, generally speaking, you'll want to use anchors when working with Sprites. One final note: unlike CSS, where setting the transform-origin of the image doesn't move it, in PixiJS setting an anchor or pivot will move your object on the screen. In other words, setting an anchor or pivot affects not just the rotation origin, but also the position of the sprite relative to its parent.","s":"Pivot vs Anchor","u":"/7.x/guides/components/sprites","h":"#pivot-vs-anchor","p":838},{"i":851,"t":"DisplayObject 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 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.","s":"Display Objects","u":"/7.x/guides/components/display-object","h":"","p":850},{"i":853,"t":"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","s":"Commonly Used Attributes","u":"/7.x/guides/components/display-object","h":"#commonly-used-attributes","p":850},{"i":855,"t":"Graphics 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. Check out the graphics example code.","s":"Graphics","u":"/7.x/guides/components/graphics","h":"","p":854},{"i":857,"t":"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: // 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); // 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??? 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. 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.","s":"Graphics Is About Building - Not Drawing","u":"/7.x/guides/components/graphics","h":"#graphics-is-about-building---not-drawing","p":854},{"i":859,"t":"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: Line Rect RoundRect Circle Ellipse Arc Bezier and Quadratic Curve In addition, the Graphics Extras package (@pixi/graphics-extras) optionally includes the following complex primitives: Torus Chamfer Rect Fillet Rect Regular Polygon Star Rounded Polygon","s":"Types of Primitives","u":"/7.x/guides/components/graphics","h":"#types-of-primitives","p":854},{"i":861,"t":"Inside every Graphics object is a GraphicsGeometry object. The GraphicsGeometry 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. 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: // Create a master graphics object let template = new PIXI.Graphics(); // Add a circle template.drawCircle(100, 100, 50); // 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); } 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.","s":"The Geometry List","u":"/7.x/guides/components/graphics","h":"#the-geometry-list","p":854},{"i":863,"t":"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. 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.","s":"Graphics For Display","u":"/7.x/guides/components/graphics","h":"#graphics-for-display","p":854},{"i":865,"t":"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. Check out the masking example code.","s":"Graphics as a Mask","u":"/7.x/guides/components/graphics","h":"#graphics-as-a-mask","p":854},{"i":867,"t":"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. 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. 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.","s":"Caveats and Gotchas","u":"/7.x/guides/components/graphics","h":"#caveats-and-gotchas","p":854},{"i":869,"t":"Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object. Let's dig into how this works.","s":"Text","u":"/7.x/guides/components/text","h":"","p":868},{"i":871,"t":"Because of the challenges of working with text in WebGL, PixiJS provides two very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance.","s":"There Are Two Kinds of Text","u":"/7.x/guides/components/text","h":"#there-are-two-kinds-of-text","p":868},{"i":873,"t":"In order to draw text to the screen, you use a Text object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high. So when working with PIXI.Text objects, there are two sets of options - standard display object options like position, rotation, etc that work after the text is rasterized internally, and text style options that are used while rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled. Check out the text example code.","s":"The Text Object","u":"/7.x/guides/components/text","h":"#the-text-object","p":868},{"i":875,"t":"There are a lot of text style options available (see TextStyle), but they break down into 5 main groups: Font: fontFamily to select the webfont to use, fontSize to specify the size of the text to draw, along with options for font weight, style and variant. Appearance: Set the color with fill or add a stroke outline, including options for gradient fills. Drop-Shadows: Set a drop-shadow with dropShadow, with a host of related options to specify offset, blur, opacity, etc. Layout: Enable with wordWrap and wordWrapWidth, and then customize the lineHeight and align or letterSpacing Utilities: Add padding or trim extra space to deal with funky font families if needed. To interactively test out feature of Text Style, check out this tool.","s":"Text Styles","u":"/7.x/guides/components/text","h":"#text-styles","p":868},{"i":877,"t":"In order for PixiJS to build a PIXI.Text object, you'll need to make sure that the font you want to use is loaded by the browser. Unfortunately, at the time of writing, the PIXI.Loader system does not support loading font files, so you'll need to use a 3rd party font loader to ensure that any custom web fonts you want to use are pre-loaded. It's not enough to add an @font-face declaration in your project's CSS because browsers will happily render text using a fallback font while your custom font loads. Any javascript library that can load a web font will work, you just want something that will delay starting your project until the font has been fully loaded by the browser. One such library is FontFaceObserver. Here's a simple example that shows how to use it to ensure the web font \"Short Stack\" is loaded before your app starts. First, we need a font-face declaration in CSS: @font-face { font-family: Short Stack; src: url(short-stack.woff2) format('woff2'), url(short-stack.woff) format('woff'); } Now that the browser knows what our font is and how to find the source files, it's time to use the library to load them: // Create the loader let font = new FontFaceObserver('Short Stack', {}); // Start loading the font font.load().then(() => { // Successful load, start up your PixiJS app as usual let app = new PIXI.Application({ width: 640, height: 360 }); document.body.appendChild(app.view); // ... etc ... }, () => { // Failed load, log the error or display a message to the user alert('Unable to load required font!'); });","s":"Loading and Using Fonts","u":"/7.x/guides/components/text","h":"#loading-and-using-fonts","p":868},{"i":879,"t":"While PixiJS does make working with text easy, there are a few things you need to watch out for. First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a PIXI.BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes. Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp.","s":"Caveats and Gotchas","u":"/7.x/guides/components/text","h":"#caveats-and-gotchas","p":868},{"i":881,"t":"In addition to the standard PIXI.Text approach to adding text to your project, PixiJS also supports bitmap fonts. Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image. The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture. Check out the bitmap text example code.","s":"BitmapText","u":"/7.x/guides/components/text","h":"#bitmaptext","p":868},{"i":883,"t":"3rd party solutions BitmapFont.from auto-generation","s":"BitmapFont","u":"/7.x/guides/components/text","h":"#bitmapfont","p":868},{"i":885,"t":"PIXI.Text Static text Small number of text objects High fidelity text rendering (kerning e.g.) Text layout (line & letter spacing) PIXI.BitmapText Dynamic text Large number of text objects Lower memory","s":"Selecting the Right Approach","u":"/7.x/guides/components/text","h":"#selecting-the-right-approach","p":868},{"i":887,"t":"This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we've try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.","s":"v5 Migration Guide","u":"/7.x/guides/migrations/v5","h":"","p":886},{"i":890,"t":"PixiJS v5 has made WebGL the first-class renderer and made CanvasRenderer to be second-class. Functionally, there's not much that changed from v4, but there are a bunch of subtle internal naming changes which could trip-up some developers upgrading to v5. For instance: WebGLRenderer becomes Renderer renderWebGL becomes render (in DisplayObject, Sprite, Container, etc) _renderWebGL becomes _render (in DisplayObject, Container, etc) If you created a plugin or project that previously used render on a Container (see #5510), this will probably cause your project to not render correctly. Please consider renaming your user-defined render to something else. In most other cases, you'll get a deprecation warning trying to invoke WebGL-related classes or methods, e.g., new PIXI.WebGLRenderer().","s":"Making WebGL First-Class","u":"/7.x/guides/migrations/v5","h":"#making-webgl-first-class","p":886},{"i":892,"t":"Specifying options as a third parameter in Renderer constructor is officially dropped (same with PIXI.Application, PIXI.autoDetectRenderer & PIXI.CanvasRenderer). In v4 we supported two function signatures, but in v5 we dropped width, height, options signature. Please add width and height to options. const renderer = new PIXI.Renderer(800, 600, { transparent: true }); // bad const renderer = new PIXI.Renderer({ width: 800, height: 600, transparent: true }); // good Note: Adding transparent: true in Renderer or Application constructor options might help with strange artifacts on some devices, but it might reduce FPS. It's much better than preserveDrawingBuffer: true. If you need the v4 default behavior of resizing the canvas using css pixels, add autoDensity: true to the options. Not everything went to params. To enable WebGL1 even if WebGL2 is available, use PIXI.settings.PREFER_ENV = PIXI.ENV.WEBGL;","s":"Renderer Parameters","u":"/7.x/guides/migrations/v5","h":"#renderer-parameters","p":886},{"i":894,"t":"PixiJS v5 introduces a new class called PIXI.Mesh. This allows overriding the default shader and the ability to add more attributes to geometry. For example, you can add colors to vertices. The old v4 Mesh class has moved from PIXI.mesh.Mesh to PIXI.SimpleMesh, it extends PIXI.Mesh. PIXI.mesh.Rope, PIXI.mesh.Plane, PIXI.mesh.NineSlicePlane have moved to PIXI.SimpleRope, PIXI.SimplePlane and PIXI.NineSlicePlane respectively. If you used custom shaders or generated meshes in v4, you might be impacted by these changes in v5. PIXI.SimpleMesh fields vertices, uvs, indices are wrapped inside mesh.geometry attribute buffers. For example, this is how access to buffers provided through mesh.uvBuffer property: get uvBuffer() { return this.geometry.buffers[1]; } The indices property shortcut is also missing, but you can access the data inside mesh.geometry.indexBuffer. You can override buffer data, and notify it that data was changed, in this case buffer will be uploaded to GPU lazily. Previously in v4 mesh had several flags that indicated which attributes have to be updated and their names confused people.","s":"Mesh, Plane, Rope","u":"/7.x/guides/migrations/v5","h":"#mesh-plane-rope","p":886},{"i":896,"t":"Drawing holes in Graphics was very limited in v4. This only supported non-Shape drawing, like using lineTo, bezierCurveTo, etc. In v5, we improved the hole API by supporting shapes. Unfortunately, there's no deprecation strategy to support the v4 API. For instance, in v4: const graphic = new PIXI.Graphics() .beginFill(0xff0000) .moveTo(0, 0) .lineTo(100, 0) .lineTo(100, 100) .lineTo(0, 100) .moveTo(10, 10) .lineTo(90, 10) .lineTo(90, 90) .lineTo(10, 90) .addHole(); Live example in v4.x In v5, Graphics has simplified and the API changed from addHole to beginHole and endHole. const graphic = new PIXI.Graphics() .beginFill(0xff0000) .drawRect(0, 0, 100, 100) .beginHole() .drawCircle(50, 50, 30) .endHole(); Live example in dev","s":"Graphics Holes","u":"/7.x/guides/migrations/v5","h":"#graphics-holes","p":886},{"i":898,"t":"In v4 filters had a default padding of 4 and in v5 this has been changed to a default of 0. This can cause some filters to look broken when used. To fix this issue simply add some padding to the filters you create. // Glow filter from https://github.com/pixijs/pixi-filters const filter = new PIXI.filters.GlowFilter(); filter.padding = 4; Some filters, like BlurFilter, automatically calculate the padding so changes may not be necessary.","s":"Filter Padding","u":"/7.x/guides/migrations/v5","h":"#filter-padding","p":886},{"i":900,"t":"We reorganized all uniforms dedicated to coordinate system transforms, and renamed them. If your filter doesn't work anymore, check if you use default vertex shader. In that case, you can use old v4 vertex shader code. All changes are explained in [[Creating Filters|v5-Creating-filters]]","s":"Filter Default Vertex Shader","u":"/7.x/guides/migrations/v5","h":"#filter-default-vertex-shader","p":886},{"i":902,"t":"Previously, you may have ended up with code like this in v4 (specifically if you saw Ivan's comment/JSFiddle): const renderer = PIXI.autoDetectRenderer(); renderer.bindTexture(baseRenderTex, false, 0); const glTex = baseRenderTex._glTextures[renderer.CONTEXT_UID]; glTex.enableMipmap(); // this is what actually generates mipmaps in WebGL glTex.enableLinearScaling(); // this is what tells WebGL to USE those mipmaps In v5, this code is no longer needed.","s":"Enable Mipmapping for RenderTexture","u":"/7.x/guides/migrations/v5","h":"#enable-mipmapping-for-rendertexture","p":886},{"i":904,"t":"One of the newest features in v5 is that we decoupled all the asset-specific functionality from BaseTexture. We created a new system called \"resources\" and each BaseTexture now has a resource that wraps some specific asset type. For instance: VideoResource, SVGResource, ImageResource, CanvasResource. In the future, we hope to be able to add other resource types. If there were asset-specific methods or properties being called before, these will probably be on baseTexture.resource. Also, we removed all of the from* methods from BaseTexture, so you just can call BaseTexture.from and pass in whatever resource. Please see docs for more information about from. const canvas = document.createElement('canvas'); const baseTexture = PIXI.BaseTexture.from(canvas); That API also allows to use pure WebGL and 2d context calls, see the gradient example.","s":"BaseTexture Resources","u":"/7.x/guides/migrations/v5","h":"#basetexture-resources","p":886},{"i":906,"t":"Has been moved to baseTexture.resource.source, moved into resource corresponding to the baseTexture. baseTexture.resource does not exist for RenderTexture, and source does not exist for resources that dont have source.","s":"BaseTexture.source","u":"/7.x/guides/migrations/v5","h":"#basetexturesource","p":886},{"i":908,"t":"If you use transparent interactive graphics trick, make sure that you use specify alpha=0 for all element, not for its parts. How PixiJS deals with shapes that have alpha=0 is considered undefined behaviour. We might change it back, but we have no guarantees about it. graphics.beginFill(0xffffff, 0.0); //bad graphics.alpha = 0; //good","s":"Graphics Interaction","u":"/7.x/guides/migrations/v5","h":"#graphics-interaction","p":886},{"i":911,"t":"Since WebGL and WebGL2 are now first-class, we have removed the canvas-based fallback from the default pixi.js package. If you need CanvasRenderer, you should switch to use pixi.js-legacy instead. import * as PIXI from \"pixi.js\"; // Will NOT return CanvasRenderer because canvas-based // functionality was removed from \"pixi.js\" const renderer = PIXI.autoDetectRenderer(); // return PIXI.Renderer or throws error Instead, use the legacy bundle to have access to the canvas rendering. import * as PIXI from \"pixi.js-legacy\"; const renderer = PIXI.autoDetectRenderer(); // returns PIXI.Renderer or PIXI.CanvasRenderer","s":"Canvas Becomes Legacy","u":"/7.x/guides/migrations/v5","h":"#canvas-becomes-legacy","p":886},{"i":913,"t":"If you're using Rollup, Parcel or another bundler to add PixiJS into your project there are a few subtle changes when moving to v5. Namely, the global PIXI object is no longer created automatically. This was removed from bundling for two purpose: 1) to improve tree-shaking for bundlers, and 2) for security purpose by protecting PIXI. This is no longer a valid way to import: import \"pixi.js\"; const renderer = PIXI.autoDetectRenderer(); // INVALID! No more global.PIXI! Instead, you should import as a namespace or individual elements: import * as PIXI from \"pixi.js\"; const renderer = PIXI.autoDetectRenderer(); // or even better: import { autoDetectRenderer } from \"pixi.js\"; const renderer = autoDetectRenderer(); Lastly, some 3rd-party plugins maybe expecting window.PIXI, so you might have to explicitly expose the global like this, however this is not recommended. import * as PIXI from 'pixi.js'; window.PIXI = PIXI; // some bundlers might prefer \"global\" instead of \"window\"","s":"Bundling Changes","u":"/7.x/guides/migrations/v5","h":"#bundling-changes","p":886},{"i":915,"t":"When Webpack and 3rd-party plugins, like pixi-spine, you might have difficulties building the global PIXI object resulting in a runtime error ReferenceError: PIXI is not defined. Usually this can be resolved by using Webpack shimming globals. For instance, here's your import code: import * as PIXI from 'pixi.js'; import 'pixi-spine'; // or other plugins that need global 'PIXI' to be defined first Add a plugins section to your webpack.config.js to let know Webpack that the global PIXI variable make reference to pixi.js module. For instance: const webpack = require('webpack'); module.exports = { entry: '...', output: { ... }, plugins: [ new webpack.ProvidePlugin({ PIXI: 'pixi.js' }) ] }","s":"Webpack","u":"/7.x/guides/migrations/v5","h":"#webpack","p":886},{"i":917,"t":"We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures. In PixiJS, textures are one of the core resources used by display objects. A texture, broadly speaking, represents a source of pixels to be used to fill in an area on the screen. The simplest example is a sprite - a rectangle that is completely filled with a single texture. But things can get much more complex.","s":"Textures","u":"/7.x/guides/components/textures","h":"","p":916},{"i":919,"t":"Let's examine how textures really work, by following the path your image data travels on its way to the screen. Here's the flow we're going to follow: Source Image > Loader > BaseTexture > Texture","s":"Life-cycle of a Texture","u":"/7.x/guides/components/textures","h":"#life-cycle-of-a-texture","p":916},{"i":921,"t":"To start with, you have the image you want to display. The first step is to make it available on your server. This may seem obvious, but if you're coming to PixiJS from other game development systems, it's worth remembering that everything has to be loaded over the network. If you're developing locally, please be aware that you must use a webserver to test, or your images won't load due to how browsers treat local file security.","s":"Serving the Image","u":"/7.x/guides/components/textures","h":"#serving-the-image","p":916},{"i":923,"t":"To work with the image, the first step is to pull the image file from your webserver into the user's web browser. To do this, we can use PIXI.Texture.from(), which works for quick demos, but in production you'll use the Loader class. A Loader wraps and manages using an element to tell the browser to fetch the image, and then notifies you when that has been completed. This process is asynchronous - you request the load, then time passes, then an event fires to let you know the load is completed. We'll go into the loader in a lot more depth in a later guide.","s":"Loading the Image","u":"/7.x/guides/components/textures","h":"#loading-the-image","p":916},{"i":925,"t":"Once the Loader has done its work, the loaded element contains the pixel data we need. But to use it to render something, PixiJS has to take that raw image file and upload it to the GPU. This brings us to the real workhorse of the texture system - the BaseTexture class. Each BaseTexture manages a single pixel source - usually an image, but can also be a Canvas or Video element. BaseTextures allow PixiJS to convert the image to pixels and use those pixels in rendering. In addition, it also contains settings that control how the texture data is rendered, such as the wrap mode (for UV coordinates outside the 0.0-1.0 range) and scale mode (used when scaling a texture). BaseTextures are automatically cached, so that calling PIXI.Texture.from() repeatedly for the same URL returns the same BaseTexture each time. Destroying a BaseTexture frees the image data associated with it.","s":"BaseTextures Own the Data","u":"/7.x/guides/components/textures","h":"#basetextures-own-the-data","p":916},{"i":927,"t":"So finally, we get to the PIXI.Texture class itself! At this point, you may be wondering what the Texture object does. After all, the BaseTexture manages the pixels and render settings. And the answer is, it doesn't do very much. Textures are light-weight views on an underlying BaseTexture. Their main attribute is the source rectangle within the BaseTexture from which to pull. If all PixiJS drew were sprites, that would be pretty redundant. But consider SpriteSheets. A SpriteSheet is a single image that contains multiple sprite images arranged within. In a Spritesheet object, a single BaseTexture is referenced by a set of Textures, one for each source image in the original sprite sheet. By sharing a single BaseTexture, the browser only downloads one file, and our batching renderer can blaze through drawing sprites since they all share the same underlying pixel data. The SpriteSheet's Textures pull out just the rectangle of pixels needed by each sprite. 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.","s":"Textures are a View on BaseTextures","u":"/7.x/guides/components/textures","h":"#textures-are-a-view-on-basetextures","p":916},{"i":929,"t":"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. Instead, here's a quick cheat sheet of one good solution: Show a loading image Create a Loader Run all texture-based objects, add their textures to the loader Start the loader, and optionally update your loading image based on progress callbacks On loader completion, run all objects and use PIXI.Texture.from() to pull the loaded textures out of the texture cache Prepare your textures (optional - see below) 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 plugin, which allows you to pre-load textures in a final step before displaying your project.","s":"Loading Textures","u":"/7.x/guides/components/textures","h":"#loading-textures","p":916},{"i":931,"t":"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.","s":"Unloading Textures","u":"/7.x/guides/components/textures","h":"#unloading-textures","p":916},{"i":933,"t":"As we alluded to above, you can make a Texture out of more than just images: Video: Pass an HTML5
+ + \ No newline at end of file diff --git a/7.x/tutorials/getting-started.html b/7.x/tutorials/getting-started.html index a5191bf2c..27242e54f 100644 --- a/7.x/tutorials/getting-started.html +++ b/7.x/tutorials/getting-started.html @@ -9,13 +9,13 @@ - - + + - - +
Version: v7.x

LOADING...

+ + \ No newline at end of file diff --git a/8.x/branding.html b/8.x/branding.html new file mode 100644 index 000000000..52a334500 --- /dev/null +++ b/8.x/branding.html @@ -0,0 +1,28 @@ + + + + + +Branding | PixiJS + + + + + + + + + +
+
Version: v8.x

Branding

Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.

This is the banner that is displayed at the top of our README.

PixiJS Banner

We recommend using the Logo in places where the audience may not be familiar with PixiJS.

Logo (Dark)

Download: SVG +PNG

PixiJS Logo Full Dark

Logo (Dark, Transparent)

Download: SVG +PNG

PixiJS Logo Full Dark

Logo (Pink)

Download: SVG +PNG

PixiJS Logo Full Light

Logo (Pink, Transparent)

Download: SVG +PNG

PixiJS Logo Full Light

Mark

We recommend using the Mark in places where the audience is someone familiar with the ecosystem, such as PixiJS Discord users, plugin authors, social media followers.

Mark (Pink, Large)

512px x 512px

Download: SVG +PNG

PixiJS Logo Full Dark

Mark (Pink)

Download: SVG +PNG

PixiJS Logo Mark Dark

Mark (Light)

Download: SVG +PNG

PixiJS Logo Mark Light

+ + + + \ No newline at end of file diff --git a/8.x/examples.html b/8.x/examples.html new file mode 100644 index 000000000..4973c7770 --- /dev/null +++ b/8.x/examples.html @@ -0,0 +1,21 @@ + + + + + +Examples | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/collision-detection.html b/8.x/examples/advanced/collision-detection.html new file mode 100644 index 000000000..53e1c1543 --- /dev/null +++ b/8.x/examples/advanced/collision-detection.html @@ -0,0 +1,21 @@ + + + + + +Collision Detection | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/mouse-trail.html b/8.x/examples/advanced/mouse-trail.html new file mode 100644 index 000000000..6271bde6e --- /dev/null +++ b/8.x/examples/advanced/mouse-trail.html @@ -0,0 +1,21 @@ + + + + + +Mouse Trail | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/scratch-card.html b/8.x/examples/advanced/scratch-card.html new file mode 100644 index 000000000..31a35ae2f --- /dev/null +++ b/8.x/examples/advanced/scratch-card.html @@ -0,0 +1,21 @@ + + + + + +Scratch Card | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/screen-shot.html b/8.x/examples/advanced/screen-shot.html new file mode 100644 index 000000000..d6f02268b --- /dev/null +++ b/8.x/examples/advanced/screen-shot.html @@ -0,0 +1,21 @@ + + + + + +Screen Shot | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/slots.html b/8.x/examples/advanced/slots.html new file mode 100644 index 000000000..79993be12 --- /dev/null +++ b/8.x/examples/advanced/slots.html @@ -0,0 +1,21 @@ + + + + + +Slots | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/spinners.html b/8.x/examples/advanced/spinners.html new file mode 100644 index 000000000..b914ea177 --- /dev/null +++ b/8.x/examples/advanced/spinners.html @@ -0,0 +1,21 @@ + + + + + +Spinners | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/advanced/star-warp.html b/8.x/examples/advanced/star-warp.html new file mode 100644 index 000000000..660ca976a --- /dev/null +++ b/8.x/examples/advanced/star-warp.html @@ -0,0 +1,21 @@ + + + + + +Star Warp | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/assets/async.html b/8.x/examples/assets/async.html new file mode 100644 index 000000000..09183952d --- /dev/null +++ b/8.x/examples/assets/async.html @@ -0,0 +1,21 @@ + + + + + +Async | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/assets/background.html b/8.x/examples/assets/background.html new file mode 100644 index 000000000..b2299a4a7 --- /dev/null +++ b/8.x/examples/assets/background.html @@ -0,0 +1,21 @@ + + + + + +Background | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/assets/bundle.html b/8.x/examples/assets/bundle.html new file mode 100644 index 000000000..cca8f8b6c --- /dev/null +++ b/8.x/examples/assets/bundle.html @@ -0,0 +1,21 @@ + + + + + +Bundle | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/assets/multiple.html b/8.x/examples/assets/multiple.html new file mode 100644 index 000000000..b6befdc3c --- /dev/null +++ b/8.x/examples/assets/multiple.html @@ -0,0 +1,21 @@ + + + + + +Multiple | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/assets/promise.html b/8.x/examples/assets/promise.html new file mode 100644 index 000000000..28b27cdfa --- /dev/null +++ b/8.x/examples/assets/promise.html @@ -0,0 +1,21 @@ + + + + + +Promise | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/blend-modes.html b/8.x/examples/basic/blend-modes.html new file mode 100644 index 000000000..b692b7429 --- /dev/null +++ b/8.x/examples/basic/blend-modes.html @@ -0,0 +1,21 @@ + + + + + +Blend Modes | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/container.html b/8.x/examples/basic/container.html new file mode 100644 index 000000000..7c8e0c632 --- /dev/null +++ b/8.x/examples/basic/container.html @@ -0,0 +1,21 @@ + + + + + +Container | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/mesh-plane.html b/8.x/examples/basic/mesh-plane.html new file mode 100644 index 000000000..1dd4558ae --- /dev/null +++ b/8.x/examples/basic/mesh-plane.html @@ -0,0 +1,21 @@ + + + + + +Mesh Plane | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/particle-container.html b/8.x/examples/basic/particle-container.html new file mode 100644 index 000000000..2bf3d99a4 --- /dev/null +++ b/8.x/examples/basic/particle-container.html @@ -0,0 +1,21 @@ + + + + + +Particle Container | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/render-group.html b/8.x/examples/basic/render-group.html new file mode 100644 index 000000000..068d03f30 --- /dev/null +++ b/8.x/examples/basic/render-group.html @@ -0,0 +1,21 @@ + + + + + +Render Group | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/tinting.html b/8.x/examples/basic/tinting.html new file mode 100644 index 000000000..b72d6d172 --- /dev/null +++ b/8.x/examples/basic/tinting.html @@ -0,0 +1,21 @@ + + + + + +Tinting | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/basic/transparent-background.html b/8.x/examples/basic/transparent-background.html new file mode 100644 index 000000000..f49b08638 --- /dev/null +++ b/8.x/examples/basic/transparent-background.html @@ -0,0 +1,21 @@ + + + + + +Transparent Background | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/click.html b/8.x/examples/events/click.html new file mode 100644 index 000000000..2725c39eb --- /dev/null +++ b/8.x/examples/events/click.html @@ -0,0 +1,21 @@ + + + + + +Click | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/custom-hitarea.html b/8.x/examples/events/custom-hitarea.html new file mode 100644 index 000000000..ee3557002 --- /dev/null +++ b/8.x/examples/events/custom-hitarea.html @@ -0,0 +1,21 @@ + + + + + +Custom Hitarea | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/custom-mouse-icon.html b/8.x/examples/events/custom-mouse-icon.html new file mode 100644 index 000000000..9fc7914ce --- /dev/null +++ b/8.x/examples/events/custom-mouse-icon.html @@ -0,0 +1,21 @@ + + + + + +Custom Mouse Icon | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/dragging.html b/8.x/examples/events/dragging.html new file mode 100644 index 000000000..4002f2bf8 --- /dev/null +++ b/8.x/examples/events/dragging.html @@ -0,0 +1,21 @@ + + + + + +Dragging | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/interactivity.html b/8.x/examples/events/interactivity.html new file mode 100644 index 000000000..e3ab67fc6 --- /dev/null +++ b/8.x/examples/events/interactivity.html @@ -0,0 +1,21 @@ + + + + + +Interactivity | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/logger.html b/8.x/examples/events/logger.html new file mode 100644 index 000000000..fb1494ba1 --- /dev/null +++ b/8.x/examples/events/logger.html @@ -0,0 +1,21 @@ + + + + + +Logger | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/pointer-tracker.html b/8.x/examples/events/pointer-tracker.html new file mode 100644 index 000000000..bb44458e9 --- /dev/null +++ b/8.x/examples/events/pointer-tracker.html @@ -0,0 +1,21 @@ + + + + + +Pointer Tracker | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/events/slider.html b/8.x/examples/events/slider.html new file mode 100644 index 000000000..ff2b37e57 --- /dev/null +++ b/8.x/examples/events/slider.html @@ -0,0 +1,21 @@ + + + + + +Slider | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-advanced/custom.html b/8.x/examples/filters-advanced/custom.html new file mode 100644 index 000000000..3f736ee99 --- /dev/null +++ b/8.x/examples/filters-advanced/custom.html @@ -0,0 +1,21 @@ + + + + + +Custom | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-advanced/mouse-blending.html b/8.x/examples/filters-advanced/mouse-blending.html new file mode 100644 index 000000000..8a2c52ea4 --- /dev/null +++ b/8.x/examples/filters-advanced/mouse-blending.html @@ -0,0 +1,21 @@ + + + + + +Mouse Blending | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-basic/blur.html b/8.x/examples/filters-basic/blur.html new file mode 100644 index 000000000..e288a4114 --- /dev/null +++ b/8.x/examples/filters-basic/blur.html @@ -0,0 +1,21 @@ + + + + + +Blur | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-basic/color-matrix.html b/8.x/examples/filters-basic/color-matrix.html new file mode 100644 index 000000000..204e22c92 --- /dev/null +++ b/8.x/examples/filters-basic/color-matrix.html @@ -0,0 +1,21 @@ + + + + + +Color Matrix | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-basic/displacement-map-crawlies.html b/8.x/examples/filters-basic/displacement-map-crawlies.html new file mode 100644 index 000000000..ff9ebbfa4 --- /dev/null +++ b/8.x/examples/filters-basic/displacement-map-crawlies.html @@ -0,0 +1,21 @@ + + + + + +Displacement Map Crawlies | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/filters-basic/displacement-map-flag.html b/8.x/examples/filters-basic/displacement-map-flag.html new file mode 100644 index 000000000..46aa1b735 --- /dev/null +++ b/8.x/examples/filters-basic/displacement-map-flag.html @@ -0,0 +1,21 @@ + + + + + +Displacement Map Flag | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/advanced.html b/8.x/examples/graphics/advanced.html new file mode 100644 index 000000000..7ee30ec67 --- /dev/null +++ b/8.x/examples/graphics/advanced.html @@ -0,0 +1,21 @@ + + + + + +Advanced | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/dynamic.html b/8.x/examples/graphics/dynamic.html new file mode 100644 index 000000000..75d8dec28 --- /dev/null +++ b/8.x/examples/graphics/dynamic.html @@ -0,0 +1,21 @@ + + + + + +Dynamic | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/fill-gradient.html b/8.x/examples/graphics/fill-gradient.html new file mode 100644 index 000000000..a064bb9a5 --- /dev/null +++ b/8.x/examples/graphics/fill-gradient.html @@ -0,0 +1,21 @@ + + + + + +Fill Gradient | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/mesh-from-path.html b/8.x/examples/graphics/mesh-from-path.html new file mode 100644 index 000000000..a05c275ca --- /dev/null +++ b/8.x/examples/graphics/mesh-from-path.html @@ -0,0 +1,21 @@ + + + + + +Mesh From Path | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/simple.html b/8.x/examples/graphics/simple.html new file mode 100644 index 000000000..2d041924d --- /dev/null +++ b/8.x/examples/graphics/simple.html @@ -0,0 +1,21 @@ + + + + + +Simple | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/svg-load.html b/8.x/examples/graphics/svg-load.html new file mode 100644 index 000000000..98aa4d89a --- /dev/null +++ b/8.x/examples/graphics/svg-load.html @@ -0,0 +1,21 @@ + + + + + +Svg Load | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/svg.html b/8.x/examples/graphics/svg.html new file mode 100644 index 000000000..90c190b96 --- /dev/null +++ b/8.x/examples/graphics/svg.html @@ -0,0 +1,21 @@ + + + + + +Svg | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/graphics/texture.html b/8.x/examples/graphics/texture.html new file mode 100644 index 000000000..569c24342 --- /dev/null +++ b/8.x/examples/graphics/texture.html @@ -0,0 +1,21 @@ + + + + + +Texture | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/masks/filter.html b/8.x/examples/masks/filter.html new file mode 100644 index 000000000..2795c84a8 --- /dev/null +++ b/8.x/examples/masks/filter.html @@ -0,0 +1,21 @@ + + + + + +Filter | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/masks/graphics.html b/8.x/examples/masks/graphics.html new file mode 100644 index 000000000..a3c5ec6d5 --- /dev/null +++ b/8.x/examples/masks/graphics.html @@ -0,0 +1,21 @@ + + + + + +Graphics | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/masks/sprite.html b/8.x/examples/masks/sprite.html new file mode 100644 index 000000000..243ee0ce9 --- /dev/null +++ b/8.x/examples/masks/sprite.html @@ -0,0 +1,21 @@ + + + + + +Sprite | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/instanced-geometry.html b/8.x/examples/mesh-and-shaders/instanced-geometry.html new file mode 100644 index 000000000..c73ccc763 --- /dev/null +++ b/8.x/examples/mesh-and-shaders/instanced-geometry.html @@ -0,0 +1,21 @@ + + + + + +Instanced Geometry | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/multipass-mesh.html b/8.x/examples/mesh-and-shaders/multipass-mesh.html new file mode 100644 index 000000000..b8e9d183e --- /dev/null +++ b/8.x/examples/mesh-and-shaders/multipass-mesh.html @@ -0,0 +1,21 @@ + + + + + +Multipass Mesh | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/shader-toy-mesh.html b/8.x/examples/mesh-and-shaders/shader-toy-mesh.html new file mode 100644 index 000000000..0b483714d --- /dev/null +++ b/8.x/examples/mesh-and-shaders/shader-toy-mesh.html @@ -0,0 +1,21 @@ + + + + + +Shader Toy Mesh | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/shared-geometry.html b/8.x/examples/mesh-and-shaders/shared-geometry.html new file mode 100644 index 000000000..fc0440b05 --- /dev/null +++ b/8.x/examples/mesh-and-shaders/shared-geometry.html @@ -0,0 +1,21 @@ + + + + + +Shared Geometry | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/shared-shader.html b/8.x/examples/mesh-and-shaders/shared-shader.html new file mode 100644 index 000000000..826b25fbb --- /dev/null +++ b/8.x/examples/mesh-and-shaders/shared-shader.html @@ -0,0 +1,21 @@ + + + + + +Shared Shader | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/textured-mesh-advanced.html b/8.x/examples/mesh-and-shaders/textured-mesh-advanced.html new file mode 100644 index 000000000..1f2af397b --- /dev/null +++ b/8.x/examples/mesh-and-shaders/textured-mesh-advanced.html @@ -0,0 +1,21 @@ + + + + + +Textured Mesh Advanced | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/textured-mesh-basic.html b/8.x/examples/mesh-and-shaders/textured-mesh-basic.html new file mode 100644 index 000000000..cf0146a5a --- /dev/null +++ b/8.x/examples/mesh-and-shaders/textured-mesh-basic.html @@ -0,0 +1,21 @@ + + + + + +Textured Mesh Basic | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/triangle-color.html b/8.x/examples/mesh-and-shaders/triangle-color.html new file mode 100644 index 000000000..2f2c9a04d --- /dev/null +++ b/8.x/examples/mesh-and-shaders/triangle-color.html @@ -0,0 +1,21 @@ + + + + + +Triangle Color | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/triangle-textured.html b/8.x/examples/mesh-and-shaders/triangle-textured.html new file mode 100644 index 000000000..7af93ce29 --- /dev/null +++ b/8.x/examples/mesh-and-shaders/triangle-textured.html @@ -0,0 +1,21 @@ + + + + + +Triangle Textured | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/mesh-and-shaders/triangle.html b/8.x/examples/mesh-and-shaders/triangle.html new file mode 100644 index 000000000..6c5581482 --- /dev/null +++ b/8.x/examples/mesh-and-shaders/triangle.html @@ -0,0 +1,21 @@ + + + + + +Triangle | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/offscreen-canvas/basic.html b/8.x/examples/offscreen-canvas/basic.html new file mode 100644 index 000000000..14128af78 --- /dev/null +++ b/8.x/examples/offscreen-canvas/basic.html @@ -0,0 +1,21 @@ + + + + + +Basic | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/animated-sprite-animation-speed.html b/8.x/examples/sprite/animated-sprite-animation-speed.html new file mode 100644 index 000000000..6d8f0743d --- /dev/null +++ b/8.x/examples/sprite/animated-sprite-animation-speed.html @@ -0,0 +1,21 @@ + + + + + +Animated Sprite Animation Speed | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/animated-sprite-explosion.html b/8.x/examples/sprite/animated-sprite-explosion.html new file mode 100644 index 000000000..83764185b --- /dev/null +++ b/8.x/examples/sprite/animated-sprite-explosion.html @@ -0,0 +1,21 @@ + + + + + +Animated Sprite Explosion | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/animated-sprite-jet.html b/8.x/examples/sprite/animated-sprite-jet.html new file mode 100644 index 000000000..6e7d0e201 --- /dev/null +++ b/8.x/examples/sprite/animated-sprite-jet.html @@ -0,0 +1,21 @@ + + + + + +Animated Sprite Jet | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/basic.html b/8.x/examples/sprite/basic.html new file mode 100644 index 000000000..979f2c84a --- /dev/null +++ b/8.x/examples/sprite/basic.html @@ -0,0 +1,21 @@ + + + + + +Basic | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/texture-swap.html b/8.x/examples/sprite/texture-swap.html new file mode 100644 index 000000000..b5071fe05 --- /dev/null +++ b/8.x/examples/sprite/texture-swap.html @@ -0,0 +1,21 @@ + + + + + +Texture Swap | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/tiling-sprite.html b/8.x/examples/sprite/tiling-sprite.html new file mode 100644 index 000000000..2a356cd0a --- /dev/null +++ b/8.x/examples/sprite/tiling-sprite.html @@ -0,0 +1,21 @@ + + + + + +Tiling Sprite | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/sprite/video.html b/8.x/examples/sprite/video.html new file mode 100644 index 000000000..fef6f2a07 --- /dev/null +++ b/8.x/examples/sprite/video.html @@ -0,0 +1,21 @@ + + + + + +Video | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/text/bitmap-text.html b/8.x/examples/text/bitmap-text.html new file mode 100644 index 000000000..193acefdc --- /dev/null +++ b/8.x/examples/text/bitmap-text.html @@ -0,0 +1,21 @@ + + + + + +Bitmap Text | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/text/from-font.html b/8.x/examples/text/from-font.html new file mode 100644 index 000000000..a1d174347 --- /dev/null +++ b/8.x/examples/text/from-font.html @@ -0,0 +1,21 @@ + + + + + +From Font | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/text/pixi-text.html b/8.x/examples/text/pixi-text.html new file mode 100644 index 000000000..5416a7bca --- /dev/null +++ b/8.x/examples/text/pixi-text.html @@ -0,0 +1,21 @@ + + + + + +Pixi Text | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/text/web-font.html b/8.x/examples/text/web-font.html new file mode 100644 index 000000000..49bdafa66 --- /dev/null +++ b/8.x/examples/text/web-font.html @@ -0,0 +1,21 @@ + + + + + +Web Font | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/textures/render-texture-advanced.html b/8.x/examples/textures/render-texture-advanced.html new file mode 100644 index 000000000..d62ed4fc4 --- /dev/null +++ b/8.x/examples/textures/render-texture-advanced.html @@ -0,0 +1,21 @@ + + + + + +Render Texture Advanced | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/textures/render-texture-basic.html b/8.x/examples/textures/render-texture-basic.html new file mode 100644 index 000000000..6eb94aba8 --- /dev/null +++ b/8.x/examples/textures/render-texture-basic.html @@ -0,0 +1,21 @@ + + + + + +Render Texture Basic | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/examples/textures/texture-rotate.html b/8.x/examples/textures/texture-rotate.html new file mode 100644 index 000000000..f05a2bda8 --- /dev/null +++ b/8.x/examples/textures/texture-rotate.html @@ -0,0 +1,21 @@ + + + + + +Texture Rotate | PixiJS + + + + + + + + + + + + + + \ No newline at end of file diff --git a/8.x/faq.html b/8.x/faq.html new file mode 100644 index 000000000..2177e2f90 --- /dev/null +++ b/8.x/faq.html @@ -0,0 +1,28 @@ + + + + + +FAQ | PixiJS + + + + + + + + + +
+
Version: v8.x

FAQ

What is PixiJS for?

Everything! Pixi.js is a rendering library that will allow you to create rich, +interactive graphic experiences, cross-platform applications, and games without +having to dive into the WebGL API or grapple with the intricacies of browser and +device compatibility. Killer performance with a clean API, means not only will +your content be better - but also faster to build!

Is PixiJS free?

PixiJS is and always will be free and Open Source. That said, financial contributions +are what make it possible to push PixiJS further, faster. Contributions allow us to +commission the PixiJS developer community to accelerate feature development and create +more in-depth documentation. Support Us by making a contribution via Open Collective. Go on! It will be a massive help AND make you feel good about yourself, win win ;)

Where do I get it?

Visit our GitHub page to download the very latest version of PixiJS. This is the most up-to-date resource for PixiJS and should always be your first port of call to make sure you are using the latest version. Just click the 'Download' link in the navigation.

How do I get started?

Right here! Take a look through the Resources section for a wealth of information including documentation, forums, tutorials and the Goodboy blog.

Why should I use PixiJS?

Because you care about speed. PixiJS' #1 mantra has always been speed. We really do feel the need! We do everything we can to make PixiJS as streamlined, efficient and fast as possible, whilst balancing it with offering as many crucial and valuable features as we can.

Is PixiJS a game engine?

No. PixiJS is what we've come to think of as a "creation engine". Whilst it is extremely good for making games, the core essence of PixiJS is simply moving things around on screens as quickly and efficiently as possible. It does of course happen that it is absolutely brilliant for making games though!

Who makes PixiJS?

Outside of the highly active PixiJS community, it is primarily maintained by Mat Groves, Technical Partner of our creative agency Goodboy Digital. One of the huge advantages of creating PixiJS within the framework of a working agency is that it means its features are always driven by genuine industry demands and critically are always trialled "in anger" in our cutting-edge games, sites and apps.

I found a bug. What should I do?

Two things - lets us know via the PixiJS GitHub community and even better yet, if you know how, post a fix! Our Community is stronger in numbers so we're always keen to welcome new contributors into the team to help us shape what PixiJS becomes next.

+ + + + \ No newline at end of file diff --git a/8.x/guides.html b/8.x/guides.html new file mode 100644 index 000000000..eb27a1e02 --- /dev/null +++ b/8.x/guides.html @@ -0,0 +1,21 @@ + + + + + +Welcome | PixiJS + + + + + + + + + +
+
Version: v8.x

Welcome

PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.

About The Guides

If you're new to PixiJS, we suggest you start with the Basics and read through them in order (a good place to start is Getting Started). While PixiJS has a mature API and solid documentation, the guides go over many common issues and questions that developers new to the system encounter.

Other Resources

As you explore the guides, you may find these resources valuable:

+ + + + \ No newline at end of file diff --git a/8.x/guides/advanced/render-groups.html b/8.x/guides/advanced/render-groups.html new file mode 100644 index 000000000..e1ab6af1e --- /dev/null +++ b/8.x/guides/advanced/render-groups.html @@ -0,0 +1,21 @@ + + + + + +Render Groups | PixiJS + + + + + + + + + +
+
Version: v8.x

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

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.

+ + + + \ No newline at end of file diff --git a/8.x/guides/basics/architecture-overview.html b/8.x/guides/basics/architecture-overview.html new file mode 100644 index 000000000..bbc4c23ba --- /dev/null +++ b/8.x/guides/basics/architecture-overview.html @@ -0,0 +1,21 @@ + + + + + +Architecture Overview | PixiJS + + + + + + + + + +
+
Version: v8.x

Architecture Overview

OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.

The Code

Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on GitHub. Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called TypeScript, which enables type-checking in JavaScript via a pre-compile step.

The Components

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

ComponentDescription
RendererThe 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.
ContainerMain scene object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See Scene Graph for more details.
AssetsThe Asset system provides tools for asynchronously loading resources such as images and audio files.
TickerTickers 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.
ApplicationThe 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.
EventsPixiJS supports pointer-based interaction - making objects clickable, firing hover events, etc.
AccessibilityWoven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility.
+ + + + \ No newline at end of file diff --git a/guides/basics/getting-started.html b/8.x/guides/basics/getting-started.html similarity index 74% rename from guides/basics/getting-started.html rename to 8.x/guides/basics/getting-started.html index 5a87887e1..6c09f11f1 100644 --- a/guides/basics/getting-started.html +++ b/8.x/guides/basics/getting-started.html @@ -3,22 +3,22 @@ -Getting Started | PixiJS +Getting Started | PixiJS - - + +
-

Getting Started

In this section we're going to build the simplest possible PixiJS application. In doing so, we'll walk through the basics of how to build and serve the code.

Advanced Users

A quick note before we start: this guide is aimed at beginning PixiJS developers who have minimal +

Version: v8.x

Getting Started

In this section we're going to build the simplest possible PixiJS application. In doing so, we'll walk through the basics of how to build and serve the code.

Advanced Users

A quick note before we start: this guide is aimed at beginning PixiJS developers who have minimal experience developing JavaScript-based applications. If you are a coding veteran, you may find that the level of detail here is not helpful. If that's the case, you may want to skim this guide, then -jump into how to work with PixiJS and packers like webpack and npm.

A Note About JavaScript

One final note. The JavaScript universe is currently in transition from old-school JavaScript (ES5) to the newer ES6 flavor:

// ES5
var x = 5;
setTimeout(function() { alert(x); }, 1000);
// ES6
const x = 5;
setTimeout(() => alert(x), 1000);

ES6 brings a number of major advantages in terms of clearer syntax, better variable scoping, native class support, etc. By now, all major browsers support it. Given this, our examples in these guides will use ES6. This doesn't mean you can't use PixiJS with ES5 programs! Just mentally substitute "var" for "let/const", expand the shorter function-passing syntax, and everything will run just fine.

Components of a PixiJS Application

OK! With those notes out of the way, let's get started. There are only a few steps required to write a PixiJS application:

  • Create an HTML file
  • Serve the file with a web server
  • Load the PixiJS library
  • Create an Application
  • Add the generated view to the DOM
  • Add an image to the stage
  • Write an update loop

Let's walk through them together.

The HTML File

PixiJS is a JavaScript library that runs in a web page. So the first thing we're going to need is some HTML in a file. In a real PixiJS application, you might want to embed your display within a complex existing page, or you might want your display area to fill the whole page. For this demo, we'll build an empty page to start:

<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello PixiJS</h1>
</body>
</html>

Create a new folder named pixi-test, then copy and paste this HTML into a new file in the pixi-test folder named index.html.

Serving the File

You will need to run a web server to develop locally with PixiJS. Web browsers prevent loading local files (such as images and audio files) on locally loaded web pages. If you just double-click your new HTML file, you'll get an error when you try to add a sprite to the PixiJS stage.

Running a web server sounds complex and difficult, but it turns out there are a number of simple web servers that will serve this purpose. For this guide, we're going to be working with Mongoose, but you could just as easily use XAMPP or the http-server Node.js package to serve your files.

To start serving your page with Mongoose, go to the Mongoose download page and download the free server for your operating system. Mongoose defaults to serving the files in the folder it's run in, so copy the downloaded executable into the folder you created in the prior step (pixi-test). Double-click the executable, tell your operating system that you trust the file to run, and you'll have a running web server, serving your new folder.

Test that everything is working by opening your browser of choice and entering http://127.0.0.1:8080 in the location bar. (Mongoose by default serves files on port 8080.) You should see "Hello PixiJS" and nothing else. If you get an error at this step, it means you didn't name your file index.html or you mis-configured your web server.

Loading PixiJS

OK, so we have a web page, and we're serving it. But it's empty. The next step is to actually load the PixiJS library. If we were building a real application, we'd want to download a target version of PixiJS from the Pixi Github repo so that our version wouldn't change on us. But for this sample application, we'll just use the CDN version of PixiJS. Add this line to the <head> section of your index.html file:

<script src="https://pixijs.download/release/pixi.js"></script>

This will include a non-minified version of the latest version of PixiJS when your page loads, ready to be used. We use the non-minified version because we're in development. In production, you'd want to use pixi.min.js instead, which is compressed for faster download and excludes assertions and deprecation warnings that can help when building your project, but take longer to download and run.

Creating an Application

Loading the library doesn't do much good if we don't use it, so the next step is to start up PixiJS. Start by replacing the line <h1>Hello PixiJS</h1> with a script tag like so:

<script>
const app = new PIXI.Application();
app.init({ width: 640, height: 360 }).then(()=>{})
</script>

What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. Application 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 then keyword to start our logic after the promise has completed. This is because PixiJS uses WebGPU or WebGL under the hood, and the former API asynchronous.

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:

  document.body.appendChild(app.canvas);

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. 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. 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:

  // Magically load the PNG asynchronously
let sprite = PIXI.Sprite.from('sample.png');

Download the sample PNG here, and save it into your pixi-test directory next to your index.html.

Adding the Sprite to the Stage

Finally, we need to add our new sprite to the stage. The stage is simply a Container 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.

  app.stage.addChild(sprite);

Writing an Update Loop

While you can use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ticker. A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:

  // Add a variable to count up the seconds our demo has been running
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((ticker) => {
// Add the time to our total elapsed time
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;
});

All you need to do is to call app.ticker.add(...), pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations.

Putting It All Together

That's it! The simplest PixiJS project!

Here's the whole thing in one place. Check your file and make sure it matches if you're getting errors.

<!doctype html>
<html>
<head>
<script src="https://pixijs.download/release/pixi.min.js"></script>
</head>
<body>
<script>
// Create the application helper and add its render target to the page
const app = new PIXI.Application();
app.init({ width: 640, height: 360 }).then(()=>{
document.body.appendChild(app.canvas);

// Create the sprite and add it to the stage
let sprite = PIXI.Sprite.from('sample.png');
app.stage.addChild(sprite);

// Add a ticker callback to move the sprite back and forth
let elapsed = 0.0;
app.ticker.add((ticker) => {
elapsed += ticker.deltaTime;
sprite.x = 100.0 + Math.cos(elapsed/50.0) * 100.0;
});
})
</script>
</body>
</html>

Once you have things working, the next thing to do is to read through the rest of the Basics guides to dig into how all this works in much greater depth.

- - +jump into how to work with PixiJS and packers like webpack and npm.

A Note About JavaScript

One final note. The JavaScript universe is currently in transition from old-school JavaScript (ES5) to the newer ES6 flavor:

// ES5
var x = 5;
setTimeout(function() { alert(x); }, 1000);
// ES6
const x = 5;
setTimeout(() => alert(x), 1000);

ES6 brings a number of major advantages in terms of clearer syntax, better variable scoping, native class support, etc. By now, all major browsers support it. Given this, our examples in these guides will use ES6. This doesn't mean you can't use PixiJS with ES5 programs! Just mentally substitute "var" for "let/const", expand the shorter function-passing syntax, and everything will run just fine.

Components of a PixiJS Application

OK! With those notes out of the way, let's get started. There are only a few steps required to write a PixiJS application:

  • Create an HTML file
  • Serve the file with a web server
  • Load the PixiJS library
  • Create an Application
  • Add the generated view to the DOM
  • Add an image to the stage
  • Write an update loop

Let's walk through them together.

The HTML File

PixiJS is a JavaScript library that runs in a web page. So the first thing we're going to need is some HTML in a file. In a real PixiJS application, you might want to embed your display within a complex existing page, or you might want your display area to fill the whole page. For this demo, we'll build an empty page to start:

<!doctype html>
<html>
<head>
</head>
<body>
<h1>Hello PixiJS</h1>
</body>
</html>

Create a new folder named pixi-test, then copy and paste this HTML into a new file in the pixi-test folder named index.html.

Serving the File

You will need to run a web server to develop locally with PixiJS. Web browsers prevent loading local files (such as images and audio files) on locally loaded web pages. If you just double-click your new HTML file, you'll get an error when you try to add a sprite to the PixiJS stage.

Running a web server sounds complex and difficult, but it turns out there are a number of simple web servers that will serve this purpose. For this guide, we're going to be working with Mongoose, but you could just as easily use XAMPP or the http-server Node.js package to serve your files.

To start serving your page with Mongoose, go to the Mongoose download page and download the free server for your operating system. Mongoose defaults to serving the files in the folder it's run in, so copy the downloaded executable into the folder you created in the prior step (pixi-test). Double-click the executable, tell your operating system that you trust the file to run, and you'll have a running web server, serving your new folder.

Test that everything is working by opening your browser of choice and entering http://127.0.0.1:8080 in the location bar. (Mongoose by default serves files on port 8080.) You should see "Hello PixiJS" and nothing else. If you get an error at this step, it means you didn't name your file index.html or you mis-configured your web server.

Loading PixiJS

OK, so we have a web page, and we're serving it. But it's empty. The next step is to actually load the PixiJS library. If we were building a real application, we'd want to download a target version of PixiJS from the Pixi Github repo so that our version wouldn't change on us. But for this sample application, we'll just use the CDN version of PixiJS. Add this line to the <head> section of your index.html file:

<script src="https://pixijs.download/release/pixi.js"></script>

This will include a non-minified version of the latest version of PixiJS when your page loads, ready to be used. We use the non-minified version because we're in development. In production, you'd want to use pixi.min.js instead, which is compressed for faster download and excludes assertions and deprecation warnings that can help when building your project, but take longer to download and run.

Creating an Application

Loading the library doesn't do much good if we don't use it, so the next step is to start up PixiJS. Start by replacing the line <h1>Hello PixiJS</h1> with a script tag like so:

<script>
const app = new PIXI.Application();
app.init({ width: 640, height: 360 }).then(()=>{})
</script>

What we're doing here is adding a JavaScript code block, and in that block creating a new PIXI.Application instance. Application 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 then keyword to start our logic after the promise has completed. This is because PixiJS uses WebGPU or WebGL under the hood, and the former API asynchronous.

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:

  document.body.appendChild(app.canvas);

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. 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. 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:

  // Magically load the PNG asynchronously
let sprite = PIXI.Sprite.from('sample.png');

Download the sample PNG here, and save it into your pixi-test directory next to your index.html.

Adding the Sprite to the Stage

Finally, we need to add our new sprite to the stage. The stage is simply a Container 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.

  app.stage.addChild(sprite);

Writing an Update Loop

While you can use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ticker. A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:

  // Add a variable to count up the seconds our demo has been running
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((ticker) => {
// Add the time to our total elapsed time
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;
});

All you need to do is to call app.ticker.add(...), pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations.

Putting It All Together

That's it! The simplest PixiJS project!

Here's the whole thing in one place. Check your file and make sure it matches if you're getting errors.

<!doctype html>
<html>
<head>
<script src="https://pixijs.download/release/pixi.min.js"></script>
</head>
<body>
<script>
// Create the application helper and add its render target to the page
const app = new PIXI.Application();
app.init({ width: 640, height: 360 }).then(()=>{
document.body.appendChild(app.canvas);

// Create the sprite and add it to the stage
let sprite = PIXI.Sprite.from('sample.png');
app.stage.addChild(sprite);

// Add a ticker callback to move the sprite back and forth
let elapsed = 0.0;
app.ticker.add((ticker) => {
elapsed += ticker.deltaTime;
sprite.x = 100.0 + Math.cos(elapsed/50.0) * 100.0;
});
})
</script>
</body>
</html>

Once you have things working, the next thing to do is to read through the rest of the Basics guides to dig into how all this works in much greater depth.

+ + \ No newline at end of file diff --git a/8.x/guides/basics/render-loop.html b/8.x/guides/basics/render-loop.html new file mode 100644 index 000000000..0c14e8007 --- /dev/null +++ b/8.x/guides/basics/render-loop.html @@ -0,0 +1,21 @@ + + + + + +Render Loop | PixiJS + + + + + + + + + +
+
Version: v8.x

Render Loop

Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.

The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering.

Let's walk through what happens each frame of the render loop. There are three main steps.

Running Ticker Callbacks

The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering.

Updating the Scene Graph

We'll talk a lot more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing.

Rendering the Scene Graph

Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (app.stage), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization.

Frame Rates

A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue.

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

What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach.

+ + + + \ No newline at end of file diff --git a/8.x/guides/basics/scene-graph.html b/8.x/guides/basics/scene-graph.html new file mode 100644 index 000000000..6516d3839 --- /dev/null +++ b/8.x/guides/basics/scene-graph.html @@ -0,0 +1,21 @@ + + + + + +Scene Graph | PixiJS + + + + + + + + + +
+
Version: v8.x

Scene Graph

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

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 for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)

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.

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:

// Create the application helper and add its render target to the page
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 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 wrapper = new Container();
let sprite = Sprite.from('assets/images/sample.png');
sprite.anchor.set(0.5);
wrapper.addChild(sprite);
parent.addChild(wrapper);
sprites.push(wrapper);
parent = wrapper;
}

// Set all sprite's properties to the same value, animated over time
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60;
const amount = Math.sin(elapsed);
const scale = 1.0 + 0.25 * amount;
const alpha = 0.75 + 0.25 * amount;
const angle = 40 * amount;
const x = 75 * amount;
for (let i = 0; i < sprites.length; i++) {
const sprite = sprites[i];
sprite.scale.set(scale);
sprite.alpha = alpha;
sprite.angle = angle;
sprite.x = x;
}
});

The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's worldTransform property. Similarly, the cumulative alpha value is stored in the worldAlpha property.

Render Order

So we have a tree of things to draw. Who gets drawn first?

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.

Check out this example, with two parent objects A & D, and two children B & C under A:

// Create the application helper and add its render target to the page
const app = new Application();
await app.init({ width: 640, height: 360 })
document.body.appendChild(app.canvas);

// Label showing scene graph hierarchy
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 Sprite(Texture.WHITE);
bg.width = 100;
bg.height = 100;
bg.tint = color;

const text = new Text({
text:letter,
style:{fill: "#ffffff"}
});

text.anchor.set(0.5);
text.position = {x: 50, y: 50};

const container = new Container();
container.position = pos;
container.visible = false;
container.addChild(bg, text);
parent.addChild(container);

letters.push(container);
return container;
}

// Define 4 letters
let a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});
let b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});
let c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});
let d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});

// Display them over time, in order
let elapsed = 0.0;
app.ticker.add((delta) => {
elapsed += delta / 60.0;
if (elapsed >= letters.length) { elapsed = 0.0; }
for (let i = 0; i < letters.length; i ++) {
letters[i].visible = elapsed >= i;
}
});

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

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 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.

Local vs Global Coordinates

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.

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.

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));

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.

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.

+ + + + \ No newline at end of file diff --git a/8.x/guides/basics/what-pixijs-is-not.html b/8.x/guides/basics/what-pixijs-is-not.html new file mode 100644 index 000000000..1139d006f --- /dev/null +++ b/8.x/guides/basics/what-pixijs-is-not.html @@ -0,0 +1,21 @@ + + + + + +What PixiJS Is Not | PixiJS + + + + + + + + + +
+
Version: v8.x

What PixiJS Is Not

While PixiJS can do many things, there are things it can't do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you're about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.

PixiJS Is Not ... A Framework

PixiJS is a rendering engine, and it supports additional features such as interaction management that are commonly needed when using a render engine. But it is not a framework like Unity or Phaser. Frameworks are designed to do all the things you'd need to do when building a game - user settings management, music playback, object scripting, art pipeline management... the list goes on. PixiJS is designed to do one thing really well - render graphical content. This lets us focus on keeping up with new technology, and makes downloading PixiJS blazingly fast.

... A 3D Renderer

PixiJS is built for 2D. Platformers, adventure games, interactive ads, custom data visualization... all good. But if you want to render 3D models, you might want to check out babylon.js or three.js.

... A Mobile App

If you're looking to build mobile games, you can do it with PixiJS, but you'll need to use a deployment system like Apache Cordova if you want access to native bindings. We don't provide access to the camera, location services, notifications, etc.

... A UI Library

Building a truly generic UI system is a huge challenge, as anyone who has worked with Unity's UI tools can attest. We've chosen to avoid the complexity to stay true to our core focus on speed. While you can certainly build your own UI using PixiJS's scene graph and interaction manager, we don't ship with a UI library out of the box.

... A Data Store

There are many techniques and technologies that you can use to store settings, scores, and other data. Cookies, Web Storage, server-based storage... there are many solutions, each with advantages and disadvantages. You can use any of them with PixiJS, but we don't provide tools to do so.

... An Audio Library

At least, not out of the box. Again, web audio technology is a constantly evolving challenge, with constantly changing rules and requirements across many browsers. There are a number of dedicated web audio libraries (such as Howler.js that can be used with PixiJS to play sound effects and music. Alternatively, the PixiJS Sound plugin is designed to work well with PixiJS.

... A Development Environment

There are a number of tools that are useful for building 2D art and games that you might expect to be a part of PixiJS, but we're a rendering engine, not a development environment. Packing sprite sheets, processing images, building mipmaps or Retina-ready sprites - there are great standalone tools for this type of tooling. Where appropriate throughout the guides, we'll point you to tools that may be useful.

So Is PixiJS Right For Me?

Only you know! If you're looking for a tightly focused, fast and efficient rendering engine for your next web-based project, PixiJS is likely a great fit.

If you need a full game development framework, with native bindings and a rich UI library, you may want to explore other options.

Or you may not. It can be faster and easier to build just the subset of a full framework that your project needs than it can be to digest a monolithic API with bells and whistles you don't need. There are hundreds of complex, rich games and visual projects that use PixiJS for rendering, with plugins or custom code to add the UI and sound effects. There are benefits to both approaches. Regardless, we hope you have a better feel for what PixiJS can (and cannot!) offer your project.

+ + + + \ No newline at end of file diff --git a/8.x/guides/basics/what-pixijs-is.html b/8.x/guides/basics/what-pixijs-is.html new file mode 100644 index 000000000..be771aa11 --- /dev/null +++ b/8.x/guides/basics/what-pixijs-is.html @@ -0,0 +1,21 @@ + + + + + +What PixiJS Is | PixiJS + + + + + + + + + +
+
Version: v8.x

What PixiJS Is

So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.

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 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 MeshRope. Draw polygons, lines, circles and other primitives with Graphics. Text 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.

... Hardware accelerated

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 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

Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and hosted on GitHub for issue tracking and ease of access.

... Extensible

Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs.

... Easy to Deploy

Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock.

+ + + + \ No newline at end of file diff --git a/guides/components/assets.html b/8.x/guides/components/assets.html similarity index 79% rename from guides/components/assets.html rename to 8.x/guides/components/assets.html index 46d61a39b..fe2b89e4c 100644 --- a/guides/components/assets.html +++ b/8.x/guides/components/assets.html @@ -3,26 +3,26 @@ -Assets | PixiJS +Assets | PixiJS - - + +
-

Assets

The Assets package

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

Assets relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser doesn't support promises you should look into polyfilling them.

Making our first Assets Promise

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. +

Version: v8.x

Assets

The Assets package

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

Assets relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser doesn't support promises you should look into polyfilling them.

Making our first Assets Promise

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.

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:

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:

  • Textures (avif, webp, png, jpg, gif)
  • Sprite sheets (json)
  • Bitmap fonts (xml, fnt, txt)
  • Web fonts (ttf, woff, woff2)
  • Json files (json)
  • Text files (txt)

More types can be added fairly easily by creating additional loader parsers.

Warning about solved promises

When an asset is downloaded, it is cached as a promise inside the Assets instance and if you try to download it again you will get a reference to the already resolved promise. However promise handlers .then(...)/.catch(...)/.finally(...) are always asynchronous, this means that even if a promise was already resolved the code below the .then(...)/.catch(...)/.finally(...) will execute before the code inside them. See this example:

console.log(1);
alreadyResolvedPromise.then(() => console.log(2));
console.log(3);

// Console output:
// 1
// 3
// 2

To learn more about why this happens you will need to learn about Microtasks, however, using async functions should mitigate this problem.

Using Async/Await

There is a way to work with promises that is more intuitive and easier to read: async/await.

To use it we first need to create a function/method and mark it as async.

async function test() {
// ...
}

This function now wraps the return value in a promise and allows us to use the await keyword before a promise to halt the execution of the code until it is resolved and gives us the value.

See this example:

// 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.

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 Assets.add(...) and then calling Assets.load(...) with all the keys you want to have loaded. See the following example:

// 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 Assets.addBundle(...)/Assets.loadBundle(...).

  Assets.addBundle('animals', {
bunny: 'bunny.png',
chicken: 'chicken.png',
thumper: 'thumper.png',
});

const assets = await Assets.loadBundle('animals');

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.

{
"bundles":[
{
"name":"load-screen",
"assets":[
{
"alias":"background",
"src":"sunset.png"
},
{
"alias":"bar",
"src":"load-bar.{png,webp}"
}
]
},
{
"name":"game-screen",
"assets":[
{
"alias":"character",
"src":"robot.png"
},
{
"alias":"enemy",
"src":"bad-guy.png"
}
]
}
]
}
Assets.init({manifest: "path/manifest.json"});

Beware that you can only call init once.

Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!

Background loading

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, 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 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 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:

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!

- - +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.

{
"bundles":[
{
"name":"load-screen",
"assets":[
{
"alias":"background",
"src":"sunset.png"
},
{
"alias":"bar",
"src":"load-bar.{png,webp}"
}
]
},
{
"name":"game-screen",
"assets":[
{
"alias":"character",
"src":"robot.png"
},
{
"alias":"enemy",
"src":"bad-guy.png"
}
]
}
]
}
Assets.init({manifest: "path/manifest.json"});

Beware that you can only call init once.

Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!

Background loading

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, 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 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 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:

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!

+ + \ No newline at end of file diff --git a/8.x/guides/components/containers.html b/8.x/guides/components/containers.html new file mode 100644 index 000000000..81ed7aec9 --- /dev/null +++ b/8.x/guides/components/containers.html @@ -0,0 +1,21 @@ + + + + + +Containers | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Containers

The Container 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:

PropertyDescription
positionX- 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
rotationRotation is specified in radians, and turns an object clockwise (0.0 - 2 * Math.PI)
angleAngle is an alias for rotation that is specified in degrees instead of radians (0.0 - 360.0)
pivotPoint the object rotates around, in pixels - also sets origin for child objects
alphaOpacity from 0.0 (fully transparent) to 1.0 (fully opaque), inherited by children
scaleScale is specified as a percent with 1.0 being 100% or actual-size, and can be set independently for the x and y axis
skewSkew transforms the object in x and y similar to the CSS skew() function, and is specified in radians
visibleWhether the object is visible or not, as a boolean value - prevents updating and rendering object and children
renderableWhether 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! 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.

So that's the primary use for Containers - as groups of renderable objects in a hierarchy.

Check out the container example code.

Masking

Another common use for Container objects is as hosts for masked content. "Masking" is a technique where parts of your scene graph are only visible within a given area.

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.

// Create the application helper and add its render target to the page
let app = new Application({ width: 640, height: 360 });
document.body.appendChild(app.view);

// Create window frame
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 Graphics()
// Add the rectangular area to show
.rect(0,0,200,200)
.fill(0xffffff);

// Add container that will hold our masked content
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
maskContainer.addChild(mask);
// Offset by the window's frame width
maskContainer.position.set(4,4);
// And add the container to the window!
frame.addChild(maskContainer);

// Create contents for the masked container
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
},
x:10
});

maskContainer.addChild(text);

// Add a ticker callback to scroll the text up and down
let elapsed = 0.0;
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;
});

There are two types of masks supported by PixiJS:

Use a Graphics object to create a mask with an arbitrary shape - powerful, but doesn't support anti-aliasing

Sprite: Use the alpha channel from a Sprite 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/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.

FilterDescription
AlphaFilterSimilar to setting alpha property, but flattens the Container instead of applying to children individually.
BlurFilterApply a blur effect
ColorMatrixFilterA color matrix is a flexible way to apply more complex tints or color transforms (e.g., sepia tone).
DisplacementFilterDisplacement maps create visual offset pixels, for instance creating a wavy water effect.
NoiseFilterCreate 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.

+ + + + \ No newline at end of file diff --git a/8.x/guides/components/graphics.html b/8.x/guides/components/graphics.html new file mode 100644 index 000000000..a22374708 --- /dev/null +++ b/8.x/guides/components/graphics.html @@ -0,0 +1,21 @@ + + + + + +Graphics | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Graphics

Graphics 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.

Check out the graphics example code.

Graphics Is About Building - Not Drawing

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:

// 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???

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 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.

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 Graphics class, but as a quick orientation, here's the list of basic primitives you can add:

  • Line
  • Rect
  • RoundRect
  • Circle
  • Ellipse
  • Arc
  • Bezier and Quadratic Curve

In addition, you have access to the following complex primitives:

  • Torus
  • Chamfer Rect
  • Fillet Rect
  • Regular Polygon
  • Star
  • Rounded Polygon

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!

 let mySvg = new Graphics()
.svg('M 100 350 q 150 -300 300 0');

The GraphicsContext

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:

// Create 5 circles
for (let i = 0; i < 5; i++) {
let circle = new Graphics()
.circle(100, 100, 50)
.fill('red');
}

Versus sharing a GraphicsContext:

// 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 circleContext
let duplicate = new Graphics(circleContext);
}

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!

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]);

// 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 class manages the list of geometry primitives created by the Graphics parent object. Graphics functions are literally passed through to the internal contexts:

let circleGraphics = new Graphics()
.circle(100, 100, 50)
.fill('red')

same as:

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

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 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.

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.

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.

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.

+ + + + \ No newline at end of file diff --git a/8.x/guides/components/interaction.html b/8.x/guides/components/interaction.html new file mode 100644 index 000000000..3bf2bc088 --- /dev/null +++ b/8.x/guides/components/interaction.html @@ -0,0 +1,21 @@ + + + + + +Interaction | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Interaction

PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.

Event Modes

The new event-based system that replaced InteractionManager from v6 has expanded the definition of what a Container means to be interactive. With this we have introduced eventMode which allows you to control how an object responds to interaction events. This is similar to the interactive property in v6 but with more options.

eventModeDescription
noneIgnores all interaction events, similar to CSS's pointer-events: none, good optimization for non-interactive children
passiveDoes not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. This is default eventMode for all containers
autoDoes not emit events and but is hit tested if parent is interactive. Same as interactive = false in v7
staticEmit events and is hit tested. Same as interaction = true in v7, useful for objects like buttons that do not move.
dynamicEmits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.

Event Types

PixiJS supports the following event types:

Event TypeDescription
pointercancelFired when a pointer device button is released outside the display object that initially registered a pointerdown.
pointerdownFired when a pointer device button is pressed on the display object.
pointerenterFired when a pointer device enters the display object.
pointerleaveFired when a pointer device leaves the display object.
pointermoveFired when a pointer device is moved while over the display object.
globalpointermoveFired when a pointer device is moved, regardless of hit-testing the current object.
pointeroutFired when a pointer device is moved off the display object.
pointeroverFired when a pointer device is moved onto the display object.
pointertapFired when a pointer device is tapped twice on the display object.
pointerupFired when a pointer device button is released over the display object.
pointerupoutsideFired when a pointer device button is released outside the display object that initially registered a pointerdown.
mousedown Fired when a mouse button is pressed on the display object.
mouseenterFired when the mouse cursor enters the display object.
mouseleaveFired when the mouse cursor leaves the display object.
mousemove Fired when the mouse cursor is moved while over the display object.
globalmousemoveFired when a mouse is moved, regardless of hit-testing the current object.
mouseout Fired when the mouse cursor is moved off the display object.
mouseover Fired when the mouse cursor is moved onto the display object.
mouseup Fired when a mouse button is released over the display object.
mouseupoutside Fired when a mouse button is released outside the display object that initially registered a mousedown.
click Fired when a mouse button is clicked (pressed and released) over the display object.
touchcancel Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchend Fired when a touch point is removed from the display object.
touchendoutside Fired when a touch point is removed outside of the display object that initially registered a touchstart.
touchmove Fired when a touch point is moved along the display object.
globaltouchmoveFired when a touch point is moved, regardless of hit-testing the current object.
touchstart Fired when a touch point is placed on the display object.
tap Fired when a touch point is tapped twice on the display object.
wheel Fired when a mouse wheel is spun over the display object.
rightclick Fired when a right mouse button is clicked (pressed and released) over the display object.
rightdown Fired when a right mouse button is pressed on the display object.
rightup Fired when a right mouse button is released over the display object.
rightupoutside Fired when a right mouse button is released outside the display object that initially registered a rightdown.

Enabling Interaction

Any Container-derived object (Sprite, Container, etc.) can become interactive simply by setting its eventMode property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior.

Check out the interaction example code.

To respond to clicks and taps, bind to the events fired on the object, like so:

let sprite = Sprite.from('/some/texture.png');
sprite.on('pointerdown', (event) => { alert('clicked!'); });
sprite.eventMode = 'static';

Check out the Container for the list of interaction events supported.

Checking if Object is Interactive

You can check if an object is interactive by calling the isInteractive property. This will return true if eventMode is set to static or dynamic.

if (sprite.isInteractive()) {
// sprite is interactive
}

Use Pointer Events

PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for both.

What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with either mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events.

Optimization

Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named interactiveChildren. If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to false and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set background.interactiveChildren = false for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain.

The EventSystem can also be customised to be more performant:

const app = new Application({
eventMode: 'passive',
eventFeatures: {
move: true,
/** disables the global move events which can be very expensive in large scenes */
globalMove: false,
click: true,
wheel: true,
}
});
+ + + + \ No newline at end of file diff --git a/8.x/guides/components/sprite-sheets.html b/8.x/guides/components/sprite-sheets.html new file mode 100644 index 000000000..39361f62f --- /dev/null +++ b/8.x/guides/components/sprite-sheets.html @@ -0,0 +1,21 @@ + + + + + +Spritesheets | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Spritesheets

Now that you understand basic sprites, it's time to talk about a better way to create them - the Spritesheet class.

A Spritesheet is a media format for more efficiently downloading and rendering Sprites. While somewhat more complex to create and use, they are a key tool in optimizing your project.

Anatomy of a Spritesheet

The basic idea of a spritesheet is to pack a series of images together into a single image, track where each source image ends up, and use that combined image as a shared BaseTexture for the resulting Sprites.

The first step is to collect the images you want to combine. The sprite packer then collects the images, and creates a new combined image.

As this image is being created, the tool building it keeps track of the location of the rectangle where each source image is stored. It then writes out a JSON file with that information.

These two files, in combination, can be passed into a SpriteSheet constructor. The SpriteSheet object then parses the JSON, and creates a series of Texture objects, one for each source image, setting the source rectangle for each based on the JSON data. Each texture uses the same shared BaseTexture as its source.

Doubly Efficient

SpriteSheets help your project in two ways.

First, by speeding up the loading process. While downloading a SpriteSheet's texture requires moving the same (or even slightly more!) number of bytes, they're grouped into a single file. This means that the user's browser can request and download far fewer files for the same number of Sprites. The number of files itself is a key driver of download speed, because each request requires a round-trip to the webserver, and browsers are limited to how many files they can download simultaneously. Converting a project from individual source images to shared sprite sheets can cut your download time in half, at no cost in quality.

Second, by improving batch rendering. WebGL rendering speed scales roughly with the number of draw calls made. Batching multiple Sprites, etc. into a single draw call is the main secret to how PixiJS can run so blazingly fast. Maximizing batching is a complex topic, but when multiple Sprites all share a common BaseTexture, it makes it more likely that they can be batched together and rendered in a single call.

Creating SpriteSheets

You can use a 3rd party tool to assemble your sprite sheet files. Here are two that may fit your needs:

ShoeBox: ShoeBox is a free, Adobe AIR-based sprite packing utility that is great for small projects or learning how SpriteSheets work.

TexturePacker: TexturePacker is a more polished tool that supports advanced features and workflows. A free version is available which has all the necessary features for packing spritesheets for PixiJS. It's a good fit for larger projects and professional game development, or projects that need more complex tile mapping features.

Spritesheet data can also be created manually or programmatically, and supplied to a new AnimatedSprite. This may be an easier option if your sprites are already contained in a single image.

// Create object to store sprite sheet data
const atlasData = {
frames: {
enemy1: {
frame: { x: 0, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
enemy2: {
frame: { x: 32, y:0, w:32, h:32 },
sourceSize: { w: 32, h: 32 },
spriteSourceSize: { x: 0, y: 0, w: 32, h: 32 }
},
},
meta: {
image: 'images/spritesheet.png',
format: 'RGBA8888',
size: { w: 128, h: 32 },
scale: 1
},
animations: {
enemy: ['enemy1','enemy2'] //array of frames by name
}
}


// Create the SpriteSheet from data and image
const spritesheet = new Spritesheet(
Texture.from(atlasData.meta.image),
atlasData
);

// Generate all the Textures asynchronously
await spritesheet.parse();

// spritesheet is ready to use!
const anim = new AnimatedSprite(spritesheet.animations.enemy);

// set the animation speed
anim.animationSpeed = 0.1666;
// play the animation on a loop
anim.play();
// add it to the stage to render
app.stage.addChild(anim);
+ + + + \ No newline at end of file diff --git a/8.x/guides/components/sprites.html b/8.x/guides/components/sprites.html new file mode 100644 index 000000000..46b5dbd78 --- /dev/null +++ b/8.x/guides/components/sprites.html @@ -0,0 +1,21 @@ + + + + + +Sprites | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Sprites

Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.

Creating Sprites

To create a Sprite, all you need is a Texture (check out the Texture guide). Load a PNG's URL using the Assets class, then call Sprite.from(url) and you're all set. Unlike v7 you now must load your texture before using it, this is to ensure best practices.

Check out the sprite example code.

Using Sprites

In our Container guide, we learned about the Container class and the various properties it defines. Since Sprite objects are also containers, you can move a sprite, rotate it, and update any other display property.

Alpha, Tint and Blend Modes

Alpha is a standard display object property. You can use it to fade sprites into the scene by animating each sprite's alpha from 0.0 to 1.0 over a period of time.

Tinting allows you multiply the color value of every pixel by a single color. For example, if you had a dungeon game, you might show a character's poison status by setting obj.tint = 0x00FF00, which would give a green tint to the character.

Blend modes change how pixel colors are added to the screen when rendering. The three main modes are add, which adds each pixel's RGB channels to whatever is under your sprite (useful for glows and lighting), multiply which works like tint, but on a per-pixel basis, and screen, which overlays the pixels, brightening whatever is underneath them.

Scale vs Width & Height

One common area of confusion when working with sprites lies in scaling and dimensions. The Container class allows you to set the x and y scale for any object. Sprites, being Containers, also support scaling. In addition, however, Sprites support explicit width and height attributes that can be used to achieve the same effect, but are in pixels instead of a percentage. This works because a Sprite object owns a Texture, which has an explicit width and height. When you set a Sprite's width, internally PixiJS converts that width into a percentage of the underlying texture's width and updates the object's x-scale. So width and height are really just convenience methods for changing scale, based on pixel dimensions rather than percentages.

Pivot vs Anchor

If you add a sprite to your stage and rotate it, it will by default rotate around the top-left corner of the image. In some cases, this is what you want. In many cases, however, what you want is for the sprite to rotate around the center of the image it contains, or around an arbitrary point.

There are two ways to achieve this: pivots and anchors

An object's pivot is an offset, expressed in pixels, from the top-left corner of the Sprite. It defaults to (0, 0). If you have a Sprite whose texture is 100px x 50px, and want to set the pivot point to the center of the image, you'd set your pivot to (50, 25) - half the width, and half the height. Note that pivots can be set outside of the image, meaning the pivot may be less than zero or greater than the width/height. This can be useful in setting up complex animation hierarchies, for example. Every Container has a pivot.

An anchor, in contrast, is only available for Sprites. Anchors are specified in percentages, from 0.0 to 1.0, in each dimension. To rotate around the center point of a texture using anchors, you'd set your Sprite's anchor to (0.5, 0.5) - 50% in width and height. While less common, anchors can also be outside the standard 0.0 - 1.0 range.

The nice thing about anchors is that they are resolution and dimension agnostic. If you set your Sprite to be anchored in the middle then later change the size of the texture, your object will still rotate correctly. If you had instead set a pivot using pixel-based calculations, changing the texture size would require changing your pivot point.

So, generally speaking, you'll want to use anchors when working with Sprites.

One final note: unlike CSS, where setting the transform-origin of the image doesn't move it, in PixiJS setting an anchor or pivot will move your object on the screen. In other words, setting an anchor or pivot affects not just the rotation origin, but also the position of the sprite relative to its parent.

+ + + + \ No newline at end of file diff --git a/8.x/guides/components/text.html b/8.x/guides/components/text.html new file mode 100644 index 000000000..b922a6f57 --- /dev/null +++ b/8.x/guides/components/text.html @@ -0,0 +1,21 @@ + + + + + +Text | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Text

Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.

Let's dig into how this works.

There Are Three Kinds of Text

Because of the challenges of working with text in WebGL, PixiJS provides three very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance.

The Text Object

In order to draw text to the screen, you use a Text object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high.

So when working with Text objects, there are two sets of options - standard display object options like position, rotation, etc that work after the text is rasterized internally, and text style options that are used while rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled.

Check out the text example code.

Text Styles

There are a lot of text style options available (see TextStyle), but they break down into 5 main groups:

Font: fontFamily to select the webfont to use, fontSize to specify the size of the text to draw, along with options for font weight, style and variant.

Appearance: Set the color with fill or add a stroke outline, including options for gradient fills.

Drop-Shadows: Set a drop-shadow with dropShadow, with a host of related options to specify offset, blur, opacity, etc.

Layout: Enable with wordWrap and wordWrapWidth, and then customize the lineHeight and align or letterSpacing

Utilities: Add padding or trim extra space to deal with funky font families if needed.

To interactively test out feature of Text Style, check out this tool.

Loading and Using Fonts

In order for PixiJS to build a Text object, you'll need to make sure that the font you want to use is loaded by the browser. This can be easily accomplished with our good friends Assets

// load the fonts
await Assets.load('short-stack.woff2');

// now they can be used!

const text = new Text({
text:'hello',
style:{
fontFamily:'short-stack'
}
})

Caveats and Gotchas

While PixiJS does make working with text easy, there are a few things you need to watch out for.

First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes.

Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp.

BitmapText

In addition to the standard Text approach to adding text to your project, PixiJS also supports bitmap fonts. Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image.

The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture.

Check out the bitmap text example code.

BitmapFont

  • 3rd party solutions
  • BitmapFont.from auto-generation

Selecting the Right Approach

Text

  • Static text
  • Small number of text objects
  • High fidelity text rendering (kerning e.g.)
  • Text layout (line & letter spacing)

BitmapText

  • Dynamic text
  • Large number of text objects
  • Lower memory

HTMLText

  • Static text
  • Need that HTML formatting
+ + + + \ No newline at end of file diff --git a/8.x/guides/components/textures.html b/8.x/guides/components/textures.html new file mode 100644 index 000000000..1e2f4db8c --- /dev/null +++ b/8.x/guides/components/textures.html @@ -0,0 +1,21 @@ + + + + + +Textures | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

Textures

We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures.

In PixiJS, textures are one of the core resources used by display objects. A texture, broadly speaking, represents a source of pixels to be used to fill in an area on the screen. The simplest example is a sprite - a rectangle that is completely filled with a single texture. But things can get much more complex.

Life-cycle of a Texture

Let's examine how textures really work, by following the path your image data travels on its way to the screen.

Here's the flow we're going to follow: Source Image > Loader > BaseTexture > Texture

Serving the Image

To start with, you have the image you want to display. The first step is to make it available on your server. This may seem obvious, but if you're coming to PixiJS from other game development systems, it's worth remembering that everything has to be loaded over the network. If you're developing locally, please be aware that you must use a webserver to test, or your images won't load due to how browsers treat local file security.

Loading the Image

To work with the image, the first step is to pull the image file from your webserver into the user's web browser. To do this, we can use Assets.load('myTexture.png'). Assets wraps and deals with telling the browser to fetch the image, convert it and then let you when that has been completed. This process is asynchronous - you request the load, then time passes, then a proimise completes to let you know the load is completed. We'll go into the loader in a lot more depth in a later guide.

const texture = await Assets.load('myTexture.png');

// pass a texture explicitly
const sprite = new Sprite(texture);
// as options
const sprite2 = new Sprite({texture});
// from the cache as the texture is loaded
const sprite3 = Sprite.from('myTexture.png')

TextureSources Own the Data

Once the texture has loaded, the loaded <IMG> element contains the pixel data we need. But to use it to render something, PixiJS has to take that raw image file and upload it to the GPU. This brings us to the real workhorse of the texture system - the TextureSource class. Each TextureSource manages a single pixel source - usually an image, but can also be a Canvas or Video element. TextureSources allow PixiJS to convert the image to pixels and use those pixels in rendering. In addition, it also contains settings that control how the texture data is rendered, such as the wrap mode (for UV coordinates outside the 0.0-1.0 range) and scale mode (used when scaling a texture).

TextureSource are automatically cached, so that calling Texture.from() repeatedly for the same URL returns the same TextureSource each time. Destroying a TextureSource frees the image data associated with it.

Textures are a View on BaseTextures

So finally, we get to the Texture class itself! At this point, you may be wondering what the Texture object does. After all, the BaseTexture manages the pixels and render settings. And the answer is, it doesn't do very much. Textures are light-weight views on an underlying BaseTexture. Their main attribute is the source rectangle within the TextureSource from which to pull.

If all PixiJS drew were sprites, that would be pretty redundant. But consider SpriteSheets. A SpriteSheet is a single image that contains multiple sprite images arranged within. In a Spritesheet object, a single TextureSource is referenced by a set of Textures, one for each source image in the original sprite sheet. By sharing a single TextureSource, the browser only downloads one file, and our batching renderer can blaze through drawing sprites since they all share the same underlying pixel data. The SpriteSheet's Textures pull out just the rectangle of pixels needed by each sprite.

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.

here's a quick cheat sheet of one good solution:

  1. Show a loading image
  2. Use Assets 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 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 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 <VIDEO> element to TextureSource.from() to allow you to display video in your project. Since it's a texture, you can tint it, add filters, or even apply it to custom geometry.

Canvas: Similarly, you can wrap an HTML5 <CANVAS> element in a BaseTexture to let you use canvas's drawing methods to dynamically create a texture.

SVG: Pass in an <SVG> element or load a .svg URL, and PixiJS will attempt to rasterize it. For highly network-constrained projects, this can allow for beautiful graphics with minimal network load times.

RenderTexture: A more advanced (but very powerful!) feature is to build a Texture from a RenderTexture. This can allow for building complex geometry using a Geometry object, then baking that geometry down to a simple texture.

Each of these texture sources has caveats and nuances that we can't cover in this guide, but they should give you a feeling for the power of PixiJS's texture system.

Check out the render texture example code.

+ + + + \ No newline at end of file diff --git a/8.x/guides/migrations/v5.html b/8.x/guides/migrations/v5.html new file mode 100644 index 000000000..51fd00ad1 --- /dev/null +++ b/8.x/guides/migrations/v5.html @@ -0,0 +1,21 @@ + + + + + +v5 Migration Guide | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

v5 Migration Guide

This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we've try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.

🚧 API Changes

Making WebGL First-Class

PixiJS v5 has made WebGL the first-class renderer and made CanvasRenderer to be second-class. Functionally, there's not much that changed from v4, but there are a bunch of subtle internal naming changes which could trip-up some developers upgrading to v5. For instance:

  • WebGLRenderer becomes Renderer
  • renderWebGL becomes render (in DisplayObject, Sprite, Container, etc)
  • _renderWebGL becomes _render (in DisplayObject, Container, etc)

If you created a plugin or project that previously used render on a Container (see #5510), this will probably cause your project to not render correctly. Please consider renaming your user-defined render to something else. In most other cases, you'll get a deprecation warning trying to invoke WebGL-related classes or methods, e.g., new PIXI.WebGLRenderer().

Renderer Parameters

Specifying options as a third parameter in Renderer constructor is officially dropped (same with PIXI.Application, PIXI.autoDetectRenderer & PIXI.CanvasRenderer). In v4 we supported two function signatures, but in v5 we dropped width, height, options signature. Please add width and height to options.

const renderer = new PIXI.Renderer(800, 600, { transparent: true }); // bad
const renderer = new PIXI.Renderer({ width: 800, height: 600, transparent: true }); // good
  • Note: Adding transparent: true in Renderer or Application constructor options might help with strange artifacts on some devices, but it might reduce FPS. It's much better than preserveDrawingBuffer: true.

  • If you need the v4 default behavior of resizing the canvas using css pixels, add autoDensity: true to the options.

Not everything went to params. To enable WebGL1 even if WebGL2 is available, use

PIXI.settings.PREFER_ENV = PIXI.ENV.WEBGL;

Mesh, Plane, Rope

PixiJS v5 introduces a new class called PIXI.Mesh. This allows overriding the default shader and the ability to add more attributes to geometry. For example, you can add colors to vertices.

The old v4 Mesh class has moved from PIXI.mesh.Mesh to PIXI.SimpleMesh, it extends PIXI.Mesh.

PIXI.mesh.Rope, PIXI.mesh.Plane, PIXI.mesh.NineSlicePlane have moved to PIXI.SimpleRope, PIXI.SimplePlane and PIXI.NineSlicePlane respectively.

If you used custom shaders or generated meshes in v4, you might be impacted by these changes in v5.

PIXI.SimpleMesh fields vertices, uvs, indices are wrapped inside mesh.geometry attribute buffers. For example, this is how access to buffers provided through mesh.uvBuffer property:

get uvBuffer()
{
return this.geometry.buffers[1];
}

The indices property shortcut is also missing, but you can access the data inside mesh.geometry.indexBuffer.

You can override buffer data, and notify it that data was changed, in this case buffer will be uploaded to GPU lazily. Previously in v4 mesh had several flags that indicated which attributes have to be updated and their names confused people.

Graphics Holes

Drawing holes in Graphics was very limited in v4. This only supported non-Shape drawing, like using lineTo, bezierCurveTo, etc. In v5, we improved the hole API by supporting shapes. Unfortunately, there's no deprecation strategy to support the v4 API. For instance, in v4:

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.moveTo(0, 0)
.lineTo(100, 0)
.lineTo(100, 100)
.lineTo(0, 100)
.moveTo(10, 10)
.lineTo(90, 10)
.lineTo(90, 90)
.lineTo(10, 90)
.addHole();

Live example in v4.x

In v5, Graphics has simplified and the API changed from addHole to beginHole and endHole.

const graphic = new PIXI.Graphics()
.beginFill(0xff0000)
.drawRect(0, 0, 100, 100)
.beginHole()
.drawCircle(50, 50, 30)
.endHole();

Live example in dev

Filter Padding

In v4 filters had a default padding of 4 and in v5 this has been changed to a default of 0. This can cause some filters to look broken when used. To fix this issue simply add some padding to the filters you create.

// Glow filter from https://github.com/pixijs/pixi-filters
const filter = new PIXI.filters.GlowFilter();
filter.padding = 4;

Some filters, like BlurFilter, automatically calculate the padding so changes may not be necessary.

Filter Default Vertex Shader

We reorganized all uniforms dedicated to coordinate system transforms, and renamed them. If your filter doesn't work anymore, check if you use default vertex shader. In that case, you can use old v4 vertex shader code.

All changes are explained in [[Creating Filters|v5-Creating-filters]]

Enable Mipmapping for RenderTexture

Previously, you may have ended up with code like this in v4 (specifically if you saw Ivan's comment/JSFiddle):

const renderer = PIXI.autoDetectRenderer();
renderer.bindTexture(baseRenderTex, false, 0);
const glTex = baseRenderTex._glTextures[renderer.CONTEXT_UID];
glTex.enableMipmap(); // this is what actually generates mipmaps in WebGL
glTex.enableLinearScaling(); // this is what tells WebGL to USE those mipmaps

In v5, this code is no longer needed.

BaseTexture Resources

One of the newest features in v5 is that we decoupled all the asset-specific functionality from BaseTexture. We created a new system called "resources" and each BaseTexture now has a resource that wraps some specific asset type. For instance: VideoResource, SVGResource, ImageResource, CanvasResource. In the future, we hope to be able to add other resource types. If there were asset-specific methods or properties being called before, these will probably be on baseTexture.resource.

Also, we removed all of the from* methods from BaseTexture, so you just can call BaseTexture.from and pass in whatever resource. Please see docs for more information about from.

const canvas = document.createElement('canvas');
const baseTexture = PIXI.BaseTexture.from(canvas);

That API also allows to use pure WebGL and 2d context calls, see the gradient example.

BaseTexture.source

Has been moved to baseTexture.resource.source, moved into resource corresponding to the baseTexture. baseTexture.resource does not exist for RenderTexture, and source does not exist for resources that dont have source.

Graphics Interaction

If you use transparent interactive graphics trick, make sure that you use specify alpha=0 for all element, not for its parts. How PixiJS deals with shapes that have alpha=0 is considered undefined behaviour. We might change it back, but we have no guarantees about it.

graphics.beginFill(0xffffff, 0.0); //bad
graphics.alpha = 0; //good

📦 Publishing Changes

Canvas Becomes Legacy

Since WebGL and WebGL2 are now first-class, we have removed the canvas-based fallback from the default pixi.js package. If you need CanvasRenderer, you should switch to use pixi.js-legacy instead.

import * as PIXI from "pixi.js";
// Will NOT return CanvasRenderer because canvas-based
// functionality was removed from "pixi.js"
const renderer = PIXI.autoDetectRenderer(); // return PIXI.Renderer or throws error

Instead, use the legacy bundle to have access to the canvas rendering.

import * as PIXI from "pixi.js-legacy";
const renderer = PIXI.autoDetectRenderer(); // returns PIXI.Renderer or PIXI.CanvasRenderer

Bundling Changes

If you're using Rollup, Parcel or another bundler to add PixiJS into your project there are a few subtle changes when moving to v5. Namely, the global PIXI object is no longer created automatically. This was removed from bundling for two purpose: 1) to improve tree-shaking for bundlers, and 2) for security purpose by protecting PIXI.

This is no longer a valid way to import:

import "pixi.js";
const renderer = PIXI.autoDetectRenderer(); // INVALID! No more global.PIXI!

Instead, you should import as a namespace or individual elements:

import * as PIXI from "pixi.js";
const renderer = PIXI.autoDetectRenderer();

// or even better:
import { autoDetectRenderer } from "pixi.js";
const renderer = autoDetectRenderer();

Lastly, some 3rd-party plugins maybe expecting window.PIXI, so you might have to explicitly expose the global like this, however this is not recommended.

import * as PIXI from 'pixi.js';
window.PIXI = PIXI; // some bundlers might prefer "global" instead of "window"

Webpack

When Webpack and 3rd-party plugins, like pixi-spine, you might have difficulties building the global PIXI object resulting in a runtime error ReferenceError: PIXI is not defined. Usually this can be resolved by using Webpack shimming globals.

For instance, here's your import code:

import * as PIXI from 'pixi.js';
import 'pixi-spine'; // or other plugins that need global 'PIXI' to be defined first

Add a plugins section to your webpack.config.js to let know Webpack that the global PIXI variable make reference to pixi.js module. For instance:

const webpack = require('webpack');

module.exports = {
entry: '...',
output: {
...
},
plugins: [
new webpack.ProvidePlugin({
PIXI: 'pixi.js'
})
]
}
+ + + + \ No newline at end of file diff --git a/8.x/guides/migrations/v6.html b/8.x/guides/migrations/v6.html new file mode 100644 index 000000000..62c55f73a --- /dev/null +++ b/8.x/guides/migrations/v6.html @@ -0,0 +1,21 @@ + + + + + +v6 Migration Guide | PixiJS + + + + + + + + + +
+
Skip to main content
Version: v8.x

v6 Migration Guide

PixiJS 6 comes with few surface-level breaking changes. This document is not complete.

Typings

If you're using TypeScript, make sure the follow is added to your tsconfig.json:

{
"compilerOptions": {
"moduleResolution": "node",
// Required for importing 3rd-party dependencies like EventEmitter3
"esModuleInterop": true
}
}

Mesh Internals

If you ever overrode Mesh._renderDefault to take into account more uniforms like this: v5 Reference

if (shader.program.uniformData.translationMatrix)
{
shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
}

Remove the if, leave the contents, otherwise you might not get correct sync uniform for translationMatrix, or even worse - get null pointer. v6 Reference.

shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);
+ + + + \ No newline at end of file diff --git a/guides/migrations/v7.html b/8.x/guides/migrations/v7.html similarity index 60% rename from guides/migrations/v7.html rename to 8.x/guides/migrations/v7.html index 40f2da862..42e941682 100644 --- a/guides/migrations/v7.html +++ b/8.x/guides/migrations/v7.html @@ -3,21 +3,21 @@ -v7 Migration Guide | PixiJS +v7 Migration Guide | PixiJS - - + +
-
Skip to main content

v7 Migration Guide

First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn't really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.

👋 Dropping Internet Explorer

Microsoft officially ended support for IE, so we decided to follow. It simplified many of our modernizations since IE was an outliner from Safari/Chrome/Firefox/Edge and mobile browsers. If you need support for IE, please consider using Babel or some other trans-piling tool.

🗑️ Remove Polyfills

We removed the bundled polyfills such as requestAnimationFrame and Promise. These things are widely available in browsers now. If projects require them, developers should include the polyfills they need for backward-compatibility. Please check out polyfill.io.

💬 Output ES2020 (modules) and ES2017 (browser)

PixiJS historically only published ES5 (no classes!). A new output standard allows us to use ES2017 features that previously we couldn't use (e.g., String.prototype.startsWith, Array.prototype.contains, etc). Not only does it make the code more readable, but the output looks nicer as well. For modules we are outputting ES2020, which contains syntax like nullish coalescing (??). If your project needs to have backward compatibility, you can use Babel to transpile or polyfill.

🐭 Replaces InteractionManager with EventSystem

InteractionManager was getting complex and difficult to maintain. Few core team members understood the code. We decided to move to FederatedEvents, which is concise, better aligned with the DOM, and supports things like bubbling. The good news, is you shouldn't have to change code, as it is largely a drop-in replacement. We added addEventListener and removeEventListener APIs to DisplayObject which have the same DOM signature and can be used instead of on and off.

📦 Replaces Loader with Assets

Similarly, we've been wanting to remove the Loader because of its legacy approach (e.g., XMLHttpRequest). This was forked from resource-loader that has been with PixiJS for a long time. The original design inspiration for Loader was driven largely by Flash/AS3, which now seem dated. There were a few things we wanted out of a new iteration: static loading, loading with Workers, background loading, Promise-based, fewer layers of caching. Here's a quick example of how this will change:

import { Loader, Sprite } from 'pixi.js';

const loader = new Loader();
loader.add('background', 'path/to/assets/background.jpg');
loader.load((loader, resources) => {
const image = Sprite.from(resources.background.texture);
});

Now becomes:

import { Assets, Sprite } from 'pixi.js';

const texture = await Assets.load('path/to/assets/background.jpg');
const image = Sprite.from(texture);

🤝 Abandon the use of peerDependencies

PixiJS heavily uses peerDependencies in the package.json within each package. This design choice has plagued Pixi with many issues. It's a breaking change to remove, so now was a good time. We have decided to completely remove peerDependencies, instead opting for nothing. This should make installing and upgrading pixi.js much easier. We are working on updating our tooling for composing a custom version with packages. Edit: As of 7.2.0, we have reverted this change to keep compatibility with some module-based CDNs.

👂 Other Changes

  • Browser builds have been removed for all packages, with the exception of pixi.js and pixi.js-legacy.
  • Removes Graphics.nextRoundedRectBehavior this is now the default behavior
  • Removes Text.nextLineHeightBehavior this is now the default behavior
  • AbstractBatchRenderer and BatchPluginFactory has been removed. Either extends BatchRenderer or use setShaderGenerator on the default BatchRenderer, (e.g., renderer.plugins.batch)
  • BatchRenderer is installed by default in @pixi/core, no need to Renderer.registerPlugin('batch', BatchRenderer) anymore

Exports from @pixi/core

The @pixi/core package now depends and re-exports the following packages.

  • @pixi/math
  • @pixi/contants
  • @pixi/utils
  • @pixi/runner
  • @pixi/settings
  • @pixi/ticker

While some packages will still work when installed directly, others will not, since by installing them alongside @pixi/core you will be effectively importing two copies of the same code.  +

Version: v8.x

v7 Migration Guide

First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn't really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.

👋 Dropping Internet Explorer

Microsoft officially ended support for IE, so we decided to follow. It simplified many of our modernizations since IE was an outliner from Safari/Chrome/Firefox/Edge and mobile browsers. If you need support for IE, please consider using Babel or some other trans-piling tool.

🗑️ Remove Polyfills

We removed the bundled polyfills such as requestAnimationFrame and Promise. These things are widely available in browsers now. If projects require them, developers should include the polyfills they need for backward-compatibility. Please check out polyfill.io.

💬 Output ES2020 (modules) and ES2017 (browser)

PixiJS historically only published ES5 (no classes!). A new output standard allows us to use ES2017 features that previously we couldn't use (e.g., String.prototype.startsWith, Array.prototype.contains, etc). Not only does it make the code more readable, but the output looks nicer as well. For modules we are outputting ES2020, which contains syntax like nullish coalescing (??). If your project needs to have backward compatibility, you can use Babel to transpile or polyfill.

🐭 Replaces InteractionManager with EventSystem

InteractionManager was getting complex and difficult to maintain. Few core team members understood the code. We decided to move to FederatedEvents, which is concise, better aligned with the DOM, and supports things like bubbling. The good news, is you shouldn't have to change code, as it is largely a drop-in replacement. We added addEventListener and removeEventListener APIs to DisplayObject which have the same DOM signature and can be used instead of on and off.

📦 Replaces Loader with Assets

Similarly, we've been wanting to remove the Loader because of its legacy approach (e.g., XMLHttpRequest). This was forked from resource-loader that has been with PixiJS for a long time. The original design inspiration for Loader was driven largely by Flash/AS3, which now seem dated. There were a few things we wanted out of a new iteration: static loading, loading with Workers, background loading, Promise-based, fewer layers of caching. Here's a quick example of how this will change:

import { Loader, Sprite } from 'pixi.js';

const loader = new Loader();
loader.add('background', 'path/to/assets/background.jpg');
loader.load((loader, resources) => {
const image = Sprite.from(resources.background.texture);
});

Now becomes:

import { Assets, Sprite } from 'pixi.js';

const texture = await Assets.load('path/to/assets/background.jpg');
const image = Sprite.from(texture);

🤝 Abandon the use of peerDependencies

PixiJS heavily uses peerDependencies in the package.json within each package. This design choice has plagued Pixi with many issues. It's a breaking change to remove, so now was a good time. We have decided to completely remove peerDependencies, instead opting for nothing. This should make installing and upgrading pixi.js much easier. We are working on updating our tooling for composing a custom version with packages. Edit: As of 7.2.0, we have reverted this change to keep compatibility with some module-based CDNs.

👂 Other Changes

  • Browser builds have been removed for all packages, with the exception of pixi.js and pixi.js-legacy.
  • Removes Graphics.nextRoundedRectBehavior this is now the default behavior
  • Removes Text.nextLineHeightBehavior this is now the default behavior
  • AbstractBatchRenderer and BatchPluginFactory has been removed. Either extends BatchRenderer or use setShaderGenerator on the default BatchRenderer, (e.g., renderer.plugins.batch)
  • BatchRenderer is installed by default in @pixi/core, no need to Renderer.registerPlugin('batch', BatchRenderer) anymore

Exports from @pixi/core

The @pixi/core package now depends and re-exports the following packages.

  • @pixi/math
  • @pixi/contants
  • @pixi/utils
  • @pixi/runner
  • @pixi/settings
  • @pixi/ticker

While some packages will still work when installed directly, others will not, since by installing them alongside @pixi/core you will be effectively importing two copies of the same code.  This will lead to errors where changing settings from @pixi/settings doesn't do anything since @pixi/core has its own version of that package. -It is recommended that you uninstall these from your project and use @pixi/core instead.

import { Rectangle } from '@pixi/math';
import { settings } from '@pixi/settings';
import { ALPHA_MODES } from '@pixi/constants';
import { string2hex } from '@pixi/utils';

Now becomes:

import { Rectangle, settings, ALPHA_MODES, utils } from '@pixi/core';

const { string2hex } = utils;

Extract and Prepare Systems

Extract and prepare plugins have been converted to Renderer "systems".

renderer.plugins.extract
renderer.plugins.prepare

Now becomes:

renderer.extract
renderer.prepare

Extensions Self-Install

Extensions now install themselves, so you should only need to import the class in order to use. For example, in v6:

import { AccessibilityManager } from '@pixi/accessibility';
import { extensions } from '@pixi/core';
extensions.add(AccessibilityManager);

Now becomes:

import '@pixi/accessibility';

Using hitTest with Events

With the new events system, one of the common APIs that changed is `hitTest.

import {Application} from 'pixi.js';

const app = new Application();
app.renderer.plugins.interaction.hitTest({x, y});

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const boundary = new EventBoundary(app.stage);
boundary.hitTest(x, y);

New Async Extract Methods

The following methods are now async and return a Promise.

  • CanvasExtract.base64()
  • CanvasExtract.image()
  • Extract.base64()
  • Extract.image()
import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = app.renderer.extract.base64();

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = await app.renderer.extract.base64();

Interactive Move Events

Interaction events in PixiJS now behave like the DOM in v7. This was intentional to align around behavior that would be familiar with developers, but obviously impacts the behavior with pointermove, mousemove, and touchmove.

Like the DOM, move events are now local. This means that if you are outside the bounds of the object, you will not receive a move event. Generally, you should consider adding move events to the stage or parent instead of the DisplayObject itself.

Working example: https://jsfiddle.net/bigtimebuddy/spnv4wm6/

Interactive Property Handlers are Removed

Property-based handlers were removed from events. This was a feature of the old InteractionManager. For instance:

sprite.pointertap = () => {
// handler the pointertap
};

Now becomes:

sprite.on('pointertap', () => {
// handler the pointertap
});

Property buttonMode has been removed

The property buttonMode was a convenience for toggling the cursor property between pointer and null. It has now been removed.

sprite.buttonMode = true;

Now becomes:

sprite.cursor = 'pointer';

If you would like to re-add this functionality, you can patch DisplayObject's prototype:

import { DisplayObject } from 'pixi.js';

Object.defineProperty(DisplayObject.prototype, 'buttonMode', {
get() { return this.cursor === 'pointer'; },
set(value) { this.cursor = value ? 'pointer' : null; },
});

☝️ Suggestions for Upgrading

If you're planning on transitioning your code from v6, it would be helpful to implement some of the more dramatic changes in v6 first before upgrading to v7:

import { InteractionManager, extensions, Application } from 'pixi.js';
import { EventSystem } from '@pixi/events';

// Uninstall interaction
extensions.remove(InteractionManager);

// Create the renderer or application
const app = new Application();

// Install events
app.renderer.addSystem(EventSystem, 'events');
  • Switch to the Assets package by installing @pixi/assets and swapping for Loader. For more information on implementing Assets, see this guide.
  • Set Graphics.nextRoundedRectBehavior = true, this uses arcs for corner radius instead of bezier curves.
  • Set Text.nextLineHeightBehavior = true, this defaults to the DOM-like behavior for line height.

🏗️ Plugin Supported

PluginCompatiblePlugin Version Supported
PixiJS Soundv5.0.0+
PixiJS HTMLTextv3.0.0+
PixiJS Filtersv5.0.0+
PixiJS GIFv2.0.0+
PixiJS Spinev4.0.0+
PixiJS Particle Emitterv5.0.8+
PixiJS Animate
PixiJS Layersv2.0.0+
PixiJS Lightsv4.0.0+
PixiJS Graphics Smoothv1.0.0+
PixiJS Tilemap
- - +It is recommended that you uninstall these from your project and use @pixi/core instead.

import { Rectangle } from '@pixi/math';
import { settings } from '@pixi/settings';
import { ALPHA_MODES } from '@pixi/constants';
import { string2hex } from '@pixi/utils';

Now becomes:

import { Rectangle, settings, ALPHA_MODES, utils } from '@pixi/core';

const { string2hex } = utils;

Extract and Prepare Systems

Extract and prepare plugins have been converted to Renderer "systems".

renderer.plugins.extract
renderer.plugins.prepare

Now becomes:

renderer.extract
renderer.prepare

Extensions Self-Install

Extensions now install themselves, so you should only need to import the class in order to use. For example, in v6:

import { AccessibilityManager } from '@pixi/accessibility';
import { extensions } from '@pixi/core';
extensions.add(AccessibilityManager);

Now becomes:

import '@pixi/accessibility';

Using hitTest with Events

With the new events system, one of the common APIs that changed is `hitTest.

import {Application} from 'pixi.js';

const app = new Application();
app.renderer.plugins.interaction.hitTest({x, y});

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const boundary = new EventBoundary(app.stage);
boundary.hitTest(x, y);

New Async Extract Methods

The following methods are now async and return a Promise.

  • CanvasExtract.base64()
  • CanvasExtract.image()
  • Extract.base64()
  • Extract.image()
import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = app.renderer.extract.base64();

Now becomes:

import {Application, EventBoundary} from 'pixi.js';

const app = new Application();
const dataUri = await app.renderer.extract.base64();

Interactive Move Events

Interaction events in PixiJS now behave like the DOM in v7. This was intentional to align around behavior that would be familiar with developers, but obviously impacts the behavior with pointermove, mousemove, and touchmove.

Like the DOM, move events are now local. This means that if you are outside the bounds of the object, you will not receive a move event. Generally, you should consider adding move events to the stage or parent instead of the DisplayObject itself.

Working example: https://jsfiddle.net/bigtimebuddy/spnv4wm6/

Interactive Property Handlers are Removed

Property-based handlers were removed from events. This was a feature of the old InteractionManager. For instance:

sprite.pointertap = () => {
// handler the pointertap
};

Now becomes:

sprite.on('pointertap', () => {
// handler the pointertap
});

Property buttonMode has been removed

The property buttonMode was a convenience for toggling the cursor property between pointer and null. It has now been removed.

sprite.buttonMode = true;

Now becomes:

sprite.cursor = 'pointer';

If you would like to re-add this functionality, you can patch DisplayObject's prototype:

import { DisplayObject } from 'pixi.js';

Object.defineProperty(DisplayObject.prototype, 'buttonMode', {
get() { return this.cursor === 'pointer'; },
set(value) { this.cursor = value ? 'pointer' : null; },
});

☝️ Suggestions for Upgrading

If you're planning on transitioning your code from v6, it would be helpful to implement some of the more dramatic changes in v6 first before upgrading to v7:

import { InteractionManager, extensions, Application } from 'pixi.js';
import { EventSystem } from '@pixi/events';

// Uninstall interaction
extensions.remove(InteractionManager);

// Create the renderer or application
const app = new Application();

// Install events
app.renderer.addSystem(EventSystem, 'events');
  • Switch to the Assets package by installing @pixi/assets and swapping for Loader. For more information on implementing Assets, see this guide.
  • Set Graphics.nextRoundedRectBehavior = true, this uses arcs for corner radius instead of bezier curves.
  • Set Text.nextLineHeightBehavior = true, this defaults to the DOM-like behavior for line height.

🏗️ Plugin Supported

PluginCompatiblePlugin Version Supported
PixiJS Soundv5.0.0+
PixiJS HTMLTextv3.0.0+
PixiJS Filtersv5.0.0+
PixiJS GIFv2.0.0+
PixiJS Spinev4.0.0+
PixiJS Particle Emitterv5.0.8+
PixiJS Animate
PixiJS Layersv2.0.0+
PixiJS Lightsv4.0.0+
PixiJS Graphics Smoothv1.0.0+
PixiJS Tilemap
+ + \ No newline at end of file diff --git a/guides/migrations/v8.html b/8.x/guides/migrations/v8.html similarity index 84% rename from guides/migrations/v8.html rename to 8.x/guides/migrations/v8.html index f169f8c8b..7b7086ea3 100644 --- a/guides/migrations/v8.html +++ b/8.x/guides/migrations/v8.html @@ -3,23 +3,23 @@ -v8 Migration Guide | PixiJS +v8 Migration Guide | PixiJS - - + +
-
Skip to main content

v8 Migration Guide

Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.

Table of Contents

  1. Introduction
  2. Breaking Changes
  3. Deprecated Features
  4. Resources

1. Introduction

PixiJS v8 introduces several exciting changes and improvements that dramatically enhance the performance of the renderer. While we've made efforts to keep the migration process as smooth as possible, some breaking changes are inevitable. This guide will walk you through the necessary steps to migrate your PixiJS v7 project to PixiJS v8.

2. Breaking Changes

Before diving into the migration process, let's review the breaking changes introduced in PixiJS v8. Make sure to pay close attention to these changes as they may impact your existing codebase.

New Package Structure

Since version 5, PixiJS has utilized individual sub-packages to organize its codebase into smaller units. However, this approach led to issues, such as conflicting installations of different PixiJS versions, causing complications with internal caches.

In v8, PixiJS has reverted to a single-package structure. While you can still import specific parts of PixiJS, you only need to install the main package.

Old:

import { Application } from '@pixi/app';
import { Sprite } from '@pixi/sprite';

New:

import { Application, Sprite } from 'pixi.js';

Custom Builds

PixiJS uses an "extensions" system to add renderer functionality. By default, PixiJS includes many extensions for a comprehensive out-of-the-box experience. However, for full control over features and bundle size, you can manually import specific PixiJS components.

  // imported by default
import 'pixi.js/accessibility'
import 'pixi.js/app'
import 'pixi.js/events'
import 'pixi.js/filters'
import 'pixi.js/sprite-tiling'
import 'pixi.js/text'
import 'pixi.js/text-bitmap'
import 'pixi.js/text-html'
import 'pixi.js/graphics'
import 'pixi.js/mesh'
import 'pixi.js/sprite-nine-slice'

// not added by default, everyone needs to import these manually
import 'pixi.js/advanced-blend-modes'
import 'pixi.js/unsafe-eval'
import 'pixi.js/prepare'
import 'pixi.js/math-extras'
import 'pixi.js/dds'
import 'pixi.js/ktx'
import 'pixi.js/basis'

import { Application } from 'pixi.js';

const app = new Application();

await app.init({
manageImports: false, // disable importing the above extensions
});

When initializing the application, you can disable the auto-import feature, preventing PixiJS from importing any extensions automatically. You'll need to import them manually, as demonstrated above.

It should also be noted that the pixi.js/text-bitmap, also add Assets loading functionality. +

Version: v8.x

v8 Migration Guide

Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.

Table of Contents

  1. Introduction
  2. Breaking Changes
  3. Deprecated Features
  4. Resources

1. Introduction

PixiJS v8 introduces several exciting changes and improvements that dramatically enhance the performance of the renderer. While we've made efforts to keep the migration process as smooth as possible, some breaking changes are inevitable. This guide will walk you through the necessary steps to migrate your PixiJS v7 project to PixiJS v8.

2. Breaking Changes

Before diving into the migration process, let's review the breaking changes introduced in PixiJS v8. Make sure to pay close attention to these changes as they may impact your existing codebase.

New Package Structure

Since version 5, PixiJS has utilized individual sub-packages to organize its codebase into smaller units. However, this approach led to issues, such as conflicting installations of different PixiJS versions, causing complications with internal caches.

In v8, PixiJS has reverted to a single-package structure. While you can still import specific parts of PixiJS, you only need to install the main package.

Old:

import { Application } from '@pixi/app';
import { Sprite } from '@pixi/sprite';

New:

import { Application, Sprite } from 'pixi.js';

Custom Builds

PixiJS uses an "extensions" system to add renderer functionality. By default, PixiJS includes many extensions for a comprehensive out-of-the-box experience. However, for full control over features and bundle size, you can manually import specific PixiJS components.

  // imported by default
import 'pixi.js/accessibility'
import 'pixi.js/app'
import 'pixi.js/events'
import 'pixi.js/filters'
import 'pixi.js/sprite-tiling'
import 'pixi.js/text'
import 'pixi.js/text-bitmap'
import 'pixi.js/text-html'
import 'pixi.js/graphics'
import 'pixi.js/mesh'
import 'pixi.js/sprite-nine-slice'

// not added by default, everyone needs to import these manually
import 'pixi.js/advanced-blend-modes'
import 'pixi.js/unsafe-eval'
import 'pixi.js/prepare'
import 'pixi.js/math-extras'
import 'pixi.js/dds'
import 'pixi.js/ktx'
import 'pixi.js/basis'

import { Application } from 'pixi.js';

const app = new Application();

await app.init({
manageImports: false, // disable importing the above extensions
});

When initializing the application, you can disable the auto-import feature, preventing PixiJS from importing any extensions automatically. You'll need to import them manually, as demonstrated above.

It should also be noted that the pixi.js/text-bitmap, also add Assets loading functionality. Therefore if you want to load bitmap fonts BEFORE initialising the renderer, you will need to import this extension.

  import 'pixi.js/text-bitmap'
import { Assets, Application } from 'pixi.js';

await Assets.load('my-font.fnt'); // If 'pixi.js/text-bitmap' is not imported, this will not load
await new Application().init();

Async Initialisation

PixiJS will now need to be initialised asynchronously. With the introduction of the WebGPU renderer PixiJS will now need to be awaited before being used

Old:

import { Application } from 'pixi.js'

const app = new Application();

// do pixi things

New:

import { Application } from 'pixi.js'

const app = new Application();

(async () => {
await app.init({
// application options
});

// do pixi things
})()

With this change it also means that the ApplicationOptions object can now be passed into the init function instead of the constructor.

Graphics API Overhaul

There are a few key changes to the Graphics API. In fact this is probably the most changed part of v8. We have added deprecations where possible but below is the rundown of changes:

  • Instead of beginning a fill or a stroke and then building a shape, v8 asks you to build your shape and then stroke / fill it. The terminology of Line has been replaced with the terminology of Stroke

Old

// red rect
const graphics = new Graphics()
.beginFill(0xFF0000);
.drawRect(50, 50, 100, 100);
.endFill();

// blur rect with stroke
const graphics2 = new Graphics()
.lineStyle(2, 'white');
.beginFill('blue');
.circle(530, 50, 140, 100);
.endFill();

New

// red rect
const graphics = new Graphics()
.rect(50, 50, 100, 100)
.fill(0xFF0000)


// blur rect with stroke
const graphics2 = new Graphics()
.rect(50, 50, 100, 100)
.fill('blue')
.stroke({width:2, color:'white'})
  • Shape functions have been renamed. Each drawing function has been simplified into a shorter version of its name. They have the same parameters though:
v7 API Callv8 API Equivalent
drawChamferRectchamferRect
drawCirclecircle
drawEllipseellipse
drawFilletRectfilletRect
drawPolygonpoly
drawRectrect
drawRegularPolygonregularPoly
drawRoundedPolygonroundPoly
drawRoundedRectroundRect
drawRoundedShaperoundShape
drawStarstar
  • fills functions expect FillStyle options or a color, rather than a string of parameters. This also replaces beginTextureFillOld
  const rect = new Graphics()
.beginTextureFill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})
.drawRect(0, 0, 100, 100)
.endFill()
.beginFill(0xFFFF00, 0.5)
.drawRect(100, 0, 100, 100)
.endFill()

New

  const rect = new Graphics()
.rect(0, 0, 100, 100)
.fill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})
.rect(100, 0, 100, 100)
.fill({color:0xFFFF00, alpha:0.5})
  • stokes functions expect StrokeStyle options or a color, rather than a string of parameters. This also replaces lineTextureStyleOld
  const rect = new Graphics()
.lineTextureStyle({texture:Texture.WHITE, width:10, color:0xFF0000})
.drawRect(0, 0, 100, 100)
.endFill()
.lineStyle(2, 0xFEEB77);
.drawRect(100, 0, 100, 100)
.endFill()

New

  const rect = new Graphics()
.rect(0, 0, 100, 100)
.stroke({texture:Texture.WHITE, width:10, color:0xFF0000})
.rect(100, 0, 100, 100)
.stroke({color:0xFEEB77, width:2})
  • holes now make use of a new cut function. As with stroke and fill, cut acts on the previous shape. Old
  const rectAndHole = new Graphics()
.beginFill(0x00FF00)
.drawRect(0, 0, 100, 100)
.beginHole()
.drawCircle(50, 50, 20)
.endHole()
.endFill();

New

  const rectAndHole = new Graphics()
.rect(0, 0, 100, 100)
.fill(0x00FF00)
.circle(50, 50, 20)
.cut();
  • GraphicsGeometry has been replaced with GraphicsContext this allows for sharing of Graphics data more efficiently. Old
  const rect = new Graphics()
.beginFill(0xFF0000);
.drawRect(50, 50, 100, 100);
.endFill();

const geometry = rect.geometry;

const secondRect = new Graphics(geometry);

New

  const context = new GraphicsContext()
.rect(50, 50, 100, 100)
.fill(0xFF0000)

const rect = new Graphics(context);
const secondRect = new Graphics(context);

Other Breaking Changes

  • DisplayObject has been removed. Container is now the base class for all PixiJS objects.

  • updateTransform has been removed as nodes no longer contain any rendering logic

    We do recognise that many people used this function to do custom logic every frame, so we have added a new onRender function that can be used for this purpose.

    Old:

    class MySprite extends Sprite {
    constructor() {
    super();
    this.updateTransform();
    }

    updateTransform() {
    super.updateTransform();
    // do custom logic
    }
    }

    New:

    class MySprite extends Sprite {
    constructor() {
    super();
    this.onRender = this._onRender.bind(this);
    }

    _onRender() {
    // do custom logic
    }
    }
  • Mipmap generation changes

    • The BaseTexture mipmap property has been renamed to autoGenerateMipmaps.
    • Mipmaps for RenderTextures have been adjusted so that developer is responsible for updating them mipmaps. Mipmap generation can be expensive, and due to the new reactive way we handle textures we do not want to accidentally generate mipmaps when they are not required.
const myRenderTexture = RenderTexture.create({width:100, height:100, autoGenerateMipmaps:true})

// do some rendering..
renderer.render({target:myRenderTexture, container:scene})

// now refresh mipmaps when you are ready
myRenderTexture.source.updateMipmaps();
  • Due to the new way PixiJS handles things internally, sprites no longer get notified if a texture's UVs have been modified. The best practice is not to modify texture UVs once they have been created. It's best to have textures ready to go (they are inexpensive to create and store).
  • Sometimes, you might want to employ a special technique that animates the UVs. In this last instance, you will be responsible for updating the sprite (it's worth noting that it may update automatically - but due to the new optimizations, this will not be guaranteed). Updating the source data (e.g., a video texture) will, however, always be reflected immediately.
const texture = await Assets.load('bunny.png');
const sprite = new Sprite(texture);

texture.frame.width = texture.frame.width/2;
texture.update();

// guarantees the texture changes will be reflected on the sprite
sprite.onViewUpdate();


// alternatively you can hooke into the sprites event
texture.on('update', ()=>{sprite.onViewUpdate});

The act of adding and removing the event when a sprite's texture was changed led to an unacceptable performance drop, especially when swapping many textures (imagine shooting games with lots of keyframe textures swapping). This is why we now leave that responsibility to the user.

  • New Container culling approach

    With this version of PixiJS we have changed how the cullable property works on containers. Previously culling was done for you automatically during the render loop. However, we have moved this logic out and provided users the ability to control when culling happens themselves.

    With this change we have added a couple of new properties:

    • cullable - Whether or not the container can be culled
    • cullArea - A cull area that will be used instead of the bounds of the container
    • cullableChildren - Whether or not the containers children can be culled. This can help optimise large scenes

    New:

    const container = new GameWorld();
    const view = new Rectangle(0, 0, 800, 600);

    container.cullable = true;
    container.cullArea = new Rectangle(0,0,400,400);
    container.cullableChildren = false;

    Culler.shared.cull(myContainer, view);
    renderer.render(myContainer);

    There is also a CullerPlugin that can be used to automatically call Culler.shared.cull every frame if you want to simulate the old behaviour.

    import {extensions, CullerPlugin} from 'pixi.js'
    extensions.add(CullerPlugin)
  • Renamed several mesh classes

    • renamed SimpleMesh -> MeshSimple
    • renamed SimplePlane -> MeshPlane
    • renamed SimpleRope -> MeshRope
  • Deprecations for Assets removed

    Old:

    import { Assets } from 'pixi.js'

    Assets.add('bunny', 'bunny.png')

    New:

    import { Assets } from 'pixi.js'

    Assets.add({ alias: 'bunny', src: 'bunny.png' })
  • settings object has been removed

    Old:

    import { settings, BrowserAdapter } from 'pixi.js'

    settings.RESOLUTION = 1
    settings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false
    settings.ADAPTER = BrowserAdapter

    New:

    import { AbstractRenderer, DOMAdapter, BrowserAdapter } from 'pixi.js'

    // Can also be passed into the renderer directly e.g `autoDetectRenderer({resolution: 1})`
    AbstractRenderer.defaultOptions.resolution = 1;

    // Can also be passed into the renderer directly e.g `autoDetectRenderer({failIfMajorPerformanceCaveat: false})`
    AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat = false;

    // See below for more information about changes to the adapter
    DOMAdapter.set(BrowserAdapter)
  • Adapter and Web Worker Changes

    • settings.ADAPTER has been removed and replaced with DOMAdapter

      • DOMAdapter is a static class that can be used to set the adapter for the entire application
      • PixiJS has two adapters built in BrowserAdapter and WebWorkerAdapter
        • BrowserAdapter is the default adapter and is used when running in the browser
        • WebWorkerAdapter is used when running in a web worker

      Old:

      import { settings, WebWorkerAdapter } from 'pixi.js'

      settings.ADAPTER = WebWorkerAdapter
      settings.ADAPTER.createCanvas()

      New:

      import { DOMAdapter, WebWorkerAdapter } from 'pixi.js'

      DOMAdapter.set(WebWorkerAdapter)
      DOMAdapter.get().createCanvas()
  • Application type now accepts Renderer instead of view by @Zyie in https://github.com/pixijs/pixijs/pull/9740

    This is to allow app.renderer to be typed correctly

    Old:

    const app = new Application<HTMLCanvasElement>()

    New:

    // WebGL or WebGPU renderer
    const app = new Application<Renderer<HTMLCanvasElement>>()
    // WebGL specific renderer
    const app = new Application<WebGLRenderer<HTMLCanvasElement>>();
    // WebGPU specific renderer
    const app = new Application<WebGPURenderer<HTMLCanvasElement>>();
  • Texture.from no longer will load a texture from a URL.

    When using Texture.from you will need to pass in a source such as CanvasSource/ImageSource/VideoSource or a resource such as HTMLImageElement/HTMLCanvasElement/HTMLVideoElement or a string that has been loaded through Assets.load

    Old:

    import { Texture } from 'pixi.js';

    const texture = Texture.from('https://i.imgur.com/IaUrttj.png');

    New:

    import { Assets, Texture } from 'pixi.js';

    await Assets.load('https://i.imgur.com/IaUrttj.png');
    const texture = Texture.from('https://i.imgur.com/IaUrttj.png');
  • The Ticker's callback will now pass the Ticker instance instead of the delta time. -This is to allow for more control over what unit of time is used.

    Old:

    Ticker.shared.add((dt)=> {
    bunny.rotation += dt
    });

    New:

    Ticker.shared.add((ticker)=> {
    bunny.rotation += ticker.deltaTime;
    });
  • Text parsers have been renamed

    • TextFormat -> bitmapFontTextParser
    • XMLStringFormat -> bitmapFontXMLStringParser
    • XMLFormat -> bitmapFontXMLParser
  • The default eventMode is now passive instead of auto

  • utils has been removed. All the functions are available as direct imports.

    Old:

    import { utils } from 'pixi.js'

    utils.isMobile.any()

    New:

    import { isMobile } from 'pixi.js'

    isMobile.any()
  • container.getBounds() now returns a Bounds object instead of a Rectangle object. You can access the rectangle by using container.getBounds().rectangle instead.

    Old:

    const bounds = container.getBounds();

    New:

    const bounds = container.getBounds().rectangle
  • ParticleContainer has been removed, you should use normal a regular Container instead. The performance improvements that ParticleContainer provided are no longer necessary due to the new rendering architecture.

3. Deprecated Features

Certain features from PixiJS v7 have been deprecated in v8. While they will still work, it's recommended to update your code to use the new alternatives. Refer to the deprecated features section for details on what to replace them with.

  • Leaf nodes no longer allow children

    Only Containers can have children. This means that Sprite, Mesh, Graphics etc can no longer have children.

    To replicate the old behaviour you can create a Container and add the leaf nodes to it.

    Old:

    const sprite = new Sprite();
    const spriteChild = new Sprite();
    sprite.addChild(spriteChild);

    New:

    const container = new Container();
    const sprite = new Sprite();
    const spriteChild = new Sprite();

    container.addChild(sprite);
    container.addChild(spriteChild);
  • Application.view replaced with Application.canvas

    Old:

    const app = new Application({ view: document.createElement('canvas') });
    document.body.appendChild(app.view);

    New:

    const app = new Application();
    await app.init({ view: document.createElement('canvas') });
    document.body.appendChild(app.canvas);
  • NineSlicePlane renamed to NineSliceSprite

  • SCALE_MODES replaced with ScaleMode strings

    • SCALE_MODES.NEAREST -> 'nearest',
    • SCALE_MODES.LINEAR -> 'linear',
  • WRAP_MODES replaced with WrapMode strings

    • WRAP_MODES.CLAMP -> 'clamp',
    • WRAP_MODES.REPEAT -> 'repeat',
    • WRAP_MODES.MIRRORED_REPEAT -> 'mirror-repeat',
  • DRAW_MODES replaced with Topology strings

    • DRAW_MODES.POINTS -> 'point-list',
    • DRAW_MODES.LINES -> 'line-list',
    • DRAW_MODES.LINE_STRIP -> 'line-strip',
    • DRAW_MODES.TRIANGLES -> 'triangle-list',
    • DRAW_MODES.TRIANGLE_STRIP -> 'triangle-strip',
  • Constructors have largely been changed to accept objects instead of multiple arguments

    Old:

    const blurFilter = new BlurFilter(8, 4, 1, 5);
    const displacementFilter = new DisplacementFilter(sprite, 5);
    const meshGeometry = new MeshGeometry(vertices, uvs, index);
    const mesh = new Mesh(geometry, shader, state, drawMode);
    const plane = new PlaneGeometry(width, height, segWidth, segHeight);
    const nineSlicePlane = new NineSlicePlane(texture, leftWidth, topHeight, rightWidth, bottomHeight);
    const tileSprite = new TileSprite(texture, width, height);
    const text = new Text('Hello World', style);
    const bitmapText = new BitmapText('Hello World', style);
    const htmlText = new HTMLText('Hello World', style);

    New:

    const blurFilter = new BlurFilter({
    blur: 8,
    quality: 4,
    resolution: 1,
    kernelSize: 5,
    });
    const displacementFilter = new DisplacementFilter({
    sprite,
    scale: 5,
    });
    const meshGeometry = new MeshGeometry({
    positions: vertices,
    uvs,
    indices: index,
    topology: 'triangle-list';
    shrinkBuffersToFit: boolean;
    });
    const mesh = new Mesh({
    geometry
    shader
    texture
    });
    const plane = new PlaneGeometry({
    width,
    height,
    verticesX: segWidth,
    verticesY: segHeight,
    });
    const nineSliceSprite = new NineSliceSprite({
    texture,
    leftWidth,
    topHeight,
    rightWidth,
    bottomHeight,
    });
    const tileSprite = new TileSprite({
    texture,
    width,
    height,
    });
    const text = new Text({
    text: 'Hello World',
    style,
    });
    const bitmapText = new BitmapText({
    text:'Hello World',
    style,
    });
    const htmlText = new HTMLText({
    text:'Hello World',
    style,
    });
  • container.name is now container.label

4. Resources

- - +This is to allow for more control over what unit of time is used.

Old:

Ticker.shared.add((dt)=> {
bunny.rotation += dt
});

New:

Ticker.shared.add((ticker)=> {
bunny.rotation += ticker.deltaTime;
});
  • Text parsers have been renamed

    • TextFormat -> bitmapFontTextParser
    • XMLStringFormat -> bitmapFontXMLStringParser
    • XMLFormat -> bitmapFontXMLParser
  • The default eventMode is now passive instead of auto

  • utils has been removed. All the functions are available as direct imports.

    Old:

    import { utils } from 'pixi.js'

    utils.isMobile.any()

    New:

    import { isMobile } from 'pixi.js'

    isMobile.any()
  • container.getBounds() now returns a Bounds object instead of a Rectangle object. You can access the rectangle by using container.getBounds().rectangle instead.

    Old:

    const bounds = container.getBounds();

    New:

    const bounds = container.getBounds().rectangle
  • ParticleContainer has been removed, you should use normal a regular Container instead. The performance improvements that ParticleContainer provided are no longer necessary due to the new rendering architecture.

  • 3. Deprecated Features

    Certain features from PixiJS v7 have been deprecated in v8. While they will still work, it's recommended to update your code to use the new alternatives. Refer to the deprecated features section for details on what to replace them with.

    • Leaf nodes no longer allow children

      Only Containers can have children. This means that Sprite, Mesh, Graphics etc can no longer have children.

      To replicate the old behaviour you can create a Container and add the leaf nodes to it.

      Old:

      const sprite = new Sprite();
      const spriteChild = new Sprite();
      sprite.addChild(spriteChild);

      New:

      const container = new Container();
      const sprite = new Sprite();
      const spriteChild = new Sprite();

      container.addChild(sprite);
      container.addChild(spriteChild);
    • Application.view replaced with Application.canvas

      Old:

      const app = new Application({ view: document.createElement('canvas') });
      document.body.appendChild(app.view);

      New:

      const app = new Application();
      await app.init({ view: document.createElement('canvas') });
      document.body.appendChild(app.canvas);
    • NineSlicePlane renamed to NineSliceSprite

    • SCALE_MODES replaced with ScaleMode strings

      • SCALE_MODES.NEAREST -> 'nearest',
      • SCALE_MODES.LINEAR -> 'linear',
    • WRAP_MODES replaced with WrapMode strings

      • WRAP_MODES.CLAMP -> 'clamp',
      • WRAP_MODES.REPEAT -> 'repeat',
      • WRAP_MODES.MIRRORED_REPEAT -> 'mirror-repeat',
    • DRAW_MODES replaced with Topology strings

      • DRAW_MODES.POINTS -> 'point-list',
      • DRAW_MODES.LINES -> 'line-list',
      • DRAW_MODES.LINE_STRIP -> 'line-strip',
      • DRAW_MODES.TRIANGLES -> 'triangle-list',
      • DRAW_MODES.TRIANGLE_STRIP -> 'triangle-strip',
    • Constructors have largely been changed to accept objects instead of multiple arguments

      Old:

      const blurFilter = new BlurFilter(8, 4, 1, 5);
      const displacementFilter = new DisplacementFilter(sprite, 5);
      const meshGeometry = new MeshGeometry(vertices, uvs, index);
      const mesh = new Mesh(geometry, shader, state, drawMode);
      const plane = new PlaneGeometry(width, height, segWidth, segHeight);
      const nineSlicePlane = new NineSlicePlane(texture, leftWidth, topHeight, rightWidth, bottomHeight);
      const tileSprite = new TileSprite(texture, width, height);
      const text = new Text('Hello World', style);
      const bitmapText = new BitmapText('Hello World', style);
      const htmlText = new HTMLText('Hello World', style);

      New:

      const blurFilter = new BlurFilter({
      blur: 8,
      quality: 4,
      resolution: 1,
      kernelSize: 5,
      });
      const displacementFilter = new DisplacementFilter({
      sprite,
      scale: 5,
      });
      const meshGeometry = new MeshGeometry({
      positions: vertices,
      uvs,
      indices: index,
      topology: 'triangle-list';
      shrinkBuffersToFit: boolean;
      });
      const mesh = new Mesh({
      geometry
      shader
      texture
      });
      const plane = new PlaneGeometry({
      width,
      height,
      verticesX: segWidth,
      verticesY: segHeight,
      });
      const nineSliceSprite = new NineSliceSprite({
      texture,
      leftWidth,
      topHeight,
      rightWidth,
      bottomHeight,
      });
      const tileSprite = new TileSprite({
      texture,
      width,
      height,
      });
      const text = new Text({
      text: 'Hello World',
      style,
      });
      const bitmapText = new BitmapText({
      text:'Hello World',
      style,
      });
      const htmlText = new HTMLText({
      text:'Hello World',
      style,
      });
    • container.name is now container.label

    4. Resources

    + + \ No newline at end of file diff --git a/8.x/guides/production/performance-tips.html b/8.x/guides/production/performance-tips.html new file mode 100644 index 000000000..11ab38e28 --- /dev/null +++ b/8.x/guides/production/performance-tips.html @@ -0,0 +1,21 @@ + + + + + +Performance Tips | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    Performance Tips

    General

    • Only optimize when you need to! PixiJS can handle a fair amount of content off the bat
    • Be mindful of the complexity of your scene. The more objects you add the slower things will end up
    • Order can help, for example sprite / graphic / sprite / graphic is slower than sprite / sprite / graphic / graphic
    • Some older mobile devices run things a little slower. Passing in the option useContextAlpha: false and antialias: false to the Renderer or Application can help with performance
    • Culling is disabled by default as it's often better to do this at an application level or set objects to be cullable = true. If you are GPU-bound it will improve performance; if you are CPU-bound it will degrade performance

    Sprites

    • Use Spritesheets where possible to minimize total textures
    • Sprites can be batched with up to 16 different textures (dependent on hardware)
    • This is the fastest way to render content
    • On older devices use smaller low resolution textures
    • Add the extention @0.5x.png to the 50% scale-down spritesheet so PixiJS will visually-double them automatically
    • Draw order can be important

    Graphics

    • Graphics objects are fastest when they are not modified constantly (not including the transform, alpha or tint!)
    • Graphics objects are batched when under a certain size (100 points or smaller)
    • Small Graphics objects are as fast as Sprites (rectangles, triangles)
    • Using 100s of graphics complex objects can be slow, in this instance use sprites (you can create a texture)

    Texture

    • Textures are automatically managed by a Texture Garbage Collector
    • You can also manage them yourself by using texture.destroy()
    • If you plan to destroy more than one at once add a random delay to their destruction to remove freezing
    • Delay texture destroy if you plan to delete a lot of textures yourself

    Text

    • Avoid changing it on every frame as this can be expensive (each time it draws to a canvas and then uploads to GPU)
    • Bitmap Text gives much better performance for dynamically changing text
    • Text resolution matches the renderer resolution, decrease resolution yourself by setting the resolution property, which can consume less memory

    Masks

    • Masks can be expensive if too many are used: e.g., 100s of masks will really slow things down
    • Axis-aligned Rectangle masks are the fastest (as the use scissor rect)
    • Graphics masks are second fastest (as they use the stencil buffer)
    • Sprite masks are the third fastest (they uses filters). They are really expensive. Do not use too many in your scene!

    Filters

    • Release memory: container.filters = null
    • If you know the size of them: container.filterArea = new Rectangle(x,y,w,h). This can speed things up as it means the object does not need to be measured
    • Filters are expensive, using too many will start to slow things down!

    BlendModes

    • Different blend modes will cause batches to break (de-optimize)
    • SceenSprite / NormalSprite / SceenSprite / NormalSprite would be 4 draw calls
    • SceenSprite / SceenSprite / NormalSprite / NormalSprite would be 2 draw calls

    Events

    • If an object has no interactive children use interactiveChildren = false. The event system will then be able to avoid crawling through the object
    • Setting hitArea = new Rectangle(x,y,w,h) as above should stop the event system from crawling through the object
    + + + + \ No newline at end of file diff --git a/8.x/playground.html b/8.x/playground.html new file mode 100644 index 000000000..accb564fb --- /dev/null +++ b/8.x/playground.html @@ -0,0 +1,21 @@ + + + + + +index | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x
    + + + + \ No newline at end of file diff --git a/8.x/tutorials.html b/8.x/tutorials.html new file mode 100644 index 000000000..7ebe7cabf --- /dev/null +++ b/8.x/tutorials.html @@ -0,0 +1,21 @@ + + + + + +Tutorials | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    Tutorials

    Welcome to the tutorials page! Here you can find hand-crafted exercises to get you started with the PixiJS.

    Getting Started

    Learn the basics of how to use PixiJS.

    Fish Pond

    Let's create a lively fish pond!

    Choo Choo Train

    Onboard the graphical Choo Choo Train!

    Spine Boy Adventure

    Behold the power of interactive Spine animation!

    + + + + \ No newline at end of file diff --git a/8.x/tutorials/choo-choo-train.html b/8.x/tutorials/choo-choo-train.html new file mode 100644 index 000000000..22e395903 --- /dev/null +++ b/8.x/tutorials/choo-choo-train.html @@ -0,0 +1,21 @@ + + + + + +choo-choo-train | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    LOADING...

    + + + + \ No newline at end of file diff --git a/8.x/tutorials/fish-pond.html b/8.x/tutorials/fish-pond.html new file mode 100644 index 000000000..c9ad83de2 --- /dev/null +++ b/8.x/tutorials/fish-pond.html @@ -0,0 +1,21 @@ + + + + + +fish-pond | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    LOADING...

    + + + + \ No newline at end of file diff --git a/8.x/tutorials/getting-started.html b/8.x/tutorials/getting-started.html new file mode 100644 index 000000000..220ba029c --- /dev/null +++ b/8.x/tutorials/getting-started.html @@ -0,0 +1,21 @@ + + + + + +getting-started | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    LOADING...

    + + + + \ No newline at end of file diff --git a/8.x/tutorials/spine-boy-adventure.html b/8.x/tutorials/spine-boy-adventure.html new file mode 100644 index 000000000..6cf461cb9 --- /dev/null +++ b/8.x/tutorials/spine-boy-adventure.html @@ -0,0 +1,21 @@ + + + + + +spine-boy-adventure | PixiJS + + + + + + + + + +
    +
    Skip to main content
    Version: v8.x

    LOADING...

    + + + + \ No newline at end of file diff --git a/assets/js/0480b142.044af528.js b/assets/js/0480b142.044af528.js new file mode 100644 index 000000000..e695d702d --- /dev/null +++ b/assets/js/0480b142.044af528.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[836],{3584:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>l,contentTitle:()=>n,default:()=>d,frontMatter:()=>s,metadata:()=>r,toc:()=>u});var a=t(7462),o=(t(7294),t(3905));const s={},n="FAQ",r={unversionedId:"faq",id:"faq",title:"FAQ",description:"What is PixiJS for?",source:"@site/docs/faq.md",sourceDirName:".",slug:"/faq",permalink:"/8.x/faq",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/faq.md",tags:[],version:"current",frontMatter:{}},l={},u=[{value:"What is PixiJS for?",id:"what-is-pixijs-for",level:2},{value:"Is PixiJS free?",id:"is-pixijs-free",level:2},{value:"Where do I get it?",id:"where-do-i-get-it",level:2},{value:"How do I get started?",id:"how-do-i-get-started",level:2},{value:"Why should I use PixiJS?",id:"why-should-i-use-pixijs",level:2},{value:"Is PixiJS a game engine?",id:"is-pixijs-a-game-engine",level:2},{value:"Who makes PixiJS?",id:"who-makes-pixijs",level:2},{value:"I found a bug. What should I do?",id:"i-found-a-bug-what-should-i-do",level:2}],h={toc:u};function d(e){let{components:i,...t}=e;return(0,o.kt)("wrapper",(0,a.Z)({},h,t,{components:i,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"faq"},"FAQ"),(0,o.kt)("h2",{id:"what-is-pixijs-for"},"What is PixiJS for?"),(0,o.kt)("p",null,"Everything! Pixi.js is a rendering library that will allow you to create rich,\ninteractive graphic experiences, cross-platform applications, and games without\nhaving to dive into the WebGL API or grapple with the intricacies of browser and\ndevice compatibility. Killer performance with a clean API, means not only will\nyour content be better - but also faster to build!"),(0,o.kt)("h2",{id:"is-pixijs-free"},"Is PixiJS free?"),(0,o.kt)("p",null,"PixiJS is and always will be free and Open Source. That said, financial contributions\nare what make it possible to push PixiJS further, faster. Contributions allow us to\ncommission the PixiJS developer community to accelerate feature development and create\nmore in-depth documentation. ",(0,o.kt)("a",{href:"https://opencollective.com/pixijs",target:"_blank"},"Support Us")," by making a contribution via ",(0,o.kt)("a",{href:"https://opencollective.com/pixijs",target:"_blank"},"Open Collective"),". Go on! It will be a massive help AND make you feel good about yourself, win win ;)"),(0,o.kt)("h2",{id:"where-do-i-get-it"},"Where do I get it?"),(0,o.kt)("p",null,"Visit our GitHub page to download the very latest version of PixiJS. This is the most up-to-date resource for PixiJS and should always be your first port of call to make sure you are using the latest version. Just click the 'Download' link in the navigation."),(0,o.kt)("h2",{id:"how-do-i-get-started"},"How do I get started?"),(0,o.kt)("p",null,"Right here! Take a look through the Resources section for a wealth of information including documentation, forums, tutorials and the Goodboy blog."),(0,o.kt)("h2",{id:"why-should-i-use-pixijs"},"Why should I use PixiJS?"),(0,o.kt)("p",null,"Because you care about speed. PixiJS' #1 mantra has always been speed. We really do feel the need! We do everything we can to make PixiJS as streamlined, efficient and fast as possible, whilst balancing it with offering as many crucial and valuable features as we can."),(0,o.kt)("h2",{id:"is-pixijs-a-game-engine"},"Is PixiJS a game engine?"),(0,o.kt)("p",null,'No. PixiJS is what we\'ve come to think of as a "creation engine". Whilst it is extremely good for making games, the core essence of PixiJS is simply moving things around on screens as quickly and efficiently as possible. It does of course happen that it is absolutely brilliant for making games though!'),(0,o.kt)("h2",{id:"who-makes-pixijs"},"Who makes PixiJS?"),(0,o.kt)("p",null,'Outside of the highly active PixiJS community, it is primarily maintained by Mat Groves, Technical Partner of our creative agency Goodboy Digital. One of the huge advantages of creating PixiJS within the framework of a working agency is that it means its features are always driven by genuine industry demands and critically are always trialled "in anger" in our cutting-edge games, sites and apps.'),(0,o.kt)("h2",{id:"i-found-a-bug-what-should-i-do"},"I found a bug. What should I do?"),(0,o.kt)("p",null,"Two things - lets us know via the ",(0,o.kt)("a",{href:"https://github.com/pixijs/pixijs/issues/new"},"PixiJS GitHub community")," and even better yet, if you know how, post a fix! Our Community is stronger in numbers so we're always keen to welcome new contributors into the team to help us shape what PixiJS becomes next."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/0480b142.b8b6a1a1.js b/assets/js/0480b142.b8b6a1a1.js deleted file mode 100644 index 3c2c41278..000000000 --- a/assets/js/0480b142.b8b6a1a1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[836],{3584:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>l,contentTitle:()=>n,default:()=>d,frontMatter:()=>s,metadata:()=>r,toc:()=>u});var a=t(7462),o=(t(7294),t(3905));const s={},n="FAQ",r={unversionedId:"faq",id:"faq",title:"FAQ",description:"What is PixiJS for?",source:"@site/docs/faq.md",sourceDirName:".",slug:"/faq",permalink:"/faq",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/faq.md",tags:[],version:"current",frontMatter:{}},l={},u=[{value:"What is PixiJS for?",id:"what-is-pixijs-for",level:2},{value:"Is PixiJS free?",id:"is-pixijs-free",level:2},{value:"Where do I get it?",id:"where-do-i-get-it",level:2},{value:"How do I get started?",id:"how-do-i-get-started",level:2},{value:"Why should I use PixiJS?",id:"why-should-i-use-pixijs",level:2},{value:"Is PixiJS a game engine?",id:"is-pixijs-a-game-engine",level:2},{value:"Who makes PixiJS?",id:"who-makes-pixijs",level:2},{value:"I found a bug. What should I do?",id:"i-found-a-bug-what-should-i-do",level:2}],h={toc:u};function d(e){let{components:i,...t}=e;return(0,o.kt)("wrapper",(0,a.Z)({},h,t,{components:i,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"faq"},"FAQ"),(0,o.kt)("h2",{id:"what-is-pixijs-for"},"What is PixiJS for?"),(0,o.kt)("p",null,"Everything! Pixi.js is a rendering library that will allow you to create rich,\ninteractive graphic experiences, cross-platform applications, and games without\nhaving to dive into the WebGL API or grapple with the intricacies of browser and\ndevice compatibility. Killer performance with a clean API, means not only will\nyour content be better - but also faster to build!"),(0,o.kt)("h2",{id:"is-pixijs-free"},"Is PixiJS free?"),(0,o.kt)("p",null,"PixiJS is and always will be free and Open Source. That said, financial contributions\nare what make it possible to push PixiJS further, faster. Contributions allow us to\ncommission the PixiJS developer community to accelerate feature development and create\nmore in-depth documentation. ",(0,o.kt)("a",{href:"https://opencollective.com/pixijs",target:"_blank"},"Support Us")," by making a contribution via ",(0,o.kt)("a",{href:"https://opencollective.com/pixijs",target:"_blank"},"Open Collective"),". Go on! It will be a massive help AND make you feel good about yourself, win win ;)"),(0,o.kt)("h2",{id:"where-do-i-get-it"},"Where do I get it?"),(0,o.kt)("p",null,"Visit our GitHub page to download the very latest version of PixiJS. This is the most up-to-date resource for PixiJS and should always be your first port of call to make sure you are using the latest version. Just click the 'Download' link in the navigation."),(0,o.kt)("h2",{id:"how-do-i-get-started"},"How do I get started?"),(0,o.kt)("p",null,"Right here! Take a look through the Resources section for a wealth of information including documentation, forums, tutorials and the Goodboy blog."),(0,o.kt)("h2",{id:"why-should-i-use-pixijs"},"Why should I use PixiJS?"),(0,o.kt)("p",null,"Because you care about speed. PixiJS' #1 mantra has always been speed. We really do feel the need! We do everything we can to make PixiJS as streamlined, efficient and fast as possible, whilst balancing it with offering as many crucial and valuable features as we can."),(0,o.kt)("h2",{id:"is-pixijs-a-game-engine"},"Is PixiJS a game engine?"),(0,o.kt)("p",null,'No. PixiJS is what we\'ve come to think of as a "creation engine". Whilst it is extremely good for making games, the core essence of PixiJS is simply moving things around on screens as quickly and efficiently as possible. It does of course happen that it is absolutely brilliant for making games though!'),(0,o.kt)("h2",{id:"who-makes-pixijs"},"Who makes PixiJS?"),(0,o.kt)("p",null,'Outside of the highly active PixiJS community, it is primarily maintained by Mat Groves, Technical Partner of our creative agency Goodboy Digital. One of the huge advantages of creating PixiJS within the framework of a working agency is that it means its features are always driven by genuine industry demands and critically are always trialled "in anger" in our cutting-edge games, sites and apps.'),(0,o.kt)("h2",{id:"i-found-a-bug-what-should-i-do"},"I found a bug. What should I do?"),(0,o.kt)("p",null,"Two things - lets us know via the ",(0,o.kt)("a",{href:"https://github.com/pixijs/pixijs/issues/new"},"PixiJS GitHub community")," and even better yet, if you know how, post a fix! Our Community is stronger in numbers so we're always keen to welcome new contributors into the team to help us shape what PixiJS becomes next."))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/059a6190.e1ab475f.js b/assets/js/059a6190.02a446d4.js similarity index 99% rename from assets/js/059a6190.e1ab475f.js rename to assets/js/059a6190.02a446d4.js index 642c89844..42f74e50a 100644 --- a/assets/js/059a6190.e1ab475f.js +++ b/assets/js/059a6190.02a446d4.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7344],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},8360:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/spine-boy-adventure",id:"tutorials/spine-boy-adventure",title:"spine-boy-adventure",description:"",source:"@site/docs/tutorials/spine-boy-adventure.md",sourceDirName:"tutorials",slug:"/tutorials/spine-boy-adventure",permalink:"/tutorials/spine-boy-adventure",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"spineBoyAdventure",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7344],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},8360:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/spine-boy-adventure",id:"tutorials/spine-boy-adventure",title:"spine-boy-adventure",description:"",source:"@site/docs/tutorials/spine-boy-adventure.md",sourceDirName:"tutorials",slug:"/tutorials/spine-boy-adventure",permalink:"/8.x/tutorials/spine-boy-adventure",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"spineBoyAdventure",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/0779818e.c72642e5.js b/assets/js/0779818e.c72642e5.js deleted file mode 100644 index 2d280b369..000000000 --- a/assets/js/0779818e.c72642e5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6686],{8989:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>c,frontMatter:()=>d,metadata:()=>r,toc:()=>u});var t=i(7462),a=(i(7294),i(3905)),n=i(8010),l=i(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Mouse Blending"},o=void 0,r={unversionedId:"examples/filters-advanced/mouse-blending",id:"examples/filters-advanced/mouse-blending",title:"Mouse Blending",description:"",source:"@site/docs/examples/filters-advanced/mouse-blending.md",sourceDirName:"examples/filters-advanced",slug:"/examples/filters-advanced/mouse-blending",permalink:"/examples/filters-advanced/mouse-blending",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Mouse Blending"},sidebar:"examplesSidebar",previous:{title:"Displacement Map Flag",permalink:"/examples/filters-basic/displacement-map-flag"},next:{title:"Custom",permalink:"/examples/filters-advanced/custom"}},p={},u=[],m={toc:u};function c(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"filtersAdvanced.mouseBlending",pixiVersion:l,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/0779818e.e49bf497.js b/assets/js/0779818e.e49bf497.js new file mode 100644 index 000000000..92863f583 --- /dev/null +++ b/assets/js/0779818e.e49bf497.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6686],{8989:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>c,frontMatter:()=>d,metadata:()=>r,toc:()=>u});var t=i(7462),a=(i(7294),i(3905)),n=i(8010),l=i(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Mouse Blending"},o=void 0,r={unversionedId:"examples/filters-advanced/mouse-blending",id:"examples/filters-advanced/mouse-blending",title:"Mouse Blending",description:"",source:"@site/docs/examples/filters-advanced/mouse-blending.md",sourceDirName:"examples/filters-advanced",slug:"/examples/filters-advanced/mouse-blending",permalink:"/8.x/examples/filters-advanced/mouse-blending",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Mouse Blending"},sidebar:"examplesSidebar",previous:{title:"Displacement Map Flag",permalink:"/8.x/examples/filters-basic/displacement-map-flag"},next:{title:"Custom",permalink:"/8.x/examples/filters-advanced/custom"}},p={},u=[],m={toc:u};function c(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"filtersAdvanced.mouseBlending",pixiVersion:l,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/0f70954b.9d7bdc73.js b/assets/js/0f70954b.9d7bdc73.js deleted file mode 100644 index 6a409f04c..000000000 --- a/assets/js/0f70954b.9d7bdc73.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4057],{5133:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>n,default:()=>m,frontMatter:()=>c,metadata:()=>o,toc:()=>p});var s=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const c={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Scratch Card"},n=void 0,o={unversionedId:"examples/advanced/scratch-card",id:"examples/advanced/scratch-card",title:"Scratch Card",description:"",source:"@site/docs/examples/advanced/scratch-card.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/scratch-card",permalink:"/examples/advanced/scratch-card",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Scratch Card"},sidebar:"examplesSidebar",previous:{title:"Slots",permalink:"/examples/advanced/slots"},next:{title:"Star Warp",permalink:"/examples/advanced/star-warp"}},l={},p=[],u={toc:p};function m(e){let{components:a,...t}=e;return(0,i.kt)("wrapper",(0,s.Z)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"advanced.scratchCard",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/0f70954b.a14f4b10.js b/assets/js/0f70954b.a14f4b10.js new file mode 100644 index 000000000..5e724c6ef --- /dev/null +++ b/assets/js/0f70954b.a14f4b10.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4057],{5133:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>n,default:()=>m,frontMatter:()=>c,metadata:()=>o,toc:()=>p});var s=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const c={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Scratch Card"},n=void 0,o={unversionedId:"examples/advanced/scratch-card",id:"examples/advanced/scratch-card",title:"Scratch Card",description:"",source:"@site/docs/examples/advanced/scratch-card.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/scratch-card",permalink:"/8.x/examples/advanced/scratch-card",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Scratch Card"},sidebar:"examplesSidebar",previous:{title:"Slots",permalink:"/8.x/examples/advanced/slots"},next:{title:"Star Warp",permalink:"/8.x/examples/advanced/star-warp"}},l={},p=[],u={toc:p};function m(e){let{components:a,...t}=e;return(0,i.kt)("wrapper",(0,s.Z)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"advanced.scratchCard",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/125a7167.198b08b4.js b/assets/js/125a7167.198b08b4.js deleted file mode 100644 index 75fbf8d12..000000000 --- a/assets/js/125a7167.198b08b4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6959],{9116:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>m,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var i=s(7462),r=(s(7294),s(3905)),n=s(8010),o=s(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Pointer Tracker"},p=void 0,l={unversionedId:"examples/events/pointer-tracker",id:"examples/events/pointer-tracker",title:"Pointer Tracker",description:"",source:"@site/docs/examples/events/pointer-tracker.md",sourceDirName:"examples/events",slug:"/examples/events/pointer-tracker",permalink:"/examples/events/pointer-tracker",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Pointer Tracker"},sidebar:"examplesSidebar",previous:{title:"Logger",permalink:"/examples/events/logger"},next:{title:"Slider",permalink:"/examples/events/slider"}},d={},c=[],u={toc:c};function m(e){let{components:t,...s}=e;return(0,r.kt)("wrapper",(0,i.Z)({},u,s,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(n.Z,{id:"events.pointerTracker",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/125a7167.fe8c0d5e.js b/assets/js/125a7167.fe8c0d5e.js new file mode 100644 index 000000000..91dc57161 --- /dev/null +++ b/assets/js/125a7167.fe8c0d5e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6959],{9116:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>m,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var i=s(7462),r=(s(7294),s(3905)),n=s(8010),o=s(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Pointer Tracker"},p=void 0,l={unversionedId:"examples/events/pointer-tracker",id:"examples/events/pointer-tracker",title:"Pointer Tracker",description:"",source:"@site/docs/examples/events/pointer-tracker.md",sourceDirName:"examples/events",slug:"/examples/events/pointer-tracker",permalink:"/8.x/examples/events/pointer-tracker",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Pointer Tracker"},sidebar:"examplesSidebar",previous:{title:"Logger",permalink:"/8.x/examples/events/logger"},next:{title:"Slider",permalink:"/8.x/examples/events/slider"}},d={},c=[],u={toc:c};function m(e){let{components:t,...s}=e;return(0,r.kt)("wrapper",(0,i.Z)({},u,s,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(n.Z,{id:"events.pointerTracker",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/1663c392.8647b2ae.js b/assets/js/1663c392.8647b2ae.js new file mode 100644 index 000000000..ce7be6b84 --- /dev/null +++ b/assets/js/1663c392.8647b2ae.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5355],{9068:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>l,contentTitle:()=>p,default:()=>m,frontMatter:()=>o,metadata:()=>r,toc:()=>c});var t=i(7462),n=(i(7294),i(3905)),a=i(8010),d=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Spinners"},p=void 0,r={unversionedId:"examples/advanced/spinners",id:"examples/advanced/spinners",title:"Spinners",description:"",source:"@site/docs/examples/advanced/spinners.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/spinners",permalink:"/8.x/examples/advanced/spinners",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Spinners"},sidebar:"examplesSidebar",previous:{title:"Collision Detection",permalink:"/8.x/examples/advanced/collision-detection"},next:{title:"Basic",permalink:"/8.x/examples/sprite/basic"}},l={},c=[],u={toc:c};function m(e){let{components:s,...i}=e;return(0,n.kt)("wrapper",(0,t.Z)({},u,i,{components:s,mdxType:"MDXLayout"}),(0,n.kt)(a.Z,{id:"advanced.spinners",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/1663c392.c16dfce5.js b/assets/js/1663c392.c16dfce5.js deleted file mode 100644 index 65d0eaab9..000000000 --- a/assets/js/1663c392.c16dfce5.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5355],{9068:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>l,contentTitle:()=>p,default:()=>m,frontMatter:()=>o,metadata:()=>r,toc:()=>c});var t=i(7462),n=(i(7294),i(3905)),a=i(8010),d=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Spinners"},p=void 0,r={unversionedId:"examples/advanced/spinners",id:"examples/advanced/spinners",title:"Spinners",description:"",source:"@site/docs/examples/advanced/spinners.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/spinners",permalink:"/examples/advanced/spinners",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Spinners"},sidebar:"examplesSidebar",previous:{title:"Collision Detection",permalink:"/examples/advanced/collision-detection"},next:{title:"Basic",permalink:"/examples/sprite/basic"}},l={},c=[],u={toc:c};function m(e){let{components:s,...i}=e;return(0,n.kt)("wrapper",(0,t.Z)({},u,i,{components:s,mdxType:"MDXLayout"}),(0,n.kt)(a.Z,{id:"advanced.spinners",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/172e21b1.2f9b876b.js b/assets/js/172e21b1.2f9b876b.js deleted file mode 100644 index 48c4d9b95..000000000 --- a/assets/js/172e21b1.2f9b876b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4778],{5406:(e,i,s)=>{s.r(i),s.d(i,{assets:()=>l,contentTitle:()=>d,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>c});var t=s(7462),a=(s(7294),s(3905)),n=s(8010),p=s(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Dynamic"},d=void 0,o={unversionedId:"examples/graphics/dynamic",id:"examples/graphics/dynamic",title:"Dynamic",description:"",source:"@site/docs/examples/graphics/dynamic.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/dynamic",permalink:"/examples/graphics/dynamic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Dynamic"},sidebar:"examplesSidebar",previous:{title:"Advanced",permalink:"/examples/graphics/advanced"},next:{title:"Svg",permalink:"/examples/graphics/svg"}},l={},c=[],m={toc:c};function u(e){let{components:i,...s}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,s,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"graphics.dynamic",pixiVersion:p,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/172e21b1.a78eb096.js b/assets/js/172e21b1.a78eb096.js new file mode 100644 index 000000000..6caafe1c8 --- /dev/null +++ b/assets/js/172e21b1.a78eb096.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4778],{5406:(e,i,s)=>{s.r(i),s.d(i,{assets:()=>l,contentTitle:()=>d,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>c});var t=s(7462),a=(s(7294),s(3905)),n=s(8010),p=s(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Dynamic"},d=void 0,o={unversionedId:"examples/graphics/dynamic",id:"examples/graphics/dynamic",title:"Dynamic",description:"",source:"@site/docs/examples/graphics/dynamic.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/dynamic",permalink:"/8.x/examples/graphics/dynamic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Dynamic"},sidebar:"examplesSidebar",previous:{title:"Advanced",permalink:"/8.x/examples/graphics/advanced"},next:{title:"Svg",permalink:"/8.x/examples/graphics/svg"}},l={},c=[],m={toc:c};function u(e){let{components:i,...s}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,s,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"graphics.dynamic",pixiVersion:p,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/1961e970.0ccdeebd.js b/assets/js/1961e970.0ccdeebd.js deleted file mode 100644 index 3b3f0dbe7..000000000 --- a/assets/js/1961e970.0ccdeebd.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5582],{6456:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>u,frontMatter:()=>n,metadata:()=>N,toc:()=>l});var a=i(7462),M=(i(7294),i(3905));const n={title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},r=void 0,N={permalink:"/blog/pixi-v8-launches",source:"@site/blog/2024-03-05-pixi-v8-launches.md",title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",date:"2024-03-05T00:00:00.000Z",formattedDate:"March 5, 2024",tags:[{label:"PixiJS",permalink:"/blog/tags/pixi-js"},{label:"WebGPU",permalink:"/blog/tags/web-gpu"},{label:"WebGL",permalink:"/blog/tags/web-gl"}],readingTime:9.86,hasTruncateMarker:!1,authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],frontMatter:{title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},nextItem:{title:"PixiJS v8 Beta! \ud83c\udf89",permalink:"/blog/pixi-v8-beta"}},o={authorsImageUrls:[void 0]},l=[{value:"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8",id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8",level:2},{value:"\ud83d\udd17 Quick links",id:"-quick-links",level:2},{value:"\ud83c\udf81 Whats New?",id:"-whats-new",level:2},{value:"\ud83d\udcc8 New Performance Bar",id:"-new-performance-bar",level:4},{value:"\ud83d\udda5\ufe0f WebGPU Renderer",id:"\ufe0f-webgpu-renderer",level:4},{value:"\ud83d\udce6 New Package Structure",id:"-new-package-structure",level:4},{value:"\u2728 We promise the Renderer will work",id:"-we-promise-the-renderer-will-work",level:3},{value:"\ud83c\udf1f Scene Upgrades",id:"-scene-upgrades",level:4},{value:"\ud83c\udfa8 Graphics Upgrades",id:"-graphics-upgrades",level:4},{value:"\ud83d\udcdd Text Upgrades",id:"-text-upgrades",level:4},{value:"\ud83e\udd1d What now? Get involved!",id:"-what-now-get-involved",level:2},{value:"\ud83d\udcf2 Keep in touch",id:"-keep-in-touch",level:2}],g={toc:l};function u(e){let{components:t,...n}=e;return(0,M.kt)("wrapper",(0,a.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,M.kt)("p",null,"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time."),(0,M.kt)("h2",{id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8"},"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(2324).Z,width:"1600",height:"360"})),(0,M.kt)("p",null,"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration."),(0,M.kt)("p",null,"Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture."),(0,M.kt)("p",null,"With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code."),(0,M.kt)("p",null,"Our vision for v8 was clear:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Longevity:")," We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Innovation with WebGPU:")," Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Leveraging Modern JavaScript:")," The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Correcting Past Oversights:")," Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!)."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Boosting Performance:")," PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7.")),(0,M.kt)("p",null,"We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!"),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-quick-links"},"\ud83d\udd17 Quick links"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The new Docs for v8 can be found ",(0,M.kt)("a",{parentName:"li",href:"https://pixijs.download/v8.0.0/docs/index.html"},"here")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"/guides/migrations/v8"},"Migration")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"/examples"},"Examples")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/pixijs/open-games"},"Open Games"))),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-whats-new"},"\ud83c\udf81 Whats New?"),(0,M.kt)("p",null,"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above."),(0,M.kt)("h4",{id:"-new-performance-bar"},"\ud83d\udcc8 New Performance Bar"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"bunnies",src:i(4636).Z,width:"1600",height:"718"})),(0,M.kt)("p",null,"The performance of v8 is faster for ",(0,M.kt)("strong",{parentName:"p"},"both")," renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"CPU")," = time spent by the CPU rendering a single frame"),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"GPU")," = time spent by the GPU rendering a single frame")),(0,M.kt)("table",null,(0,M.kt)("thead",{parentName:"table"},(0,M.kt)("tr",{parentName:"thead"},(0,M.kt)("th",{parentName:"tr",align:null},"Bunny Situation"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"CPU Dif"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"GPU dif"))),(0,M.kt)("tbody",{parentName:"table"},(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites all moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~15ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"233%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites not moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~21ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.12ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"17417%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.5ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"1700%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites (changing scene structure)"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~24ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"108%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))))),(0,M.kt)("p",null,"These benchmark numbers are based on the Bunnymark test that you can try yourself."),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v7&count=100000&renderer=webgpu"},"v7 Bunnymark")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgpu"},"v8 Bunnymark - WebGPU")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgl"},"v8 Bunnymark - WebGL")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/GoodBoyDigital/pixi-bunnymark"},"Repo"))),(0,M.kt)("h4",{id:"\ufe0f-webgpu-renderer"},"\ud83d\udda5\ufe0f WebGPU Renderer"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS + webGPU = love",src:i(9349).Z,width:"1390",height:"322"})),(0,M.kt)("p",null,"We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements."),(0,M.kt)("h4",{id:"-new-package-structure"},"\ud83d\udce6 New Package Structure"),(0,M.kt)("p",null,'No more "lerna." PixiJS is now just one package with one import root: ',(0,M.kt)("inlineCode",{parentName:"p"},"import {stuff} from \u2018pixi.js\u2019"),". This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported."),(0,M.kt)("p",null,"Old:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite } from "@pixi/sprite";\nimport { Graphic } from "@pixi/graphics";\n')),(0,M.kt)("p",null,"New:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite, Graphic } from "pixi.js";\n')),(0,M.kt)("h3",{id:"-we-promise-the-renderer-will-work"},"\u2728 We ",(0,M.kt)("em",{parentName:"h3"},"promise")," the Renderer will work"),(0,M.kt)("p",null,"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere!"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Application, autoDetectRenderer } from "pixi.js";\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // or\n const renderer = await autoDetectRenderer({}); // WebGL or WebGPU\n\n // do pixi things\n})();\n')),(0,M.kt)("h4",{id:"-scene-upgrades"},"\ud83c\udf1f Scene Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(3012).Z,width:"1000",height:"400"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container({\n isRenderGroup:true // this containers transform is now handled on the GPU!\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as:")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"// will make all the children tinted red\ncontainer.tint = 'red'\n// will make all the children have the add blend mode\ncontainer.blendMode = 'add'\n")),(0,M.kt)("p",null,"Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"const texture = RenderTexture.create({\n width:100,\n height:100,\n antialias:true // easy as that\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics:",(0,M.kt)("ul",{parentName:"li"},(0,M.kt)("li",{parentName:"ul"},"ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend."))),(0,M.kt)("li",{parentName:"ul"},"It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!)\n\nmyContainer.blendMode = 'color-burn` // easy!\n")),(0,M.kt)("h4",{id:"-graphics-upgrades"},"\ud83c\udfa8 Graphics Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"alt text",src:i(8074).Z,width:"2952",height:"826"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics\n .rect(50, 50, 100, 100)\n .fill('blue');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"A ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsContext")," has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Support for SVG drawing has been added. For example:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics.svg('M 100 350 q 150 -300 300 0');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The new ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsPath")," class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the ",(0,M.kt)("inlineCode",{parentName:"p"},"buildGeometryFromPath")," function, opening up new possibilities for intricate and detailed graphic designs."))),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"}," const path = new GraphicsPath()\n .rect(-50, -50, 100, 100)\n\n // create geometry from the path:\n const geometry = buildGeometryFromPath({\n path,\n });\n\n const mesh = new Mesh({\n geometry,\n texture: Texture.WHITE,\n });\n\n")),(0,M.kt)("p",null,"For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the ",(0,M.kt)("a",{parentName:"p",href:"/guides/migrations/v8"},"migration guide"),", or why not jump in and play with some ",(0,M.kt)("a",{parentName:"p",href:"examples/graphics/simple"},"examples"),"."),(0,M.kt)("h4",{id:"-text-upgrades"},"\ud83d\udcdd Text Upgrades"),(0,M.kt)("p",null,"Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard."),(0,M.kt)("p",null,"BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"\nconst myText = new BitmapText({\n text: 'hello im a bitmap font!',\n // font will be dynamically created\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: 'red',\n }\n})\n")),(0,M.kt)("p",null,"Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const myText = new Text({\n text: 'hello im some fancy text',\n // font will be dynamically created!\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: { texture, color:'red'} // same as graphics api fills\n stroke: { width:3, color:'blue' } // same as graphics api strokes\n }\n})\n")),(0,M.kt)("h2",{id:"-what-now-get-involved"},"\ud83e\udd1d What now? Get involved!"),(0,M.kt)("p",null,"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on ",(0,M.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub")," and ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," to any issues that arise, valuing your input to make PixiJS even better."),(0,M.kt)("p",null,"A heartfelt thanks to our early adopters (everyone in ",(0,M.kt)("a",{parentName:"p",href:"https://discord.com/channels/734147990985375826/1143191340230914068"},"here"),") for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you!"),(0,M.kt)("p",null,"A final big shout-out to PlayCo for their support in making this release a reality!"),(0,M.kt)("p",null,"Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help."),(0,M.kt)("h2",{id:"-keep-in-touch"},"\ud83d\udcf2 Keep in touch"),(0,M.kt)("p",null,"To stay in the loop, we invite you to follow ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/Doormat23"},"Doormat23")," and ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/PixiJS"},"PixiJS")," on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," for direct engagement and real-time chit-chats."))}u.isMDXComponent=!0},3012:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/blend-modes-2c499d097578c17f1154867dbeead3a4.png"},9349:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-1-82838aaed491ecba56cb42c2ee5aa1fa.png"},8074:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-4-8e8a66475de0767192be60bb45ccf6f1.png"},4636:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-72e0ca7d8183d62c10a0f5e8dcc4ebda.png"},2324:(e,t,i)=>{i.d(t,{Z:()=>a});const a="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwMCIgaGVpZ2h0PSIzNjAiIHZpZXdCb3g9IjAgMCAxNjAwIDM2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2MDAiIGhlaWdodD0iMzYwIiBmaWxsPSIjMjUyQjM2Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xXzcpIj4KPHBhdGggZD0iTTc1MS41MjggMjkxVjEzOC4zNjVINzA1Ljg4M0M3MDUuNzI4IDEzOC4zNjUgNzA1LjU2NiAxMzguMzY1IDcwNS4zOTcgMTM4LjM3M0M2OTcuMTkzIDEzOC42NTggNjkwLjYgMTQ1LjQwMiA2OTAuNTIzIDE1My42MDRDNjkwLjUyMyAxNTMuNjUgNjkwLjUyMyAxNTMuNjk2IDY5MC41MjMgMTUzLjczNVYyNzUuODg0QzY5MC41MjMgMjc2LjEzOSA2OTAuNTM5IDI3Ni40MjQgNjkwLjU2MiAyNzYuNzRDNjkxLjI1NiAyODQuNjAyIDY5Ny44NDEgMjkwLjYzIDcwNS43MjggMjkwLjk2OUM3MDYuMDYgMjkwLjk4NSA3MDYuMzc2IDI5MC45OTIgNzA2LjY2OSAyOTAuOTkySDc1MS41MjhWMjkxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTM5LjY5NCAyOTAuOTIzQzk0OC4xOTggMjkwLjI4MyA5NTQuOTMgMjgzLjI4NCA5NTQuOTkxIDI3NC43NTFDOTU0Ljk5MSAyNzQuNjk3IDk1NC45OTEgMjc0LjY0MyA5NTQuOTkxIDI3NC41OVYxNTMuOTEyQzk1NC45OTEgMTUzLjg2NiA5NTQuOTkxIDE1My44MTIgOTU0Ljk5MSAxNTMuNzY2Qzk1NC45NTMgMTQ1LjMzMyA5NDguMDkgMTM4LjQ1NyA5MzkuNjU1IDEzOC4zNzNDOTM5LjU5NCAxMzguMzczIDkzOS41MzIgMTM4LjM3MyA5MzkuNDcgMTM4LjM3M0g4OTMuOTg2VjI5MS4wMDhIOTM3Ljc4OUM5MzguMzgzIDI5MS4wMDggOTM5LjAyMyAyOTAuOTc3IDkzOS42OTQgMjkwLjkzMVYyOTAuOTIzWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNNzQyLjk3IDEzOC4zNzNINzkwLjc3NEM3OTUuNDYyIDEzOC4zNzMgODAyLjA5MyAxNDIuMTY1IDgwNS41OTQgMTQ2Ljg1MUw5MDYuOTYzIDI4Mi41MTNDOTEwLjQ2NCAyODcuMiA5MDkuNSAyOTAuOTkyIDkwNC44MiAyOTAuOTkySDg1Ny4wMTVDODUyLjMyNyAyOTAuOTkyIDg0NS42OTYgMjg3LjIgODQyLjE5NSAyODIuNTEzTDc0MC44MTkgMTQ2Ljg1MUM3MzcuMzE4IDE0Mi4xNzMgNzM4LjI4MiAxMzguMzczIDc0Mi45NjIgMTM4LjM3M0g3NDIuOTdaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik05MDEuMDM0IDEzOC4zNzNIODUzLjIyOUM4NDguNTQxIDEzOC4zNzMgODQxLjkxIDE0Mi4xNjUgODM4LjQxIDE0Ni44NTFMNzM3LjA0IDI4Mi41MTNDNzMzLjU0IDI4Ny4yIDczNC41MDQgMjkwLjk5MiA3MzkuMTg0IDI5MC45OTJINzg2Ljk4OUM3OTEuNjc3IDI5MC45OTIgNzk4LjMwOCAyODcuMiA4MDEuODA4IDI4Mi41MTNMOTAzLjE3NyAxNDYuODUxQzkwNi42NzggMTQyLjE3MyA5MDUuNzE0IDEzOC4zNzMgOTAxLjAzNCAxMzguMzczWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTI0LjkzNiA2OUM5NDEuMTEzIDY5IDk1NC4yMzYgODIuMTExNCA5NTQuMjM2IDk4LjI5MDZDOTU0LjIzNiAxMTQuNDcgOTQxLjEyIDEyNy41ODEgOTI0LjkzNiAxMjcuNTgxQzkwOC43NTIgMTI3LjU4MSA4OTUuNjM2IDExNC40NyA4OTUuNjM2IDk4LjI5MDZDODk1LjYzNiA4Mi4xMTE0IDkwOC43NTIgNjkgOTI0LjkzNiA2OVoiIGZpbGw9IiNFNzIyNjQiLz4KPHBhdGggZD0iTTcyMS4zODEgNjlDNzM3LjU1NyA2OSA3NTAuNjggODIuMTExNCA3NTAuNjggOTguMjkwNkM3NTAuNjggMTE0LjQ3IDczNy41NjUgMTI3LjU4MSA3MjEuMzgxIDEyNy41ODFDNzA1LjE5NiAxMjcuNTgxIDY5Mi4wODEgMTE0LjQ3IDY5Mi4wODEgOTguMjkwNkM2OTIuMDgxIDgyLjExMTQgNzA1LjE5NiA2OSA3MjEuMzgxIDY5WiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNMTA1Mi45MyA2OUgxMDA2LjY3Qzk5MC4wNTggNjkgOTc2LjU5NiA4Mi40NTgzIDk3Ni41OTYgOTkuMDYxNVYxNDUuMzFDOTc2LjU5NiAxNjEuOTEzIDk5MC4wNTggMTc1LjM3MSAxMDA2LjY3IDE3NS4zNzFIMTA1Mi45M0MxMDY5LjU0IDE3NS4zNzEgMTA4MyAxNjEuOTEzIDEwODMgMTQ1LjMxVjk5LjA2MTVDMTA4MyA4Mi40NTgzIDEwNjkuNTQgNjkgMTA1Mi45MyA2OVpNMTAyMi4zNyA5OS4zMjM1VjEzMC41NDFDMTAyMi4zNyAxMzQuNzUgMTAyMS41MyAxMzguNDAzIDEwMTkuODUgMTQxLjQ5NEMxMDE4LjE3IDE0NC41ODUgMTAxNS43NyAxNDYuOTgyIDEwMTIuNjYgMTQ4LjY4NkMxMDA5LjU0IDE1MC4zODkgMTAwNS44NSAxNTEuMjQ1IDEwMDEuNTkgMTUxLjI0NUM5OTcuOTYyIDE1MS4yNDUgOTk0LjgzMSAxNTAuNTkgOTkyLjE4NiAxNDkuMjg3Qzk4OS41NDIgMTQ3Ljk4NSA5ODcuMzIxIDE0Ni4xNTggOTg1LjUwOSAxNDMuODE0Qzk4NS41MDkgMTQzLjgxNCA5ODAuNDU5IDEzOC4zNjUgOTg1LjA4NSAxMzQuNTE5Qzk4OS44MTIgMTMwLjU4NyA5OTMuOTA2IDEzNS43MzYgOTkzLjkwNiAxMzUuNzM2Qzk5NC44NjIgMTM3LjA3IDk5NS45OTUgMTM4LjA3MiA5OTcuMzA2IDEzOC43MzVDOTk4LjYwOSAxMzkuNDA1IDEwMDAuMDkgMTM5LjczNyAxMDAxLjc1IDEzOS43MzdDMTAwMy4yOSAxMzkuNzM3IDEwMDQuNjUgMTM5LjQyMSAxMDA1LjgzIDEzOC43ODFDMTAwNyAxMzguMTQxIDEwMDcuOTMgMTM3LjE4NiAxMDA4LjYzIDEzNS45MDZDMTAwOS4zMiAxMzQuNjI2IDEwMDkuNjcgMTMzLjA1NCAxMDA5LjY3IDEzMS4xODlWOTkuMzMxMkMxMDA5LjY3IDk5LjMzMTIgMTAwOS41NCA5Mi45MDI3IDEwMTUuOTMgOTIuOTAyN0MxMDIyLjMxIDkyLjkwMjcgMTAyMi4zOCA5OS4zMzEyIDEwMjIuMzggOTkuMzMxMkwxMDIyLjM3IDk5LjMyMzVaTTEwNjcuOCAxNDQuODMyQzEwNjQuMyAxNDguMjg1IDEwNTkuMTYgMTUwLjM2NiAxMDUyLjM3IDE1MS4wNzVDMTA0Ny43MSAxNTEuNTY5IDEwNDMuNjQgMTUxLjI0NSAxMDQwLjE3IDE1MC4xMTJDMTAzNi43IDE0OC45NzkgMTAzMy40NSAxNDcuMDEzIDEwMzAuNDIgMTQ0LjIyM0MxMDMwLjQyIDE0NC4yMjMgMTAyNS43MyAxNDAuMTg0IDEwMjkuNTEgMTM1LjExMkMxMDMyLjM1IDEzMS4yOTcgMTAzNy42OSAxMzUuMjU5IDEwMzcuNjkgMTM1LjI1OUMxMDM5LjcxIDEzNy4xMzkgMTA0MS44NyAxMzguNTI3IDEwNDQuMTkgMTM5LjQzNkMxMDQ2LjUxIDE0MC4zNDYgMTA0OS4xMyAxNDAuNjQ2IDEwNTIuMDUgMTQwLjMzOEMxMDU0LjY1IDE0MC4wNjggMTA1Ni42MiAxMzkuMzU5IDEwNTcuOTggMTM4LjIyNkMxMDU5LjM0IDEzNy4wOTMgMTA1OS45MiAxMzUuNjc1IDEwNTkuNzUgMTMzLjk3OUMxMDU5LjU5IDEzMi40OTkgMTA1OSAxMzEuMzI3IDEwNTcuOTcgMTMwLjQ3MkMxMDU2Ljk1IDEyOS42MTYgMTA1NS42IDEyOC45MyAxMDUzLjkzIDEyOC40MDZDMTA1Mi4yNyAxMjcuODgyIDEwNTAuNDggMTI3LjQxOSAxMDQ4LjU2IDEyNy4wMDNDMTA0Ni42NCAxMjYuNTg3IDEwNDQuNjkgMTI2LjA1NSAxMDQyLjcyIDEyNS40MDhDMTA0MC43NSAxMjQuNzYgMTAzOC45MiAxMjMuOTA1IDEwMzcuMjIgMTIyLjg0OUMxMDM1LjUzIDEyMS43OTMgMTAzNC4xMSAxMjAuMzY3IDEwMzIuOTUgMTE4LjU1NUMxMDMxLjc5IDExNi43NDQgMTAzMS4wNyAxMTQuNDM5IDEwMzAuNzggMTExLjYzM0MxMDMwLjQxIDEwOC4xMzQgMTAzMC45MyAxMDUuMDUxIDEwMzIuMzMgMTAyLjM4NEMxMDMzLjc1IDk5LjcxNjYgMTAzNS44OSA5Ny41NzM4IDEwMzguNzcgOTUuOTU1MUMxMDQxLjY2IDk0LjMzNjQgMTA0NS4wMSA5My4zMjY3IDEwNDguODMgOTIuOTI1OEMxMDUyLjgxIDkyLjUwOTYgMTA1Ni40OCA5Mi44MTc5IDEwNTkuODYgOTMuODUwOEMxMDYzLjI0IDk0Ljg5MTQgMTA2Ni4xMSA5Ni40NjM4IDEwNjguNDggOTguNTY4MUMxMDY4LjQ4IDk4LjU2ODEgMTA3MS44IDEwMS4zNTEgMTA2OS4xMSAxMDYuMTM3QzEwNjcuMDIgMTA5Ljg2IDEwNjEuMTUgMTA3LjYyNSAxMDYxLjE1IDEwNy42MjVDMTA1OS4yNyAxMDYuMDUzIDEwNTcuNDIgMTA0LjkzNSAxMDU1LjYxIDEwNC4yNzJDMTA1My44IDEwMy42MDkgMTA1MS44MyAxMDMuMzg2IDEwNDkuNzEgMTAzLjYwOUMxMDQ3LjU0IDEwMy44NCAxMDQ1Ljg2IDEwNC40MzQgMTA0NC42NyAxMDUuMzgyQzEwNDMuNDggMTA2LjMzOCAxMDQyLjk4IDEwNy41ODcgMTA0My4xNCAxMDkuMTJDMTA0My4yOCAxMTAuNSAxMDQzLjg4IDExMS41NzIgMTA0NC45MyAxMTIuMzVDMTA0NS45NyAxMTMuMTIxIDEwNDcuMzEgMTEzLjc0NSAxMDQ4Ljk0IDExNC4yMTZDMTA1MC41NyAxMTQuNjg2IDEwNTIuMzcgMTE1LjE0IDEwNTQuMzEgMTE1LjU4QzEwNTYuMjYgMTE2LjAxOSAxMDU4LjIyIDExNi41NTEgMTA2MC4xOCAxMTcuMTc1QzEwNjIuMTUgMTE3LjggMTA2My45OCAxMTguNjk0IDEwNjUuNjUgMTE5Ljg1OEMxMDY3LjMyIDEyMS4wMjIgMTA2OC43NiAxMjIuNTMzIDEwNjkuOTUgMTI0LjM5QzEwNzEuMTMgMTI2LjI0OCAxMDcxLjg4IDEyOC42NiAxMDcyLjIgMTMxLjYyOEMxMDcyLjc2IDEzNi45ODUgMTA3MS4zIDE0MS4zODYgMTA2Ny44IDE0NC44MzJaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik01OTkuNjMzIDY5LjI2MjFDNTUzLjk5NSA2OS4yNjIxIDUxNyAxMDYuMjQ1IDUxNyAxNTEuODY5QzUxNyAxNTIuNzY0IDUxNy4wMTUgMTUzLjY1IDUxNy4wNDYgMTU0LjUzNlYyMzMuOTIyQzUxNy4wNDYgMjMzLjkyMiA1MTcuMDQ2IDIzMy45MjkgNTE3LjA0NiAyMzMuOTM3VjI0Mi4wMDhDNTE3LjA0NiAyNDIuMDA4IDUxNy4wNDYgMjQyLjAwOCA1MTcuMDU0IDI0MlYyNjEuOTU2QzUxNy4wNTQgMjYyLjExIDUxNy4wNDYgMjYyLjI1NyA1MTcuMDQ2IDI2Mi40MTFDNTE3LjA0NiAyNjIuNTY1IDUxNy4wNTQgMjYyLjcxOSA1MTcuMDYyIDI2Mi44NzNWMjYzLjI0M0g1MTcuMDY5QzUxNy41MDkgMjc4LjU3NSA1MzAuMDY5IDI5MC44NjkgNTQ1LjUxMyAyOTAuODY5QzU2MC45NTcgMjkwLjg2OSA1NzMuNjQ5IDI3OC41NzUgNTc0LjA4OCAyNjMuMjQzVjIzMy45MzdINTk3LjY5N0M1OTguMzM3IDIzMy45NTMgNTk4Ljk4NSAyMzMuOTYgNTk5LjYyNSAyMzMuOTZDNjQ1LjI2MyAyMzMuOTYgNjgyLjI1OCAxOTcuNDkzIDY4Mi4yNTggMTUxLjg2OUM2ODIuMjU4IDEwNi4yNDUgNjQ1LjI3MSA2OS4yNjIxIDU5OS42MzMgNjkuMjYyMVpNNTk5LjYzMyAxNzYuOTIxQzU5NC41ODIgMTc2LjkyMSA1ODEuNzgzIDE3Ni45MjEgNTc2LjQ3MSAxNzYuOTIxSDU3NC4xMTFWMTUxLjg3N0M1NzQuMTExIDEzNy44NDEgNTg1LjU5MiAxMjYuNDU2IDU5OS42MzMgMTI2LjQ1NkM2MTMuNjczIDEyNi40NTYgNjI1LjA2MiAxMzcuODQxIDYyNS4wNjIgMTUxLjg3N0M2MjUuMDYyIDE2NS45MTQgNjEzLjY3MyAxNzYuOTI4IDU5OS42MzMgMTc2LjkyOFYxNzYuOTIxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBvcGFjaXR5PSIwLjE1IiBkPSJNNTQ0Ljc5NiAyMzMuOTUzSDU3NC4wNVYyNjMuMjUxSDU3My45MThDNTczLjQ3OSAyNzguNTgyIDU2MC45MTkgMjkwLjg3NyA1NDUuNDc1IDI5MC44NzdDNTMwLjAzMSAyOTAuODc3IDUxNy40NjMgMjc4LjU4MiA1MTcuMDIzIDI2My4yNTFINTE3LjAxNVYyNjIuODgxQzUxNy4wMTUgMjYyLjcyNyA1MTcgMjYyLjU3MyA1MTcgMjYyLjQxOEM1MTcgMjYyLjI2NCA1MTcuMDA4IDI2Mi4xMTggNTE3LjAwOCAyNjEuOTY0VjI1OS44OThDNTE3Ljg4NyAyNDYuMTg1IDUzMC44MzMgMjMzLjk1MyA1NDQuNzg4IDIzMy45NTNINTQ0Ljc5NloiIGZpbGw9IiMxRDFEMUIiLz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTUxNy4wNDYgMTk4LjcxNlYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRWMTY1LjcwMUM1NTEuMDggMTY4LjEyMSA1MzAuNzAxIDE4MC41MDkgNTE3LjA0NiAxOTguNzE2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTUxNy4wOTMgMjYwLjQ1MUM1MjMuODE2IDI1NS41MTggNTMxLjk3NCAyNTIuNjEyIDU0MC43NzkgMjUyLjYxMkg1NzQuMTAzVjIzMy45NDNINTQ1LjU1MkM1MzAuNDcgMjMzLjk0MyA1MTguMTQxIDI0NS42MzYgNTE3LjA5MyAyNjAuNDUxWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY2LjczNEM1NTAuODg3IDE2OS4wMDggNTMwLjQwOCAxODEuNjY1IDUxNy4wNDYgMjAwLjI1VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMwLjg3OSAyMzMuOTQzIDUxOC44MDQgMjQ1LjAxOSA1MTcuMiAyNTkuMjY0QzUyMy45MTYgMjU0LjAyMiA1MzIuMjEzIDI1MC45MjQgNTQxLjIxMSAyNTAuOTI0SDU3NC4wOTZWMjMzLjk1MUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE2Ny43NTlDNTUwLjY3OSAxNjkuODc5IDUzMC4wNjkgMTgyLjg0NCA1MTcuMDQ2IDIwMS44NDVWMjMzLjkzNUM1MTcuMDQ2IDIwMi45MDEgNTQzLjEzMSAxNzcuMzU2IDU3NC4wOTYgMTc2LjkzMlYxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS4yODcgMjMzLjk0MyA1MTkuNDgzIDI0NC40MDMgNTE3LjM1NSAyNTguMDY5QzUyNC4wMzIgMjUyLjUyNyA1MzIuNDY3IDI0OS4yMiA1NDEuNjQzIDI0OS4yMkg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY4Ljc5MkM1NTAuNDQgMTcwLjc1IDUyOS42OTEgMTg0LjA3IDUxNy4wNDYgMjAzLjUzM1YyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS42OTYgMjMzLjk0MyA1MjAuMTU0IDI0My44MTcgNTE3LjU2MyAyNTYuOTA1QzUyNC4xODYgMjUxLjA1NSA1MzIuNzM3IDI0Ny41MjUgNTQyLjA4MiAyNDcuNTI1SDU3NC4xMDNWMjMzLjk0M0g1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY5LjgzM0M1NTAuMTYzIDE3MS42MDUgNTI5LjI1MiAxODUuMzQyIDUxNy4wNDYgMjA1LjMxNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMi4xMTIgMjMzLjk0MyA1MjAuODU1IDI0My4yMzEgNTE3LjgyNSAyNTUuNzI2QzUyNC4zNTYgMjQ5LjU3NSA1MzMuMDE1IDI0NS44MjEgNTQyLjUxNCAyNDUuODIxSDU3NC4wOTZWMjMzLjkzNUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE3MC44NzNDNTQ5Ljg0NiAxNzIuNDYxIDUyOC43MzUgMTg2LjY2OCA1MTcuMDQ2IDIwNy4yMjZWMjMzLjk0M0M1MTcuMDQ2IDIwMi45MDkgNTQzLjEzMSAxNzcuMzY0IDU3NC4wOTYgMTc2Ljk0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTU0NS41NTIgMjMzLjk0M0M1MzIuNTM2IDIzMy45NDMgNTIxLjU3MiAyNDIuNjUzIDUxOC4xNDEgMjU0LjU0N0M1MjQuNTU2IDI0OC4wOTUgNTMzLjMgMjQ0LjEyNSA1NDIuOTUzIDI0NC4xMjVINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzEuOTIxQzU0OS40NzYgMTczLjMwMSA1MjguMTI2IDE4OC4wNzggNTE3LjA0NiAyMDkuMjk5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMyLjk3NiAyMzMuOTQzIDUyMi4zMDUgMjQyLjA4MyA1MTguNTA0IDI1My4zNzVDNTI0Ljc3MiAyNDYuNjMgNTMzLjYwMSAyNDIuNDI5IDU0My4zNzcgMjQyLjQyOUg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTcyLjk3OEM1NDkuMDIxIDE3NC4xMzQgNTI3LjM4NiAxODkuNjA1IDUxNy4wNDYgMjExLjYwNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMy40MDggMjMzLjk0MyA1MjMuMDQ1IDI0MS41MzUgNTE4LjkyIDI1Mi4yMjZDNTI1LjAxOSAyNDUuMTgxIDUzMy45MDEgMjQwLjczNCA1NDMuODE3IDI0MC43MzRINTc0LjEwNFYyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzQuMDU3QzU0OC40NDMgMTc0Ljk3NCA1MjYuNDM4IDE5MS4zMTYgNTE3LjA0NiAyMTQuMjc5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMzLjg0NyAyMzMuOTQzIDUyMy43OTMgMjQwLjk5NiA1MTkuMzk4IDI1MS4wNzhDNTI1LjI4MSAyNDMuNzMyIDUzNC4yMjUgMjM5LjAzOCA1NDQuMjU2IDIzOS4wMzhINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMV83Ij4KPHJlY3Qgd2lkdGg9IjU2NiIgaGVpZ2h0PSIyMjIuMDA4IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTE3IDY5KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="}}]); \ No newline at end of file diff --git a/assets/js/1961e970.b3c53115.js b/assets/js/1961e970.b3c53115.js new file mode 100644 index 000000000..0a7c12b80 --- /dev/null +++ b/assets/js/1961e970.b3c53115.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5582],{6456:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>u,frontMatter:()=>n,metadata:()=>N,toc:()=>l});var a=i(7462),M=(i(7294),i(3905));const n={title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},r=void 0,N={permalink:"/blog/pixi-v8-launches",source:"@site/blog/2024-03-05-pixi-v8-launches.md",title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",date:"2024-03-05T00:00:00.000Z",formattedDate:"March 5, 2024",tags:[{label:"PixiJS",permalink:"/blog/tags/pixi-js"},{label:"WebGPU",permalink:"/blog/tags/web-gpu"},{label:"WebGL",permalink:"/blog/tags/web-gl"}],readingTime:9.86,hasTruncateMarker:!1,authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],frontMatter:{title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},nextItem:{title:"PixiJS v8 Beta! \ud83c\udf89",permalink:"/blog/pixi-v8-beta"}},o={authorsImageUrls:[void 0]},l=[{value:"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8",id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8",level:2},{value:"\ud83d\udd17 Quick links",id:"-quick-links",level:2},{value:"\ud83c\udf81 Whats New?",id:"-whats-new",level:2},{value:"\ud83d\udcc8 New Performance Bar",id:"-new-performance-bar",level:4},{value:"\ud83d\udda5\ufe0f WebGPU Renderer",id:"\ufe0f-webgpu-renderer",level:4},{value:"\ud83d\udce6 New Package Structure",id:"-new-package-structure",level:4},{value:"\u2728 We promise the Renderer will work",id:"-we-promise-the-renderer-will-work",level:3},{value:"\ud83c\udf1f Scene Upgrades",id:"-scene-upgrades",level:4},{value:"\ud83c\udfa8 Graphics Upgrades",id:"-graphics-upgrades",level:4},{value:"\ud83d\udcdd Text Upgrades",id:"-text-upgrades",level:4},{value:"\ud83e\udd1d What now? Get involved!",id:"-what-now-get-involved",level:2},{value:"\ud83d\udcf2 Keep in touch",id:"-keep-in-touch",level:2}],g={toc:l};function u(e){let{components:t,...n}=e;return(0,M.kt)("wrapper",(0,a.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,M.kt)("p",null,"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time."),(0,M.kt)("h2",{id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8"},"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(2324).Z,width:"1600",height:"360"})),(0,M.kt)("p",null,"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration."),(0,M.kt)("p",null,"Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture."),(0,M.kt)("p",null,"With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code."),(0,M.kt)("p",null,"Our vision for v8 was clear:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Longevity:")," We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Innovation with WebGPU:")," Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Leveraging Modern JavaScript:")," The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Correcting Past Oversights:")," Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!)."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Boosting Performance:")," PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7.")),(0,M.kt)("p",null,"We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!"),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-quick-links"},"\ud83d\udd17 Quick links"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The new Docs for v8 can be found ",(0,M.kt)("a",{parentName:"li",href:"https://pixijs.download/v8.0.0/docs/index.html"},"here")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"8.x/guides/migrations/v8"},"Migration")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"8.x/examples"},"Examples")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/pixijs/open-games"},"Open Games"))),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-whats-new"},"\ud83c\udf81 Whats New?"),(0,M.kt)("p",null,"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above."),(0,M.kt)("h4",{id:"-new-performance-bar"},"\ud83d\udcc8 New Performance Bar"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"bunnies",src:i(4636).Z,width:"1600",height:"718"})),(0,M.kt)("p",null,"The performance of v8 is faster for ",(0,M.kt)("strong",{parentName:"p"},"both")," renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"CPU")," = time spent by the CPU rendering a single frame"),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"GPU")," = time spent by the GPU rendering a single frame")),(0,M.kt)("table",null,(0,M.kt)("thead",{parentName:"table"},(0,M.kt)("tr",{parentName:"thead"},(0,M.kt)("th",{parentName:"tr",align:null},"Bunny Situation"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"CPU Dif"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"GPU dif"))),(0,M.kt)("tbody",{parentName:"table"},(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites all moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~15ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"233%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites not moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~21ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.12ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"17417%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.5ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"1700%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites (changing scene structure)"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~24ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"108%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))))),(0,M.kt)("p",null,"These benchmark numbers are based on the Bunnymark test that you can try yourself."),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v7&count=100000&renderer=webgpu"},"v7 Bunnymark")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgpu"},"v8 Bunnymark - WebGPU")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgl"},"v8 Bunnymark - WebGL")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/GoodBoyDigital/pixi-bunnymark"},"Repo"))),(0,M.kt)("h4",{id:"\ufe0f-webgpu-renderer"},"\ud83d\udda5\ufe0f WebGPU Renderer"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS + webGPU = love",src:i(9349).Z,width:"1390",height:"322"})),(0,M.kt)("p",null,"We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements."),(0,M.kt)("h4",{id:"-new-package-structure"},"\ud83d\udce6 New Package Structure"),(0,M.kt)("p",null,'No more "lerna." PixiJS is now just one package with one import root: ',(0,M.kt)("inlineCode",{parentName:"p"},"import {stuff} from \u2018pixi.js\u2019"),". This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported."),(0,M.kt)("p",null,"Old:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite } from "@pixi/sprite";\nimport { Graphic } from "@pixi/graphics";\n')),(0,M.kt)("p",null,"New:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite, Graphic } from "pixi.js";\n')),(0,M.kt)("h3",{id:"-we-promise-the-renderer-will-work"},"\u2728 We ",(0,M.kt)("em",{parentName:"h3"},"promise")," the Renderer will work"),(0,M.kt)("p",null,"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere!"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Application, autoDetectRenderer } from "pixi.js";\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // or\n const renderer = await autoDetectRenderer({}); // WebGL or WebGPU\n\n // do pixi things\n})();\n')),(0,M.kt)("h4",{id:"-scene-upgrades"},"\ud83c\udf1f Scene Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(3012).Z,width:"1000",height:"400"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container({\n isRenderGroup:true // this containers transform is now handled on the GPU!\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as:")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"// will make all the children tinted red\ncontainer.tint = 'red'\n// will make all the children have the add blend mode\ncontainer.blendMode = 'add'\n")),(0,M.kt)("p",null,"Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"const texture = RenderTexture.create({\n width:100,\n height:100,\n antialias:true // easy as that\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics:",(0,M.kt)("ul",{parentName:"li"},(0,M.kt)("li",{parentName:"ul"},"ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend."))),(0,M.kt)("li",{parentName:"ul"},"It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!)\n\nmyContainer.blendMode = 'color-burn` // easy!\n")),(0,M.kt)("h4",{id:"-graphics-upgrades"},"\ud83c\udfa8 Graphics Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"alt text",src:i(8074).Z,width:"2952",height:"826"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics\n .rect(50, 50, 100, 100)\n .fill('blue');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"A ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsContext")," has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Support for SVG drawing has been added. For example:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics.svg('M 100 350 q 150 -300 300 0');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The new ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsPath")," class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the ",(0,M.kt)("inlineCode",{parentName:"p"},"buildGeometryFromPath")," function, opening up new possibilities for intricate and detailed graphic designs."))),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"}," const path = new GraphicsPath()\n .rect(-50, -50, 100, 100)\n\n // create geometry from the path:\n const geometry = buildGeometryFromPath({\n path,\n });\n\n const mesh = new Mesh({\n geometry,\n texture: Texture.WHITE,\n });\n\n")),(0,M.kt)("p",null,"For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the ",(0,M.kt)("a",{parentName:"p",href:"8.x/guides/migrations/v8"},"migration guide"),", or why not jump in and play with some ",(0,M.kt)("a",{parentName:"p",href:"8.x/examples/graphics/simple"},"examples"),"."),(0,M.kt)("h4",{id:"-text-upgrades"},"\ud83d\udcdd Text Upgrades"),(0,M.kt)("p",null,"Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard."),(0,M.kt)("p",null,"BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"\nconst myText = new BitmapText({\n text: 'hello im a bitmap font!',\n // font will be dynamically created\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: 'red',\n }\n})\n")),(0,M.kt)("p",null,"Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const myText = new Text({\n text: 'hello im some fancy text',\n // font will be dynamically created!\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: { texture, color:'red'} // same as graphics api fills\n stroke: { width:3, color:'blue' } // same as graphics api strokes\n }\n})\n")),(0,M.kt)("h2",{id:"-what-now-get-involved"},"\ud83e\udd1d What now? Get involved!"),(0,M.kt)("p",null,"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on ",(0,M.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub")," and ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," to any issues that arise, valuing your input to make PixiJS even better."),(0,M.kt)("p",null,"A heartfelt thanks to our early adopters (everyone in ",(0,M.kt)("a",{parentName:"p",href:"https://discord.com/channels/734147990985375826/1143191340230914068"},"here"),") for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you!"),(0,M.kt)("p",null,"A final big shout-out to PlayCo for their support in making this release a reality!"),(0,M.kt)("p",null,"Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help."),(0,M.kt)("h2",{id:"-keep-in-touch"},"\ud83d\udcf2 Keep in touch"),(0,M.kt)("p",null,"To stay in the loop, we invite you to follow ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/Doormat23"},"Doormat23")," and ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/PixiJS"},"PixiJS")," on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," for direct engagement and real-time chit-chats."))}u.isMDXComponent=!0},3012:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/blend-modes-2c499d097578c17f1154867dbeead3a4.png"},9349:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-1-82838aaed491ecba56cb42c2ee5aa1fa.png"},8074:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-4-8e8a66475de0767192be60bb45ccf6f1.png"},4636:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-72e0ca7d8183d62c10a0f5e8dcc4ebda.png"},2324:(e,t,i)=>{i.d(t,{Z:()=>a});const a="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwMCIgaGVpZ2h0PSIzNjAiIHZpZXdCb3g9IjAgMCAxNjAwIDM2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2MDAiIGhlaWdodD0iMzYwIiBmaWxsPSIjMjUyQjM2Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xXzcpIj4KPHBhdGggZD0iTTc1MS41MjggMjkxVjEzOC4zNjVINzA1Ljg4M0M3MDUuNzI4IDEzOC4zNjUgNzA1LjU2NiAxMzguMzY1IDcwNS4zOTcgMTM4LjM3M0M2OTcuMTkzIDEzOC42NTggNjkwLjYgMTQ1LjQwMiA2OTAuNTIzIDE1My42MDRDNjkwLjUyMyAxNTMuNjUgNjkwLjUyMyAxNTMuNjk2IDY5MC41MjMgMTUzLjczNVYyNzUuODg0QzY5MC41MjMgMjc2LjEzOSA2OTAuNTM5IDI3Ni40MjQgNjkwLjU2MiAyNzYuNzRDNjkxLjI1NiAyODQuNjAyIDY5Ny44NDEgMjkwLjYzIDcwNS43MjggMjkwLjk2OUM3MDYuMDYgMjkwLjk4NSA3MDYuMzc2IDI5MC45OTIgNzA2LjY2OSAyOTAuOTkySDc1MS41MjhWMjkxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTM5LjY5NCAyOTAuOTIzQzk0OC4xOTggMjkwLjI4MyA5NTQuOTMgMjgzLjI4NCA5NTQuOTkxIDI3NC43NTFDOTU0Ljk5MSAyNzQuNjk3IDk1NC45OTEgMjc0LjY0MyA5NTQuOTkxIDI3NC41OVYxNTMuOTEyQzk1NC45OTEgMTUzLjg2NiA5NTQuOTkxIDE1My44MTIgOTU0Ljk5MSAxNTMuNzY2Qzk1NC45NTMgMTQ1LjMzMyA5NDguMDkgMTM4LjQ1NyA5MzkuNjU1IDEzOC4zNzNDOTM5LjU5NCAxMzguMzczIDkzOS41MzIgMTM4LjM3MyA5MzkuNDcgMTM4LjM3M0g4OTMuOTg2VjI5MS4wMDhIOTM3Ljc4OUM5MzguMzgzIDI5MS4wMDggOTM5LjAyMyAyOTAuOTc3IDkzOS42OTQgMjkwLjkzMVYyOTAuOTIzWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNNzQyLjk3IDEzOC4zNzNINzkwLjc3NEM3OTUuNDYyIDEzOC4zNzMgODAyLjA5MyAxNDIuMTY1IDgwNS41OTQgMTQ2Ljg1MUw5MDYuOTYzIDI4Mi41MTNDOTEwLjQ2NCAyODcuMiA5MDkuNSAyOTAuOTkyIDkwNC44MiAyOTAuOTkySDg1Ny4wMTVDODUyLjMyNyAyOTAuOTkyIDg0NS42OTYgMjg3LjIgODQyLjE5NSAyODIuNTEzTDc0MC44MTkgMTQ2Ljg1MUM3MzcuMzE4IDE0Mi4xNzMgNzM4LjI4MiAxMzguMzczIDc0Mi45NjIgMTM4LjM3M0g3NDIuOTdaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik05MDEuMDM0IDEzOC4zNzNIODUzLjIyOUM4NDguNTQxIDEzOC4zNzMgODQxLjkxIDE0Mi4xNjUgODM4LjQxIDE0Ni44NTFMNzM3LjA0IDI4Mi41MTNDNzMzLjU0IDI4Ny4yIDczNC41MDQgMjkwLjk5MiA3MzkuMTg0IDI5MC45OTJINzg2Ljk4OUM3OTEuNjc3IDI5MC45OTIgNzk4LjMwOCAyODcuMiA4MDEuODA4IDI4Mi41MTNMOTAzLjE3NyAxNDYuODUxQzkwNi42NzggMTQyLjE3MyA5MDUuNzE0IDEzOC4zNzMgOTAxLjAzNCAxMzguMzczWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTI0LjkzNiA2OUM5NDEuMTEzIDY5IDk1NC4yMzYgODIuMTExNCA5NTQuMjM2IDk4LjI5MDZDOTU0LjIzNiAxMTQuNDcgOTQxLjEyIDEyNy41ODEgOTI0LjkzNiAxMjcuNTgxQzkwOC43NTIgMTI3LjU4MSA4OTUuNjM2IDExNC40NyA4OTUuNjM2IDk4LjI5MDZDODk1LjYzNiA4Mi4xMTE0IDkwOC43NTIgNjkgOTI0LjkzNiA2OVoiIGZpbGw9IiNFNzIyNjQiLz4KPHBhdGggZD0iTTcyMS4zODEgNjlDNzM3LjU1NyA2OSA3NTAuNjggODIuMTExNCA3NTAuNjggOTguMjkwNkM3NTAuNjggMTE0LjQ3IDczNy41NjUgMTI3LjU4MSA3MjEuMzgxIDEyNy41ODFDNzA1LjE5NiAxMjcuNTgxIDY5Mi4wODEgMTE0LjQ3IDY5Mi4wODEgOTguMjkwNkM2OTIuMDgxIDgyLjExMTQgNzA1LjE5NiA2OSA3MjEuMzgxIDY5WiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNMTA1Mi45MyA2OUgxMDA2LjY3Qzk5MC4wNTggNjkgOTc2LjU5NiA4Mi40NTgzIDk3Ni41OTYgOTkuMDYxNVYxNDUuMzFDOTc2LjU5NiAxNjEuOTEzIDk5MC4wNTggMTc1LjM3MSAxMDA2LjY3IDE3NS4zNzFIMTA1Mi45M0MxMDY5LjU0IDE3NS4zNzEgMTA4MyAxNjEuOTEzIDEwODMgMTQ1LjMxVjk5LjA2MTVDMTA4MyA4Mi40NTgzIDEwNjkuNTQgNjkgMTA1Mi45MyA2OVpNMTAyMi4zNyA5OS4zMjM1VjEzMC41NDFDMTAyMi4zNyAxMzQuNzUgMTAyMS41MyAxMzguNDAzIDEwMTkuODUgMTQxLjQ5NEMxMDE4LjE3IDE0NC41ODUgMTAxNS43NyAxNDYuOTgyIDEwMTIuNjYgMTQ4LjY4NkMxMDA5LjU0IDE1MC4zODkgMTAwNS44NSAxNTEuMjQ1IDEwMDEuNTkgMTUxLjI0NUM5OTcuOTYyIDE1MS4yNDUgOTk0LjgzMSAxNTAuNTkgOTkyLjE4NiAxNDkuMjg3Qzk4OS41NDIgMTQ3Ljk4NSA5ODcuMzIxIDE0Ni4xNTggOTg1LjUwOSAxNDMuODE0Qzk4NS41MDkgMTQzLjgxNCA5ODAuNDU5IDEzOC4zNjUgOTg1LjA4NSAxMzQuNTE5Qzk4OS44MTIgMTMwLjU4NyA5OTMuOTA2IDEzNS43MzYgOTkzLjkwNiAxMzUuNzM2Qzk5NC44NjIgMTM3LjA3IDk5NS45OTUgMTM4LjA3MiA5OTcuMzA2IDEzOC43MzVDOTk4LjYwOSAxMzkuNDA1IDEwMDAuMDkgMTM5LjczNyAxMDAxLjc1IDEzOS43MzdDMTAwMy4yOSAxMzkuNzM3IDEwMDQuNjUgMTM5LjQyMSAxMDA1LjgzIDEzOC43ODFDMTAwNyAxMzguMTQxIDEwMDcuOTMgMTM3LjE4NiAxMDA4LjYzIDEzNS45MDZDMTAwOS4zMiAxMzQuNjI2IDEwMDkuNjcgMTMzLjA1NCAxMDA5LjY3IDEzMS4xODlWOTkuMzMxMkMxMDA5LjY3IDk5LjMzMTIgMTAwOS41NCA5Mi45MDI3IDEwMTUuOTMgOTIuOTAyN0MxMDIyLjMxIDkyLjkwMjcgMTAyMi4zOCA5OS4zMzEyIDEwMjIuMzggOTkuMzMxMkwxMDIyLjM3IDk5LjMyMzVaTTEwNjcuOCAxNDQuODMyQzEwNjQuMyAxNDguMjg1IDEwNTkuMTYgMTUwLjM2NiAxMDUyLjM3IDE1MS4wNzVDMTA0Ny43MSAxNTEuNTY5IDEwNDMuNjQgMTUxLjI0NSAxMDQwLjE3IDE1MC4xMTJDMTAzNi43IDE0OC45NzkgMTAzMy40NSAxNDcuMDEzIDEwMzAuNDIgMTQ0LjIyM0MxMDMwLjQyIDE0NC4yMjMgMTAyNS43MyAxNDAuMTg0IDEwMjkuNTEgMTM1LjExMkMxMDMyLjM1IDEzMS4yOTcgMTAzNy42OSAxMzUuMjU5IDEwMzcuNjkgMTM1LjI1OUMxMDM5LjcxIDEzNy4xMzkgMTA0MS44NyAxMzguNTI3IDEwNDQuMTkgMTM5LjQzNkMxMDQ2LjUxIDE0MC4zNDYgMTA0OS4xMyAxNDAuNjQ2IDEwNTIuMDUgMTQwLjMzOEMxMDU0LjY1IDE0MC4wNjggMTA1Ni42MiAxMzkuMzU5IDEwNTcuOTggMTM4LjIyNkMxMDU5LjM0IDEzNy4wOTMgMTA1OS45MiAxMzUuNjc1IDEwNTkuNzUgMTMzLjk3OUMxMDU5LjU5IDEzMi40OTkgMTA1OSAxMzEuMzI3IDEwNTcuOTcgMTMwLjQ3MkMxMDU2Ljk1IDEyOS42MTYgMTA1NS42IDEyOC45MyAxMDUzLjkzIDEyOC40MDZDMTA1Mi4yNyAxMjcuODgyIDEwNTAuNDggMTI3LjQxOSAxMDQ4LjU2IDEyNy4wMDNDMTA0Ni42NCAxMjYuNTg3IDEwNDQuNjkgMTI2LjA1NSAxMDQyLjcyIDEyNS40MDhDMTA0MC43NSAxMjQuNzYgMTAzOC45MiAxMjMuOTA1IDEwMzcuMjIgMTIyLjg0OUMxMDM1LjUzIDEyMS43OTMgMTAzNC4xMSAxMjAuMzY3IDEwMzIuOTUgMTE4LjU1NUMxMDMxLjc5IDExNi43NDQgMTAzMS4wNyAxMTQuNDM5IDEwMzAuNzggMTExLjYzM0MxMDMwLjQxIDEwOC4xMzQgMTAzMC45MyAxMDUuMDUxIDEwMzIuMzMgMTAyLjM4NEMxMDMzLjc1IDk5LjcxNjYgMTAzNS44OSA5Ny41NzM4IDEwMzguNzcgOTUuOTU1MUMxMDQxLjY2IDk0LjMzNjQgMTA0NS4wMSA5My4zMjY3IDEwNDguODMgOTIuOTI1OEMxMDUyLjgxIDkyLjUwOTYgMTA1Ni40OCA5Mi44MTc5IDEwNTkuODYgOTMuODUwOEMxMDYzLjI0IDk0Ljg5MTQgMTA2Ni4xMSA5Ni40NjM4IDEwNjguNDggOTguNTY4MUMxMDY4LjQ4IDk4LjU2ODEgMTA3MS44IDEwMS4zNTEgMTA2OS4xMSAxMDYuMTM3QzEwNjcuMDIgMTA5Ljg2IDEwNjEuMTUgMTA3LjYyNSAxMDYxLjE1IDEwNy42MjVDMTA1OS4yNyAxMDYuMDUzIDEwNTcuNDIgMTA0LjkzNSAxMDU1LjYxIDEwNC4yNzJDMTA1My44IDEwMy42MDkgMTA1MS44MyAxMDMuMzg2IDEwNDkuNzEgMTAzLjYwOUMxMDQ3LjU0IDEwMy44NCAxMDQ1Ljg2IDEwNC40MzQgMTA0NC42NyAxMDUuMzgyQzEwNDMuNDggMTA2LjMzOCAxMDQyLjk4IDEwNy41ODcgMTA0My4xNCAxMDkuMTJDMTA0My4yOCAxMTAuNSAxMDQzLjg4IDExMS41NzIgMTA0NC45MyAxMTIuMzVDMTA0NS45NyAxMTMuMTIxIDEwNDcuMzEgMTEzLjc0NSAxMDQ4Ljk0IDExNC4yMTZDMTA1MC41NyAxMTQuNjg2IDEwNTIuMzcgMTE1LjE0IDEwNTQuMzEgMTE1LjU4QzEwNTYuMjYgMTE2LjAxOSAxMDU4LjIyIDExNi41NTEgMTA2MC4xOCAxMTcuMTc1QzEwNjIuMTUgMTE3LjggMTA2My45OCAxMTguNjk0IDEwNjUuNjUgMTE5Ljg1OEMxMDY3LjMyIDEyMS4wMjIgMTA2OC43NiAxMjIuNTMzIDEwNjkuOTUgMTI0LjM5QzEwNzEuMTMgMTI2LjI0OCAxMDcxLjg4IDEyOC42NiAxMDcyLjIgMTMxLjYyOEMxMDcyLjc2IDEzNi45ODUgMTA3MS4zIDE0MS4zODYgMTA2Ny44IDE0NC44MzJaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik01OTkuNjMzIDY5LjI2MjFDNTUzLjk5NSA2OS4yNjIxIDUxNyAxMDYuMjQ1IDUxNyAxNTEuODY5QzUxNyAxNTIuNzY0IDUxNy4wMTUgMTUzLjY1IDUxNy4wNDYgMTU0LjUzNlYyMzMuOTIyQzUxNy4wNDYgMjMzLjkyMiA1MTcuMDQ2IDIzMy45MjkgNTE3LjA0NiAyMzMuOTM3VjI0Mi4wMDhDNTE3LjA0NiAyNDIuMDA4IDUxNy4wNDYgMjQyLjAwOCA1MTcuMDU0IDI0MlYyNjEuOTU2QzUxNy4wNTQgMjYyLjExIDUxNy4wNDYgMjYyLjI1NyA1MTcuMDQ2IDI2Mi40MTFDNTE3LjA0NiAyNjIuNTY1IDUxNy4wNTQgMjYyLjcxOSA1MTcuMDYyIDI2Mi44NzNWMjYzLjI0M0g1MTcuMDY5QzUxNy41MDkgMjc4LjU3NSA1MzAuMDY5IDI5MC44NjkgNTQ1LjUxMyAyOTAuODY5QzU2MC45NTcgMjkwLjg2OSA1NzMuNjQ5IDI3OC41NzUgNTc0LjA4OCAyNjMuMjQzVjIzMy45MzdINTk3LjY5N0M1OTguMzM3IDIzMy45NTMgNTk4Ljk4NSAyMzMuOTYgNTk5LjYyNSAyMzMuOTZDNjQ1LjI2MyAyMzMuOTYgNjgyLjI1OCAxOTcuNDkzIDY4Mi4yNTggMTUxLjg2OUM2ODIuMjU4IDEwNi4yNDUgNjQ1LjI3MSA2OS4yNjIxIDU5OS42MzMgNjkuMjYyMVpNNTk5LjYzMyAxNzYuOTIxQzU5NC41ODIgMTc2LjkyMSA1ODEuNzgzIDE3Ni45MjEgNTc2LjQ3MSAxNzYuOTIxSDU3NC4xMTFWMTUxLjg3N0M1NzQuMTExIDEzNy44NDEgNTg1LjU5MiAxMjYuNDU2IDU5OS42MzMgMTI2LjQ1NkM2MTMuNjczIDEyNi40NTYgNjI1LjA2MiAxMzcuODQxIDYyNS4wNjIgMTUxLjg3N0M2MjUuMDYyIDE2NS45MTQgNjEzLjY3MyAxNzYuOTI4IDU5OS42MzMgMTc2LjkyOFYxNzYuOTIxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBvcGFjaXR5PSIwLjE1IiBkPSJNNTQ0Ljc5NiAyMzMuOTUzSDU3NC4wNVYyNjMuMjUxSDU3My45MThDNTczLjQ3OSAyNzguNTgyIDU2MC45MTkgMjkwLjg3NyA1NDUuNDc1IDI5MC44NzdDNTMwLjAzMSAyOTAuODc3IDUxNy40NjMgMjc4LjU4MiA1MTcuMDIzIDI2My4yNTFINTE3LjAxNVYyNjIuODgxQzUxNy4wMTUgMjYyLjcyNyA1MTcgMjYyLjU3MyA1MTcgMjYyLjQxOEM1MTcgMjYyLjI2NCA1MTcuMDA4IDI2Mi4xMTggNTE3LjAwOCAyNjEuOTY0VjI1OS44OThDNTE3Ljg4NyAyNDYuMTg1IDUzMC44MzMgMjMzLjk1MyA1NDQuNzg4IDIzMy45NTNINTQ0Ljc5NloiIGZpbGw9IiMxRDFEMUIiLz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTUxNy4wNDYgMTk4LjcxNlYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRWMTY1LjcwMUM1NTEuMDggMTY4LjEyMSA1MzAuNzAxIDE4MC41MDkgNTE3LjA0NiAxOTguNzE2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTUxNy4wOTMgMjYwLjQ1MUM1MjMuODE2IDI1NS41MTggNTMxLjk3NCAyNTIuNjEyIDU0MC43NzkgMjUyLjYxMkg1NzQuMTAzVjIzMy45NDNINTQ1LjU1MkM1MzAuNDcgMjMzLjk0MyA1MTguMTQxIDI0NS42MzYgNTE3LjA5MyAyNjAuNDUxWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY2LjczNEM1NTAuODg3IDE2OS4wMDggNTMwLjQwOCAxODEuNjY1IDUxNy4wNDYgMjAwLjI1VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMwLjg3OSAyMzMuOTQzIDUxOC44MDQgMjQ1LjAxOSA1MTcuMiAyNTkuMjY0QzUyMy45MTYgMjU0LjAyMiA1MzIuMjEzIDI1MC45MjQgNTQxLjIxMSAyNTAuOTI0SDU3NC4wOTZWMjMzLjk1MUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE2Ny43NTlDNTUwLjY3OSAxNjkuODc5IDUzMC4wNjkgMTgyLjg0NCA1MTcuMDQ2IDIwMS44NDVWMjMzLjkzNUM1MTcuMDQ2IDIwMi45MDEgNTQzLjEzMSAxNzcuMzU2IDU3NC4wOTYgMTc2LjkzMlYxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS4yODcgMjMzLjk0MyA1MTkuNDgzIDI0NC40MDMgNTE3LjM1NSAyNTguMDY5QzUyNC4wMzIgMjUyLjUyNyA1MzIuNDY3IDI0OS4yMiA1NDEuNjQzIDI0OS4yMkg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY4Ljc5MkM1NTAuNDQgMTcwLjc1IDUyOS42OTEgMTg0LjA3IDUxNy4wNDYgMjAzLjUzM1YyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS42OTYgMjMzLjk0MyA1MjAuMTU0IDI0My44MTcgNTE3LjU2MyAyNTYuOTA1QzUyNC4xODYgMjUxLjA1NSA1MzIuNzM3IDI0Ny41MjUgNTQyLjA4MiAyNDcuNTI1SDU3NC4xMDNWMjMzLjk0M0g1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY5LjgzM0M1NTAuMTYzIDE3MS42MDUgNTI5LjI1MiAxODUuMzQyIDUxNy4wNDYgMjA1LjMxNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMi4xMTIgMjMzLjk0MyA1MjAuODU1IDI0My4yMzEgNTE3LjgyNSAyNTUuNzI2QzUyNC4zNTYgMjQ5LjU3NSA1MzMuMDE1IDI0NS44MjEgNTQyLjUxNCAyNDUuODIxSDU3NC4wOTZWMjMzLjkzNUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE3MC44NzNDNTQ5Ljg0NiAxNzIuNDYxIDUyOC43MzUgMTg2LjY2OCA1MTcuMDQ2IDIwNy4yMjZWMjMzLjk0M0M1MTcuMDQ2IDIwMi45MDkgNTQzLjEzMSAxNzcuMzY0IDU3NC4wOTYgMTc2Ljk0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTU0NS41NTIgMjMzLjk0M0M1MzIuNTM2IDIzMy45NDMgNTIxLjU3MiAyNDIuNjUzIDUxOC4xNDEgMjU0LjU0N0M1MjQuNTU2IDI0OC4wOTUgNTMzLjMgMjQ0LjEyNSA1NDIuOTUzIDI0NC4xMjVINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzEuOTIxQzU0OS40NzYgMTczLjMwMSA1MjguMTI2IDE4OC4wNzggNTE3LjA0NiAyMDkuMjk5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMyLjk3NiAyMzMuOTQzIDUyMi4zMDUgMjQyLjA4MyA1MTguNTA0IDI1My4zNzVDNTI0Ljc3MiAyNDYuNjMgNTMzLjYwMSAyNDIuNDI5IDU0My4zNzcgMjQyLjQyOUg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTcyLjk3OEM1NDkuMDIxIDE3NC4xMzQgNTI3LjM4NiAxODkuNjA1IDUxNy4wNDYgMjExLjYwNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMy40MDggMjMzLjk0MyA1MjMuMDQ1IDI0MS41MzUgNTE4LjkyIDI1Mi4yMjZDNTI1LjAxOSAyNDUuMTgxIDUzMy45MDEgMjQwLjczNCA1NDMuODE3IDI0MC43MzRINTc0LjEwNFYyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzQuMDU3QzU0OC40NDMgMTc0Ljk3NCA1MjYuNDM4IDE5MS4zMTYgNTE3LjA0NiAyMTQuMjc5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMzLjg0NyAyMzMuOTQzIDUyMy43OTMgMjQwLjk5NiA1MTkuMzk4IDI1MS4wNzhDNTI1LjI4MSAyNDMuNzMyIDUzNC4yMjUgMjM5LjAzOCA1NDQuMjU2IDIzOS4wMzhINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMV83Ij4KPHJlY3Qgd2lkdGg9IjU2NiIgaGVpZ2h0PSIyMjIuMDA4IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTE3IDY5KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="}}]); \ No newline at end of file diff --git a/assets/js/1bdfe0fa.145a1f02.js b/assets/js/1bdfe0fa.145a1f02.js deleted file mode 100644 index d23c185e5..000000000 --- a/assets/js/1bdfe0fa.145a1f02.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7277],{8688:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>u,frontMatter:()=>n,metadata:()=>N,toc:()=>l});var a=i(7462),M=(i(7294),i(3905));const n={title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},r=void 0,N={permalink:"/blog/pixi-v8-launches",source:"@site/blog/2024-03-05-pixi-v8-launches.md",title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",date:"2024-03-05T00:00:00.000Z",formattedDate:"March 5, 2024",tags:[{label:"PixiJS",permalink:"/blog/tags/pixi-js"},{label:"WebGPU",permalink:"/blog/tags/web-gpu"},{label:"WebGL",permalink:"/blog/tags/web-gl"}],readingTime:9.86,hasTruncateMarker:!1,authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],frontMatter:{title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},nextItem:{title:"PixiJS v8 Beta! \ud83c\udf89",permalink:"/blog/pixi-v8-beta"}},o={authorsImageUrls:[void 0]},l=[{value:"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8",id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8",level:2},{value:"\ud83d\udd17 Quick links",id:"-quick-links",level:2},{value:"\ud83c\udf81 Whats New?",id:"-whats-new",level:2},{value:"\ud83d\udcc8 New Performance Bar",id:"-new-performance-bar",level:4},{value:"\ud83d\udda5\ufe0f WebGPU Renderer",id:"\ufe0f-webgpu-renderer",level:4},{value:"\ud83d\udce6 New Package Structure",id:"-new-package-structure",level:4},{value:"\u2728 We promise the Renderer will work",id:"-we-promise-the-renderer-will-work",level:3},{value:"\ud83c\udf1f Scene Upgrades",id:"-scene-upgrades",level:4},{value:"\ud83c\udfa8 Graphics Upgrades",id:"-graphics-upgrades",level:4},{value:"\ud83d\udcdd Text Upgrades",id:"-text-upgrades",level:4},{value:"\ud83e\udd1d What now? Get involved!",id:"-what-now-get-involved",level:2},{value:"\ud83d\udcf2 Keep in touch",id:"-keep-in-touch",level:2}],g={toc:l};function u(e){let{components:t,...n}=e;return(0,M.kt)("wrapper",(0,a.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,M.kt)("p",null,"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time."),(0,M.kt)("h2",{id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8"},"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(2324).Z,width:"1600",height:"360"})),(0,M.kt)("p",null,"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration."),(0,M.kt)("p",null,"Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture."),(0,M.kt)("p",null,"With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code."),(0,M.kt)("p",null,"Our vision for v8 was clear:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Longevity:")," We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Innovation with WebGPU:")," Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Leveraging Modern JavaScript:")," The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Correcting Past Oversights:")," Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!)."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Boosting Performance:")," PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7.")),(0,M.kt)("p",null,"We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!"),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-quick-links"},"\ud83d\udd17 Quick links"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The new Docs for v8 can be found ",(0,M.kt)("a",{parentName:"li",href:"https://pixijs.download/v8.0.0/docs/index.html"},"here")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"/guides/migrations/v8"},"Migration")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"/examples"},"Examples")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/pixijs/open-games"},"Open Games"))),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-whats-new"},"\ud83c\udf81 Whats New?"),(0,M.kt)("p",null,"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above."),(0,M.kt)("h4",{id:"-new-performance-bar"},"\ud83d\udcc8 New Performance Bar"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"bunnies",src:i(4636).Z,width:"1600",height:"718"})),(0,M.kt)("p",null,"The performance of v8 is faster for ",(0,M.kt)("strong",{parentName:"p"},"both")," renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"CPU")," = time spent by the CPU rendering a single frame"),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"GPU")," = time spent by the GPU rendering a single frame")),(0,M.kt)("table",null,(0,M.kt)("thead",{parentName:"table"},(0,M.kt)("tr",{parentName:"thead"},(0,M.kt)("th",{parentName:"tr",align:null},"Bunny Situation"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"CPU Dif"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"GPU dif"))),(0,M.kt)("tbody",{parentName:"table"},(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites all moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~15ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"233%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites not moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~21ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.12ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"17417%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.5ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"1700%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites (changing scene structure)"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~24ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"108%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))))),(0,M.kt)("p",null,"These benchmark numbers are based on the Bunnymark test that you can try yourself."),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v7&count=100000&renderer=webgpu"},"v7 Bunnymark")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgpu"},"v8 Bunnymark - WebGPU")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgl"},"v8 Bunnymark - WebGL")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/GoodBoyDigital/pixi-bunnymark"},"Repo"))),(0,M.kt)("h4",{id:"\ufe0f-webgpu-renderer"},"\ud83d\udda5\ufe0f WebGPU Renderer"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS + webGPU = love",src:i(9349).Z,width:"1390",height:"322"})),(0,M.kt)("p",null,"We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements."),(0,M.kt)("h4",{id:"-new-package-structure"},"\ud83d\udce6 New Package Structure"),(0,M.kt)("p",null,'No more "lerna." PixiJS is now just one package with one import root: ',(0,M.kt)("inlineCode",{parentName:"p"},"import {stuff} from \u2018pixi.js\u2019"),". This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported."),(0,M.kt)("p",null,"Old:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite } from "@pixi/sprite";\nimport { Graphic } from "@pixi/graphics";\n')),(0,M.kt)("p",null,"New:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite, Graphic } from "pixi.js";\n')),(0,M.kt)("h3",{id:"-we-promise-the-renderer-will-work"},"\u2728 We ",(0,M.kt)("em",{parentName:"h3"},"promise")," the Renderer will work"),(0,M.kt)("p",null,"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere!"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Application, autoDetectRenderer } from "pixi.js";\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // or\n const renderer = await autoDetectRenderer({}); // WebGL or WebGPU\n\n // do pixi things\n})();\n')),(0,M.kt)("h4",{id:"-scene-upgrades"},"\ud83c\udf1f Scene Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(3012).Z,width:"1000",height:"400"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container({\n isRenderGroup:true // this containers transform is now handled on the GPU!\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as:")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"// will make all the children tinted red\ncontainer.tint = 'red'\n// will make all the children have the add blend mode\ncontainer.blendMode = 'add'\n")),(0,M.kt)("p",null,"Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"const texture = RenderTexture.create({\n width:100,\n height:100,\n antialias:true // easy as that\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics:",(0,M.kt)("ul",{parentName:"li"},(0,M.kt)("li",{parentName:"ul"},"ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend."))),(0,M.kt)("li",{parentName:"ul"},"It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!)\n\nmyContainer.blendMode = 'color-burn` // easy!\n")),(0,M.kt)("h4",{id:"-graphics-upgrades"},"\ud83c\udfa8 Graphics Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"alt text",src:i(8074).Z,width:"2952",height:"826"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics\n .rect(50, 50, 100, 100)\n .fill('blue');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"A ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsContext")," has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Support for SVG drawing has been added. For example:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics.svg('M 100 350 q 150 -300 300 0');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The new ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsPath")," class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the ",(0,M.kt)("inlineCode",{parentName:"p"},"buildGeometryFromPath")," function, opening up new possibilities for intricate and detailed graphic designs."))),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"}," const path = new GraphicsPath()\n .rect(-50, -50, 100, 100)\n\n // create geometry from the path:\n const geometry = buildGeometryFromPath({\n path,\n });\n\n const mesh = new Mesh({\n geometry,\n texture: Texture.WHITE,\n });\n\n")),(0,M.kt)("p",null,"For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the ",(0,M.kt)("a",{parentName:"p",href:"/guides/migrations/v8"},"migration guide"),", or why not jump in and play with some ",(0,M.kt)("a",{parentName:"p",href:"examples/graphics/simple"},"examples"),"."),(0,M.kt)("h4",{id:"-text-upgrades"},"\ud83d\udcdd Text Upgrades"),(0,M.kt)("p",null,"Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard."),(0,M.kt)("p",null,"BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"\nconst myText = new BitmapText({\n text: 'hello im a bitmap font!',\n // font will be dynamically created\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: 'red',\n }\n})\n")),(0,M.kt)("p",null,"Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const myText = new Text({\n text: 'hello im some fancy text',\n // font will be dynamically created!\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: { texture, color:'red'} // same as graphics api fills\n stroke: { width:3, color:'blue' } // same as graphics api strokes\n }\n})\n")),(0,M.kt)("h2",{id:"-what-now-get-involved"},"\ud83e\udd1d What now? Get involved!"),(0,M.kt)("p",null,"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on ",(0,M.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub")," and ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," to any issues that arise, valuing your input to make PixiJS even better."),(0,M.kt)("p",null,"A heartfelt thanks to our early adopters (everyone in ",(0,M.kt)("a",{parentName:"p",href:"https://discord.com/channels/734147990985375826/1143191340230914068"},"here"),") for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you!"),(0,M.kt)("p",null,"A final big shout-out to PlayCo for their support in making this release a reality!"),(0,M.kt)("p",null,"Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help."),(0,M.kt)("h2",{id:"-keep-in-touch"},"\ud83d\udcf2 Keep in touch"),(0,M.kt)("p",null,"To stay in the loop, we invite you to follow ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/Doormat23"},"Doormat23")," and ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/PixiJS"},"PixiJS")," on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," for direct engagement and real-time chit-chats."))}u.isMDXComponent=!0},3012:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/blend-modes-2c499d097578c17f1154867dbeead3a4.png"},9349:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-1-82838aaed491ecba56cb42c2ee5aa1fa.png"},8074:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-4-8e8a66475de0767192be60bb45ccf6f1.png"},4636:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-72e0ca7d8183d62c10a0f5e8dcc4ebda.png"},2324:(e,t,i)=>{i.d(t,{Z:()=>a});const a="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwMCIgaGVpZ2h0PSIzNjAiIHZpZXdCb3g9IjAgMCAxNjAwIDM2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2MDAiIGhlaWdodD0iMzYwIiBmaWxsPSIjMjUyQjM2Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xXzcpIj4KPHBhdGggZD0iTTc1MS41MjggMjkxVjEzOC4zNjVINzA1Ljg4M0M3MDUuNzI4IDEzOC4zNjUgNzA1LjU2NiAxMzguMzY1IDcwNS4zOTcgMTM4LjM3M0M2OTcuMTkzIDEzOC42NTggNjkwLjYgMTQ1LjQwMiA2OTAuNTIzIDE1My42MDRDNjkwLjUyMyAxNTMuNjUgNjkwLjUyMyAxNTMuNjk2IDY5MC41MjMgMTUzLjczNVYyNzUuODg0QzY5MC41MjMgMjc2LjEzOSA2OTAuNTM5IDI3Ni40MjQgNjkwLjU2MiAyNzYuNzRDNjkxLjI1NiAyODQuNjAyIDY5Ny44NDEgMjkwLjYzIDcwNS43MjggMjkwLjk2OUM3MDYuMDYgMjkwLjk4NSA3MDYuMzc2IDI5MC45OTIgNzA2LjY2OSAyOTAuOTkySDc1MS41MjhWMjkxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTM5LjY5NCAyOTAuOTIzQzk0OC4xOTggMjkwLjI4MyA5NTQuOTMgMjgzLjI4NCA5NTQuOTkxIDI3NC43NTFDOTU0Ljk5MSAyNzQuNjk3IDk1NC45OTEgMjc0LjY0MyA5NTQuOTkxIDI3NC41OVYxNTMuOTEyQzk1NC45OTEgMTUzLjg2NiA5NTQuOTkxIDE1My44MTIgOTU0Ljk5MSAxNTMuNzY2Qzk1NC45NTMgMTQ1LjMzMyA5NDguMDkgMTM4LjQ1NyA5MzkuNjU1IDEzOC4zNzNDOTM5LjU5NCAxMzguMzczIDkzOS41MzIgMTM4LjM3MyA5MzkuNDcgMTM4LjM3M0g4OTMuOTg2VjI5MS4wMDhIOTM3Ljc4OUM5MzguMzgzIDI5MS4wMDggOTM5LjAyMyAyOTAuOTc3IDkzOS42OTQgMjkwLjkzMVYyOTAuOTIzWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNNzQyLjk3IDEzOC4zNzNINzkwLjc3NEM3OTUuNDYyIDEzOC4zNzMgODAyLjA5MyAxNDIuMTY1IDgwNS41OTQgMTQ2Ljg1MUw5MDYuOTYzIDI4Mi41MTNDOTEwLjQ2NCAyODcuMiA5MDkuNSAyOTAuOTkyIDkwNC44MiAyOTAuOTkySDg1Ny4wMTVDODUyLjMyNyAyOTAuOTkyIDg0NS42OTYgMjg3LjIgODQyLjE5NSAyODIuNTEzTDc0MC44MTkgMTQ2Ljg1MUM3MzcuMzE4IDE0Mi4xNzMgNzM4LjI4MiAxMzguMzczIDc0Mi45NjIgMTM4LjM3M0g3NDIuOTdaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik05MDEuMDM0IDEzOC4zNzNIODUzLjIyOUM4NDguNTQxIDEzOC4zNzMgODQxLjkxIDE0Mi4xNjUgODM4LjQxIDE0Ni44NTFMNzM3LjA0IDI4Mi41MTNDNzMzLjU0IDI4Ny4yIDczNC41MDQgMjkwLjk5MiA3MzkuMTg0IDI5MC45OTJINzg2Ljk4OUM3OTEuNjc3IDI5MC45OTIgNzk4LjMwOCAyODcuMiA4MDEuODA4IDI4Mi41MTNMOTAzLjE3NyAxNDYuODUxQzkwNi42NzggMTQyLjE3MyA5MDUuNzE0IDEzOC4zNzMgOTAxLjAzNCAxMzguMzczWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTI0LjkzNiA2OUM5NDEuMTEzIDY5IDk1NC4yMzYgODIuMTExNCA5NTQuMjM2IDk4LjI5MDZDOTU0LjIzNiAxMTQuNDcgOTQxLjEyIDEyNy41ODEgOTI0LjkzNiAxMjcuNTgxQzkwOC43NTIgMTI3LjU4MSA4OTUuNjM2IDExNC40NyA4OTUuNjM2IDk4LjI5MDZDODk1LjYzNiA4Mi4xMTE0IDkwOC43NTIgNjkgOTI0LjkzNiA2OVoiIGZpbGw9IiNFNzIyNjQiLz4KPHBhdGggZD0iTTcyMS4zODEgNjlDNzM3LjU1NyA2OSA3NTAuNjggODIuMTExNCA3NTAuNjggOTguMjkwNkM3NTAuNjggMTE0LjQ3IDczNy41NjUgMTI3LjU4MSA3MjEuMzgxIDEyNy41ODFDNzA1LjE5NiAxMjcuNTgxIDY5Mi4wODEgMTE0LjQ3IDY5Mi4wODEgOTguMjkwNkM2OTIuMDgxIDgyLjExMTQgNzA1LjE5NiA2OSA3MjEuMzgxIDY5WiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNMTA1Mi45MyA2OUgxMDA2LjY3Qzk5MC4wNTggNjkgOTc2LjU5NiA4Mi40NTgzIDk3Ni41OTYgOTkuMDYxNVYxNDUuMzFDOTc2LjU5NiAxNjEuOTEzIDk5MC4wNTggMTc1LjM3MSAxMDA2LjY3IDE3NS4zNzFIMTA1Mi45M0MxMDY5LjU0IDE3NS4zNzEgMTA4MyAxNjEuOTEzIDEwODMgMTQ1LjMxVjk5LjA2MTVDMTA4MyA4Mi40NTgzIDEwNjkuNTQgNjkgMTA1Mi45MyA2OVpNMTAyMi4zNyA5OS4zMjM1VjEzMC41NDFDMTAyMi4zNyAxMzQuNzUgMTAyMS41MyAxMzguNDAzIDEwMTkuODUgMTQxLjQ5NEMxMDE4LjE3IDE0NC41ODUgMTAxNS43NyAxNDYuOTgyIDEwMTIuNjYgMTQ4LjY4NkMxMDA5LjU0IDE1MC4zODkgMTAwNS44NSAxNTEuMjQ1IDEwMDEuNTkgMTUxLjI0NUM5OTcuOTYyIDE1MS4yNDUgOTk0LjgzMSAxNTAuNTkgOTkyLjE4NiAxNDkuMjg3Qzk4OS41NDIgMTQ3Ljk4NSA5ODcuMzIxIDE0Ni4xNTggOTg1LjUwOSAxNDMuODE0Qzk4NS41MDkgMTQzLjgxNCA5ODAuNDU5IDEzOC4zNjUgOTg1LjA4NSAxMzQuNTE5Qzk4OS44MTIgMTMwLjU4NyA5OTMuOTA2IDEzNS43MzYgOTkzLjkwNiAxMzUuNzM2Qzk5NC44NjIgMTM3LjA3IDk5NS45OTUgMTM4LjA3MiA5OTcuMzA2IDEzOC43MzVDOTk4LjYwOSAxMzkuNDA1IDEwMDAuMDkgMTM5LjczNyAxMDAxLjc1IDEzOS43MzdDMTAwMy4yOSAxMzkuNzM3IDEwMDQuNjUgMTM5LjQyMSAxMDA1LjgzIDEzOC43ODFDMTAwNyAxMzguMTQxIDEwMDcuOTMgMTM3LjE4NiAxMDA4LjYzIDEzNS45MDZDMTAwOS4zMiAxMzQuNjI2IDEwMDkuNjcgMTMzLjA1NCAxMDA5LjY3IDEzMS4xODlWOTkuMzMxMkMxMDA5LjY3IDk5LjMzMTIgMTAwOS41NCA5Mi45MDI3IDEwMTUuOTMgOTIuOTAyN0MxMDIyLjMxIDkyLjkwMjcgMTAyMi4zOCA5OS4zMzEyIDEwMjIuMzggOTkuMzMxMkwxMDIyLjM3IDk5LjMyMzVaTTEwNjcuOCAxNDQuODMyQzEwNjQuMyAxNDguMjg1IDEwNTkuMTYgMTUwLjM2NiAxMDUyLjM3IDE1MS4wNzVDMTA0Ny43MSAxNTEuNTY5IDEwNDMuNjQgMTUxLjI0NSAxMDQwLjE3IDE1MC4xMTJDMTAzNi43IDE0OC45NzkgMTAzMy40NSAxNDcuMDEzIDEwMzAuNDIgMTQ0LjIyM0MxMDMwLjQyIDE0NC4yMjMgMTAyNS43MyAxNDAuMTg0IDEwMjkuNTEgMTM1LjExMkMxMDMyLjM1IDEzMS4yOTcgMTAzNy42OSAxMzUuMjU5IDEwMzcuNjkgMTM1LjI1OUMxMDM5LjcxIDEzNy4xMzkgMTA0MS44NyAxMzguNTI3IDEwNDQuMTkgMTM5LjQzNkMxMDQ2LjUxIDE0MC4zNDYgMTA0OS4xMyAxNDAuNjQ2IDEwNTIuMDUgMTQwLjMzOEMxMDU0LjY1IDE0MC4wNjggMTA1Ni42MiAxMzkuMzU5IDEwNTcuOTggMTM4LjIyNkMxMDU5LjM0IDEzNy4wOTMgMTA1OS45MiAxMzUuNjc1IDEwNTkuNzUgMTMzLjk3OUMxMDU5LjU5IDEzMi40OTkgMTA1OSAxMzEuMzI3IDEwNTcuOTcgMTMwLjQ3MkMxMDU2Ljk1IDEyOS42MTYgMTA1NS42IDEyOC45MyAxMDUzLjkzIDEyOC40MDZDMTA1Mi4yNyAxMjcuODgyIDEwNTAuNDggMTI3LjQxOSAxMDQ4LjU2IDEyNy4wMDNDMTA0Ni42NCAxMjYuNTg3IDEwNDQuNjkgMTI2LjA1NSAxMDQyLjcyIDEyNS40MDhDMTA0MC43NSAxMjQuNzYgMTAzOC45MiAxMjMuOTA1IDEwMzcuMjIgMTIyLjg0OUMxMDM1LjUzIDEyMS43OTMgMTAzNC4xMSAxMjAuMzY3IDEwMzIuOTUgMTE4LjU1NUMxMDMxLjc5IDExNi43NDQgMTAzMS4wNyAxMTQuNDM5IDEwMzAuNzggMTExLjYzM0MxMDMwLjQxIDEwOC4xMzQgMTAzMC45MyAxMDUuMDUxIDEwMzIuMzMgMTAyLjM4NEMxMDMzLjc1IDk5LjcxNjYgMTAzNS44OSA5Ny41NzM4IDEwMzguNzcgOTUuOTU1MUMxMDQxLjY2IDk0LjMzNjQgMTA0NS4wMSA5My4zMjY3IDEwNDguODMgOTIuOTI1OEMxMDUyLjgxIDkyLjUwOTYgMTA1Ni40OCA5Mi44MTc5IDEwNTkuODYgOTMuODUwOEMxMDYzLjI0IDk0Ljg5MTQgMTA2Ni4xMSA5Ni40NjM4IDEwNjguNDggOTguNTY4MUMxMDY4LjQ4IDk4LjU2ODEgMTA3MS44IDEwMS4zNTEgMTA2OS4xMSAxMDYuMTM3QzEwNjcuMDIgMTA5Ljg2IDEwNjEuMTUgMTA3LjYyNSAxMDYxLjE1IDEwNy42MjVDMTA1OS4yNyAxMDYuMDUzIDEwNTcuNDIgMTA0LjkzNSAxMDU1LjYxIDEwNC4yNzJDMTA1My44IDEwMy42MDkgMTA1MS44MyAxMDMuMzg2IDEwNDkuNzEgMTAzLjYwOUMxMDQ3LjU0IDEwMy44NCAxMDQ1Ljg2IDEwNC40MzQgMTA0NC42NyAxMDUuMzgyQzEwNDMuNDggMTA2LjMzOCAxMDQyLjk4IDEwNy41ODcgMTA0My4xNCAxMDkuMTJDMTA0My4yOCAxMTAuNSAxMDQzLjg4IDExMS41NzIgMTA0NC45MyAxMTIuMzVDMTA0NS45NyAxMTMuMTIxIDEwNDcuMzEgMTEzLjc0NSAxMDQ4Ljk0IDExNC4yMTZDMTA1MC41NyAxMTQuNjg2IDEwNTIuMzcgMTE1LjE0IDEwNTQuMzEgMTE1LjU4QzEwNTYuMjYgMTE2LjAxOSAxMDU4LjIyIDExNi41NTEgMTA2MC4xOCAxMTcuMTc1QzEwNjIuMTUgMTE3LjggMTA2My45OCAxMTguNjk0IDEwNjUuNjUgMTE5Ljg1OEMxMDY3LjMyIDEyMS4wMjIgMTA2OC43NiAxMjIuNTMzIDEwNjkuOTUgMTI0LjM5QzEwNzEuMTMgMTI2LjI0OCAxMDcxLjg4IDEyOC42NiAxMDcyLjIgMTMxLjYyOEMxMDcyLjc2IDEzNi45ODUgMTA3MS4zIDE0MS4zODYgMTA2Ny44IDE0NC44MzJaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik01OTkuNjMzIDY5LjI2MjFDNTUzLjk5NSA2OS4yNjIxIDUxNyAxMDYuMjQ1IDUxNyAxNTEuODY5QzUxNyAxNTIuNzY0IDUxNy4wMTUgMTUzLjY1IDUxNy4wNDYgMTU0LjUzNlYyMzMuOTIyQzUxNy4wNDYgMjMzLjkyMiA1MTcuMDQ2IDIzMy45MjkgNTE3LjA0NiAyMzMuOTM3VjI0Mi4wMDhDNTE3LjA0NiAyNDIuMDA4IDUxNy4wNDYgMjQyLjAwOCA1MTcuMDU0IDI0MlYyNjEuOTU2QzUxNy4wNTQgMjYyLjExIDUxNy4wNDYgMjYyLjI1NyA1MTcuMDQ2IDI2Mi40MTFDNTE3LjA0NiAyNjIuNTY1IDUxNy4wNTQgMjYyLjcxOSA1MTcuMDYyIDI2Mi44NzNWMjYzLjI0M0g1MTcuMDY5QzUxNy41MDkgMjc4LjU3NSA1MzAuMDY5IDI5MC44NjkgNTQ1LjUxMyAyOTAuODY5QzU2MC45NTcgMjkwLjg2OSA1NzMuNjQ5IDI3OC41NzUgNTc0LjA4OCAyNjMuMjQzVjIzMy45MzdINTk3LjY5N0M1OTguMzM3IDIzMy45NTMgNTk4Ljk4NSAyMzMuOTYgNTk5LjYyNSAyMzMuOTZDNjQ1LjI2MyAyMzMuOTYgNjgyLjI1OCAxOTcuNDkzIDY4Mi4yNTggMTUxLjg2OUM2ODIuMjU4IDEwNi4yNDUgNjQ1LjI3MSA2OS4yNjIxIDU5OS42MzMgNjkuMjYyMVpNNTk5LjYzMyAxNzYuOTIxQzU5NC41ODIgMTc2LjkyMSA1ODEuNzgzIDE3Ni45MjEgNTc2LjQ3MSAxNzYuOTIxSDU3NC4xMTFWMTUxLjg3N0M1NzQuMTExIDEzNy44NDEgNTg1LjU5MiAxMjYuNDU2IDU5OS42MzMgMTI2LjQ1NkM2MTMuNjczIDEyNi40NTYgNjI1LjA2MiAxMzcuODQxIDYyNS4wNjIgMTUxLjg3N0M2MjUuMDYyIDE2NS45MTQgNjEzLjY3MyAxNzYuOTI4IDU5OS42MzMgMTc2LjkyOFYxNzYuOTIxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBvcGFjaXR5PSIwLjE1IiBkPSJNNTQ0Ljc5NiAyMzMuOTUzSDU3NC4wNVYyNjMuMjUxSDU3My45MThDNTczLjQ3OSAyNzguNTgyIDU2MC45MTkgMjkwLjg3NyA1NDUuNDc1IDI5MC44NzdDNTMwLjAzMSAyOTAuODc3IDUxNy40NjMgMjc4LjU4MiA1MTcuMDIzIDI2My4yNTFINTE3LjAxNVYyNjIuODgxQzUxNy4wMTUgMjYyLjcyNyA1MTcgMjYyLjU3MyA1MTcgMjYyLjQxOEM1MTcgMjYyLjI2NCA1MTcuMDA4IDI2Mi4xMTggNTE3LjAwOCAyNjEuOTY0VjI1OS44OThDNTE3Ljg4NyAyNDYuMTg1IDUzMC44MzMgMjMzLjk1MyA1NDQuNzg4IDIzMy45NTNINTQ0Ljc5NloiIGZpbGw9IiMxRDFEMUIiLz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTUxNy4wNDYgMTk4LjcxNlYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRWMTY1LjcwMUM1NTEuMDggMTY4LjEyMSA1MzAuNzAxIDE4MC41MDkgNTE3LjA0NiAxOTguNzE2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTUxNy4wOTMgMjYwLjQ1MUM1MjMuODE2IDI1NS41MTggNTMxLjk3NCAyNTIuNjEyIDU0MC43NzkgMjUyLjYxMkg1NzQuMTAzVjIzMy45NDNINTQ1LjU1MkM1MzAuNDcgMjMzLjk0MyA1MTguMTQxIDI0NS42MzYgNTE3LjA5MyAyNjAuNDUxWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY2LjczNEM1NTAuODg3IDE2OS4wMDggNTMwLjQwOCAxODEuNjY1IDUxNy4wNDYgMjAwLjI1VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMwLjg3OSAyMzMuOTQzIDUxOC44MDQgMjQ1LjAxOSA1MTcuMiAyNTkuMjY0QzUyMy45MTYgMjU0LjAyMiA1MzIuMjEzIDI1MC45MjQgNTQxLjIxMSAyNTAuOTI0SDU3NC4wOTZWMjMzLjk1MUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE2Ny43NTlDNTUwLjY3OSAxNjkuODc5IDUzMC4wNjkgMTgyLjg0NCA1MTcuMDQ2IDIwMS44NDVWMjMzLjkzNUM1MTcuMDQ2IDIwMi45MDEgNTQzLjEzMSAxNzcuMzU2IDU3NC4wOTYgMTc2LjkzMlYxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS4yODcgMjMzLjk0MyA1MTkuNDgzIDI0NC40MDMgNTE3LjM1NSAyNTguMDY5QzUyNC4wMzIgMjUyLjUyNyA1MzIuNDY3IDI0OS4yMiA1NDEuNjQzIDI0OS4yMkg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY4Ljc5MkM1NTAuNDQgMTcwLjc1IDUyOS42OTEgMTg0LjA3IDUxNy4wNDYgMjAzLjUzM1YyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS42OTYgMjMzLjk0MyA1MjAuMTU0IDI0My44MTcgNTE3LjU2MyAyNTYuOTA1QzUyNC4xODYgMjUxLjA1NSA1MzIuNzM3IDI0Ny41MjUgNTQyLjA4MiAyNDcuNTI1SDU3NC4xMDNWMjMzLjk0M0g1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY5LjgzM0M1NTAuMTYzIDE3MS42MDUgNTI5LjI1MiAxODUuMzQyIDUxNy4wNDYgMjA1LjMxNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMi4xMTIgMjMzLjk0MyA1MjAuODU1IDI0My4yMzEgNTE3LjgyNSAyNTUuNzI2QzUyNC4zNTYgMjQ5LjU3NSA1MzMuMDE1IDI0NS44MjEgNTQyLjUxNCAyNDUuODIxSDU3NC4wOTZWMjMzLjkzNUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE3MC44NzNDNTQ5Ljg0NiAxNzIuNDYxIDUyOC43MzUgMTg2LjY2OCA1MTcuMDQ2IDIwNy4yMjZWMjMzLjk0M0M1MTcuMDQ2IDIwMi45MDkgNTQzLjEzMSAxNzcuMzY0IDU3NC4wOTYgMTc2Ljk0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTU0NS41NTIgMjMzLjk0M0M1MzIuNTM2IDIzMy45NDMgNTIxLjU3MiAyNDIuNjUzIDUxOC4xNDEgMjU0LjU0N0M1MjQuNTU2IDI0OC4wOTUgNTMzLjMgMjQ0LjEyNSA1NDIuOTUzIDI0NC4xMjVINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzEuOTIxQzU0OS40NzYgMTczLjMwMSA1MjguMTI2IDE4OC4wNzggNTE3LjA0NiAyMDkuMjk5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMyLjk3NiAyMzMuOTQzIDUyMi4zMDUgMjQyLjA4MyA1MTguNTA0IDI1My4zNzVDNTI0Ljc3MiAyNDYuNjMgNTMzLjYwMSAyNDIuNDI5IDU0My4zNzcgMjQyLjQyOUg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTcyLjk3OEM1NDkuMDIxIDE3NC4xMzQgNTI3LjM4NiAxODkuNjA1IDUxNy4wNDYgMjExLjYwNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMy40MDggMjMzLjk0MyA1MjMuMDQ1IDI0MS41MzUgNTE4LjkyIDI1Mi4yMjZDNTI1LjAxOSAyNDUuMTgxIDUzMy45MDEgMjQwLjczNCA1NDMuODE3IDI0MC43MzRINTc0LjEwNFYyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzQuMDU3QzU0OC40NDMgMTc0Ljk3NCA1MjYuNDM4IDE5MS4zMTYgNTE3LjA0NiAyMTQuMjc5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMzLjg0NyAyMzMuOTQzIDUyMy43OTMgMjQwLjk5NiA1MTkuMzk4IDI1MS4wNzhDNTI1LjI4MSAyNDMuNzMyIDUzNC4yMjUgMjM5LjAzOCA1NDQuMjU2IDIzOS4wMzhINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMV83Ij4KPHJlY3Qgd2lkdGg9IjU2NiIgaGVpZ2h0PSIyMjIuMDA4IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTE3IDY5KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="}}]); \ No newline at end of file diff --git a/assets/js/1bdfe0fa.46b495c0.js b/assets/js/1bdfe0fa.46b495c0.js new file mode 100644 index 000000000..828a67a3f --- /dev/null +++ b/assets/js/1bdfe0fa.46b495c0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7277],{8688:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>o,contentTitle:()=>r,default:()=>u,frontMatter:()=>n,metadata:()=>N,toc:()=>l});var a=i(7462),M=(i(7294),i(3905));const n={title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},r=void 0,N={permalink:"/blog/pixi-v8-launches",source:"@site/blog/2024-03-05-pixi-v8-launches.md",title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",date:"2024-03-05T00:00:00.000Z",formattedDate:"March 5, 2024",tags:[{label:"PixiJS",permalink:"/blog/tags/pixi-js"},{label:"WebGPU",permalink:"/blog/tags/web-gpu"},{label:"WebGL",permalink:"/blog/tags/web-gl"}],readingTime:9.86,hasTruncateMarker:!1,authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],frontMatter:{title:"PixiJS v8 Launches! \ud83c\udf89",description:"PixiJS v8 The Future of 2D Web Graphics is Here!",slug:"pixi-v8-launches",authors:[{name:"GoodBoyDigital",title:"PixiJS Creator",url:"https://github.com/GoodBoyDigital",image_url:"https://github.com/GoodBoyDigital.png",imageURL:"https://github.com/GoodBoyDigital.png"}],tags:["PixiJS","WebGPU","WebGL"],hide_table_of_contents:!0,keywords:["PixiJS","pixi.js","webGL","webGPU","performance","2d rendering","2d webGL","javascript graphics","game development"]},nextItem:{title:"PixiJS v8 Beta! \ud83c\udf89",permalink:"/blog/pixi-v8-beta"}},o={authorsImageUrls:[void 0]},l=[{value:"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8",id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8",level:2},{value:"\ud83d\udd17 Quick links",id:"-quick-links",level:2},{value:"\ud83c\udf81 Whats New?",id:"-whats-new",level:2},{value:"\ud83d\udcc8 New Performance Bar",id:"-new-performance-bar",level:4},{value:"\ud83d\udda5\ufe0f WebGPU Renderer",id:"\ufe0f-webgpu-renderer",level:4},{value:"\ud83d\udce6 New Package Structure",id:"-new-package-structure",level:4},{value:"\u2728 We promise the Renderer will work",id:"-we-promise-the-renderer-will-work",level:3},{value:"\ud83c\udf1f Scene Upgrades",id:"-scene-upgrades",level:4},{value:"\ud83c\udfa8 Graphics Upgrades",id:"-graphics-upgrades",level:4},{value:"\ud83d\udcdd Text Upgrades",id:"-text-upgrades",level:4},{value:"\ud83e\udd1d What now? Get involved!",id:"-what-now-get-involved",level:2},{value:"\ud83d\udcf2 Keep in touch",id:"-keep-in-touch",level:2}],g={toc:l};function u(e){let{components:t,...n}=e;return(0,M.kt)("wrapper",(0,a.Z)({},g,n,{components:t,mdxType:"MDXLayout"}),(0,M.kt)("p",null,"Get ready to push the boundaries of what's possible on the web! PixiJS v8 has landed, and it's a game-changer. Celebrating a decade of driving innovation, we've supercharged PixiJS with the latest technological advancements, making it faster, more robust, and ridiculously powerful. From the seamless integration of WebGPU to leveraging modern JavaScript for smoother development, PixiJS v8 is all about empowering you to create jaw-dropping web experiences with ease. It's not just an update; it's the future of 2D web graphics, today. Dive in and let PixiJS v8 elevate your projects to unseen heights. Let's make the web a more beautiful place, one pixi(el) at a time."),(0,M.kt)("h2",{id:"-revolutionizing-web-graphics-welcome-to-pixijs-v8"},"\ud83d\ude80 Revolutionizing Web Graphics: Welcome to PixiJS v8"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(2324).Z,width:"1600",height:"360"})),(0,M.kt)("p",null,"It's hard to believe that PixiJS has been part of the open-source community for a whopping ten years. In that time, the digital landscape has evolved tremendously, and so has PixiJS. We've seen significant updates, like the transition to TypeScript, and we've overhauled major parts of the engine, such as asset loading and WebGL integration."),(0,M.kt)("p",null,"Now, we're thrilled to unveil PixiJS v8, arguably our most substantial update ever. This release is not just a reflection on the shortcomings of v7, which has served us well, but an acknowledgment that there's always room for improvement. Over time, we've all encountered aspects of our code we wished we could refine. Often, the best solutions and insights emerge only after we've stepped back from the problem, allowing us to see the bigger picture."),(0,M.kt)("p",null,"With PixiJS v8, our aim was to revisit and enhance the foundation of PixiJS, streamlining its core rather than just adding layers of code."),(0,M.kt)("p",null,"Our vision for v8 was clear:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Longevity:")," We designed v8 to stand the test of time, anticipating it will remain relevant and robust for another decade."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Innovation with WebGPU:")," Embracing the latest in rendering technology, we've seamlessly integrated WebGPU, not as an add-on to our existing WebGL renderer but as a core paradigm, ensuring PixiJS remains at the cutting edge as WebGL phases out."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Leveraging Modern JavaScript:")," The advancements in JavaScript have significantly simplified development. We've utilized features like object destructuring and options to make v8 cleaner and more powerful."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Correcting Past Oversights:")," Every project has its lessons. With v8, we've addressed and rearchitected certain aspects of PixiJS, reducing complexity and enhancing functionality, particularly in areas we felt were overengineered in the past (looking at you, textures!)."),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"Boosting Performance:")," PixiJS is already renowned for its speed. With v8, we've unlocked even greater performance, making it faster across the board compared to v7.")),(0,M.kt)("p",null,"We're incredibly proud of PixiJS v8 and eager to share the improvements and new features with you. While there are some breaking API changes, we've provided a migration guide and ensured compatibility with v7 wherever possible. Get ready to experience the next level of 2D rendering with PixiJS v8!"),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-quick-links"},"\ud83d\udd17 Quick links"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The new Docs for v8 can be found ",(0,M.kt)("a",{parentName:"li",href:"https://pixijs.download/v8.0.0/docs/index.html"},"here")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"8.x/guides/migrations/v8"},"Migration")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"8.x/examples"},"Examples")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/pixijs/open-games"},"Open Games"))),(0,M.kt)("hr",null),(0,M.kt)("h2",{id:"-whats-new"},"\ud83c\udf81 Whats New?"),(0,M.kt)("p",null,"There are numerous updates to discuss, more than can be covered in a single post! Below are the key highlights. For a more detailed exploration of these changes, be sure to follow the links provided above."),(0,M.kt)("h4",{id:"-new-performance-bar"},"\ud83d\udcc8 New Performance Bar"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"bunnies",src:i(4636).Z,width:"1600",height:"718"})),(0,M.kt)("p",null,"The performance of v8 is faster for ",(0,M.kt)("strong",{parentName:"p"},"both")," renderers. This means by using v8 and the WebGL renderer, all the speed improvements apply! This is mainly as we have taken great care to make a more reactive render loop that only updates what it needs to. Check out the numbers here:"),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"CPU")," = time spent by the CPU rendering a single frame"),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("strong",{parentName:"li"},"GPU")," = time spent by the GPU rendering a single frame")),(0,M.kt)("table",null,(0,M.kt)("thead",{parentName:"table"},(0,M.kt)("tr",{parentName:"thead"},(0,M.kt)("th",{parentName:"tr",align:null},"Bunny Situation"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 CPU"),(0,M.kt)("th",{parentName:"tr",align:null},"CPU Dif"),(0,M.kt)("th",{parentName:"tr",align:null},"V7 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"V8 GPU"),(0,M.kt)("th",{parentName:"tr",align:null},"GPU dif"))),(0,M.kt)("tbody",{parentName:"table"},(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites all moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~15ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"233%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites not moving"),(0,M.kt)("td",{parentName:"tr",align:null},"~21ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.12ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"17417%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~0.5ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"1700%"))),(0,M.kt)("tr",{parentName:"tbody"},(0,M.kt)("td",{parentName:"tr",align:null},"100k sprites (changing scene structure)"),(0,M.kt)("td",{parentName:"tr",align:null},"~50ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~24ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"108%")),(0,M.kt)("td",{parentName:"tr",align:null},"~9ms"),(0,M.kt)("td",{parentName:"tr",align:null},"~2ms"),(0,M.kt)("td",{parentName:"tr",align:null},(0,M.kt)("div",{style:{backgroundColor:"lightgreen",color:"black"}},"350%"))))),(0,M.kt)("p",null,"These benchmark numbers are based on the Bunnymark test that you can try yourself."),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v7&count=100000&renderer=webgpu"},"v7 Bunnymark")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgpu"},"v8 Bunnymark - WebGPU")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://goodboydigital.github.io/pixi-bunnymark/dist/?version=v8&count=100000&renderer=webgl"},"v8 Bunnymark - WebGL")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("a",{parentName:"li",href:"https://github.com/GoodBoyDigital/pixi-bunnymark"},"Repo"))),(0,M.kt)("h4",{id:"\ufe0f-webgpu-renderer"},"\ud83d\udda5\ufe0f WebGPU Renderer"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS + webGPU = love",src:i(9349).Z,width:"1390",height:"322"})),(0,M.kt)("p",null,"We've implemented a WebGPU backend for rendering. Whilst this has created a better graphics paradigm under the hood and set us up for the future of rich web content, it's important to note that WebGPU does not automatically guarantee improved performance over WebGL in all scenarios, as PixiJS often encounters more limitations on the CPU side than the GPU. However, for scenes with numerous batch breaks, such as filters, masks, and blend modes, WebGPU may offer better performance due to its more modern to rendering. As WebGPU is relatively new, it's expected to enhance in speed over time, similar to the development of WebGL. It serves as a solid foundation for future advancements."),(0,M.kt)("h4",{id:"-new-package-structure"},"\ud83d\udce6 New Package Structure"),(0,M.kt)("p",null,'No more "lerna." PixiJS is now just one package with one import root: ',(0,M.kt)("inlineCode",{parentName:"p"},"import {stuff} from \u2018pixi.js\u2019"),". This change means we now have much better tree shaking during app compilation, reducing bundle size if not imported."),(0,M.kt)("p",null,"Old:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite } from "@pixi/sprite";\nimport { Graphic } from "@pixi/graphics";\n')),(0,M.kt)("p",null,"New:"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Sprite, Graphic } from "pixi.js";\n')),(0,M.kt)("h3",{id:"-we-promise-the-renderer-will-work"},"\u2728 We ",(0,M.kt)("em",{parentName:"h3"},"promise")," the Renderer will work"),(0,M.kt)("p",null,"When initializing a renderer, this process is now asynchronous. This serves two purposes: firstly, identifying and loading the necessary renderer code to minimize what is loaded for your users. We only load the one backend that your user is using. There's no point in loading all the WebGL stuff if they are using WebGPU. Secondly, the initialization of WebGPU itself is an asynchronous process, so we need to have a promise in there somewhere!"),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},'import { Application, autoDetectRenderer } from "pixi.js";\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // or\n const renderer = await autoDetectRenderer({}); // WebGL or WebGPU\n\n // do pixi things\n})();\n')),(0,M.kt)("h4",{id:"-scene-upgrades"},"\ud83c\udf1f Scene Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"PixiJS logo",src:i(3012).Z,width:"1000",height:"400"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"The concept of render groups has been introduced, enabling containers to utilize GPU for their transformations. This facilitates a true 2D hardware-accelerated camera, ideal for navigating large static worlds through panning and zooming, similar to how a camera moves in a 3D environment rather than moving the world itself. This approach can significantly enhance performance.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container({\n isRenderGroup:true // this containers transform is now handled on the GPU!\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"Another cool new change is that now blend modes and tints are inherited, much like transforms and alpha. This means you can now easily tint a container, and all its children will have the tint applied - same for blend modes, its as easy as:")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"// will make all the children tinted red\ncontainer.tint = 'red'\n// will make all the children have the add blend mode\ncontainer.blendMode = 'add'\n")),(0,M.kt)("p",null,"Rendering to a texture with antialiasing has been simplified; you only need to enable the new antialiasing property by setting it to true during the creation of a render texture or when applying a filter, similar to the process used for creating your renderer."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"},"const texture = RenderTexture.create({\n width:100,\n height:100,\n antialias:true // easy as that\n})\n")),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},"We have also added support for a wide range of Photoshop-like filters, This allows you to take your rendering to the next level! We have including all the classics:",(0,M.kt)("ul",{parentName:"li"},(0,M.kt)("li",{parentName:"ul"},"ColorBlend, ColorBurnBlend, ColorDodgeBlend, DarkenBlend, DifferenceBlend, DivideBlend, ExclusionBlend, HardLightBlend, HardMixBlend, LightenBlend, LinearBurnBlend, LinearDodgeBlend, LinearLightBlend, LuminosityBlend, NegationBlend, OverlayBlend, PinLightBlend, SaturationBlend, SoftLightBlend, SubtractBlend, VividLightBlend."))),(0,M.kt)("li",{parentName:"ul"},"It's important to mention that these are essentially filters at the core, so it's advisable not to overuse them to avoid potential slowdowns.")),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"import `pixi.js/advanced-blend-modes` // make sure to include them in you lib! (or cherry pick one!)\n\nmyContainer.blendMode = 'color-burn` // easy!\n")),(0,M.kt)("h4",{id:"-graphics-upgrades"},"\ud83c\udfa8 Graphics Upgrades"),(0,M.kt)("p",null,(0,M.kt)("img",{alt:"alt text",src:i(8074).Z,width:"2952",height:"826"})),(0,M.kt)("ul",null,(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The Graphics API has undergone changes to become more intuitive and user-friendly, closely resembling the HTML Canvas 2D context API. For instance, drawing and filling a rectangle is simplified as follows:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics\n .rect(50, 50, 100, 100)\n .fill('blue');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"A ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsContext")," has been introduced, powering all graphics operations. Similar to how one texture can be used across many sprites, a single GraphicsContext can now be utilized by multiple Graphics objects, enhancing efficiency and flexibility.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Support for SVG drawing has been added. For example:"),(0,M.kt)("pre",{parentName:"li"},(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"graphics.svg('M 100 350 q 150 -300 300 0');\n"))),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"Gradient fill support has been introduced, currently limited to linear gradients, allowing for more visually engaging designs.")),(0,M.kt)("li",{parentName:"ul"},(0,M.kt)("p",{parentName:"li"},"The new ",(0,M.kt)("inlineCode",{parentName:"p"},"GraphicsPath")," class enables the drawing and sharing of shapes. This feature is particularly useful as it allows for the creation of paths that can then be transformed into Mesh geometry using the ",(0,M.kt)("inlineCode",{parentName:"p"},"buildGeometryFromPath")," function, opening up new possibilities for intricate and detailed graphic designs."))),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre"}," const path = new GraphicsPath()\n .rect(-50, -50, 100, 100)\n\n // create geometry from the path:\n const geometry = buildGeometryFromPath({\n path,\n });\n\n const mesh = new Mesh({\n geometry,\n texture: Texture.WHITE,\n });\n\n")),(0,M.kt)("p",null,"For more information on these graphics upgrades and guidance on how to adapt to the enhanced Graphics API, please refer to the ",(0,M.kt)("a",{parentName:"p",href:"8.x/guides/migrations/v8"},"migration guide"),", or why not jump in and play with some ",(0,M.kt)("a",{parentName:"p",href:"8.x/examples/graphics/simple"},"examples"),"."),(0,M.kt)("h4",{id:"-text-upgrades"},"\ud83d\udcdd Text Upgrades"),(0,M.kt)("p",null,"Text has been upgraded to allow for better performance and usability! We have also integrated HTMLText into v8 as standard."),(0,M.kt)("p",null,"BitmapFonts can now be generated on the fly or installed upfront as you prefer. They dynamically add characters as the font's glyphs are required, saving on memory. The layout of bitmap text is almost identical to the layout of the default text now, making it easier to switch between the two depending on your needs."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"\nconst myText = new BitmapText({\n text: 'hello im a bitmap font!',\n // font will be dynamically created\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: 'red',\n }\n})\n")),(0,M.kt)("p",null,"Text fills and strokes now conform to the same fills and strokes as graphics. This means Gradients, textures, and all the fun ways you can fill and stroke graphics can now be applied to Text."),(0,M.kt)("pre",null,(0,M.kt)("code",{parentName:"pre",className:"language-ts"},"const myText = new Text({\n text: 'hello im some fancy text',\n // font will be dynamically created!\n style:{\n fontFamily: 'Outfit',\n fontSize: 12,\n fill: { texture, color:'red'} // same as graphics api fills\n stroke: { width:3, color:'blue' } // same as graphics api strokes\n }\n})\n")),(0,M.kt)("h2",{id:"-what-now-get-involved"},"\ud83e\udd1d What now? Get involved!"),(0,M.kt)("p",null,"As PixiJS v8 takes its first steps into the world, we're eager to see it grow with your feedback and contributions. Now we know things won't be perfect, but we're committed to quick responses on ",(0,M.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub")," and ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," to any issues that arise, valuing your input to make PixiJS even better."),(0,M.kt)("p",null,"A heartfelt thanks to our early adopters (everyone in ",(0,M.kt)("a",{parentName:"p",href:"https://discord.com/channels/734147990985375826/1143191340230914068"},"here"),") for testing the limits of v8, to our dedicated contributors and team for their hard work. Your efforts and insights are invaluable to us. We could not have gotten here without you!"),(0,M.kt)("p",null,"A final big shout-out to PlayCo for their support in making this release a reality!"),(0,M.kt)("p",null,"Let's continue to innovate and push the boundaries of web graphics together. Your engagement is key to PixiJS's evolution, and we're excited to see where we can go with your help."),(0,M.kt)("h2",{id:"-keep-in-touch"},"\ud83d\udcf2 Keep in touch"),(0,M.kt)("p",null,"To stay in the loop, we invite you to follow ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/Doormat23"},"Doormat23")," and ",(0,M.kt)("a",{parentName:"p",href:"https://twitter.com/PixiJS"},"PixiJS")," on social media, where we'll be unveiling more exciting updates shortly. Alternatively, you can join our vibrant community on ",(0,M.kt)("a",{parentName:"p",href:"https://discord.gg/nrnDP9wtyX"},"Discord")," for direct engagement and real-time chit-chats."))}u.isMDXComponent=!0},3012:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/blend-modes-2c499d097578c17f1154867dbeead3a4.png"},9349:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-1-82838aaed491ecba56cb42c2ee5aa1fa.png"},8074:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-4-8e8a66475de0767192be60bb45ccf6f1.png"},4636:(e,t,i)=>{i.d(t,{Z:()=>a});const a=i.p+"assets/images/image-72e0ca7d8183d62c10a0f5e8dcc4ebda.png"},2324:(e,t,i)=>{i.d(t,{Z:()=>a});const a="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYwMCIgaGVpZ2h0PSIzNjAiIHZpZXdCb3g9IjAgMCAxNjAwIDM2MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE2MDAiIGhlaWdodD0iMzYwIiBmaWxsPSIjMjUyQjM2Ii8+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xXzcpIj4KPHBhdGggZD0iTTc1MS41MjggMjkxVjEzOC4zNjVINzA1Ljg4M0M3MDUuNzI4IDEzOC4zNjUgNzA1LjU2NiAxMzguMzY1IDcwNS4zOTcgMTM4LjM3M0M2OTcuMTkzIDEzOC42NTggNjkwLjYgMTQ1LjQwMiA2OTAuNTIzIDE1My42MDRDNjkwLjUyMyAxNTMuNjUgNjkwLjUyMyAxNTMuNjk2IDY5MC41MjMgMTUzLjczNVYyNzUuODg0QzY5MC41MjMgMjc2LjEzOSA2OTAuNTM5IDI3Ni40MjQgNjkwLjU2MiAyNzYuNzRDNjkxLjI1NiAyODQuNjAyIDY5Ny44NDEgMjkwLjYzIDcwNS43MjggMjkwLjk2OUM3MDYuMDYgMjkwLjk4NSA3MDYuMzc2IDI5MC45OTIgNzA2LjY2OSAyOTAuOTkySDc1MS41MjhWMjkxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTM5LjY5NCAyOTAuOTIzQzk0OC4xOTggMjkwLjI4MyA5NTQuOTMgMjgzLjI4NCA5NTQuOTkxIDI3NC43NTFDOTU0Ljk5MSAyNzQuNjk3IDk1NC45OTEgMjc0LjY0MyA5NTQuOTkxIDI3NC41OVYxNTMuOTEyQzk1NC45OTEgMTUzLjg2NiA5NTQuOTkxIDE1My44MTIgOTU0Ljk5MSAxNTMuNzY2Qzk1NC45NTMgMTQ1LjMzMyA5NDguMDkgMTM4LjQ1NyA5MzkuNjU1IDEzOC4zNzNDOTM5LjU5NCAxMzguMzczIDkzOS41MzIgMTM4LjM3MyA5MzkuNDcgMTM4LjM3M0g4OTMuOTg2VjI5MS4wMDhIOTM3Ljc4OUM5MzguMzgzIDI5MS4wMDggOTM5LjAyMyAyOTAuOTc3IDkzOS42OTQgMjkwLjkzMVYyOTAuOTIzWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNNzQyLjk3IDEzOC4zNzNINzkwLjc3NEM3OTUuNDYyIDEzOC4zNzMgODAyLjA5MyAxNDIuMTY1IDgwNS41OTQgMTQ2Ljg1MUw5MDYuOTYzIDI4Mi41MTNDOTEwLjQ2NCAyODcuMiA5MDkuNSAyOTAuOTkyIDkwNC44MiAyOTAuOTkySDg1Ny4wMTVDODUyLjMyNyAyOTAuOTkyIDg0NS42OTYgMjg3LjIgODQyLjE5NSAyODIuNTEzTDc0MC44MTkgMTQ2Ljg1MUM3MzcuMzE4IDE0Mi4xNzMgNzM4LjI4MiAxMzguMzczIDc0Mi45NjIgMTM4LjM3M0g3NDIuOTdaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik05MDEuMDM0IDEzOC4zNzNIODUzLjIyOUM4NDguNTQxIDEzOC4zNzMgODQxLjkxIDE0Mi4xNjUgODM4LjQxIDE0Ni44NTFMNzM3LjA0IDI4Mi41MTNDNzMzLjU0IDI4Ny4yIDczNC41MDQgMjkwLjk5MiA3MzkuMTg0IDI5MC45OTJINzg2Ljk4OUM3OTEuNjc3IDI5MC45OTIgNzk4LjMwOCAyODcuMiA4MDEuODA4IDI4Mi41MTNMOTAzLjE3NyAxNDYuODUxQzkwNi42NzggMTQyLjE3MyA5MDUuNzE0IDEzOC4zNzMgOTAxLjAzNCAxMzguMzczWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNOTI0LjkzNiA2OUM5NDEuMTEzIDY5IDk1NC4yMzYgODIuMTExNCA5NTQuMjM2IDk4LjI5MDZDOTU0LjIzNiAxMTQuNDcgOTQxLjEyIDEyNy41ODEgOTI0LjkzNiAxMjcuNTgxQzkwOC43NTIgMTI3LjU4MSA4OTUuNjM2IDExNC40NyA4OTUuNjM2IDk4LjI5MDZDODk1LjYzNiA4Mi4xMTE0IDkwOC43NTIgNjkgOTI0LjkzNiA2OVoiIGZpbGw9IiNFNzIyNjQiLz4KPHBhdGggZD0iTTcyMS4zODEgNjlDNzM3LjU1NyA2OSA3NTAuNjggODIuMTExNCA3NTAuNjggOTguMjkwNkM3NTAuNjggMTE0LjQ3IDczNy41NjUgMTI3LjU4MSA3MjEuMzgxIDEyNy41ODFDNzA1LjE5NiAxMjcuNTgxIDY5Mi4wODEgMTE0LjQ3IDY5Mi4wODEgOTguMjkwNkM2OTIuMDgxIDgyLjExMTQgNzA1LjE5NiA2OSA3MjEuMzgxIDY5WiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBkPSJNMTA1Mi45MyA2OUgxMDA2LjY3Qzk5MC4wNTggNjkgOTc2LjU5NiA4Mi40NTgzIDk3Ni41OTYgOTkuMDYxNVYxNDUuMzFDOTc2LjU5NiAxNjEuOTEzIDk5MC4wNTggMTc1LjM3MSAxMDA2LjY3IDE3NS4zNzFIMTA1Mi45M0MxMDY5LjU0IDE3NS4zNzEgMTA4MyAxNjEuOTEzIDEwODMgMTQ1LjMxVjk5LjA2MTVDMTA4MyA4Mi40NTgzIDEwNjkuNTQgNjkgMTA1Mi45MyA2OVpNMTAyMi4zNyA5OS4zMjM1VjEzMC41NDFDMTAyMi4zNyAxMzQuNzUgMTAyMS41MyAxMzguNDAzIDEwMTkuODUgMTQxLjQ5NEMxMDE4LjE3IDE0NC41ODUgMTAxNS43NyAxNDYuOTgyIDEwMTIuNjYgMTQ4LjY4NkMxMDA5LjU0IDE1MC4zODkgMTAwNS44NSAxNTEuMjQ1IDEwMDEuNTkgMTUxLjI0NUM5OTcuOTYyIDE1MS4yNDUgOTk0LjgzMSAxNTAuNTkgOTkyLjE4NiAxNDkuMjg3Qzk4OS41NDIgMTQ3Ljk4NSA5ODcuMzIxIDE0Ni4xNTggOTg1LjUwOSAxNDMuODE0Qzk4NS41MDkgMTQzLjgxNCA5ODAuNDU5IDEzOC4zNjUgOTg1LjA4NSAxMzQuNTE5Qzk4OS44MTIgMTMwLjU4NyA5OTMuOTA2IDEzNS43MzYgOTkzLjkwNiAxMzUuNzM2Qzk5NC44NjIgMTM3LjA3IDk5NS45OTUgMTM4LjA3MiA5OTcuMzA2IDEzOC43MzVDOTk4LjYwOSAxMzkuNDA1IDEwMDAuMDkgMTM5LjczNyAxMDAxLjc1IDEzOS43MzdDMTAwMy4yOSAxMzkuNzM3IDEwMDQuNjUgMTM5LjQyMSAxMDA1LjgzIDEzOC43ODFDMTAwNyAxMzguMTQxIDEwMDcuOTMgMTM3LjE4NiAxMDA4LjYzIDEzNS45MDZDMTAwOS4zMiAxMzQuNjI2IDEwMDkuNjcgMTMzLjA1NCAxMDA5LjY3IDEzMS4xODlWOTkuMzMxMkMxMDA5LjY3IDk5LjMzMTIgMTAwOS41NCA5Mi45MDI3IDEwMTUuOTMgOTIuOTAyN0MxMDIyLjMxIDkyLjkwMjcgMTAyMi4zOCA5OS4zMzEyIDEwMjIuMzggOTkuMzMxMkwxMDIyLjM3IDk5LjMyMzVaTTEwNjcuOCAxNDQuODMyQzEwNjQuMyAxNDguMjg1IDEwNTkuMTYgMTUwLjM2NiAxMDUyLjM3IDE1MS4wNzVDMTA0Ny43MSAxNTEuNTY5IDEwNDMuNjQgMTUxLjI0NSAxMDQwLjE3IDE1MC4xMTJDMTAzNi43IDE0OC45NzkgMTAzMy40NSAxNDcuMDEzIDEwMzAuNDIgMTQ0LjIyM0MxMDMwLjQyIDE0NC4yMjMgMTAyNS43MyAxNDAuMTg0IDEwMjkuNTEgMTM1LjExMkMxMDMyLjM1IDEzMS4yOTcgMTAzNy42OSAxMzUuMjU5IDEwMzcuNjkgMTM1LjI1OUMxMDM5LjcxIDEzNy4xMzkgMTA0MS44NyAxMzguNTI3IDEwNDQuMTkgMTM5LjQzNkMxMDQ2LjUxIDE0MC4zNDYgMTA0OS4xMyAxNDAuNjQ2IDEwNTIuMDUgMTQwLjMzOEMxMDU0LjY1IDE0MC4wNjggMTA1Ni42MiAxMzkuMzU5IDEwNTcuOTggMTM4LjIyNkMxMDU5LjM0IDEzNy4wOTMgMTA1OS45MiAxMzUuNjc1IDEwNTkuNzUgMTMzLjk3OUMxMDU5LjU5IDEzMi40OTkgMTA1OSAxMzEuMzI3IDEwNTcuOTcgMTMwLjQ3MkMxMDU2Ljk1IDEyOS42MTYgMTA1NS42IDEyOC45MyAxMDUzLjkzIDEyOC40MDZDMTA1Mi4yNyAxMjcuODgyIDEwNTAuNDggMTI3LjQxOSAxMDQ4LjU2IDEyNy4wMDNDMTA0Ni42NCAxMjYuNTg3IDEwNDQuNjkgMTI2LjA1NSAxMDQyLjcyIDEyNS40MDhDMTA0MC43NSAxMjQuNzYgMTAzOC45MiAxMjMuOTA1IDEwMzcuMjIgMTIyLjg0OUMxMDM1LjUzIDEyMS43OTMgMTAzNC4xMSAxMjAuMzY3IDEwMzIuOTUgMTE4LjU1NUMxMDMxLjc5IDExNi43NDQgMTAzMS4wNyAxMTQuNDM5IDEwMzAuNzggMTExLjYzM0MxMDMwLjQxIDEwOC4xMzQgMTAzMC45MyAxMDUuMDUxIDEwMzIuMzMgMTAyLjM4NEMxMDMzLjc1IDk5LjcxNjYgMTAzNS44OSA5Ny41NzM4IDEwMzguNzcgOTUuOTU1MUMxMDQxLjY2IDk0LjMzNjQgMTA0NS4wMSA5My4zMjY3IDEwNDguODMgOTIuOTI1OEMxMDUyLjgxIDkyLjUwOTYgMTA1Ni40OCA5Mi44MTc5IDEwNTkuODYgOTMuODUwOEMxMDYzLjI0IDk0Ljg5MTQgMTA2Ni4xMSA5Ni40NjM4IDEwNjguNDggOTguNTY4MUMxMDY4LjQ4IDk4LjU2ODEgMTA3MS44IDEwMS4zNTEgMTA2OS4xMSAxMDYuMTM3QzEwNjcuMDIgMTA5Ljg2IDEwNjEuMTUgMTA3LjYyNSAxMDYxLjE1IDEwNy42MjVDMTA1OS4yNyAxMDYuMDUzIDEwNTcuNDIgMTA0LjkzNSAxMDU1LjYxIDEwNC4yNzJDMTA1My44IDEwMy42MDkgMTA1MS44MyAxMDMuMzg2IDEwNDkuNzEgMTAzLjYwOUMxMDQ3LjU0IDEwMy44NCAxMDQ1Ljg2IDEwNC40MzQgMTA0NC42NyAxMDUuMzgyQzEwNDMuNDggMTA2LjMzOCAxMDQyLjk4IDEwNy41ODcgMTA0My4xNCAxMDkuMTJDMTA0My4yOCAxMTAuNSAxMDQzLjg4IDExMS41NzIgMTA0NC45MyAxMTIuMzVDMTA0NS45NyAxMTMuMTIxIDEwNDcuMzEgMTEzLjc0NSAxMDQ4Ljk0IDExNC4yMTZDMTA1MC41NyAxMTQuNjg2IDEwNTIuMzcgMTE1LjE0IDEwNTQuMzEgMTE1LjU4QzEwNTYuMjYgMTE2LjAxOSAxMDU4LjIyIDExNi41NTEgMTA2MC4xOCAxMTcuMTc1QzEwNjIuMTUgMTE3LjggMTA2My45OCAxMTguNjk0IDEwNjUuNjUgMTE5Ljg1OEMxMDY3LjMyIDEyMS4wMjIgMTA2OC43NiAxMjIuNTMzIDEwNjkuOTUgMTI0LjM5QzEwNzEuMTMgMTI2LjI0OCAxMDcxLjg4IDEyOC42NiAxMDcyLjIgMTMxLjYyOEMxMDcyLjc2IDEzNi45ODUgMTA3MS4zIDE0MS4zODYgMTA2Ny44IDE0NC44MzJaIiBmaWxsPSIjRTcyMjY0Ii8+CjxwYXRoIGQ9Ik01OTkuNjMzIDY5LjI2MjFDNTUzLjk5NSA2OS4yNjIxIDUxNyAxMDYuMjQ1IDUxNyAxNTEuODY5QzUxNyAxNTIuNzY0IDUxNy4wMTUgMTUzLjY1IDUxNy4wNDYgMTU0LjUzNlYyMzMuOTIyQzUxNy4wNDYgMjMzLjkyMiA1MTcuMDQ2IDIzMy45MjkgNTE3LjA0NiAyMzMuOTM3VjI0Mi4wMDhDNTE3LjA0NiAyNDIuMDA4IDUxNy4wNDYgMjQyLjAwOCA1MTcuMDU0IDI0MlYyNjEuOTU2QzUxNy4wNTQgMjYyLjExIDUxNy4wNDYgMjYyLjI1NyA1MTcuMDQ2IDI2Mi40MTFDNTE3LjA0NiAyNjIuNTY1IDUxNy4wNTQgMjYyLjcxOSA1MTcuMDYyIDI2Mi44NzNWMjYzLjI0M0g1MTcuMDY5QzUxNy41MDkgMjc4LjU3NSA1MzAuMDY5IDI5MC44NjkgNTQ1LjUxMyAyOTAuODY5QzU2MC45NTcgMjkwLjg2OSA1NzMuNjQ5IDI3OC41NzUgNTc0LjA4OCAyNjMuMjQzVjIzMy45MzdINTk3LjY5N0M1OTguMzM3IDIzMy45NTMgNTk4Ljk4NSAyMzMuOTYgNTk5LjYyNSAyMzMuOTZDNjQ1LjI2MyAyMzMuOTYgNjgyLjI1OCAxOTcuNDkzIDY4Mi4yNTggMTUxLjg2OUM2ODIuMjU4IDEwNi4yNDUgNjQ1LjI3MSA2OS4yNjIxIDU5OS42MzMgNjkuMjYyMVpNNTk5LjYzMyAxNzYuOTIxQzU5NC41ODIgMTc2LjkyMSA1ODEuNzgzIDE3Ni45MjEgNTc2LjQ3MSAxNzYuOTIxSDU3NC4xMTFWMTUxLjg3N0M1NzQuMTExIDEzNy44NDEgNTg1LjU5MiAxMjYuNDU2IDU5OS42MzMgMTI2LjQ1NkM2MTMuNjczIDEyNi40NTYgNjI1LjA2MiAxMzcuODQxIDYyNS4wNjIgMTUxLjg3N0M2MjUuMDYyIDE2NS45MTQgNjEzLjY3MyAxNzYuOTI4IDU5OS42MzMgMTc2LjkyOFYxNzYuOTIxWiIgZmlsbD0iI0U3MjI2NCIvPgo8cGF0aCBvcGFjaXR5PSIwLjE1IiBkPSJNNTQ0Ljc5NiAyMzMuOTUzSDU3NC4wNVYyNjMuMjUxSDU3My45MThDNTczLjQ3OSAyNzguNTgyIDU2MC45MTkgMjkwLjg3NyA1NDUuNDc1IDI5MC44NzdDNTMwLjAzMSAyOTAuODc3IDUxNy40NjMgMjc4LjU4MiA1MTcuMDIzIDI2My4yNTFINTE3LjAxNVYyNjIuODgxQzUxNy4wMTUgMjYyLjcyNyA1MTcgMjYyLjU3MyA1MTcgMjYyLjQxOEM1MTcgMjYyLjI2NCA1MTcuMDA4IDI2Mi4xMTggNTE3LjAwOCAyNjEuOTY0VjI1OS44OThDNTE3Ljg4NyAyNDYuMTg1IDUzMC44MzMgMjMzLjk1MyA1NDQuNzg4IDIzMy45NTNINTQ0Ljc5NloiIGZpbGw9IiMxRDFEMUIiLz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTUxNy4wNDYgMTk4LjcxNlYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRWMTY1LjcwMUM1NTEuMDggMTY4LjEyMSA1MzAuNzAxIDE4MC41MDkgNTE3LjA0NiAxOTguNzE2WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTUxNy4wOTMgMjYwLjQ1MUM1MjMuODE2IDI1NS41MTggNTMxLjk3NCAyNTIuNjEyIDU0MC43NzkgMjUyLjYxMkg1NzQuMTAzVjIzMy45NDNINTQ1LjU1MkM1MzAuNDcgMjMzLjk0MyA1MTguMTQxIDI0NS42MzYgNTE3LjA5MyAyNjAuNDUxWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY2LjczNEM1NTAuODg3IDE2OS4wMDggNTMwLjQwOCAxODEuNjY1IDUxNy4wNDYgMjAwLjI1VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMwLjg3OSAyMzMuOTQzIDUxOC44MDQgMjQ1LjAxOSA1MTcuMiAyNTkuMjY0QzUyMy45MTYgMjU0LjAyMiA1MzIuMjEzIDI1MC45MjQgNTQxLjIxMSAyNTAuOTI0SDU3NC4wOTZWMjMzLjk1MUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE2Ny43NTlDNTUwLjY3OSAxNjkuODc5IDUzMC4wNjkgMTgyLjg0NCA1MTcuMDQ2IDIwMS44NDVWMjMzLjkzNUM1MTcuMDQ2IDIwMi45MDEgNTQzLjEzMSAxNzcuMzU2IDU3NC4wOTYgMTc2LjkzMlYxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS4yODcgMjMzLjk0MyA1MTkuNDgzIDI0NC40MDMgNTE3LjM1NSAyNTguMDY5QzUyNC4wMzIgMjUyLjUyNyA1MzIuNDY3IDI0OS4yMiA1NDEuNjQzIDI0OS4yMkg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY4Ljc5MkM1NTAuNDQgMTcwLjc1IDUyOS42OTEgMTg0LjA3IDUxNy4wNDYgMjAzLjUzM1YyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMS42OTYgMjMzLjk0MyA1MjAuMTU0IDI0My44MTcgNTE3LjU2MyAyNTYuOTA1QzUyNC4xODYgMjUxLjA1NSA1MzIuNzM3IDI0Ny41MjUgNTQyLjA4MiAyNDcuNTI1SDU3NC4xMDNWMjMzLjk0M0g1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTY5LjgzM0M1NTAuMTYzIDE3MS42MDUgNTI5LjI1MiAxODUuMzQyIDUxNy4wNDYgMjA1LjMxNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMi4xMTIgMjMzLjk0MyA1MjAuODU1IDI0My4yMzEgNTE3LjgyNSAyNTUuNzI2QzUyNC4zNTYgMjQ5LjU3NSA1MzMuMDE1IDI0NS44MjEgNTQyLjUxNCAyNDUuODIxSDU3NC4wOTZWMjMzLjkzNUg1NDUuNTQ0TDU0NS41NTIgMjMzLjk0M1oiIGZpbGw9ImJsYWNrIi8+CjwvZz4KPGcgb3BhY2l0eT0iMC4wNSI+CjxwYXRoIG9wYWNpdHk9IjAuNSIgZD0iTTU3NC4wOTYgMTc2Ljk0VjE3MC44NzNDNTQ5Ljg0NiAxNzIuNDYxIDUyOC43MzUgMTg2LjY2OCA1MTcuMDQ2IDIwNy4yMjZWMjMzLjk0M0M1MTcuMDQ2IDIwMi45MDkgNTQzLjEzMSAxNzcuMzY0IDU3NC4wOTYgMTc2Ljk0WiIgZmlsbD0iYmxhY2siLz4KPHBhdGggZD0iTTU0NS41NTIgMjMzLjk0M0M1MzIuNTM2IDIzMy45NDMgNTIxLjU3MiAyNDIuNjUzIDUxOC4xNDEgMjU0LjU0N0M1MjQuNTU2IDI0OC4wOTUgNTMzLjMgMjQ0LjEyNSA1NDIuOTUzIDI0NC4xMjVINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzEuOTIxQzU0OS40NzYgMTczLjMwMSA1MjguMTI2IDE4OC4wNzggNTE3LjA0NiAyMDkuMjk5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMyLjk3NiAyMzMuOTQzIDUyMi4zMDUgMjQyLjA4MyA1MTguNTA0IDI1My4zNzVDNTI0Ljc3MiAyNDYuNjMgNTMzLjYwMSAyNDIuNDI5IDU0My4zNzcgMjQyLjQyOUg1NzQuMDk2VjIzMy45NDNINTQ1LjU0NEg1NDUuNTUyWiIgZmlsbD0iYmxhY2siLz4KPC9nPgo8ZyBvcGFjaXR5PSIwLjA1Ij4KPHBhdGggb3BhY2l0eT0iMC41IiBkPSJNNTc0LjA5NiAxNzYuOTRWMTcyLjk3OEM1NDkuMDIxIDE3NC4xMzQgNTI3LjM4NiAxODkuNjA1IDUxNy4wNDYgMjExLjYwNFYyMzMuOTQzQzUxNy4wNDYgMjAyLjkwOSA1NDMuMTMxIDE3Ny4zNjQgNTc0LjA5NiAxNzYuOTRaIiBmaWxsPSJibGFjayIvPgo8cGF0aCBkPSJNNTQ1LjU1MiAyMzMuOTQzQzUzMy40MDggMjMzLjk0MyA1MjMuMDQ1IDI0MS41MzUgNTE4LjkyIDI1Mi4yMjZDNTI1LjAxOSAyNDUuMTgxIDUzMy45MDEgMjQwLjczNCA1NDMuODE3IDI0MC43MzRINTc0LjEwNFYyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjxnIG9wYWNpdHk9IjAuMDUiPgo8cGF0aCBvcGFjaXR5PSIwLjUiIGQ9Ik01NzQuMDk2IDE3Ni45NFYxNzQuMDU3QzU0OC40NDMgMTc0Ljk3NCA1MjYuNDM4IDE5MS4zMTYgNTE3LjA0NiAyMTQuMjc5VjIzMy45NDNDNTE3LjA0NiAyMDIuOTA5IDU0My4xMzEgMTc3LjM2NCA1NzQuMDk2IDE3Ni45NFoiIGZpbGw9ImJsYWNrIi8+CjxwYXRoIGQ9Ik01NDUuNTUyIDIzMy45NDNDNTMzLjg0NyAyMzMuOTQzIDUyMy43OTMgMjQwLjk5NiA1MTkuMzk4IDI1MS4wNzhDNTI1LjI4MSAyNDMuNzMyIDUzNC4yMjUgMjM5LjAzOCA1NDQuMjU2IDIzOS4wMzhINTc0LjEwM1YyMzMuOTQzSDU0NS41NTJaIiBmaWxsPSJibGFjayIvPgo8L2c+CjwvZz4KPGRlZnM+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMV83Ij4KPHJlY3Qgd2lkdGg9IjU2NiIgaGVpZ2h0PSIyMjIuMDA4IiBmaWxsPSJ3aGl0ZSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNTE3IDY5KSIvPgo8L2NsaXBQYXRoPgo8L2RlZnM+Cjwvc3ZnPgo="}}]); \ No newline at end of file diff --git a/assets/js/1d34b751.7ebb6417.js b/assets/js/1d34b751.7ebb6417.js deleted file mode 100644 index 7cabf3067..000000000 --- a/assets/js/1d34b751.7ebb6417.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[858],{3585:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>u,toc:()=>p});var r=s(7462),i=(s(7294),s(3905)),d=s(8010),a=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Render Texture Advanced"},o=void 0,u={unversionedId:"examples/textures/render-texture-advanced",id:"examples/textures/render-texture-advanced",title:"Render Texture Advanced",description:"",source:"@site/docs/examples/textures/render-texture-advanced.md",sourceDirName:"examples/textures",slug:"/examples/textures/render-texture-advanced",permalink:"/examples/textures/render-texture-advanced",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Render Texture Advanced"},sidebar:"examplesSidebar",previous:{title:"Render Texture Basic",permalink:"/examples/textures/render-texture-basic"},next:{title:"Promise",permalink:"/examples/assets/promise"}},l={},p=[],x={toc:p};function c(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},x,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"textures.renderTextureAdvanced",pixiVersion:a,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/1d34b751.f8531309.js b/assets/js/1d34b751.f8531309.js new file mode 100644 index 000000000..477f1eb6c --- /dev/null +++ b/assets/js/1d34b751.f8531309.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[858],{3585:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>x,contentTitle:()=>o,default:()=>c,frontMatter:()=>n,metadata:()=>u,toc:()=>l});var r=s(7462),i=(s(7294),s(3905)),d=s(8010),a=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Render Texture Advanced"},o=void 0,u={unversionedId:"examples/textures/render-texture-advanced",id:"examples/textures/render-texture-advanced",title:"Render Texture Advanced",description:"",source:"@site/docs/examples/textures/render-texture-advanced.md",sourceDirName:"examples/textures",slug:"/examples/textures/render-texture-advanced",permalink:"/8.x/examples/textures/render-texture-advanced",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Render Texture Advanced"},sidebar:"examplesSidebar",previous:{title:"Render Texture Basic",permalink:"/8.x/examples/textures/render-texture-basic"},next:{title:"Promise",permalink:"/8.x/examples/assets/promise"}},x={},l=[],p={toc:l};function c(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},p,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"textures.renderTextureAdvanced",pixiVersion:a,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/1df93b7f.4261ca58.js b/assets/js/1df93b7f.4261ca58.js deleted file mode 100644 index e1036af3d..000000000 --- a/assets/js/1df93b7f.4261ca58.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3237],{2566:(e,t,i)=>{i.r(t),i.d(t,{default:()=>q});var s=i(2263),n=i(9860),a=i(6010);const r="heroBackground_QArt",o="heroBanner_pQnV",l="heroLogo_EYQj";var c=i(9960);const d="button_sPMG",h="buttonShadow_o7NZ",p="outline_NKzY",u="white_flyO";var m=i(5893);function g(e){let t=d;return e.white&&(t+=` ${u}`),e.outline&&(t+=` ${p}`),e.anim&&(t+=` ${e.anim}`),(0,m.jsxs)(c.Z,{className:t,to:e.link,style:(null==e?void 0:e.style)||{},children:[(0,m.jsx)("div",{className:h,children:(0,m.jsx)("div",{})}),(0,m.jsx)("span",{children:e.label}),(0,m.jsxs)("svg",{className:"next",version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",x:"0px",y:"0px",viewBox:"0 0 23 14",xmlSpace:"preserve",children:[(0,m.jsx)("line",{x1:"22",y1:"7",x2:"16",y2:"1"}),(0,m.jsx)("line",{x1:"22",y1:"7",x2:"16",y2:"13"}),(0,m.jsx)("line",{x1:"0",y1:"7",x2:"23",y2:"7"})]})]})}function x(){return(0,m.jsxs)("header",{className:(0,a.Z)("hero hero--dark",o),children:[(0,m.jsx)("iframe",{className:r,src:"/header/index.html"}),(0,m.jsxs)("div",{className:"container",children:[(0,m.jsx)("img",{className:l,src:"/images/logo.svg",alt:""}),(0,m.jsx)("h1",{className:"hero__subtitle",children:"The HTML5 Creation Engine"}),(0,m.jsx)("h4",{className:"hero__subsubtitle",children:"Create beautiful digital content with the fastest, most flexible 2D WebGL renderer."}),(0,m.jsxs)("div",{className:"buttonRow",children:[(0,m.jsx)(g,{label:"Download",link:"https://github.com/pixijs/pixijs/releases"}),"\xa0",(0,m.jsx)(g,{label:"Get Started",link:"/tutorials",white:!0,outline:!0})]})]})]})}const f="testimonialsSection_PtYd",v="carouselText_jMN2",b="carouselWrapper_fl0O",y="carousel_ikfF",w="carouselItem_GpMR";function j(){const e=["adobe","20th_century_fox","barclays","bbc","bose","cartoon_network","disney","google","hbo","hm","lego","lucasfilm","marvel","mcdonalds","orange","pbs","rayban","redbull","spotify","steam","tedx","toyota","ubisoft","volkswagen","youtube"];return(0,m.jsxs)("div",{className:`${f} padding-vert--lg`,children:[(0,m.jsx)("p",{className:v,children:"A mature solution for hundreds of global brands"}),(0,m.jsx)("div",{style:{"--carousel-amount":e.length},className:b,children:(0,m.jsx)("div",{className:`col col--12 ${y}`,children:[...e,...e].map(((e,t)=>(0,m.jsx)("div",{className:w,children:(0,m.jsx)("img",{src:`/images/brand-logos/${e}.png`})},t)))})})]})}const N="highlights_dgss",k="devices_MMUi",_=JSON.parse('[{"heading":"Fast","description":"PixiJS\' strength is speed. When it comes to 2D rendering, PixiJS is the fastest there is."},{"heading":"Flexible","description":"Friendly, feature-rich API lets PixiJS take care of the fundamentals whilst you focus on producing incredible multiplatform experiences."},{"heading":"Free","description":"PixiJS is and always will be Open Source, with a large and supportive community pushing its growth and evolution."}]'),S=JSON.parse('{"U":"/images/devices/device-background.png","H":[{"img":"/images/devices/device-desktop.png","alt":"Desktop","styles":{"left":"50%","width":"386px","top":0,"zIndex":1,"marginLeft":"-193px","dataDelay":0,"transform":"translateY(200vh)"},"canvas":{"left":"19px","top":"28px","width":"348px","height":"198px"}},{"img":"/images/devices/device-ipad.png","alt":"iPad","styles":{"left":"60.79%","width":"155px","top":"138px","zIndex":2,"dataDelay":0.15,"transform":"translateY(200vh)"},"canvas":{"left":"17px","top":"16px","width":"124px","height":"164px"}},{"img":"/images/devices/device-iphone.png","alt":"iPhone","styles":{"left":"75.35%","width":"92px","top":"177px","zIndex":3,"dataDelay":0.45,"transform":"translateY(200vh)"},"canvas":{"left":"10px","top":"18px","width":"72px","height":"126px"}},{"img":"/images/devices/device-phone.png","alt":"Phone","styles":{"width":"170px","right":0,"bottom":0,"zIndex":4,"dataDelay":0.75,"transform":"translateY(200vh)"},"canvas":{"left":"20px","top":"9px","width":"132px","height":"80px"}},{"img":"/images/devices/device-laptop.png","alt":"Laptop","styles":{"left":"6.16%","width":"355px","top":"127px","zIndex":2,"dataDelay":0.3,"transform":"translateY(200vh)"},"canvas":{"left":"46px","top":"16px","width":"264px","height":"166px"}},{"img":"/images/devices/device-tablet.png","alt":"Tablet","styles":{"left":0,"width":"195px","bottom":0,"zIndex":4,"dataDelay":0.6,"transform":"translateY(200vh)"},"canvas":{"left":"17px","top":"15px","width":"160px","height":"95px"}}]}');var $=i(7294);const D=new Map,I=new WeakMap;let P,C=0;function V(e){return Object.keys(e).sort().filter((t=>void 0!==e[t])).map((t=>{return`${t}_${"root"===t?(i=e.root,i?(I.has(i)||(C+=1,I.set(i,C.toString())),I.get(i)):"0"):e[t]}`;var i})).toString()}function M(e,t,i={},s=P){if(void 0===window.IntersectionObserver&&void 0!==s){const n=e.getBoundingClientRect();return t(s,{isIntersecting:s,target:e,intersectionRatio:"number"==typeof i.threshold?i.threshold:0,time:0,boundingClientRect:n,intersectionRect:n,rootBounds:n}),()=>{}}const{id:n,observer:a,elements:r}=function(e){let t=V(e),i=D.get(t);if(!i){const s=new Map;let n;const a=new IntersectionObserver((t=>{t.forEach((t=>{var i;const a=t.isIntersecting&&n.some((e=>t.intersectionRatio>=e));e.trackVisibility&&void 0===t.isVisible&&(t.isVisible=a),null==(i=s.get(t.target))||i.forEach((e=>{e(a,t)}))}))}),e);n=a.thresholds||(Array.isArray(e.threshold)?e.threshold:[e.threshold||0]),i={id:t,observer:a,elements:s},D.set(t,i)}return i}(i);let o=r.get(e)||[];return r.has(e)||r.set(e,o),o.push(t),a.observe(e),function(){o.splice(o.indexOf(t),1),0===o.length&&(r.delete(e),a.unobserve(e)),0===r.size&&(a.disconnect(),D.delete(n))}}class O extends $.Component{constructor(e){super(e),this.node=null,this._unobserveCb=null,this.handleNode=e=>{this.node&&(this.unobserve(),e||this.props.triggerOnce||this.props.skip||this.setState({inView:!!this.props.initialInView,entry:void 0})),this.node=e||null,this.observeNode()},this.handleChange=(e,t)=>{e&&this.props.triggerOnce&&this.unobserve(),isPlainChildren(this.props)||this.setState({inView:e,entry:t}),this.props.onChange&&this.props.onChange(e,t)},this.state={inView:!!e.initialInView,entry:void 0}}componentDidUpdate(e){e.rootMargin===this.props.rootMargin&&e.root===this.props.root&&e.threshold===this.props.threshold&&e.skip===this.props.skip&&e.trackVisibility===this.props.trackVisibility&&e.delay===this.props.delay||(this.unobserve(),this.observeNode())}componentWillUnmount(){this.unobserve(),this.node=null}observeNode(){if(!this.node||this.props.skip)return;const{threshold:e,root:t,rootMargin:i,trackVisibility:s,delay:n,fallbackInView:a}=this.props;this._unobserveCb=M(this.node,this.handleChange,{threshold:e,root:t,rootMargin:i,trackVisibility:s,delay:n},a)}unobserve(){this._unobserveCb&&(this._unobserveCb(),this._unobserveCb=null)}render(){if(!isPlainChildren(this.props)){const{inView:e,entry:t}=this.state;return this.props.children({inView:e,entry:t,ref:this.handleNode})}const e=this.props,{children:t,as:i}=e,s=function(e,t){if(null==e)return{};var i,s,n={},a=Object.keys(e);for(0;s=0||(n[i]=e[i]);return n}(e,null);return React.createElement(i||"div",_extends({ref:this.handleNode},s),t)}}function A({threshold:e,delay:t,trackVisibility:i,rootMargin:s,root:n,triggerOnce:a,skip:r,initialInView:o,fallbackInView:l,onChange:c}={}){var d;const[h,p]=$.useState(null),u=$.useRef(),[m,g]=$.useState({inView:!!o,entry:void 0});u.current=c,$.useEffect((()=>{if(r||!h)return;let o;return o=M(h,((e,t)=>{g({inView:e,entry:t}),u.current&&u.current(e,t),t.isIntersecting&&a&&o&&(o(),o=void 0)}),{root:n,rootMargin:s,threshold:e,trackVisibility:i,delay:t},l),()=>{o&&o()}}),[Array.isArray(e)?e.toString():e,h,n,s,a,r,i,l,t]);const x=null==(d=m.entry)?void 0:d.target,f=$.useRef();h||!x||a||r||f.current===x||(f.current=x,g({inView:!!o,entry:void 0}));const v=[p,m.inView,m.entry];return v.ref=v[0],v.inView=v[1],v.entry=v[2],v}const J=()=>{const{ref:e,inView:t}=A({triggerOnce:!0});return(0,m.jsx)("div",{ref:e,className:"container flex",children:_.map(((e,i)=>t?(0,m.jsxs)("div",{className:"col col--4 padding-vert--md offering-anim",style:{transform:"translateX(100vw)",animationDelay:`${Number(.15*i)}s`},children:[(0,m.jsx)("span",{children:`0${i+1}`}),(0,m.jsx)("h2",{className:"underline",children:e.heading}),(0,m.jsx)("p",{children:e.description})]},i):null))})},T=()=>{const{ref:e,inView:t}=A({triggerOnce:!0});return(0,m.jsx)("div",{ref:e,className:k,children:S.H.map(((e,i)=>{const s=e.styles.dataDelay;return t?(0,m.jsxs)("div",{className:"device-anim",style:{...e.styles,position:"absolute",animationDelay:`${s}s`},children:[(0,m.jsx)("img",{src:e.img,alt:e.alt}),(0,m.jsx)("canvas",{style:{position:"absolute",background:`#ecedf1 url(${S.U}) center center`,border:"1px solid #b1b8c4",...e.canvas}})]},i):null}))})};function G(){return(0,m.jsxs)("div",{className:`padding-vert--lg ${N} features`,style:{clipPath:"polygon(0 0, 100% 0, 100% 100%, 0 100%)"},children:[(0,m.jsx)(J,{}),(0,m.jsx)(T,{})]})}const L="wrapper_BtuJ",R="title_xi1R",E="features_wn1h",F="feature_N9w0",W=JSON.parse('[{"icon":"/images/features/feature-multiplatform.png","heading":"Multi-platform Support","description":"Interactive, visually compelling content on desktop, mobile and beyond, all reached with a single codebase to deliver transferable experiences.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-type.png","heading":"Advanced Text Rendering","description":"Beautiful anti-aliased text at native and retina resolutions means that Pixi copy is as easy on the eye as it is on any other delivery method.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-tinting-blending.png","heading":"Tinting & Blending Modes","description":"Designers and clients will be thrilled by Photoshop quality blending and colour modes.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-scenegraph.png","heading":"Full Scene Graph","description":"Organise your objects in hierarchical trees, with parent-child relationships.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-sprite-sheet.png","heading":"Sprite Sheet Support","description":"PixiJS caters for a range of sprite sheet formats and includes advanced support for features like trimming and rotational packing.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-canvas.png","heading":"Renderer Auto-detect","description":"Certain, older platforms may not be able to use WebGL. Not a problem with PixiJS as Canvas fallback is seamless and automated.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-asset-loader.png","heading":"Asset Loader","description":"Sprite-sheets, graphics, fonts, animation data (soon to have Adobe Animate support). All your incoming assets can be loaded and handled by PixiJS.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-apps.png","heading":"Deploy into Apps","description":"Use technologies such as Cordova to rapidly deploy your Pixi project as an App. Superb for both concepting and full delivery.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-api.png","heading":"Easy API","description":"Designed to be intuitive and easy to pick up. Developers old and new will find themselves right at home with its simple yet powerful API.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-accessibility.png","heading":"Accessibility","description":"PixiJS is an inclusive technology and all content can be made to be screen reader accessible with ease. The only WebGL renderer out there that does.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-filters.png","heading":"WebGL Filters","description":"Use and create your own spectacular WebGL filters and shaders to give your projects next-level visual fidelity and performance.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-multitouch.png","heading":"Multi-touch Interactivity","description":"True multi-touch input and tracking means that you can create interactions such as pinch-to-scale that give audiences native feeling experiences.","data-anim":"feature-l-in"}]');function Y(){const[e,t]=A({triggerOnce:!0}),[i,s]=A({triggerOnce:!0});return(0,m.jsxs)("div",{className:`${L} padding--md padding-vert--xl`,children:[(0,m.jsx)("div",{ref:i,className:"col col--12 margin-bottom--lg",children:s&&(0,m.jsx)("h2",{className:`${R} underline short-up-anim`,style:{opacity:0,animationDuration:"0.3s",animationDelay:"0.5s"},children:"PixiJS Features"})}),(0,m.jsx)("div",{ref:e,className:`row ${E}`,children:W.map(((e,i)=>{if(!t)return null;const s=i%2,n=Math.floor(i/2),a=W.length/2;let r=.5;return 0===s?r+=.15*n:r=.15*(n+a),(0,m.jsxs)("div",{className:`${F} col col--6 ${e["data-anim"]}-anim`,style:{opacity:0,transform:"translateX(300px)",animationDelay:`${r}s`},children:[(0,m.jsx)("img",{src:e.icon}),(0,m.jsxs)("div",{children:[(0,m.jsx)("h6",{children:e.heading}),(0,m.jsx)("p",{children:e.description})]})]},i)}))})]})}const B="closing_TUKb",U=(e,t)=>({opacity:0,animationDuration:`${e}s`,animationDelay:`${t}s`});function z(){const[e,t]=A({triggerOnce:!0});return(0,m.jsx)("div",{className:`${B}`,children:(0,m.jsx)("div",{ref:e,children:t&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("h3",{className:"short-up-anim",style:U(.3,.25),children:"Elevate your Traditional HTML5 Techniques"}),(0,m.jsx)("h5",{className:"short-up-anim",style:U(.3,.4),children:"Unbeatable performance, intuitive API, globally used and battle tested."}),(0,m.jsxs)("div",{className:"buttonRow",children:[(0,m.jsx)(g,{anim:"short-up-anim",style:U(.3,.55),label:"Download",link:"https://github.com/pixijs/pixijs/releases"}),"\xa0",(0,m.jsx)(g,{anim:"short-up-anim",style:U(.3,.7),label:"Get Started",link:"/tutorials",outline:!0})]})]})})})}const H={wrapper:"wrapper_IE1J",title:"title_jCIY",sponsorGrid:"sponsorGrid_I9ky",sponsor:"sponsor_QlZw",sponsorImage:"sponsorImage_cBjE"},Z=(e,t)=>({opacity:0,animationDuration:`${e}s`,animationDelay:`${t}s`});function Q(){const[e,t]=(0,$.useState)([]);(0,$.useEffect)((()=>{!async function(){const e=await fetch("https://opencollective.com/pixijs/members/all.json"),i=(await e.json()).filter((e=>"sponsor"===e.tier)).sort(((e,t)=>t.totalAmountDonated-e.totalAmountDonated)).map((e=>({name:e.name,image:e.image,website:e.website})));i.unshift({name:"Playco",image:"/images/logo-playco.png",website:"https://www.play.co/"}),t(i)}()}),[]);const[i,s]=A({triggerOnce:!0});return(0,m.jsx)("div",{className:`${H.wrapper}`,children:(0,m.jsx)("div",{ref:i,children:s&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("h3",{className:`${H.title} underline short-up-anim`,style:Z(.3,.25),children:"Sponsors"}),(0,m.jsx)("h5",{className:`${H.subtitle} short-up-anim`,style:Z(.3,.4),children:"These contributors support PixiJS financially, which allows us to spend more time working on PixiJS."}),(0,m.jsx)("div",{className:`${H.sponsorGrid} short-up-anim`,style:Z(.3,.55),children:e.map((e=>(0,m.jsx)("div",{className:H.sponsor,children:(0,m.jsx)("a",{href:e.website,children:(0,m.jsx)("img",{src:e.image,alt:`${e.name} logo`,className:H.sponsorImage})})},e.name)))})]})})})}function X(){const{siteConfig:e}=(0,s.Z)();return(0,m.jsx)(n.Z,{title:`${e.title} | The HTML5 Creation Engine`,description:"PixiJS - The HTML5 Creation Engine. Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.",children:(0,m.jsx)("main",{children:(0,m.jsxs)("div",{className:"text--center",children:[(0,m.jsx)(x,{}),(0,m.jsx)(j,{}),(0,m.jsx)(G,{}),(0,m.jsx)(Y,{}),(0,m.jsx)(z,{}),(0,m.jsx)(Q,{})]})})})}function q(){return(0,m.jsx)(X,{})}}}]); \ No newline at end of file diff --git a/assets/js/1df93b7f.5da23e41.js b/assets/js/1df93b7f.5da23e41.js new file mode 100644 index 000000000..1fa0637d7 --- /dev/null +++ b/assets/js/1df93b7f.5da23e41.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3237],{2566:(e,t,i)=>{i.r(t),i.d(t,{default:()=>q});var s=i(2263),n=i(9860),a=i(6010);const r="heroBackground_QArt",o="heroBanner_pQnV",l="heroLogo_EYQj";var c=i(9960);const d="button_sPMG",h="buttonShadow_o7NZ",p="outline_NKzY",u="white_flyO";var m=i(5893);function g(e){let t=d;return e.white&&(t+=` ${u}`),e.outline&&(t+=` ${p}`),e.anim&&(t+=` ${e.anim}`),(0,m.jsxs)(c.Z,{className:t,to:e.link,style:(null==e?void 0:e.style)||{},children:[(0,m.jsx)("div",{className:h,children:(0,m.jsx)("div",{})}),(0,m.jsx)("span",{children:e.label}),(0,m.jsxs)("svg",{className:"next",version:"1.1",xmlns:"http://www.w3.org/2000/svg",xmlnsXlink:"http://www.w3.org/1999/xlink",x:"0px",y:"0px",viewBox:"0 0 23 14",xmlSpace:"preserve",children:[(0,m.jsx)("line",{x1:"22",y1:"7",x2:"16",y2:"1"}),(0,m.jsx)("line",{x1:"22",y1:"7",x2:"16",y2:"13"}),(0,m.jsx)("line",{x1:"0",y1:"7",x2:"23",y2:"7"})]})]})}function x(){return(0,m.jsxs)("header",{className:(0,a.Z)("hero hero--dark",o),children:[(0,m.jsx)("iframe",{className:r,src:"/header/index.html"}),(0,m.jsxs)("div",{className:"container",children:[(0,m.jsx)("img",{className:l,src:"/images/logo.svg",alt:""}),(0,m.jsx)("h1",{className:"hero__subtitle",children:"The HTML5 Creation Engine"}),(0,m.jsx)("h4",{className:"hero__subsubtitle",children:"Create beautiful digital content with the fastest, most flexible 2D WebGL renderer."}),(0,m.jsxs)("div",{className:"buttonRow",children:[(0,m.jsx)(g,{label:"Download",link:"https://github.com/pixijs/pixijs/releases"}),"\xa0",(0,m.jsx)(g,{label:"Get Started",link:"8.x/tutorials",white:!0,outline:!0})]})]})]})}const f="testimonialsSection_PtYd",v="carouselText_jMN2",b="carouselWrapper_fl0O",y="carousel_ikfF",w="carouselItem_GpMR";function j(){const e=["adobe","20th_century_fox","barclays","bbc","bose","cartoon_network","disney","google","hbo","hm","lego","lucasfilm","marvel","mcdonalds","orange","pbs","rayban","redbull","spotify","steam","tedx","toyota","ubisoft","volkswagen","youtube"];return(0,m.jsxs)("div",{className:`${f} padding-vert--lg`,children:[(0,m.jsx)("p",{className:v,children:"A mature solution for hundreds of global brands"}),(0,m.jsx)("div",{style:{"--carousel-amount":e.length},className:b,children:(0,m.jsx)("div",{className:`col col--12 ${y}`,children:[...e,...e].map(((e,t)=>(0,m.jsx)("div",{className:w,children:(0,m.jsx)("img",{src:`/images/brand-logos/${e}.png`})},t)))})})]})}const N="highlights_dgss",k="devices_MMUi",_=JSON.parse('[{"heading":"Fast","description":"PixiJS\' strength is speed. When it comes to 2D rendering, PixiJS is the fastest there is."},{"heading":"Flexible","description":"Friendly, feature-rich API lets PixiJS take care of the fundamentals whilst you focus on producing incredible multiplatform experiences."},{"heading":"Free","description":"PixiJS is and always will be Open Source, with a large and supportive community pushing its growth and evolution."}]'),S=JSON.parse('{"U":"/images/devices/device-background.png","H":[{"img":"/images/devices/device-desktop.png","alt":"Desktop","styles":{"left":"50%","width":"386px","top":0,"zIndex":1,"marginLeft":"-193px","dataDelay":0,"transform":"translateY(200vh)"},"canvas":{"left":"19px","top":"28px","width":"348px","height":"198px"}},{"img":"/images/devices/device-ipad.png","alt":"iPad","styles":{"left":"60.79%","width":"155px","top":"138px","zIndex":2,"dataDelay":0.15,"transform":"translateY(200vh)"},"canvas":{"left":"17px","top":"16px","width":"124px","height":"164px"}},{"img":"/images/devices/device-iphone.png","alt":"iPhone","styles":{"left":"75.35%","width":"92px","top":"177px","zIndex":3,"dataDelay":0.45,"transform":"translateY(200vh)"},"canvas":{"left":"10px","top":"18px","width":"72px","height":"126px"}},{"img":"/images/devices/device-phone.png","alt":"Phone","styles":{"width":"170px","right":0,"bottom":0,"zIndex":4,"dataDelay":0.75,"transform":"translateY(200vh)"},"canvas":{"left":"20px","top":"9px","width":"132px","height":"80px"}},{"img":"/images/devices/device-laptop.png","alt":"Laptop","styles":{"left":"6.16%","width":"355px","top":"127px","zIndex":2,"dataDelay":0.3,"transform":"translateY(200vh)"},"canvas":{"left":"46px","top":"16px","width":"264px","height":"166px"}},{"img":"/images/devices/device-tablet.png","alt":"Tablet","styles":{"left":0,"width":"195px","bottom":0,"zIndex":4,"dataDelay":0.6,"transform":"translateY(200vh)"},"canvas":{"left":"17px","top":"15px","width":"160px","height":"95px"}}]}');var $=i(7294);const D=new Map,I=new WeakMap;let P,C=0;function V(e){return Object.keys(e).sort().filter((t=>void 0!==e[t])).map((t=>{return`${t}_${"root"===t?(i=e.root,i?(I.has(i)||(C+=1,I.set(i,C.toString())),I.get(i)):"0"):e[t]}`;var i})).toString()}function M(e,t,i={},s=P){if(void 0===window.IntersectionObserver&&void 0!==s){const n=e.getBoundingClientRect();return t(s,{isIntersecting:s,target:e,intersectionRatio:"number"==typeof i.threshold?i.threshold:0,time:0,boundingClientRect:n,intersectionRect:n,rootBounds:n}),()=>{}}const{id:n,observer:a,elements:r}=function(e){let t=V(e),i=D.get(t);if(!i){const s=new Map;let n;const a=new IntersectionObserver((t=>{t.forEach((t=>{var i;const a=t.isIntersecting&&n.some((e=>t.intersectionRatio>=e));e.trackVisibility&&void 0===t.isVisible&&(t.isVisible=a),null==(i=s.get(t.target))||i.forEach((e=>{e(a,t)}))}))}),e);n=a.thresholds||(Array.isArray(e.threshold)?e.threshold:[e.threshold||0]),i={id:t,observer:a,elements:s},D.set(t,i)}return i}(i);let o=r.get(e)||[];return r.has(e)||r.set(e,o),o.push(t),a.observe(e),function(){o.splice(o.indexOf(t),1),0===o.length&&(r.delete(e),a.unobserve(e)),0===r.size&&(a.disconnect(),D.delete(n))}}class O extends $.Component{constructor(e){super(e),this.node=null,this._unobserveCb=null,this.handleNode=e=>{this.node&&(this.unobserve(),e||this.props.triggerOnce||this.props.skip||this.setState({inView:!!this.props.initialInView,entry:void 0})),this.node=e||null,this.observeNode()},this.handleChange=(e,t)=>{e&&this.props.triggerOnce&&this.unobserve(),isPlainChildren(this.props)||this.setState({inView:e,entry:t}),this.props.onChange&&this.props.onChange(e,t)},this.state={inView:!!e.initialInView,entry:void 0}}componentDidUpdate(e){e.rootMargin===this.props.rootMargin&&e.root===this.props.root&&e.threshold===this.props.threshold&&e.skip===this.props.skip&&e.trackVisibility===this.props.trackVisibility&&e.delay===this.props.delay||(this.unobserve(),this.observeNode())}componentWillUnmount(){this.unobserve(),this.node=null}observeNode(){if(!this.node||this.props.skip)return;const{threshold:e,root:t,rootMargin:i,trackVisibility:s,delay:n,fallbackInView:a}=this.props;this._unobserveCb=M(this.node,this.handleChange,{threshold:e,root:t,rootMargin:i,trackVisibility:s,delay:n},a)}unobserve(){this._unobserveCb&&(this._unobserveCb(),this._unobserveCb=null)}render(){if(!isPlainChildren(this.props)){const{inView:e,entry:t}=this.state;return this.props.children({inView:e,entry:t,ref:this.handleNode})}const e=this.props,{children:t,as:i}=e,s=function(e,t){if(null==e)return{};var i,s,n={},a=Object.keys(e);for(0;s=0||(n[i]=e[i]);return n}(e,null);return React.createElement(i||"div",_extends({ref:this.handleNode},s),t)}}function A({threshold:e,delay:t,trackVisibility:i,rootMargin:s,root:n,triggerOnce:a,skip:r,initialInView:o,fallbackInView:l,onChange:c}={}){var d;const[h,p]=$.useState(null),u=$.useRef(),[m,g]=$.useState({inView:!!o,entry:void 0});u.current=c,$.useEffect((()=>{if(r||!h)return;let o;return o=M(h,((e,t)=>{g({inView:e,entry:t}),u.current&&u.current(e,t),t.isIntersecting&&a&&o&&(o(),o=void 0)}),{root:n,rootMargin:s,threshold:e,trackVisibility:i,delay:t},l),()=>{o&&o()}}),[Array.isArray(e)?e.toString():e,h,n,s,a,r,i,l,t]);const x=null==(d=m.entry)?void 0:d.target,f=$.useRef();h||!x||a||r||f.current===x||(f.current=x,g({inView:!!o,entry:void 0}));const v=[p,m.inView,m.entry];return v.ref=v[0],v.inView=v[1],v.entry=v[2],v}const J=()=>{const{ref:e,inView:t}=A({triggerOnce:!0});return(0,m.jsx)("div",{ref:e,className:"container flex",children:_.map(((e,i)=>t?(0,m.jsxs)("div",{className:"col col--4 padding-vert--md offering-anim",style:{transform:"translateX(100vw)",animationDelay:`${Number(.15*i)}s`},children:[(0,m.jsx)("span",{children:`0${i+1}`}),(0,m.jsx)("h2",{className:"underline",children:e.heading}),(0,m.jsx)("p",{children:e.description})]},i):null))})},T=()=>{const{ref:e,inView:t}=A({triggerOnce:!0});return(0,m.jsx)("div",{ref:e,className:k,children:S.H.map(((e,i)=>{const s=e.styles.dataDelay;return t?(0,m.jsxs)("div",{className:"device-anim",style:{...e.styles,position:"absolute",animationDelay:`${s}s`},children:[(0,m.jsx)("img",{src:e.img,alt:e.alt}),(0,m.jsx)("canvas",{style:{position:"absolute",background:`#ecedf1 url(${S.U}) center center`,border:"1px solid #b1b8c4",...e.canvas}})]},i):null}))})};function G(){return(0,m.jsxs)("div",{className:`padding-vert--lg ${N} features`,style:{clipPath:"polygon(0 0, 100% 0, 100% 100%, 0 100%)"},children:[(0,m.jsx)(J,{}),(0,m.jsx)(T,{})]})}const L="wrapper_BtuJ",R="title_xi1R",E="features_wn1h",F="feature_N9w0",W=JSON.parse('[{"icon":"/images/features/feature-multiplatform.png","heading":"Multi-platform Support","description":"Interactive, visually compelling content on desktop, mobile and beyond, all reached with a single codebase to deliver transferable experiences.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-type.png","heading":"Advanced Text Rendering","description":"Beautiful anti-aliased text at native and retina resolutions means that Pixi copy is as easy on the eye as it is on any other delivery method.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-tinting-blending.png","heading":"Tinting & Blending Modes","description":"Designers and clients will be thrilled by Photoshop quality blending and colour modes.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-scenegraph.png","heading":"Full Scene Graph","description":"Organise your objects in hierarchical trees, with parent-child relationships.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-sprite-sheet.png","heading":"Sprite Sheet Support","description":"PixiJS caters for a range of sprite sheet formats and includes advanced support for features like trimming and rotational packing.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-canvas.png","heading":"Renderer Auto-detect","description":"Certain, older platforms may not be able to use WebGL. Not a problem with PixiJS as Canvas fallback is seamless and automated.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-asset-loader.png","heading":"Asset Loader","description":"Sprite-sheets, graphics, fonts, animation data (soon to have Adobe Animate support). All your incoming assets can be loaded and handled by PixiJS.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-apps.png","heading":"Deploy into Apps","description":"Use technologies such as Cordova to rapidly deploy your Pixi project as an App. Superb for both concepting and full delivery.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-api.png","heading":"Easy API","description":"Designed to be intuitive and easy to pick up. Developers old and new will find themselves right at home with its simple yet powerful API.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-accessibility.png","heading":"Accessibility","description":"PixiJS is an inclusive technology and all content can be made to be screen reader accessible with ease. The only WebGL renderer out there that does.","data-anim":"feature-l-in"},{"icon":"/images/features/feature-filters.png","heading":"WebGL Filters","description":"Use and create your own spectacular WebGL filters and shaders to give your projects next-level visual fidelity and performance.","data-anim":"feature-r-in"},{"icon":"/images/features/feature-multitouch.png","heading":"Multi-touch Interactivity","description":"True multi-touch input and tracking means that you can create interactions such as pinch-to-scale that give audiences native feeling experiences.","data-anim":"feature-l-in"}]');function Y(){const[e,t]=A({triggerOnce:!0}),[i,s]=A({triggerOnce:!0});return(0,m.jsxs)("div",{className:`${L} padding--md padding-vert--xl`,children:[(0,m.jsx)("div",{ref:i,className:"col col--12 margin-bottom--lg",children:s&&(0,m.jsx)("h2",{className:`${R} underline short-up-anim`,style:{opacity:0,animationDuration:"0.3s",animationDelay:"0.5s"},children:"PixiJS Features"})}),(0,m.jsx)("div",{ref:e,className:`row ${E}`,children:W.map(((e,i)=>{if(!t)return null;const s=i%2,n=Math.floor(i/2),a=W.length/2;let r=.5;return 0===s?r+=.15*n:r=.15*(n+a),(0,m.jsxs)("div",{className:`${F} col col--6 ${e["data-anim"]}-anim`,style:{opacity:0,transform:"translateX(300px)",animationDelay:`${r}s`},children:[(0,m.jsx)("img",{src:e.icon}),(0,m.jsxs)("div",{children:[(0,m.jsx)("h6",{children:e.heading}),(0,m.jsx)("p",{children:e.description})]})]},i)}))})]})}const B="closing_TUKb",U=(e,t)=>({opacity:0,animationDuration:`${e}s`,animationDelay:`${t}s`});function z(){const[e,t]=A({triggerOnce:!0});return(0,m.jsx)("div",{className:`${B}`,children:(0,m.jsx)("div",{ref:e,children:t&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("h3",{className:"short-up-anim",style:U(.3,.25),children:"Elevate your Traditional HTML5 Techniques"}),(0,m.jsx)("h5",{className:"short-up-anim",style:U(.3,.4),children:"Unbeatable performance, intuitive API, globally used and battle tested."}),(0,m.jsxs)("div",{className:"buttonRow",children:[(0,m.jsx)(g,{anim:"short-up-anim",style:U(.3,.55),label:"Download",link:"https://github.com/pixijs/pixijs/releases"}),"\xa0",(0,m.jsx)(g,{anim:"short-up-anim",style:U(.3,.7),label:"Get Started",link:"8.x/tutorials",outline:!0})]})]})})})}const H={wrapper:"wrapper_IE1J",title:"title_jCIY",sponsorGrid:"sponsorGrid_I9ky",sponsor:"sponsor_QlZw",sponsorImage:"sponsorImage_cBjE"},Z=(e,t)=>({opacity:0,animationDuration:`${e}s`,animationDelay:`${t}s`});function Q(){const[e,t]=(0,$.useState)([]);(0,$.useEffect)((()=>{!async function(){const e=await fetch("https://opencollective.com/pixijs/members/all.json"),i=(await e.json()).filter((e=>"sponsor"===e.tier)).sort(((e,t)=>t.totalAmountDonated-e.totalAmountDonated)).map((e=>({name:e.name,image:e.image,website:e.website})));i.unshift({name:"Playco",image:"/images/logo-playco.png",website:"https://www.play.co/"}),t(i)}()}),[]);const[i,s]=A({triggerOnce:!0});return(0,m.jsx)("div",{className:`${H.wrapper}`,children:(0,m.jsx)("div",{ref:i,children:s&&(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("h3",{className:`${H.title} underline short-up-anim`,style:Z(.3,.25),children:"Sponsors"}),(0,m.jsx)("h5",{className:`${H.subtitle} short-up-anim`,style:Z(.3,.4),children:"These contributors support PixiJS financially, which allows us to spend more time working on PixiJS."}),(0,m.jsx)("div",{className:`${H.sponsorGrid} short-up-anim`,style:Z(.3,.55),children:e.map((e=>(0,m.jsx)("div",{className:H.sponsor,children:(0,m.jsx)("a",{href:e.website,children:(0,m.jsx)("img",{src:e.image,alt:`${e.name} logo`,className:H.sponsorImage})})},e.name)))})]})})})}function X(){const{siteConfig:e}=(0,s.Z)();return(0,m.jsx)(n.Z,{title:`${e.title} | The HTML5 Creation Engine`,description:"PixiJS - The HTML5 Creation Engine. Create beautiful digital content with the fastest, most flexible 2D WebGL renderer.",children:(0,m.jsx)("main",{children:(0,m.jsxs)("div",{className:"text--center",children:[(0,m.jsx)(x,{}),(0,m.jsx)(j,{}),(0,m.jsx)(G,{}),(0,m.jsx)(Y,{}),(0,m.jsx)(z,{}),(0,m.jsx)(Q,{})]})})})}function q(){return(0,m.jsx)(X,{})}}}]); \ No newline at end of file diff --git a/assets/js/214aaae9.35fc3f82.js b/assets/js/214aaae9.35fc3f82.js deleted file mode 100644 index 56ff20ebc..000000000 --- a/assets/js/214aaae9.35fc3f82.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4074],{7820:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),n=i(8010),o=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Container"},p=void 0,l={unversionedId:"examples/basic/container",id:"examples/basic/container",title:"Container",description:"",source:"@site/docs/examples/basic/container.md",sourceDirName:"examples/basic",slug:"/examples/basic/container",permalink:"/examples/basic/container",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Container"},sidebar:"examplesSidebar",previous:{title:"Examples",permalink:"/examples/"},next:{title:"Transparent Background",permalink:"/examples/basic/transparent-background"}},c={},d=[],u={toc:d};function m(e){let{components:t,...i}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"basic.container",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/214aaae9.fe12457f.js b/assets/js/214aaae9.fe12457f.js new file mode 100644 index 000000000..197180025 --- /dev/null +++ b/assets/js/214aaae9.fe12457f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4074],{7820:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>p,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),n=i(8010),o=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Container"},p=void 0,l={unversionedId:"examples/basic/container",id:"examples/basic/container",title:"Container",description:"",source:"@site/docs/examples/basic/container.md",sourceDirName:"examples/basic",slug:"/examples/basic/container",permalink:"/8.x/examples/basic/container",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Container"},sidebar:"examplesSidebar",previous:{title:"Examples",permalink:"/8.x/examples/"},next:{title:"Transparent Background",permalink:"/8.x/examples/basic/transparent-background"}},c={},d=[],u={toc:d};function m(e){let{components:t,...i}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"basic.container",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/21ab5248.66c925a3.js b/assets/js/21ab5248.66c925a3.js deleted file mode 100644 index a9829cd4e..000000000 --- a/assets/js/21ab5248.66c925a3.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1788],{8380:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>r,contentTitle:()=>d,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>p});var s=t(7462),o=(t(7294),t(3905)),n=t(8010),a=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Collision Detection"},d=void 0,c={unversionedId:"examples/advanced/collision-detection",id:"examples/advanced/collision-detection",title:"Collision Detection",description:"",source:"@site/docs/examples/advanced/collision-detection.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/collision-detection",permalink:"/examples/advanced/collision-detection",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Collision Detection"},sidebar:"examplesSidebar",previous:{title:"Screen Shot",permalink:"/examples/advanced/screen-shot"},next:{title:"Spinners",permalink:"/examples/advanced/spinners"}},r={},p=[],u={toc:p};function m(e){let{components:i,...t}=e;return(0,o.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"advanced.collisionDetection",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/21ab5248.eece5abf.js b/assets/js/21ab5248.eece5abf.js new file mode 100644 index 000000000..8c22b8950 --- /dev/null +++ b/assets/js/21ab5248.eece5abf.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1788],{8380:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>r,contentTitle:()=>d,default:()=>m,frontMatter:()=>l,metadata:()=>c,toc:()=>p});var s=t(7462),o=(t(7294),t(3905)),n=t(8010),a=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Collision Detection"},d=void 0,c={unversionedId:"examples/advanced/collision-detection",id:"examples/advanced/collision-detection",title:"Collision Detection",description:"",source:"@site/docs/examples/advanced/collision-detection.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/collision-detection",permalink:"/8.x/examples/advanced/collision-detection",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Collision Detection"},sidebar:"examplesSidebar",previous:{title:"Screen Shot",permalink:"/8.x/examples/advanced/screen-shot"},next:{title:"Spinners",permalink:"/8.x/examples/advanced/spinners"}},r={},p=[],u={toc:p};function m(e){let{components:i,...t}=e;return(0,o.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"advanced.collisionDetection",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/22c05754.1d18ab59.js b/assets/js/22c05754.1d18ab59.js deleted file mode 100644 index 3d895194b..000000000 --- a/assets/js/22c05754.1d18ab59.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3661],{9121:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>n,metadata:()=>s,toc:()=>c});var i=a(7462),r=(a(7294),a(3905));const n={},o="Architecture Overview",s={unversionedId:"guides/basics/architecture-overview",id:"guides/basics/architecture-overview",title:"Architecture Overview",description:"OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.",source:"@site/docs/guides/basics/architecture-overview.md",sourceDirName:"guides/basics",slug:"/guides/basics/architecture-overview",permalink:"/guides/basics/architecture-overview",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/architecture-overview.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Getting Started",permalink:"/guides/basics/getting-started"},next:{title:"Render Loop",permalink:"/guides/basics/render-loop"}},l={},c=[{value:"The Code",id:"the-code",level:2},{value:"The Components",id:"the-components",level:2},{value:"Major Components",id:"major-components",level:3}],p={toc:c};function d(e){let{components:t,...a}=e;return(0,r.kt)("wrapper",(0,i.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"architecture-overview"},"Architecture Overview"),(0,r.kt)("p",null,"OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together."),(0,r.kt)("h2",{id:"the-code"},"The Code"),(0,r.kt)("p",null,"Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub"),". Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called ",(0,r.kt)("a",{parentName:"p",href:"https://www.typescriptlang.org"},"TypeScript"),", which enables type-checking in JavaScript via a pre-compile step."),(0,r.kt)("h2",{id:"the-components"},"The Components"),(0,r.kt)("p",null,"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."),(0,r.kt)("h3",{id:"major-components"},"Major Components"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Component"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Renderer")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Container")),(0,r.kt)("td",{parentName:"tr",align:null},"Main scene object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See ",(0,r.kt)("a",{parentName:"td",href:"scene-graph"},"Scene Graph")," for more details.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Assets")),(0,r.kt)("td",{parentName:"tr",align:null},"The Asset system provides tools for asynchronously loading resources such as images and audio files.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Ticker")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Application")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Events")),(0,r.kt)("td",{parentName:"tr",align:null},"PixiJS supports pointer-based interaction - making objects clickable, firing hover events, etc.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Accessibility")),(0,r.kt)("td",{parentName:"tr",align:null},"Woven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility.")))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/22c05754.974161a7.js b/assets/js/22c05754.974161a7.js new file mode 100644 index 000000000..079aedfaf --- /dev/null +++ b/assets/js/22c05754.974161a7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3661],{9121:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>d,frontMatter:()=>n,metadata:()=>s,toc:()=>c});var i=a(7462),r=(a(7294),a(3905));const n={},o="Architecture Overview",s={unversionedId:"guides/basics/architecture-overview",id:"guides/basics/architecture-overview",title:"Architecture Overview",description:"OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together.",source:"@site/docs/guides/basics/architecture-overview.md",sourceDirName:"guides/basics",slug:"/guides/basics/architecture-overview",permalink:"/8.x/guides/basics/architecture-overview",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/architecture-overview.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Getting Started",permalink:"/8.x/guides/basics/getting-started"},next:{title:"Render Loop",permalink:"/8.x/guides/basics/render-loop"}},l={},c=[{value:"The Code",id:"the-code",level:2},{value:"The Components",id:"the-components",level:2},{value:"Major Components",id:"major-components",level:3}],p={toc:c};function d(e){let{components:t,...a}=e;return(0,r.kt)("wrapper",(0,i.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"architecture-overview"},"Architecture Overview"),(0,r.kt)("p",null,"OK, now that you've gotten a feel for how easy it is to build a PixiJS application, let's get into the specifics. For the rest of the Basics section, we're going to work from the high level down to the details. We'll start with an overview of how PixiJS is put together."),(0,r.kt)("h2",{id:"the-code"},"The Code"),(0,r.kt)("p",null,"Before we get into how the code is layed out, let's talk about where it lives. PixiJS is an open source product hosted on ",(0,r.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"GitHub"),". Like any GitHub repo, you can browse and download the raw source files for each PixiJS class, as well as search existing issues & bugs, and even submit your own. PixiJS is written in a JavaScript variant called ",(0,r.kt)("a",{parentName:"p",href:"https://www.typescriptlang.org"},"TypeScript"),", which enables type-checking in JavaScript via a pre-compile step."),(0,r.kt)("h2",{id:"the-components"},"The Components"),(0,r.kt)("p",null,"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."),(0,r.kt)("h3",{id:"major-components"},"Major Components"),(0,r.kt)("table",null,(0,r.kt)("thead",{parentName:"table"},(0,r.kt)("tr",{parentName:"thead"},(0,r.kt)("th",{parentName:"tr",align:null},"Component"),(0,r.kt)("th",{parentName:"tr",align:null},"Description"))),(0,r.kt)("tbody",{parentName:"table"},(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Renderer")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Container")),(0,r.kt)("td",{parentName:"tr",align:null},"Main scene object which creates a scene graph: the tree of renderable objects to be displayed, such as sprites, graphics and text. See ",(0,r.kt)("a",{parentName:"td",href:"scene-graph"},"Scene Graph")," for more details.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Assets")),(0,r.kt)("td",{parentName:"tr",align:null},"The Asset system provides tools for asynchronously loading resources such as images and audio files.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Ticker")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Application")),(0,r.kt)("td",{parentName:"tr",align:null},"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.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Events")),(0,r.kt)("td",{parentName:"tr",align:null},"PixiJS supports pointer-based interaction - making objects clickable, firing hover events, etc.")),(0,r.kt)("tr",{parentName:"tbody"},(0,r.kt)("td",{parentName:"tr",align:null},(0,r.kt)("strong",{parentName:"td"},"Accessibility")),(0,r.kt)("td",{parentName:"tr",align:null},"Woven through our display system is a rich set of tools for enabling keyboard and screen-reader accessibility.")))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/235d3212.260188a1.js b/assets/js/235d3212.260188a1.js deleted file mode 100644 index 099d3f973..000000000 --- a/assets/js/235d3212.260188a1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1213],{5081:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>c,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>p,toc:()=>d});var s=t(7462),a=(t(7294),t(3905)),n=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Particle Container"},l=void 0,p={unversionedId:"examples/basic/particle-container",id:"examples/basic/particle-container",title:"Particle Container",description:"",source:"@site/docs/examples/basic/particle-container.md",sourceDirName:"examples/basic",slug:"/examples/basic/particle-container",permalink:"/examples/basic/particle-container",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Particle Container"},sidebar:"examplesSidebar",previous:{title:"Tinting",permalink:"/examples/basic/tinting"},next:{title:"Blend Modes",permalink:"/examples/basic/blend-modes"}},c={},d=[],u={toc:d};function m(e){let{components:i,...t}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"basic.particleContainer",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/235d3212.5c61e201.js b/assets/js/235d3212.5c61e201.js new file mode 100644 index 000000000..804734bfd --- /dev/null +++ b/assets/js/235d3212.5c61e201.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1213],{5081:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>c,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>p,toc:()=>d});var s=t(7462),a=(t(7294),t(3905)),n=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Particle Container"},l=void 0,p={unversionedId:"examples/basic/particle-container",id:"examples/basic/particle-container",title:"Particle Container",description:"",source:"@site/docs/examples/basic/particle-container.md",sourceDirName:"examples/basic",slug:"/examples/basic/particle-container",permalink:"/8.x/examples/basic/particle-container",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Particle Container"},sidebar:"examplesSidebar",previous:{title:"Tinting",permalink:"/8.x/examples/basic/tinting"},next:{title:"Blend Modes",permalink:"/8.x/examples/basic/blend-modes"}},c={},d=[],u={toc:d};function m(e){let{components:i,...t}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"basic.particleContainer",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/24c49760.97167ebd.js b/assets/js/24c49760.97167ebd.js new file mode 100644 index 000000000..3f5954f1f --- /dev/null +++ b/assets/js/24c49760.97167ebd.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5676],{710:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>c,contentTitle:()=>n,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>d});var t=i(7462),a=(i(7294),i(3905)),l=i(8010),p=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Displacement Map Crawlies"},n=void 0,o={unversionedId:"examples/filters-basic/displacement-map-crawlies",id:"examples/filters-basic/displacement-map-crawlies",title:"Displacement Map Crawlies",description:"",source:"@site/docs/examples/filters-basic/displacement-map-crawlies.md",sourceDirName:"examples/filters-basic",slug:"/examples/filters-basic/displacement-map-crawlies",permalink:"/8.x/examples/filters-basic/displacement-map-crawlies",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Displacement Map Crawlies"},sidebar:"examplesSidebar",previous:{title:"Color Matrix",permalink:"/8.x/examples/filters-basic/color-matrix"},next:{title:"Displacement Map Flag",permalink:"/8.x/examples/filters-basic/displacement-map-flag"}},c={},d=[],m={toc:d};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"filtersBasic.displacementMapCrawlies",pixiVersion:p,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/24c49760.ec86bd54.js b/assets/js/24c49760.ec86bd54.js deleted file mode 100644 index a73a12f79..000000000 --- a/assets/js/24c49760.ec86bd54.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5676],{710:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>c,contentTitle:()=>n,default:()=>u,frontMatter:()=>r,metadata:()=>o,toc:()=>d});var t=i(7462),a=(i(7294),i(3905)),l=i(8010),p=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Displacement Map Crawlies"},n=void 0,o={unversionedId:"examples/filters-basic/displacement-map-crawlies",id:"examples/filters-basic/displacement-map-crawlies",title:"Displacement Map Crawlies",description:"",source:"@site/docs/examples/filters-basic/displacement-map-crawlies.md",sourceDirName:"examples/filters-basic",slug:"/examples/filters-basic/displacement-map-crawlies",permalink:"/examples/filters-basic/displacement-map-crawlies",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Displacement Map Crawlies"},sidebar:"examplesSidebar",previous:{title:"Color Matrix",permalink:"/examples/filters-basic/color-matrix"},next:{title:"Displacement Map Flag",permalink:"/examples/filters-basic/displacement-map-flag"}},c={},d=[],m={toc:d};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"filtersBasic.displacementMapCrawlies",pixiVersion:p,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/2917be12.56a7bfb8.js b/assets/js/2917be12.56a7bfb8.js new file mode 100644 index 000000000..39d45f88f --- /dev/null +++ b/assets/js/2917be12.56a7bfb8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7167],{8754:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>c,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var t=i(7462),a=(i(7294),i(3905)),n=i(8010),o=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Basic"},c=void 0,l={unversionedId:"examples/offscreen-canvas/basic",id:"examples/offscreen-canvas/basic",title:"Basic",description:"",source:"@site/docs/examples/offscreen-canvas/basic.md",sourceDirName:"examples/offscreen-canvas",slug:"/examples/offscreen-canvas/basic",permalink:"/8.x/examples/offscreen-canvas/basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Basic"},sidebar:"examplesSidebar",previous:{title:"Bundle",permalink:"/8.x/examples/assets/bundle"}},p={},d=[],u={toc:d};function m(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},u,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"offscreenCanvas.basic",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/2917be12.fbe74fd6.js b/assets/js/2917be12.fbe74fd6.js deleted file mode 100644 index 9f0ffb9d6..000000000 --- a/assets/js/2917be12.fbe74fd6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7167],{8754:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>c,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var t=i(7462),a=(i(7294),i(3905)),n=i(8010),o=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Basic"},c=void 0,l={unversionedId:"examples/offscreen-canvas/basic",id:"examples/offscreen-canvas/basic",title:"Basic",description:"",source:"@site/docs/examples/offscreen-canvas/basic.md",sourceDirName:"examples/offscreen-canvas",slug:"/examples/offscreen-canvas/basic",permalink:"/examples/offscreen-canvas/basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Basic"},sidebar:"examplesSidebar",previous:{title:"Bundle",permalink:"/examples/assets/bundle"}},p={},d=[],u={toc:d};function m(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},u,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"offscreenCanvas.basic",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/2c83f0c9.0e8cee4f.js b/assets/js/2c83f0c9.0e8cee4f.js deleted file mode 100644 index 447a0b84d..000000000 --- a/assets/js/2c83f0c9.0e8cee4f.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4066],{4278:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>p,frontMatter:()=>r,metadata:()=>s,toc:()=>h});var n=a(7462),o=(a(7294),a(3905));const r={},i="Render Loop",s={unversionedId:"guides/basics/render-loop",id:"guides/basics/render-loop",title:"Render Loop",description:"Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.",source:"@site/docs/guides/basics/render-loop.md",sourceDirName:"guides/basics",slug:"/guides/basics/render-loop",permalink:"/guides/basics/render-loop",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/render-loop.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Architecture Overview",permalink:"/guides/basics/architecture-overview"},next:{title:"Scene Graph",permalink:"/guides/basics/scene-graph"}},l={},h=[{value:"Running Ticker Callbacks",id:"running-ticker-callbacks",level:2},{value:"Updating the Scene Graph",id:"updating-the-scene-graph",level:2},{value:"Rendering the Scene Graph",id:"rendering-the-scene-graph",level:2},{value:"Frame Rates",id:"frame-rates",level:2},{value:"Custom Render Loops",id:"custom-render-loops",level:2}],d={toc:h};function p(e){let{components:t,...a}=e;return(0,o.kt)("wrapper",(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"render-loop"},"Render Loop"),(0,o.kt)("p",null,"Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop."),(0,o.kt)("p",null,"The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering."),(0,o.kt)("p",null,"Let's walk through what happens each frame of the render loop. There are three main steps."),(0,o.kt)("h2",{id:"running-ticker-callbacks"},"Running Ticker Callbacks"),(0,o.kt)("p",null,"The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering."),(0,o.kt)("h2",{id:"updating-the-scene-graph"},"Updating the Scene Graph"),(0,o.kt)("p",null,"We'll talk a ",(0,o.kt)("em",{parentName:"p"},"lot")," more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing."),(0,o.kt)("h2",{id:"rendering-the-scene-graph"},"Rendering the Scene Graph"),(0,o.kt)("p",null,"Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (",(0,o.kt)("inlineCode",{parentName:"p"},"app.stage"),"), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization."),(0,o.kt)("h2",{id:"frame-rates"},"Frame Rates"),(0,o.kt)("p",null,"A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue."),(0,o.kt)("p",null,"In cases where you want to adjust that behavior, you can set the ",(0,o.kt)("inlineCode",{parentName:"p"},"minFPS")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("em",{parentName:"p"},"guarantee")," a given FPS. Use the passed ",(0,o.kt)("inlineCode",{parentName:"p"},"ticker.deltaTime")," value in your ticker callbacks to scale any animations to ensure smooth playback."),(0,o.kt)("h2",{id:"custom-render-loops"},"Custom Render Loops"),(0,o.kt)("p",null,"What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. "," While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2c83f0c9.96799a81.js b/assets/js/2c83f0c9.96799a81.js new file mode 100644 index 000000000..8156a6cf9 --- /dev/null +++ b/assets/js/2c83f0c9.96799a81.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4066],{4278:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>p,frontMatter:()=>r,metadata:()=>s,toc:()=>h});var n=a(7462),o=(a(7294),a(3905));const r={},i="Render Loop",s={unversionedId:"guides/basics/render-loop",id:"guides/basics/render-loop",title:"Render Loop",description:"Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.",source:"@site/docs/guides/basics/render-loop.md",sourceDirName:"guides/basics",slug:"/guides/basics/render-loop",permalink:"/8.x/guides/basics/render-loop",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/render-loop.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Architecture Overview",permalink:"/8.x/guides/basics/architecture-overview"},next:{title:"Scene Graph",permalink:"/8.x/guides/basics/scene-graph"}},l={},h=[{value:"Running Ticker Callbacks",id:"running-ticker-callbacks",level:2},{value:"Updating the Scene Graph",id:"updating-the-scene-graph",level:2},{value:"Rendering the Scene Graph",id:"rendering-the-scene-graph",level:2},{value:"Frame Rates",id:"frame-rates",level:2},{value:"Custom Render Loops",id:"custom-render-loops",level:2}],d={toc:h};function p(e){let{components:t,...a}=e;return(0,o.kt)("wrapper",(0,n.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"render-loop"},"Render Loop"),(0,o.kt)("p",null,"Now that you understand the major parts of the system, let's look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop."),(0,o.kt)("p",null,"The majority of any PixiJS project is contained in this update + render cycle. You code the updates, PixiJS handles the rendering."),(0,o.kt)("p",null,"Let's walk through what happens each frame of the render loop. There are three main steps."),(0,o.kt)("h2",{id:"running-ticker-callbacks"},"Running Ticker Callbacks"),(0,o.kt)("p",null,"The first step is to calculate how much time has elapsed since the last frame, and then call the Application object's ticker callbacks with that time delta. This allows your project's code to animate and update the sprites, etc. on the stage in preparation for rendering."),(0,o.kt)("h2",{id:"updating-the-scene-graph"},"Updating the Scene Graph"),(0,o.kt)("p",null,"We'll talk a ",(0,o.kt)("em",{parentName:"p"},"lot")," more about what a scene graph is and what it's made of in the next guide, but for now, all you need to know is that it contains the things you're drawing - sprites, text, etc. - and that these objects are in a tree-like hierarchy. After you've updated your game objects by moving, rotating and so forth, PixiJS needs to calculate the new positions and state of every object in the scene, before it can start drawing."),(0,o.kt)("h2",{id:"rendering-the-scene-graph"},"Rendering the Scene Graph"),(0,o.kt)("p",null,"Now that our game's state has been updated, it's time to draw it to the screen. The rendering system starts with the root of the scene graph (",(0,o.kt)("inlineCode",{parentName:"p"},"app.stage"),"), and starts rendering each object and its children, until all objects have been drawn. No culling or other cleverness is built into this process. If you have lots of objects outside of the visible portion of the stage, you'll want to investigate disabling them as an optimization."),(0,o.kt)("h2",{id:"frame-rates"},"Frame Rates"),(0,o.kt)("p",null,"A note about frame rates. The render loop can't be run infinitely fast - drawing things to the screen takes time. In addition, it's not generally useful to have a frame updated more than once per screen update (commonly 60fps, but newer monitors can support 144fps and up). Finally, PixiJS runs in the context of a web browser like Chrome or Firefox. The browser itself has to balance the needs of various internal operations with servicing any open tabs. All this to say, determining when to draw a frame is a complex issue."),(0,o.kt)("p",null,"In cases where you want to adjust that behavior, you can set the ",(0,o.kt)("inlineCode",{parentName:"p"},"minFPS")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("em",{parentName:"p"},"guarantee")," a given FPS. Use the passed ",(0,o.kt)("inlineCode",{parentName:"p"},"ticker.deltaTime")," value in your ticker callbacks to scale any animations to ensure smooth playback."),(0,o.kt)("h2",{id:"custom-render-loops"},"Custom Render Loops"),(0,o.kt)("p",null,"What we've just covered is the default render loop provided out of the box by the Application helper class. There are many other ways of creating a render loop that may be helpful for advanced users looking to solve a given problem. "," While you're prototyping and learning PixiJS, sticking with the Application's provided system is the recommended approach."))}p.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/2f34f036.8190e35d.js b/assets/js/2f34f036.8190e35d.js deleted file mode 100644 index 586002d90..000000000 --- a/assets/js/2f34f036.8190e35d.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8986],{5109:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>l,default:()=>c,frontMatter:()=>n,metadata:()=>p,toc:()=>u});var i=s(7462),o=(s(7294),s(3905)),r=s(8010),a=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Logger"},l=void 0,p={unversionedId:"examples/events/logger",id:"examples/events/logger",title:"Logger",description:"",source:"@site/docs/examples/events/logger.md",sourceDirName:"examples/events",slug:"/examples/events/logger",permalink:"/examples/events/logger",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Logger"},sidebar:"examplesSidebar",previous:{title:"Custom Hitarea",permalink:"/examples/events/custom-hitarea"},next:{title:"Pointer Tracker",permalink:"/examples/events/pointer-tracker"}},d={},u=[],m={toc:u};function c(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(r.Z,{id:"events.logger",pixiVersion:a,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/2f34f036.e2d70e06.js b/assets/js/2f34f036.e2d70e06.js new file mode 100644 index 000000000..cd6f8a2da --- /dev/null +++ b/assets/js/2f34f036.e2d70e06.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8986],{5109:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>l,default:()=>c,frontMatter:()=>n,metadata:()=>p,toc:()=>u});var i=s(7462),o=(s(7294),s(3905)),r=s(8010),a=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Logger"},l=void 0,p={unversionedId:"examples/events/logger",id:"examples/events/logger",title:"Logger",description:"",source:"@site/docs/examples/events/logger.md",sourceDirName:"examples/events",slug:"/examples/events/logger",permalink:"/8.x/examples/events/logger",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Logger"},sidebar:"examplesSidebar",previous:{title:"Custom Hitarea",permalink:"/8.x/examples/events/custom-hitarea"},next:{title:"Pointer Tracker",permalink:"/8.x/examples/events/pointer-tracker"}},d={},u=[],m={toc:u};function c(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(r.Z,{id:"events.logger",pixiVersion:a,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/376e58ab.1f4ce4ce.js b/assets/js/376e58ab.1f4ce4ce.js new file mode 100644 index 000000000..cb70f929b --- /dev/null +++ b/assets/js/376e58ab.1f4ce4ce.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2785],{8230:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>n,default:()=>m,frontMatter:()=>p,metadata:()=>o,toc:()=>c});var s=t(7462),a=(t(7294),t(3905)),r=t(8010),l=t(7949);const p={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Fill Gradient"},n=void 0,o={unversionedId:"examples/graphics/fill-gradient",id:"examples/graphics/fill-gradient",title:"Fill Gradient",description:"",source:"@site/docs/examples/graphics/fill-gradient.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/fill-gradient",permalink:"/8.x/examples/graphics/fill-gradient",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Fill Gradient"},sidebar:"examplesSidebar",previous:{title:"Texture",permalink:"/8.x/examples/graphics/texture"},next:{title:"Mesh From Path",permalink:"/8.x/examples/graphics/mesh-from-path"}},d={},c=[],u={toc:c};function m(e){let{components:i,...t}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(r.Z,{id:"graphics.fillGradient",pixiVersion:l,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/376e58ab.761dbe00.js b/assets/js/376e58ab.761dbe00.js deleted file mode 100644 index 0ba1a6690..000000000 --- a/assets/js/376e58ab.761dbe00.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2785],{8230:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>n,default:()=>m,frontMatter:()=>p,metadata:()=>o,toc:()=>c});var s=t(7462),a=(t(7294),t(3905)),r=t(8010),l=t(7949);const p={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Fill Gradient"},n=void 0,o={unversionedId:"examples/graphics/fill-gradient",id:"examples/graphics/fill-gradient",title:"Fill Gradient",description:"",source:"@site/docs/examples/graphics/fill-gradient.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/fill-gradient",permalink:"/examples/graphics/fill-gradient",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Fill Gradient"},sidebar:"examplesSidebar",previous:{title:"Texture",permalink:"/examples/graphics/texture"},next:{title:"Mesh From Path",permalink:"/examples/graphics/mesh-from-path"}},d={},c=[],u={toc:c};function m(e){let{components:i,...t}=e;return(0,a.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,a.kt)(r.Z,{id:"graphics.fillGradient",pixiVersion:l,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/38eecf5f.7abaf5ee.js b/assets/js/38eecf5f.7abaf5ee.js new file mode 100644 index 000000000..a380cc8aa --- /dev/null +++ b/assets/js/38eecf5f.7abaf5ee.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5797],{6525:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>l,default:()=>x,frontMatter:()=>a,metadata:()=>o,toc:()=>m});var s=t(7462),p=(t(7294),t(3905)),r=t(8010),n=t(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Tiling Sprite"},l=void 0,o={unversionedId:"examples/sprite/tiling-sprite",id:"examples/sprite/tiling-sprite",title:"Tiling Sprite",description:"",source:"@site/docs/examples/sprite/tiling-sprite.md",sourceDirName:"examples/sprite",slug:"/examples/sprite/tiling-sprite",permalink:"/8.x/examples/sprite/tiling-sprite",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Tiling Sprite"},sidebar:"examplesSidebar",previous:{title:"Animated Sprite Animation Speed",permalink:"/8.x/examples/sprite/animated-sprite-animation-speed"},next:{title:"Video",permalink:"/8.x/examples/sprite/video"}},d={},m=[],u={toc:m};function x(e){let{components:i,...t}=e;return(0,p.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,p.kt)(r.Z,{id:"sprite.tilingSprite",pixiVersion:n,mdxType:"Example"}))}x.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/38eecf5f.b160e218.js b/assets/js/38eecf5f.b160e218.js deleted file mode 100644 index 30f1a2669..000000000 --- a/assets/js/38eecf5f.b160e218.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5797],{6525:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>l,default:()=>c,frontMatter:()=>a,metadata:()=>o,toc:()=>m});var s=t(7462),p=(t(7294),t(3905)),r=t(8010),n=t(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Tiling Sprite"},l=void 0,o={unversionedId:"examples/sprite/tiling-sprite",id:"examples/sprite/tiling-sprite",title:"Tiling Sprite",description:"",source:"@site/docs/examples/sprite/tiling-sprite.md",sourceDirName:"examples/sprite",slug:"/examples/sprite/tiling-sprite",permalink:"/examples/sprite/tiling-sprite",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Tiling Sprite"},sidebar:"examplesSidebar",previous:{title:"Animated Sprite Animation Speed",permalink:"/examples/sprite/animated-sprite-animation-speed"},next:{title:"Video",permalink:"/examples/sprite/video"}},d={},m=[],u={toc:m};function c(e){let{components:i,...t}=e;return(0,p.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,p.kt)(r.Z,{id:"sprite.tilingSprite",pixiVersion:n,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/39a0bbcc.b6261976.js b/assets/js/39a0bbcc.b6261976.js deleted file mode 100644 index a560975e1..000000000 --- a/assets/js/39a0bbcc.b6261976.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9206],{8166:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>d,default:()=>u,frontMatter:()=>n,metadata:()=>r,toc:()=>c});var t=i(7462),a=(i(7294),i(3905)),o=i(8010),l=i(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Blend Modes"},d=void 0,r={unversionedId:"examples/basic/blend-modes",id:"examples/basic/blend-modes",title:"Blend Modes",description:"",source:"@site/docs/examples/basic/blend-modes.md",sourceDirName:"examples/basic",slug:"/examples/basic/blend-modes",permalink:"/examples/basic/blend-modes",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Blend Modes"},sidebar:"examplesSidebar",previous:{title:"Particle Container",permalink:"/examples/basic/particle-container"},next:{title:"Mesh Plane",permalink:"/examples/basic/mesh-plane"}},p={},c=[],m={toc:c};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(o.Z,{id:"basic.blendModes",pixiVersion:l,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/39a0bbcc.eae73dde.js b/assets/js/39a0bbcc.eae73dde.js new file mode 100644 index 000000000..aaf0b2ce8 --- /dev/null +++ b/assets/js/39a0bbcc.eae73dde.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9206],{8166:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>p,contentTitle:()=>d,default:()=>u,frontMatter:()=>n,metadata:()=>r,toc:()=>c});var t=i(7462),a=(i(7294),i(3905)),o=i(8010),l=i(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Blend Modes"},d=void 0,r={unversionedId:"examples/basic/blend-modes",id:"examples/basic/blend-modes",title:"Blend Modes",description:"",source:"@site/docs/examples/basic/blend-modes.md",sourceDirName:"examples/basic",slug:"/examples/basic/blend-modes",permalink:"/8.x/examples/basic/blend-modes",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Blend Modes"},sidebar:"examplesSidebar",previous:{title:"Particle Container",permalink:"/8.x/examples/basic/particle-container"},next:{title:"Mesh Plane",permalink:"/8.x/examples/basic/mesh-plane"}},p={},c=[],m={toc:c};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},m,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(o.Z,{id:"basic.blendModes",pixiVersion:l,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/3c597c45.ca778485.js b/assets/js/3c597c45.ca778485.js deleted file mode 100644 index 23982abc3..000000000 --- a/assets/js/3c597c45.ca778485.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1732],{7319:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>p,contentTitle:()=>a,default:()=>d,frontMatter:()=>s,metadata:()=>o,toc:()=>u});var r=i(7462),n=(i(7294),i(3905));const s={},a="v6 Migration Guide",o={unversionedId:"guides/migrations/v6",id:"guides/migrations/v6",title:"v6 Migration Guide",description:"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.",source:"@site/docs/guides/migrations/v6.md",sourceDirName:"guides/migrations",slug:"/guides/migrations/v6",permalink:"/guides/migrations/v6",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/migrations/v6.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"v7 Migration Guide",permalink:"/guides/migrations/v7"},next:{title:"v5 Migration Guide",permalink:"/guides/migrations/v5"}},p={},u=[{value:"Typings",id:"typings",level:2},{value:"Mesh Internals",id:"mesh-internals",level:2}],l={toc:u};function d(e){let{components:t,...i}=e;return(0,n.kt)("wrapper",(0,r.Z)({},l,i,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"v6-migration-guide"},"v6 Migration Guide"),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/releases/tag/v6.0.0"},"PixiJS 6")," comes with few surface-level breaking changes. This document is ",(0,n.kt)("strong",{parentName:"p"},"not complete"),"."),(0,n.kt)("h2",{id:"typings"},"Typings"),(0,n.kt)("p",null,"If you're using TypeScript, make sure the follow is added to your ",(0,n.kt)("strong",{parentName:"p"},"tsconfig.json"),":"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-js"},'{\n "compilerOptions": {\n "moduleResolution": "node",\n // Required for importing 3rd-party dependencies like EventEmitter3\n "esModuleInterop": true\n }\n}\n')),(0,n.kt)("h2",{id:"mesh-internals"},"Mesh Internals"),(0,n.kt)("p",null,"If you ever overrode ",(0,n.kt)("inlineCode",{parentName:"p"},"Mesh._renderDefault")," to take into account more uniforms like this: ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/blob/b05fb9c4b31efda244d40b680f6abf304c9daec3/packages/mesh/src/Mesh.ts#L314-L317"},"v5 Reference")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"if (shader.program.uniformData.translationMatrix)\n{\n shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);\n}\n")),(0,n.kt)("p",null,"Remove the if, leave the contents, otherwise you might not get correct sync uniform for ",(0,n.kt)("inlineCode",{parentName:"p"},"translationMatrix"),", or even worse - get null pointer. ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/blob/2a4bb1f2b015bd557d9c037d8886f68a467cf40d/packages/mesh/src/Mesh.ts#L318"},"v6 Reference"),"."),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3c597c45.d030a848.js b/assets/js/3c597c45.d030a848.js new file mode 100644 index 000000000..b8a89f2a6 --- /dev/null +++ b/assets/js/3c597c45.d030a848.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1732],{7319:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>p,contentTitle:()=>a,default:()=>d,frontMatter:()=>s,metadata:()=>o,toc:()=>u});var r=i(7462),n=(i(7294),i(3905));const s={},a="v6 Migration Guide",o={unversionedId:"guides/migrations/v6",id:"guides/migrations/v6",title:"v6 Migration Guide",description:"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.",source:"@site/docs/guides/migrations/v6.md",sourceDirName:"guides/migrations",slug:"/guides/migrations/v6",permalink:"/8.x/guides/migrations/v6",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/migrations/v6.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"v7 Migration Guide",permalink:"/8.x/guides/migrations/v7"},next:{title:"v5 Migration Guide",permalink:"/8.x/guides/migrations/v5"}},p={},u=[{value:"Typings",id:"typings",level:2},{value:"Mesh Internals",id:"mesh-internals",level:2}],l={toc:u};function d(e){let{components:t,...i}=e;return(0,n.kt)("wrapper",(0,r.Z)({},l,i,{components:t,mdxType:"MDXLayout"}),(0,n.kt)("h1",{id:"v6-migration-guide"},"v6 Migration Guide"),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/releases/tag/v6.0.0"},"PixiJS 6")," comes with few surface-level breaking changes. This document is ",(0,n.kt)("strong",{parentName:"p"},"not complete"),"."),(0,n.kt)("h2",{id:"typings"},"Typings"),(0,n.kt)("p",null,"If you're using TypeScript, make sure the follow is added to your ",(0,n.kt)("strong",{parentName:"p"},"tsconfig.json"),":"),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-js"},'{\n "compilerOptions": {\n "moduleResolution": "node",\n // Required for importing 3rd-party dependencies like EventEmitter3\n "esModuleInterop": true\n }\n}\n')),(0,n.kt)("h2",{id:"mesh-internals"},"Mesh Internals"),(0,n.kt)("p",null,"If you ever overrode ",(0,n.kt)("inlineCode",{parentName:"p"},"Mesh._renderDefault")," to take into account more uniforms like this: ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/blob/b05fb9c4b31efda244d40b680f6abf304c9daec3/packages/mesh/src/Mesh.ts#L314-L317"},"v5 Reference")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"if (shader.program.uniformData.translationMatrix)\n{\n shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);\n}\n")),(0,n.kt)("p",null,"Remove the if, leave the contents, otherwise you might not get correct sync uniform for ",(0,n.kt)("inlineCode",{parentName:"p"},"translationMatrix"),", or even worse - get null pointer. ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixi.js/blob/2a4bb1f2b015bd557d9c037d8886f68a467cf40d/packages/mesh/src/Mesh.ts#L318"},"v6 Reference"),"."),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-typescript"},"shader.uniforms.translationMatrix = this.transform.worldTransform.toArray(true);\n")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/3fb9768c.a11270a2.js b/assets/js/3fb9768c.a11270a2.js new file mode 100644 index 000000000..ff7329efb --- /dev/null +++ b/assets/js/3fb9768c.a11270a2.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9178],{5616:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>m,frontMatter:()=>d,metadata:()=>l,toc:()=>p});var i=s(7462),a=(s(7294),s(3905)),n=s(8010),o=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Screen Shot"},r=void 0,l={unversionedId:"examples/advanced/screen-shot",id:"examples/advanced/screen-shot",title:"Screen Shot",description:"",source:"@site/docs/examples/advanced/screen-shot.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/screen-shot",permalink:"/8.x/examples/advanced/screen-shot",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Screen Shot"},sidebar:"examplesSidebar",previous:{title:"Mouse Trail",permalink:"/8.x/examples/advanced/mouse-trail"},next:{title:"Collision Detection",permalink:"/8.x/examples/advanced/collision-detection"}},c={},p=[],u={toc:p};function m(e){let{components:t,...s}=e;return(0,a.kt)("wrapper",(0,i.Z)({},u,s,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"advanced.screenShot",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/3fb9768c.be892f34.js b/assets/js/3fb9768c.be892f34.js deleted file mode 100644 index 4eb9a4413..000000000 --- a/assets/js/3fb9768c.be892f34.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9178],{5616:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>m,frontMatter:()=>d,metadata:()=>l,toc:()=>p});var i=s(7462),a=(s(7294),s(3905)),n=s(8010),o=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Screen Shot"},r=void 0,l={unversionedId:"examples/advanced/screen-shot",id:"examples/advanced/screen-shot",title:"Screen Shot",description:"",source:"@site/docs/examples/advanced/screen-shot.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/screen-shot",permalink:"/examples/advanced/screen-shot",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Screen Shot"},sidebar:"examplesSidebar",previous:{title:"Mouse Trail",permalink:"/examples/advanced/mouse-trail"},next:{title:"Collision Detection",permalink:"/examples/advanced/collision-detection"}},c={},p=[],u={toc:p};function m(e){let{components:t,...s}=e;return(0,a.kt)("wrapper",(0,i.Z)({},u,s,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"advanced.screenShot",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/40d1cc3f.d766c6d9.js b/assets/js/40d1cc3f.744699ec.js similarity index 99% rename from assets/js/40d1cc3f.d766c6d9.js rename to assets/js/40d1cc3f.744699ec.js index d683f503f..6d984b23f 100644 --- a/assets/js/40d1cc3f.d766c6d9.js +++ b/assets/js/40d1cc3f.744699ec.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7705],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},5954:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/choo-choo-train",id:"tutorials/choo-choo-train",title:"choo-choo-train",description:"",source:"@site/docs/tutorials/choo-choo-train.md",sourceDirName:"tutorials",slug:"/tutorials/choo-choo-train",permalink:"/tutorials/choo-choo-train",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"chooChooTrain",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7705],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},5954:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/choo-choo-train",id:"tutorials/choo-choo-train",title:"choo-choo-train",description:"",source:"@site/docs/tutorials/choo-choo-train.md",sourceDirName:"tutorials",slug:"/tutorials/choo-choo-train",permalink:"/8.x/tutorials/choo-choo-train",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"chooChooTrain",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/40d5eb26.1f095d3b.js b/assets/js/40d5eb26.1f095d3b.js deleted file mode 100644 index d02586ed4..000000000 --- a/assets/js/40d5eb26.1f095d3b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9813],{3839:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>h,frontMatter:()=>n,metadata:()=>o,toc:()=>u});var a=s(7462),r=(s(7294),s(3905)),i=s(8010),d=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Triangle Textured"},l=void 0,o={unversionedId:"examples/mesh-and-shaders/triangle-textured",id:"examples/mesh-and-shaders/triangle-textured",title:"Triangle Textured",description:"",source:"@site/docs/examples/mesh-and-shaders/triangle-textured.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/triangle-textured",permalink:"/examples/mesh-and-shaders/triangle-textured",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Triangle Textured"},sidebar:"examplesSidebar",previous:{title:"Triangle Color",permalink:"/examples/mesh-and-shaders/triangle-color"},next:{title:"Shared Geometry",permalink:"/examples/mesh-and-shaders/shared-geometry"}},p={},u=[],m={toc:u};function h(e){let{components:t,...s}=e;return(0,r.kt)("wrapper",(0,a.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(i.Z,{id:"meshAndShaders.triangleTextured",pixiVersion:d,mdxType:"Example"}))}h.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/40d5eb26.594b36d9.js b/assets/js/40d5eb26.594b36d9.js new file mode 100644 index 000000000..d92ea2483 --- /dev/null +++ b/assets/js/40d5eb26.594b36d9.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9813],{3839:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>x,frontMatter:()=>n,metadata:()=>o,toc:()=>u});var a=s(7462),r=(s(7294),s(3905)),i=s(8010),d=s(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Triangle Textured"},l=void 0,o={unversionedId:"examples/mesh-and-shaders/triangle-textured",id:"examples/mesh-and-shaders/triangle-textured",title:"Triangle Textured",description:"",source:"@site/docs/examples/mesh-and-shaders/triangle-textured.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/triangle-textured",permalink:"/8.x/examples/mesh-and-shaders/triangle-textured",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:4,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:4,custom_edit_url:null,title:"Triangle Textured"},sidebar:"examplesSidebar",previous:{title:"Triangle Color",permalink:"/8.x/examples/mesh-and-shaders/triangle-color"},next:{title:"Shared Geometry",permalink:"/8.x/examples/mesh-and-shaders/shared-geometry"}},p={},u=[],m={toc:u};function x(e){let{components:t,...s}=e;return(0,r.kt)("wrapper",(0,a.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(i.Z,{id:"meshAndShaders.triangleTextured",pixiVersion:d,mdxType:"Example"}))}x.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/41ee92be.84df8d5a.js b/assets/js/41ee92be.84df8d5a.js deleted file mode 100644 index 293a80eef..000000000 --- a/assets/js/41ee92be.84df8d5a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8374],{6767:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>n,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var s=i(7462),p=(i(7294),i(3905)),x=i(8010),a=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Pixi Text"},l=void 0,r={unversionedId:"examples/text/pixi-text",id:"examples/text/pixi-text",title:"Pixi Text",description:"",source:"@site/docs/examples/text/pixi-text.md",sourceDirName:"examples/text",slug:"/examples/text/pixi-text",permalink:"/examples/text/pixi-text",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Pixi Text"},sidebar:"examplesSidebar",previous:{title:"Video",permalink:"/examples/sprite/video"},next:{title:"Bitmap Text",permalink:"/examples/text/bitmap-text"}},n={},d=[],u={toc:d};function m(e){let{components:t,...i}=e;return(0,p.kt)("wrapper",(0,s.Z)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,p.kt)(x.Z,{id:"text.pixiText",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/41ee92be.e9f2c27f.js b/assets/js/41ee92be.e9f2c27f.js new file mode 100644 index 000000000..d84a4a622 --- /dev/null +++ b/assets/js/41ee92be.e9f2c27f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8374],{6767:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>n,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>r,toc:()=>d});var s=i(7462),x=(i(7294),i(3905)),p=i(8010),a=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Pixi Text"},l=void 0,r={unversionedId:"examples/text/pixi-text",id:"examples/text/pixi-text",title:"Pixi Text",description:"",source:"@site/docs/examples/text/pixi-text.md",sourceDirName:"examples/text",slug:"/examples/text/pixi-text",permalink:"/8.x/examples/text/pixi-text",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Pixi Text"},sidebar:"examplesSidebar",previous:{title:"Video",permalink:"/8.x/examples/sprite/video"},next:{title:"Bitmap Text",permalink:"/8.x/examples/text/bitmap-text"}},n={},d=[],u={toc:d};function m(e){let{components:t,...i}=e;return(0,x.kt)("wrapper",(0,s.Z)({},u,i,{components:t,mdxType:"MDXLayout"}),(0,x.kt)(p.Z,{id:"text.pixiText",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/450ca9db.2e76b6d6.js b/assets/js/450ca9db.2e76b6d6.js new file mode 100644 index 000000000..d8fc94088 --- /dev/null +++ b/assets/js/450ca9db.2e76b6d6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3865],{3497:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var n=a(7462),i=(a(7294),a(3905));const o={},r="Text",s={unversionedId:"guides/components/text",id:"guides/components/text",title:"Text",description:"Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.",source:"@site/docs/guides/components/text.md",sourceDirName:"guides/components",slug:"/guides/components/text",permalink:"/8.x/guides/components/text",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/text.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Spritesheets",permalink:"/8.x/guides/components/sprite-sheets"},next:{title:"Textures",permalink:"/8.x/guides/components/textures"}},l={},p=[{value:"There Are Three Kinds of Text",id:"there-are-three-kinds-of-text",level:2},{value:"The Text Object",id:"the-text-object",level:2},{value:"Text Styles",id:"text-styles",level:2},{value:"Loading and Using Fonts",id:"loading-and-using-fonts",level:2},{value:"Caveats and Gotchas",id:"caveats-and-gotchas",level:2},{value:"BitmapText",id:"bitmaptext",level:2},{value:"BitmapFont",id:"bitmapfont",level:2},{value:"Selecting the Right Approach",id:"selecting-the-right-approach",level:2}],h={toc:p};function d(e){let{components:t,...a}=e;return(0,i.kt)("wrapper",(0,n.Z)({},h,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"text"},"Text"),(0,i.kt)("p",null,"Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object."),(0,i.kt)("p",null,"Let's dig into how this works."),(0,i.kt)("h2",{id:"there-are-three-kinds-of-text"},"There Are Three Kinds of Text"),(0,i.kt)("p",null,"Because of the challenges of working with text in WebGL, PixiJS provides three very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance."),(0,i.kt)("h2",{id:"the-text-object"},"The Text Object"),(0,i.kt)("p",null,"In order to draw text to the screen, you use a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Text.html"},"Text")," object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high."),(0,i.kt)("p",null,"So when working with Text objects, there are two sets of options - standard display object options like position, rotation, etc that work ",(0,i.kt)("em",{parentName:"p"},"after")," the text is rasterized internally, and text style options that are used ",(0,i.kt)("em",{parentName:"p"},"while")," rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/text/pixi-text"},"text example code"),"."),(0,i.kt)("h2",{id:"text-styles"},"Text Styles"),(0,i.kt)("p",null,"There are a lot of text style options available (see ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/text.TextStyle.html"},"TextStyle"),"), but they break down into 5 main groups:"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Font"),": ",(0,i.kt)("inlineCode",{parentName:"p"},"fontFamily")," to select the webfont to use, ",(0,i.kt)("inlineCode",{parentName:"p"},"fontSize")," to specify the size of the text to draw, along with options for font weight, style and variant."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Appearance"),": Set the color with ",(0,i.kt)("inlineCode",{parentName:"p"},"fill")," or add a ",(0,i.kt)("inlineCode",{parentName:"p"},"stroke")," outline, including options for gradient fills."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Drop-Shadows"),": Set a drop-shadow with ",(0,i.kt)("inlineCode",{parentName:"p"},"dropShadow"),", with a host of related options to specify offset, blur, opacity, etc."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Layout"),": Enable with ",(0,i.kt)("inlineCode",{parentName:"p"},"wordWrap")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"wordWrapWidth"),", and then customize the ",(0,i.kt)("inlineCode",{parentName:"p"},"lineHeight")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"align")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"letterSpacing")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Utilities"),": Add ",(0,i.kt)("inlineCode",{parentName:"p"},"padding")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"trim")," extra space to deal with funky font families if needed."),(0,i.kt)("p",null,"To interactively test out feature of Text Style, ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.io/pixi-text-style/"},"check out this tool"),"."),(0,i.kt)("h2",{id:"loading-and-using-fonts"},"Loading and Using Fonts"),(0,i.kt)("p",null,"In order for PixiJS to build a Text object, you'll need to make sure that the font you want to use is loaded by the browser. This can be easily accomplished with our good friends ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// load the fonts\nawait Assets.load('short-stack.woff2');\n\n// now they can be used!\n\nconst text = new Text({\n text:'hello',\n style:{\n fontFamily:'short-stack'\n }\n})\n")),(0,i.kt)("h2",{id:"caveats-and-gotchas"},"Caveats and Gotchas"),(0,i.kt)("p",null,"While PixiJS does make working with text easy, there are a few things you need to watch out for."),(0,i.kt)("p",null,"First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes."),(0,i.kt)("p",null,"Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp."),(0,i.kt)("h2",{id:"bitmaptext"},"BitmapText"),(0,i.kt)("p",null,"In addition to the standard Text approach to adding text to your project, PixiJS also supports ",(0,i.kt)("em",{parentName:"p"},"bitmap fonts"),". Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image."),(0,i.kt)("p",null,"The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/text/bitmap-text"},"bitmap text example code"),"."),(0,i.kt)("h2",{id:"bitmapfont"},"BitmapFont"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"3rd party solutions"),(0,i.kt)("li",{parentName:"ul"},"BitmapFont.from auto-generation")),(0,i.kt)("h2",{id:"selecting-the-right-approach"},"Selecting the Right Approach"),(0,i.kt)("p",null,"Text"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Static text"),(0,i.kt)("li",{parentName:"ul"},"Small number of text objects"),(0,i.kt)("li",{parentName:"ul"},"High fidelity text rendering (kerning e.g.)"),(0,i.kt)("li",{parentName:"ul"},"Text layout (line & letter spacing)")),(0,i.kt)("p",null,"BitmapText"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Dynamic text"),(0,i.kt)("li",{parentName:"ul"},"Large number of text objects"),(0,i.kt)("li",{parentName:"ul"},"Lower memory")),(0,i.kt)("p",null,"HTMLText"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Static text"),(0,i.kt)("li",{parentName:"ul"},"Need that HTML formatting")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/450ca9db.a096b847.js b/assets/js/450ca9db.a096b847.js deleted file mode 100644 index b47310b80..000000000 --- a/assets/js/450ca9db.a096b847.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3865],{3497:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>d,frontMatter:()=>o,metadata:()=>s,toc:()=>p});var n=a(7462),i=(a(7294),a(3905));const o={},r="Text",s={unversionedId:"guides/components/text",id:"guides/components/text",title:"Text",description:"Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.",source:"@site/docs/guides/components/text.md",sourceDirName:"guides/components",slug:"/guides/components/text",permalink:"/guides/components/text",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/text.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Spritesheets",permalink:"/guides/components/sprite-sheets"},next:{title:"Textures",permalink:"/guides/components/textures"}},l={},p=[{value:"There Are Three Kinds of Text",id:"there-are-three-kinds-of-text",level:2},{value:"The Text Object",id:"the-text-object",level:2},{value:"Text Styles",id:"text-styles",level:2},{value:"Loading and Using Fonts",id:"loading-and-using-fonts",level:2},{value:"Caveats and Gotchas",id:"caveats-and-gotchas",level:2},{value:"BitmapText",id:"bitmaptext",level:2},{value:"BitmapFont",id:"bitmapfont",level:2},{value:"Selecting the Right Approach",id:"selecting-the-right-approach",level:2}],h={toc:p};function d(e){let{components:t,...a}=e;return(0,i.kt)("wrapper",(0,n.Z)({},h,a,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"text"},"Text"),(0,i.kt)("p",null,"Whether it's a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there's no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object."),(0,i.kt)("p",null,"Let's dig into how this works."),(0,i.kt)("h2",{id:"there-are-three-kinds-of-text"},"There Are Three Kinds of Text"),(0,i.kt)("p",null,"Because of the challenges of working with text in WebGL, PixiJS provides three very different solutions. In this guide, we're going to go over both methods in some detail to help you make the right choice for your project's needs. Selecting the wrong text type can have a large negative impact on your project's performance and appearance."),(0,i.kt)("h2",{id:"the-text-object"},"The Text Object"),(0,i.kt)("p",null,"In order to draw text to the screen, you use a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Text.html"},"Text")," object. Under the hood, this class draws text to an off-screen buffer using the browser's normal text rendering, then uses that offscreen buffer as the source for drawing the text object. Effectively what this means is that whenever you create or change text, PixiJS creates a new rasterized image of that text, and then treats it like a sprite. This approach allows truly rich text display while keeping rendering speed high."),(0,i.kt)("p",null,"So when working with Text objects, there are two sets of options - standard display object options like position, rotation, etc that work ",(0,i.kt)("em",{parentName:"p"},"after")," the text is rasterized internally, and text style options that are used ",(0,i.kt)("em",{parentName:"p"},"while")," rasterizing. Because text once rendered is basically just a sprite, there's no need to review the standard options. Instead, let's focus on how text is styled."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/text/pixi-text"},"text example code"),"."),(0,i.kt)("h2",{id:"text-styles"},"Text Styles"),(0,i.kt)("p",null,"There are a lot of text style options available (see ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/text.TextStyle.html"},"TextStyle"),"), but they break down into 5 main groups:"),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Font"),": ",(0,i.kt)("inlineCode",{parentName:"p"},"fontFamily")," to select the webfont to use, ",(0,i.kt)("inlineCode",{parentName:"p"},"fontSize")," to specify the size of the text to draw, along with options for font weight, style and variant."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Appearance"),": Set the color with ",(0,i.kt)("inlineCode",{parentName:"p"},"fill")," or add a ",(0,i.kt)("inlineCode",{parentName:"p"},"stroke")," outline, including options for gradient fills."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Drop-Shadows"),": Set a drop-shadow with ",(0,i.kt)("inlineCode",{parentName:"p"},"dropShadow"),", with a host of related options to specify offset, blur, opacity, etc."),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Layout"),": Enable with ",(0,i.kt)("inlineCode",{parentName:"p"},"wordWrap")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"wordWrapWidth"),", and then customize the ",(0,i.kt)("inlineCode",{parentName:"p"},"lineHeight")," and ",(0,i.kt)("inlineCode",{parentName:"p"},"align")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"letterSpacing")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Utilities"),": Add ",(0,i.kt)("inlineCode",{parentName:"p"},"padding")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"trim")," extra space to deal with funky font families if needed."),(0,i.kt)("p",null,"To interactively test out feature of Text Style, ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.io/pixi-text-style/"},"check out this tool"),"."),(0,i.kt)("h2",{id:"loading-and-using-fonts"},"Loading and Using Fonts"),(0,i.kt)("p",null,"In order for PixiJS to build a Text object, you'll need to make sure that the font you want to use is loaded by the browser. This can be easily accomplished with our good friends ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// load the fonts\nawait Assets.load('short-stack.woff2');\n\n// now they can be used!\n\nconst text = new Text({\n text:'hello',\n style:{\n fontFamily:'short-stack'\n }\n})\n")),(0,i.kt)("h2",{id:"caveats-and-gotchas"},"Caveats and Gotchas"),(0,i.kt)("p",null,"While PixiJS does make working with text easy, there are a few things you need to watch out for."),(0,i.kt)("p",null,"First, changing an existing text string requires re-generating the internal render of that text, which is a slow operation that can impact performance if you change many text objects each frame. If your project requires lots of frequently changing text on the screen at once, consider using a BitmapText object (explained below) which uses a fixed bitmap font that doesn't require re-generation when text changes."),(0,i.kt)("p",null,"Second, be careful when scaling text. Setting a text object's scale to > 1.0 will result in blurry/pixely display, because the text is not re-rendered at the higher resolution needed to look sharp - it's still the same resolution it was when generated. To deal with this, you can render at a higher initial size and down-scale, instead. This will use more memory, but will allow your text to always look clear and crisp."),(0,i.kt)("h2",{id:"bitmaptext"},"BitmapText"),(0,i.kt)("p",null,"In addition to the standard Text approach to adding text to your project, PixiJS also supports ",(0,i.kt)("em",{parentName:"p"},"bitmap fonts"),". Bitmap fonts are very different from TrueType or other general purpose fonts, in that they consist of a single image containing pre-rendered versions of every letter you want to use. When drawing text with a bitmap font, PixiJS doesn't need to render the font glyphs into a temporary buffer - it can simply copy and stamp out each character of a string from the master font image."),(0,i.kt)("p",null,"The primary advantage of this approach is speed - changing text frequently is much cheaper and rendering each additional piece of text is much faster due to the shared source texture."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/text/bitmap-text"},"bitmap text example code"),"."),(0,i.kt)("h2",{id:"bitmapfont"},"BitmapFont"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"3rd party solutions"),(0,i.kt)("li",{parentName:"ul"},"BitmapFont.from auto-generation")),(0,i.kt)("h2",{id:"selecting-the-right-approach"},"Selecting the Right Approach"),(0,i.kt)("p",null,"Text"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Static text"),(0,i.kt)("li",{parentName:"ul"},"Small number of text objects"),(0,i.kt)("li",{parentName:"ul"},"High fidelity text rendering (kerning e.g.)"),(0,i.kt)("li",{parentName:"ul"},"Text layout (line & letter spacing)")),(0,i.kt)("p",null,"BitmapText"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Dynamic text"),(0,i.kt)("li",{parentName:"ul"},"Large number of text objects"),(0,i.kt)("li",{parentName:"ul"},"Lower memory")),(0,i.kt)("p",null,"HTMLText"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Static text"),(0,i.kt)("li",{parentName:"ul"},"Need that HTML formatting")))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/4c07c1d7.496937c3.js b/assets/js/4c07c1d7.496937c3.js new file mode 100644 index 000000000..825b43e1c --- /dev/null +++ b/assets/js/4c07c1d7.496937c3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3855],{5896:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>n,default:()=>u,frontMatter:()=>o,metadata:()=>h,toc:()=>p});var a=t(7462),i=(t(7294),t(3905)),d=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:8,custom_edit_url:null,title:"Shader Toy Mesh"},n=void 0,h={unversionedId:"examples/mesh-and-shaders/shader-toy-mesh",id:"examples/mesh-and-shaders/shader-toy-mesh",title:"Shader Toy Mesh",description:"",source:"@site/docs/examples/mesh-and-shaders/shader-toy-mesh.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/shader-toy-mesh",permalink:"/8.x/examples/mesh-and-shaders/shader-toy-mesh",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:8,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:8,custom_edit_url:null,title:"Shader Toy Mesh"},sidebar:"examplesSidebar",previous:{title:"Instanced Geometry",permalink:"/8.x/examples/mesh-and-shaders/instanced-geometry"},next:{title:"Multipass Mesh",permalink:"/8.x/examples/mesh-and-shaders/multipass-mesh"}},l={},p=[],m={toc:p};function u(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},m,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"meshAndShaders.shaderToyMesh",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/4c07c1d7.95c6e245.js b/assets/js/4c07c1d7.95c6e245.js deleted file mode 100644 index afde6388b..000000000 --- a/assets/js/4c07c1d7.95c6e245.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3855],{5896:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>n,default:()=>u,frontMatter:()=>o,metadata:()=>h,toc:()=>p});var a=t(7462),i=(t(7294),t(3905)),d=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:8,custom_edit_url:null,title:"Shader Toy Mesh"},n=void 0,h={unversionedId:"examples/mesh-and-shaders/shader-toy-mesh",id:"examples/mesh-and-shaders/shader-toy-mesh",title:"Shader Toy Mesh",description:"",source:"@site/docs/examples/mesh-and-shaders/shader-toy-mesh.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/shader-toy-mesh",permalink:"/examples/mesh-and-shaders/shader-toy-mesh",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:8,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:8,custom_edit_url:null,title:"Shader Toy Mesh"},sidebar:"examplesSidebar",previous:{title:"Instanced Geometry",permalink:"/examples/mesh-and-shaders/instanced-geometry"},next:{title:"Multipass Mesh",permalink:"/examples/mesh-and-shaders/multipass-mesh"}},l={},p=[],m={toc:p};function u(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},m,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"meshAndShaders.shaderToyMesh",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/56ef77ee.4fc0064e.js b/assets/js/56ef77ee.4fc0064e.js deleted file mode 100644 index 79fb452ec..000000000 --- a/assets/js/56ef77ee.4fc0064e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4490],{6772:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>x,contentTitle:()=>u,default:()=>c,frontMatter:()=>d,metadata:()=>o,toc:()=>l});var r=s(7462),i=(s(7294),s(3905)),a=s(8010),n=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Render Texture Basic"},u=void 0,o={unversionedId:"examples/textures/render-texture-basic",id:"examples/textures/render-texture-basic",title:"Render Texture Basic",description:"",source:"@site/docs/examples/textures/render-texture-basic.md",sourceDirName:"examples/textures",slug:"/examples/textures/render-texture-basic",permalink:"/examples/textures/render-texture-basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Render Texture Basic"},sidebar:"examplesSidebar",previous:{title:"Texture Rotate",permalink:"/examples/textures/texture-rotate"},next:{title:"Render Texture Advanced",permalink:"/examples/textures/render-texture-advanced"}},x={},l=[],p={toc:l};function c(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},p,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(a.Z,{id:"textures.renderTextureBasic",pixiVersion:n,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/56ef77ee.cdee5979.js b/assets/js/56ef77ee.cdee5979.js new file mode 100644 index 000000000..e1ff2a319 --- /dev/null +++ b/assets/js/56ef77ee.cdee5979.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4490],{6772:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>x,contentTitle:()=>u,default:()=>c,frontMatter:()=>d,metadata:()=>o,toc:()=>l});var r=s(7462),i=(s(7294),s(3905)),a=s(8010),n=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Render Texture Basic"},u=void 0,o={unversionedId:"examples/textures/render-texture-basic",id:"examples/textures/render-texture-basic",title:"Render Texture Basic",description:"",source:"@site/docs/examples/textures/render-texture-basic.md",sourceDirName:"examples/textures",slug:"/examples/textures/render-texture-basic",permalink:"/8.x/examples/textures/render-texture-basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Render Texture Basic"},sidebar:"examplesSidebar",previous:{title:"Texture Rotate",permalink:"/8.x/examples/textures/texture-rotate"},next:{title:"Render Texture Advanced",permalink:"/8.x/examples/textures/render-texture-advanced"}},x={},l=[],p={toc:l};function c(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},p,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(a.Z,{id:"textures.renderTextureBasic",pixiVersion:n,mdxType:"Example"}))}c.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/58ed2303.43a88a8c.js b/assets/js/58ed2303.eaa205f8.js similarity index 82% rename from assets/js/58ed2303.43a88a8c.js rename to assets/js/58ed2303.eaa205f8.js index 7c4bdfa31..01d3fae2b 100644 --- a/assets/js/58ed2303.43a88a8c.js +++ b/assets/js/58ed2303.eaa205f8.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1606],{3464:(e,t,n)=>{n.d(t,{Z:()=>N});var a=n(5166),r=n(7294),s=n(4184),i=n.n(s);const l="select_LFtK",o="label_iJQG",c="labelText_vfPa";var u=n(5893);const d=e=>e.map((e=>{let{value:t,label:n}=e;return(0,u.jsx)("option",{value:t,children:n},t)}));function p(e){let{className:t,label:n,labelClassName:a,selectedId:s,options:p,onValueChange:x,...h}=e;const m=(0,r.useCallback)((e=>{x(e.target.value)}),[x]),v=(e=>e.length>0&&Array.isArray(e[0].options))(p),g=(0,u.jsxs)("select",{className:i()(l,t),...h,value:s,onChange:m,children:[v&&(f=p,f.map((e=>{let{label:t,options:n}=e;return(0,u.jsx)("optgroup",{label:t,children:d(n)},t)}))),!v&&d(p)]});var f;return n?(0,u.jsxs)("label",{className:i()(o,a),children:[(0,u.jsx)("span",{className:c,children:n}),g]}):g}var x=n(3874),h=n(2124),m=n(6550);function v(e,t,n){const a=(0,m.k6)(),s=(0,r.useMemo)((()=>n(function(e){const t=new URLSearchParams(e);return Array.from(t.entries()).reduce(((e,t)=>{let[n,a]=t;return{...e,[n]:a}}),{})}(a.location.search))),[n,a.location.search]),i=(0,r.useRef)("function"==typeof e?e(s):e),l=(0,r.useMemo)((()=>({...i.current,...s})),[s]);return[l,function(e,n,r){void 0===n&&(n=!0),void 0===r&&(r=!1);const s="function"==typeof e?e(l):{...l,...e},o=Object.entries(s).reduce(((e,t)=>{let[n,a]=t;return a===i.current[n]?e:{...e,[n]:a}}),{}),c=new URLSearchParams(Object.entries(t(o))),u={pathname:location.pathname,search:c.toString()};r?n?a.push(u):a.replace(u):n?history.pushState(u,"",`${u.pathname}?${c.toString()}`):history.replaceState(u,"",`${u.pathname}?${c.toString()}`)}]}const g=(0,h.qhW)(h.kKJ,(function(e){const t=JSON.stringify(e);return btoa(t)})),f=(0,h.qhW)(h.kKJ,(function(e){try{return JSON.parse(atob(e))}catch{return}})),b=(0,h.rO9)({state:g}),y=(0,h.rO9)({state:f}),j="wrapper_GThw",_="nav_LVun",C="select_VJqz";var k=n(1262);function N(e){let{pixiVersion:t}=e;const[n,r]=(e=>{let{defaultExampleId:t}=e;return v((e=>{const{state:n,exampleId:a}=e;return{state:n??void 0,exampleId:n?"custom":a??t}}),b,y)})({defaultExampleId:x.L8,defaultPixiVersion:t.version}),{state:s,exampleId:i}=n,{indexCode:l,extraFiles:o,activeFile:c,usesWebWorkerLibrary:d,exampleOptions:h,handleOptionSelected:m,handleEditorCodeChanged:g}=(0,x.AQ)({urlState:s,selectedOptionId:i,setURLState:r,pixiVersion:t});return(0,u.jsx)("div",{className:j,children:(0,u.jsx)(k.Z,{children:()=>(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("div",{className:_,children:(0,u.jsx)(p,{label:"Example:",labelClassName:C,selectedId:i,options:h,onValueChange:m})}),(0,u.jsx)(a.Z,{code:l,extraFiles:o,activeFile:c,pixiVersion:t.version,isPixiDevVersion:t.dev,isPixiWebWorkerVersion:d,onCodeChanged:g,mode:"fullscreen"})]})})})}},2801:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>x,frontMatter:()=>l,metadata:()=>c,toc:()=>d});var a=n(7462),r=(n(7294),n(3905)),s=n(3464),i=n(7949);const l={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},o=void 0,c={unversionedId:"playground/index",id:"playground/index",title:"index",description:"",source:"@site/docs/playground/index.md",sourceDirName:"playground",slug:"/playground/",permalink:"/playground/",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},u={},d=[],p={toc:d};function x(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(s.Z,{pixiVersion:i,mdxType:"Playground"}))}x.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1606],{3464:(e,t,n)=>{n.d(t,{Z:()=>N});var a=n(5166),r=n(7294),s=n(4184),i=n.n(s);const l="select_LFtK",o="label_iJQG",c="labelText_vfPa";var u=n(5893);const d=e=>e.map((e=>{let{value:t,label:n}=e;return(0,u.jsx)("option",{value:t,children:n},t)}));function p(e){let{className:t,label:n,labelClassName:a,selectedId:s,options:p,onValueChange:x,...h}=e;const m=(0,r.useCallback)((e=>{x(e.target.value)}),[x]),v=(e=>e.length>0&&Array.isArray(e[0].options))(p),g=(0,u.jsxs)("select",{className:i()(l,t),...h,value:s,onChange:m,children:[v&&(f=p,f.map((e=>{let{label:t,options:n}=e;return(0,u.jsx)("optgroup",{label:t,children:d(n)},t)}))),!v&&d(p)]});var f;return n?(0,u.jsxs)("label",{className:i()(o,a),children:[(0,u.jsx)("span",{className:c,children:n}),g]}):g}var x=n(3874),h=n(2124),m=n(6550);function v(e,t,n){const a=(0,m.k6)(),s=(0,r.useMemo)((()=>n(function(e){const t=new URLSearchParams(e);return Array.from(t.entries()).reduce(((e,t)=>{let[n,a]=t;return{...e,[n]:a}}),{})}(a.location.search))),[n,a.location.search]),i=(0,r.useRef)("function"==typeof e?e(s):e),l=(0,r.useMemo)((()=>({...i.current,...s})),[s]);return[l,function(e,n,r){void 0===n&&(n=!0),void 0===r&&(r=!1);const s="function"==typeof e?e(l):{...l,...e},o=Object.entries(s).reduce(((e,t)=>{let[n,a]=t;return a===i.current[n]?e:{...e,[n]:a}}),{}),c=new URLSearchParams(Object.entries(t(o))),u={pathname:location.pathname,search:c.toString()};r?n?a.push(u):a.replace(u):n?history.pushState(u,"",`${u.pathname}?${c.toString()}`):history.replaceState(u,"",`${u.pathname}?${c.toString()}`)}]}const g=(0,h.qhW)(h.kKJ,(function(e){const t=JSON.stringify(e);return btoa(t)})),f=(0,h.qhW)(h.kKJ,(function(e){try{return JSON.parse(atob(e))}catch{return}})),b=(0,h.rO9)({state:g}),y=(0,h.rO9)({state:f}),j="wrapper_GThw",_="nav_LVun",C="select_VJqz";var k=n(1262);function N(e){let{pixiVersion:t}=e;const[n,r]=(e=>{let{defaultExampleId:t}=e;return v((e=>{const{state:n,exampleId:a}=e;return{state:n??void 0,exampleId:n?"custom":a??t}}),b,y)})({defaultExampleId:x.L8,defaultPixiVersion:t.version}),{state:s,exampleId:i}=n,{indexCode:l,extraFiles:o,activeFile:c,usesWebWorkerLibrary:d,exampleOptions:h,handleOptionSelected:m,handleEditorCodeChanged:g}=(0,x.AQ)({urlState:s,selectedOptionId:i,setURLState:r,pixiVersion:t});return(0,u.jsx)("div",{className:j,children:(0,u.jsx)(k.Z,{children:()=>(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)("div",{className:_,children:(0,u.jsx)(p,{label:"Example:",labelClassName:C,selectedId:i,options:h,onValueChange:m})}),(0,u.jsx)(a.Z,{code:l,extraFiles:o,activeFile:c,pixiVersion:t.version,isPixiDevVersion:t.dev,isPixiWebWorkerVersion:d,onCodeChanged:g,mode:"fullscreen"})]})})})}},2801:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>u,contentTitle:()=>o,default:()=>x,frontMatter:()=>l,metadata:()=>c,toc:()=>d});var a=n(7462),r=(n(7294),n(3905)),s=n(3464),i=n(7949);const l={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},o=void 0,c={unversionedId:"playground/index",id:"playground/index",title:"index",description:"",source:"@site/docs/playground/index.md",sourceDirName:"playground",slug:"/playground/",permalink:"/8.x/playground/",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},u={},d=[],p={toc:d};function x(e){let{components:t,...n}=e;return(0,r.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,r.kt)(s.Z,{pixiVersion:i,mdxType:"Playground"}))}x.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/5b758207.12523c6a.js b/assets/js/5b758207.12523c6a.js deleted file mode 100644 index 8641cdfae..000000000 --- a/assets/js/5b758207.12523c6a.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2463],{5143:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>o,metadata:()=>r,toc:()=>m});var t=i(7462),a=(i(7294),i(3905)),l=i(8010),n=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Mesh Plane"},p=void 0,r={unversionedId:"examples/basic/mesh-plane",id:"examples/basic/mesh-plane",title:"Mesh Plane",description:"",source:"@site/docs/examples/basic/mesh-plane.md",sourceDirName:"examples/basic",slug:"/examples/basic/mesh-plane",permalink:"/examples/basic/mesh-plane",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Mesh Plane"},sidebar:"examplesSidebar",previous:{title:"Blend Modes",permalink:"/examples/basic/blend-modes"},next:{title:"Render Group",permalink:"/examples/basic/render-group"}},d={},m=[],c={toc:m};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},c,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"basic.meshPlane",pixiVersion:n,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/5b758207.37edd799.js b/assets/js/5b758207.37edd799.js new file mode 100644 index 000000000..13ec6ab62 --- /dev/null +++ b/assets/js/5b758207.37edd799.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2463],{5143:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>o,metadata:()=>r,toc:()=>m});var t=i(7462),a=(i(7294),i(3905)),l=i(8010),n=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Mesh Plane"},p=void 0,r={unversionedId:"examples/basic/mesh-plane",id:"examples/basic/mesh-plane",title:"Mesh Plane",description:"",source:"@site/docs/examples/basic/mesh-plane.md",sourceDirName:"examples/basic",slug:"/examples/basic/mesh-plane",permalink:"/8.x/examples/basic/mesh-plane",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Mesh Plane"},sidebar:"examplesSidebar",previous:{title:"Blend Modes",permalink:"/8.x/examples/basic/blend-modes"},next:{title:"Render Group",permalink:"/8.x/examples/basic/render-group"}},d={},m=[],c={toc:m};function u(e){let{components:s,...i}=e;return(0,a.kt)("wrapper",(0,t.Z)({},c,i,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"basic.meshPlane",pixiVersion:n,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/63560707.9523be23.js b/assets/js/63560707.9523be23.js deleted file mode 100644 index 9a132d45d..000000000 --- a/assets/js/63560707.9523be23.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3322],{7685:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>l,toc:()=>m});var a=t(7462),i=(t(7294),t(3905)),d=t(8010),r=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Textured Mesh Basic"},o=void 0,l={unversionedId:"examples/mesh-and-shaders/textured-mesh-basic",id:"examples/mesh-and-shaders/textured-mesh-basic",title:"Textured Mesh Basic",description:"",source:"@site/docs/examples/mesh-and-shaders/textured-mesh-basic.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/textured-mesh-basic",permalink:"/examples/mesh-and-shaders/textured-mesh-basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Textured Mesh Basic"},sidebar:"examplesSidebar",previous:{title:"Custom",permalink:"/examples/filters-advanced/custom"},next:{title:"Textured Mesh Advanced",permalink:"/examples/mesh-and-shaders/textured-mesh-advanced"}},p={},m=[],u={toc:m};function h(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"meshAndShaders.texturedMeshBasic",pixiVersion:r,mdxType:"Example"}))}h.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/63560707.a91a44d5.js b/assets/js/63560707.a91a44d5.js new file mode 100644 index 000000000..ad1cb6afb --- /dev/null +++ b/assets/js/63560707.a91a44d5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3322],{7685:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>h,frontMatter:()=>n,metadata:()=>l,toc:()=>m});var a=t(7462),i=(t(7294),t(3905)),d=t(8010),r=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Textured Mesh Basic"},o=void 0,l={unversionedId:"examples/mesh-and-shaders/textured-mesh-basic",id:"examples/mesh-and-shaders/textured-mesh-basic",title:"Textured Mesh Basic",description:"",source:"@site/docs/examples/mesh-and-shaders/textured-mesh-basic.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/textured-mesh-basic",permalink:"/8.x/examples/mesh-and-shaders/textured-mesh-basic",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Textured Mesh Basic"},sidebar:"examplesSidebar",previous:{title:"Custom",permalink:"/8.x/examples/filters-advanced/custom"},next:{title:"Textured Mesh Advanced",permalink:"/8.x/examples/mesh-and-shaders/textured-mesh-advanced"}},p={},m=[],u={toc:m};function h(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(d.Z,{id:"meshAndShaders.texturedMeshBasic",pixiVersion:r,mdxType:"Example"}))}h.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/63a750ce.13e49e3e.js b/assets/js/63a750ce.13e49e3e.js deleted file mode 100644 index 5fe41e31b..000000000 --- a/assets/js/63a750ce.13e49e3e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3997],{5013:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>n,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(8010),d=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Slots"},n=void 0,r={unversionedId:"examples/advanced/slots",id:"examples/advanced/slots",title:"Slots",description:"",source:"@site/docs/examples/advanced/slots.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/slots",permalink:"/examples/advanced/slots",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Slots"},sidebar:"examplesSidebar",previous:{title:"Render Group",permalink:"/examples/basic/render-group"},next:{title:"Scratch Card",permalink:"/examples/advanced/scratch-card"}},p={},c=[],u={toc:c};function m(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"advanced.slots",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/63a750ce.a0cc1416.js b/assets/js/63a750ce.a0cc1416.js new file mode 100644 index 000000000..f4e215490 --- /dev/null +++ b/assets/js/63a750ce.a0cc1416.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3997],{5013:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>n,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(8010),d=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Slots"},n=void 0,r={unversionedId:"examples/advanced/slots",id:"examples/advanced/slots",title:"Slots",description:"",source:"@site/docs/examples/advanced/slots.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/slots",permalink:"/8.x/examples/advanced/slots",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Slots"},sidebar:"examplesSidebar",previous:{title:"Render Group",permalink:"/8.x/examples/basic/render-group"},next:{title:"Scratch Card",permalink:"/8.x/examples/advanced/scratch-card"}},p={},c=[],u={toc:c};function m(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"advanced.slots",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/64b0ce6a.c5eaf6ad.js b/assets/js/64b0ce6a.c5eaf6ad.js new file mode 100644 index 000000000..987de0c94 --- /dev/null +++ b/assets/js/64b0ce6a.c5eaf6ad.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8861],{7116:(i,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var e=n(7462),t=(n(7294),n(3905));const l={},r="Branding",o={unversionedId:"branding",id:"branding",title:"Branding",description:"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.",source:"@site/docs/branding.md",sourceDirName:".",slug:"/branding",permalink:"/8.x/branding",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/branding.md",tags:[],version:"current",frontMatter:{}},s={},p=[{value:"Banner",id:"banner",level:2},{value:"Logo",id:"logo",level:2},{value:"Logo (Dark)",id:"logo-dark",level:3},{value:"Logo (Dark, Transparent)",id:"logo-dark-transparent",level:3},{value:"Logo (Pink)",id:"logo-pink",level:3},{value:"Logo (Pink, Transparent)",id:"logo-pink-transparent",level:3},{value:"Mark",id:"mark",level:2},{value:"Mark (Pink, Large)",id:"mark-pink-large",level:3},{value:"Mark (Pink)",id:"mark-pink",level:3},{value:"Mark (Light)",id:"mark-light",level:3}],d={toc:p};function g(i){let{components:a,...n}=i;return(0,t.kt)("wrapper",(0,e.Z)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,t.kt)("h1",{id:"branding"},"Branding"),(0,t.kt)("p",null,"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please ",(0,t.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs.com/issues/new"},"file an issue"),"."),(0,t.kt)("h2",{id:"banner"},"Banner"),(0,t.kt)("p",null,"This is the banner that is displayed at the top of our ",(0,t.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs/blob/dev/README.md"},"README"),"."),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-banner.png",alt:"PixiJS Banner"})),(0,t.kt)("h2",{id:"logo"},"Logo"),(0,t.kt)("p",null,"We recommend using the Logo in places where the audience may not be familiar with PixiJS. "),(0,t.kt)("h3",{id:"logo-dark"},"Logo (Dark)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-full-dark.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"logo-dark-transparent"},"Logo (Dark, Transparent)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"logo-pink"},"Logo (Pink)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-full-light.png",alt:"PixiJS Logo Full Light"})),(0,t.kt)("h3",{id:"logo-pink-transparent"},"Logo (Pink, Transparent)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.png",alt:"PixiJS Logo Full Light"})),(0,t.kt)("h2",{id:"mark"},"Mark"),(0,t.kt)("p",null,"We recommend using the Mark in places where the audience is someone familiar with the ecosystem, such as PixiJS Discord users, plugin authors, social media followers."),(0,t.kt)("h3",{id:"mark-pink-large"},"Mark (Pink, Large)"),(0,t.kt)("p",null,"512px x 512px "),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"mark-pink"},"Mark (Pink)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.png",alt:"PixiJS Logo Mark Dark"})),(0,t.kt)("h3",{id:"mark-light"},"Mark (Light)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-mark-light.png",alt:"PixiJS Logo Mark Light"})))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/64b0ce6a.fbf48efb.js b/assets/js/64b0ce6a.fbf48efb.js deleted file mode 100644 index 5fd4fa65a..000000000 --- a/assets/js/64b0ce6a.fbf48efb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8861],{7116:(i,a,n)=>{n.r(a),n.d(a,{assets:()=>s,contentTitle:()=>r,default:()=>g,frontMatter:()=>l,metadata:()=>o,toc:()=>p});var e=n(7462),t=(n(7294),n(3905));const l={},r="Branding",o={unversionedId:"branding",id:"branding",title:"Branding",description:"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue.",source:"@site/docs/branding.md",sourceDirName:".",slug:"/branding",permalink:"/branding",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/branding.md",tags:[],version:"current",frontMatter:{}},s={},p=[{value:"Banner",id:"banner",level:2},{value:"Logo",id:"logo",level:2},{value:"Logo (Dark)",id:"logo-dark",level:3},{value:"Logo (Dark, Transparent)",id:"logo-dark-transparent",level:3},{value:"Logo (Pink)",id:"logo-pink",level:3},{value:"Logo (Pink, Transparent)",id:"logo-pink-transparent",level:3},{value:"Mark",id:"mark",level:2},{value:"Mark (Pink, Large)",id:"mark-pink-large",level:3},{value:"Mark (Pink)",id:"mark-pink",level:3},{value:"Mark (Light)",id:"mark-light",level:3}],d={toc:p};function g(i){let{components:a,...n}=i;return(0,t.kt)("wrapper",(0,e.Z)({},d,n,{components:a,mdxType:"MDXLayout"}),(0,t.kt)("h1",{id:"branding"},"Branding"),(0,t.kt)("p",null,"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please ",(0,t.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs.com/issues/new"},"file an issue"),"."),(0,t.kt)("h2",{id:"banner"},"Banner"),(0,t.kt)("p",null,"This is the banner that is displayed at the top of our ",(0,t.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs/blob/dev/README.md"},"README"),"."),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-banner.png",alt:"PixiJS Banner"})),(0,t.kt)("h2",{id:"logo"},"Logo"),(0,t.kt)("p",null,"We recommend using the Logo in places where the audience may not be familiar with PixiJS. "),(0,t.kt)("h3",{id:"logo-dark"},"Logo (Dark)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-full-dark.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"logo-dark-transparent"},"Logo (Dark, Transparent)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-transparent-dark.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"logo-pink"},"Logo (Pink)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-full-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-full-light.png",alt:"PixiJS Logo Full Light"})),(0,t.kt)("h3",{id:"logo-pink-transparent"},"Logo (Pink, Transparent)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-transparent-light.png",alt:"PixiJS Logo Full Light"})),(0,t.kt)("h2",{id:"mark"},"Mark"),(0,t.kt)("p",null,"We recommend using the Mark in places where the audience is someone familiar with the ecosystem, such as PixiJS Discord users, plugin authors, social media followers."),(0,t.kt)("h3",{id:"mark-pink-large"},"Mark (Pink, Large)"),(0,t.kt)("p",null,"512px x 512px "),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo.png",alt:"PixiJS Logo Full Dark"})),(0,t.kt)("h3",{id:"mark-pink"},"Mark (Pink)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-mark-dark.png",alt:"PixiJS Logo Mark Dark"})),(0,t.kt)("h3",{id:"mark-light"},"Mark (Light)"),(0,t.kt)("p",null,"Download: ",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-light.svg"},"SVG"),"\n",(0,t.kt)("a",{parentName:"p",href:"https://files.pixijs.download/branding/pixijs-logo-mark-light.png"},"PNG")),(0,t.kt)("p",null,(0,t.kt)("img",{parentName:"p",src:"https://files.pixijs.download/branding/pixijs-logo-mark-light.png",alt:"PixiJS Logo Mark Light"})))}g.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/65aa242e.6745d8f6.js b/assets/js/65aa242e.6745d8f6.js new file mode 100644 index 000000000..daaade759 --- /dev/null +++ b/assets/js/65aa242e.6745d8f6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2e3],{4744:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>l,metadata:()=>r,toc:()=>x});var i=s(7462),o=(s(7294),s(3905)),n=s(8010),a=s(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Web Font"},p=void 0,r={unversionedId:"examples/text/web-font",id:"examples/text/web-font",title:"Web Font",description:"",source:"@site/docs/examples/text/web-font.md",sourceDirName:"examples/text",slug:"/examples/text/web-font",permalink:"/8.x/examples/text/web-font",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Web Font"},sidebar:"examplesSidebar",previous:{title:"From Font",permalink:"/8.x/examples/text/from-font"},next:{title:"Simple",permalink:"/8.x/examples/graphics/simple"}},d={},x=[],m={toc:x};function u(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"text.webFont",pixiVersion:a,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/65aa242e.a8b17e71.js b/assets/js/65aa242e.a8b17e71.js deleted file mode 100644 index 4cb2f494b..000000000 --- a/assets/js/65aa242e.a8b17e71.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2e3],{4744:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>l,metadata:()=>r,toc:()=>x});var i=s(7462),o=(s(7294),s(3905)),n=s(8010),a=s(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Web Font"},p=void 0,r={unversionedId:"examples/text/web-font",id:"examples/text/web-font",title:"Web Font",description:"",source:"@site/docs/examples/text/web-font.md",sourceDirName:"examples/text",slug:"/examples/text/web-font",permalink:"/examples/text/web-font",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Web Font"},sidebar:"examplesSidebar",previous:{title:"From Font",permalink:"/examples/text/from-font"},next:{title:"Simple",permalink:"/examples/graphics/simple"}},d={},x=[],m={toc:x};function u(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},m,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"text.webFont",pixiVersion:a,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/65ba9fa8.1d6c707f.js b/assets/js/65ba9fa8.1d6c707f.js new file mode 100644 index 000000000..fb6bc363a --- /dev/null +++ b/assets/js/65ba9fa8.1d6c707f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8328],{1221:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>p,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),l=i(8010),n=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Click"},r=void 0,p={unversionedId:"examples/events/click",id:"examples/events/click",title:"Click",description:"",source:"@site/docs/examples/events/click.md",sourceDirName:"examples/events",slug:"/examples/events/click",permalink:"/8.x/examples/events/click",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Click"},sidebar:"examplesSidebar",previous:{title:"Mesh From Path",permalink:"/8.x/examples/graphics/mesh-from-path"},next:{title:"Interactivity",permalink:"/8.x/examples/events/interactivity"}},c={},d=[],m={toc:d};function u(e){let{components:t,...i}=e;return(0,a.kt)("wrapper",(0,s.Z)({},m,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"events.click",pixiVersion:n,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/65ba9fa8.c82f54fe.js b/assets/js/65ba9fa8.c82f54fe.js deleted file mode 100644 index 236b455d2..000000000 --- a/assets/js/65ba9fa8.c82f54fe.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8328],{1221:(e,t,i)=>{i.r(t),i.d(t,{assets:()=>c,contentTitle:()=>r,default:()=>u,frontMatter:()=>o,metadata:()=>p,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),l=i(8010),n=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Click"},r=void 0,p={unversionedId:"examples/events/click",id:"examples/events/click",title:"Click",description:"",source:"@site/docs/examples/events/click.md",sourceDirName:"examples/events",slug:"/examples/events/click",permalink:"/examples/events/click",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Click"},sidebar:"examplesSidebar",previous:{title:"Mesh From Path",permalink:"/examples/graphics/mesh-from-path"},next:{title:"Interactivity",permalink:"/examples/events/interactivity"}},c={},d=[],m={toc:d};function u(e){let{components:t,...i}=e;return(0,a.kt)("wrapper",(0,s.Z)({},m,i,{components:t,mdxType:"MDXLayout"}),(0,a.kt)(l.Z,{id:"events.click",pixiVersion:n,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/6d055c2c.47dbc415.js b/assets/js/6d055c2c.47dbc415.js new file mode 100644 index 000000000..01f3d6189 --- /dev/null +++ b/assets/js/6d055c2c.47dbc415.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2737],{9204:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>u,metadata:()=>n,toc:()=>x});var r=s(7462),i=(s(7294),s(3905)),a=s(8010),o=s(7949);const u={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Texture Rotate"},l=void 0,n={unversionedId:"examples/textures/texture-rotate",id:"examples/textures/texture-rotate",title:"Texture Rotate",description:"",source:"@site/docs/examples/textures/texture-rotate.md",sourceDirName:"examples/textures",slug:"/examples/textures/texture-rotate",permalink:"/8.x/examples/textures/texture-rotate",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Texture Rotate"},sidebar:"examplesSidebar",previous:{title:"Multipass Mesh",permalink:"/8.x/examples/mesh-and-shaders/multipass-mesh"},next:{title:"Render Texture Basic",permalink:"/8.x/examples/textures/render-texture-basic"}},p={},x=[],d={toc:x};function m(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},d,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(a.Z,{id:"textures.textureRotate",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/6d055c2c.737d6fbd.js b/assets/js/6d055c2c.737d6fbd.js deleted file mode 100644 index 438a26dcc..000000000 --- a/assets/js/6d055c2c.737d6fbd.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2737],{9204:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>u,metadata:()=>n,toc:()=>x});var r=s(7462),i=(s(7294),s(3905)),a=s(8010),o=s(7949);const u={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Texture Rotate"},l=void 0,n={unversionedId:"examples/textures/texture-rotate",id:"examples/textures/texture-rotate",title:"Texture Rotate",description:"",source:"@site/docs/examples/textures/texture-rotate.md",sourceDirName:"examples/textures",slug:"/examples/textures/texture-rotate",permalink:"/examples/textures/texture-rotate",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Texture Rotate"},sidebar:"examplesSidebar",previous:{title:"Multipass Mesh",permalink:"/examples/mesh-and-shaders/multipass-mesh"},next:{title:"Render Texture Basic",permalink:"/examples/textures/render-texture-basic"}},p={},x=[],d={toc:x};function m(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,r.Z)({},d,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(a.Z,{id:"textures.textureRotate",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/6ea3c477.aae04604.js b/assets/js/6ea3c477.aae04604.js deleted file mode 100644 index 0d4f9c0c5..000000000 --- a/assets/js/6ea3c477.aae04604.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9927],{2278:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>p,default:()=>m,frontMatter:()=>n,metadata:()=>d,toc:()=>u});var i=t(7462),r=(t(7294),t(3905)),a=t(8010),o=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Render Group"},p=void 0,d={unversionedId:"examples/basic/render-group",id:"examples/basic/render-group",title:"Render Group",description:"",source:"@site/docs/examples/basic/render-group.md",sourceDirName:"examples/basic",slug:"/examples/basic/render-group",permalink:"/examples/basic/render-group",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Render Group"},sidebar:"examplesSidebar",previous:{title:"Mesh Plane",permalink:"/examples/basic/mesh-plane"},next:{title:"Slots",permalink:"/examples/advanced/slots"}},l={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(a.Z,{id:"basic.renderGroup",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/6ea3c477.aded6e90.js b/assets/js/6ea3c477.aded6e90.js new file mode 100644 index 000000000..d5a7045e5 --- /dev/null +++ b/assets/js/6ea3c477.aded6e90.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9927],{2278:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>l,contentTitle:()=>p,default:()=>m,frontMatter:()=>n,metadata:()=>d,toc:()=>u});var i=t(7462),r=(t(7294),t(3905)),a=t(8010),o=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Render Group"},p=void 0,d={unversionedId:"examples/basic/render-group",id:"examples/basic/render-group",title:"Render Group",description:"",source:"@site/docs/examples/basic/render-group.md",sourceDirName:"examples/basic",slug:"/examples/basic/render-group",permalink:"/8.x/examples/basic/render-group",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:6,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:6,custom_edit_url:null,title:"Render Group"},sidebar:"examplesSidebar",previous:{title:"Mesh Plane",permalink:"/8.x/examples/basic/mesh-plane"},next:{title:"Slots",permalink:"/8.x/examples/advanced/slots"}},l={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(a.Z,{id:"basic.renderGroup",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/6f626e4c.6ce2ae43.js b/assets/js/6f626e4c.969e72c6.js similarity index 99% rename from assets/js/6f626e4c.6ce2ae43.js rename to assets/js/6f626e4c.969e72c6.js index 77188e0da..493e55503 100644 --- a/assets/js/6f626e4c.6ce2ae43.js +++ b/assets/js/6f626e4c.969e72c6.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5842],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},3336:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/fish-pond",id:"tutorials/fish-pond",title:"fish-pond",description:"",source:"@site/docs/tutorials/fish-pond.md",sourceDirName:"tutorials",slug:"/tutorials/fish-pond",permalink:"/tutorials/fish-pond",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"fishPond",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5842],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},3336:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/fish-pond",id:"tutorials/fish-pond",title:"fish-pond",description:"",source:"@site/docs/tutorials/fish-pond.md",sourceDirName:"tutorials",slug:"/tutorials/fish-pond",permalink:"/8.x/tutorials/fish-pond",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"fishPond",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/780ae8a3.6e66de25.js b/assets/js/780ae8a3.6e66de25.js new file mode 100644 index 000000000..94ab92412 --- /dev/null +++ b/assets/js/780ae8a3.6e66de25.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5045],{9117:(e,i,s)=>{s.r(i),s.d(i,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>n,toc:()=>m});var t=s(7462),p=(s(7294),s(3905)),a=s(8010),l=s(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Simple"},o=void 0,n={unversionedId:"examples/graphics/simple",id:"examples/graphics/simple",title:"Simple",description:"",source:"@site/docs/examples/graphics/simple.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/simple",permalink:"/8.x/examples/graphics/simple",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Simple"},sidebar:"examplesSidebar",previous:{title:"Web Font",permalink:"/8.x/examples/text/web-font"},next:{title:"Advanced",permalink:"/8.x/examples/graphics/advanced"}},d={},m=[],c={toc:m};function u(e){let{components:i,...s}=e;return(0,p.kt)("wrapper",(0,t.Z)({},c,s,{components:i,mdxType:"MDXLayout"}),(0,p.kt)(a.Z,{id:"graphics.simple",pixiVersion:l,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/780ae8a3.bc7396f7.js b/assets/js/780ae8a3.bc7396f7.js deleted file mode 100644 index 79114fcac..000000000 --- a/assets/js/780ae8a3.bc7396f7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5045],{9117:(e,i,s)=>{s.r(i),s.d(i,{assets:()=>d,contentTitle:()=>o,default:()=>u,frontMatter:()=>r,metadata:()=>n,toc:()=>m});var t=s(7462),p=(s(7294),s(3905)),a=s(8010),l=s(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Simple"},o=void 0,n={unversionedId:"examples/graphics/simple",id:"examples/graphics/simple",title:"Simple",description:"",source:"@site/docs/examples/graphics/simple.md",sourceDirName:"examples/graphics",slug:"/examples/graphics/simple",permalink:"/examples/graphics/simple",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Simple"},sidebar:"examplesSidebar",previous:{title:"Web Font",permalink:"/examples/text/web-font"},next:{title:"Advanced",permalink:"/examples/graphics/advanced"}},d={},m=[],c={toc:m};function u(e){let{components:i,...s}=e;return(0,p.kt)("wrapper",(0,t.Z)({},c,s,{components:i,mdxType:"MDXLayout"}),(0,p.kt)(a.Z,{id:"graphics.simple",pixiVersion:l,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7a33d139.66cc1540.js b/assets/js/7a33d139.66cc1540.js deleted file mode 100644 index 7c134ff77..000000000 --- a/assets/js/7a33d139.66cc1540.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6577],{4126:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>n,metadata:()=>l,toc:()=>h});var d=t(7462),a=(t(7294),t(3905)),i=t(8010),r=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Textured Mesh Advanced"},o=void 0,l={unversionedId:"examples/mesh-and-shaders/textured-mesh-advanced",id:"examples/mesh-and-shaders/textured-mesh-advanced",title:"Textured Mesh Advanced",description:"",source:"@site/docs/examples/mesh-and-shaders/textured-mesh-advanced.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/textured-mesh-advanced",permalink:"/examples/mesh-and-shaders/textured-mesh-advanced",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Textured Mesh Advanced"},sidebar:"examplesSidebar",previous:{title:"Textured Mesh Basic",permalink:"/examples/mesh-and-shaders/textured-mesh-basic"},next:{title:"Triangle",permalink:"/examples/mesh-and-shaders/triangle"}},p={},h=[],m={toc:h};function u(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,d.Z)({},m,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(i.Z,{id:"meshAndShaders.texturedMeshAdvanced",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7a33d139.b9ec74c4.js b/assets/js/7a33d139.b9ec74c4.js new file mode 100644 index 000000000..dc7bcdb7c --- /dev/null +++ b/assets/js/7a33d139.b9ec74c4.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6577],{4126:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>u,frontMatter:()=>n,metadata:()=>l,toc:()=>h});var d=t(7462),a=(t(7294),t(3905)),i=t(8010),r=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Textured Mesh Advanced"},o=void 0,l={unversionedId:"examples/mesh-and-shaders/textured-mesh-advanced",id:"examples/mesh-and-shaders/textured-mesh-advanced",title:"Textured Mesh Advanced",description:"",source:"@site/docs/examples/mesh-and-shaders/textured-mesh-advanced.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/textured-mesh-advanced",permalink:"/8.x/examples/mesh-and-shaders/textured-mesh-advanced",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Textured Mesh Advanced"},sidebar:"examplesSidebar",previous:{title:"Textured Mesh Basic",permalink:"/8.x/examples/mesh-and-shaders/textured-mesh-basic"},next:{title:"Triangle",permalink:"/8.x/examples/mesh-and-shaders/triangle"}},p={},h=[],m={toc:h};function u(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,d.Z)({},m,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(i.Z,{id:"meshAndShaders.texturedMeshAdvanced",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7b84543e.06d9898a.js b/assets/js/7b84543e.06d9898a.js new file mode 100644 index 000000000..7554638c9 --- /dev/null +++ b/assets/js/7b84543e.06d9898a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5114],{5835:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={},o="Interaction",l={unversionedId:"guides/components/interaction",id:"guides/components/interaction",title:"Interaction",description:"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.",source:"@site/docs/guides/components/interaction.md",sourceDirName:"guides/components",slug:"/guides/components/interaction",permalink:"/8.x/guides/components/interaction",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/interaction.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Graphics",permalink:"/8.x/guides/components/graphics"},next:{title:"Sprites",permalink:"/8.x/guides/components/sprites"}},d={},p=[{value:"Event Modes",id:"event-modes",level:2},{value:"Event Types",id:"event-types",level:2},{value:"Enabling Interaction",id:"enabling-interaction",level:2},{value:"Checking if Object is Interactive",id:"checking-if-object-is-interactive",level:3},{value:"Use Pointer Events",id:"use-pointer-events",level:2},{value:"Optimization",id:"optimization",level:2}],s={toc:p};function m(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"interaction"},"Interaction"),(0,i.kt)("p",null,"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent."),(0,i.kt)("h2",{id:"event-modes"},"Event Modes"),(0,i.kt)("p",null,"The new event-based system that replaced ",(0,i.kt)("inlineCode",{parentName:"p"},"InteractionManager")," from v6 has expanded the definition of what a Container means to be interactive. With this we have introduced ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," which allows you to control how an object responds to interaction events. This is similar to the ",(0,i.kt)("inlineCode",{parentName:"p"},"interactive")," property in v6 but with more options."),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"eventMode"),(0,i.kt)("th",{parentName:"tr",align:null},"Description"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"none")),(0,i.kt)("td",{parentName:"tr",align:null},"Ignores all interaction events, similar to CSS's ",(0,i.kt)("inlineCode",{parentName:"td"},"pointer-events: none"),", good optimization for non-interactive children")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"passive")),(0,i.kt)("td",{parentName:"tr",align:null},"Does not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. This is default eventMode for all containers")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"auto")),(0,i.kt)("td",{parentName:"tr",align:null},"Does not emit events and but is hit tested if parent is interactive. Same as ",(0,i.kt)("inlineCode",{parentName:"td"},"interactive = false")," in v7")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"static")),(0,i.kt)("td",{parentName:"tr",align:null},"Emit events and is hit tested. Same as ",(0,i.kt)("inlineCode",{parentName:"td"},"interaction = true")," in v7, useful for objects like buttons that do not move.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"dynamic")),(0,i.kt)("td",{parentName:"tr",align:null},"Emits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.")))),(0,i.kt)("h2",{id:"event-types"},"Event Types"),(0,i.kt)("p",null,"PixiJS supports the following event types:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Event Type"),(0,i.kt)("th",{parentName:"tr",align:null},"Description"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointercancel")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released outside the display object that initially registered a pointerdown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerdown")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerenter")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device enters the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerleave")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device leaves the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointermove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved while over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globalpointermove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerout")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved off the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerover")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved onto the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointertap")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is tapped twice on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerup")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerupoutside")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released outside the display object that initially registered a pointerdown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mousedown ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseenter")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor enters the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseleave")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor leaves the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mousemove ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved while over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globalmousemove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseout ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved off the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseover ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved onto the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseup ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseupoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is released outside the display object that initially registered a mousedown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"click ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is clicked (pressed and released) over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchcancel ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed outside of the display object that initially registered a touchstart.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchend ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed from the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchendoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed outside of the display object that initially registered a touchstart.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchmove ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is moved along the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globaltouchmove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchstart ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is placed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"tap ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is tapped twice on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"wheel ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse wheel is spun over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightclick ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is clicked (pressed and released) over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightdown ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightup ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightupoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is released outside the display object that initially registered a rightdown.")))),(0,i.kt)("h2",{id:"enabling-interaction"},"Enabling Interaction"),(0,i.kt)("p",null,"Any Container-derived object (Sprite, Container, etc.) can become interactive simply by setting its ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/events/click"},"interaction example code"),"."),(0,i.kt)("p",null,"To respond to clicks and taps, bind to the events fired on the object, like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-javascript"},"let sprite = Sprite.from('/some/texture.png');\nsprite.on('pointerdown', (event) => { alert('clicked!'); });\nsprite.eventMode = 'static';\n")),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Container.html"},"Container")," for the list of interaction events supported."),(0,i.kt)("h3",{id:"checking-if-object-is-interactive"},"Checking if Object is Interactive"),(0,i.kt)("p",null,"You can check if an object is interactive by calling the ",(0,i.kt)("inlineCode",{parentName:"p"},"isInteractive")," property. This will return true if ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," is set to ",(0,i.kt)("inlineCode",{parentName:"p"},"static")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"dynamic"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-javascript"},"if (sprite.isInteractive()) {\n // sprite is interactive\n}\n")),(0,i.kt)("h2",{id:"use-pointer-events"},"Use Pointer Events"),(0,i.kt)("p",null,"PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for ",(0,i.kt)("em",{parentName:"p"},"both"),"."),(0,i.kt)("p",null,"What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with ",(0,i.kt)("em",{parentName:"p"},"either")," mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events."),(0,i.kt)("h2",{id:"optimization"},"Optimization"),(0,i.kt)("p",null,"Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named ",(0,i.kt)("inlineCode",{parentName:"p"},"interactiveChildren"),". If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set ",(0,i.kt)("inlineCode",{parentName:"p"},"background.interactiveChildren = false")," for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"EventSystem")," can also be customised to be more performant:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},"const app = new Application({\n eventMode: 'passive',\n eventFeatures: {\n move: true,\n /** disables the global move events which can be very expensive in large scenes */\n globalMove: false,\n click: true,\n wheel: true,\n }\n});\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7b84543e.f39f5f67.js b/assets/js/7b84543e.f39f5f67.js deleted file mode 100644 index 585e20f3c..000000000 --- a/assets/js/7b84543e.f39f5f67.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5114],{5835:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>d,contentTitle:()=>o,default:()=>m,frontMatter:()=>r,metadata:()=>l,toc:()=>p});var a=n(7462),i=(n(7294),n(3905));const r={},o="Interaction",l={unversionedId:"guides/components/interaction",id:"guides/components/interaction",title:"Interaction",description:"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.",source:"@site/docs/guides/components/interaction.md",sourceDirName:"guides/components",slug:"/guides/components/interaction",permalink:"/guides/components/interaction",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/interaction.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Graphics",permalink:"/guides/components/graphics"},next:{title:"Sprites",permalink:"/guides/components/sprites"}},d={},p=[{value:"Event Modes",id:"event-modes",level:2},{value:"Event Types",id:"event-types",level:2},{value:"Enabling Interaction",id:"enabling-interaction",level:2},{value:"Checking if Object is Interactive",id:"checking-if-object-is-interactive",level:3},{value:"Use Pointer Events",id:"use-pointer-events",level:2},{value:"Optimization",id:"optimization",level:2}],s={toc:p};function m(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},s,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"interaction"},"Interaction"),(0,i.kt)("p",null,"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent."),(0,i.kt)("h2",{id:"event-modes"},"Event Modes"),(0,i.kt)("p",null,"The new event-based system that replaced ",(0,i.kt)("inlineCode",{parentName:"p"},"InteractionManager")," from v6 has expanded the definition of what a Container means to be interactive. With this we have introduced ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," which allows you to control how an object responds to interaction events. This is similar to the ",(0,i.kt)("inlineCode",{parentName:"p"},"interactive")," property in v6 but with more options."),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"eventMode"),(0,i.kt)("th",{parentName:"tr",align:null},"Description"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"none")),(0,i.kt)("td",{parentName:"tr",align:null},"Ignores all interaction events, similar to CSS's ",(0,i.kt)("inlineCode",{parentName:"td"},"pointer-events: none"),", good optimization for non-interactive children")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"passive")),(0,i.kt)("td",{parentName:"tr",align:null},"Does not emit events and ignores hit testing on itself but does allow for events and hit testing only its interactive children. This is default eventMode for all containers")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"auto")),(0,i.kt)("td",{parentName:"tr",align:null},"Does not emit events and but is hit tested if parent is interactive. Same as ",(0,i.kt)("inlineCode",{parentName:"td"},"interactive = false")," in v7")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"static")),(0,i.kt)("td",{parentName:"tr",align:null},"Emit events and is hit tested. Same as ",(0,i.kt)("inlineCode",{parentName:"td"},"interaction = true")," in v7, useful for objects like buttons that do not move.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"dynamic")),(0,i.kt)("td",{parentName:"tr",align:null},"Emits events and is hit tested but will also receive mock interaction events fired from a ticker to allow for interaction when the mouse isn't moving. This is useful for elements that independently moving or animating.")))),(0,i.kt)("h2",{id:"event-types"},"Event Types"),(0,i.kt)("p",null,"PixiJS supports the following event types:"),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"Event Type"),(0,i.kt)("th",{parentName:"tr",align:null},"Description"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointercancel")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released outside the display object that initially registered a pointerdown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerdown")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerenter")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device enters the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerleave")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device leaves the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointermove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved while over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globalpointermove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerout")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved off the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerover")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is moved onto the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointertap")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device is tapped twice on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerup")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"pointerupoutside")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a pointer device button is released outside the display object that initially registered a pointerdown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mousedown ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseenter")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor enters the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseleave")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor leaves the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mousemove ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved while over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globalmousemove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseout ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved off the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseover ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when the mouse cursor is moved onto the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseup ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"mouseupoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is released outside the display object that initially registered a mousedown.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"click ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse button is clicked (pressed and released) over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchcancel ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed outside of the display object that initially registered a touchstart.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchend ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed from the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchendoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is removed outside of the display object that initially registered a touchstart.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchmove ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is moved along the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"globaltouchmove")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is moved, regardless of hit-testing the current object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"touchstart ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is placed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"tap ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a touch point is tapped twice on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"wheel ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a mouse wheel is spun over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightclick ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is clicked (pressed and released) over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightdown ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is pressed on the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightup ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is released over the display object.")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},(0,i.kt)("inlineCode",{parentName:"td"},"rightupoutside ")),(0,i.kt)("td",{parentName:"tr",align:null},"Fired when a right mouse button is released outside the display object that initially registered a rightdown.")))),(0,i.kt)("h2",{id:"enabling-interaction"},"Enabling Interaction"),(0,i.kt)("p",null,"Any Container-derived object (Sprite, Container, etc.) can become interactive simply by setting its ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," property to any of the eventModes listed above. Doing so will cause the object to emit interaction events that can be responded to in order to drive your project's behavior."),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"../../examples/events/click"},"interaction example code"),"."),(0,i.kt)("p",null,"To respond to clicks and taps, bind to the events fired on the object, like so:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-javascript"},"let sprite = Sprite.from('/some/texture.png');\nsprite.on('pointerdown', (event) => { alert('clicked!'); });\nsprite.eventMode = 'static';\n")),(0,i.kt)("p",null,"Check out the ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Container.html"},"Container")," for the list of interaction events supported."),(0,i.kt)("h3",{id:"checking-if-object-is-interactive"},"Checking if Object is Interactive"),(0,i.kt)("p",null,"You can check if an object is interactive by calling the ",(0,i.kt)("inlineCode",{parentName:"p"},"isInteractive")," property. This will return true if ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," is set to ",(0,i.kt)("inlineCode",{parentName:"p"},"static")," or ",(0,i.kt)("inlineCode",{parentName:"p"},"dynamic"),"."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-javascript"},"if (sprite.isInteractive()) {\n // sprite is interactive\n}\n")),(0,i.kt)("h2",{id:"use-pointer-events"},"Use Pointer Events"),(0,i.kt)("p",null,"PixiJS supports three types of interaction events - mouse, touch and pointer. Mouse events are fired by mouse movement, clicks etc. Touch events are fired for touch-capable devices. And pointer events are fired for ",(0,i.kt)("em",{parentName:"p"},"both"),"."),(0,i.kt)("p",null,"What this means is that, in many cases, you can write your project to use pointer events and it will just work when used with ",(0,i.kt)("em",{parentName:"p"},"either")," mouse or touch input. Given that, the only reason to use non-pointer events is to support different modes of operation based on input type or to support multi-touch interaction. In all other cases, prefer pointer events."),(0,i.kt)("h2",{id:"optimization"},"Optimization"),(0,i.kt)("p",null,"Hit testing requires walking the full object tree, which in complex projects can become an optimization bottleneck. To mitigate this issue, PixiJS Container-derived objects have a property named ",(0,i.kt)("inlineCode",{parentName:"p"},"interactiveChildren"),". If you have Containers or other objects with complex child trees that you know will never be interactive, you can set this property to ",(0,i.kt)("inlineCode",{parentName:"p"},"false")," and the hit testing algorithm will skip those children when checking for hover and click events. As an example, if you were building a side-scrolling game, you would probably want to set ",(0,i.kt)("inlineCode",{parentName:"p"},"background.interactiveChildren = false")," for your background layer with rocks, clouds, flowers, etc. Doing so would speed up hit testing substantially due to the number of unclickable child objects the background layer would contain."),(0,i.kt)("p",null,"The ",(0,i.kt)("inlineCode",{parentName:"p"},"EventSystem")," can also be customised to be more performant:"),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-js"},"const app = new Application({\n eventMode: 'passive',\n eventFeatures: {\n move: true,\n /** disables the global move events which can be very expensive in large scenes */\n globalMove: false,\n click: true,\n wheel: true,\n }\n});\n")))}m.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7c92c4a9.20a9f2ad.js b/assets/js/7c92c4a9.20a9f2ad.js deleted file mode 100644 index 821ff10f5..000000000 --- a/assets/js/7c92c4a9.20a9f2ad.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6550],{3323:(t,e,i)=>{i.r(e),i.d(e,{assets:()=>n,contentTitle:()=>x,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),p=i(8010),o=i(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Bitmap Text"},x=void 0,l={unversionedId:"examples/text/bitmap-text",id:"examples/text/bitmap-text",title:"Bitmap Text",description:"",source:"@site/docs/examples/text/bitmap-text.md",sourceDirName:"examples/text",slug:"/examples/text/bitmap-text",permalink:"/examples/text/bitmap-text",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Bitmap Text"},sidebar:"examplesSidebar",previous:{title:"Pixi Text",permalink:"/examples/text/pixi-text"},next:{title:"From Font",permalink:"/examples/text/from-font"}},n={},d=[],m={toc:d};function u(t){let{components:e,...i}=t;return(0,a.kt)("wrapper",(0,s.Z)({},m,i,{components:e,mdxType:"MDXLayout"}),(0,a.kt)(p.Z,{id:"text.bitmapText",pixiVersion:o,mdxType:"Example"}))}u.isMDXComponent=!0},7949:t=>{t.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7c92c4a9.5357a59f.js b/assets/js/7c92c4a9.5357a59f.js new file mode 100644 index 000000000..3b238675b --- /dev/null +++ b/assets/js/7c92c4a9.5357a59f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[6550],{3323:(t,e,i)=>{i.r(e),i.d(e,{assets:()=>n,contentTitle:()=>r,default:()=>u,frontMatter:()=>x,metadata:()=>l,toc:()=>d});var s=i(7462),a=(i(7294),i(3905)),p=i(8010),o=i(7949);const x={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Bitmap Text"},r=void 0,l={unversionedId:"examples/text/bitmap-text",id:"examples/text/bitmap-text",title:"Bitmap Text",description:"",source:"@site/docs/examples/text/bitmap-text.md",sourceDirName:"examples/text",slug:"/examples/text/bitmap-text",permalink:"/8.x/examples/text/bitmap-text",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Bitmap Text"},sidebar:"examplesSidebar",previous:{title:"Pixi Text",permalink:"/8.x/examples/text/pixi-text"},next:{title:"From Font",permalink:"/8.x/examples/text/from-font"}},n={},d=[],m={toc:d};function u(t){let{components:e,...i}=t;return(0,a.kt)("wrapper",(0,s.Z)({},m,i,{components:e,mdxType:"MDXLayout"}),(0,a.kt)(p.Z,{id:"text.bitmapText",pixiVersion:o,mdxType:"Example"}))}u.isMDXComponent=!0},7949:t=>{t.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7dfda297.558d96fe.js b/assets/js/7dfda297.558d96fe.js deleted file mode 100644 index 9c6af1da7..000000000 --- a/assets/js/7dfda297.558d96fe.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3421],{4970:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var a=n(7462),o=(n(7294),n(3905));const i={},r="Scene Graph",s={unversionedId:"guides/basics/scene-graph",id:"guides/basics/scene-graph",title:"Scene Graph",description:"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.",source:"@site/docs/guides/basics/scene-graph.md",sourceDirName:"guides/basics",slug:"/guides/basics/scene-graph",permalink:"/guides/basics/scene-graph",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/scene-graph.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Render Loop",permalink:"/guides/basics/render-loop"},next:{title:"Render Groups",permalink:"/guides/advanced/render-groups"}},l={},d=[{value:"The Scene Graph Is a Tree",id:"the-scene-graph-is-a-tree",level:2},{value:"Parents and Children",id:"parents-and-children",level:2},{value:"Render Order",id:"render-order",level:2},{value:"RenderGroups",id:"rendergroups",level:2},{value:"Culling",id:"culling",level:2},{value:"Local vs Global Coordinates",id:"local-vs-global-coordinates",level:2},{value:"Global vs Screen Coordinates",id:"global-vs-screen-coordinates",level:2}],p={toc:d};function c(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"scene-graph"},"Scene Graph"),(0,o.kt)("p",null,"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."),(0,o.kt)("h2",{id:"the-scene-graph-is-a-tree"},"The Scene Graph Is a Tree"),(0,o.kt)("p",null,"The scene graph's root node is a container maintained by the application, and referenced with ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"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."),(0,o.kt)("p",null,"(A helpful tool for exploring your project is the ",(0,o.kt)("a",{parentName:"p",href:"https://chrome.google.com/webstore/detail/pixijs-devtools/aamddddknhcagpehecnhphigffljadon"},"Pixi.js devtools plugin")," for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)"),(0,o.kt)("h2",{id:"parents-and-children"},"Parents and Children"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ts"},"// Create the application helper and add its render target to the page\nconst app = new Application();\nawait app.init({ width: 640, height: 360 })\ndocument.body.appendChild(app.canvas);\n\n// Add a container to center our sprite stack on the page\nconst container = new Container({\n x:app.screen.width / 2,\n y:app.screen.height / 2;\n});\n\napp.stage.addChild(container);\n\n// load the texture\nawait Assets.load('assets/images/sample.png');\n\n// Create the 3 sprites, each a child of the last\nconst sprites = [];\nlet parent = container;\nfor (let i = 0; i < 3; i++) {\n let wrapper = new Container();\n let sprite = Sprite.from('assets/images/sample.png');\n sprite.anchor.set(0.5);\n wrapper.addChild(sprite);\n parent.addChild(wrapper);\n sprites.push(wrapper);\n parent = wrapper;\n}\n\n// Set all sprite's properties to the same value, animated over time\nlet elapsed = 0.0;\napp.ticker.add((delta) => {\n elapsed += delta / 60;\n const amount = Math.sin(elapsed);\n const scale = 1.0 + 0.25 * amount;\n const alpha = 0.75 + 0.25 * amount;\n const angle = 40 * amount;\n const x = 75 * amount;\n for (let i = 0; i < sprites.length; i++) {\n const sprite = sprites[i];\n sprite.scale.set(scale);\n sprite.alpha = alpha;\n sprite.angle = angle;\n sprite.x = x;\n }\n});\n")),(0,o.kt)("p",null,"The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's ",(0,o.kt)("inlineCode",{parentName:"p"},"worldTransform")," property. Similarly, the cumulative alpha value is stored in the ",(0,o.kt)("inlineCode",{parentName:"p"},"worldAlpha")," property."),(0,o.kt)("h2",{id:"render-order"},"Render Order"),(0,o.kt)("p",null,"So we have a tree of things to draw. Who gets drawn first?"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"Check out this example, with two parent objects A & D, and two children B & C under A:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create the application helper and add its render target to the page\nconst app = new Application();\nawait app.init({ width: 640, height: 360 })\ndocument.body.appendChild(app.canvas);\n\n// Label showing scene graph hierarchy\nconst label = new Text({\n text:'Scene Graph:\\n\\napp.stage\\n \u2517 A\\n \u2517 B\\n \u2517 C\\n \u2517 D',\n style:{fill: '#ffffff'},\n position: {x: 300, y: 100}\n});\n\napp.stage.addChild(label);\n\n// Helper function to create a block of color with a letter\nconst letters = [];\nfunction addLetter(letter, parent, color, pos) {\n const bg = new Sprite(Texture.WHITE);\n bg.width = 100;\n bg.height = 100;\n bg.tint = color;\n\n const text = new Text({\n text:letter,\n style:{fill: \"#ffffff\"}\n });\n\n text.anchor.set(0.5);\n text.position = {x: 50, y: 50};\n\n const container = new Container();\n container.position = pos;\n container.visible = false;\n container.addChild(bg, text);\n parent.addChild(container);\n\n letters.push(container);\n return container;\n}\n\n// Define 4 letters\nlet a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});\nlet b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});\nlet c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});\nlet d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});\n\n// Display them over time, in order\nlet elapsed = 0.0;\napp.ticker.add((delta) => {\n elapsed += delta / 60.0;\n if (elapsed >= letters.length) { elapsed = 0.0; }\n for (let i = 0; i < letters.length; i ++) {\n letters[i].visible = elapsed >= i;\n }\n});\n")),(0,o.kt)("p",null,"If you'd like to re-order a child object, you can use ",(0,o.kt)("inlineCode",{parentName:"p"},"setChildIndex()"),". To add a child at a given point in a parent's list, use ",(0,o.kt)("inlineCode",{parentName:"p"},"addChildAt()"),". Finally, you can enable automatic sorting of an object's children using the ",(0,o.kt)("inlineCode",{parentName:"p"},"sortableChildren")," option combined with setting the ",(0,o.kt)("inlineCode",{parentName:"p"},"zIndex")," property on each child."),(0,o.kt)("h2",{id:"rendergroups"},"RenderGroups"),(0,o.kt)("p",null,"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 ",(0,o.kt)("a",{parentName:"p",href:"../advanced/render-groups"},"RenderGroups overview")),(0,o.kt)("h2",{id:"culling"},"Culling"),(0,o.kt)("p",null,"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 ",(0,o.kt)("em",{parentName:"p"},"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."),(0,o.kt)("p",null,"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"renderable")," to false on any object that doesn't need to be drawn."),(0,o.kt)("h2",{id:"local-vs-global-coordinates"},"Local vs Global Coordinates"),(0,o.kt)("p",null,"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 ",(0,o.kt)("em",{parentName:"p"},"from its parent"),"."),(0,o.kt)("p",null,'We call these two coordinate systems "global" and "local" coordinates. When you use ',(0,o.kt)("inlineCode",{parentName:"p"},"position.set(x, y)")," on an object, you're always working in local coordinates, relative to the object's parent."),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"To convert from local to global coordinates, you use the ",(0,o.kt)("inlineCode",{parentName:"p"},"toGlobal()")," function. Here's a sample usage:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Get the global position of an object, relative to the top-left of the screen\nlet globalPos = obj.toGlobal(new Point(0,0));\n")),(0,o.kt)("p",null,"This snippet will set ",(0,o.kt)("inlineCode",{parentName:"p"},"globalPos")," to be the global coordinates for the child object, relative to ","[0, 0]"," in the global coordinate system."),(0,o.kt)("h2",{id:"global-vs-screen-coordinates"},"Global vs Screen Coordinates"),(0,o.kt)("p",null,'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.'),(0,o.kt)("p",null,"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("em",{parentName:"p"},"not"),", resulting in a mis-match between world coordinates and screen coordinates."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7dfda297.b9f623af.js b/assets/js/7dfda297.b9f623af.js new file mode 100644 index 000000000..f1e438f6c --- /dev/null +++ b/assets/js/7dfda297.b9f623af.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3421],{4970:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>l,contentTitle:()=>r,default:()=>c,frontMatter:()=>i,metadata:()=>s,toc:()=>d});var a=n(7462),o=(n(7294),n(3905));const i={},r="Scene Graph",s={unversionedId:"guides/basics/scene-graph",id:"guides/basics/scene-graph",title:"Scene Graph",description:"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.",source:"@site/docs/guides/basics/scene-graph.md",sourceDirName:"guides/basics",slug:"/guides/basics/scene-graph",permalink:"/8.x/guides/basics/scene-graph",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/scene-graph.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Render Loop",permalink:"/8.x/guides/basics/render-loop"},next:{title:"Render Groups",permalink:"/8.x/guides/advanced/render-groups"}},l={},d=[{value:"The Scene Graph Is a Tree",id:"the-scene-graph-is-a-tree",level:2},{value:"Parents and Children",id:"parents-and-children",level:2},{value:"Render Order",id:"render-order",level:2},{value:"RenderGroups",id:"rendergroups",level:2},{value:"Culling",id:"culling",level:2},{value:"Local vs Global Coordinates",id:"local-vs-global-coordinates",level:2},{value:"Global vs Screen Coordinates",id:"global-vs-screen-coordinates",level:2}],p={toc:d};function c(e){let{components:t,...n}=e;return(0,o.kt)("wrapper",(0,a.Z)({},p,n,{components:t,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"scene-graph"},"Scene Graph"),(0,o.kt)("p",null,"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."),(0,o.kt)("h2",{id:"the-scene-graph-is-a-tree"},"The Scene Graph Is a Tree"),(0,o.kt)("p",null,"The scene graph's root node is a container maintained by the application, and referenced with ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"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."),(0,o.kt)("p",null,"(A helpful tool for exploring your project is the ",(0,o.kt)("a",{parentName:"p",href:"https://chrome.google.com/webstore/detail/pixijs-devtools/aamddddknhcagpehecnhphigffljadon"},"Pixi.js devtools plugin")," for Chrome, which allows you to view and manipulate the scene graph in real time as it's running!)"),(0,o.kt)("h2",{id:"parents-and-children"},"Parents and Children"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-ts"},"// Create the application helper and add its render target to the page\nconst app = new Application();\nawait app.init({ width: 640, height: 360 })\ndocument.body.appendChild(app.canvas);\n\n// Add a container to center our sprite stack on the page\nconst container = new Container({\n x:app.screen.width / 2,\n y:app.screen.height / 2;\n});\n\napp.stage.addChild(container);\n\n// load the texture\nawait Assets.load('assets/images/sample.png');\n\n// Create the 3 sprites, each a child of the last\nconst sprites = [];\nlet parent = container;\nfor (let i = 0; i < 3; i++) {\n let wrapper = new Container();\n let sprite = Sprite.from('assets/images/sample.png');\n sprite.anchor.set(0.5);\n wrapper.addChild(sprite);\n parent.addChild(wrapper);\n sprites.push(wrapper);\n parent = wrapper;\n}\n\n// Set all sprite's properties to the same value, animated over time\nlet elapsed = 0.0;\napp.ticker.add((delta) => {\n elapsed += delta / 60;\n const amount = Math.sin(elapsed);\n const scale = 1.0 + 0.25 * amount;\n const alpha = 0.75 + 0.25 * amount;\n const angle = 40 * amount;\n const x = 75 * amount;\n for (let i = 0; i < sprites.length; i++) {\n const sprite = sprites[i];\n sprite.scale.set(scale);\n sprite.alpha = alpha;\n sprite.angle = angle;\n sprite.x = x;\n }\n});\n")),(0,o.kt)("p",null,"The cumulative translation, rotation, scale and skew of any given node in the scene graph is stored in the object's ",(0,o.kt)("inlineCode",{parentName:"p"},"worldTransform")," property. Similarly, the cumulative alpha value is stored in the ",(0,o.kt)("inlineCode",{parentName:"p"},"worldAlpha")," property."),(0,o.kt)("h2",{id:"render-order"},"Render Order"),(0,o.kt)("p",null,"So we have a tree of things to draw. Who gets drawn first?"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"Check out this example, with two parent objects A & D, and two children B & C under A:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create the application helper and add its render target to the page\nconst app = new Application();\nawait app.init({ width: 640, height: 360 })\ndocument.body.appendChild(app.canvas);\n\n// Label showing scene graph hierarchy\nconst label = new Text({\n text:'Scene Graph:\\n\\napp.stage\\n \u2517 A\\n \u2517 B\\n \u2517 C\\n \u2517 D',\n style:{fill: '#ffffff'},\n position: {x: 300, y: 100}\n});\n\napp.stage.addChild(label);\n\n// Helper function to create a block of color with a letter\nconst letters = [];\nfunction addLetter(letter, parent, color, pos) {\n const bg = new Sprite(Texture.WHITE);\n bg.width = 100;\n bg.height = 100;\n bg.tint = color;\n\n const text = new Text({\n text:letter,\n style:{fill: \"#ffffff\"}\n });\n\n text.anchor.set(0.5);\n text.position = {x: 50, y: 50};\n\n const container = new Container();\n container.position = pos;\n container.visible = false;\n container.addChild(bg, text);\n parent.addChild(container);\n\n letters.push(container);\n return container;\n}\n\n// Define 4 letters\nlet a = addLetter('A', app.stage, 0xff0000, {x: 100, y: 100});\nlet b = addLetter('B', a, 0x00ff00, {x: 20, y: 20});\nlet c = addLetter('C', a, 0x0000ff, {x: 20, y: 40});\nlet d = addLetter('D', app.stage, 0xff8800, {x: 140, y: 100});\n\n// Display them over time, in order\nlet elapsed = 0.0;\napp.ticker.add((delta) => {\n elapsed += delta / 60.0;\n if (elapsed >= letters.length) { elapsed = 0.0; }\n for (let i = 0; i < letters.length; i ++) {\n letters[i].visible = elapsed >= i;\n }\n});\n")),(0,o.kt)("p",null,"If you'd like to re-order a child object, you can use ",(0,o.kt)("inlineCode",{parentName:"p"},"setChildIndex()"),". To add a child at a given point in a parent's list, use ",(0,o.kt)("inlineCode",{parentName:"p"},"addChildAt()"),". Finally, you can enable automatic sorting of an object's children using the ",(0,o.kt)("inlineCode",{parentName:"p"},"sortableChildren")," option combined with setting the ",(0,o.kt)("inlineCode",{parentName:"p"},"zIndex")," property on each child."),(0,o.kt)("h2",{id:"rendergroups"},"RenderGroups"),(0,o.kt)("p",null,"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 ",(0,o.kt)("a",{parentName:"p",href:"../advanced/render-groups"},"RenderGroups overview")),(0,o.kt)("h2",{id:"culling"},"Culling"),(0,o.kt)("p",null,"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 ",(0,o.kt)("em",{parentName:"p"},"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."),(0,o.kt)("p",null,"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"renderable")," to false on any object that doesn't need to be drawn."),(0,o.kt)("h2",{id:"local-vs-global-coordinates"},"Local vs Global Coordinates"),(0,o.kt)("p",null,"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 ",(0,o.kt)("em",{parentName:"p"},"from its parent"),"."),(0,o.kt)("p",null,'We call these two coordinate systems "global" and "local" coordinates. When you use ',(0,o.kt)("inlineCode",{parentName:"p"},"position.set(x, y)")," on an object, you're always working in local coordinates, relative to the object's parent."),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"To convert from local to global coordinates, you use the ",(0,o.kt)("inlineCode",{parentName:"p"},"toGlobal()")," function. Here's a sample usage:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Get the global position of an object, relative to the top-left of the screen\nlet globalPos = obj.toGlobal(new Point(0,0));\n")),(0,o.kt)("p",null,"This snippet will set ",(0,o.kt)("inlineCode",{parentName:"p"},"globalPos")," to be the global coordinates for the child object, relative to ","[0, 0]"," in the global coordinate system."),(0,o.kt)("h2",{id:"global-vs-screen-coordinates"},"Global vs Screen Coordinates"),(0,o.kt)("p",null,'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.'),(0,o.kt)("p",null,"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 ",(0,o.kt)("inlineCode",{parentName:"p"},"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 ",(0,o.kt)("em",{parentName:"p"},"not"),", resulting in a mis-match between world coordinates and screen coordinates."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/7f2b0826.4a12eb21.js b/assets/js/7f2b0826.4a12eb21.js deleted file mode 100644 index 6648a801d..000000000 --- a/assets/js/7f2b0826.4a12eb21.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5025],{1458:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>u,contentTitle:()=>p,default:()=>m,frontMatter:()=>o,metadata:()=>n,toc:()=>d});var t=i(7462),r=(i(7294),i(3905)),l=i(8010),a=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Blur"},p=void 0,n={unversionedId:"examples/filters-basic/blur",id:"examples/filters-basic/blur",title:"Blur",description:"",source:"@site/docs/examples/filters-basic/blur.md",sourceDirName:"examples/filters-basic",slug:"/examples/filters-basic/blur",permalink:"/examples/filters-basic/blur",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Blur"},sidebar:"examplesSidebar",previous:{title:"Filter",permalink:"/examples/masks/filter"},next:{title:"Color Matrix",permalink:"/examples/filters-basic/color-matrix"}},u={},d=[],c={toc:d};function m(e){let{components:s,...i}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,i,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(l.Z,{id:"filtersBasic.blur",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/7f2b0826.8d8d00db.js b/assets/js/7f2b0826.8d8d00db.js new file mode 100644 index 000000000..ca5a7c799 --- /dev/null +++ b/assets/js/7f2b0826.8d8d00db.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5025],{1458:(e,s,i)=>{i.r(s),i.d(s,{assets:()=>u,contentTitle:()=>p,default:()=>m,frontMatter:()=>o,metadata:()=>n,toc:()=>d});var t=i(7462),r=(i(7294),i(3905)),l=i(8010),a=i(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Blur"},p=void 0,n={unversionedId:"examples/filters-basic/blur",id:"examples/filters-basic/blur",title:"Blur",description:"",source:"@site/docs/examples/filters-basic/blur.md",sourceDirName:"examples/filters-basic",slug:"/examples/filters-basic/blur",permalink:"/8.x/examples/filters-basic/blur",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:0,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:0,custom_edit_url:null,title:"Blur"},sidebar:"examplesSidebar",previous:{title:"Filter",permalink:"/8.x/examples/masks/filter"},next:{title:"Color Matrix",permalink:"/8.x/examples/filters-basic/color-matrix"}},u={},d=[],c={toc:d};function m(e){let{components:s,...i}=e;return(0,r.kt)("wrapper",(0,t.Z)({},c,i,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(l.Z,{id:"filtersBasic.blur",pixiVersion:a,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/82290857.1826ad49.js b/assets/js/82290857.1826ad49.js new file mode 100644 index 000000000..a6ce4fad5 --- /dev/null +++ b/assets/js/82290857.1826ad49.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4760],{1774:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>d,contentTitle:()=>p,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>c});var i=t(7462),a=(t(7294),t(3905)),n=t(8010),o=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Async"},p=void 0,r={unversionedId:"examples/assets/async",id:"examples/assets/async",title:"Async",description:"",source:"@site/docs/examples/assets/async.md",sourceDirName:"examples/assets",slug:"/examples/assets/async",permalink:"/8.x/examples/assets/async",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Async"},sidebar:"examplesSidebar",previous:{title:"Promise",permalink:"/8.x/examples/assets/promise"},next:{title:"Multiple",permalink:"/8.x/examples/assets/multiple"}},d={},c=[],u={toc:c};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"assets.async",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/82290857.49174b98.js b/assets/js/82290857.49174b98.js deleted file mode 100644 index 410c18fc1..000000000 --- a/assets/js/82290857.49174b98.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4760],{1774:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>d,contentTitle:()=>p,default:()=>m,frontMatter:()=>l,metadata:()=>r,toc:()=>c});var i=t(7462),a=(t(7294),t(3905)),n=t(8010),o=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Async"},p=void 0,r={unversionedId:"examples/assets/async",id:"examples/assets/async",title:"Async",description:"",source:"@site/docs/examples/assets/async.md",sourceDirName:"examples/assets",slug:"/examples/assets/async",permalink:"/examples/assets/async",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Async"},sidebar:"examplesSidebar",previous:{title:"Promise",permalink:"/examples/assets/promise"},next:{title:"Multiple",permalink:"/examples/assets/multiple"}},d={},c=[],u={toc:c};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},u,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(n.Z,{id:"assets.async",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/83f744ee.9afc9c02.js b/assets/js/83f744ee.9afc9c02.js deleted file mode 100644 index 96d099a6a..000000000 --- a/assets/js/83f744ee.9afc9c02.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8790],{5025:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>a,metadata:()=>l,toc:()=>m});var i=s(7462),o=(s(7294),s(3905)),n=s(8010),r=s(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"From Font"},p=void 0,l={unversionedId:"examples/text/from-font",id:"examples/text/from-font",title:"From Font",description:"",source:"@site/docs/examples/text/from-font.md",sourceDirName:"examples/text",slug:"/examples/text/from-font",permalink:"/examples/text/from-font",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"From Font"},sidebar:"examplesSidebar",previous:{title:"Bitmap Text",permalink:"/examples/text/bitmap-text"},next:{title:"Web Font",permalink:"/examples/text/web-font"}},d={},m=[],x={toc:m};function u(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"text.fromFont",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/83f744ee.dfab8d25.js b/assets/js/83f744ee.dfab8d25.js new file mode 100644 index 000000000..78b810a22 --- /dev/null +++ b/assets/js/83f744ee.dfab8d25.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8790],{5025:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>a,metadata:()=>l,toc:()=>m});var i=s(7462),o=(s(7294),s(3905)),n=s(8010),r=s(7949);const a={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"From Font"},p=void 0,l={unversionedId:"examples/text/from-font",id:"examples/text/from-font",title:"From Font",description:"",source:"@site/docs/examples/text/from-font.md",sourceDirName:"examples/text",slug:"/examples/text/from-font",permalink:"/8.x/examples/text/from-font",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"From Font"},sidebar:"examplesSidebar",previous:{title:"Bitmap Text",permalink:"/8.x/examples/text/bitmap-text"},next:{title:"Web Font",permalink:"/8.x/examples/text/web-font"}},d={},m=[],x={toc:m};function u(e){let{components:t,...s}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,s,{components:t,mdxType:"MDXLayout"}),(0,o.kt)(n.Z,{id:"text.fromFont",pixiVersion:r,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/851b1605.0274b043.js b/assets/js/851b1605.0274b043.js new file mode 100644 index 000000000..fbab9eda8 --- /dev/null +++ b/assets/js/851b1605.0274b043.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5225],{9739:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var a=t(7462),s=(t(7294),t(3905));const o={},i="Assets",l={unversionedId:"guides/components/assets",id:"guides/components/assets",title:"Assets",description:"The Assets package",source:"@site/docs/guides/components/assets.md",sourceDirName:"guides/components",slug:"/guides/components/assets",permalink:"/8.x/guides/components/assets",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/assets.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Render Groups",permalink:"/8.x/guides/advanced/render-groups"},next:{title:"Containers",permalink:"/8.x/guides/components/containers"}},r={},p=[{value:"The Assets package",id:"the-assets-package",level:2},{value:"Getting started",id:"getting-started",level:2},{value:"Making our first Assets Promise",id:"making-our-first-assets-promise",level:2},{value:"Warning about solved promises",id:"warning-about-solved-promises",level:2},{value:"Using Async/Await",id:"using-asyncawait",level:2},{value:"Loading multiple assets",id:"loading-multiple-assets",level:2},{value:"Background loading",id:"background-loading",level:2}],d={toc:p};function u(e){let{components:n,...t}=e;return(0,s.kt)("wrapper",(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,s.kt)("h1",{id:"assets"},"Assets"),(0,s.kt)("h2",{id:"the-assets-package"},"The Assets package"),(0,s.kt)("p",null,"The Assets package is a modern replacement for the old ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("h2",{id:"getting-started"},"Getting started"),(0,s.kt)("p",null,(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser ",(0,s.kt)("a",{parentName:"p",href:"https://caniuse.com/promises"},"doesn't support promises")," you should look into ",(0,s.kt)("a",{parentName:"p",href:"https://github.com/zloirock/core-js#ecmascript-promise"},"polyfilling them"),"."),(0,s.kt)("h2",{id:"making-our-first-assets-promise"},"Making our first Assets Promise"),(0,s.kt)("p",null,"To quickly use the ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," instance, you just need to call ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load")," and pass in an asset. This will return a promise that when resolved will yield the value you seek.\nIn this example, we will load a texture and then turn it into a sprite."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Create a new application\nconst app = new Application();\n\n// Initialize the application\nawait app.init({ background: '#1099bb', resizeTo: window });\n\n// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\n\n// Start loading right away and create a promise\nconst texturePromise = Assets.load('https://pixijs.com/assets/bunny.png');\n\n// When the promise resolves, we have the texture!\ntexturePromise.then((resolvedTexture) =>\n{\n // create a new Sprite from the resolved loaded Texture\n const bunny = Sprite.from(resolvedTexture);\n\n // center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n app.stage.addChild(bunny);\n});\n")),(0,s.kt)("p",null,"One very important thing to keep in mind while using ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," is that all requests are cached and if the URL is the same, the promise returned will also be the same.\nTo show it in code:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"promise1 = Assets.load('bunny.png')\npromise2 = Assets.load('bunny.png')\n// promise1 === promise2\n")),(0,s.kt)("p",null,"Out of the box, the following assets types can be loaded without the need for external plugins:"),(0,s.kt)("ul",null,(0,s.kt)("li",{parentName:"ul"},"Textures (",(0,s.kt)("inlineCode",{parentName:"li"},"avif"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"webp"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"png"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"jpg"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"gif"),")"),(0,s.kt)("li",{parentName:"ul"},"Sprite sheets (",(0,s.kt)("inlineCode",{parentName:"li"},"json"),")"),(0,s.kt)("li",{parentName:"ul"},"Bitmap fonts (",(0,s.kt)("inlineCode",{parentName:"li"},"xml"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"fnt"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"txt"),")"),(0,s.kt)("li",{parentName:"ul"},"Web fonts (",(0,s.kt)("inlineCode",{parentName:"li"},"ttf"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"woff"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"woff2"),")"),(0,s.kt)("li",{parentName:"ul"},"Json files (",(0,s.kt)("inlineCode",{parentName:"li"},"json"),")"),(0,s.kt)("li",{parentName:"ul"},"Text files (",(0,s.kt)("inlineCode",{parentName:"li"},"txt"),")")),(0,s.kt)("p",null,"More types can be added fairly easily by creating additional loader parsers."),(0,s.kt)("h2",{id:"warning-about-solved-promises"},"Warning about solved promises"),(0,s.kt)("p",null,"When an asset is downloaded, it is cached as a promise inside the ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," instance and if you try to download it again you will get a reference to the already resolved promise.\nHowever promise handlers ",(0,s.kt)("inlineCode",{parentName:"p"},".then(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".catch(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".finally(...)")," are always asynchronous, this means that even if a promise was already resolved the code below the ",(0,s.kt)("inlineCode",{parentName:"p"},".then(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".catch(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".finally(...)")," will execute before the code inside them.\nSee this example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"console.log(1);\nalreadyResolvedPromise.then(() => console.log(2));\nconsole.log(3);\n\n// Console output:\n// 1\n// 3\n// 2\n")),(0,s.kt)("p",null,"To learn more about why this happens you will need to learn about ",(0,s.kt)("a",{parentName:"p",href:"https://javascript.info/microtask-queue"},"Microtasks"),", however, using async functions should mitigate this problem."),(0,s.kt)("h2",{id:"using-asyncawait"},"Using Async/Await"),(0,s.kt)("p",null,"There is a way to work with promises that is more intuitive and easier to read: ",(0,s.kt)("inlineCode",{parentName:"p"},"async"),"/",(0,s.kt)("inlineCode",{parentName:"p"},"await"),"."),(0,s.kt)("p",null,"To use it we first need to create a function/method and mark it as ",(0,s.kt)("inlineCode",{parentName:"p"},"async"),"."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"async function test() {\n // ...\n}\n")),(0,s.kt)("p",null,"This function now wraps the return value in a promise and allows us to use the ",(0,s.kt)("inlineCode",{parentName:"p"},"await")," keyword before a promise to halt the execution of the code until it is resolved and gives us the value."),(0,s.kt)("p",null,"See this example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"// Create a new application\nconst app = new Application();\n// Initialize the application\nawait app.init({ background: '#1099bb', resizeTo: window });\n// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\nconst texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n// Create a new Sprite from the awaited loaded Texture\nconst bunny = Sprite.from(texture);\n// Center the sprite's anchor point\nbunny.anchor.set(0.5);\n// Move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\napp.stage.addChild(bunny);\n")),(0,s.kt)("p",null,"The ",(0,s.kt)("inlineCode",{parentName:"p"},"texture")," variable now is not a promise but the resolved texture that resulted after waiting for this promise to resolve."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"const texture = await Assets.load('examples/assets/bunny.png');\n")),(0,s.kt)("p",null,"This allows us to write more readable code without falling into callback hell and to better think when our program halts and yields."),(0,s.kt)("h2",{id:"loading-multiple-assets"},"Loading multiple assets"),(0,s.kt)("p",null,"We can add assets to the cache and then load them all simultaneously by using ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.add(...)")," and then calling ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load(...)")," with all the keys you want to have loaded.\nSee the following example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\n// Add the assets to load\nAssets.add({ alias: 'flowerTop', src: 'https://pixijs.com/assets/flowerTop.png' });\nAssets.add({ alias: 'eggHead', src: 'https://pixijs.com/assets/eggHead.png' });\n// Load the assets and get a resolved promise once both are loaded\nconst texturesPromise = Assets.load(['flowerTop', 'eggHead']); // => Promise<{flowerTop: Texture, eggHead: Texture}>\n// When the promise resolves, we have the texture!\ntexturesPromise.then((textures) =>\n{\n // Create a new Sprite from the resolved loaded Textures\n const flower = Sprite.from(textures.flowerTop);\n flower.anchor.set(0.5);\n flower.x = app.screen.width * 0.25;\n flower.y = app.screen.height / 2;\n app.stage.addChild(flower);\n const egg = Sprite.from(textures.eggHead);\n egg.anchor.set(0.5);\n egg.x = app.screen.width * 0.75;\n egg.y = app.screen.height / 2;\n app.stage.addChild(egg);\n});\n")),(0,s.kt)("p",null,"However, if you want to take full advantage of ",(0,s.kt)("inlineCode",{parentName:"p"},"@pixi/Assets")," you should use bundles.\nBundles are just a way to group assets together and can be added manually by calling ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.addBundle(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.loadBundle(...)"),"."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"}," Assets.addBundle('animals', {\n bunny: 'bunny.png',\n chicken: 'chicken.png',\n thumper: 'thumper.png',\n });\n\n const assets = await Assets.loadBundle('animals');\n")),(0,s.kt)("p",null,"However, the best way to handle bundles is to use a manifest and call ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.init({manifest})")," with said manifest (or even better, an URL pointing to it).\nSplitting 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."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-json"},'{\n "bundles":[\n {\n "name":"load-screen",\n "assets":[\n {\n "alias":"background",\n "src":"sunset.png"\n },\n {\n "alias":"bar",\n "src":"load-bar.{png,webp}"\n }\n ]\n },\n {\n "name":"game-screen",\n "assets":[\n {\n "alias":"character",\n "src":"robot.png"\n },\n {\n "alias":"enemy",\n "src":"bad-guy.png"\n }\n ]\n }\n ]\n}\n')),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},'Assets.init({manifest: "path/manifest.json"});\n')),(0,s.kt)("p",null,"Beware that ",(0,s.kt)("strong",{parentName:"p"},"you can only call ",(0,s.kt)("inlineCode",{parentName:"strong"},"init")," once"),"."),(0,s.kt)("p",null,"Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!"),(0,s.kt)("h2",{id:"background-loading"},"Background loading"),(0,s.kt)("p",null,"The old approach to loading was to use ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"Luckily, ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"To achieve this, we have the methods ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.backgroundLoad(...)")," and ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"When you finally need the assets to show, you call the usual ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load(...)")," or ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.loadBundle(...)")," and you will get the corresponding promise."),(0,s.kt)("p",null,"The best way to do this is using bundles, see the following example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Create a new application\nconst app = new Application();\n\nasync function init()\n{\n // Initialize the application\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Append the application canvas to the document body\n document.body.appendChild(app.canvas);\n\n // Manifest example\n const manifestExample = {\n bundles: [\n {\n name: 'load-screen',\n assets: [\n {\n alias: 'flowerTop',\n src: 'https://pixijs.com/assets/flowerTop.png',\n },\n ],\n },\n {\n name: 'game-screen',\n assets: [\n {\n alias: 'eggHead',\n src: 'https://pixijs.com/assets/eggHead.png',\n },\n ],\n },\n ],\n };\n\n await Assets.init({ manifest: manifestExample });\n\n // Bundles can be loaded in the background too!\n Assets.backgroundLoadBundle(['load-screen', 'game-screen']);\n}\n\ninit();\n")),(0,s.kt)("p",null,"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!"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/851b1605.d3a4062c.js b/assets/js/851b1605.d3a4062c.js deleted file mode 100644 index 6d0b030a9..000000000 --- a/assets/js/851b1605.d3a4062c.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5225],{9739:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>i,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>p});var a=t(7462),s=(t(7294),t(3905));const o={},i="Assets",l={unversionedId:"guides/components/assets",id:"guides/components/assets",title:"Assets",description:"The Assets package",source:"@site/docs/guides/components/assets.md",sourceDirName:"guides/components",slug:"/guides/components/assets",permalink:"/guides/components/assets",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/assets.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Render Groups",permalink:"/guides/advanced/render-groups"},next:{title:"Containers",permalink:"/guides/components/containers"}},r={},p=[{value:"The Assets package",id:"the-assets-package",level:2},{value:"Getting started",id:"getting-started",level:2},{value:"Making our first Assets Promise",id:"making-our-first-assets-promise",level:2},{value:"Warning about solved promises",id:"warning-about-solved-promises",level:2},{value:"Using Async/Await",id:"using-asyncawait",level:2},{value:"Loading multiple assets",id:"loading-multiple-assets",level:2},{value:"Background loading",id:"background-loading",level:2}],d={toc:p};function u(e){let{components:n,...t}=e;return(0,s.kt)("wrapper",(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,s.kt)("h1",{id:"assets"},"Assets"),(0,s.kt)("h2",{id:"the-assets-package"},"The Assets package"),(0,s.kt)("p",null,"The Assets package is a modern replacement for the old ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("h2",{id:"getting-started"},"Getting started"),(0,s.kt)("p",null,(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," relies heavily on JavaScript Promises that all modern browsers support, however, if your target browser ",(0,s.kt)("a",{parentName:"p",href:"https://caniuse.com/promises"},"doesn't support promises")," you should look into ",(0,s.kt)("a",{parentName:"p",href:"https://github.com/zloirock/core-js#ecmascript-promise"},"polyfilling them"),"."),(0,s.kt)("h2",{id:"making-our-first-assets-promise"},"Making our first Assets Promise"),(0,s.kt)("p",null,"To quickly use the ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," instance, you just need to call ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load")," and pass in an asset. This will return a promise that when resolved will yield the value you seek.\nIn this example, we will load a texture and then turn it into a sprite."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Create a new application\nconst app = new Application();\n\n// Initialize the application\nawait app.init({ background: '#1099bb', resizeTo: window });\n\n// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\n\n// Start loading right away and create a promise\nconst texturePromise = Assets.load('https://pixijs.com/assets/bunny.png');\n\n// When the promise resolves, we have the texture!\ntexturePromise.then((resolvedTexture) =>\n{\n // create a new Sprite from the resolved loaded Texture\n const bunny = Sprite.from(resolvedTexture);\n\n // center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n app.stage.addChild(bunny);\n});\n")),(0,s.kt)("p",null,"One very important thing to keep in mind while using ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," is that all requests are cached and if the URL is the same, the promise returned will also be the same.\nTo show it in code:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"promise1 = Assets.load('bunny.png')\npromise2 = Assets.load('bunny.png')\n// promise1 === promise2\n")),(0,s.kt)("p",null,"Out of the box, the following assets types can be loaded without the need for external plugins:"),(0,s.kt)("ul",null,(0,s.kt)("li",{parentName:"ul"},"Textures (",(0,s.kt)("inlineCode",{parentName:"li"},"avif"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"webp"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"png"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"jpg"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"gif"),")"),(0,s.kt)("li",{parentName:"ul"},"Sprite sheets (",(0,s.kt)("inlineCode",{parentName:"li"},"json"),")"),(0,s.kt)("li",{parentName:"ul"},"Bitmap fonts (",(0,s.kt)("inlineCode",{parentName:"li"},"xml"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"fnt"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"txt"),")"),(0,s.kt)("li",{parentName:"ul"},"Web fonts (",(0,s.kt)("inlineCode",{parentName:"li"},"ttf"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"woff"),", ",(0,s.kt)("inlineCode",{parentName:"li"},"woff2"),")"),(0,s.kt)("li",{parentName:"ul"},"Json files (",(0,s.kt)("inlineCode",{parentName:"li"},"json"),")"),(0,s.kt)("li",{parentName:"ul"},"Text files (",(0,s.kt)("inlineCode",{parentName:"li"},"txt"),")")),(0,s.kt)("p",null,"More types can be added fairly easily by creating additional loader parsers."),(0,s.kt)("h2",{id:"warning-about-solved-promises"},"Warning about solved promises"),(0,s.kt)("p",null,"When an asset is downloaded, it is cached as a promise inside the ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets")," instance and if you try to download it again you will get a reference to the already resolved promise.\nHowever promise handlers ",(0,s.kt)("inlineCode",{parentName:"p"},".then(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".catch(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".finally(...)")," are always asynchronous, this means that even if a promise was already resolved the code below the ",(0,s.kt)("inlineCode",{parentName:"p"},".then(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".catch(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},".finally(...)")," will execute before the code inside them.\nSee this example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"console.log(1);\nalreadyResolvedPromise.then(() => console.log(2));\nconsole.log(3);\n\n// Console output:\n// 1\n// 3\n// 2\n")),(0,s.kt)("p",null,"To learn more about why this happens you will need to learn about ",(0,s.kt)("a",{parentName:"p",href:"https://javascript.info/microtask-queue"},"Microtasks"),", however, using async functions should mitigate this problem."),(0,s.kt)("h2",{id:"using-asyncawait"},"Using Async/Await"),(0,s.kt)("p",null,"There is a way to work with promises that is more intuitive and easier to read: ",(0,s.kt)("inlineCode",{parentName:"p"},"async"),"/",(0,s.kt)("inlineCode",{parentName:"p"},"await"),"."),(0,s.kt)("p",null,"To use it we first need to create a function/method and mark it as ",(0,s.kt)("inlineCode",{parentName:"p"},"async"),"."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"async function test() {\n // ...\n}\n")),(0,s.kt)("p",null,"This function now wraps the return value in a promise and allows us to use the ",(0,s.kt)("inlineCode",{parentName:"p"},"await")," keyword before a promise to halt the execution of the code until it is resolved and gives us the value."),(0,s.kt)("p",null,"See this example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"// Create a new application\nconst app = new Application();\n// Initialize the application\nawait app.init({ background: '#1099bb', resizeTo: window });\n// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\nconst texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n// Create a new Sprite from the awaited loaded Texture\nconst bunny = Sprite.from(texture);\n// Center the sprite's anchor point\nbunny.anchor.set(0.5);\n// Move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\napp.stage.addChild(bunny);\n")),(0,s.kt)("p",null,"The ",(0,s.kt)("inlineCode",{parentName:"p"},"texture")," variable now is not a promise but the resolved texture that resulted after waiting for this promise to resolve."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},"const texture = await Assets.load('examples/assets/bunny.png');\n")),(0,s.kt)("p",null,"This allows us to write more readable code without falling into callback hell and to better think when our program halts and yields."),(0,s.kt)("h2",{id:"loading-multiple-assets"},"Loading multiple assets"),(0,s.kt)("p",null,"We can add assets to the cache and then load them all simultaneously by using ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.add(...)")," and then calling ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load(...)")," with all the keys you want to have loaded.\nSee the following example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"// Append the application canvas to the document body\ndocument.body.appendChild(app.canvas);\n// Add the assets to load\nAssets.add({ alias: 'flowerTop', src: 'https://pixijs.com/assets/flowerTop.png' });\nAssets.add({ alias: 'eggHead', src: 'https://pixijs.com/assets/eggHead.png' });\n// Load the assets and get a resolved promise once both are loaded\nconst texturesPromise = Assets.load(['flowerTop', 'eggHead']); // => Promise<{flowerTop: Texture, eggHead: Texture}>\n// When the promise resolves, we have the texture!\ntexturesPromise.then((textures) =>\n{\n // Create a new Sprite from the resolved loaded Textures\n const flower = Sprite.from(textures.flowerTop);\n flower.anchor.set(0.5);\n flower.x = app.screen.width * 0.25;\n flower.y = app.screen.height / 2;\n app.stage.addChild(flower);\n const egg = Sprite.from(textures.eggHead);\n egg.anchor.set(0.5);\n egg.x = app.screen.width * 0.75;\n egg.y = app.screen.height / 2;\n app.stage.addChild(egg);\n});\n")),(0,s.kt)("p",null,"However, if you want to take full advantage of ",(0,s.kt)("inlineCode",{parentName:"p"},"@pixi/Assets")," you should use bundles.\nBundles are just a way to group assets together and can be added manually by calling ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.addBundle(...)"),"/",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.loadBundle(...)"),"."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"}," Assets.addBundle('animals', {\n bunny: 'bunny.png',\n chicken: 'chicken.png',\n thumper: 'thumper.png',\n });\n\n const assets = await Assets.loadBundle('animals');\n")),(0,s.kt)("p",null,"However, the best way to handle bundles is to use a manifest and call ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.init({manifest})")," with said manifest (or even better, an URL pointing to it).\nSplitting 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."),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-json"},'{\n "bundles":[\n {\n "name":"load-screen",\n "assets":[\n {\n "alias":"background",\n "src":"sunset.png"\n },\n {\n "alias":"bar",\n "src":"load-bar.{png,webp}"\n }\n ]\n },\n {\n "name":"game-screen",\n "assets":[\n {\n "alias":"character",\n "src":"robot.png"\n },\n {\n "alias":"enemy",\n "src":"bad-guy.png"\n }\n ]\n }\n ]\n}\n')),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-js"},'Assets.init({manifest: "path/manifest.json"});\n')),(0,s.kt)("p",null,"Beware that ",(0,s.kt)("strong",{parentName:"p"},"you can only call ",(0,s.kt)("inlineCode",{parentName:"strong"},"init")," once"),"."),(0,s.kt)("p",null,"Remember there is no downside in repeating URLs since they will all be cached, so if you need the same asset in two bundles you can duplicate the request without any extra cost!"),(0,s.kt)("h2",{id:"background-loading"},"Background loading"),(0,s.kt)("p",null,"The old approach to loading was to use ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"Luckily, ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"To achieve this, we have the methods ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.backgroundLoad(...)")," and ",(0,s.kt)("inlineCode",{parentName:"p"},"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."),(0,s.kt)("p",null,"When you finally need the assets to show, you call the usual ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.load(...)")," or ",(0,s.kt)("inlineCode",{parentName:"p"},"Assets.loadBundle(...)")," and you will get the corresponding promise."),(0,s.kt)("p",null,"The best way to do this is using bundles, see the following example:"),(0,s.kt)("pre",null,(0,s.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Create a new application\nconst app = new Application();\n\nasync function init()\n{\n // Initialize the application\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Append the application canvas to the document body\n document.body.appendChild(app.canvas);\n\n // Manifest example\n const manifestExample = {\n bundles: [\n {\n name: 'load-screen',\n assets: [\n {\n alias: 'flowerTop',\n src: 'https://pixijs.com/assets/flowerTop.png',\n },\n ],\n },\n {\n name: 'game-screen',\n assets: [\n {\n alias: 'eggHead',\n src: 'https://pixijs.com/assets/eggHead.png',\n },\n ],\n },\n ],\n };\n\n await Assets.init({ manifest: manifestExample });\n\n // Bundles can be loaded in the background too!\n Assets.backgroundLoadBundle(['load-screen', 'game-screen']);\n}\n\ninit();\n")),(0,s.kt)("p",null,"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!"))}u.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/853af732.856c62b1.js b/assets/js/853af732.856c62b1.js deleted file mode 100644 index 622c18d06..000000000 --- a/assets/js/853af732.856c62b1.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4351],{4672:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>r,metadata:()=>d,toc:()=>u});var i=t(7462),a=(t(7294),t(3905)),o=t(8010),n=t(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Background"},l=void 0,d={unversionedId:"examples/assets/background",id:"examples/assets/background",title:"Background",description:"",source:"@site/docs/examples/assets/background.md",sourceDirName:"examples/assets",slug:"/examples/assets/background",permalink:"/examples/assets/background",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Background"},sidebar:"examplesSidebar",previous:{title:"Multiple",permalink:"/examples/assets/multiple"},next:{title:"Bundle",permalink:"/examples/assets/bundle"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(o.Z,{id:"assets.background",pixiVersion:n,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/853af732.ae4fc6e5.js b/assets/js/853af732.ae4fc6e5.js new file mode 100644 index 000000000..007880278 --- /dev/null +++ b/assets/js/853af732.ae4fc6e5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4351],{4672:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>r,metadata:()=>d,toc:()=>u});var i=t(7462),a=(t(7294),t(3905)),o=t(8010),n=t(7949);const r={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Background"},l=void 0,d={unversionedId:"examples/assets/background",id:"examples/assets/background",title:"Background",description:"",source:"@site/docs/examples/assets/background.md",sourceDirName:"examples/assets",slug:"/examples/assets/background",permalink:"/8.x/examples/assets/background",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Background"},sidebar:"examplesSidebar",previous:{title:"Multiple",permalink:"/8.x/examples/assets/multiple"},next:{title:"Bundle",permalink:"/8.x/examples/assets/bundle"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(o.Z,{id:"assets.background",pixiVersion:n,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/8a3bb170.43ab8fba.js b/assets/js/8a3bb170.43ab8fba.js new file mode 100644 index 000000000..c240443e3 --- /dev/null +++ b/assets/js/8a3bb170.43ab8fba.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5556],{1406:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>n,metadata:()=>r,toc:()=>u});var i=t(7462),a=(t(7294),t(3905)),d=t(8010),o=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Custom"},l=void 0,r={unversionedId:"examples/filters-advanced/custom",id:"examples/filters-advanced/custom",title:"Custom",description:"",source:"@site/docs/examples/filters-advanced/custom.md",sourceDirName:"examples/filters-advanced",slug:"/examples/filters-advanced/custom",permalink:"/8.x/examples/filters-advanced/custom",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Custom"},sidebar:"examplesSidebar",previous:{title:"Mouse Blending",permalink:"/8.x/examples/filters-advanced/mouse-blending"},next:{title:"Textured Mesh Basic",permalink:"/8.x/examples/mesh-and-shaders/textured-mesh-basic"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(d.Z,{id:"filtersAdvanced.custom",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/8a3bb170.51de73fb.js b/assets/js/8a3bb170.51de73fb.js deleted file mode 100644 index a5433ef4c..000000000 --- a/assets/js/8a3bb170.51de73fb.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[5556],{1406:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>n,metadata:()=>r,toc:()=>u});var i=t(7462),a=(t(7294),t(3905)),d=t(8010),o=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Custom"},l=void 0,r={unversionedId:"examples/filters-advanced/custom",id:"examples/filters-advanced/custom",title:"Custom",description:"",source:"@site/docs/examples/filters-advanced/custom.md",sourceDirName:"examples/filters-advanced",slug:"/examples/filters-advanced/custom",permalink:"/examples/filters-advanced/custom",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:1,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:1,custom_edit_url:null,title:"Custom"},sidebar:"examplesSidebar",previous:{title:"Mouse Blending",permalink:"/examples/filters-advanced/mouse-blending"},next:{title:"Textured Mesh Basic",permalink:"/examples/mesh-and-shaders/textured-mesh-basic"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,a.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,a.kt)(d.Z,{id:"filtersAdvanced.custom",pixiVersion:o,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/8e36650c.42f92946.js b/assets/js/8e36650c.de99aebc.js similarity index 99% rename from assets/js/8e36650c.42f92946.js rename to assets/js/8e36650c.de99aebc.js index e00e33727..cb64fd841 100644 --- a/assets/js/8e36650c.42f92946.js +++ b/assets/js/8e36650c.de99aebc.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3990],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},9567:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/getting-started",id:"tutorials/getting-started",title:"getting-started",description:"",source:"@site/docs/tutorials/getting-started.md",sourceDirName:"tutorials",slug:"/tutorials/getting-started",permalink:"/tutorials/getting-started",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"gettingStarted",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[3990],{5103:(e,n,t)=>{t.d(n,{Z:()=>h});var a=t(7294);const i={wrapper:"wrapper_IMn0",content:"content_gcvh",card:"card_FbVX",navigator:"navigator_LnKI",interactionArea:"interactionArea_WAqO",dropdown:"dropdown_jD6X",selected:"selected_dCXs",footer:"footer_HOIY",next:"next_dXvJ",editorToggle:"editorToggle_OOG5",showEditor:"showEditor_d5qi",loader:"loader_bTGi"};var o=t(9960),s=t(1262),r=t(5166),p=t(2956),l=t(3874),d=t(5893);function c(e){let{data:n,pixiVersion:t,extraPackages:s}=e,p=Number(window.location.hash.replace("#",""));(!p||p<=0||p>n.length)&&(p=1),(0,a.useEffect)((()=>{window.location.hash=p.toString()}),[p]);const{Content:c,code:h,completedCode:u}=n[p-1],[m,g]=(0,a.useState)(!1),f=()=>{g(!1)},{indexCode:k,extraFiles:y}=(0,l.K7)(h),{indexCode:w,extraFiles:b}=(0,l.K7)(u??h);return(0,d.jsxs)(d.Fragment,{children:[(0,d.jsx)("div",{className:i.content,children:(0,d.jsxs)("div",{className:i.card,children:[(0,d.jsxs)("div",{className:i.navigator,children:[(0,d.jsx)("div",{className:i.interactionArea}),(0,d.jsx)("span",{children:`${p} / ${n.length}`}),(0,d.jsx)("ul",{className:i.dropdown,children:n.map(((e,n)=>(0,d.jsx)(o.Z,{onClick:f,to:`#${n+1}`,children:(0,d.jsx)("div",{className:`${n===p-1?i.selected:""}`,children:`${n+1}. ${e.header}`})},n)))})]}),(0,d.jsx)(c,{}),u&&(0,d.jsx)("button",{onClick:()=>{g(!m)},children:m?"Reset":"Solution"}),(0,d.jsxs)("div",{className:i.footer,children:[p>1&&(0,d.jsx)(o.Z,{onClick:f,className:i.prev,to:"#"+(p-1),children:"< Prev"}),p"})]})]})}),(0,d.jsx)(r.Z,{code:u&&m?w:k,extraFiles:u&&m?b:y,extraPackages:s,pixiVersion:t.version,isPixiDevVersion:t.dev,mode:"tutorial"})]})}function h(e){let{id:n,pixiVersion:t}=e;const o=t.version,[r,l]=(0,a.useState)(!1),h=(0,p.S)(o,n);return(0,d.jsxs)("div",{className:`${i.wrapper} ${r?i.showEditor:""}`,children:[(0,d.jsx)("button",{onClick:()=>{l(!r)},className:i.editorToggle,children:r?"< To Instructions":"To Editor >"}),(0,d.jsx)(s.Z,{fallback:(0,d.jsx)("h1",{className:i.loader,children:"LOADING..."}),children:()=>(0,d.jsx)(c,{data:h.steps,pixiVersion:t,extraPackages:h.extraPackages})})]})}},2956:(e,n,t)=>{t.d(n,{M:()=>Ve,S:()=>qe});var a=t(1249);var i=t(7462),o=(t(7294),t(3905));const s={toc:[]};function r(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},s,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create a PixiJS application of type canvas with specify background color and make it resize to the iframe window\nconst app = new PIXI.Application() < HTMLCanvasElement > { background: '#1099bb', resizeTo: window };\n\n// Adding the application's view to the DOM\ndocument.body.appendChild(app.view);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}r.isMDXComponent=!0;const p={toc:[]};function l(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},p,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplayObject.html"},"DisplayObjects"),". A Sprite is a type of DisplayObject that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"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:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Magically load the PNG asynchronously\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png')\n")),(0,o.kt)("p",null,"Then we need to add our new sprite to the stage. The stage is simply a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Container")," 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.stage.addChild(bunny)\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// center the sprite's anchor point\nbunny.anchor.set(0.5)\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}l.isMDXComponent=!0;const d={toc:[]};function c(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Listen for animate update\napp.ticker.add((delta) => {\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}c.isMDXComponent=!0;const h={toc:[]};function u(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}u.isMDXComponent=!0;const m={gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:[{header:"Getting Started",Content:r,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n"},{header:"Set up something",Content:l,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// add to stage\napp.stage.addChild(bunny);\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n"},{header:"Do something",Content:c,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n",completedCode:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"},{header:"You did it!",Content:u,code:"import * as PIXI from 'pixi.js';\n\nconst app = new PIXI.Application({\n background: '#1099bb',\n resizeTo: window,\n});\n\ndocument.body.appendChild(app.view);\n\n// create a new Sprite from an image path\nconst bunny = PIXI.Sprite.from('https://pixijs.com/assets/bunny.png');\n\n// center the sprite's anchor point\nbunny.anchor.set(0.5);\n\n// move the sprite to the center of the screen\nbunny.x = app.screen.width / 2;\nbunny.y = app.screen.height / 2;\n\napp.stage.addChild(bunny);\n\n// Listen for animate update\napp.ticker.add((delta) =>\n{\n // just for fun, let's rotate mr rabbit a little\n // delta is 1 if running at 100% performance\n // creates frame-independent transformation\n bunny.rotation += 0.1 * delta;\n});\n"}]}},g={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function f(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},g,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"onboard-the-choo-choo-train"},"Onboard the Choo Choo Train!"),(0,o.kt)("p",null,"Welcome to the Choo Choo Train workshop!"),(0,o.kt)("p",null,"We are going to handcraft a cute little scene of a train moving through a landscape at night. We will solely be using the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.com/guides/components/graphics"},"Graphics")," API to draw out the whole scene. In this tutorial, we will be exploring a handful of methods it provides to draw a variety of shapes. For the full list of methods, please check out the Graphics ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. We can then initialize the application and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}f.isMDXComponent=!0;const k={toc:[]};function y(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},k,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-stars"},"Adding Stars"),(0,o.kt)("p",null,"Let's start with the sky! It's a little plain and boring right now, so how about adding some stars to it?"),(0,o.kt)("p",null,"Because we will be drawing many different elements on the remaining steps, let's separate the building of each element into its own function to be called from within the main IIFE. Here, the ",(0,o.kt)("inlineCode",{parentName:"p"},"addStars")," function has been set up for you to fill out."),(0,o.kt)("p",null,"Graphics API has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"star(x, y, points, radius, innerRadius?, rotation?)")," method for this with the ability to specify number of star points, its rotation, radius and even inner radius if you prefer it with a hollow."),(0,o.kt)("p",null,"Here, we will use a for-loop to create a number of 5-point stars with randomized radius, rotation and deterministically randomized positions across the whole scene. Let create 20 scattered stars with a radius size between 2 - 5 units to start under a single Graphics instance. After drawing out the individual invisible shape, we can then use the ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method to color it in, specifying the color and the opacity calculated from the percentage of random radius to the max radius."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," The Graphics API methods (with a few exceptions) return back the Graphics instance so it can be used for chained as you will see in the future steps"))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const starCount = 20;\nconst graphics = new Graphics();\n\nfor (let index = 0; index < starCount; index++)\n{\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n}\n\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Now we have a starry sky! But let's take it a little further to lighten up our sky even more on the next step."))}y.isMDXComponent=!0;const w={toc:[]};function b(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},w,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-moon"},"Adding Moon"),(0,o.kt)("p",null,"For the moon crescent, we will cheat a little bit with the included moon SVG file."),(0,o.kt)("p",null,"Graphics API also has a built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"svg(svgString)")," method for drawing vector graphics using SVG data. Have a go at it on the set up ",(0,o.kt)("inlineCode",{parentName:"p"},"addMoon")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics().svg(parsedSvg);\n\ngraphics.x = app.screen.width / 2 + 100;\ngraphics.y = app.screen.height / 8;\napp.stage.addChild(graphics);\n")),(0,o.kt)("p",null,"Think the sky is enough, let's us now proceed to add some landscape elements!"))}b.isMDXComponent=!0;const x={toc:[{value:"Create Mountain Groups",id:"create-mountain-groups",level:2},{value:"Set Up Mountain Groups",id:"set-up-mountain-groups",level:2},{value:"Animate Mountains",id:"animate-mountains",level:2}]};function v(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},x,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-mountains"},"Adding Mountains"),(0,o.kt)("p",null,"For the background let's put up some mountains, shall we? We will also animate them to the left to give an impression that the scene is moving rightwards."),(0,o.kt)("h2",{id:"create-mountain-groups"},"Create Mountain Groups"),(0,o.kt)("p",null,"Since we are moving the mountains to the left, they will eventually go off the screen and at the same time leaving empty spaces to the right. To fix this, we will be looping them back to the right of the scene once they go out of the scene view. In order to make the loop seamless, we will be making 2 mountain groups where each covers the whole scene. Then we will offset one group off the screen to the right. This is so that the second group and slowly filling in the screen from the right as the first group moving off the screen to the left before looping back to be offscreen to the right of the second group and repeating the process."),(0,o.kt)("p",null,"Let start by filling in the logic for creating a mountain group in the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," function which will return a Graphics instance of a mountain group. We will use this to create the 2 group instances later."),(0,o.kt)("p",null,"Here, we are using a single Graphics instance for a group of mountains. Taking into account the screen dimension we can draw out 3 mountains with different heights and colors. In this case, we will imagine the Graphics instance as a pen and for each of the mountain we move the pen to the starting position using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"moveTo(x, y)")," method and then contour out the mountain arc using ",(0,o.kt)("inlineCode",{parentName:"p"},"bezierCurveTo(cx1, cy1, cx2, cy2, x, y)")," method, where ","[",(0,o.kt)("inlineCode",{parentName:"p"},"cx"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"cy"),"]"," positions are control point coordinates for the curve going from where it was to the ","[",(0,o.kt)("inlineCode",{parentName:"p"},"x"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"y"),"]"," position. Again, we then need to fill the resulted shape with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)"),"."),(0,o.kt)("blockquote",null,(0,o.kt)("p",{parentName:"blockquote"},(0,o.kt)("em",{parentName:"p"},(0,o.kt)("strong",{parentName:"em"},"TIPS:")," In this case, we do not have to connect the end point to the starting point as the Graphics' context will automatically infer a closed shape by doing so for the fill."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const graphics = new Graphics();\nconst width = app.screen.width / 2;\nconst startY = app.screen.height;\nconst startXLeft = 0;\nconst startXMiddle = Number(app.screen.width) / 4;\nconst startXRight = app.screen.width / 2;\nconst heightLeft = app.screen.height / 2;\nconst heightMiddle = (app.screen.height * 4) / 5;\nconst heightRight = (app.screen.height * 2) / 3;\nconst colorLeft = 0xc1c0c2;\nconst colorMiddle = 0x7e818f;\nconst colorRight = 0x8c919f;\n\ngraphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-mountain-groups"},"Set Up Mountain Groups"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createMountainGroup()")," helper function, we can then create 2 instances of the mountain group and offset one of them off the screen to the right."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const group1 = createMountainGroup();\nconst group2 = createMountainGroup();\n\ngroup2.x = app.screen.width;\napp.stage.addChild(group1, group2);\n")),(0,o.kt)("p",null,"You should now see a single group of mountains covering the whole scene."),(0,o.kt)("h2",{id:"animate-mountains"},"Animate Mountains"),(0,o.kt)("p",null,"Using the application's ticker, we can add a callback function which will reposition the mountain groups every ticker update, creating a continuous animation. The callback function will be supplied with the Ticker object in which time-related data can be inferred like the ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," that we will be using to calculate the distance for the mountain to move consistently. Remember to reposition the groups when they moved completely off the screen. "),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 0.5;\n\n group1.x -= dx;\n group2.x -= dx;\n\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n});\n")))}v.isMDXComponent=!0;const C={toc:[{value:"Create Tree",id:"create-tree",level:2},{value:"Set Up Trees",id:"set-up-trees",level:2},{value:"Animate Trees",id:"animate-trees",level:2}]};function T(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},C,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-trees"},"Adding Trees"),(0,o.kt)("p",null,"Let's apply the same principles we used on the mountains step and do the same thing for the trees layer."),(0,o.kt)("h2",{id:"create-tree"},"Create Tree"),(0,o.kt)("p",null,"Starting off with the helper function to create a tree, ",(0,o.kt)("inlineCode",{parentName:"p"},"createTree(width, height)")," which will instantiate a Graphics element with a tree of specified width and height drawn on. We begin with drawing the trunk using Graphics API's ",(0,o.kt)("inlineCode",{parentName:"p"},"rect(x, y, width, height)")," method and fill it out with ",(0,o.kt)("inlineCode",{parentName:"p"},"fill(style)")," method as usual."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trunkWidth = 30;\nconst trunkHeight = height / 4;\nconst trunkColor = 0x563929;\nconst graphics = new Graphics()\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n")),(0,o.kt)("p",null,"Then for the crown, we will draw 4 stacking triangles with each triangle being thinner as we move upwards and the top triangles slightly overlapping the lower ones. Here's an example of how we can achieve that iteratively:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const crownHeight = height - trunkHeight;\nconst crownLevels = 4;\nconst crownLevelHeight = crownHeight / crownLevels;\nconst crownWidthIncrement = width / crownLevels;\nconst crownColor = 0x264d3d;\n\nfor (let index = 0; index < crownLevels; index++)\n{\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n}\n\nreturn graphics;\n")),(0,o.kt)("h2",{id:"set-up-trees"},"Set Up Trees"),(0,o.kt)("p",null,"Now in the ",(0,o.kt)("inlineCode",{parentName:"p"},"addTree()")," function we can instantiate as many trees as we need to cover the screen horizontally, with a few additions as offscreen buffers."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const treeWidth = 200;\nconst y = app.screen.height - 20;\nconst spacing = 15;\nconst count = app.screen.width / (treeWidth + spacing) + 1;\nconst trees = [];\n\nfor (let index = 0; index < count; index++)\n{\n const treeHeight = 225 + Math.random() * 50;\n const tree = createTree(treeWidth, treeHeight);\n\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n app.stage.addChild(tree);\n trees.push(tree);\n}\n")),(0,o.kt)("h2",{id:"animate-trees"},"Animate Trees"),(0,o.kt)("p",null,"Then do the same animation animation setup as we did for the mountains using the application's ticker. However, we will make the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") faster than that of the mountains to simulate the trees being closer to the camera, which should make them go by faster due to the parallax effect."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n tree.x -= dx;\n\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n});\n")))}T.isMDXComponent=!0;const S={toc:[{value:"Snow Layer",id:"snow-layer",level:2},{value:"Track's Planks",id:"tracks-planks",level:2},{value:"Track's Rail",id:"tracks-rail",level:2}]};function j(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},S,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-ground"},"Adding Ground"),(0,o.kt)("p",null,"The trees are floating in space right at this point, but that's because we left some space for the ground layer. Let's fill that up together now!"),(0,o.kt)("p",null,"We will be making 3 layers of the ground with the bottom-most being the snow and the top two being the train track parts."),(0,o.kt)("h2",{id:"snow-layer"},"Snow Layer"),(0,o.kt)("p",null,"For this, we can simply draw a long rectangle strip across the screen and fill in the color of the snow."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const width = app.screen.width;\nconst groundHeight = 20;\nconst groundY = app.screen.height;\nconst ground = new Graphics()\n .rect(0, groundY - groundHeight, width, groundHeight)\n .fill({ color: 0xdddddd });\n\napp.stage.addChild(ground);\n")),(0,o.kt)("h2",{id:"tracks-planks"},"Track's Planks"),(0,o.kt)("p",null,"For the planks, we will be doing the same thing as we did for the trees. First by defining the dimensions of each plank and determining the amount needed to cover the width of the scene with a few additional offscreen buffers as we will be animating them as well. We will position them on top of the snow layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const trackHeight = 15;\nconst plankWidth = 50;\nconst plankHeight = trackHeight / 2;\nconst plankGap = 20;\nconst plankCount = width / (plankWidth + plankGap) + 1;\nconst plankY = groundY - groundHeight;\nconst planks = [];\n\nfor (let index = 0; index < plankCount; index++)\n{\n const plank = new Graphics()\n .rect(0, plankY - plankHeight, plankWidth, plankHeight)\n .fill({ color: 0x241811 });\n\n plank.x = index * (plankWidth + plankGap);\n app.stage.addChild(plank);\n planks.push(plank);\n}\n")),(0,o.kt)("p",null,"Then add the animation to the planks in the similar manner to the trees animation. Again, making the rate of change (",(0,o.kt)("inlineCode",{parentName:"p"},"dx"),") even faster than the trees to simulate the track being closer to the camera, and hence travel faster across the screen (Parallax Effect)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n plank.x -= dx;\n\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n});\n")),(0,o.kt)("h2",{id:"tracks-rail"},"Track's Rail"),(0,o.kt)("p",null,"For the metal rail for the train's wheels to go onto, it will be another simple rectangle strip just like the ground and we will place them above the planks layer."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const railHeight = trackHeight / 2;\nconst railY = plankY - plankHeight;\nconst rail = new Graphics()\n .rect(0, railY - railHeight, width, railHeight)\n .fill({ color: 0x5c5c5c });\n\napp.stage.addChild(rail);\n")),(0,o.kt)("hr",null),(0,o.kt)("p",null,"With the layers coming together, it should sell an effect of the track being passed by. Next, we can finally move on to work on the main star of the workshop - the train!"))}j.isMDXComponent=!0;const A={toc:[{value:"Body",id:"body",level:2},{value:"Wheels",id:"wheels",level:2},{value:"Combine and Animate",id:"combine-and-animate",level:2}]};function W(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},A,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-head"},"Adding Train Head"),(0,o.kt)("p",null,"We will start by making the head of the train first, and to do so we will be separating them into parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Cabin"),(0,o.kt)("li",{parentName:"ul"},"Door"),(0,o.kt)("li",{parentName:"ul"},"Window"),(0,o.kt)("li",{parentName:"ul"},"Roof"),(0,o.kt)("li",{parentName:"ul"},"Front"),(0,o.kt)("li",{parentName:"ul"},"Chimney"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"Apart from the wheels, the parts will be drawn using a single Graphics instance. Let wrap all of the logic for this inside the already set-up ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function that will return a Container element holding all the parts together."),(0,o.kt)("h2",{id:"body"},"Body"),(0,o.kt)("p",null,"The body parts includes the cabin with its overlaying door and window topped with a roof, and the protruding front with the chimney on top."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const frontHeight = 100;\nconst frontWidth = 140;\nconst frontRadius = frontHeight / 2;\n\nconst cabinHeight = 200;\nconst cabinWidth = 150;\nconst cabinRadius = 15;\n\nconst chimneyBaseWidth = 30;\nconst chimneyTopWidth = 50;\nconst chimneyHeight = 70;\nconst chimneyDomeHeight = 25;\nconst chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\nconst chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\nconst chimneyStartY = -frontHeight;\n\nconst roofHeight = 25;\nconst roofExcess = 20;\n\nconst doorWidth = cabinWidth * 0.7;\nconst doorHeight = cabinHeight * 0.7;\nconst doorStartX = (cabinWidth - doorWidth) * 0.5;\nconst doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\nconst windowWidth = doorWidth * 0.8;\nconst windowHeight = doorHeight * 0.4;\nconst offset = (doorWidth - windowWidth) / 2;\n\nconst graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n")),(0,o.kt)("h2",{id:"wheels"},"Wheels"),(0,o.kt)("p",null,"For the wheels, lets make a helper function that will instantiate individual wheel given a radius. This has been set up for you as the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function."),(0,o.kt)("p",null,"Inside a wheel, we can split it further into parts as:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Wheel base"),(0,o.kt)("li",{parentName:"ul"},"Tyre surrounding the base"),(0,o.kt)("li",{parentName:"ul"},"Spokes on the base")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const strokeThickness = radius / 3;\nconst innerRadius = radius - strokeThickness;\n\nreturn (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n);\n")),(0,o.kt)("p",null,"Then we can this helper function inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," function to create the 3 wheels for the train head which include one larger wheel at the back and two standard sized ones in front."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bigWheelRadius = 55;\nconst smallWheelRadius = 35;\nconst wheelGap = 5;\nconst wheelOffsetY = 5;\n\nconst backWheel = createTrainWheel(bigWheelRadius);\nconst midWheel = createTrainWheel(smallWheelRadius);\nconst frontWheel = createTrainWheel(smallWheelRadius);\n\nbackWheel.x = bigWheelRadius;\nbackWheel.y = wheelOffsetY;\nmidWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\nmidWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\nfrontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\nfrontWheel.y = midWheel.y;\n")),(0,o.kt)("h2",{id:"combine-and-animate"},"Combine and Animate"),(0,o.kt)("p",null,"Now that we have the Graphics instance of the train head's body and its wheels, let add them all onto a wrapping container and then animate the spinning of the wheels before returning the container as the result. Notice here that we make the back wheel rotate proportionally slower like it logically should as it's bigger with more circumference to cover in a revolution."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\ncontainer.addChild(graphics, backWheel, midWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")))}W.isMDXComponent=!0;const N={toc:[{value:"Assemble Train",id:"assemble-train",level:2}]};function H(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},N,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-train-carriage"},"Adding Train Carriage"),(0,o.kt)("p",null,"Accompanying the head, let's add a trailing carriage to complete a running train. Here we will be doing the same procedures as when we were building the head inside the new ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," function. The carriage consists of 4 parts:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"Container"),(0,o.kt)("li",{parentName:"ul"},"Top Edge"),(0,o.kt)("li",{parentName:"ul"},"Connectors"),(0,o.kt)("li",{parentName:"ul"},"Wheels")),(0,o.kt)("p",null,"We can re-use the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainWheel(radius)")," function to create the two standard sized wheels which will be animated in the same manner as before."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const container = new Container();\n\nconst containerHeight = 125;\nconst containerWidth = 200;\nconst containerRadius = 15;\nconst edgeHeight = 25;\nconst edgeExcess = 20;\nconst connectorWidth = 30;\nconst connectorHeight = 10;\nconst connectorGap = 10;\nconst connectorOffsetY = 20;\n\nconst graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\nconst wheelRadius = 35;\nconst wheelGap = 40;\nconst centerX = (containerWidth + edgeExcess) / 2;\nconst offsetX = wheelRadius + wheelGap / 2;\n\nconst backWheel = createTrainWheel(wheelRadius);\nconst frontWheel = createTrainWheel(wheelRadius);\n\nbackWheel.x = centerX - offsetX;\nfrontWheel.x = centerX + offsetX;\nfrontWheel.y = backWheel.y = 25;\n\ncontainer.addChild(graphics, backWheel, frontWheel);\n\napp.ticker.add((time) =>\n{\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n});\n\nreturn container;\n")),(0,o.kt)("h2",{id:"assemble-train"},"Assemble Train"),(0,o.kt)("p",null,"With the ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainHead()")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"createTrainCarriage()")," functions completed, let's use them to create the sections, adding them to a wrapping container, offsetting the trailing carriage to be behind the train head. We can then top it up with a little bobble up and down to simulate shaking due to the travel along the track."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const head = createTrainHead();\nconst carriage = createTrainCarriage();\n\ncarriage.x = -carriage.width;\n\ntrainContainer.addChild(head, carriage);\napp.stage.addChild(trainContainer);\n\nconst scale = 0.75;\n\ntrainContainer.scale.set(scale);\ntrainContainer.x = app.screen.width / 2 - head.width / 2;\n\nlet elapsed = 0;\nconst shakeDistance = 3;\nconst baseY = app.screen.height - 35 - 55 * scale;\nconst speed = 0.5;\n\ntrainContainer.y = baseY;\n\napp.ticker.add((time) =>\n{\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n trainContainer.y = baseY + offset;\n});\n")),(0,o.kt)("p",null,"We have now successfully crafted a evening scene of a training moving through the landscape with just the Graphics API. But what's the point of having a chimney without any smoke!"))}H.isMDXComponent=!0;const M={toc:[{value:"Create Smoke Groups",id:"create-smoke-groups",level:2},{value:"Animate Smokes",id:"animate-smokes",level:2}]};function D(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},M,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-smokes"},"Adding Smokes"),(0,o.kt)("p",null,"For the final touch, let's create groups of smoke particles animating in from the train chimney and out off the screen."),(0,o.kt)("h2",{id:"create-smoke-groups"},"Create Smoke Groups"),(0,o.kt)("p",null,"First we need to create the individual groups of circular particles of varying size and position within the cluster, each group under a single Graphics instance. For the purpose of animation, we then assign a custom ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," property to each group which will be used to reference the percentage of the animation from the chimney to the disappearing point."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const groupCount = 5;\nconst particleCount = 7;\nconst groups = [];\nconst baseX = trainContainer.x + 170;\nconst baseY = trainContainer.y - 120;\n\nfor (let index = 0; index < groupCount; index++)\n{\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n smokeGroup.circle(x, y, radius);\n }\n\n smokeGroup.fill({ color: 0xc9c9c9, alpha: 0.5 });\n\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n smokeGroup.tick = index * (1 / groupCount);\n\n groups.push(smokeGroup);\n}\n")),(0,o.kt)("h2",{id:"animate-smokes"},"Animate Smokes"),(0,o.kt)("p",null,"As you can see, we previously offset the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on each group initially to distribute them out so that it illustrates the constant line of smokes coming out from the chimney. We then use the same technique of using the application's ticker for the animation, incrementing the ",(0,o.kt)("inlineCode",{parentName:"p"},"tick")," value on all groups which is then used to calculate the position and scale of each. The value is modulated so that it goes back to the starting point when it finishes at the disappearing point, ie. the value will loop infinitely from 0 -> 1."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) =>\n{\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n group.tick = (group.tick + dt) % 1;\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n });\n});\n")),(0,o.kt)("p",null,"And that is a wrap!"))}D.isMDXComponent=!0;const I={toc:[]};function B(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},I,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, hope you enjoyed the journey! Now you are an expert on the Graphics API. Make sure to explore other features that the API has to offer on the official ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Graphics.html"},"documentation"),", like the ability to cut shapes out from existing ones, advance lines and curves, using gradients or textures for fill and stroke - just to list a few."),(0,o.kt)("p",null,"Feel free to head back to the gallery and explore other tutorials."))}B.isMDXComponent=!0;const R="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n})();\n",E="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n})();\n",P="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n})();\n",X="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n})();\n",G="import { Application } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n})();\n",O="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",L="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n})();\n",F="import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n",z="import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n const starCount = 20;\n\n // Create a graphics object to hold all the stars.\n const graphics = new Graphics();\n\n for (let index = 0; index < starCount; index++)\n {\n // Randomize the position, radius, and rotation of each star.\n const x = (index * 0.78695 * app.screen.width) % app.screen.width;\n const y = (index * 0.9382 * app.screen.height) % app.screen.height;\n const radius = 2 + Math.random() * 3;\n const rotation = Math.random() * Math.PI * 2;\n\n // Draw the star onto the graphics object.\n graphics.star(x, y, 5, radius, 0, rotation).fill({ color: 0xffdf00, alpha: radius / 5 });\n }\n\n // Add the stars to the stage.\n app.stage.addChild(graphics);\n}\n",Y='\n \n \n',J="import { Graphics } from 'pixi.js';\nimport moonSvg from './moon.svg';\n\nexport function addMoon(app)\n{\n // Create a moon graphics object from an SVG code.\n const graphics = new Graphics().svg(moonSvg);\n\n // Position the moon.\n graphics.x = app.screen.width / 2 + 100;\n graphics.y = app.screen.height / 8;\n\n // Add the moon to the stage.\n app.stage.addChild(graphics);\n}\n",_="import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n // Create two mountain groups where one will be on the screen and the other will be off screen.\n // When the first group moves off screen, it will be moved to the right of the second group.\n const group1 = createMountainGroup(app);\n const group2 = createMountainGroup(app);\n\n // Position the 2nd group off the screen to the right.\n group2.x = app.screen.width;\n\n // Add the mountain groups to the stage.\n app.stage.addChild(group1, group2);\n\n // Animate the mountain groups\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the mountain groups per tick.\n const dx = time.deltaTime * 0.5;\n\n // Move the mountain groups leftwards.\n group1.x -= dx;\n group2.x -= dx;\n\n // Reposition the mountain groups when they move off screen.\n if (group1.x <= -app.screen.width)\n {\n group1.x += app.screen.width * 2;\n }\n if (group2.x <= -app.screen.width)\n {\n group2.x += app.screen.width * 2;\n }\n });\n}\n\nfunction createMountainGroup(app)\n{\n // Create a graphics object to hold all the mountains in a group.\n const graphics = new Graphics();\n\n // Width of all the mountains.\n const width = app.screen.width / 2;\n\n // Starting point on the y-axis of all the mountains.\n // This is the bottom of the screen.\n const startY = app.screen.height;\n\n // Start point on the x-axis of the individual mountain.\n const startXLeft = 0;\n const startXMiddle = Number(app.screen.width) / 4;\n const startXRight = app.screen.width / 2;\n\n // Height of the individual mountain.\n const heightLeft = app.screen.height / 2;\n const heightMiddle = (app.screen.height * 4) / 5;\n const heightRight = (app.screen.height * 2) / 3;\n\n // Color of the individual mountain.\n const colorLeft = 0xc1c0c2;\n const colorMiddle = 0x7e818f;\n const colorRight = 0x8c919f;\n\n graphics\n // Draw the middle mountain\n .moveTo(startXMiddle, startY)\n .bezierCurveTo(\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width / 2,\n startY - heightMiddle,\n startXMiddle + width,\n startY,\n )\n .fill({ color: colorMiddle })\n\n // Draw the left mountain\n .moveTo(startXLeft, startY)\n .bezierCurveTo(\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width / 2,\n startY - heightLeft,\n startXLeft + width,\n startY,\n )\n .fill({ color: colorLeft })\n\n // Draw the right mountain\n .moveTo(startXRight, startY)\n .bezierCurveTo(\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width / 2,\n startY - heightRight,\n startXRight + width,\n startY,\n )\n .fill({ color: colorRight });\n\n return graphics;\n}\n",U="import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n // Width of each tree.\n const treeWidth = 200;\n\n // Position of the base of the trees on the y-axis.\n const y = app.screen.height - 20;\n\n // Spacing between each tree.\n const spacing = 15;\n\n // Calculate the number of trees needed to fill the screen horizontally.\n const count = app.screen.width / (treeWidth + spacing) + 1;\n\n // Create an array to store all the trees.\n const trees = [];\n\n for (let index = 0; index < count; index++)\n {\n // Randomize the height of each tree within a constrained range.\n const treeHeight = 225 + Math.random() * 50;\n\n // Create a tree instance.\n const tree = createTree(treeWidth, treeHeight);\n\n // Initially position the tree.\n tree.x = index * (treeWidth + spacing);\n tree.y = y;\n\n // Add the tree to the stage and the reference array.\n app.stage.addChild(tree);\n trees.push(tree);\n }\n\n // Animate the trees.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the trees per tick.\n const dx = time.deltaTime * 3;\n\n trees.forEach((tree) =>\n {\n // Move the trees leftwards.\n tree.x -= dx;\n\n // Reposition the trees when they move off screen.\n if (tree.x <= -(treeWidth / 2 + spacing))\n {\n tree.x += count * (treeWidth + spacing) + spacing * 3;\n }\n });\n });\n}\n\nfunction createTree(width, height)\n{\n // Define the dimensions of the tree trunk.\n const trunkWidth = 30;\n const trunkHeight = height / 4;\n\n // Define the dimensions and parameters for the tree crown layers.\n const crownHeight = height - trunkHeight;\n const crownLevels = 4;\n const crownLevelHeight = crownHeight / crownLevels;\n const crownWidthIncrement = width / crownLevels;\n\n // Define the colors of the parts.\n const crownColor = 0x264d3d;\n const trunkColor = 0x563929;\n\n const graphics = new Graphics()\n // Draw the trunk.\n .rect(-trunkWidth / 2, -trunkHeight, trunkWidth, trunkHeight)\n .fill({ color: trunkColor });\n\n for (let index = 0; index < crownLevels; index++)\n {\n const y = -trunkHeight - crownLevelHeight * index;\n const levelWidth = width - crownWidthIncrement * index;\n const offset = index < crownLevels - 1 ? crownLevelHeight / 2 : 0;\n\n // Draw a crown layer.\n graphics\n .moveTo(-levelWidth / 2, y)\n .lineTo(0, y - crownLevelHeight - offset)\n .lineTo(levelWidth / 2, y)\n .fill({ color: crownColor });\n }\n\n return graphics;\n}\n",Z="import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n const width = app.screen.width;\n\n // Create and draw the bottom ground graphic.\n const groundHeight = 20;\n const groundY = app.screen.height;\n const ground = new Graphics().rect(0, groundY - groundHeight, width, groundHeight).fill({ color: 0xdddddd });\n\n // Add the ground to the stage.\n app.stage.addChild(ground);\n\n // Define the total height of the track. Both the planks and the rail layers.\n const trackHeight = 15;\n\n // Define the dimensions and parameters for the planks.\n const plankWidth = 50;\n const plankHeight = trackHeight / 2;\n const plankGap = 20;\n const plankCount = width / (plankWidth + plankGap) + 1;\n const plankY = groundY - groundHeight;\n\n // Create an array to store all the planks.\n const planks = [];\n\n for (let index = 0; index < plankCount; index++)\n {\n // Create and draw a plank graphic.\n const plank = new Graphics().rect(0, plankY - plankHeight, plankWidth, plankHeight).fill({ color: 0x241811 });\n\n // Position the plank to distribute it across the screen.\n plank.x = index * (plankWidth + plankGap);\n\n // Add the plank to the stage and the reference array.\n app.stage.addChild(plank);\n planks.push(plank);\n }\n\n // Create and draw the rail strip graphic.\n const railHeight = trackHeight / 2;\n const railY = plankY - plankHeight;\n const rail = new Graphics().rect(0, railY - railHeight, width, railHeight).fill({ color: 0x5c5c5c });\n\n // Add the rail to the stage.\n app.stage.addChild(rail);\n\n // Animate just the planks to simulate the passing of the ground.\n // Since the rail and the ground are uniform strips, they do not need to be animated.\n app.ticker.add((time) =>\n {\n // Calculate the amount of distance to move the planks per tick.\n const dx = time.deltaTime * 6;\n\n planks.forEach((plank) =>\n {\n // Move the planks leftwards.\n plank.x -= dx;\n\n // Reposition the planks when they move off screen.\n if (plank.x <= -(plankWidth + plankGap))\n {\n plank.x += plankCount * (plankWidth + plankGap) + plankGap * 1.5;\n }\n });\n });\n}\n",q="import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n // Position the carriage behind the head.\n carriage.x = -carriage.width;\n\n // Add the head and the carriage to the train container.\n container.addChild(head, carriage);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train on the x-axis, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n\n // Define animation parameters.\n let elapsed = 0;\n const shakeDistance = 3;\n const baseY = app.screen.height - 35 - 55 * scale;\n const speed = 0.5;\n\n // Initially position the train on the y-axis.\n container.y = baseY;\n\n // Animate the train - bobbing it up and down a tiny bit on the track.\n app.ticker.add((time) =>\n {\n elapsed += time.deltaTime;\n const offset = (Math.sin(elapsed * 0.5 * speed) * 0.5 + 0.5) * shakeDistance;\n\n container.y = baseY + offset;\n });\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n // Create a container to hold all the train carriage parts.\n const container = new Container();\n\n // Define the dimensions of the carriage parts.\n const containerHeight = 125;\n const containerWidth = 200;\n const containerRadius = 15;\n const edgeHeight = 25;\n const edgeExcess = 20;\n const connectorWidth = 30;\n const connectorHeight = 10;\n const connectorGap = 10;\n const connectorOffsetY = 20;\n\n const graphics = new Graphics()\n // Draw the body\n .roundRect(edgeExcess / 2, -containerHeight, containerWidth, containerHeight, containerRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the top edge\n .rect(0, containerRadius - containerHeight - edgeHeight, containerWidth + edgeExcess, edgeHeight)\n .fill({ color: 0x52431c })\n\n // Draw the connectors\n .rect(containerWidth + edgeExcess / 2, -connectorOffsetY - connectorHeight, connectorWidth, connectorHeight)\n .rect(\n containerWidth + edgeExcess / 2,\n -connectorOffsetY - connectorHeight * 2 - connectorGap,\n connectorWidth,\n connectorHeight,\n )\n .fill({ color: 0x121212 });\n\n // Define the dimensions of the wheels.\n const wheelRadius = 35;\n const wheelGap = 40;\n const centerX = (containerWidth + edgeExcess) / 2;\n const offsetX = wheelRadius + wheelGap / 2;\n\n // Create the wheels.\n const backWheel = createTrainWheel(wheelRadius);\n const frontWheel = createTrainWheel(wheelRadius);\n\n // Position the wheels.\n backWheel.x = centerX - offsetX;\n frontWheel.x = centerX + offsetX;\n frontWheel.y = backWheel.y = 25;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, frontWheel);\n\n // Animate the wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n",V="import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n const groupCount = 5;\n const particleCount = 7;\n\n // Create an array to store all the smoke groups.\n const groups = [];\n\n // Define the emitter position based on the train's position.\n const baseX = train.x + 170;\n const baseY = train.y - 120;\n\n for (let index = 0; index < groupCount; index++)\n {\n const smokeGroup = new Graphics();\n\n for (let i = 0; i < particleCount; i++)\n {\n // Randomize the position and radius of each particle.\n const radius = 20 + Math.random() * 20;\n const x = (Math.random() * 2 - 1) * 40;\n const y = (Math.random() * 2 - 1) * 40;\n\n // Draw a smoke particle.\n smokeGroup.circle(x, y, radius);\n }\n\n // Fill the smoke group with gray color.\n smokeGroup.fill({ color: 0xc9c9c9 });\n\n // Position the smoke group.\n smokeGroup.x = baseX;\n smokeGroup.y = baseY;\n\n // Add a tick custom property to the smoke group for storing the animation progress ratio.\n smokeGroup.tick = index * (1 / groupCount);\n\n // Add the smoke group to the stage and the reference array.\n app.stage.addChild(smokeGroup);\n groups.push(smokeGroup);\n }\n\n // Animate the smoke groups.\n app.ticker.add((time) =>\n {\n // Calculate the change in amount of animation progress ratio per tick.\n const dt = time.deltaTime * 0.01;\n\n groups.forEach((group) =>\n {\n // Update the animation progress ratio.\n group.tick = (group.tick + dt) % 1;\n\n // Update the position and scale of the smoke group based on the animation progress ratio.\n group.x = baseX - Math.pow(group.tick, 2) * 400;\n group.y = baseY - group.tick * 200;\n group.scale.set(Math.pow(group.tick, 0.75));\n group.alpha = 1 - Math.pow(group.tick, 0.5);\n });\n });\n}\n",K=[{header:"Introduction",Content:f,code:"import { Application } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Adding Stars",Content:y,code:{index:R,"src/addStars.js*":"import { Graphics } from 'pixi.js';\n\nexport function addStars(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:R,"src/addStars.js*":z}},{header:"Adding Moon",Content:b,code:{index:E,"src/addStars.js!":z,"src/addMoon.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMoon(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n","src/moon.svg":Y},completedCode:{index:E,"src/addStars.js!":z,"src/addMoon.js*":J,"src/moon.svg":Y}},{header:"Adding Mountains",Content:v,code:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":"import { Graphics } from 'pixi.js';\n\nexport function addMountains(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createMountainGroup(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:P,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js*":_}},{header:"Adding Trees",Content:T,code:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":"import { Graphics } from 'pixi.js';\n\nexport function addTrees(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTree(width, height)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:X,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js*":U}},{header:"Adding Ground",Content:j,code:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":"import { Graphics } from 'pixi.js';\n\nexport function addGround(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:G,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js*":Z}},{header:"Adding Train Head",Content:W,code:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead();\n\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainHead(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:O,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"}},{header:"Adding Train Carriage",Content:H,code:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":"import { Container, Graphics } from 'pixi.js';\n\nexport function addTrain(app, container)\n{\n const head = createTrainHead(app);\n const carriage = createTrainCarriage(app);\n\n /** -- ADJUST CODE HERE -- */\n\n // Add the head to the train container.\n container.addChild(head);\n\n // Add the train container to the stage.\n app.stage.addChild(container);\n\n const scale = 0.75;\n\n // Adjust the scaling of the train.\n container.scale.set(scale);\n\n // Position the train, taking into account the variety of screen width.\n // To keep the train as the main focus, the train is offset slightly to the left of the screen center.\n container.x = app.screen.width / 2 - head.width / 2;\n container.y = app.screen.height - 35 - 55 * scale;\n}\n\nfunction createTrainHead(app)\n{\n // Create a container to hold all the train head parts.\n const container = new Container();\n\n // Define the dimensions of the head front.\n const frontHeight = 100;\n const frontWidth = 140;\n const frontRadius = frontHeight / 2;\n\n // Define the dimensions of the cabin.\n const cabinHeight = 200;\n const cabinWidth = 150;\n const cabinRadius = 15;\n\n // Define the dimensions of the chimney.\n const chimneyBaseWidth = 30;\n const chimneyTopWidth = 50;\n const chimneyHeight = 70;\n const chimneyDomeHeight = 25;\n const chimneyTopOffset = (chimneyTopWidth - chimneyBaseWidth) / 2;\n const chimneyStartX = cabinWidth + frontWidth - frontRadius - chimneyBaseWidth;\n const chimneyStartY = -frontHeight;\n\n // Define the dimensions of the roof.\n const roofHeight = 25;\n const roofExcess = 20;\n\n // Define the dimensions of the door.\n const doorWidth = cabinWidth * 0.7;\n const doorHeight = cabinHeight * 0.7;\n const doorStartX = (cabinWidth - doorWidth) * 0.5;\n const doorStartY = -(cabinHeight - doorHeight) * 0.5 - doorHeight;\n\n // Define the dimensions of the window.\n const windowWidth = doorWidth * 0.8;\n const windowHeight = doorHeight * 0.4;\n const offset = (doorWidth - windowWidth) / 2;\n\n const graphics = new Graphics()\n // Draw the chimney\n .moveTo(chimneyStartX, chimneyStartY)\n .lineTo(chimneyStartX - chimneyTopOffset, chimneyStartY - chimneyHeight + chimneyDomeHeight)\n .quadraticCurveTo(\n chimneyStartX + chimneyBaseWidth / 2,\n chimneyStartY - chimneyHeight - chimneyDomeHeight,\n chimneyStartX + chimneyBaseWidth + chimneyTopOffset,\n chimneyStartY - chimneyHeight + chimneyDomeHeight,\n )\n .lineTo(chimneyStartX + chimneyBaseWidth, chimneyStartY)\n .fill({ color: 0x121212 })\n\n // Draw the head front\n .roundRect(\n cabinWidth - frontRadius - cabinRadius,\n -frontHeight,\n frontWidth + frontRadius + cabinRadius,\n frontHeight,\n frontRadius,\n )\n .fill({ color: 0x7f3333 })\n\n // Draw the cabin\n .roundRect(0, -cabinHeight, cabinWidth, cabinHeight, cabinRadius)\n .fill({ color: 0x725f19 })\n\n // Draw the roof\n .rect(-roofExcess / 2, cabinRadius - cabinHeight - roofHeight, cabinWidth + roofExcess, roofHeight)\n .fill({ color: 0x52431c })\n\n // Draw the door\n .roundRect(doorStartX, doorStartY, doorWidth, doorHeight, cabinRadius)\n .stroke({ color: 0x52431c, width: 3 })\n\n // Draw the window\n .roundRect(doorStartX + offset, doorStartY + offset, windowWidth, windowHeight, 10)\n .fill({ color: 0x848484 });\n\n // Define the dimensions of the wheels.\n const bigWheelRadius = 55;\n const smallWheelRadius = 35;\n const wheelGap = 5;\n const wheelOffsetY = 5;\n\n // Create all the wheels.\n const backWheel = createTrainWheel(bigWheelRadius);\n const midWheel = createTrainWheel(smallWheelRadius);\n const frontWheel = createTrainWheel(smallWheelRadius);\n\n // Position the wheels.\n backWheel.x = bigWheelRadius;\n backWheel.y = wheelOffsetY;\n midWheel.x = backWheel.x + bigWheelRadius + smallWheelRadius + wheelGap;\n midWheel.y = backWheel.y + bigWheelRadius - smallWheelRadius;\n frontWheel.x = midWheel.x + smallWheelRadius * 2 + wheelGap;\n frontWheel.y = midWheel.y;\n\n // Add all the parts to the container.\n container.addChild(graphics, backWheel, midWheel, frontWheel);\n\n // Animate the wheels - making the big wheel rotate proportionally slower than the small wheels.\n app.ticker.add((time) =>\n {\n const dr = time.deltaTime * 0.15;\n\n backWheel.rotation += dr * (smallWheelRadius / bigWheelRadius);\n midWheel.rotation += dr;\n frontWheel.rotation += dr;\n });\n\n return container;\n}\n\nfunction createTrainCarriage(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nfunction createTrainWheel(radius)\n{\n // Define the dimensions of the wheel.\n const strokeThickness = radius / 3;\n const innerRadius = radius - strokeThickness;\n\n return (\n new Graphics()\n .circle(0, 0, radius)\n // Draw the wheel\n .fill({ color: 0x848484 })\n // Draw the tyre\n .stroke({ color: 0x121212, width: strokeThickness, alignment: 1 })\n // Draw the spokes\n .rect(-strokeThickness / 2, -innerRadius, strokeThickness, innerRadius * 2)\n .rect(-innerRadius, -strokeThickness / 2, innerRadius * 2, strokeThickness)\n .fill({ color: 0x4f4f4f })\n );\n}\n"},completedCode:{index:L,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js*":q}},{header:"Adding Smokes",Content:D,code:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":"import { Graphics } from 'pixi.js';\n\nexport function addSmokes(app, train)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:F,"src/addStars.js!":z,"src/addMoon.js!":J,"src/moon.svg!":Y,"src/addMountains.js!":_,"src/addTrees.js!":U,"src/addGround.js!":Z,"src/addTrain.js!":q,"src/addSmokes.js*":V}},{header:"You did it!",Content:B,code:{index:"import { Application, Container } from 'pixi.js';\nimport { addStars } from './addStars';\nimport { addMoon } from './addMoon';\nimport { addMountains } from './addMountains';\nimport { addTrees } from './addTrees';\nimport { addGround } from './addGround';\nimport { addTrain } from './addTrain';\nimport { addSmokes } from './addSmokes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Create a container to hold all the train parts.\nconst trainContainer = new Container();\n\n// Asynchronous IIFE\n(async () =>\n{\n // Intialize the application.\n await app.init({ background: '#021f4b', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n addStars(app);\n addMoon(app);\n addMountains(app);\n addTrees(app);\n addGround(app);\n addTrain(app, trainContainer);\n addSmokes(app, trainContainer);\n})();\n","src/addStars.js":z,"src/addMoon.js":J,"src/moon.svg!":Y,"src/addMountains.js":_,"src/addTrees.js":U,"src/addGround.js":Z,"src/addTrain.js":q,"src/addSmokes.js":V}}],$={toc:[{value:"Application Setup",id:"application-setup",level:2},{value:"Preloading Assets",id:"preloading-assets",level:2}]};function Q(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},$,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"lets-make-a-pond"},"Let's make a pond!"),(0,o.kt)("p",null,"Welcome to the Fish Pond workshop!"),(0,o.kt)("p",null,"We are going to build a virtual pond and fill them with a number of colorful fishes. In the process, we will be learning about basic manipulation of ",(0,o.kt)("a",{parentName:"p",href:"/guides/components/sprites"},"Sprites"),", ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.TilingSprite.html"},"TilingSprite")," and Filter, specifically the ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.DisplacementFilter.html"},"Displacement Filter"),"."),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start off by creation a PixiJS application, initialize it, add its canvas to the DOM, and preload the required assets ahead of the subsequent steps."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application outside of the IIFE just so that it can be referenced across other functions declared outside. The initialization and appending the application's canvas will be done from within the ",(0,o.kt)("inlineCode",{parentName:"p"},"setup")," function which is called inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function setup()\n{\n await app.init({ background: '#1099bb', resizeTo: window });\n document.body.appendChild(app.canvas);\n}\n")),(0,o.kt)("h2",{id:"preloading-assets"},"Preloading Assets"),(0,o.kt)("p",null,"After the application setup, we will then preload all the textures required for the rest of the tutorial. Here we also provide aliases so that they can be intuitively referred to later on. This will be done inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"preload")," function which is also called inside the IIFE after the setup."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"async function preload()\n{\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n await Assets.load(assets);\n}\n")),(0,o.kt)("p",null,"At this point, you should see the preview filled with an empty light blue background."),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Q.isMDXComponent=!0;const ee={toc:[{value:"Create and Setup Background Sprite",id:"create-and-setup-background-sprite",level:2},{value:"Fit and Position Sprite",id:"fit-and-position-sprite",level:2}]};function ne(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ee,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-a-background"},"Adding a Background"),(0,o.kt)("p",null,"Now lets fill the pond with some rocks and pebbles, shall we? Let's work inside the already prepared ",(0,o.kt)("inlineCode",{parentName:"p"},"addBackground")," function."),(0,o.kt)("h2",{id:"create-and-setup-background-sprite"},"Create and Setup Background Sprite"),(0,o.kt)("p",null,"We already preloaded the pond background asset as the alias 'background' so we can just simply create a sprite"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const background = Sprite.from('background');\n\nbackground.anchor.set(0.5);\n")),(0,o.kt)("h2",{id:"fit-and-position-sprite"},"Fit and Position Sprite"),(0,o.kt)("p",null,"Now we want the background sprite to fill the whole screen without any distortion so we will compare and fill the longer axis and then apply the same scale on the smaller axis for a uniform scaling."),(0,o.kt)("p",null,(0,o.kt)("em",{parentName:"p"},"(Note: x1.2 scaling to the dimension is to overflow the screen slightly to compensate for the last's step distortion from post-processing)")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (app.screen.width > app.screen.height)\n{\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n}\nelse\n{\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n}\n")),(0,o.kt)("p",null,"When we manually set the width or height on a sprite, it will apply a scale on the corresponding axis depending on the width or height of the original texture. Hence, we can simply equalize the scale on both axes this way."),(0,o.kt)("p",null,"Then we simply position it at the center of the preview."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"background.x = app.screen.width / 2;\nbackground.y = app.screen.height / 2;\n")),(0,o.kt)("p",null,"We got a beautiful pond! Now let's proceed to add some fishes!"))}ne.isMDXComponent=!0;const te={toc:[{value:"Create and Setup Fish Sprites",id:"create-and-setup-fish-sprites",level:2},{value:"Animate Fishes",id:"animate-fishes",level:2}]};function ae(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},te,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-fishes"},"Adding Fishes"),(0,o.kt)("p",null,"What's a pond without the fishes, right? Let's use what we learn from the previous step to add some fish sprites to the scene as well. We will also animate them afterwards to give them life."),(0,o.kt)("h2",{id:"create-and-setup-fish-sprites"},"Create and Setup Fish Sprites"),(0,o.kt)("p",null,"Let's encapsulate all the following setup within the ",(0,o.kt)("inlineCode",{parentName:"p"},"addFishes")," function that has already been prepared for you. We begin by creating a container to hold all the fish sprites together and add it to the stage. This is a great practice for better separation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishContainer = new Container();\n\napp.stage.addChild(fishContainer);\n")),(0,o.kt)("p",null,"Then we declare some reference variables like how many fishes should there be in the pond and what are the fish types available. For the types, we refer to the 5 different fish assets we have preloaded earlier and made them into an array of aliases."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const fishCount = 20;\nconst fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n")),(0,o.kt)("p",null,"Instead of creating each of the fish individually, which will be super tedious, we will use a simple ",(0,o.kt)("inlineCode",{parentName:"p"},"for")," loop to create each of the fish until it reaches our desire count, also cycling through the fish asset aliases array. In addition to the basic setup and applying initial transforms, we also assign them with custom properties like ",(0,o.kt)("inlineCode",{parentName:"p"},"direction"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"speed")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"turnSpeed")," which will be used during the animation. We will store the fishes in a reference array defined outside of the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"for (let i = 0; i < fishCount; i++)\n{\n const fishAsset = fishAssets[i % fishAssets.length];\n const fish = Sprite.from(fishAsset);\n\n fish.anchor.set(0.5);\n\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n fishContainer.addChild(fish);\n fishes.push(fish);\n}\n")),(0,o.kt)("h2",{id:"animate-fishes"},"Animate Fishes"),(0,o.kt)("p",null,"It's time to give the fishes some movements! Another function ",(0,o.kt)("inlineCode",{parentName:"p"},"animateFishes")," has been prepared and connected to the application's ticker which will be continuously called. It is supplied with a Ticker object which we can use to infer the amount of time passed between the calls."),(0,o.kt)("p",null,"We will declare a few variables to help us with the animation. We extract ",(0,o.kt)("inlineCode",{parentName:"p"},"deltaTime")," from the Ticker object which tells us the amount of time passed since last call, in seconds. We also define an imaginary bound that is larger than the stage itself to wrap the position of the fishes when they go off the screen. We use this bound instead of the actual screen size to avoid having the fishes disappear before they actually go off the edges, since the fish sprites' anchor is in the center so, eg. when a ",(0,o.kt)("inlineCode",{parentName:"p"},"fish.x = 0"),", half of the fish's width is still apparent on the screen."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const delta = time.deltaTime;\n\nconst stagePadding = 100;\nconst boundWidth = app.screen.width + stagePadding * 2;\nconst boundHeight = app.screen.height + stagePadding * 2;\n")),(0,o.kt)("p",null,"We can then simply loop through individual fishes array and update them one by one. First by updating the fish's pseudo direction which dictates the changes in its sprite position and rotation. To keep the fish within the screen bound, we use the padded bound defined earlier to check and wrap the fish as soon as it goes off the bound."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"fishes.forEach((fish) =>\n{\n fish.direction += fish.turnSpeed * 0.01;\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n fish.rotation = -fish.direction - Math.PI / 2;\n\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n});\n")),(0,o.kt)("p",null,"They are beautiful aren't they! Next, let's add a water surface effect to make the pond feels more dynamic."))}ae.isMDXComponent=!0;const ie={toc:[{value:"Create and Setup Tiling Sprite",id:"create-and-setup-tiling-sprite",level:2},{value:"Animate Overlay",id:"animate-overlay",level:2}]};function oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-water-overlay"},"Adding Water Overlay"),(0,o.kt)("p",null,"At the point, the fishes look like they are floating on the rocks and pebbles. We will overlay what we have so far with a tiling sprite of a tiled water texture. Tiling sprite is essentially a sprite with the capabilities of transforming and rending an infinitely repeating grid of a single texture, preferably a tiled one where the edges seamlessly connect with each other when put together. We will use this to give an illusion of a forever moving water surface."),(0,o.kt)("h2",{id:"create-and-setup-tiling-sprite"},"Create and Setup Tiling Sprite"),(0,o.kt)("p",null,"Here we create a tiling sprite, supplying a texture and dimensions as an option object, and add it to the stage."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = Texture.from('overlay');\n\noverlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n});\napp.stage.addChild(overlay);\n")),(0,o.kt)("h2",{id:"animate-overlay"},"Animate Overlay"),(0,o.kt)("p",null,"Similar to the previous step, we will now animate the water overlay using the application's ticker. The code has been modify to call both animation functions for the fish and this overlay so we only need to add the animation logic inside the ",(0,o.kt)("inlineCode",{parentName:"p"},"animateWaterOverlay")," function."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"elapsed += time.deltaTime;\noverlay.tilePosition.x = elapsed * -1;\noverlay.tilePosition.y = elapsed * -1;\n")),(0,o.kt)("p",null,"Congratulations, we have now completed a beautiful pond! But we can take it a step further. Let's proceed to the final touch!"))}oe.isMDXComponent=!0;const se={toc:[]};function re(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-displacement-effect"},"Adding Displacement Effect"),(0,o.kt)("p",null,"Let's be a bit extra and simulate distortion effect from the water."),(0,o.kt)("p",null,"PixiJS comes with a handful of filters built-in and many dozens of fancy ones on the (PixiJS Filters package)","[https://github.com/pixijs/filters]",". Here, we will be using the displacement filter for the distortion, which is built-in to the native PixiJS so we do not have to install any additional filter packages."),(0,o.kt)("p",null,"Displacement filter requires a sprite as a parameter for its options object. We will need to create a sprite from the displacement map asset and set its base texture's wrap mode to be 'repeat' so that the shader can tile and repeated it."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const sprite = Sprite.from('displacement');\n\nsprite.texture.baseTexture.wrapMode = 'repeat';\n")),(0,o.kt)("p",null,"From here, we can simply create the displacement filter and add it to the stage container's filters list."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n});\n\napp.stage.filters = [filter];\n")),(0,o.kt)("p",null,"Now you should see the post-processed pond in effect. Looks like we are looking down directly into a real pond, right?"))}re.isMDXComponent=!0;const pe={toc:[]};function le(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations once again! Well done for creating this master piece. Feel free to head back to the gallery and explore other tutorials."))}le.isMDXComponent=!0;const de="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\n\n// Create a PixiJS application.\nconst app = new Application();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n})();\n",ce="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n\n // Add the fish animation callback to the application's ticker.\n app.ticker.add((time) => animateFishes(app, fishes, time));\n})();\n",he="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",ue="import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n",me="import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n // Create a background sprite.\n const background = Sprite.from('background');\n\n // Center background sprite anchor.\n background.anchor.set(0.5);\n\n /**\n * If the preview is landscape, fill the width of the screen\n * and apply horizontal scale to the vertical scale for a uniform fit.\n */\n if (app.screen.width > app.screen.height)\n {\n background.width = app.screen.width * 1.2;\n background.scale.y = background.scale.x;\n }\n else\n {\n /**\n * If the preview is square or portrait, then fill the height of the screen instead\n * and apply the scaling to the horizontal scale accordingly.\n */\n background.height = app.screen.height * 1.2;\n background.scale.x = background.scale.y;\n }\n\n // Position the background sprite in the center of the stage.\n background.x = app.screen.width / 2;\n background.y = app.screen.height / 2;\n\n // Add the background to the stage.\n app.stage.addChild(background);\n}\n",ge="import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n // Create a container to hold all the fish sprites.\n const fishContainer = new Container();\n\n // Add the fish container to the stage.\n app.stage.addChild(fishContainer);\n\n const fishCount = 20;\n const fishAssets = ['fish1', 'fish2', 'fish3', 'fish4', 'fish5'];\n\n // Create a fish sprite for each fish.\n for (let i = 0; i < fishCount; i++)\n {\n // Cycle through the fish assets for each sprite.\n const fishAsset = fishAssets[i % fishAssets.length];\n\n // Create a fish sprite.\n const fish = Sprite.from(fishAsset);\n\n // Center the sprite anchor.\n fish.anchor.set(0.5);\n\n // Assign additional properties for the animation.\n fish.direction = Math.random() * Math.PI * 2;\n fish.speed = 2 + Math.random() * 2;\n fish.turnSpeed = Math.random() - 0.8;\n\n // Randomly position the fish sprite around the stage.\n fish.x = Math.random() * app.screen.width;\n fish.y = Math.random() * app.screen.height;\n\n // Randomly scale the fish sprite to create some variety.\n fish.scale.set(0.5 + Math.random() * 0.2);\n\n // Add the fish sprite to the fish container.\n fishContainer.addChild(fish);\n\n // Add the fish sprite to the fish array.\n fishes.push(fish);\n }\n}\n\nexport function animateFishes(app, fishes, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Define the padding around the stage where fishes are considered out of sight.\n const stagePadding = 100;\n const boundWidth = app.screen.width + stagePadding * 2;\n const boundHeight = app.screen.height + stagePadding * 2;\n\n // Iterate through each fish sprite.\n fishes.forEach((fish) =>\n {\n // Animate the fish movement direction according to the turn speed.\n fish.direction += fish.turnSpeed * 0.01;\n\n // Animate the fish position according to the direction and speed.\n fish.x += Math.sin(fish.direction) * fish.speed;\n fish.y += Math.cos(fish.direction) * fish.speed;\n\n // Apply the fish rotation according to the direction.\n fish.rotation = -fish.direction - Math.PI / 2;\n\n // Wrap the fish position when it goes out of bounds.\n if (fish.x < -stagePadding)\n {\n fish.x += boundWidth;\n }\n if (fish.x > app.screen.width + stagePadding)\n {\n fish.x -= boundWidth;\n }\n if (fish.y < -stagePadding)\n {\n fish.y += boundHeight;\n }\n if (fish.y > app.screen.height + stagePadding)\n {\n fish.y -= boundHeight;\n }\n });\n}\n",fe="import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n // Create a water texture object.\n const texture = Texture.from('overlay');\n\n // Create a tiling sprite with the water texture and specify the dimensions.\n overlay = new TilingSprite({\n texture,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the overlay to the stage.\n app.stage.addChild(overlay);\n}\n\nexport function animateWaterOverlay(app, time)\n{\n // Extract the delta time from the Ticker object.\n const delta = time.deltaTime;\n\n // Animate the overlay.\n overlay.tilePosition.x -= delta;\n overlay.tilePosition.y -= delta;\n}\n",ke="import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n // Create a sprite from the preloaded displacement asset.\n const sprite = Sprite.from('displacement');\n\n // Set the base texture wrap mode to repeat to allow the texture UVs to be tiled and repeated.\n sprite.texture.baseTexture.wrapMode = 'repeat';\n\n // Create a displacement filter using the sprite texture.\n const filter = new DisplacementFilter({\n sprite,\n scale: 50,\n width: app.screen.width,\n height: app.screen.height,\n });\n\n // Add the filter to the stage.\n app.stage.filters = [filter];\n}\n",ye=[{header:"Introduction",Content:Q,code:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n /** -- INSERT CODE HERE -- */\n}\n\nasync function preload()\n{\n /** -- INSERT CODE HERE -- */\n}\n",completedCode:"import { Application, Assets } from 'pixi.js';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n})();\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n"},{header:"Adding Background",Content:ne,code:{index:de,"src/addBackground.js*":"import { Sprite } from 'pixi.js';\n\nexport function addBackground(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:de,"src/addBackground.js*":me}},{header:"Adding Fishes",Content:ae,code:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":"import { Container, Sprite } from 'pixi.js';\n\nexport function addFishes(app, fishes)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateFishes(app, fishes, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ce,"src/addBackground.js!":me,"src/addFishes.js*":ge}},{header:"Adding Water Overlay",Content:oe,code:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":"import { Texture, TilingSprite } from 'pixi.js';\n\n// Reference to the water overlay.\nlet overlay;\n\nexport function addWaterOverlay(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n\nexport function animateWaterOverlay(app, time)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:he,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js*":fe}},{header:"Adding Displacement Effect",Content:re,code:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":"import { Sprite, DisplacementFilter } from 'pixi.js';\n\nexport function addDisplacementEffect(app)\n{\n /** -- INSERT CODE HERE -- */\n}\n"},completedCode:{index:ue,"src/addBackground.js!":me,"src/addFishes.js!":ge,"src/addWaterOverlay.js!":fe,"src/addDisplacementEffect.js*":ke}},{header:"You did it!",Content:le,code:{index:"import { Application, Assets } from 'pixi.js';\nimport { addBackground } from './addBackground';\nimport { addFishes, animateFishes } from './addFishes';\nimport { addWaterOverlay, animateWaterOverlay } from './addWaterOverlay';\nimport { addDisplacementEffect } from './addDisplacementEffect';\n\n// Create a PixiJS application.\nconst app = new Application();\n\n// Store an array of fish sprites for animation.\nconst fishes = [];\n\nasync function setup()\n{\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n}\n\nasync function preload()\n{\n // Create an array of asset data to load.\n const assets = [\n { alias: 'background', src: 'https://pixijs.com/assets/tutorials/fish-pond/pond_background.jpg' },\n { alias: 'fish1', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish1.png' },\n { alias: 'fish2', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish2.png' },\n { alias: 'fish3', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish3.png' },\n { alias: 'fish4', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish4.png' },\n { alias: 'fish5', src: 'https://pixijs.com/assets/tutorials/fish-pond/fish5.png' },\n { alias: 'overlay', src: 'https://pixijs.com/assets/tutorials/fish-pond/wave_overlay.png' },\n { alias: 'displacement', src: 'https://pixijs.com/assets/tutorials/fish-pond/displacement_map.png' },\n ];\n\n // Load the assets defined above.\n await Assets.load(assets);\n}\n\n// Asynchronous IIFE\n(async () =>\n{\n await setup();\n await preload();\n\n addBackground(app);\n addFishes(app, fishes);\n addWaterOverlay(app);\n addDisplacementEffect(app);\n\n // Add the animation callbacks to the application's ticker.\n app.ticker.add((time) =>\n {\n animateFishes(app, fishes, time);\n animateWaterOverlay(app, time);\n });\n})();\n","src/addBackground.js":me,"src/addFishes.js":ge,"src/addWaterOverlay.js":fe,"src/addDisplacementEffect.js":ke}}],we={toc:[{value:"Application Setup",id:"application-setup",level:2}]};function be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},we,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"getting-started"},"Getting Started"),(0,o.kt)("p",null,"Welcome to the PixiJS tutorial!"),(0,o.kt)("p",null,"Please go through the tutorial steps at your own pace and challenge yourself using the editor on the right hand side. Here PixiJS has already been included as guided under the ",(0,o.kt)("a",{parentName:"p",href:"/guides/basics/getting-started#loading-pixijs"},"Getting Started")," section. Let's start with the creation of a PixiJS canvas application and add its view to the DOM."),(0,o.kt)("p",null,"We will be using an asynchronous immediately invoked function expression (",(0,o.kt)("a",{parentName:"p",href:"https://developer.mozilla.org/en-US/docs/Glossary/IIFE"},"IIFE"),"), but you are free to switch to use promises instead."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"Let's create the application and initialize it within the the IIFE before appending the its canvas to the DOM. If you came from PixiJS v7 or below, the key differences to pay attention to is that application options are now passed in as an object parameter to the ",(0,o.kt)("inlineCode",{parentName:"p"},"init")," call, and that it is asynchronous which should be awaited before proceeding to use the application."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const app = new Application();\n\nawait app.init({ background: '#1099bb', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("p",null,"When you are ready, proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}be.isMDXComponent=!0;const xe={toc:[]};function ve(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},xe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"creating-a-sprite"},"Creating a Sprite"),(0,o.kt)("p",null,"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."),(0,o.kt)("p",null,"There are a number of ways to draw images in PixiJS, but the simplest is by using a ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Sprite.html"},"Sprite"),". 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 ",(0,o.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/PIXI.Container.html"},"Containers"),". A Sprite is an extension of Container that wraps a loaded image resource to allow drawing it, scaling it, rotating it, and so forth."),(0,o.kt)("p",null,"Before PixiJS can render an image, it needs to be loaded. Just like in any web page, image loading happens asynchronously. For now, we will simply load a single texture up on the spot with the ",(0,o.kt)("inlineCode",{parentName:"p"},"Assets")," utility class."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n")),(0,o.kt)("p",null,"Then we need to create and add our new bunny sprite to the stage. The stage is also simply a Container 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."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const bunny = new Sprite(texture);\n\napp.stage.addChild(bunny);\n")),(0,o.kt)("p",null,"Now let's set the Sprite's anchor and position it so that it's bang on at the center."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"bunny.anchor.set(0.5)\n\nbunny.x = app.screen.width / 2\nbunny.y = app.screen.height / 2\n")))}ve.isMDXComponent=!0;const Ce={toc:[]};function Te(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ce,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"writing-an-update-loop"},"Writing an Update Loop"),(0,o.kt)("p",null,"While you ",(0,o.kt)("em",{parentName:"p"},"can")," use PixiJS for static content, for most projects you'll want to add animation. Our sample app is actually cranking away, rendering the same sprite in the same place multiple times a second. All we have to do to make the image move is to update its attributes once per frame. To do this, we want to hook into the application's ",(0,o.kt)("em",{parentName:"p"},"ticker"),". A ticker is a PixiJS object that runs one or more callbacks each frame. Doing so is surprisingly easy. Add the following to the end of your script block:"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"app.ticker.add((time) => {\n bunny.rotation += 0.1 * time.deltaTime;\n});\n")),(0,o.kt)("p",null,"All you need to do is to call ",(0,o.kt)("inlineCode",{parentName:"p"},"app.ticker.add(...)"),", pass it a callback function, and then update your scene in that function. It will get called every frame, and you can move, rotate etc. whatever you'd like to drive your project's animations."))}Te.isMDXComponent=!0;const Se={toc:[]};function je(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Se,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations! Now you are ready for the real world ~"))}je.isMDXComponent=!0;const Ae=[{header:"Getting Started",Content:be,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n"},{header:"Set up something",Content:ve,code:"import { Application } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path\n const bunny = new Sprite(texture);\n\n // Add to stage\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n"},{header:"Do something",Content:Te,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n})();\n",completedCode:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"},{header:"You did it!",Content:je,code:"import { Application, Assets, Sprite } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the bunny texture.\n const texture = await Assets.load('https://pixijs.com/assets/bunny.png');\n\n // Create a new Sprite from an image path.\n const bunny = new Sprite(texture);\n\n // Add to stage.\n app.stage.addChild(bunny);\n\n // Center the sprite's anchor point.\n bunny.anchor.set(0.5);\n\n // Move the sprite to the center of the screen.\n bunny.x = app.screen.width / 2;\n bunny.y = app.screen.height / 2;\n\n // Add an animation loop callback to the application's ticker.\n app.ticker.add((time) =>\n {\n /**\n * Just for fun, let's rotate mr rabbit a little.\n * Time is a Ticker object which holds time related data.\n * Here we use deltaTime, which is the time elapsed between the frame callbacks\n * to create frame-independent transformation. Keeping the speed consistent.\n */\n bunny.rotation += 0.1 * time.deltaTime;\n });\n})();\n"}],We="import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n",Ne="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the spine to the main view.\n this.view.addChild(this.spine);\n }\n}\n",He="import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n // Play the jump animation if not already playing.\n if (this.state.jump) this.playAnimation(animationMap.jump);\n\n // Skip the rest of the animation updates during the jump animation.\n if (this.isAnimationPlaying(animationMap.jump)) return;\n\n // Handle the character animation based on the latest state and in the priority order.\n if (this.state.hover) this.playAnimation(animationMap.hover);\n else if (this.state.run) this.playAnimation(animationMap.run);\n else if (this.state.walk) this.playAnimation(animationMap.walk);\n else this.playAnimation(animationMap.idle);\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n",Me="// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // If not already in the double-tap state, toggle the double tap state if the key was pressed twice within 300ms.\n this.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n\n // Toggle on the key pressed state.\n this.keys[key].pressed = true;\n }\n\n keyupHandler(event)\n {\n const key = keyMap[event.code];\n\n if (!key) return;\n\n const now = Date.now();\n\n // Reset the key pressed state.\n this.keys[key].pressed = false;\n\n // Reset double tap only if the key is in the double-tap state.\n if (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\n // Otherwise, update the timestamp to track the time difference till the next potential key down.\n else this.keys[key].timestamp = now;\n }\n}\n",De="import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n return this.platform.tilePosition.x;\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n this.background.tilePosition.x = value * 0.1;\n this.midground.tilePosition.x = value * 0.25;\n this.platform.tilePosition.x = value;\n }\n}\n",Ie={toc:[{value:"What is Spine",id:"what-is-spine",level:2},{value:"Application Setup",id:"application-setup",level:2},{value:"Assets Preloading",id:"assets-preloading",level:2}]};function Be(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ie,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"spineboy-adventure"},"SpineBoy Adventure"),(0,o.kt)("p",null,"Welcome to the SpineBoy Adventure workshop!"),(0,o.kt)("p",null,"Let's venture into the world of the PixiJS ecosystem. We are going to explore one of the official plugins; ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8"},"Spine plugin (",(0,o.kt)("inlineCode",{parentName:"a"},"@pixi/spine-pixi"),")")," which allow us to render and manipulate Spine animations on our PixiJS."),(0,o.kt)("p",null,"We will be creating a mini interactive side-scroller experience using the famous SpineBoy which will be controlled by the keyboard. For the sake of simplicity, we will be focusing on just the movement around the scene."),(0,o.kt)("h2",{id:"what-is-spine"},"What is Spine"),(0,o.kt)("p",null,(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/"},"Spine"),", developed by Esoteric Software, is a 2D animation software specifically designed for games. It streamlines 2D game animation with skeletal animation, robust tools, and exportable, lightweight animations."),(0,o.kt)("h2",{id:"application-setup"},"Application Setup"),(0,o.kt)("p",null,"As usual, let's begin by creating an application, initializing it, and appending its canvas to the DOM inside the IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await app.init({ background: '#021f4b', resizeTo: window });\ndocument.body.appendChild(app.canvas);\n")),(0,o.kt)("h2",{id:"assets-preloading"},"Assets Preloading"),(0,o.kt)("p",null,"Let's then preload all of our required assets upfront which includes:"),(0,o.kt)("ol",null,(0,o.kt)("li",{parentName:"ol"},"Spine Assets",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Skeleton data file."),(0,o.kt)("li",{parentName:"ul"},"Accompanying ATLAS."))),(0,o.kt)("li",{parentName:"ol"},"Scene Images",(0,o.kt)("ul",{parentName:"li"},(0,o.kt)("li",{parentName:"ul"},"Static sky gradient image."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the massive buildings in the distance."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the city skyline."),(0,o.kt)("li",{parentName:"ul"},"Tiled image of the platform that the character will be moving on.")))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/spineboy.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n]);\n")),(0,o.kt)("p",null,"Now you are ready to dive straight into the adventure! Proceed to the next exercise using the ",(0,o.kt)("em",{parentName:"p"},"Next >")," button below, or feel free to skip to any exercise using the drop-down menu on the top right hand corner of the card."))}Be.isMDXComponent=!0;const Re={toc:[]};function Ee(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Re,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-character"},"Setting Up Character"),(0,o.kt)("p",null,"We will now create a class for containing and handling our character Spine animations."),(0,o.kt)("p",null,"Here, a `SpineBoy`` class has been set up on a different file. Lets start off by doing the minimum to get the character Spine displayed. Inside the class, a view container has also been set up to hold any of the content from within the class."),(0,o.kt)("p",null,"We can use the ",(0,o.kt)("inlineCode",{parentName:"p"},"Spine.from(options)")," method to instantiate our SpineBoy using the preloaded Character's Spine skeleton file and ATLAS file. We then store it as the ",(0,o.kt)("inlineCode",{parentName:"p"},"spine")," member of the class for future references both internally and externally. And of course, remember to add it to the class' view container."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n});\nthis.view.addChild(this.spine);\n")),(0,o.kt)("p",null,"Let's also create an instance of our SpineBoy class on our main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," file and add its view to our application's stage. To keep it simple, let just keep our character in the middle of the screen and 80 pixels from the bottom of the screen, and also scale it down a little to ensure the fit."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"// Create our character\nconst spineBoy = new SpineBoy();\n\n// Adjust character transformation.\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - 80;\nspineBoy.spine.scale.set(0.5);\n\n// Add character to the stage.\napp.stage.addChild(spineBoy.view);\n")),(0,o.kt)("p",null,"Now we should have our static character on the screen!"))}Ee.isMDXComponent=!0;const Pe={toc:[{value:"Key-Down Handler",id:"key-down-handler",level:2},{value:"Key-Up Handler",id:"key-up-handler",level:2},{value:"Using Controller",id:"using-controller",level:2}]};function Xe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Pe,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"adding-keyboard-controller"},"Adding Keyboard Controller"),(0,o.kt)("p",null,"Before we proceed to work on the character animations, we will need a handler for our keyboard input."),(0,o.kt)("p",null,"To speed things up, a ",(0,o.kt)("inlineCode",{parentName:"p"},"Controller")," class has been set up on another file with the key map and the controller state map define, as well as the key listeners hooked up."),(0,o.kt)("p",null,"As you can we, we have 3 tracked properties on each of the state keys:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," simply tells whether the key is being pressed."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," tracks if the key has been rapidly pressed after letting go."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"timestamp")," is an internal time tracker for determining whether the tap is considered as a double tap.")),(0,o.kt)("p",null,"Please note that we have also defined ",(0,o.kt)("strong",{parentName:"p"},"W"),", ",(0,o.kt)("strong",{parentName:"p"},"A"),", ",(0,o.kt)("strong",{parentName:"p"},"S")," and ",(0,o.kt)("strong",{parentName:"p"},"D")," keys as directional input on the key map so they will behave like the arrow keys."),(0,o.kt)("p",null,"Let's start by updating our key-down and key-up handlers so that the controller state is updated accordingly."),(0,o.kt)("h2",{id:"key-down-handler"},"Key-Down Handler"),(0,o.kt)("p",null,"For this, we simply need to set the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". And so for the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," if the difference in time from the point of the timestamp recorded for that key is less than a threshold, 300ms in this case. Since the key-down handler will be called continuously while a key is held, the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state should remain ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," on subsequent callback if it was already, despite the growing deference in time from the timestamp (As the timestamp only gets reset on the key-up handler)."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = true;\nthis.keys[key].doubleTap = this.keys[key].doubleTap || now - this.keys[key].timestamp < 300;\n")),(0,o.kt)("h2",{id:"key-up-handler"},"Key-Up Handler"),(0,o.kt)("p",null,"Similary, we reset the ",(0,o.kt)("inlineCode",{parentName:"p"},"pressed")," state of the corresponded key state to ",(0,o.kt)("inlineCode",{parentName:"p"},"false")," on key-up, as well as the ",(0,o.kt)("inlineCode",{parentName:"p"},"doubleTap")," state if it was previously ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),". Otherwise, we reset the timestamp to allow subsequent key presses to validate any rapid double-tap."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const key = keyMap[event.code];\n\nif (!key) return;\n\nconst now = Date.now();\n\nthis.keys[key].pressed = false;\n\nif (this.keys[key].doubleTap) this.keys[key].doubleTap = false;\nelse this.keys[key].timestamp = now;\n")),(0,o.kt)("h2",{id:"using-controller"},"Using Controller"),(0,o.kt)("p",null,"Just like for our character, we then create an instance of the controller on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),"' IIFE."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const controller = new Controller();\n")),(0,o.kt)("p",null,"Then we can try connecting the controller state to the character's walk animation. Let's do this for just the right key for now on an application's ticker update. Here, we temporarily store a reference to an active animation key on spot to only allow playing once per toggle since we are already specifying for them to be loops. The toggle will be between the animations with the key of ",(0,o.kt)("inlineCode",{parentName:"p"},"idle")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),"."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let currentAnimation;\n\napp.ticker.add((time) =>\n{\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n if (currentAnimation !== animationName)\n {\n currentAnimation = animationName;\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n});\n")),(0,o.kt)("p",null,"Now tap on the preview screen to make sure the canvas is focused, then try tapping away the right button. We now having a functioning controller!"))}Xe.isMDXComponent=!0;const Ge={toc:[{value:"Preparation",id:"preparation",level:2},{value:"Animation Map",id:"animation-map",level:3},{value:"Helper Methods",id:"helper-methods",level:3},{value:"playAnimation(animation)",id:"playanimationanimation",level:4},{value:"isAnimationPlaying(animation)",id:"isanimationplayinganimation",level:4},{value:"spawn()",id:"spawn",level:4},{value:"isSpawning()",id:"isspawning",level:4},{value:"Handling Direction",id:"handling-direction",level:3},{value:"Spine State Animation Default Mix",id:"spine-state-animation-default-mix",level:3},{value:"Update Loop",id:"update-loop",level:2},{value:"Connecting to Controller",id:"connecting-to-controller",level:2}]};function Oe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Ge,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-character"},"Animating Character"),(0,o.kt)("p",null,"Returning to the star of our workshop, let's upgrade our Character to handle various movement animations. For this example, we will simply store a state set where we can then use an update loop to trigger animations according to the combination of the state values. We can then externally update the character state depending on the controller input state."),(0,o.kt)("h2",{id:"preparation"},"Preparation"),(0,o.kt)("p",null,"For the upgrade, an animation map and assorted helper methods have been added to make handling Spine animation a little cleaner."),(0,o.kt)("h3",{id:"animation-map"},"Animation Map"),(0,o.kt)("p",null,"This lists out all the available animations to be included in our character, each with a ",(0,o.kt)("inlineCode",{parentName:"p"},"name")," parameter that corresponds to an animation key existed on the character Spine data and an optional ",(0,o.kt)("inlineCode",{parentName:"p"},"loop")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"timeScale")," parameters to customize the animation."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n")),(0,o.kt)("h3",{id:"helper-methods"},"Helper Methods"),(0,o.kt)("h4",{id:"playanimationanimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"playAnimation(animation)")),(0,o.kt)("p",null,"Wraps Spine state's ",(0,o.kt)("inlineCode",{parentName:"p"},"setAnimation(track, name, loop)")," method that plays an animation using a passed in animation data defined on the animation map. It prevents the same animation from being played on top of each other."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isanimationplayinganimation"},(0,o.kt)("inlineCode",{parentName:"h4"},"isAnimationPlaying(animation)")),(0,o.kt)("p",null,"Check whether an animation is still active. That is when the Spine state's main track has a track entry of an animation with a key equals to that of the queried animation's name, and that the track entry is yet completed."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"spawn"},(0,o.kt)("inlineCode",{parentName:"h4"},"spawn()")),(0,o.kt)("p",null,"Simply kick start the portal-in spawn animation. To be triggered externally."),(0,o.kt)("hr",null),(0,o.kt)("h4",{id:"isspawning"},(0,o.kt)("inlineCode",{parentName:"h4"},"isSpawning()")),(0,o.kt)("p",null,"Utilizing the ",(0,o.kt)("inlineCode",{parentName:"p"},"isAnimationPlaying(animation)")," to check if the spawn animation is still ongoing."),(0,o.kt)("hr",null),(0,o.kt)("h3",{id:"handling-direction"},"Handling Direction"),(0,o.kt)("p",null,"You may have noticed that the spine instance is now wrapped in an extra ",(0,o.kt)("inlineCode",{parentName:"p"},"directionalView")," container before being added to the main view. This is just to distinctly separate the transform, especially the horizontal scaling in this case where we will externally set to be ",(0,o.kt)("inlineCode",{parentName:"p"},"1")," for rightward or ",(0,o.kt)("inlineCode",{parentName:"p"},"-1")," for leftward depending on the controller input state. A getter and setter for ",(0,o.kt)("inlineCode",{parentName:"p"},"direction")," have been added for simplification."),(0,o.kt)("h3",{id:"spine-state-animation-default-mix"},"Spine State Animation Default Mix"),(0,o.kt)("p",null,(0,o.kt)("inlineCode",{parentName:"p"},"this.spine.state.data.defaultMix = 0.2")," sets the default amount of time in second for the state to blend the animations when transitioning from one to another for all animations, like a cross-fade of the skeletal positions."),(0,o.kt)("h2",{id:"update-loop"},"Update Loop"),(0,o.kt)("p",null,"The only thing left to do is to handle the animation according to the character state in real-time on the ",(0,o.kt)("inlineCode",{parentName:"p"},"update()")," method. Let's utilize all the stuff that has been prepared for us. In a logical order of priority for this specific example:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},(0,o.kt)("inlineCode",{parentName:"p"},"jump")," state should be handle immediately and the character should remain in the jump animation until it finishes even the jump state is no longer ",(0,o.kt)("inlineCode",{parentName:"p"},"true"),".")),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("p",{parentName:"li"},"The rest of the state members should trigger a corresponding animation immediately, depending on the priority order: ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," > ",(0,o.kt)("inlineCode",{parentName:"p"},"idle"),". Note that multiple state members can be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," at the same time, ie. ",(0,o.kt)("inlineCode",{parentName:"p"},"walk")," will be ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," while ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," is ",(0,o.kt)("inlineCode",{parentName:"p"},"true")," since the directional key is down in both scenarios."))),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"if (this.state.jump) this.playAnimation(animationMap.jump);\nif (this.isAnimationPlaying(animationMap.jump)) return;\nif (this.state.hover) this.playAnimation(animationMap.hover);\nelse if (this.state.run) this.playAnimation(animationMap.run);\nelse if (this.state.walk) this.playAnimation(animationMap.walk);\nelse this.playAnimation(animationMap.idle);\n")),(0,o.kt)("h2",{id:"connecting-to-controller"},"Connecting to Controller"),(0,o.kt)("p",null,"Back on ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's trigger the character's spawn animation at the start and update our application's ticker update callback."),(0,o.kt)("p",null,"On the callback, we should skip updating the character state and calling its local update loop while the spawn animation is happening. Otherwise, we can hook the controller input state to the character state as followed:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"left")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"right")," input ",(0,o.kt)("inlineCode",{parentName:"li"},"pressed")," state will toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"walk")," state and will update its direction value which should flip the character back and fourth horizontally to face the correct way. ",(0,o.kt)("inlineCode",{parentName:"li"},"doubleTap")," state will also toggle on character's ",(0,o.kt)("inlineCode",{parentName:"li"},"run")," state while still updating the direction accordingly."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"down")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"hover")," state."),(0,o.kt)("li",{parentName:"ul"},(0,o.kt)("inlineCode",{parentName:"li"},"space")," input state is dedicated to character's ",(0,o.kt)("inlineCode",{parentName:"li"},"jump")," state.")),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"spineBoy.spawn();\n\napp.ticker.add(() =>\n{\n if (spineBoy.isSpawning()) return;\n\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n spineBoy.update();\n});\n")),(0,o.kt)("p",null,"That's a wrap for our character! Now we need an environment for him to be moving in."))}Oe.isMDXComponent=!0;const Le={toc:[{value:"Sky",id:"sky",level:2},{value:"Parallax Layers",id:"parallax-layers",level:2},{value:"Adding the Scene",id:"adding-the-scene",level:2}]};function Fe(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Le,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"setting-up-scene"},"Setting Up Scene"),(0,o.kt)("p",null,"The scene is much less complicated and only involves a static ",(0,o.kt)("inlineCode",{parentName:"p"},"Sprite")," for the sky and 3 ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s for the parallax layers of the platform, the mid-ground and the background."),(0,o.kt)("p",null,"Again, a Scene class has been set up on another file with a view container added. And since we already preloaded all the required assets, we can go straight to the action."),(0,o.kt)("p",null,"We will establish the scene from bottom up so we are going to anchor all element at the bottom right corner."),(0,o.kt)("h2",{id:"sky"},"Sky"),(0,o.kt)("p",null,"Create the sky sprite, set the anchor as mentioned and use the passed in scene width and height as dimensions to fill up the whole scene."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.sky = Sprite.from('sky');\nthis.sky.anchor.set(0, 1);\nthis.sky.width = width;\nthis.sky.height = height;\n")),(0,o.kt)("h2",{id:"parallax-layers"},"Parallax Layers"),(0,o.kt)("p",null,"For the parallax layers, we begin by creating ",(0,o.kt)("inlineCode",{parentName:"p"},"Texture"),"s from the preloaded assets."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const backgroundTexture = Texture.from('background');\nconst midgroundTexture = Texture.from('midground');\nconst platformTexture = Texture.from('platform');\n")),(0,o.kt)("p",null,"We then calculate the ideal platform height which is 40% of the scene height but not exceeding the platform texture height. And then calculate a scale that we need to apply to the platform tiling texture to get it to the ideal height, which we also apply to other parallax layers for visual consistency."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const maxPlatformHeight = platformTexture.height;\nconst platformHeight = Math.min(maxPlatformHeight, height * 0.4);\nconst scale = this.scale = platformHeight / maxPlatformHeight;\n")),(0,o.kt)("p",null,"Now we can create the ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite")," objects from the defined textures and parameters."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n};\n\nthis.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n});\nthis.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n});\n")),(0,o.kt)("p",null,"After that, we need to horizontally offset the mid-ground and background layers to be just above the platform floor. Unfortunately, the platform tiling texture also includes the lamp element so we have to manually define the true height from the bottom of the platform to the floor surface. Let's store this as a member of the class, ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight"),", for external uses as well."),(0,o.kt)("p",null,"Then to wrap up the scene class, we just need to offset the mentioned layers up a ",(0,o.kt)("inlineCode",{parentName:"p"},"floorHeight")," amount and add all layers to the main view."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.floorHeight = platformHeight * 0.43;\nthis.background.y = this.midground.y = -this.floorHeight;\nthis.view.addChild(this.sky, this.background, this.midground, this.platform);\n")),(0,o.kt)("h2",{id:"adding-the-scene"},"Adding the Scene"),(0,o.kt)("p",null,"Note that ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js")," has already been updated to instantiate the scene and add it to the stage before the character."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"const scene = new Scene(app.screen.width, app.screen.height);\n\napp.stage.addChild(scene.view, spineBoy.view);\n")),(0,o.kt)("p",null,"The scene is then placed at the bottom the screen and the character's transformation has been updated to take into account the platform floor height and the scene scaling."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"scene.view.y = app.screen.height;\nspineBoy.view.x = app.screen.width / 2;\nspineBoy.view.y = app.screen.height - scene.floorHeight;\nspineBoy.spine.scale.set(scene.scale * 0.32);\n")))}Fe.isMDXComponent=!0;const ze={toc:[{value:"Getter",id:"getter",level:3},{value:"Setter",id:"setter",level:3}]};function Ye(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},ze,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"animating-scene"},"Animating Scene"),(0,o.kt)("p",null,"Last but not least, we need to match the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," scroll according to the character movement state."),(0,o.kt)("p",null,"Lets begin by having an unified ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property for the ",(0,o.kt)("inlineCode",{parentName:"p"},"Scene")," class. For the getter, this will simply return the ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the platform ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),", and similarly for the setter we set its ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," directly but also so set ",(0,o.kt)("inlineCode",{parentName:"p"},"tilePosition.x")," of the mid-ground and the background ",(0,o.kt)("inlineCode",{parentName:"p"},"TilingSprite"),"s at descending fractions of the value. This is to create a parallax scrolling effect for the backdrop layers as the platform horizontal position changes."),(0,o.kt)("h3",{id:"getter"},"Getter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"return this.platform.tilePosition.x;\n")),(0,o.kt)("h3",{id:"setter"},"Setter"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"this.background.tilePosition.x = value * 0.1;\nthis.midground.tilePosition.x = value * 0.25;\nthis.platform.tilePosition.x = value;\n")),(0,o.kt)("p",null,"Then on the main ",(0,o.kt)("inlineCode",{parentName:"p"},"index.js"),", let's manipulate this ",(0,o.kt)("inlineCode",{parentName:"p"},"positionX")," property at the end of the application's ticker callback to animate the scrolling accordingly. Here, we will use 3 different scrolling speeds for character's ",(0,o.kt)("inlineCode",{parentName:"p"},"walk"),", ",(0,o.kt)("inlineCode",{parentName:"p"},"run")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"hover")," state. We need to also add to or subtract from the property depending on the direction/"),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-javascript"},"let speed = 1.25;\n\nif (spineBoy.state.hover) speed = 7.5;\nelse if (spineBoy.state.run) speed = 3.75;\n\nif (spineBoy.state.walk)\n{\n scene.positionX -= speed * scene.scale * spineBoy.direction;\n}\n")),(0,o.kt)("p",null,"Et voil\xe0, we have a fully interactive side-scrolling experience! Have a play around with your own adventure creation."))}Ye.isMDXComponent=!0;const Je={toc:[]};function _e(e){let{components:n,...t}=e;return(0,o.kt)("wrapper",(0,i.Z)({},Je,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"you-did-it"},"You did it!"),(0,o.kt)("p",null,"Congratulations, we hope the adventure was worthwhile! There is so much more Spine and the Pixi Spine plugin can do so please feel free to check out Esoteric's official ",(0,o.kt)("a",{parentName:"p",href:"https://esotericsoftware.com/spine-api-reference"},"Spine runtime API documentation")," and explore our ",(0,o.kt)("a",{parentName:"p",href:"https://github.com/pixijs/spine-v8/tree/main/examples"},"Pixi Spine examples"),"."),(0,o.kt)("p",null,"Please also checkout our full list of plugins, libraries and tools in our ecosystem on the site navigation bar at the top."))}_e.isMDXComponent=!0;const Ue={"v7.0.0":m,"v8.0.0":{gettingStarted:{description:"Learn the basics of how to use PixiJS.",thumbnail:"thumb_getting_started.png",steps:Ae},fishPond:{description:"Let's create a lively fish pond!",thumbnail:"thumb_fish_pond.png",steps:ye},chooChooTrain:{description:"Onboard the graphical Choo Choo Train!",thumbnail:"thumb_choo_choo_train.png",steps:K},spineBoyAdventure:{description:"Behold the power of interactive Spine animation!",thumbnail:"thumb_spineboy_adventure.png",steps:[{header:"Introduction",Content:Be,code:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n})();\n"},{header:"Setting Up Character",Content:Ee,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // Create the main view.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust character transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n})();\n","src/SpineBoy.js*":Ne}},{header:"Adding Keyboard Controller",Content:Xe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n /** -- INSERT CODE HERE -- */\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":"// Map keyboard key codes to controller's state keys\nconst keyMap = {\n Space: 'space',\n KeyW: 'up',\n ArrowUp: 'up',\n KeyA: 'left',\n ArrowLeft: 'left',\n KeyS: 'down',\n ArrowDown: 'down',\n KeyD: 'right',\n ArrowRight: 'right',\n};\n\n// Class for handling keyboard inputs.\nexport class Controller\n{\n constructor()\n {\n // The controller's state.\n this.keys = {\n up: { pressed: false, doubleTap: false, timestamp: 0 },\n left: { pressed: false, doubleTap: false, timestamp: 0 },\n down: { pressed: false, doubleTap: false, timestamp: 0 },\n right: { pressed: false, doubleTap: false, timestamp: 0 },\n space: { pressed: false, doubleTap: false, timestamp: 0 },\n };\n\n // Register event listeners for keydown and keyup events.\n window.addEventListener('keydown', (event) => this.keydownHandler(event));\n window.addEventListener('keyup', (event) => this.keyupHandler(event));\n }\n\n keydownHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n\n keyupHandler(event)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n let currentAnimation;\n\n // Animate the character - just testing the controller at this point\n app.ticker.add((time) =>\n {\n const rightPressed = controller.keys.right.pressed;\n const animationName = rightPressed ? 'walk' : 'idle';\n const loop = true;\n\n // Apply the animation if it's different from the active one.\n if (currentAnimation !== animationName)\n {\n // Store the current animation name.\n currentAnimation = animationName;\n\n // Animate the character spine based on the right key state,\n spineBoy.spine.state.setAnimation(0, animationName, loop);\n }\n });\n})();\n","src/SpineBoy.js":Ne,"src/Controller.js*":Me}},{header:"Animating Character",Content:Oe,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js*":"import { Spine } from '@pixi/spine-pixi';\nimport { Container } from 'pixi.js';\n\n// Define the Spine animation map for the character.\n// name: animation track key.\n// loop: do the animation once or infinitely.\nconst animationMap = {\n idle: {\n name: 'idle',\n loop: true,\n },\n walk: {\n name: 'walk',\n loop: true,\n },\n run: {\n name: 'run',\n loop: true,\n },\n jump: {\n name: 'jump',\n timeScale: 1.5,\n },\n hover: {\n name: 'hoverboard',\n loop: true,\n },\n spawn: {\n name: 'portal',\n },\n};\n\n// Class for handling the character Spine and its animations.\nexport class SpineBoy\n{\n constructor()\n {\n // The character's state.\n this.state = {\n walk: false,\n run: false,\n hover: false,\n jump: false,\n };\n\n // Create the main view and a nested view for directional scaling.\n this.view = new Container();\n this.directionalView = new Container();\n\n // Create the Spine instance using the preloaded Spine asset aliases.\n this.spine = Spine.from({\n skeleton: 'spineSkeleton',\n atlas: 'spineAtlas',\n });\n\n // Add the Spine instance to the directional view.\n this.directionalView.addChild(this.spine);\n\n // Add the directional view to the main view.\n this.view.addChild(this.directionalView);\n\n // Set the default mix duration for all animations.\n // This is the duration to blend from the previous animation to the next.\n this.spine.state.data.defaultMix = 0.2;\n }\n\n // Play the portal-in spawn animation.\n spawn()\n {\n this.spine.state.setAnimation(0, animationMap.spawn.name);\n }\n\n // Play the spine animation.\n playAnimation({ name, loop = false, timeScale = 1 })\n {\n // Skip if the animation is already playing.\n if (this.currentAnimationName === name) return;\n\n // Play the animation on main track instantly.\n const trackEntry = this.spine.state.setAnimation(0, name, loop);\n\n // Apply the animation's time scale (speed).\n trackEntry.timeScale = timeScale;\n }\n\n update()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n isSpawning()\n {\n return this.isAnimationPlaying(animationMap.spawn);\n }\n\n isAnimationPlaying({ name })\n {\n // Check if the current animation on main track equals to the queried.\n // Also check if the animation is still ongoing.\n return this.currentAnimationName === name && !this.spine.state.getCurrent(0).isComplete();\n }\n\n // Return the name of the current animation on main track.\n get currentAnimationName()\n {\n return this.spine.state.getCurrent(0)?.animation.name;\n }\n\n // Return character's facing direction.\n get direction()\n {\n return this.directionalView.scale.x > 0 ? 1 : -1;\n }\n\n // Set character's facing direction.\n set direction(value)\n {\n this.directionalView.scale.x = value;\n }\n}\n","src/Controller.js":Me},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - 80;\n spineBoy.spine.scale.set(0.5);\n\n // Add character to the stage.\n app.stage.addChild(spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n });\n})();\n","src/SpineBoy.js*":He,"src/Controller.js":Me}},{header:"Setting Up Scene",Content:Fe,code:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:We,"src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js*":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n}\n"}},{header:"Animating Scene",Content:Ye,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n /** -- INSERT CODE HERE -- */\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":"import { Container, Sprite, Texture, TilingSprite } from 'pixi.js';\n\n// Class for handling the environment.\nexport class Scene\n{\n constructor(width, height)\n {\n // Create a main view that holds all layers.\n this.view = new Container();\n\n // Create the stationary sky that fills the entire screen.\n this.sky = Sprite.from('sky');\n this.sky.anchor.set(0, 1);\n this.sky.width = width;\n this.sky.height = height;\n\n // Create textures for the background, mid-ground, and platform.\n const backgroundTexture = Texture.from('background');\n const midgroundTexture = Texture.from('midground');\n const platformTexture = Texture.from('platform');\n\n // Calculate the ideal platform height depending on the passed-in screen height.\n const maxPlatformHeight = platformTexture.height;\n const platformHeight = Math.min(maxPlatformHeight, height * 0.4);\n\n // Calculate the scale to be apply to all tiling textures for consistency.\n const scale = (this.scale = platformHeight / maxPlatformHeight);\n\n const baseOptions = {\n tileScale: { x: scale, y: scale },\n anchor: { x: 0, y: 1 },\n applyAnchorToTexture: true,\n };\n\n // Create the tiling sprite layers.\n this.background = new TilingSprite({\n texture: backgroundTexture,\n width,\n height: backgroundTexture.height * scale,\n ...baseOptions,\n });\n this.midground = new TilingSprite({\n texture: midgroundTexture,\n width,\n height: midgroundTexture.height * scale,\n ...baseOptions,\n });\n this.platform = new TilingSprite({\n texture: platformTexture,\n width,\n height: platformHeight,\n ...baseOptions,\n });\n\n // Calculate the floor height for external referencing.\n this.floorHeight = platformHeight * 0.43;\n\n // Position the backdrop layers.\n this.background.y = this.midground.y = -this.floorHeight;\n\n // Add all layers to the main view.\n this.view.addChild(this.sky, this.background, this.midground, this.platform);\n }\n\n // Use the platform's horizontal position as the key position for the scene.\n get positionX()\n {\n /** -- INSERT CODE HERE -- */\n }\n\n // Set the horizontal position of the platform layer while applying parallax scrolling to the backdrop layers.\n set positionX(value)\n {\n /** -- INSERT CODE HERE -- */\n }\n}\n"},completedCode:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}},{header:"You did it!",Content:_e,code:{index:"import '@pixi/spine-pixi';\n\nimport { Application, Assets } from 'pixi.js';\nimport { SpineBoy } from './SpineBoy';\nimport { Controller } from './Controller';\nimport { Scene } from './Scene';\n\n// Asynchronous IIFE\n(async () =>\n{\n // Create a PixiJS application.\n const app = new Application();\n\n // Intialize the application.\n await app.init({ background: '#1099bb', resizeTo: window });\n\n // Then adding the application's canvas to the DOM body.\n document.body.appendChild(app.canvas);\n\n // Load the assets.\n await Assets.load([\n {\n alias: 'spineSkeleton',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel',\n },\n {\n alias: 'spineAtlas',\n src: 'https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas',\n },\n {\n alias: 'sky',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/sky.png',\n },\n {\n alias: 'background',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/background.png',\n },\n {\n alias: 'midground',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/midground.png',\n },\n {\n alias: 'platform',\n src: 'https://pixijs.com/assets/tutorials/spineboy-adventure/platform.png',\n },\n ]);\n\n // Create a controller that handles keyboard inputs.\n const controller = new Controller();\n\n // Create a scene that holds the environment.\n const scene = new Scene(app.screen.width, app.screen.height);\n\n // Create our character\n const spineBoy = new SpineBoy();\n\n // Adjust views' transformation.\n scene.view.y = app.screen.height;\n spineBoy.view.x = app.screen.width / 2;\n spineBoy.view.y = app.screen.height - scene.floorHeight;\n spineBoy.spine.scale.set(scene.scale * 0.32);\n\n // Add scene and character to the stage.\n app.stage.addChild(scene.view, spineBoy.view);\n\n // Trigger character's spawn animation.\n spineBoy.spawn();\n\n // Animate the scene and the character based on the controller's input.\n app.ticker.add(() =>\n {\n // Ignore the update loops while the character is doing the spawn animation.\n if (spineBoy.isSpawning()) return;\n\n // Update character's state based on the controller's input.\n spineBoy.state.walk = controller.keys.left.pressed || controller.keys.right.pressed;\n if (spineBoy.state.run && spineBoy.state.walk) spineBoy.state.run = true;\n else spineBoy.state.run = controller.keys.left.doubleTap || controller.keys.right.doubleTap;\n spineBoy.state.hover = controller.keys.down.pressed;\n if (controller.keys.left.pressed) spineBoy.direction = -1;\n else if (controller.keys.right.pressed) spineBoy.direction = 1;\n spineBoy.state.jump = controller.keys.space.pressed;\n\n // Update character's animation based on the latest state.\n spineBoy.update();\n\n // Determine the scene's horizontal scrolling speed based on the character's state.\n let speed = 1.25;\n\n if (spineBoy.state.hover) speed = 7.5;\n else if (spineBoy.state.run) speed = 3.75;\n\n // Shift the scene's position based on the character's facing direction, if in a movement state.\n if (spineBoy.state.walk) scene.positionX -= speed * scene.scale * spineBoy.direction;\n });\n})();\n","src/SpineBoy.js":He,"src/Controller.js":Me,"src/Scene.js":De}}],extraPackages:{"@pixi/spine-pixi":"^1.0.4"}}}};function Ze(e){const n=(0,a.prerelease)(e)?`${(0,a.major)(e)}.${(0,a.minor)(e)}.${(0,a.patch)(e)}`:e,t=Object.keys(Ue).filter((e=>(0,a.valid)(e)&&(0,a.lte)(e,n))).sort(((e,n)=>(0,a.rcompare)(e,n)))[0];return Ue[t]}function qe(e,n){const t=Ze(e);return null==t?void 0:t[n]}function Ve(e){const n=Ze(e),t=[];for(const a in n){const e=n[a],{description:i,thumbnail:o}=e;t.push({title:a,description:i,thumbnail:o})}return t}},9567:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>d,contentTitle:()=>p,default:()=>u,frontMatter:()=>r,metadata:()=>l,toc:()=>c});var a=t(7462),i=(t(7294),t(3905)),o=t(5103),s=t(7949);const r={hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null},p=void 0,l={unversionedId:"tutorials/getting-started",id:"tutorials/getting-started",title:"getting-started",description:"",source:"@site/docs/tutorials/getting-started.md",sourceDirName:"tutorials",slug:"/tutorials/getting-started",permalink:"/8.x/tutorials/getting-started",draft:!1,editUrl:null,tags:[],version:"current",frontMatter:{hide_title:!0,pagination_next:null,pagination_prev:null,custom_edit_url:null}},d={},c=[],h={toc:c};function u(e){let{components:n,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:n,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"gettingStarted",pixiVersion:s,mdxType:"Tutorial"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/935c73bf.13539d8f.js b/assets/js/935c73bf.13539d8f.js new file mode 100644 index 000000000..870e1d433 --- /dev/null +++ b/assets/js/935c73bf.13539d8f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4325],{5436:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>m,frontMatter:()=>n,metadata:()=>d,toc:()=>u});var i=t(7462),r=(t(7294),t(3905)),a=t(8010),l=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:7,custom_edit_url:null,title:"Slider"},o=void 0,d={unversionedId:"examples/events/slider",id:"examples/events/slider",title:"Slider",description:"",source:"@site/docs/examples/events/slider.md",sourceDirName:"examples/events",slug:"/examples/events/slider",permalink:"/8.x/examples/events/slider",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:7,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:7,custom_edit_url:null,title:"Slider"},sidebar:"examplesSidebar",previous:{title:"Pointer Tracker",permalink:"/8.x/examples/events/pointer-tracker"},next:{title:"Graphics",permalink:"/8.x/examples/masks/graphics"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(a.Z,{id:"events.slider",pixiVersion:l,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/935c73bf.40baf355.js b/assets/js/935c73bf.40baf355.js deleted file mode 100644 index 838012392..000000000 --- a/assets/js/935c73bf.40baf355.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4325],{5436:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>p,contentTitle:()=>o,default:()=>m,frontMatter:()=>n,metadata:()=>d,toc:()=>u});var i=t(7462),r=(t(7294),t(3905)),a=t(8010),l=t(7949);const n={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:7,custom_edit_url:null,title:"Slider"},o=void 0,d={unversionedId:"examples/events/slider",id:"examples/events/slider",title:"Slider",description:"",source:"@site/docs/examples/events/slider.md",sourceDirName:"examples/events",slug:"/examples/events/slider",permalink:"/examples/events/slider",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:7,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:7,custom_edit_url:null,title:"Slider"},sidebar:"examplesSidebar",previous:{title:"Pointer Tracker",permalink:"/examples/events/pointer-tracker"},next:{title:"Graphics",permalink:"/examples/masks/graphics"}},p={},u=[],c={toc:u};function m(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,i.Z)({},c,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(a.Z,{id:"events.slider",pixiVersion:l,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.4e1fccf6.js b/assets/js/935f2afb.4e1fccf6.js deleted file mode 100644 index c052fcb20..000000000 --- a/assets/js/935f2afb.4e1fccf6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"v8.x (Latest)","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":false,"docsSidebars":{"guidesSidebar":[{"type":"link","label":"Welcome","href":"/guides/","docId":"guides/index"},{"type":"category","label":"Basics","collapsed":true,"items":[{"type":"link","label":"What PixiJS Is","href":"/guides/basics/what-pixijs-is","docId":"guides/basics/what-pixijs-is"},{"type":"link","label":"What PixiJS Is Not","href":"/guides/basics/what-pixijs-is-not","docId":"guides/basics/what-pixijs-is-not"},{"type":"link","label":"Getting Started","href":"/guides/basics/getting-started","docId":"guides/basics/getting-started"},{"type":"link","label":"Architecture Overview","href":"/guides/basics/architecture-overview","docId":"guides/basics/architecture-overview"},{"type":"link","label":"Render Loop","href":"/guides/basics/render-loop","docId":"guides/basics/render-loop"},{"type":"link","label":"Scene Graph","href":"/guides/basics/scene-graph","docId":"guides/basics/scene-graph"}],"collapsible":true},{"type":"category","label":"Advanced","collapsed":true,"items":[{"type":"link","label":"Render Groups","href":"/guides/advanced/render-groups","docId":"guides/advanced/render-groups"}],"collapsible":true},{"type":"category","label":"Components","collapsed":true,"items":[{"type":"link","label":"Assets","href":"/guides/components/assets","docId":"guides/components/assets"},{"type":"link","label":"Containers","href":"/guides/components/containers","docId":"guides/components/containers"},{"type":"link","label":"Graphics","href":"/guides/components/graphics","docId":"guides/components/graphics"},{"type":"link","label":"Interaction","href":"/guides/components/interaction","docId":"guides/components/interaction"},{"type":"link","label":"Sprites","href":"/guides/components/sprites","docId":"guides/components/sprites"},{"type":"link","label":"Spritesheets","href":"/guides/components/sprite-sheets","docId":"guides/components/sprite-sheets"},{"type":"link","label":"Text","href":"/guides/components/text","docId":"guides/components/text"},{"type":"link","label":"Textures","href":"/guides/components/textures","docId":"guides/components/textures"}],"collapsible":true},{"type":"category","label":"Production","collapsed":true,"items":[{"type":"link","label":"Performance Tips","href":"/guides/production/performance-tips","docId":"guides/production/performance-tips"}],"collapsible":true},{"type":"category","label":"Migrations","collapsed":true,"items":[{"type":"link","label":"v8 Migration Guide","href":"/guides/migrations/v8","docId":"guides/migrations/v8"},{"type":"link","label":"v7 Migration Guide","href":"/guides/migrations/v7","docId":"guides/migrations/v7"},{"type":"link","label":"v6 Migration Guide","href":"/guides/migrations/v6","docId":"guides/migrations/v6"},{"type":"link","label":"v5 Migration Guide","href":"/guides/migrations/v5","docId":"guides/migrations/v5"}],"collapsible":true}],"examplesSidebar":[{"type":"link","label":"Examples","href":"/examples/","docId":"examples/index"},{"type":"category","label":"Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Container","href":"/examples/basic/container","docId":"examples/basic/container"},{"type":"link","label":"Transparent Background","href":"/examples/basic/transparent-background","docId":"examples/basic/transparent-background"},{"type":"link","label":"Tinting","href":"/examples/basic/tinting","docId":"examples/basic/tinting"},{"type":"link","label":"Particle Container","href":"/examples/basic/particle-container","docId":"examples/basic/particle-container"},{"type":"link","label":"Blend Modes","href":"/examples/basic/blend-modes","docId":"examples/basic/blend-modes"},{"type":"link","label":"Mesh Plane","href":"/examples/basic/mesh-plane","docId":"examples/basic/mesh-plane"},{"type":"link","label":"Render Group","href":"/examples/basic/render-group","docId":"examples/basic/render-group"}]},{"type":"category","label":"Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Slots","href":"/examples/advanced/slots","docId":"examples/advanced/slots"},{"type":"link","label":"Scratch Card","href":"/examples/advanced/scratch-card","docId":"examples/advanced/scratch-card"},{"type":"link","label":"Star Warp","href":"/examples/advanced/star-warp","docId":"examples/advanced/star-warp"},{"type":"link","label":"Mouse Trail","href":"/examples/advanced/mouse-trail","docId":"examples/advanced/mouse-trail"},{"type":"link","label":"Screen Shot","href":"/examples/advanced/screen-shot","docId":"examples/advanced/screen-shot"},{"type":"link","label":"Collision Detection","href":"/examples/advanced/collision-detection","docId":"examples/advanced/collision-detection"},{"type":"link","label":"Spinners","href":"/examples/advanced/spinners","docId":"examples/advanced/spinners"}]},{"type":"category","label":"Sprite","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/examples/sprite/basic","docId":"examples/sprite/basic"},{"type":"link","label":"Texture Swap","href":"/examples/sprite/texture-swap","docId":"examples/sprite/texture-swap"},{"type":"link","label":"Animated Sprite Explosion","href":"/examples/sprite/animated-sprite-explosion","docId":"examples/sprite/animated-sprite-explosion"},{"type":"link","label":"Animated Sprite Jet","href":"/examples/sprite/animated-sprite-jet","docId":"examples/sprite/animated-sprite-jet"},{"type":"link","label":"Animated Sprite Animation Speed","href":"/examples/sprite/animated-sprite-animation-speed","docId":"examples/sprite/animated-sprite-animation-speed"},{"type":"link","label":"Tiling Sprite","href":"/examples/sprite/tiling-sprite","docId":"examples/sprite/tiling-sprite"},{"type":"link","label":"Video","href":"/examples/sprite/video","docId":"examples/sprite/video"}]},{"type":"category","label":"Text","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Pixi Text","href":"/examples/text/pixi-text","docId":"examples/text/pixi-text"},{"type":"link","label":"Bitmap Text","href":"/examples/text/bitmap-text","docId":"examples/text/bitmap-text"},{"type":"link","label":"From Font","href":"/examples/text/from-font","docId":"examples/text/from-font"},{"type":"link","label":"Web Font","href":"/examples/text/web-font","docId":"examples/text/web-font"}]},{"type":"category","label":"Graphics","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Simple","href":"/examples/graphics/simple","docId":"examples/graphics/simple"},{"type":"link","label":"Advanced","href":"/examples/graphics/advanced","docId":"examples/graphics/advanced"},{"type":"link","label":"Dynamic","href":"/examples/graphics/dynamic","docId":"examples/graphics/dynamic"},{"type":"link","label":"Svg","href":"/examples/graphics/svg","docId":"examples/graphics/svg"},{"type":"link","label":"Svg Load","href":"/examples/graphics/svg-load","docId":"examples/graphics/svg-load"},{"type":"link","label":"Texture","href":"/examples/graphics/texture","docId":"examples/graphics/texture"},{"type":"link","label":"Fill Gradient","href":"/examples/graphics/fill-gradient","docId":"examples/graphics/fill-gradient"},{"type":"link","label":"Mesh From Path","href":"/examples/graphics/mesh-from-path","docId":"examples/graphics/mesh-from-path"}]},{"type":"category","label":"Events","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Click","href":"/examples/events/click","docId":"examples/events/click"},{"type":"link","label":"Interactivity","href":"/examples/events/interactivity","docId":"examples/events/interactivity"},{"type":"link","label":"Dragging","href":"/examples/events/dragging","docId":"examples/events/dragging"},{"type":"link","label":"Custom Mouse Icon","href":"/examples/events/custom-mouse-icon","docId":"examples/events/custom-mouse-icon"},{"type":"link","label":"Custom Hitarea","href":"/examples/events/custom-hitarea","docId":"examples/events/custom-hitarea"},{"type":"link","label":"Logger","href":"/examples/events/logger","docId":"examples/events/logger"},{"type":"link","label":"Pointer Tracker","href":"/examples/events/pointer-tracker","docId":"examples/events/pointer-tracker"},{"type":"link","label":"Slider","href":"/examples/events/slider","docId":"examples/events/slider"}]},{"type":"category","label":"Masks","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Graphics","href":"/examples/masks/graphics","docId":"examples/masks/graphics"},{"type":"link","label":"Sprite","href":"/examples/masks/sprite","docId":"examples/masks/sprite"},{"type":"link","label":"Filter","href":"/examples/masks/filter","docId":"examples/masks/filter"}]},{"type":"category","label":"Filters Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Blur","href":"/examples/filters-basic/blur","docId":"examples/filters-basic/blur"},{"type":"link","label":"Color Matrix","href":"/examples/filters-basic/color-matrix","docId":"examples/filters-basic/color-matrix"},{"type":"link","label":"Displacement Map Crawlies","href":"/examples/filters-basic/displacement-map-crawlies","docId":"examples/filters-basic/displacement-map-crawlies"},{"type":"link","label":"Displacement Map Flag","href":"/examples/filters-basic/displacement-map-flag","docId":"examples/filters-basic/displacement-map-flag"}]},{"type":"category","label":"Filters Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Mouse Blending","href":"/examples/filters-advanced/mouse-blending","docId":"examples/filters-advanced/mouse-blending"},{"type":"link","label":"Custom","href":"/examples/filters-advanced/custom","docId":"examples/filters-advanced/custom"}]},{"type":"category","label":"Mesh And Shaders","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Textured Mesh Basic","href":"/examples/mesh-and-shaders/textured-mesh-basic","docId":"examples/mesh-and-shaders/textured-mesh-basic"},{"type":"link","label":"Textured Mesh Advanced","href":"/examples/mesh-and-shaders/textured-mesh-advanced","docId":"examples/mesh-and-shaders/textured-mesh-advanced"},{"type":"link","label":"Triangle","href":"/examples/mesh-and-shaders/triangle","docId":"examples/mesh-and-shaders/triangle"},{"type":"link","label":"Triangle Color","href":"/examples/mesh-and-shaders/triangle-color","docId":"examples/mesh-and-shaders/triangle-color"},{"type":"link","label":"Triangle Textured","href":"/examples/mesh-and-shaders/triangle-textured","docId":"examples/mesh-and-shaders/triangle-textured"},{"type":"link","label":"Shared Geometry","href":"/examples/mesh-and-shaders/shared-geometry","docId":"examples/mesh-and-shaders/shared-geometry"},{"type":"link","label":"Shared Shader","href":"/examples/mesh-and-shaders/shared-shader","docId":"examples/mesh-and-shaders/shared-shader"},{"type":"link","label":"Instanced Geometry","href":"/examples/mesh-and-shaders/instanced-geometry","docId":"examples/mesh-and-shaders/instanced-geometry"},{"type":"link","label":"Shader Toy Mesh","href":"/examples/mesh-and-shaders/shader-toy-mesh","docId":"examples/mesh-and-shaders/shader-toy-mesh"},{"type":"link","label":"Multipass Mesh","href":"/examples/mesh-and-shaders/multipass-mesh","docId":"examples/mesh-and-shaders/multipass-mesh"}]},{"type":"category","label":"Textures","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Texture Rotate","href":"/examples/textures/texture-rotate","docId":"examples/textures/texture-rotate"},{"type":"link","label":"Render Texture Basic","href":"/examples/textures/render-texture-basic","docId":"examples/textures/render-texture-basic"},{"type":"link","label":"Render Texture Advanced","href":"/examples/textures/render-texture-advanced","docId":"examples/textures/render-texture-advanced"}]},{"type":"category","label":"Assets","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Promise","href":"/examples/assets/promise","docId":"examples/assets/promise"},{"type":"link","label":"Async","href":"/examples/assets/async","docId":"examples/assets/async"},{"type":"link","label":"Multiple","href":"/examples/assets/multiple","docId":"examples/assets/multiple"},{"type":"link","label":"Background","href":"/examples/assets/background","docId":"examples/assets/background"},{"type":"link","label":"Bundle","href":"/examples/assets/bundle","docId":"examples/assets/bundle"}]},{"type":"category","label":"Offscreen Canvas","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/examples/offscreen-canvas/basic","docId":"examples/offscreen-canvas/basic"}]}]},"docs":{"branding":{"id":"branding","title":"Branding","description":"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue."},"examples/advanced/collision-detection":{"id":"examples/advanced/collision-detection","title":"Collision Detection","description":"","sidebar":"examplesSidebar"},"examples/advanced/mouse-trail":{"id":"examples/advanced/mouse-trail","title":"Mouse Trail","description":"","sidebar":"examplesSidebar"},"examples/advanced/scratch-card":{"id":"examples/advanced/scratch-card","title":"Scratch Card","description":"","sidebar":"examplesSidebar"},"examples/advanced/screen-shot":{"id":"examples/advanced/screen-shot","title":"Screen Shot","description":"","sidebar":"examplesSidebar"},"examples/advanced/slots":{"id":"examples/advanced/slots","title":"Slots","description":"","sidebar":"examplesSidebar"},"examples/advanced/spinners":{"id":"examples/advanced/spinners","title":"Spinners","description":"","sidebar":"examplesSidebar"},"examples/advanced/star-warp":{"id":"examples/advanced/star-warp","title":"Star Warp","description":"","sidebar":"examplesSidebar"},"examples/assets/async":{"id":"examples/assets/async","title":"Async","description":"","sidebar":"examplesSidebar"},"examples/assets/background":{"id":"examples/assets/background","title":"Background","description":"","sidebar":"examplesSidebar"},"examples/assets/bundle":{"id":"examples/assets/bundle","title":"Bundle","description":"","sidebar":"examplesSidebar"},"examples/assets/multiple":{"id":"examples/assets/multiple","title":"Multiple","description":"","sidebar":"examplesSidebar"},"examples/assets/promise":{"id":"examples/assets/promise","title":"Promise","description":"","sidebar":"examplesSidebar"},"examples/basic/blend-modes":{"id":"examples/basic/blend-modes","title":"Blend Modes","description":"","sidebar":"examplesSidebar"},"examples/basic/container":{"id":"examples/basic/container","title":"Container","description":"","sidebar":"examplesSidebar"},"examples/basic/mesh-plane":{"id":"examples/basic/mesh-plane","title":"Mesh Plane","description":"","sidebar":"examplesSidebar"},"examples/basic/particle-container":{"id":"examples/basic/particle-container","title":"Particle Container","description":"","sidebar":"examplesSidebar"},"examples/basic/render-group":{"id":"examples/basic/render-group","title":"Render Group","description":"","sidebar":"examplesSidebar"},"examples/basic/tinting":{"id":"examples/basic/tinting","title":"Tinting","description":"","sidebar":"examplesSidebar"},"examples/basic/transparent-background":{"id":"examples/basic/transparent-background","title":"Transparent Background","description":"","sidebar":"examplesSidebar"},"examples/events/click":{"id":"examples/events/click","title":"Click","description":"","sidebar":"examplesSidebar"},"examples/events/custom-hitarea":{"id":"examples/events/custom-hitarea","title":"Custom Hitarea","description":"","sidebar":"examplesSidebar"},"examples/events/custom-mouse-icon":{"id":"examples/events/custom-mouse-icon","title":"Custom Mouse Icon","description":"","sidebar":"examplesSidebar"},"examples/events/dragging":{"id":"examples/events/dragging","title":"Dragging","description":"","sidebar":"examplesSidebar"},"examples/events/interactivity":{"id":"examples/events/interactivity","title":"Interactivity","description":"","sidebar":"examplesSidebar"},"examples/events/logger":{"id":"examples/events/logger","title":"Logger","description":"","sidebar":"examplesSidebar"},"examples/events/pointer-tracker":{"id":"examples/events/pointer-tracker","title":"Pointer Tracker","description":"","sidebar":"examplesSidebar"},"examples/events/slider":{"id":"examples/events/slider","title":"Slider","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/custom":{"id":"examples/filters-advanced/custom","title":"Custom","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/mouse-blending":{"id":"examples/filters-advanced/mouse-blending","title":"Mouse Blending","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/blur":{"id":"examples/filters-basic/blur","title":"Blur","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/color-matrix":{"id":"examples/filters-basic/color-matrix","title":"Color Matrix","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-crawlies":{"id":"examples/filters-basic/displacement-map-crawlies","title":"Displacement Map Crawlies","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-flag":{"id":"examples/filters-basic/displacement-map-flag","title":"Displacement Map Flag","description":"","sidebar":"examplesSidebar"},"examples/graphics/advanced":{"id":"examples/graphics/advanced","title":"Advanced","description":"","sidebar":"examplesSidebar"},"examples/graphics/dynamic":{"id":"examples/graphics/dynamic","title":"Dynamic","description":"","sidebar":"examplesSidebar"},"examples/graphics/fill-gradient":{"id":"examples/graphics/fill-gradient","title":"Fill Gradient","description":"","sidebar":"examplesSidebar"},"examples/graphics/mesh-from-path":{"id":"examples/graphics/mesh-from-path","title":"Mesh From Path","description":"","sidebar":"examplesSidebar"},"examples/graphics/simple":{"id":"examples/graphics/simple","title":"Simple","description":"","sidebar":"examplesSidebar"},"examples/graphics/svg":{"id":"examples/graphics/svg","title":"Svg","description":"","sidebar":"examplesSidebar"},"examples/graphics/svg-load":{"id":"examples/graphics/svg-load","title":"Svg Load","description":"","sidebar":"examplesSidebar"},"examples/graphics/texture":{"id":"examples/graphics/texture","title":"Texture","description":"","sidebar":"examplesSidebar"},"examples/index":{"id":"examples/index","title":"Examples","description":"Welcome to the PixiJS Examples page! Here you can find a variety of demos and code snippets to help you get started with PixiJS.","sidebar":"examplesSidebar"},"examples/masks/filter":{"id":"examples/masks/filter","title":"Filter","description":"","sidebar":"examplesSidebar"},"examples/masks/graphics":{"id":"examples/masks/graphics","title":"Graphics","description":"","sidebar":"examplesSidebar"},"examples/masks/sprite":{"id":"examples/masks/sprite","title":"Sprite","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/instanced-geometry":{"id":"examples/mesh-and-shaders/instanced-geometry","title":"Instanced Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/multipass-mesh":{"id":"examples/mesh-and-shaders/multipass-mesh","title":"Multipass Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shader-toy-mesh":{"id":"examples/mesh-and-shaders/shader-toy-mesh","title":"Shader Toy Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-geometry":{"id":"examples/mesh-and-shaders/shared-geometry","title":"Shared Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-shader":{"id":"examples/mesh-and-shaders/shared-shader","title":"Shared Shader","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-advanced":{"id":"examples/mesh-and-shaders/textured-mesh-advanced","title":"Textured Mesh Advanced","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-basic":{"id":"examples/mesh-and-shaders/textured-mesh-basic","title":"Textured Mesh Basic","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle":{"id":"examples/mesh-and-shaders/triangle","title":"Triangle","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-color":{"id":"examples/mesh-and-shaders/triangle-color","title":"Triangle Color","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-textured":{"id":"examples/mesh-and-shaders/triangle-textured","title":"Triangle Textured","description":"","sidebar":"examplesSidebar"},"examples/offscreen-canvas/basic":{"id":"examples/offscreen-canvas/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-animation-speed":{"id":"examples/sprite/animated-sprite-animation-speed","title":"Animated Sprite Animation Speed","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-explosion":{"id":"examples/sprite/animated-sprite-explosion","title":"Animated Sprite Explosion","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-jet":{"id":"examples/sprite/animated-sprite-jet","title":"Animated Sprite Jet","description":"","sidebar":"examplesSidebar"},"examples/sprite/basic":{"id":"examples/sprite/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/texture-swap":{"id":"examples/sprite/texture-swap","title":"Texture Swap","description":"","sidebar":"examplesSidebar"},"examples/sprite/tiling-sprite":{"id":"examples/sprite/tiling-sprite","title":"Tiling Sprite","description":"","sidebar":"examplesSidebar"},"examples/sprite/video":{"id":"examples/sprite/video","title":"Video","description":"","sidebar":"examplesSidebar"},"examples/text/bitmap-text":{"id":"examples/text/bitmap-text","title":"Bitmap Text","description":"","sidebar":"examplesSidebar"},"examples/text/from-font":{"id":"examples/text/from-font","title":"From Font","description":"","sidebar":"examplesSidebar"},"examples/text/pixi-text":{"id":"examples/text/pixi-text","title":"Pixi Text","description":"","sidebar":"examplesSidebar"},"examples/text/web-font":{"id":"examples/text/web-font","title":"Web Font","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-advanced":{"id":"examples/textures/render-texture-advanced","title":"Render Texture Advanced","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-basic":{"id":"examples/textures/render-texture-basic","title":"Render Texture Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/texture-rotate":{"id":"examples/textures/texture-rotate","title":"Texture Rotate","description":"","sidebar":"examplesSidebar"},"faq":{"id":"faq","title":"FAQ","description":"What is PixiJS for?"},"guides/advanced/render-groups":{"id":"guides/advanced/render-groups","title":"Render Groups","description":"Understanding RenderGroups in PixiJS","sidebar":"guidesSidebar"},"guides/basics/architecture-overview":{"id":"guides/basics/architecture-overview","title":"Architecture Overview","description":"OK, now that you\'ve gotten a feel for how easy it is to build a PixiJS application, let\'s get into the specifics. For the rest of the Basics section, we\'re going to work from the high level down to the details. We\'ll start with an overview of how PixiJS is put together.","sidebar":"guidesSidebar"},"guides/basics/getting-started":{"id":"guides/basics/getting-started","title":"Getting Started","description":"In this section we\'re going to build the simplest possible PixiJS application. In doing so, we\'ll walk through the basics of how to build and serve the code.","sidebar":"guidesSidebar"},"guides/basics/render-loop":{"id":"guides/basics/render-loop","title":"Render Loop","description":"Now that you understand the major parts of the system, let\'s look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.","sidebar":"guidesSidebar"},"guides/basics/scene-graph":{"id":"guides/basics/scene-graph","title":"Scene Graph","description":"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.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is":{"id":"guides/basics/what-pixijs-is","title":"What PixiJS Is","description":"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is-not":{"id":"guides/basics/what-pixijs-is-not","title":"What PixiJS Is Not","description":"While PixiJS can do many things, there are things it can\'t do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you\'re about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.","sidebar":"guidesSidebar"},"guides/components/assets":{"id":"guides/components/assets","title":"Assets","description":"The Assets package","sidebar":"guidesSidebar"},"guides/components/containers":{"id":"guides/components/containers","title":"Containers","description":"The Container 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.","sidebar":"guidesSidebar"},"guides/components/graphics":{"id":"guides/components/graphics","title":"Graphics","description":"Graphics 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?","sidebar":"guidesSidebar"},"guides/components/interaction":{"id":"guides/components/interaction","title":"Interaction","description":"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.","sidebar":"guidesSidebar"},"guides/components/sprite-sheets":{"id":"guides/components/sprite-sheets","title":"Spritesheets","description":"Now that you understand basic sprites, it\'s time to talk about a better way to create them - the Spritesheet class.","sidebar":"guidesSidebar"},"guides/components/sprites":{"id":"guides/components/sprites","title":"Sprites","description":"Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.","sidebar":"guidesSidebar"},"guides/components/text":{"id":"guides/components/text","title":"Text","description":"Whether it\'s a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there\'s no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.","sidebar":"guidesSidebar"},"guides/components/textures":{"id":"guides/components/textures","title":"Textures","description":"We\'re slowly working our way down from the high level to the low. We\'ve talked about the scene graph, and in general about display objects that live in it. We\'re about to get to sprites and other simple display objects. But before we do, we need to talk about textures.","sidebar":"guidesSidebar"},"guides/index":{"id":"guides/index","title":"Welcome","description":"PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.","sidebar":"guidesSidebar"},"guides/migrations/v5":{"id":"guides/migrations/v5","title":"v5 Migration Guide","description":"This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we\'ve try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.","sidebar":"guidesSidebar"},"guides/migrations/v6":{"id":"guides/migrations/v6","title":"v6 Migration Guide","description":"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.","sidebar":"guidesSidebar"},"guides/migrations/v7":{"id":"guides/migrations/v7","title":"v7 Migration Guide","description":"First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn\'t really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.","sidebar":"guidesSidebar"},"guides/migrations/v8":{"id":"guides/migrations/v8","title":"v8 Migration Guide","description":"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.","sidebar":"guidesSidebar"},"guides/production/performance-tips":{"id":"guides/production/performance-tips","title":"Performance Tips","description":"General","sidebar":"guidesSidebar"},"playground/index":{"id":"playground/index","title":"index","description":""},"tutorials/choo-choo-train":{"id":"tutorials/choo-choo-train","title":"choo-choo-train","description":""},"tutorials/fish-pond":{"id":"tutorials/fish-pond","title":"fish-pond","description":""},"tutorials/getting-started":{"id":"tutorials/getting-started","title":"getting-started","description":""},"tutorials/index":{"id":"tutorials/index","title":"Tutorials","description":"Welcome to the tutorials page! Here you can find hand-crafted exercises to get you started with the PixiJS."},"tutorials/spine-boy-adventure":{"id":"tutorials/spine-boy-adventure","title":"spine-boy-adventure","description":""}}}')}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.ec9c0f38.js b/assets/js/935f2afb.ec9c0f38.js new file mode 100644 index 000000000..dffbe1447 --- /dev/null +++ b/assets/js/935f2afb.ec9c0f38.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[53],{1109:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"v8.x","banner":null,"badge":true,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"guidesSidebar":[{"type":"link","label":"Welcome","href":"/8.x/guides/","docId":"guides/index"},{"type":"category","label":"Basics","collapsed":true,"items":[{"type":"link","label":"What PixiJS Is","href":"/8.x/guides/basics/what-pixijs-is","docId":"guides/basics/what-pixijs-is"},{"type":"link","label":"What PixiJS Is Not","href":"/8.x/guides/basics/what-pixijs-is-not","docId":"guides/basics/what-pixijs-is-not"},{"type":"link","label":"Getting Started","href":"/8.x/guides/basics/getting-started","docId":"guides/basics/getting-started"},{"type":"link","label":"Architecture Overview","href":"/8.x/guides/basics/architecture-overview","docId":"guides/basics/architecture-overview"},{"type":"link","label":"Render Loop","href":"/8.x/guides/basics/render-loop","docId":"guides/basics/render-loop"},{"type":"link","label":"Scene Graph","href":"/8.x/guides/basics/scene-graph","docId":"guides/basics/scene-graph"}],"collapsible":true},{"type":"category","label":"Advanced","collapsed":true,"items":[{"type":"link","label":"Render Groups","href":"/8.x/guides/advanced/render-groups","docId":"guides/advanced/render-groups"}],"collapsible":true},{"type":"category","label":"Components","collapsed":true,"items":[{"type":"link","label":"Assets","href":"/8.x/guides/components/assets","docId":"guides/components/assets"},{"type":"link","label":"Containers","href":"/8.x/guides/components/containers","docId":"guides/components/containers"},{"type":"link","label":"Graphics","href":"/8.x/guides/components/graphics","docId":"guides/components/graphics"},{"type":"link","label":"Interaction","href":"/8.x/guides/components/interaction","docId":"guides/components/interaction"},{"type":"link","label":"Sprites","href":"/8.x/guides/components/sprites","docId":"guides/components/sprites"},{"type":"link","label":"Spritesheets","href":"/8.x/guides/components/sprite-sheets","docId":"guides/components/sprite-sheets"},{"type":"link","label":"Text","href":"/8.x/guides/components/text","docId":"guides/components/text"},{"type":"link","label":"Textures","href":"/8.x/guides/components/textures","docId":"guides/components/textures"}],"collapsible":true},{"type":"category","label":"Production","collapsed":true,"items":[{"type":"link","label":"Performance Tips","href":"/8.x/guides/production/performance-tips","docId":"guides/production/performance-tips"}],"collapsible":true},{"type":"category","label":"Migrations","collapsed":true,"items":[{"type":"link","label":"v8 Migration Guide","href":"/8.x/guides/migrations/v8","docId":"guides/migrations/v8"},{"type":"link","label":"v7 Migration Guide","href":"/8.x/guides/migrations/v7","docId":"guides/migrations/v7"},{"type":"link","label":"v6 Migration Guide","href":"/8.x/guides/migrations/v6","docId":"guides/migrations/v6"},{"type":"link","label":"v5 Migration Guide","href":"/8.x/guides/migrations/v5","docId":"guides/migrations/v5"}],"collapsible":true}],"examplesSidebar":[{"type":"link","label":"Examples","href":"/8.x/examples/","docId":"examples/index"},{"type":"category","label":"Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Container","href":"/8.x/examples/basic/container","docId":"examples/basic/container"},{"type":"link","label":"Transparent Background","href":"/8.x/examples/basic/transparent-background","docId":"examples/basic/transparent-background"},{"type":"link","label":"Tinting","href":"/8.x/examples/basic/tinting","docId":"examples/basic/tinting"},{"type":"link","label":"Particle Container","href":"/8.x/examples/basic/particle-container","docId":"examples/basic/particle-container"},{"type":"link","label":"Blend Modes","href":"/8.x/examples/basic/blend-modes","docId":"examples/basic/blend-modes"},{"type":"link","label":"Mesh Plane","href":"/8.x/examples/basic/mesh-plane","docId":"examples/basic/mesh-plane"},{"type":"link","label":"Render Group","href":"/8.x/examples/basic/render-group","docId":"examples/basic/render-group"}]},{"type":"category","label":"Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Slots","href":"/8.x/examples/advanced/slots","docId":"examples/advanced/slots"},{"type":"link","label":"Scratch Card","href":"/8.x/examples/advanced/scratch-card","docId":"examples/advanced/scratch-card"},{"type":"link","label":"Star Warp","href":"/8.x/examples/advanced/star-warp","docId":"examples/advanced/star-warp"},{"type":"link","label":"Mouse Trail","href":"/8.x/examples/advanced/mouse-trail","docId":"examples/advanced/mouse-trail"},{"type":"link","label":"Screen Shot","href":"/8.x/examples/advanced/screen-shot","docId":"examples/advanced/screen-shot"},{"type":"link","label":"Collision Detection","href":"/8.x/examples/advanced/collision-detection","docId":"examples/advanced/collision-detection"},{"type":"link","label":"Spinners","href":"/8.x/examples/advanced/spinners","docId":"examples/advanced/spinners"}]},{"type":"category","label":"Sprite","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/8.x/examples/sprite/basic","docId":"examples/sprite/basic"},{"type":"link","label":"Texture Swap","href":"/8.x/examples/sprite/texture-swap","docId":"examples/sprite/texture-swap"},{"type":"link","label":"Animated Sprite Explosion","href":"/8.x/examples/sprite/animated-sprite-explosion","docId":"examples/sprite/animated-sprite-explosion"},{"type":"link","label":"Animated Sprite Jet","href":"/8.x/examples/sprite/animated-sprite-jet","docId":"examples/sprite/animated-sprite-jet"},{"type":"link","label":"Animated Sprite Animation Speed","href":"/8.x/examples/sprite/animated-sprite-animation-speed","docId":"examples/sprite/animated-sprite-animation-speed"},{"type":"link","label":"Tiling Sprite","href":"/8.x/examples/sprite/tiling-sprite","docId":"examples/sprite/tiling-sprite"},{"type":"link","label":"Video","href":"/8.x/examples/sprite/video","docId":"examples/sprite/video"}]},{"type":"category","label":"Text","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Pixi Text","href":"/8.x/examples/text/pixi-text","docId":"examples/text/pixi-text"},{"type":"link","label":"Bitmap Text","href":"/8.x/examples/text/bitmap-text","docId":"examples/text/bitmap-text"},{"type":"link","label":"From Font","href":"/8.x/examples/text/from-font","docId":"examples/text/from-font"},{"type":"link","label":"Web Font","href":"/8.x/examples/text/web-font","docId":"examples/text/web-font"}]},{"type":"category","label":"Graphics","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Simple","href":"/8.x/examples/graphics/simple","docId":"examples/graphics/simple"},{"type":"link","label":"Advanced","href":"/8.x/examples/graphics/advanced","docId":"examples/graphics/advanced"},{"type":"link","label":"Dynamic","href":"/8.x/examples/graphics/dynamic","docId":"examples/graphics/dynamic"},{"type":"link","label":"Svg","href":"/8.x/examples/graphics/svg","docId":"examples/graphics/svg"},{"type":"link","label":"Svg Load","href":"/8.x/examples/graphics/svg-load","docId":"examples/graphics/svg-load"},{"type":"link","label":"Texture","href":"/8.x/examples/graphics/texture","docId":"examples/graphics/texture"},{"type":"link","label":"Fill Gradient","href":"/8.x/examples/graphics/fill-gradient","docId":"examples/graphics/fill-gradient"},{"type":"link","label":"Mesh From Path","href":"/8.x/examples/graphics/mesh-from-path","docId":"examples/graphics/mesh-from-path"}]},{"type":"category","label":"Events","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Click","href":"/8.x/examples/events/click","docId":"examples/events/click"},{"type":"link","label":"Interactivity","href":"/8.x/examples/events/interactivity","docId":"examples/events/interactivity"},{"type":"link","label":"Dragging","href":"/8.x/examples/events/dragging","docId":"examples/events/dragging"},{"type":"link","label":"Custom Mouse Icon","href":"/8.x/examples/events/custom-mouse-icon","docId":"examples/events/custom-mouse-icon"},{"type":"link","label":"Custom Hitarea","href":"/8.x/examples/events/custom-hitarea","docId":"examples/events/custom-hitarea"},{"type":"link","label":"Logger","href":"/8.x/examples/events/logger","docId":"examples/events/logger"},{"type":"link","label":"Pointer Tracker","href":"/8.x/examples/events/pointer-tracker","docId":"examples/events/pointer-tracker"},{"type":"link","label":"Slider","href":"/8.x/examples/events/slider","docId":"examples/events/slider"}]},{"type":"category","label":"Masks","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Graphics","href":"/8.x/examples/masks/graphics","docId":"examples/masks/graphics"},{"type":"link","label":"Sprite","href":"/8.x/examples/masks/sprite","docId":"examples/masks/sprite"},{"type":"link","label":"Filter","href":"/8.x/examples/masks/filter","docId":"examples/masks/filter"}]},{"type":"category","label":"Filters Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Blur","href":"/8.x/examples/filters-basic/blur","docId":"examples/filters-basic/blur"},{"type":"link","label":"Color Matrix","href":"/8.x/examples/filters-basic/color-matrix","docId":"examples/filters-basic/color-matrix"},{"type":"link","label":"Displacement Map Crawlies","href":"/8.x/examples/filters-basic/displacement-map-crawlies","docId":"examples/filters-basic/displacement-map-crawlies"},{"type":"link","label":"Displacement Map Flag","href":"/8.x/examples/filters-basic/displacement-map-flag","docId":"examples/filters-basic/displacement-map-flag"}]},{"type":"category","label":"Filters Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Mouse Blending","href":"/8.x/examples/filters-advanced/mouse-blending","docId":"examples/filters-advanced/mouse-blending"},{"type":"link","label":"Custom","href":"/8.x/examples/filters-advanced/custom","docId":"examples/filters-advanced/custom"}]},{"type":"category","label":"Mesh And Shaders","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Textured Mesh Basic","href":"/8.x/examples/mesh-and-shaders/textured-mesh-basic","docId":"examples/mesh-and-shaders/textured-mesh-basic"},{"type":"link","label":"Textured Mesh Advanced","href":"/8.x/examples/mesh-and-shaders/textured-mesh-advanced","docId":"examples/mesh-and-shaders/textured-mesh-advanced"},{"type":"link","label":"Triangle","href":"/8.x/examples/mesh-and-shaders/triangle","docId":"examples/mesh-and-shaders/triangle"},{"type":"link","label":"Triangle Color","href":"/8.x/examples/mesh-and-shaders/triangle-color","docId":"examples/mesh-and-shaders/triangle-color"},{"type":"link","label":"Triangle Textured","href":"/8.x/examples/mesh-and-shaders/triangle-textured","docId":"examples/mesh-and-shaders/triangle-textured"},{"type":"link","label":"Shared Geometry","href":"/8.x/examples/mesh-and-shaders/shared-geometry","docId":"examples/mesh-and-shaders/shared-geometry"},{"type":"link","label":"Shared Shader","href":"/8.x/examples/mesh-and-shaders/shared-shader","docId":"examples/mesh-and-shaders/shared-shader"},{"type":"link","label":"Instanced Geometry","href":"/8.x/examples/mesh-and-shaders/instanced-geometry","docId":"examples/mesh-and-shaders/instanced-geometry"},{"type":"link","label":"Shader Toy Mesh","href":"/8.x/examples/mesh-and-shaders/shader-toy-mesh","docId":"examples/mesh-and-shaders/shader-toy-mesh"},{"type":"link","label":"Multipass Mesh","href":"/8.x/examples/mesh-and-shaders/multipass-mesh","docId":"examples/mesh-and-shaders/multipass-mesh"}]},{"type":"category","label":"Textures","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Texture Rotate","href":"/8.x/examples/textures/texture-rotate","docId":"examples/textures/texture-rotate"},{"type":"link","label":"Render Texture Basic","href":"/8.x/examples/textures/render-texture-basic","docId":"examples/textures/render-texture-basic"},{"type":"link","label":"Render Texture Advanced","href":"/8.x/examples/textures/render-texture-advanced","docId":"examples/textures/render-texture-advanced"}]},{"type":"category","label":"Assets","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Promise","href":"/8.x/examples/assets/promise","docId":"examples/assets/promise"},{"type":"link","label":"Async","href":"/8.x/examples/assets/async","docId":"examples/assets/async"},{"type":"link","label":"Multiple","href":"/8.x/examples/assets/multiple","docId":"examples/assets/multiple"},{"type":"link","label":"Background","href":"/8.x/examples/assets/background","docId":"examples/assets/background"},{"type":"link","label":"Bundle","href":"/8.x/examples/assets/bundle","docId":"examples/assets/bundle"}]},{"type":"category","label":"Offscreen Canvas","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/8.x/examples/offscreen-canvas/basic","docId":"examples/offscreen-canvas/basic"}]}]},"docs":{"branding":{"id":"branding","title":"Branding","description":"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue."},"examples/advanced/collision-detection":{"id":"examples/advanced/collision-detection","title":"Collision Detection","description":"","sidebar":"examplesSidebar"},"examples/advanced/mouse-trail":{"id":"examples/advanced/mouse-trail","title":"Mouse Trail","description":"","sidebar":"examplesSidebar"},"examples/advanced/scratch-card":{"id":"examples/advanced/scratch-card","title":"Scratch Card","description":"","sidebar":"examplesSidebar"},"examples/advanced/screen-shot":{"id":"examples/advanced/screen-shot","title":"Screen Shot","description":"","sidebar":"examplesSidebar"},"examples/advanced/slots":{"id":"examples/advanced/slots","title":"Slots","description":"","sidebar":"examplesSidebar"},"examples/advanced/spinners":{"id":"examples/advanced/spinners","title":"Spinners","description":"","sidebar":"examplesSidebar"},"examples/advanced/star-warp":{"id":"examples/advanced/star-warp","title":"Star Warp","description":"","sidebar":"examplesSidebar"},"examples/assets/async":{"id":"examples/assets/async","title":"Async","description":"","sidebar":"examplesSidebar"},"examples/assets/background":{"id":"examples/assets/background","title":"Background","description":"","sidebar":"examplesSidebar"},"examples/assets/bundle":{"id":"examples/assets/bundle","title":"Bundle","description":"","sidebar":"examplesSidebar"},"examples/assets/multiple":{"id":"examples/assets/multiple","title":"Multiple","description":"","sidebar":"examplesSidebar"},"examples/assets/promise":{"id":"examples/assets/promise","title":"Promise","description":"","sidebar":"examplesSidebar"},"examples/basic/blend-modes":{"id":"examples/basic/blend-modes","title":"Blend Modes","description":"","sidebar":"examplesSidebar"},"examples/basic/container":{"id":"examples/basic/container","title":"Container","description":"","sidebar":"examplesSidebar"},"examples/basic/mesh-plane":{"id":"examples/basic/mesh-plane","title":"Mesh Plane","description":"","sidebar":"examplesSidebar"},"examples/basic/particle-container":{"id":"examples/basic/particle-container","title":"Particle Container","description":"","sidebar":"examplesSidebar"},"examples/basic/render-group":{"id":"examples/basic/render-group","title":"Render Group","description":"","sidebar":"examplesSidebar"},"examples/basic/tinting":{"id":"examples/basic/tinting","title":"Tinting","description":"","sidebar":"examplesSidebar"},"examples/basic/transparent-background":{"id":"examples/basic/transparent-background","title":"Transparent Background","description":"","sidebar":"examplesSidebar"},"examples/events/click":{"id":"examples/events/click","title":"Click","description":"","sidebar":"examplesSidebar"},"examples/events/custom-hitarea":{"id":"examples/events/custom-hitarea","title":"Custom Hitarea","description":"","sidebar":"examplesSidebar"},"examples/events/custom-mouse-icon":{"id":"examples/events/custom-mouse-icon","title":"Custom Mouse Icon","description":"","sidebar":"examplesSidebar"},"examples/events/dragging":{"id":"examples/events/dragging","title":"Dragging","description":"","sidebar":"examplesSidebar"},"examples/events/interactivity":{"id":"examples/events/interactivity","title":"Interactivity","description":"","sidebar":"examplesSidebar"},"examples/events/logger":{"id":"examples/events/logger","title":"Logger","description":"","sidebar":"examplesSidebar"},"examples/events/pointer-tracker":{"id":"examples/events/pointer-tracker","title":"Pointer Tracker","description":"","sidebar":"examplesSidebar"},"examples/events/slider":{"id":"examples/events/slider","title":"Slider","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/custom":{"id":"examples/filters-advanced/custom","title":"Custom","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/mouse-blending":{"id":"examples/filters-advanced/mouse-blending","title":"Mouse Blending","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/blur":{"id":"examples/filters-basic/blur","title":"Blur","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/color-matrix":{"id":"examples/filters-basic/color-matrix","title":"Color Matrix","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-crawlies":{"id":"examples/filters-basic/displacement-map-crawlies","title":"Displacement Map Crawlies","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-flag":{"id":"examples/filters-basic/displacement-map-flag","title":"Displacement Map Flag","description":"","sidebar":"examplesSidebar"},"examples/graphics/advanced":{"id":"examples/graphics/advanced","title":"Advanced","description":"","sidebar":"examplesSidebar"},"examples/graphics/dynamic":{"id":"examples/graphics/dynamic","title":"Dynamic","description":"","sidebar":"examplesSidebar"},"examples/graphics/fill-gradient":{"id":"examples/graphics/fill-gradient","title":"Fill Gradient","description":"","sidebar":"examplesSidebar"},"examples/graphics/mesh-from-path":{"id":"examples/graphics/mesh-from-path","title":"Mesh From Path","description":"","sidebar":"examplesSidebar"},"examples/graphics/simple":{"id":"examples/graphics/simple","title":"Simple","description":"","sidebar":"examplesSidebar"},"examples/graphics/svg":{"id":"examples/graphics/svg","title":"Svg","description":"","sidebar":"examplesSidebar"},"examples/graphics/svg-load":{"id":"examples/graphics/svg-load","title":"Svg Load","description":"","sidebar":"examplesSidebar"},"examples/graphics/texture":{"id":"examples/graphics/texture","title":"Texture","description":"","sidebar":"examplesSidebar"},"examples/index":{"id":"examples/index","title":"Examples","description":"Welcome to the PixiJS Examples page! Here you can find a variety of demos and code snippets to help you get started with PixiJS.","sidebar":"examplesSidebar"},"examples/masks/filter":{"id":"examples/masks/filter","title":"Filter","description":"","sidebar":"examplesSidebar"},"examples/masks/graphics":{"id":"examples/masks/graphics","title":"Graphics","description":"","sidebar":"examplesSidebar"},"examples/masks/sprite":{"id":"examples/masks/sprite","title":"Sprite","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/instanced-geometry":{"id":"examples/mesh-and-shaders/instanced-geometry","title":"Instanced Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/multipass-mesh":{"id":"examples/mesh-and-shaders/multipass-mesh","title":"Multipass Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shader-toy-mesh":{"id":"examples/mesh-and-shaders/shader-toy-mesh","title":"Shader Toy Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-geometry":{"id":"examples/mesh-and-shaders/shared-geometry","title":"Shared Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-shader":{"id":"examples/mesh-and-shaders/shared-shader","title":"Shared Shader","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-advanced":{"id":"examples/mesh-and-shaders/textured-mesh-advanced","title":"Textured Mesh Advanced","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-basic":{"id":"examples/mesh-and-shaders/textured-mesh-basic","title":"Textured Mesh Basic","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle":{"id":"examples/mesh-and-shaders/triangle","title":"Triangle","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-color":{"id":"examples/mesh-and-shaders/triangle-color","title":"Triangle Color","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-textured":{"id":"examples/mesh-and-shaders/triangle-textured","title":"Triangle Textured","description":"","sidebar":"examplesSidebar"},"examples/offscreen-canvas/basic":{"id":"examples/offscreen-canvas/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-animation-speed":{"id":"examples/sprite/animated-sprite-animation-speed","title":"Animated Sprite Animation Speed","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-explosion":{"id":"examples/sprite/animated-sprite-explosion","title":"Animated Sprite Explosion","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-jet":{"id":"examples/sprite/animated-sprite-jet","title":"Animated Sprite Jet","description":"","sidebar":"examplesSidebar"},"examples/sprite/basic":{"id":"examples/sprite/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/texture-swap":{"id":"examples/sprite/texture-swap","title":"Texture Swap","description":"","sidebar":"examplesSidebar"},"examples/sprite/tiling-sprite":{"id":"examples/sprite/tiling-sprite","title":"Tiling Sprite","description":"","sidebar":"examplesSidebar"},"examples/sprite/video":{"id":"examples/sprite/video","title":"Video","description":"","sidebar":"examplesSidebar"},"examples/text/bitmap-text":{"id":"examples/text/bitmap-text","title":"Bitmap Text","description":"","sidebar":"examplesSidebar"},"examples/text/from-font":{"id":"examples/text/from-font","title":"From Font","description":"","sidebar":"examplesSidebar"},"examples/text/pixi-text":{"id":"examples/text/pixi-text","title":"Pixi Text","description":"","sidebar":"examplesSidebar"},"examples/text/web-font":{"id":"examples/text/web-font","title":"Web Font","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-advanced":{"id":"examples/textures/render-texture-advanced","title":"Render Texture Advanced","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-basic":{"id":"examples/textures/render-texture-basic","title":"Render Texture Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/texture-rotate":{"id":"examples/textures/texture-rotate","title":"Texture Rotate","description":"","sidebar":"examplesSidebar"},"faq":{"id":"faq","title":"FAQ","description":"What is PixiJS for?"},"guides/advanced/render-groups":{"id":"guides/advanced/render-groups","title":"Render Groups","description":"Understanding RenderGroups in PixiJS","sidebar":"guidesSidebar"},"guides/basics/architecture-overview":{"id":"guides/basics/architecture-overview","title":"Architecture Overview","description":"OK, now that you\'ve gotten a feel for how easy it is to build a PixiJS application, let\'s get into the specifics. For the rest of the Basics section, we\'re going to work from the high level down to the details. We\'ll start with an overview of how PixiJS is put together.","sidebar":"guidesSidebar"},"guides/basics/getting-started":{"id":"guides/basics/getting-started","title":"Getting Started","description":"In this section we\'re going to build the simplest possible PixiJS application. In doing so, we\'ll walk through the basics of how to build and serve the code.","sidebar":"guidesSidebar"},"guides/basics/render-loop":{"id":"guides/basics/render-loop","title":"Render Loop","description":"Now that you understand the major parts of the system, let\'s look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.","sidebar":"guidesSidebar"},"guides/basics/scene-graph":{"id":"guides/basics/scene-graph","title":"Scene Graph","description":"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.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is":{"id":"guides/basics/what-pixijs-is","title":"What PixiJS Is","description":"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is-not":{"id":"guides/basics/what-pixijs-is-not","title":"What PixiJS Is Not","description":"While PixiJS can do many things, there are things it can\'t do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you\'re about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.","sidebar":"guidesSidebar"},"guides/components/assets":{"id":"guides/components/assets","title":"Assets","description":"The Assets package","sidebar":"guidesSidebar"},"guides/components/containers":{"id":"guides/components/containers","title":"Containers","description":"The Container 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.","sidebar":"guidesSidebar"},"guides/components/graphics":{"id":"guides/components/graphics","title":"Graphics","description":"Graphics 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?","sidebar":"guidesSidebar"},"guides/components/interaction":{"id":"guides/components/interaction","title":"Interaction","description":"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.","sidebar":"guidesSidebar"},"guides/components/sprite-sheets":{"id":"guides/components/sprite-sheets","title":"Spritesheets","description":"Now that you understand basic sprites, it\'s time to talk about a better way to create them - the Spritesheet class.","sidebar":"guidesSidebar"},"guides/components/sprites":{"id":"guides/components/sprites","title":"Sprites","description":"Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.","sidebar":"guidesSidebar"},"guides/components/text":{"id":"guides/components/text","title":"Text","description":"Whether it\'s a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there\'s no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.","sidebar":"guidesSidebar"},"guides/components/textures":{"id":"guides/components/textures","title":"Textures","description":"We\'re slowly working our way down from the high level to the low. We\'ve talked about the scene graph, and in general about display objects that live in it. We\'re about to get to sprites and other simple display objects. But before we do, we need to talk about textures.","sidebar":"guidesSidebar"},"guides/index":{"id":"guides/index","title":"Welcome","description":"PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.","sidebar":"guidesSidebar"},"guides/migrations/v5":{"id":"guides/migrations/v5","title":"v5 Migration Guide","description":"This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we\'ve try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.","sidebar":"guidesSidebar"},"guides/migrations/v6":{"id":"guides/migrations/v6","title":"v6 Migration Guide","description":"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.","sidebar":"guidesSidebar"},"guides/migrations/v7":{"id":"guides/migrations/v7","title":"v7 Migration Guide","description":"First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn\'t really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.","sidebar":"guidesSidebar"},"guides/migrations/v8":{"id":"guides/migrations/v8","title":"v8 Migration Guide","description":"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.","sidebar":"guidesSidebar"},"guides/production/performance-tips":{"id":"guides/production/performance-tips","title":"Performance Tips","description":"General","sidebar":"guidesSidebar"},"playground/index":{"id":"playground/index","title":"index","description":""},"tutorials/choo-choo-train":{"id":"tutorials/choo-choo-train","title":"choo-choo-train","description":""},"tutorials/fish-pond":{"id":"tutorials/fish-pond","title":"fish-pond","description":""},"tutorials/getting-started":{"id":"tutorials/getting-started","title":"getting-started","description":""},"tutorials/index":{"id":"tutorials/index","title":"Tutorials","description":"Welcome to the tutorials page! Here you can find hand-crafted exercises to get you started with the PixiJS."},"tutorials/spine-boy-adventure":{"id":"tutorials/spine-boy-adventure","title":"spine-boy-adventure","description":""}}}')}}]); \ No newline at end of file diff --git a/assets/js/99d196e7.72e39f12.js b/assets/js/99d196e7.72e39f12.js deleted file mode 100644 index efb4e75a8..000000000 --- a/assets/js/99d196e7.72e39f12.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1744],{6001:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>p,toc:()=>s});var a=n(7462),i=(n(7294),n(3905));const r={},l="v8 Migration Guide",p={unversionedId:"guides/migrations/v8",id:"guides/migrations/v8",title:"v8 Migration Guide",description:"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.",source:"@site/docs/guides/migrations/v8.md",sourceDirName:"guides/migrations",slug:"/guides/migrations/v8",permalink:"/guides/migrations/v8",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/migrations/v8.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Performance Tips",permalink:"/guides/production/performance-tips"},next:{title:"v7 Migration Guide",permalink:"/guides/migrations/v7"}},o={},s=[{value:"Table of Contents",id:"table-of-contents",level:2},{value:"1. Introduction",id:"1-introduction",level:2},{value:"2. Breaking Changes",id:"2-breaking-changes",level:2},{value:"New Package Structure",id:"new-package-structure",level:3},{value:"Custom Builds",id:"custom-builds",level:4},{value:"Async Initialisation",id:"async-initialisation",level:3},{value:"Graphics API Overhaul",id:"graphics-api-overhaul",level:3},{value:"Other Breaking Changes",id:"other-breaking-changes",level:3},{value:"3. Deprecated Features",id:"3-deprecated-features",level:2},{value:"4. Resources",id:"4-resources",level:2}],m={toc:s};function d(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"v8-migration-guide"},"v8 Migration Guide"),(0,i.kt)("p",null,"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration."),(0,i.kt)("h2",{id:"table-of-contents"},"Table of Contents"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#introduction"},"Introduction")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#breaking-changes"},"Breaking Changes")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#deprecated-features"},"Deprecated Features")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#resources"},"Resources"))),(0,i.kt)("h2",{id:"1-introduction"},"1. Introduction"),(0,i.kt)("p",null,"PixiJS v8 introduces several exciting changes and improvements that dramatically enhance the performance of the renderer. While we've made efforts to keep the migration process as smooth as possible, some breaking changes are inevitable. This guide will walk you through the necessary steps to migrate your PixiJS v7 project to PixiJS v8."),(0,i.kt)("h2",{id:"2-breaking-changes"},"2. Breaking Changes"),(0,i.kt)("p",null,"Before diving into the migration process, let's review the breaking changes introduced in PixiJS v8. Make sure to pay close attention to these changes as they may impact your existing codebase."),(0,i.kt)("h3",{id:"new-package-structure"},(0,i.kt)("strong",{parentName:"h3"},"New Package Structure")),(0,i.kt)("p",null," Since version 5, PixiJS has utilized individual sub-packages to organize its codebase into smaller units. However, this approach led to issues, such as conflicting installations of different PixiJS versions, causing complications with internal caches."),(0,i.kt)("p",null," In v8, PixiJS has reverted to a single-package structure. While you can still import specific parts of PixiJS, you only need to install the main package."),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from '@pixi/app';\nimport { Sprite } from '@pixi/sprite';\n")),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Sprite } from 'pixi.js';\n")),(0,i.kt)("h4",{id:"custom-builds"},"Custom Builds"),(0,i.kt)("p",null,' PixiJS uses an "extensions" system to add renderer functionality. By default, PixiJS includes many extensions for a comprehensive out-of-the-box experience. However, for full control over features and bundle size, you can manually import specific PixiJS components.'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," // imported by default\n import 'pixi.js/accessibility'\n import 'pixi.js/app'\n import 'pixi.js/events'\n import 'pixi.js/filters'\n import 'pixi.js/sprite-tiling'\n import 'pixi.js/text'\n import 'pixi.js/text-bitmap'\n import 'pixi.js/text-html'\n import 'pixi.js/graphics'\n import 'pixi.js/mesh'\n import 'pixi.js/sprite-nine-slice'\n\n // not added by default, everyone needs to import these manually\n import 'pixi.js/advanced-blend-modes'\n import 'pixi.js/unsafe-eval'\n import 'pixi.js/prepare'\n import 'pixi.js/math-extras'\n import 'pixi.js/dds'\n import 'pixi.js/ktx'\n import 'pixi.js/basis'\n\n import { Application } from 'pixi.js';\n\n const app = new Application();\n\n await app.init({\n manageImports: false, // disable importing the above extensions\n });\n")),(0,i.kt)("p",null," When initializing the application, you can disable the auto-import feature, preventing PixiJS from importing any extensions automatically. You'll need to import them manually, as demonstrated above."),(0,i.kt)("p",null," It should also be noted that the ",(0,i.kt)("inlineCode",{parentName:"p"},"pixi.js/text-bitmap"),", also add ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")," loading functionality.\nTherefore if you want to load bitmap fonts ",(0,i.kt)("strong",{parentName:"p"},"BEFORE")," initialising the renderer, you will need to import this extension."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," import 'pixi.js/text-bitmap'\n import { Assets, Application } from 'pixi.js';\n\n await Assets.load('my-font.fnt'); // If 'pixi.js/text-bitmap' is not imported, this will not load\n await new Application().init();\n")),(0,i.kt)("h3",{id:"async-initialisation"},(0,i.kt)("strong",{parentName:"h3"},"Async Initialisation")),(0,i.kt)("p",null,"PixiJS will now need to be initialised asynchronously. With the introduction of the WebGPU renderer PixiJS will now need to be awaited before being used"),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from 'pixi.js'\n\nconst app = new Application();\n\n// do pixi things\n")),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from 'pixi.js'\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // do pixi things\n})()\n")),(0,i.kt)("p",null," With this change it also means that the ",(0,i.kt)("inlineCode",{parentName:"p"},"ApplicationOptions")," object can now be passed into the ",(0,i.kt)("inlineCode",{parentName:"p"},"init")," function instead of the constructor."),(0,i.kt)("h3",{id:"graphics-api-overhaul"},(0,i.kt)("strong",{parentName:"h3"},"Graphics API Overhaul")),(0,i.kt)("p",null,"There are a few key changes to the Graphics API. In fact this is probably the most changed part of v8. We have added deprecations where possible but below is the rundown of changes:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Instead of beginning a fill or a stroke and then building a shape, v8 asks you to build your shape and then stroke / fill it. The terminology of ",(0,i.kt)("inlineCode",{parentName:"li"},"Line")," has been replaced with the terminology of ",(0,i.kt)("inlineCode",{parentName:"li"},"Stroke"))),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Old")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// red rect\nconst graphics = new Graphics()\n .beginFill(0xFF0000);\n .drawRect(50, 50, 100, 100);\n .endFill();\n\n// blur rect with stroke\nconst graphics2 = new Graphics()\n .lineStyle(2, 'white');\n .beginFill('blue');\n .circle(530, 50, 140, 100);\n .endFill();\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// red rect\nconst graphics = new Graphics()\n .rect(50, 50, 100, 100)\n .fill(0xFF0000)\n\n\n// blur rect with stroke\nconst graphics2 = new Graphics()\n .rect(50, 50, 100, 100)\n .fill('blue')\n .stroke({width:2, color:'white'})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Shape functions have been renamed. Each drawing function has been simplified into a shorter version of its name. They have the same parameters though:")),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"v7 API Call"),(0,i.kt)("th",{parentName:"tr",align:null},"v8 API Equivalent"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawChamferRect"),(0,i.kt)("td",{parentName:"tr",align:null},"chamferRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawCircle"),(0,i.kt)("td",{parentName:"tr",align:null},"circle")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawEllipse"),(0,i.kt)("td",{parentName:"tr",align:null},"ellipse")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawFilletRect"),(0,i.kt)("td",{parentName:"tr",align:null},"filletRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"poly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRect"),(0,i.kt)("td",{parentName:"tr",align:null},"rect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRegularPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"regularPoly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"roundPoly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedRect"),(0,i.kt)("td",{parentName:"tr",align:null},"roundRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedShape"),(0,i.kt)("td",{parentName:"tr",align:null},"roundShape")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawStar"),(0,i.kt)("td",{parentName:"tr",align:null},"star")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"fills functions expect ",(0,i.kt)("inlineCode",{parentName:"li"},"FillStyle")," options or a color, rather than a string of parameters. This also replaces ",(0,i.kt)("inlineCode",{parentName:"li"},"beginTextureFill"),(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .beginTextureFill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})\n .drawRect(0, 0, 100, 100)\n .endFill()\n .beginFill(0xFFFF00, 0.5)\n .drawRect(100, 0, 100, 100)\n .endFill()\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .rect(0, 0, 100, 100)\n .fill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})\n .rect(100, 0, 100, 100)\n .fill({color:0xFFFF00, alpha:0.5})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"stokes functions expect ",(0,i.kt)("inlineCode",{parentName:"li"},"StrokeStyle")," options or a color, rather than a string of parameters. This also replaces ",(0,i.kt)("inlineCode",{parentName:"li"},"lineTextureStyle"),(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .lineTextureStyle({texture:Texture.WHITE, width:10, color:0xFF0000})\n .drawRect(0, 0, 100, 100)\n .endFill()\n .lineStyle(2, 0xFEEB77);\n .drawRect(100, 0, 100, 100)\n .endFill()\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .rect(0, 0, 100, 100)\n .stroke({texture:Texture.WHITE, width:10, color:0xFF0000})\n .rect(100, 0, 100, 100)\n .stroke({color:0xFEEB77, width:2})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"holes now make use of a new ",(0,i.kt)("inlineCode",{parentName:"li"},"cut")," function. As with ",(0,i.kt)("inlineCode",{parentName:"li"},"stroke")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"fill"),", ",(0,i.kt)("inlineCode",{parentName:"li"},"cut")," acts on the previous shape.\n",(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rectAndHole = new Graphics()\n .beginFill(0x00FF00)\n .drawRect(0, 0, 100, 100)\n .beginHole()\n .drawCircle(50, 50, 20)\n .endHole()\n .endFill();\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rectAndHole = new Graphics()\n .rect(0, 0, 100, 100)\n .fill(0x00FF00)\n .circle(50, 50, 20)\n .cut();\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"GraphicsGeometry")," has been replaced with ",(0,i.kt)("inlineCode",{parentName:"li"},"GraphicsContext")," this allows for sharing of ",(0,i.kt)("inlineCode",{parentName:"li"},"Graphics")," data more efficiently.\n",(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .beginFill(0xFF0000);\n .drawRect(50, 50, 100, 100);\n .endFill();\n\n const geometry = rect.geometry;\n\n const secondRect = new Graphics(geometry);\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const context = new GraphicsContext()\n .rect(50, 50, 100, 100)\n .fill(0xFF0000)\n\n const rect = new Graphics(context);\n const secondRect = new Graphics(context);\n")),(0,i.kt)("h3",{id:"other-breaking-changes"},"Other Breaking Changes"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"DisplayObject")," has been removed. ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," is now the base class for all PixiJS objects.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"updateTransform")," has been removed as nodes no longer contain any rendering logic"),(0,i.kt)("p",{parentName:"li"},"We do recognise that many people used this function to do custom logic every frame, so we have added a new ",(0,i.kt)("inlineCode",{parentName:"p"},"onRender")," function that can be used for this purpose."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"class MySprite extends Sprite {\n constructor() {\n super();\n this.updateTransform();\n }\n\n updateTransform() {\n super.updateTransform();\n // do custom logic\n }\n}\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"class MySprite extends Sprite {\n constructor() {\n super();\n this.onRender = this._onRender.bind(this);\n }\n\n _onRender() {\n // do custom logic\n }\n}\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Mipmap generation changes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The BaseTexture ",(0,i.kt)("inlineCode",{parentName:"li"},"mipmap")," property has been renamed to ",(0,i.kt)("inlineCode",{parentName:"li"},"autoGenerateMipmaps"),"."),(0,i.kt)("li",{parentName:"ul"},"Mipmaps for ",(0,i.kt)("inlineCode",{parentName:"li"},"RenderTextures")," have been adjusted so that developer is responsible for updating them mipmaps. Mipmap generation can be expensive, and due to the new reactive way we handle textures we do not want to accidentally generate mipmaps when they are not required.")))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const myRenderTexture = RenderTexture.create({width:100, height:100, autoGenerateMipmaps:true})\n\n// do some rendering..\nrenderer.render({target:myRenderTexture, container:scene})\n\n// now refresh mipmaps when you are ready\nmyRenderTexture.source.updateMipmaps();\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Due to the new way PixiJS handles things internally, sprites no longer get notified if a texture's UVs have been modified. The best practice is not to modify texture UVs once they have been created. It's best to have textures ready to go (they are inexpensive to create and store)."),(0,i.kt)("li",{parentName:"ul"},"Sometimes, you might want to employ a special technique that animates the UVs. In this last instance, you will be responsible for updating the sprite (it's worth noting that it may update automatically - but due to the new optimizations, this will not be guaranteed). Updating the source data (e.g., a video texture) will, however, always be reflected immediately.")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const texture = await Assets.load('bunny.png');\nconst sprite = new Sprite(texture);\n\ntexture.frame.width = texture.frame.width/2;\ntexture.update();\n\n// guarantees the texture changes will be reflected on the sprite\nsprite.onViewUpdate();\n\n\n// alternatively you can hooke into the sprites event\ntexture.on('update', ()=>{sprite.onViewUpdate});\n")),(0,i.kt)("p",null,"The act of adding and removing the event when a sprite's texture was changed led to an unacceptable performance drop, especially when swapping many textures (imagine shooting games with lots of keyframe textures swapping). This is why we now leave that responsibility to the user."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"New Container culling approach"),(0,i.kt)("p",{parentName:"li"},"With this version of PixiJS we have changed how the ",(0,i.kt)("inlineCode",{parentName:"p"},"cullable")," property works on containers. Previously culling was done for you automatically during the render loop. However, we have moved this logic out and provided users the ability to control when culling happens themselves."),(0,i.kt)("p",{parentName:"li"},"With this change we have added a couple of new properties:"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullable")," - Whether or not the container can be culled"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullArea")," - A cull area that will be used instead of the bounds of the container"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullableChildren")," - Whether or not the containers children can be culled. This can help optimise large scenes")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new GameWorld();\nconst view = new Rectangle(0, 0, 800, 600);\n\ncontainer.cullable = true;\ncontainer.cullArea = new Rectangle(0,0,400,400);\ncontainer.cullableChildren = false;\n\nCuller.shared.cull(myContainer, view);\nrenderer.render(myContainer);\n")),(0,i.kt)("p",{parentName:"li"},"There is also a ",(0,i.kt)("inlineCode",{parentName:"p"},"CullerPlugin")," that can be used to automatically call ",(0,i.kt)("inlineCode",{parentName:"p"},"Culler.shared.cull")," every frame if you want to simulate the old behaviour."),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import {extensions, CullerPlugin} from 'pixi.js'\nextensions.add(CullerPlugin)\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Renamed several mesh classes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimpleMesh")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshSimple")),(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimplePlane")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshPlane")),(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimpleRope")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshRope")))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Deprecations for ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")," removed"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets } from 'pixi.js'\n\nAssets.add('bunny', 'bunny.png')\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets } from 'pixi.js'\n\nAssets.add({ alias: 'bunny', src: 'bunny.png' })\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"settings")," object has been removed"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { settings, BrowserAdapter } from 'pixi.js'\n\nsettings.RESOLUTION = 1\nsettings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false\nsettings.ADAPTER = BrowserAdapter\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { AbstractRenderer, DOMAdapter, BrowserAdapter } from 'pixi.js'\n\n// Can also be passed into the renderer directly e.g `autoDetectRenderer({resolution: 1})`\nAbstractRenderer.defaultOptions.resolution = 1;\n\n// Can also be passed into the renderer directly e.g `autoDetectRenderer({failIfMajorPerformanceCaveat: false})`\nAbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat = false;\n\n// See below for more information about changes to the adapter\nDOMAdapter.set(BrowserAdapter)\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Adapter and Web Worker Changes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"settings.ADAPTER")," has been removed and replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"DOMAdapter")),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DOMAdapter")," is a static class that can be used to set the adapter for the entire application"),(0,i.kt)("li",{parentName:"ul"},"PixiJS has two adapters built in ",(0,i.kt)("inlineCode",{parentName:"li"},"BrowserAdapter")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"WebWorkerAdapter"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"BrowserAdapter")," is the default adapter and is used when running in the browser"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WebWorkerAdapter")," is used when running in a web worker")))),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { settings, WebWorkerAdapter } from 'pixi.js'\n\nsettings.ADAPTER = WebWorkerAdapter\nsettings.ADAPTER.createCanvas()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { DOMAdapter, WebWorkerAdapter } from 'pixi.js'\n\nDOMAdapter.set(WebWorkerAdapter)\nDOMAdapter.get().createCanvas()\n"))))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Application type now accepts Renderer instead of view by @Zyie in ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs/pull/9740"},"https://github.com/pixijs/pixijs/pull/9740")),(0,i.kt)("p",{parentName:"li"},"This is to allow ",(0,i.kt)("inlineCode",{parentName:"p"},"app.renderer")," to be typed correctly"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// WebGL or WebGPU renderer\nconst app = new Application>()\n// WebGL specific renderer\nconst app = new Application>();\n// WebGPU specific renderer\nconst app = new Application>();\n")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"Texture.from")," no longer will load a texture from a URL."),(0,i.kt)("p",{parentName:"li"},"When using ",(0,i.kt)("inlineCode",{parentName:"p"},"Texture.from")," you will need to pass in a source such as ",(0,i.kt)("inlineCode",{parentName:"p"},"CanvasSource"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"ImageSource"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"VideoSource")," or a resource such as ",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLImageElement"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLCanvasElement"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLVideoElement")," or a string that has been loaded through ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets.load")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Texture } from 'pixi.js';\n\nconst texture = Texture.from('https://i.imgur.com/IaUrttj.png');\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets, Texture } from 'pixi.js';\n\nawait Assets.load('https://i.imgur.com/IaUrttj.png');\nconst texture = Texture.from('https://i.imgur.com/IaUrttj.png');\n")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"The ",(0,i.kt)("inlineCode",{parentName:"p"},"Ticker"),"'s callback will now pass the ",(0,i.kt)("inlineCode",{parentName:"p"},"Ticker")," instance instead of the delta time.\nThis is to allow for more control over what unit of time is used."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"Ticker.shared.add((dt)=> {\n bunny.rotation += dt\n});\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"Ticker.shared.add((ticker)=> {\n bunny.rotation += ticker.deltaTime;\n});\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Text parsers have been renamed"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"TextFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontTextParser")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"XMLStringFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontXMLStringParser")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"XMLFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontXMLParser")))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"The default ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," is now ",(0,i.kt)("inlineCode",{parentName:"p"},"passive")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"auto"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"utils")," has been removed. All the functions are available as direct imports."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { utils } from 'pixi.js'\n\nutils.isMobile.any()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { isMobile } from 'pixi.js'\n\nisMobile.any()\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"container.getBounds()")," now returns a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/rendering.Bounds.html"},(0,i.kt)("inlineCode",{parentName:"a"},"Bounds"))," object instead of a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/maths.Rectangle.html"},(0,i.kt)("inlineCode",{parentName:"a"},"Rectangle"))," object. You can access the rectangle by using ",(0,i.kt)("inlineCode",{parentName:"p"},"container.getBounds().rectangle")," instead."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const bounds = container.getBounds();\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const bounds = container.getBounds().rectangle\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"ParticleContainer")," has been removed, you should use normal a regular ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," instead. The performance improvements that ",(0,i.kt)("inlineCode",{parentName:"p"},"ParticleContainer")," provided are no longer necessary due to the new rendering architecture."))),(0,i.kt)("h2",{id:"3-deprecated-features"},"3. Deprecated Features"),(0,i.kt)("p",null,"Certain features from PixiJS v7 have been deprecated in v8. While they will still work, it's recommended to update your code to use the new alternatives. Refer to the deprecated features section for details on what to replace them with."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Leaf nodes no longer allow children"),(0,i.kt)("p",{parentName:"li"},"Only ",(0,i.kt)("inlineCode",{parentName:"p"},"Containers")," can have children. This means that ",(0,i.kt)("inlineCode",{parentName:"p"},"Sprite"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"Mesh"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"Graphics")," etc can no longer have children."),(0,i.kt)("p",{parentName:"li"},"To replicate the old behaviour you can create a ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," and add the leaf nodes to it."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const sprite = new Sprite();\nconst spriteChild = new Sprite();\nsprite.addChild(spriteChild);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container();\nconst sprite = new Sprite();\nconst spriteChild = new Sprite();\n\ncontainer.addChild(sprite);\ncontainer.addChild(spriteChild);\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"Application.view")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"Application.canvas")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application({ view: document.createElement('canvas') });\ndocument.body.appendChild(app.view);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application();\nawait app.init({ view: document.createElement('canvas') });\ndocument.body.appendChild(app.canvas);\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"NineSlicePlane")," renamed to ",(0,i.kt)("inlineCode",{parentName:"p"},"NineSliceSprite"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"SCALE_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"ScaleMode")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"SCALE_MODES.NEAREST")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'nearest'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"SCALE_MODES.LINEAR")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'linear'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"WRAP_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"WrapMode")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.CLAMP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'clamp'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.REPEAT")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'repeat'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.MIRRORED_REPEAT")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'mirror-repeat'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"DRAW_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"Topology")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.POINTS")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'point-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.LINES")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'line-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.LINE_STRIP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'line-strip'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.TRIANGLES")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'triangle-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.TRIANGLE_STRIP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'triangle-strip'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Constructors have largely been changed to accept objects instead of multiple arguments"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const blurFilter = new BlurFilter(8, 4, 1, 5);\nconst displacementFilter = new DisplacementFilter(sprite, 5);\nconst meshGeometry = new MeshGeometry(vertices, uvs, index);\nconst mesh = new Mesh(geometry, shader, state, drawMode);\nconst plane = new PlaneGeometry(width, height, segWidth, segHeight);\nconst nineSlicePlane = new NineSlicePlane(texture, leftWidth, topHeight, rightWidth, bottomHeight);\nconst tileSprite = new TileSprite(texture, width, height);\nconst text = new Text('Hello World', style);\nconst bitmapText = new BitmapText('Hello World', style);\nconst htmlText = new HTMLText('Hello World', style);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const blurFilter = new BlurFilter({\n blur: 8,\n quality: 4,\n resolution: 1,\n kernelSize: 5,\n});\nconst displacementFilter = new DisplacementFilter({\n sprite,\n scale: 5,\n});\nconst meshGeometry = new MeshGeometry({\n positions: vertices,\n uvs,\n indices: index,\n topology: 'triangle-list';\n shrinkBuffersToFit: boolean;\n});\nconst mesh = new Mesh({\n geometry\n shader\n texture\n});\nconst plane = new PlaneGeometry({\n width,\n height,\n verticesX: segWidth,\n verticesY: segHeight,\n});\nconst nineSliceSprite = new NineSliceSprite({\n texture,\n leftWidth,\n topHeight,\n rightWidth,\n bottomHeight,\n});\nconst tileSprite = new TileSprite({\n texture,\n width,\n height,\n});\nconst text = new Text({\n text: 'Hello World',\n style,\n});\nconst bitmapText = new BitmapText({\n text:'Hello World',\n style,\n});\nconst htmlText = new HTMLText({\n text:'Hello World',\n style,\n});\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"container.name")," is now ",(0,i.kt)("inlineCode",{parentName:"p"},"container.label")))),(0,i.kt)("h2",{id:"4-resources"},"4. Resources"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://github.com/pixijs/pixijs/releases?q=v8.0.0&expanded=true"},"PixiJS v8 Release Notes")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://discord.gg/CPTjeb28nH"},"PixiJS Discord")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://github.com/pixijs/pixijs/issues"},"PixiJS Issues"))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/99d196e7.eb0a0080.js b/assets/js/99d196e7.eb0a0080.js new file mode 100644 index 000000000..17d3e983c --- /dev/null +++ b/assets/js/99d196e7.eb0a0080.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1744],{6001:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>p,toc:()=>s});var a=n(7462),i=(n(7294),n(3905));const r={},l="v8 Migration Guide",p={unversionedId:"guides/migrations/v8",id:"guides/migrations/v8",title:"v8 Migration Guide",description:"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration.",source:"@site/docs/guides/migrations/v8.md",sourceDirName:"guides/migrations",slug:"/guides/migrations/v8",permalink:"/8.x/guides/migrations/v8",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/migrations/v8.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Performance Tips",permalink:"/8.x/guides/production/performance-tips"},next:{title:"v7 Migration Guide",permalink:"/8.x/guides/migrations/v7"}},o={},s=[{value:"Table of Contents",id:"table-of-contents",level:2},{value:"1. Introduction",id:"1-introduction",level:2},{value:"2. Breaking Changes",id:"2-breaking-changes",level:2},{value:"New Package Structure",id:"new-package-structure",level:3},{value:"Custom Builds",id:"custom-builds",level:4},{value:"Async Initialisation",id:"async-initialisation",level:3},{value:"Graphics API Overhaul",id:"graphics-api-overhaul",level:3},{value:"Other Breaking Changes",id:"other-breaking-changes",level:3},{value:"3. Deprecated Features",id:"3-deprecated-features",level:2},{value:"4. Resources",id:"4-resources",level:2}],m={toc:s};function d(e){let{components:t,...n}=e;return(0,i.kt)("wrapper",(0,a.Z)({},m,n,{components:t,mdxType:"MDXLayout"}),(0,i.kt)("h1",{id:"v8-migration-guide"},"v8 Migration Guide"),(0,i.kt)("p",null,"Welcome to the PixiJS v8 Migration Guide! This document is designed to help you smoothly transition your projects from PixiJS v7 to the latest and greatest PixiJS v8. Please follow these steps to ensure a successful migration."),(0,i.kt)("h2",{id:"table-of-contents"},"Table of Contents"),(0,i.kt)("ol",null,(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#introduction"},"Introduction")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#breaking-changes"},"Breaking Changes")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#deprecated-features"},"Deprecated Features")),(0,i.kt)("li",{parentName:"ol"},(0,i.kt)("a",{parentName:"li",href:"#resources"},"Resources"))),(0,i.kt)("h2",{id:"1-introduction"},"1. Introduction"),(0,i.kt)("p",null,"PixiJS v8 introduces several exciting changes and improvements that dramatically enhance the performance of the renderer. While we've made efforts to keep the migration process as smooth as possible, some breaking changes are inevitable. This guide will walk you through the necessary steps to migrate your PixiJS v7 project to PixiJS v8."),(0,i.kt)("h2",{id:"2-breaking-changes"},"2. Breaking Changes"),(0,i.kt)("p",null,"Before diving into the migration process, let's review the breaking changes introduced in PixiJS v8. Make sure to pay close attention to these changes as they may impact your existing codebase."),(0,i.kt)("h3",{id:"new-package-structure"},(0,i.kt)("strong",{parentName:"h3"},"New Package Structure")),(0,i.kt)("p",null," Since version 5, PixiJS has utilized individual sub-packages to organize its codebase into smaller units. However, this approach led to issues, such as conflicting installations of different PixiJS versions, causing complications with internal caches."),(0,i.kt)("p",null," In v8, PixiJS has reverted to a single-package structure. While you can still import specific parts of PixiJS, you only need to install the main package."),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from '@pixi/app';\nimport { Sprite } from '@pixi/sprite';\n")),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application, Sprite } from 'pixi.js';\n")),(0,i.kt)("h4",{id:"custom-builds"},"Custom Builds"),(0,i.kt)("p",null,' PixiJS uses an "extensions" system to add renderer functionality. By default, PixiJS includes many extensions for a comprehensive out-of-the-box experience. However, for full control over features and bundle size, you can manually import specific PixiJS components.'),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," // imported by default\n import 'pixi.js/accessibility'\n import 'pixi.js/app'\n import 'pixi.js/events'\n import 'pixi.js/filters'\n import 'pixi.js/sprite-tiling'\n import 'pixi.js/text'\n import 'pixi.js/text-bitmap'\n import 'pixi.js/text-html'\n import 'pixi.js/graphics'\n import 'pixi.js/mesh'\n import 'pixi.js/sprite-nine-slice'\n\n // not added by default, everyone needs to import these manually\n import 'pixi.js/advanced-blend-modes'\n import 'pixi.js/unsafe-eval'\n import 'pixi.js/prepare'\n import 'pixi.js/math-extras'\n import 'pixi.js/dds'\n import 'pixi.js/ktx'\n import 'pixi.js/basis'\n\n import { Application } from 'pixi.js';\n\n const app = new Application();\n\n await app.init({\n manageImports: false, // disable importing the above extensions\n });\n")),(0,i.kt)("p",null," When initializing the application, you can disable the auto-import feature, preventing PixiJS from importing any extensions automatically. You'll need to import them manually, as demonstrated above."),(0,i.kt)("p",null," It should also be noted that the ",(0,i.kt)("inlineCode",{parentName:"p"},"pixi.js/text-bitmap"),", also add ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")," loading functionality.\nTherefore if you want to load bitmap fonts ",(0,i.kt)("strong",{parentName:"p"},"BEFORE")," initialising the renderer, you will need to import this extension."),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," import 'pixi.js/text-bitmap'\n import { Assets, Application } from 'pixi.js';\n\n await Assets.load('my-font.fnt'); // If 'pixi.js/text-bitmap' is not imported, this will not load\n await new Application().init();\n")),(0,i.kt)("h3",{id:"async-initialisation"},(0,i.kt)("strong",{parentName:"h3"},"Async Initialisation")),(0,i.kt)("p",null,"PixiJS will now need to be initialised asynchronously. With the introduction of the WebGPU renderer PixiJS will now need to be awaited before being used"),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from 'pixi.js'\n\nconst app = new Application();\n\n// do pixi things\n")),(0,i.kt)("p",null," ",(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Application } from 'pixi.js'\n\nconst app = new Application();\n\n(async () => {\n await app.init({\n // application options\n });\n\n // do pixi things\n})()\n")),(0,i.kt)("p",null," With this change it also means that the ",(0,i.kt)("inlineCode",{parentName:"p"},"ApplicationOptions")," object can now be passed into the ",(0,i.kt)("inlineCode",{parentName:"p"},"init")," function instead of the constructor."),(0,i.kt)("h3",{id:"graphics-api-overhaul"},(0,i.kt)("strong",{parentName:"h3"},"Graphics API Overhaul")),(0,i.kt)("p",null,"There are a few key changes to the Graphics API. In fact this is probably the most changed part of v8. We have added deprecations where possible but below is the rundown of changes:"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Instead of beginning a fill or a stroke and then building a shape, v8 asks you to build your shape and then stroke / fill it. The terminology of ",(0,i.kt)("inlineCode",{parentName:"li"},"Line")," has been replaced with the terminology of ",(0,i.kt)("inlineCode",{parentName:"li"},"Stroke"))),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"Old")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// red rect\nconst graphics = new Graphics()\n .beginFill(0xFF0000);\n .drawRect(50, 50, 100, 100);\n .endFill();\n\n// blur rect with stroke\nconst graphics2 = new Graphics()\n .lineStyle(2, 'white');\n .beginFill('blue');\n .circle(530, 50, 140, 100);\n .endFill();\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// red rect\nconst graphics = new Graphics()\n .rect(50, 50, 100, 100)\n .fill(0xFF0000)\n\n\n// blur rect with stroke\nconst graphics2 = new Graphics()\n .rect(50, 50, 100, 100)\n .fill('blue')\n .stroke({width:2, color:'white'})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Shape functions have been renamed. Each drawing function has been simplified into a shorter version of its name. They have the same parameters though:")),(0,i.kt)("table",null,(0,i.kt)("thead",{parentName:"table"},(0,i.kt)("tr",{parentName:"thead"},(0,i.kt)("th",{parentName:"tr",align:null},"v7 API Call"),(0,i.kt)("th",{parentName:"tr",align:null},"v8 API Equivalent"))),(0,i.kt)("tbody",{parentName:"table"},(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawChamferRect"),(0,i.kt)("td",{parentName:"tr",align:null},"chamferRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawCircle"),(0,i.kt)("td",{parentName:"tr",align:null},"circle")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawEllipse"),(0,i.kt)("td",{parentName:"tr",align:null},"ellipse")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawFilletRect"),(0,i.kt)("td",{parentName:"tr",align:null},"filletRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"poly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRect"),(0,i.kt)("td",{parentName:"tr",align:null},"rect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRegularPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"regularPoly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedPolygon"),(0,i.kt)("td",{parentName:"tr",align:null},"roundPoly")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedRect"),(0,i.kt)("td",{parentName:"tr",align:null},"roundRect")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawRoundedShape"),(0,i.kt)("td",{parentName:"tr",align:null},"roundShape")),(0,i.kt)("tr",{parentName:"tbody"},(0,i.kt)("td",{parentName:"tr",align:null},"drawStar"),(0,i.kt)("td",{parentName:"tr",align:null},"star")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"fills functions expect ",(0,i.kt)("inlineCode",{parentName:"li"},"FillStyle")," options or a color, rather than a string of parameters. This also replaces ",(0,i.kt)("inlineCode",{parentName:"li"},"beginTextureFill"),(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .beginTextureFill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})\n .drawRect(0, 0, 100, 100)\n .endFill()\n .beginFill(0xFFFF00, 0.5)\n .drawRect(100, 0, 100, 100)\n .endFill()\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .rect(0, 0, 100, 100)\n .fill({texture:Texture.WHITE, alpha:0.5, color:0xFF0000})\n .rect(100, 0, 100, 100)\n .fill({color:0xFFFF00, alpha:0.5})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"stokes functions expect ",(0,i.kt)("inlineCode",{parentName:"li"},"StrokeStyle")," options or a color, rather than a string of parameters. This also replaces ",(0,i.kt)("inlineCode",{parentName:"li"},"lineTextureStyle"),(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .lineTextureStyle({texture:Texture.WHITE, width:10, color:0xFF0000})\n .drawRect(0, 0, 100, 100)\n .endFill()\n .lineStyle(2, 0xFEEB77);\n .drawRect(100, 0, 100, 100)\n .endFill()\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .rect(0, 0, 100, 100)\n .stroke({texture:Texture.WHITE, width:10, color:0xFF0000})\n .rect(100, 0, 100, 100)\n .stroke({color:0xFEEB77, width:2})\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"holes now make use of a new ",(0,i.kt)("inlineCode",{parentName:"li"},"cut")," function. As with ",(0,i.kt)("inlineCode",{parentName:"li"},"stroke")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"fill"),", ",(0,i.kt)("inlineCode",{parentName:"li"},"cut")," acts on the previous shape.\n",(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rectAndHole = new Graphics()\n .beginFill(0x00FF00)\n .drawRect(0, 0, 100, 100)\n .beginHole()\n .drawCircle(50, 50, 20)\n .endHole()\n .endFill();\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rectAndHole = new Graphics()\n .rect(0, 0, 100, 100)\n .fill(0x00FF00)\n .circle(50, 50, 20)\n .cut();\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"GraphicsGeometry")," has been replaced with ",(0,i.kt)("inlineCode",{parentName:"li"},"GraphicsContext")," this allows for sharing of ",(0,i.kt)("inlineCode",{parentName:"li"},"Graphics")," data more efficiently.\n",(0,i.kt)("strong",{parentName:"li"},"Old"))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const rect = new Graphics()\n .beginFill(0xFF0000);\n .drawRect(50, 50, 100, 100);\n .endFill();\n\n const geometry = rect.geometry;\n\n const secondRect = new Graphics(geometry);\n\n")),(0,i.kt)("p",null,(0,i.kt)("strong",{parentName:"p"},"New")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"}," const context = new GraphicsContext()\n .rect(50, 50, 100, 100)\n .fill(0xFF0000)\n\n const rect = new Graphics(context);\n const secondRect = new Graphics(context);\n")),(0,i.kt)("h3",{id:"other-breaking-changes"},"Other Breaking Changes"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"DisplayObject")," has been removed. ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," is now the base class for all PixiJS objects.")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"updateTransform")," has been removed as nodes no longer contain any rendering logic"),(0,i.kt)("p",{parentName:"li"},"We do recognise that many people used this function to do custom logic every frame, so we have added a new ",(0,i.kt)("inlineCode",{parentName:"p"},"onRender")," function that can be used for this purpose."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"class MySprite extends Sprite {\n constructor() {\n super();\n this.updateTransform();\n }\n\n updateTransform() {\n super.updateTransform();\n // do custom logic\n }\n}\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"class MySprite extends Sprite {\n constructor() {\n super();\n this.onRender = this._onRender.bind(this);\n }\n\n _onRender() {\n // do custom logic\n }\n}\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Mipmap generation changes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"The BaseTexture ",(0,i.kt)("inlineCode",{parentName:"li"},"mipmap")," property has been renamed to ",(0,i.kt)("inlineCode",{parentName:"li"},"autoGenerateMipmaps"),"."),(0,i.kt)("li",{parentName:"ul"},"Mipmaps for ",(0,i.kt)("inlineCode",{parentName:"li"},"RenderTextures")," have been adjusted so that developer is responsible for updating them mipmaps. Mipmap generation can be expensive, and due to the new reactive way we handle textures we do not want to accidentally generate mipmaps when they are not required.")))),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const myRenderTexture = RenderTexture.create({width:100, height:100, autoGenerateMipmaps:true})\n\n// do some rendering..\nrenderer.render({target:myRenderTexture, container:scene})\n\n// now refresh mipmaps when you are ready\nmyRenderTexture.source.updateMipmaps();\n")),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},"Due to the new way PixiJS handles things internally, sprites no longer get notified if a texture's UVs have been modified. The best practice is not to modify texture UVs once they have been created. It's best to have textures ready to go (they are inexpensive to create and store)."),(0,i.kt)("li",{parentName:"ul"},"Sometimes, you might want to employ a special technique that animates the UVs. In this last instance, you will be responsible for updating the sprite (it's worth noting that it may update automatically - but due to the new optimizations, this will not be guaranteed). Updating the source data (e.g., a video texture) will, however, always be reflected immediately.")),(0,i.kt)("pre",null,(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const texture = await Assets.load('bunny.png');\nconst sprite = new Sprite(texture);\n\ntexture.frame.width = texture.frame.width/2;\ntexture.update();\n\n// guarantees the texture changes will be reflected on the sprite\nsprite.onViewUpdate();\n\n\n// alternatively you can hooke into the sprites event\ntexture.on('update', ()=>{sprite.onViewUpdate});\n")),(0,i.kt)("p",null,"The act of adding and removing the event when a sprite's texture was changed led to an unacceptable performance drop, especially when swapping many textures (imagine shooting games with lots of keyframe textures swapping). This is why we now leave that responsibility to the user."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"New Container culling approach"),(0,i.kt)("p",{parentName:"li"},"With this version of PixiJS we have changed how the ",(0,i.kt)("inlineCode",{parentName:"p"},"cullable")," property works on containers. Previously culling was done for you automatically during the render loop. However, we have moved this logic out and provided users the ability to control when culling happens themselves."),(0,i.kt)("p",{parentName:"li"},"With this change we have added a couple of new properties:"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullable")," - Whether or not the container can be culled"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullArea")," - A cull area that will be used instead of the bounds of the container"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"cullableChildren")," - Whether or not the containers children can be culled. This can help optimise large scenes")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new GameWorld();\nconst view = new Rectangle(0, 0, 800, 600);\n\ncontainer.cullable = true;\ncontainer.cullArea = new Rectangle(0,0,400,400);\ncontainer.cullableChildren = false;\n\nCuller.shared.cull(myContainer, view);\nrenderer.render(myContainer);\n")),(0,i.kt)("p",{parentName:"li"},"There is also a ",(0,i.kt)("inlineCode",{parentName:"p"},"CullerPlugin")," that can be used to automatically call ",(0,i.kt)("inlineCode",{parentName:"p"},"Culler.shared.cull")," every frame if you want to simulate the old behaviour."),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import {extensions, CullerPlugin} from 'pixi.js'\nextensions.add(CullerPlugin)\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Renamed several mesh classes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimpleMesh")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshSimple")),(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimplePlane")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshPlane")),(0,i.kt)("li",{parentName:"ul"},"renamed ",(0,i.kt)("inlineCode",{parentName:"li"},"SimpleRope")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"MeshRope")))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Deprecations for ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets")," removed"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets } from 'pixi.js'\n\nAssets.add('bunny', 'bunny.png')\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets } from 'pixi.js'\n\nAssets.add({ alias: 'bunny', src: 'bunny.png' })\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"settings")," object has been removed"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { settings, BrowserAdapter } from 'pixi.js'\n\nsettings.RESOLUTION = 1\nsettings.FAIL_IF_MAJOR_PERFORMANCE_CAVEAT = false\nsettings.ADAPTER = BrowserAdapter\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { AbstractRenderer, DOMAdapter, BrowserAdapter } from 'pixi.js'\n\n// Can also be passed into the renderer directly e.g `autoDetectRenderer({resolution: 1})`\nAbstractRenderer.defaultOptions.resolution = 1;\n\n// Can also be passed into the renderer directly e.g `autoDetectRenderer({failIfMajorPerformanceCaveat: false})`\nAbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat = false;\n\n// See below for more information about changes to the adapter\nDOMAdapter.set(BrowserAdapter)\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Adapter and Web Worker Changes"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"settings.ADAPTER")," has been removed and replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"DOMAdapter")),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DOMAdapter")," is a static class that can be used to set the adapter for the entire application"),(0,i.kt)("li",{parentName:"ul"},"PixiJS has two adapters built in ",(0,i.kt)("inlineCode",{parentName:"li"},"BrowserAdapter")," and ",(0,i.kt)("inlineCode",{parentName:"li"},"WebWorkerAdapter"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"BrowserAdapter")," is the default adapter and is used when running in the browser"),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WebWorkerAdapter")," is used when running in a web worker")))),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { settings, WebWorkerAdapter } from 'pixi.js'\n\nsettings.ADAPTER = WebWorkerAdapter\nsettings.ADAPTER.createCanvas()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { DOMAdapter, WebWorkerAdapter } from 'pixi.js'\n\nDOMAdapter.set(WebWorkerAdapter)\nDOMAdapter.get().createCanvas()\n"))))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Application type now accepts Renderer instead of view by @Zyie in ",(0,i.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs/pull/9740"},"https://github.com/pixijs/pixijs/pull/9740")),(0,i.kt)("p",{parentName:"li"},"This is to allow ",(0,i.kt)("inlineCode",{parentName:"p"},"app.renderer")," to be typed correctly"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"// WebGL or WebGPU renderer\nconst app = new Application>()\n// WebGL specific renderer\nconst app = new Application>();\n// WebGPU specific renderer\nconst app = new Application>();\n")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"Texture.from")," no longer will load a texture from a URL."),(0,i.kt)("p",{parentName:"li"},"When using ",(0,i.kt)("inlineCode",{parentName:"p"},"Texture.from")," you will need to pass in a source such as ",(0,i.kt)("inlineCode",{parentName:"p"},"CanvasSource"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"ImageSource"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"VideoSource")," or a resource such as ",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLImageElement"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLCanvasElement"),"/",(0,i.kt)("inlineCode",{parentName:"p"},"HTMLVideoElement")," or a string that has been loaded through ",(0,i.kt)("inlineCode",{parentName:"p"},"Assets.load")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Texture } from 'pixi.js';\n\nconst texture = Texture.from('https://i.imgur.com/IaUrttj.png');\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { Assets, Texture } from 'pixi.js';\n\nawait Assets.load('https://i.imgur.com/IaUrttj.png');\nconst texture = Texture.from('https://i.imgur.com/IaUrttj.png');\n")))),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"The ",(0,i.kt)("inlineCode",{parentName:"p"},"Ticker"),"'s callback will now pass the ",(0,i.kt)("inlineCode",{parentName:"p"},"Ticker")," instance instead of the delta time.\nThis is to allow for more control over what unit of time is used."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"Ticker.shared.add((dt)=> {\n bunny.rotation += dt\n});\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"Ticker.shared.add((ticker)=> {\n bunny.rotation += ticker.deltaTime;\n});\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Text parsers have been renamed"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"TextFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontTextParser")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"XMLStringFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontXMLStringParser")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"XMLFormat")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"bitmapFontXMLParser")))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"The default ",(0,i.kt)("inlineCode",{parentName:"p"},"eventMode")," is now ",(0,i.kt)("inlineCode",{parentName:"p"},"passive")," instead of ",(0,i.kt)("inlineCode",{parentName:"p"},"auto"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"utils")," has been removed. All the functions are available as direct imports."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { utils } from 'pixi.js'\n\nutils.isMobile.any()\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"import { isMobile } from 'pixi.js'\n\nisMobile.any()\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"container.getBounds()")," now returns a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/rendering.Bounds.html"},(0,i.kt)("inlineCode",{parentName:"a"},"Bounds"))," object instead of a ",(0,i.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/maths.Rectangle.html"},(0,i.kt)("inlineCode",{parentName:"a"},"Rectangle"))," object. You can access the rectangle by using ",(0,i.kt)("inlineCode",{parentName:"p"},"container.getBounds().rectangle")," instead."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const bounds = container.getBounds();\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const bounds = container.getBounds().rectangle\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"ParticleContainer")," has been removed, you should use normal a regular ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," instead. The performance improvements that ",(0,i.kt)("inlineCode",{parentName:"p"},"ParticleContainer")," provided are no longer necessary due to the new rendering architecture."))),(0,i.kt)("h2",{id:"3-deprecated-features"},"3. Deprecated Features"),(0,i.kt)("p",null,"Certain features from PixiJS v7 have been deprecated in v8. While they will still work, it's recommended to update your code to use the new alternatives. Refer to the deprecated features section for details on what to replace them with."),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Leaf nodes no longer allow children"),(0,i.kt)("p",{parentName:"li"},"Only ",(0,i.kt)("inlineCode",{parentName:"p"},"Containers")," can have children. This means that ",(0,i.kt)("inlineCode",{parentName:"p"},"Sprite"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"Mesh"),", ",(0,i.kt)("inlineCode",{parentName:"p"},"Graphics")," etc can no longer have children."),(0,i.kt)("p",{parentName:"li"},"To replicate the old behaviour you can create a ",(0,i.kt)("inlineCode",{parentName:"p"},"Container")," and add the leaf nodes to it."),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const sprite = new Sprite();\nconst spriteChild = new Sprite();\nsprite.addChild(spriteChild);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const container = new Container();\nconst sprite = new Sprite();\nconst spriteChild = new Sprite();\n\ncontainer.addChild(sprite);\ncontainer.addChild(spriteChild);\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"Application.view")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"Application.canvas")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application({ view: document.createElement('canvas') });\ndocument.body.appendChild(app.view);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const app = new Application();\nawait app.init({ view: document.createElement('canvas') });\ndocument.body.appendChild(app.canvas);\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"NineSlicePlane")," renamed to ",(0,i.kt)("inlineCode",{parentName:"p"},"NineSliceSprite"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"SCALE_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"ScaleMode")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"SCALE_MODES.NEAREST")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'nearest'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"SCALE_MODES.LINEAR")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'linear'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"WRAP_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"WrapMode")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.CLAMP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'clamp'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.REPEAT")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'repeat'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"WRAP_MODES.MIRRORED_REPEAT")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'mirror-repeat'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"DRAW_MODES")," replaced with ",(0,i.kt)("inlineCode",{parentName:"p"},"Topology")," strings"),(0,i.kt)("ul",{parentName:"li"},(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.POINTS")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'point-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.LINES")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'line-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.LINE_STRIP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'line-strip'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.TRIANGLES")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'triangle-list'"),","),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("inlineCode",{parentName:"li"},"DRAW_MODES.TRIANGLE_STRIP")," -> ",(0,i.kt)("inlineCode",{parentName:"li"},"'triangle-strip'"),","))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},"Constructors have largely been changed to accept objects instead of multiple arguments"),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"Old:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const blurFilter = new BlurFilter(8, 4, 1, 5);\nconst displacementFilter = new DisplacementFilter(sprite, 5);\nconst meshGeometry = new MeshGeometry(vertices, uvs, index);\nconst mesh = new Mesh(geometry, shader, state, drawMode);\nconst plane = new PlaneGeometry(width, height, segWidth, segHeight);\nconst nineSlicePlane = new NineSlicePlane(texture, leftWidth, topHeight, rightWidth, bottomHeight);\nconst tileSprite = new TileSprite(texture, width, height);\nconst text = new Text('Hello World', style);\nconst bitmapText = new BitmapText('Hello World', style);\nconst htmlText = new HTMLText('Hello World', style);\n")),(0,i.kt)("p",{parentName:"li"},(0,i.kt)("strong",{parentName:"p"},"New:")),(0,i.kt)("pre",{parentName:"li"},(0,i.kt)("code",{parentName:"pre",className:"language-ts"},"const blurFilter = new BlurFilter({\n blur: 8,\n quality: 4,\n resolution: 1,\n kernelSize: 5,\n});\nconst displacementFilter = new DisplacementFilter({\n sprite,\n scale: 5,\n});\nconst meshGeometry = new MeshGeometry({\n positions: vertices,\n uvs,\n indices: index,\n topology: 'triangle-list';\n shrinkBuffersToFit: boolean;\n});\nconst mesh = new Mesh({\n geometry\n shader\n texture\n});\nconst plane = new PlaneGeometry({\n width,\n height,\n verticesX: segWidth,\n verticesY: segHeight,\n});\nconst nineSliceSprite = new NineSliceSprite({\n texture,\n leftWidth,\n topHeight,\n rightWidth,\n bottomHeight,\n});\nconst tileSprite = new TileSprite({\n texture,\n width,\n height,\n});\nconst text = new Text({\n text: 'Hello World',\n style,\n});\nconst bitmapText = new BitmapText({\n text:'Hello World',\n style,\n});\nconst htmlText = new HTMLText({\n text:'Hello World',\n style,\n});\n"))),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("p",{parentName:"li"},(0,i.kt)("inlineCode",{parentName:"p"},"container.name")," is now ",(0,i.kt)("inlineCode",{parentName:"p"},"container.label")))),(0,i.kt)("h2",{id:"4-resources"},"4. Resources"),(0,i.kt)("ul",null,(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://github.com/pixijs/pixijs/releases?q=v8.0.0&expanded=true"},"PixiJS v8 Release Notes")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://discord.gg/CPTjeb28nH"},"PixiJS Discord")),(0,i.kt)("li",{parentName:"ul"},(0,i.kt)("a",{parentName:"li",href:"https://github.com/pixijs/pixijs/issues"},"PixiJS Issues"))))}d.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a1cfca63.db0205fb.js b/assets/js/a1cfca63.db0205fb.js new file mode 100644 index 000000000..e55ae20ac --- /dev/null +++ b/assets/js/a1cfca63.db0205fb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1665],{5600:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var s=t(7462),n=(t(7294),t(3905)),a=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Tinting"},l=void 0,p={unversionedId:"examples/basic/tinting",id:"examples/basic/tinting",title:"Tinting",description:"",source:"@site/docs/examples/basic/tinting.md",sourceDirName:"examples/basic",slug:"/examples/basic/tinting",permalink:"/8.x/examples/basic/tinting",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Tinting"},sidebar:"examplesSidebar",previous:{title:"Transparent Background",permalink:"/8.x/examples/basic/transparent-background"},next:{title:"Particle Container",permalink:"/8.x/examples/basic/particle-container"}},d={},c=[],u={toc:c};function m(e){let{components:i,...t}=e;return(0,n.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,n.kt)(a.Z,{id:"basic.tinting",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/a1cfca63.f7d9ddb7.js b/assets/js/a1cfca63.f7d9ddb7.js deleted file mode 100644 index 458595a44..000000000 --- a/assets/js/a1cfca63.f7d9ddb7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[1665],{5600:(e,i,t)=>{t.r(i),t.d(i,{assets:()=>d,contentTitle:()=>l,default:()=>m,frontMatter:()=>o,metadata:()=>p,toc:()=>c});var s=t(7462),n=(t(7294),t(3905)),a=t(8010),r=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Tinting"},l=void 0,p={unversionedId:"examples/basic/tinting",id:"examples/basic/tinting",title:"Tinting",description:"",source:"@site/docs/examples/basic/tinting.md",sourceDirName:"examples/basic",slug:"/examples/basic/tinting",permalink:"/examples/basic/tinting",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Tinting"},sidebar:"examplesSidebar",previous:{title:"Transparent Background",permalink:"/examples/basic/transparent-background"},next:{title:"Particle Container",permalink:"/examples/basic/particle-container"}},d={},c=[],u={toc:c};function m(e){let{components:i,...t}=e;return(0,n.kt)("wrapper",(0,s.Z)({},u,t,{components:i,mdxType:"MDXLayout"}),(0,n.kt)(a.Z,{id:"basic.tinting",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/a3ab5221.6c35b8fa.js b/assets/js/a3ab5221.6c35b8fa.js new file mode 100644 index 000000000..98ad27a15 --- /dev/null +++ b/assets/js/a3ab5221.6c35b8fa.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[438],{9910:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>d,metadata:()=>n,toc:()=>u});var a=s(7462),i=(s(7294),s(3905)),o=s(8010),r=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Mouse Trail"},l=void 0,n={unversionedId:"examples/advanced/mouse-trail",id:"examples/advanced/mouse-trail",title:"Mouse Trail",description:"",source:"@site/docs/examples/advanced/mouse-trail.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/mouse-trail",permalink:"/8.x/examples/advanced/mouse-trail",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Mouse Trail"},sidebar:"examplesSidebar",previous:{title:"Star Warp",permalink:"/8.x/examples/advanced/star-warp"},next:{title:"Screen Shot",permalink:"/8.x/examples/advanced/screen-shot"}},p={},u=[],c={toc:u};function m(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,a.Z)({},c,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"advanced.mouseTrail",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/a3ab5221.789f88b6.js b/assets/js/a3ab5221.789f88b6.js deleted file mode 100644 index 9637275e7..000000000 --- a/assets/js/a3ab5221.789f88b6.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[438],{9910:(e,t,s)=>{s.r(t),s.d(t,{assets:()=>p,contentTitle:()=>l,default:()=>m,frontMatter:()=>d,metadata:()=>n,toc:()=>u});var a=s(7462),i=(s(7294),s(3905)),o=s(8010),r=s(7949);const d={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Mouse Trail"},l=void 0,n={unversionedId:"examples/advanced/mouse-trail",id:"examples/advanced/mouse-trail",title:"Mouse Trail",description:"",source:"@site/docs/examples/advanced/mouse-trail.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/mouse-trail",permalink:"/examples/advanced/mouse-trail",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:3,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:3,custom_edit_url:null,title:"Mouse Trail"},sidebar:"examplesSidebar",previous:{title:"Star Warp",permalink:"/examples/advanced/star-warp"},next:{title:"Screen Shot",permalink:"/examples/advanced/screen-shot"}},p={},u=[],c={toc:u};function m(e){let{components:t,...s}=e;return(0,i.kt)("wrapper",(0,a.Z)({},c,s,{components:t,mdxType:"MDXLayout"}),(0,i.kt)(o.Z,{id:"advanced.mouseTrail",pixiVersion:r,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/a8908f9f.32740a9b.js b/assets/js/a8908f9f.32740a9b.js new file mode 100644 index 000000000..011223b09 --- /dev/null +++ b/assets/js/a8908f9f.32740a9b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7453],{7869:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>n,toc:()=>d});var i=a(7462),s=(a(7294),a(3905));const r={},o="What PixiJS Is",n={unversionedId:"guides/basics/what-pixijs-is",id:"guides/basics/what-pixijs-is",title:"What PixiJS Is",description:"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.",source:"@site/docs/guides/basics/what-pixijs-is.md",sourceDirName:"guides/basics",slug:"/guides/basics/what-pixijs-is",permalink:"/8.x/guides/basics/what-pixijs-is",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/what-pixijs-is.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Welcome",permalink:"/8.x/guides/"},next:{title:"What PixiJS Is Not",permalink:"/8.x/guides/basics/what-pixijs-is-not"}},l={},d=[{value:"PixiJS Is ... Fast",id:"pixijs-is--fast",level:2},{value:"... More Than Just Sprites",id:"-more-than-just-sprites",level:2},{value:"... Hardware accelerated",id:"-hardware-accelerated",level:2},{value:"... Open Source",id:"-open-source",level:2},{value:"... Extensible",id:"-extensible",level:2},{value:"... Easy to Deploy",id:"-easy-to-deploy",level:2}],p={toc:d};function c(e){let{components:t,...a}=e;return(0,s.kt)("wrapper",(0,i.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,s.kt)("h1",{id:"what-pixijs-is"},"What PixiJS Is"),(0,s.kt)("p",null,"So what exactly ",(0,s.kt)("em",{parentName:"p"},"is")," PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications."),(0,s.kt)("p",null,"Here's what else you get with PixiJS:"),(0,s.kt)("h2",{id:"pixijs-is--fast"},"PixiJS Is ... Fast"),(0,s.kt)("p",null,"One of the major features that distinguishes PixiJS from other web-based rendering solutions is ",(0,s.kt)("em",{parentName:"p"},"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."),(0,s.kt)("h2",{id:"-more-than-just-sprites"},"... More Than Just Sprites"),(0,s.kt)("p",null,"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 ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.MeshRope.html"},"MeshRope"),". Draw polygons, lines, circles and other primitives with ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Graphics.html"},"Graphics"),". ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Text.html"},"Text")," 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."),(0,s.kt)("h2",{id:"-hardware-accelerated"},"... Hardware accelerated"),(0,s.kt)("p",null,"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 ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/filters.Filter.html"},"Filter")," 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."),(0,s.kt)("h2",{id:"-open-source"},"... Open Source"),(0,s.kt)("p",null,"Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and ",(0,s.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"hosted on GitHub")," for issue tracking and ease of access."),(0,s.kt)("h2",{id:"-extensible"},"... Extensible"),(0,s.kt)("p",null,"Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs."),(0,s.kt)("h2",{id:"-easy-to-deploy"},"... Easy to Deploy"),(0,s.kt)("p",null,"Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a8908f9f.e8e23eb0.js b/assets/js/a8908f9f.e8e23eb0.js deleted file mode 100644 index db6ffa143..000000000 --- a/assets/js/a8908f9f.e8e23eb0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[7453],{7869:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>o,default:()=>c,frontMatter:()=>r,metadata:()=>n,toc:()=>d});var i=a(7462),s=(a(7294),a(3905));const r={},o="What PixiJS Is",n={unversionedId:"guides/basics/what-pixijs-is",id:"guides/basics/what-pixijs-is",title:"What PixiJS Is",description:"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.",source:"@site/docs/guides/basics/what-pixijs-is.md",sourceDirName:"guides/basics",slug:"/guides/basics/what-pixijs-is",permalink:"/guides/basics/what-pixijs-is",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/basics/what-pixijs-is.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Welcome",permalink:"/guides/"},next:{title:"What PixiJS Is Not",permalink:"/guides/basics/what-pixijs-is-not"}},l={},d=[{value:"PixiJS Is ... Fast",id:"pixijs-is--fast",level:2},{value:"... More Than Just Sprites",id:"-more-than-just-sprites",level:2},{value:"... Hardware accelerated",id:"-hardware-accelerated",level:2},{value:"... Open Source",id:"-open-source",level:2},{value:"... Extensible",id:"-extensible",level:2},{value:"... Easy to Deploy",id:"-easy-to-deploy",level:2}],p={toc:d};function c(e){let{components:t,...a}=e;return(0,s.kt)("wrapper",(0,i.Z)({},p,a,{components:t,mdxType:"MDXLayout"}),(0,s.kt)("h1",{id:"what-pixijs-is"},"What PixiJS Is"),(0,s.kt)("p",null,"So what exactly ",(0,s.kt)("em",{parentName:"p"},"is")," PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications."),(0,s.kt)("p",null,"Here's what else you get with PixiJS:"),(0,s.kt)("h2",{id:"pixijs-is--fast"},"PixiJS Is ... Fast"),(0,s.kt)("p",null,"One of the major features that distinguishes PixiJS from other web-based rendering solutions is ",(0,s.kt)("em",{parentName:"p"},"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."),(0,s.kt)("h2",{id:"-more-than-just-sprites"},"... More Than Just Sprites"),(0,s.kt)("p",null,"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 ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.MeshRope.html"},"MeshRope"),". Draw polygons, lines, circles and other primitives with ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Graphics.html"},"Graphics"),". ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/scene.Text.html"},"Text")," 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."),(0,s.kt)("h2",{id:"-hardware-accelerated"},"... Hardware accelerated"),(0,s.kt)("p",null,"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 ",(0,s.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/filters.Filter.html"},"Filter")," 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."),(0,s.kt)("h2",{id:"-open-source"},"... Open Source"),(0,s.kt)("p",null,"Want to understand how the engine works? Trying to track down a bug? Been burned by closed-source projects going dark? With PixiJS, you get a mature project with full source code access. We're MIT licensed for compatibility, and ",(0,s.kt)("a",{parentName:"p",href:"https://github.com/pixijs/pixijs"},"hosted on GitHub")," for issue tracking and ease of access."),(0,s.kt)("h2",{id:"-extensible"},"... Extensible"),(0,s.kt)("p",null,"Open source helps. So does being based on JavaScript. But the real reason PixiJS is easy to extend is the clean internal API that underlies every part of the system. After years of development and 5 major releases, PixiJS is ready to make your project a success, no matter what your needs."),(0,s.kt)("h2",{id:"-easy-to-deploy"},"... Easy to Deploy"),(0,s.kt)("p",null,"Flash required the player. Unity requires an installer or app store. PixiJS requires... a browser. Deploying PixiJS on the web is exactly like deploying a web site. That's all it is - JavaScript + images + audio, like you've done a hundred times. Your users simply visit a URL, and your game or other content is ready to run. But it doesn't stop at the web. If you want to deploy a mobile app, wrap your PixiJS code in Cordova. Want to deploy a standalone desktop program? Build an Electron wrapper, and you're ready to rock."))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/assets/js/a97eb394.042b3a3e.js b/assets/js/a97eb394.042b3a3e.js new file mode 100644 index 000000000..57cabad4a --- /dev/null +++ b/assets/js/a97eb394.042b3a3e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8325],{8094:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>n,contentTitle:()=>p,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>m});var a=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:9,custom_edit_url:null,title:"Multipass Mesh"},p=void 0,o={unversionedId:"examples/mesh-and-shaders/multipass-mesh",id:"examples/mesh-and-shaders/multipass-mesh",title:"Multipass Mesh",description:"",source:"@site/docs/examples/mesh-and-shaders/multipass-mesh.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/multipass-mesh",permalink:"/8.x/examples/mesh-and-shaders/multipass-mesh",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:9,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:9,custom_edit_url:null,title:"Multipass Mesh"},sidebar:"examplesSidebar",previous:{title:"Shader Toy Mesh",permalink:"/8.x/examples/mesh-and-shaders/shader-toy-mesh"},next:{title:"Texture Rotate",permalink:"/8.x/examples/textures/texture-rotate"}},n={},m=[],h={toc:m};function u(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"meshAndShaders.multipassMesh",pixiVersion:d,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/a97eb394.f671cae0.js b/assets/js/a97eb394.f671cae0.js deleted file mode 100644 index 866563e28..000000000 --- a/assets/js/a97eb394.f671cae0.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[8325],{8094:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>n,contentTitle:()=>p,default:()=>u,frontMatter:()=>l,metadata:()=>o,toc:()=>m});var a=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const l={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:9,custom_edit_url:null,title:"Multipass Mesh"},p=void 0,o={unversionedId:"examples/mesh-and-shaders/multipass-mesh",id:"examples/mesh-and-shaders/multipass-mesh",title:"Multipass Mesh",description:"",source:"@site/docs/examples/mesh-and-shaders/multipass-mesh.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/multipass-mesh",permalink:"/examples/mesh-and-shaders/multipass-mesh",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:9,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:9,custom_edit_url:null,title:"Multipass Mesh"},sidebar:"examplesSidebar",previous:{title:"Shader Toy Mesh",permalink:"/examples/mesh-and-shaders/shader-toy-mesh"},next:{title:"Texture Rotate",permalink:"/examples/textures/texture-rotate"}},n={},m=[],h={toc:m};function u(e){let{components:s,...t}=e;return(0,i.kt)("wrapper",(0,a.Z)({},h,t,{components:s,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"meshAndShaders.multipassMesh",pixiVersion:d,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/aa76aa86.c77407c6.js b/assets/js/aa76aa86.c77407c6.js new file mode 100644 index 000000000..911f0e613 --- /dev/null +++ b/assets/js/aa76aa86.c77407c6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9835],{3710:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>n,default:()=>m,frontMatter:()=>p,metadata:()=>o,toc:()=>c});var s=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const p={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Star Warp"},n=void 0,o={unversionedId:"examples/advanced/star-warp",id:"examples/advanced/star-warp",title:"Star Warp",description:"",source:"@site/docs/examples/advanced/star-warp.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/star-warp",permalink:"/8.x/examples/advanced/star-warp",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Star Warp"},sidebar:"examplesSidebar",previous:{title:"Scratch Card",permalink:"/8.x/examples/advanced/scratch-card"},next:{title:"Mouse Trail",permalink:"/8.x/examples/advanced/mouse-trail"}},l={},c=[],u={toc:c};function m(e){let{components:a,...t}=e;return(0,i.kt)("wrapper",(0,s.Z)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"advanced.starWarp",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/aa76aa86.f71adf5b.js b/assets/js/aa76aa86.f71adf5b.js deleted file mode 100644 index 333e28b1a..000000000 --- a/assets/js/aa76aa86.f71adf5b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[9835],{3710:(e,a,t)=>{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>n,default:()=>m,frontMatter:()=>p,metadata:()=>o,toc:()=>c});var s=t(7462),i=(t(7294),t(3905)),r=t(8010),d=t(7949);const p={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Star Warp"},n=void 0,o={unversionedId:"examples/advanced/star-warp",id:"examples/advanced/star-warp",title:"Star Warp",description:"",source:"@site/docs/examples/advanced/star-warp.md",sourceDirName:"examples/advanced",slug:"/examples/advanced/star-warp",permalink:"/examples/advanced/star-warp",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:2,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:2,custom_edit_url:null,title:"Star Warp"},sidebar:"examplesSidebar",previous:{title:"Scratch Card",permalink:"/examples/advanced/scratch-card"},next:{title:"Mouse Trail",permalink:"/examples/advanced/mouse-trail"}},l={},c=[],u={toc:c};function m(e){let{components:a,...t}=e;return(0,i.kt)("wrapper",(0,s.Z)({},u,t,{components:a,mdxType:"MDXLayout"}),(0,i.kt)(r.Z,{id:"advanced.starWarp",pixiVersion:d,mdxType:"Example"}))}m.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/adc40f7e.3e45ea62.js b/assets/js/adc40f7e.3e45ea62.js new file mode 100644 index 000000000..d0f03b114 --- /dev/null +++ b/assets/js/adc40f7e.3e45ea62.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2536],{6964:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>h,contentTitle:()=>n,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>m});var a=t(7462),r=(t(7294),t(3905)),d=t(8010),i=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Shared Geometry"},n=void 0,l={unversionedId:"examples/mesh-and-shaders/shared-geometry",id:"examples/mesh-and-shaders/shared-geometry",title:"Shared Geometry",description:"",source:"@site/docs/examples/mesh-and-shaders/shared-geometry.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/shared-geometry",permalink:"/8.x/examples/mesh-and-shaders/shared-geometry",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Shared Geometry"},sidebar:"examplesSidebar",previous:{title:"Triangle Textured",permalink:"/8.x/examples/mesh-and-shaders/triangle-textured"},next:{title:"Shared Shader",permalink:"/8.x/examples/mesh-and-shaders/shared-shader"}},h={},m=[],p={toc:m};function u(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},p,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(d.Z,{id:"meshAndShaders.sharedGeometry",pixiVersion:i,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/adc40f7e.8d433125.js b/assets/js/adc40f7e.8d433125.js deleted file mode 100644 index f1c9ce2d7..000000000 --- a/assets/js/adc40f7e.8d433125.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[2536],{6964:(e,s,t)=>{t.r(s),t.d(s,{assets:()=>h,contentTitle:()=>n,default:()=>u,frontMatter:()=>o,metadata:()=>l,toc:()=>m});var a=t(7462),r=(t(7294),t(3905)),d=t(8010),i=t(7949);const o={hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Shared Geometry"},n=void 0,l={unversionedId:"examples/mesh-and-shaders/shared-geometry",id:"examples/mesh-and-shaders/shared-geometry",title:"Shared Geometry",description:"",source:"@site/docs/examples/mesh-and-shaders/shared-geometry.md",sourceDirName:"examples/mesh-and-shaders",slug:"/examples/mesh-and-shaders/shared-geometry",permalink:"/examples/mesh-and-shaders/shared-geometry",draft:!1,editUrl:null,tags:[],version:"current",sidebarPosition:5,frontMatter:{hide_table_of_contents:!0,hide_edit_this_page:!0,sidebar_position:5,custom_edit_url:null,title:"Shared Geometry"},sidebar:"examplesSidebar",previous:{title:"Triangle Textured",permalink:"/examples/mesh-and-shaders/triangle-textured"},next:{title:"Shared Shader",permalink:"/examples/mesh-and-shaders/shared-shader"}},h={},m=[],p={toc:m};function u(e){let{components:s,...t}=e;return(0,r.kt)("wrapper",(0,a.Z)({},p,t,{components:s,mdxType:"MDXLayout"}),(0,r.kt)(d.Z,{id:"meshAndShaders.sharedGeometry",pixiVersion:i,mdxType:"Example"}))}u.isMDXComponent=!0},7949:e=>{e.exports=JSON.parse('{"versionLabel":"v8.x","version":"8.0.0","releaseNotes":"https://github.com/pixijs/pixijs/releases/tag/v8.0.0","build":"https://pixijs.download/v8.0.0/pixi.min.js","docs":"https://pixijs.download/v8.0.0/docs/index.html","npm":"8.0.0","prerelease":false,"latest":true}')}}]); \ No newline at end of file diff --git a/assets/js/adcb3b88.320238e5.js b/assets/js/adcb3b88.12d04bd9.js similarity index 99% rename from assets/js/adcb3b88.320238e5.js rename to assets/js/adcb3b88.12d04bd9.js index 90e5c9435..6c2ae6d3b 100644 --- a/assets/js/adcb3b88.320238e5.js +++ b/assets/js/adcb3b88.12d04bd9.js @@ -1 +1 @@ -"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4200],{4819:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"7.x","label":"v7.x","banner":null,"badge":false,"noIndex":false,"className":"docs-version-7.x","isLast":true,"docsSidebars":{"guidesSidebar":[{"type":"link","label":"Welcome","href":"/7.x/guides/","docId":"guides/index"},{"type":"category","label":"Basics","collapsed":true,"items":[{"type":"link","label":"What PixiJS Is","href":"/7.x/guides/basics/what-pixijs-is","docId":"guides/basics/what-pixijs-is"},{"type":"link","label":"What PixiJS Is Not","href":"/7.x/guides/basics/what-pixijs-is-not","docId":"guides/basics/what-pixijs-is-not"},{"type":"link","label":"Getting Started","href":"/7.x/guides/basics/getting-started","docId":"guides/basics/getting-started"},{"type":"link","label":"Architecture Overview","href":"/7.x/guides/basics/architecture-overview","docId":"guides/basics/architecture-overview"},{"type":"link","label":"Render Loop","href":"/7.x/guides/basics/render-loop","docId":"guides/basics/render-loop"},{"type":"link","label":"Scene Graph","href":"/7.x/guides/basics/scene-graph","docId":"guides/basics/scene-graph"}],"collapsible":true},{"type":"category","label":"Components","collapsed":true,"items":[{"type":"link","label":"Assets","href":"/7.x/guides/components/assets","docId":"guides/components/assets"},{"type":"link","label":"Containers","href":"/7.x/guides/components/containers","docId":"guides/components/containers"},{"type":"link","label":"Display Objects","href":"/7.x/guides/components/display-object","docId":"guides/components/display-object"},{"type":"link","label":"Graphics","href":"/7.x/guides/components/graphics","docId":"guides/components/graphics"},{"type":"link","label":"Interaction","href":"/7.x/guides/components/interaction","docId":"guides/components/interaction"},{"type":"link","label":"Sprites","href":"/7.x/guides/components/sprites","docId":"guides/components/sprites"},{"type":"link","label":"Spritesheets","href":"/7.x/guides/components/sprite-sheets","docId":"guides/components/sprite-sheets"},{"type":"link","label":"Text","href":"/7.x/guides/components/text","docId":"guides/components/text"},{"type":"link","label":"Textures","href":"/7.x/guides/components/textures","docId":"guides/components/textures"}],"collapsible":true},{"type":"category","label":"Production","collapsed":true,"items":[{"type":"link","label":"Performance Tips","href":"/7.x/guides/production/performance-tips","docId":"guides/production/performance-tips"}],"collapsible":true},{"type":"category","label":"Migrations","collapsed":true,"items":[{"type":"link","label":"Upgrading PixiJS","href":"/7.x/guides/migrations/upgrading","docId":"guides/migrations/upgrading"},{"type":"link","label":"v7 Migration Guide","href":"/7.x/guides/migrations/v7","docId":"guides/migrations/v7"},{"type":"link","label":"v6 Migration Guide","href":"/7.x/guides/migrations/v6","docId":"guides/migrations/v6"},{"type":"link","label":"v5 Migration Guide","href":"/7.x/guides/migrations/v5","docId":"guides/migrations/v5"}],"collapsible":true}],"examplesSidebar":[{"type":"link","label":"Examples","href":"/7.x/examples/","docId":"examples/index"},{"type":"category","label":"Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Container","href":"/7.x/examples/basic/container","docId":"examples/basic/container"},{"type":"link","label":"Transparent Background","href":"/7.x/examples/basic/transparent-background","docId":"examples/basic/transparent-background"},{"type":"link","label":"Tinting","href":"/7.x/examples/basic/tinting","docId":"examples/basic/tinting"},{"type":"link","label":"Cache As Bitmap","href":"/7.x/examples/basic/cache-as-bitmap","docId":"examples/basic/cache-as-bitmap"},{"type":"link","label":"Particle Container","href":"/7.x/examples/basic/particle-container","docId":"examples/basic/particle-container"},{"type":"link","label":"Blend Modes","href":"/7.x/examples/basic/blend-modes","docId":"examples/basic/blend-modes"},{"type":"link","label":"Simple Plane","href":"/7.x/examples/basic/simple-plane","docId":"examples/basic/simple-plane"}]},{"type":"category","label":"Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Slots","href":"/7.x/examples/advanced/slots","docId":"examples/advanced/slots"},{"type":"link","label":"Scratch Card","href":"/7.x/examples/advanced/scratch-card","docId":"examples/advanced/scratch-card"},{"type":"link","label":"Star Warp","href":"/7.x/examples/advanced/star-warp","docId":"examples/advanced/star-warp"},{"type":"link","label":"Mouse Trail","href":"/7.x/examples/advanced/mouse-trail","docId":"examples/advanced/mouse-trail"},{"type":"link","label":"Screen Shot","href":"/7.x/examples/advanced/screen-shot","docId":"examples/advanced/screen-shot"},{"type":"link","label":"Collision Detection","href":"/7.x/examples/advanced/collision-detection","docId":"examples/advanced/collision-detection"},{"type":"link","label":"Spinners","href":"/7.x/examples/advanced/spinners","docId":"examples/advanced/spinners"}]},{"type":"category","label":"Sprite","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/7.x/examples/sprite/basic","docId":"examples/sprite/basic"},{"type":"link","label":"Texture Swap","href":"/7.x/examples/sprite/texture-swap","docId":"examples/sprite/texture-swap"},{"type":"link","label":"Animated Sprite Explosion","href":"/7.x/examples/sprite/animated-sprite-explosion","docId":"examples/sprite/animated-sprite-explosion"},{"type":"link","label":"Animated Sprite Jet","href":"/7.x/examples/sprite/animated-sprite-jet","docId":"examples/sprite/animated-sprite-jet"},{"type":"link","label":"Animated Sprite Animation Speed","href":"/7.x/examples/sprite/animated-sprite-animation-speed","docId":"examples/sprite/animated-sprite-animation-speed"},{"type":"link","label":"Tiling Sprite","href":"/7.x/examples/sprite/tiling-sprite","docId":"examples/sprite/tiling-sprite"},{"type":"link","label":"Video","href":"/7.x/examples/sprite/video","docId":"examples/sprite/video"}]},{"type":"category","label":"Text","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Pixi Text","href":"/7.x/examples/text/pixi-text","docId":"examples/text/pixi-text"},{"type":"link","label":"Bitmap Text","href":"/7.x/examples/text/bitmap-text","docId":"examples/text/bitmap-text"},{"type":"link","label":"From Font","href":"/7.x/examples/text/from-font","docId":"examples/text/from-font"},{"type":"link","label":"Web Font","href":"/7.x/examples/text/web-font","docId":"examples/text/web-font"}]},{"type":"category","label":"Graphics","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Simple","href":"/7.x/examples/graphics/simple","docId":"examples/graphics/simple"},{"type":"link","label":"Advanced","href":"/7.x/examples/graphics/advanced","docId":"examples/graphics/advanced"},{"type":"link","label":"Dynamic","href":"/7.x/examples/graphics/dynamic","docId":"examples/graphics/dynamic"}]},{"type":"category","label":"Events","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Click","href":"/7.x/examples/events/click","docId":"examples/events/click"},{"type":"link","label":"Interactivity","href":"/7.x/examples/events/interactivity","docId":"examples/events/interactivity"},{"type":"link","label":"Dragging","href":"/7.x/examples/events/dragging","docId":"examples/events/dragging"},{"type":"link","label":"Custom Mouse Icon","href":"/7.x/examples/events/custom-mouse-icon","docId":"examples/events/custom-mouse-icon"},{"type":"link","label":"Custom Hitarea","href":"/7.x/examples/events/custom-hitarea","docId":"examples/events/custom-hitarea"},{"type":"link","label":"Logger","href":"/7.x/examples/events/logger","docId":"examples/events/logger"},{"type":"link","label":"Nested Boundary With Projection","href":"/7.x/examples/events/nested-boundary-with-projection","docId":"examples/events/nested-boundary-with-projection"},{"type":"link","label":"Pointer Tracker","href":"/7.x/examples/events/pointer-tracker","docId":"examples/events/pointer-tracker"},{"type":"link","label":"Slider","href":"/7.x/examples/events/slider","docId":"examples/events/slider"}]},{"type":"category","label":"Masks","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Graphics","href":"/7.x/examples/masks/graphics","docId":"examples/masks/graphics"},{"type":"link","label":"Sprite","href":"/7.x/examples/masks/sprite","docId":"examples/masks/sprite"},{"type":"link","label":"Filter","href":"/7.x/examples/masks/filter","docId":"examples/masks/filter"}]},{"type":"category","label":"Filters Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Blur","href":"/7.x/examples/filters-basic/blur","docId":"examples/filters-basic/blur"},{"type":"link","label":"Color Matrix","href":"/7.x/examples/filters-basic/color-matrix","docId":"examples/filters-basic/color-matrix"},{"type":"link","label":"Displacement Map Crawlies","href":"/7.x/examples/filters-basic/displacement-map-crawlies","docId":"examples/filters-basic/displacement-map-crawlies"},{"type":"link","label":"Displacement Map Flag","href":"/7.x/examples/filters-basic/displacement-map-flag","docId":"examples/filters-basic/displacement-map-flag"}]},{"type":"category","label":"Filters Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Mouse Blending","href":"/7.x/examples/filters-advanced/mouse-blending","docId":"examples/filters-advanced/mouse-blending"},{"type":"link","label":"Custom","href":"/7.x/examples/filters-advanced/custom","docId":"examples/filters-advanced/custom"},{"type":"link","label":"Shader Toy Filter Render Texture","href":"/7.x/examples/filters-advanced/shader-toy-filter-render-texture","docId":"examples/filters-advanced/shader-toy-filter-render-texture"}]},{"type":"category","label":"Mesh And Shaders","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Textured Mesh Basic","href":"/7.x/examples/mesh-and-shaders/textured-mesh-basic","docId":"examples/mesh-and-shaders/textured-mesh-basic"},{"type":"link","label":"Textured Mesh Advanced","href":"/7.x/examples/mesh-and-shaders/textured-mesh-advanced","docId":"examples/mesh-and-shaders/textured-mesh-advanced"},{"type":"link","label":"Triangle","href":"/7.x/examples/mesh-and-shaders/triangle","docId":"examples/mesh-and-shaders/triangle"},{"type":"link","label":"Triangle Color","href":"/7.x/examples/mesh-and-shaders/triangle-color","docId":"examples/mesh-and-shaders/triangle-color"},{"type":"link","label":"Triangle Textured","href":"/7.x/examples/mesh-and-shaders/triangle-textured","docId":"examples/mesh-and-shaders/triangle-textured"},{"type":"link","label":"Uniforms","href":"/7.x/examples/mesh-and-shaders/uniforms","docId":"examples/mesh-and-shaders/uniforms"},{"type":"link","label":"Sharing Geometry","href":"/7.x/examples/mesh-and-shaders/sharing-geometry","docId":"examples/mesh-and-shaders/sharing-geometry"},{"type":"link","label":"Shared Shader","href":"/7.x/examples/mesh-and-shaders/shared-shader","docId":"examples/mesh-and-shaders/shared-shader"},{"type":"link","label":"Merging Geometry","href":"/7.x/examples/mesh-and-shaders/merging-geometry","docId":"examples/mesh-and-shaders/merging-geometry"},{"type":"link","label":"Interleaving Geometry","href":"/7.x/examples/mesh-and-shaders/interleaving-geometry","docId":"examples/mesh-and-shaders/interleaving-geometry"},{"type":"link","label":"Instanced Geometry","href":"/7.x/examples/mesh-and-shaders/instanced-geometry","docId":"examples/mesh-and-shaders/instanced-geometry"},{"type":"link","label":"Shader Toy Mesh","href":"/7.x/examples/mesh-and-shaders/shader-toy-mesh","docId":"examples/mesh-and-shaders/shader-toy-mesh"},{"type":"link","label":"Multi Pass Shader Generated Mesh","href":"/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh","docId":"examples/mesh-and-shaders/multi-pass-shader-generated-mesh"}]},{"type":"category","label":"Textures","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Texture Rotate","href":"/7.x/examples/textures/texture-rotate","docId":"examples/textures/texture-rotate"},{"type":"link","label":"Render Texture Basic","href":"/7.x/examples/textures/render-texture-basic","docId":"examples/textures/render-texture-basic"},{"type":"link","label":"Render Texture Advanced","href":"/7.x/examples/textures/render-texture-advanced","docId":"examples/textures/render-texture-advanced"},{"type":"link","label":"Gradient Basic","href":"/7.x/examples/textures/gradient-basic","docId":"examples/textures/gradient-basic"},{"type":"link","label":"Gradient Resource","href":"/7.x/examples/textures/gradient-resource","docId":"examples/textures/gradient-resource"}]},{"type":"category","label":"Assets","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Promise","href":"/7.x/examples/assets/promise","docId":"examples/assets/promise"},{"type":"link","label":"Async","href":"/7.x/examples/assets/async","docId":"examples/assets/async"},{"type":"link","label":"Multiple","href":"/7.x/examples/assets/multiple","docId":"examples/assets/multiple"},{"type":"link","label":"Background","href":"/7.x/examples/assets/background","docId":"examples/assets/background"},{"type":"link","label":"Bundle","href":"/7.x/examples/assets/bundle","docId":"examples/assets/bundle"}]},{"type":"category","label":"Offscreen Canvas","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/7.x/examples/offscreen-canvas/basic","docId":"examples/offscreen-canvas/basic"}]}]},"docs":{"branding":{"id":"branding","title":"Branding","description":"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue."},"examples/advanced/collision-detection":{"id":"examples/advanced/collision-detection","title":"Collision Detection","description":"","sidebar":"examplesSidebar"},"examples/advanced/mouse-trail":{"id":"examples/advanced/mouse-trail","title":"Mouse Trail","description":"","sidebar":"examplesSidebar"},"examples/advanced/scratch-card":{"id":"examples/advanced/scratch-card","title":"Scratch Card","description":"","sidebar":"examplesSidebar"},"examples/advanced/screen-shot":{"id":"examples/advanced/screen-shot","title":"Screen Shot","description":"","sidebar":"examplesSidebar"},"examples/advanced/slots":{"id":"examples/advanced/slots","title":"Slots","description":"","sidebar":"examplesSidebar"},"examples/advanced/spinners":{"id":"examples/advanced/spinners","title":"Spinners","description":"","sidebar":"examplesSidebar"},"examples/advanced/star-warp":{"id":"examples/advanced/star-warp","title":"Star Warp","description":"","sidebar":"examplesSidebar"},"examples/assets/async":{"id":"examples/assets/async","title":"Async","description":"","sidebar":"examplesSidebar"},"examples/assets/background":{"id":"examples/assets/background","title":"Background","description":"","sidebar":"examplesSidebar"},"examples/assets/bundle":{"id":"examples/assets/bundle","title":"Bundle","description":"","sidebar":"examplesSidebar"},"examples/assets/multiple":{"id":"examples/assets/multiple","title":"Multiple","description":"","sidebar":"examplesSidebar"},"examples/assets/promise":{"id":"examples/assets/promise","title":"Promise","description":"","sidebar":"examplesSidebar"},"examples/basic/blend-modes":{"id":"examples/basic/blend-modes","title":"Blend Modes","description":"","sidebar":"examplesSidebar"},"examples/basic/cache-as-bitmap":{"id":"examples/basic/cache-as-bitmap","title":"Cache As Bitmap","description":"","sidebar":"examplesSidebar"},"examples/basic/container":{"id":"examples/basic/container","title":"Container","description":"","sidebar":"examplesSidebar"},"examples/basic/particle-container":{"id":"examples/basic/particle-container","title":"Particle Container","description":"","sidebar":"examplesSidebar"},"examples/basic/simple-plane":{"id":"examples/basic/simple-plane","title":"Simple Plane","description":"","sidebar":"examplesSidebar"},"examples/basic/tinting":{"id":"examples/basic/tinting","title":"Tinting","description":"","sidebar":"examplesSidebar"},"examples/basic/transparent-background":{"id":"examples/basic/transparent-background","title":"Transparent Background","description":"","sidebar":"examplesSidebar"},"examples/events/click":{"id":"examples/events/click","title":"Click","description":"","sidebar":"examplesSidebar"},"examples/events/custom-hitarea":{"id":"examples/events/custom-hitarea","title":"Custom Hitarea","description":"","sidebar":"examplesSidebar"},"examples/events/custom-mouse-icon":{"id":"examples/events/custom-mouse-icon","title":"Custom Mouse Icon","description":"","sidebar":"examplesSidebar"},"examples/events/dragging":{"id":"examples/events/dragging","title":"Dragging","description":"","sidebar":"examplesSidebar"},"examples/events/interactivity":{"id":"examples/events/interactivity","title":"Interactivity","description":"","sidebar":"examplesSidebar"},"examples/events/logger":{"id":"examples/events/logger","title":"Logger","description":"","sidebar":"examplesSidebar"},"examples/events/nested-boundary-with-projection":{"id":"examples/events/nested-boundary-with-projection","title":"Nested Boundary With Projection","description":"","sidebar":"examplesSidebar"},"examples/events/pointer-tracker":{"id":"examples/events/pointer-tracker","title":"Pointer Tracker","description":"","sidebar":"examplesSidebar"},"examples/events/slider":{"id":"examples/events/slider","title":"Slider","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/custom":{"id":"examples/filters-advanced/custom","title":"Custom","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/mouse-blending":{"id":"examples/filters-advanced/mouse-blending","title":"Mouse Blending","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/shader-toy-filter-render-texture":{"id":"examples/filters-advanced/shader-toy-filter-render-texture","title":"Shader Toy Filter Render Texture","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/blur":{"id":"examples/filters-basic/blur","title":"Blur","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/color-matrix":{"id":"examples/filters-basic/color-matrix","title":"Color Matrix","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-crawlies":{"id":"examples/filters-basic/displacement-map-crawlies","title":"Displacement Map Crawlies","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-flag":{"id":"examples/filters-basic/displacement-map-flag","title":"Displacement Map Flag","description":"","sidebar":"examplesSidebar"},"examples/graphics/advanced":{"id":"examples/graphics/advanced","title":"Advanced","description":"","sidebar":"examplesSidebar"},"examples/graphics/dynamic":{"id":"examples/graphics/dynamic","title":"Dynamic","description":"","sidebar":"examplesSidebar"},"examples/graphics/simple":{"id":"examples/graphics/simple","title":"Simple","description":"","sidebar":"examplesSidebar"},"examples/index":{"id":"examples/index","title":"Examples","description":"Welcome to the PixiJS Examples page! Here you can find a variety of demos and code snippets to help you get started with PixiJS.","sidebar":"examplesSidebar"},"examples/masks/filter":{"id":"examples/masks/filter","title":"Filter","description":"","sidebar":"examplesSidebar"},"examples/masks/graphics":{"id":"examples/masks/graphics","title":"Graphics","description":"","sidebar":"examplesSidebar"},"examples/masks/sprite":{"id":"examples/masks/sprite","title":"Sprite","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/instanced-geometry":{"id":"examples/mesh-and-shaders/instanced-geometry","title":"Instanced Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/interleaving-geometry":{"id":"examples/mesh-and-shaders/interleaving-geometry","title":"Interleaving Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/merging-geometry":{"id":"examples/mesh-and-shaders/merging-geometry","title":"Merging Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/multi-pass-shader-generated-mesh":{"id":"examples/mesh-and-shaders/multi-pass-shader-generated-mesh","title":"Multi Pass Shader Generated Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shader-toy-mesh":{"id":"examples/mesh-and-shaders/shader-toy-mesh","title":"Shader Toy Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-shader":{"id":"examples/mesh-and-shaders/shared-shader","title":"Shared Shader","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/sharing-geometry":{"id":"examples/mesh-and-shaders/sharing-geometry","title":"Sharing Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-advanced":{"id":"examples/mesh-and-shaders/textured-mesh-advanced","title":"Textured Mesh Advanced","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-basic":{"id":"examples/mesh-and-shaders/textured-mesh-basic","title":"Textured Mesh Basic","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle":{"id":"examples/mesh-and-shaders/triangle","title":"Triangle","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-color":{"id":"examples/mesh-and-shaders/triangle-color","title":"Triangle Color","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-textured":{"id":"examples/mesh-and-shaders/triangle-textured","title":"Triangle Textured","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/uniforms":{"id":"examples/mesh-and-shaders/uniforms","title":"Uniforms","description":"","sidebar":"examplesSidebar"},"examples/offscreen-canvas/basic":{"id":"examples/offscreen-canvas/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-animation-speed":{"id":"examples/sprite/animated-sprite-animation-speed","title":"Animated Sprite Animation Speed","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-explosion":{"id":"examples/sprite/animated-sprite-explosion","title":"Animated Sprite Explosion","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-jet":{"id":"examples/sprite/animated-sprite-jet","title":"Animated Sprite Jet","description":"","sidebar":"examplesSidebar"},"examples/sprite/basic":{"id":"examples/sprite/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/texture-swap":{"id":"examples/sprite/texture-swap","title":"Texture Swap","description":"","sidebar":"examplesSidebar"},"examples/sprite/tiling-sprite":{"id":"examples/sprite/tiling-sprite","title":"Tiling Sprite","description":"","sidebar":"examplesSidebar"},"examples/sprite/video":{"id":"examples/sprite/video","title":"Video","description":"","sidebar":"examplesSidebar"},"examples/text/bitmap-text":{"id":"examples/text/bitmap-text","title":"Bitmap Text","description":"","sidebar":"examplesSidebar"},"examples/text/from-font":{"id":"examples/text/from-font","title":"From Font","description":"","sidebar":"examplesSidebar"},"examples/text/pixi-text":{"id":"examples/text/pixi-text","title":"Pixi Text","description":"","sidebar":"examplesSidebar"},"examples/text/web-font":{"id":"examples/text/web-font","title":"Web Font","description":"","sidebar":"examplesSidebar"},"examples/textures/gradient-basic":{"id":"examples/textures/gradient-basic","title":"Gradient Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/gradient-resource":{"id":"examples/textures/gradient-resource","title":"Gradient Resource","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-advanced":{"id":"examples/textures/render-texture-advanced","title":"Render Texture Advanced","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-basic":{"id":"examples/textures/render-texture-basic","title":"Render Texture Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/texture-rotate":{"id":"examples/textures/texture-rotate","title":"Texture Rotate","description":"","sidebar":"examplesSidebar"},"faq":{"id":"faq","title":"FAQ","description":"What is PixiJS for?"},"guides/basics/architecture-overview":{"id":"guides/basics/architecture-overview","title":"Architecture Overview","description":"OK, now that you\'ve gotten a feel for how easy it is to build a PixiJS application, let\'s get into the specifics. For the rest of the Basics section, we\'re going to work from the high level down to the details. We\'ll start with an overview of how PixiJS is put together.","sidebar":"guidesSidebar"},"guides/basics/getting-started":{"id":"guides/basics/getting-started","title":"Getting Started","description":"In this section we\'re going to build the simplest possible PixiJS application. In doing so, we\'ll walk through the basics of how to build and serve the code.","sidebar":"guidesSidebar"},"guides/basics/render-loop":{"id":"guides/basics/render-loop","title":"Render Loop","description":"Now that you understand the major parts of the system, let\'s look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.","sidebar":"guidesSidebar"},"guides/basics/scene-graph":{"id":"guides/basics/scene-graph","title":"Scene Graph","description":"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.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is":{"id":"guides/basics/what-pixijs-is","title":"What PixiJS Is","description":"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is-not":{"id":"guides/basics/what-pixijs-is-not","title":"What PixiJS Is Not","description":"While PixiJS can do many things, there are things it can\'t do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you\'re about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.","sidebar":"guidesSidebar"},"guides/components/assets":{"id":"guides/components/assets","title":"Assets","description":"The Assets package","sidebar":"guidesSidebar"},"guides/components/containers":{"id":"guides/components/containers","title":"Containers","description":"The Container 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.","sidebar":"guidesSidebar"},"guides/components/display-object":{"id":"guides/components/display-object","title":"Display Objects","description":"DisplayObject 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 to understand how to move, scale, rotate and compose the visual elements of your project.","sidebar":"guidesSidebar"},"guides/components/graphics":{"id":"guides/components/graphics","title":"Graphics","description":"Graphics 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?","sidebar":"guidesSidebar"},"guides/components/interaction":{"id":"guides/components/interaction","title":"Interaction","description":"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.","sidebar":"guidesSidebar"},"guides/components/sprite-sheets":{"id":"guides/components/sprite-sheets","title":"Spritesheets","description":"Now that you understand basic sprites, it\'s time to talk about a better way to create them - the Spritesheet class.","sidebar":"guidesSidebar"},"guides/components/sprites":{"id":"guides/components/sprites","title":"Sprites","description":"Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.","sidebar":"guidesSidebar"},"guides/components/text":{"id":"guides/components/text","title":"Text","description":"Whether it\'s a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there\'s no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.","sidebar":"guidesSidebar"},"guides/components/textures":{"id":"guides/components/textures","title":"Textures","description":"We\'re slowly working our way down from the high level to the low. We\'ve talked about the scene graph, and in general about display objects that live in it. We\'re about to get to sprites and other simple display objects. But before we do, we need to talk about textures.","sidebar":"guidesSidebar"},"guides/index":{"id":"guides/index","title":"Welcome","description":"PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.","sidebar":"guidesSidebar"},"guides/migrations/upgrading":{"id":"guides/migrations/upgrading","title":"Upgrading PixiJS","description":"PixiJS uses a lot of peerDependencies internally to define the relationship between packages. This has created unpredictable errors because of how npm resolves peers when bumping/upgrading (e.g., #8382, #8268, #8144, #7209).","sidebar":"guidesSidebar"},"guides/migrations/v5":{"id":"guides/migrations/v5","title":"v5 Migration Guide","description":"This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we\'ve try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.","sidebar":"guidesSidebar"},"guides/migrations/v6":{"id":"guides/migrations/v6","title":"v6 Migration Guide","description":"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.","sidebar":"guidesSidebar"},"guides/migrations/v7":{"id":"guides/migrations/v7","title":"v7 Migration Guide","description":"First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn\'t really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.","sidebar":"guidesSidebar"},"guides/production/performance-tips":{"id":"guides/production/performance-tips","title":"Performance Tips","description":"General","sidebar":"guidesSidebar"},"playground/index":{"id":"playground/index","title":"index","description":""},"tutorials/getting-started":{"id":"tutorials/getting-started","title":"getting-started","description":""},"tutorials/index":{"id":"tutorials/index","title":"Tutorials","description":"Welcome to the tutorials page! Here you can find hand-crafted exercises to get you started with the PixiJS."}}}')}}]); \ No newline at end of file +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4200],{4819:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"7.x","label":"v7.x","banner":null,"badge":true,"noIndex":false,"className":"docs-version-7.x","isLast":false,"docsSidebars":{"guidesSidebar":[{"type":"link","label":"Welcome","href":"/7.x/guides/","docId":"guides/index"},{"type":"category","label":"Basics","collapsed":true,"items":[{"type":"link","label":"What PixiJS Is","href":"/7.x/guides/basics/what-pixijs-is","docId":"guides/basics/what-pixijs-is"},{"type":"link","label":"What PixiJS Is Not","href":"/7.x/guides/basics/what-pixijs-is-not","docId":"guides/basics/what-pixijs-is-not"},{"type":"link","label":"Getting Started","href":"/7.x/guides/basics/getting-started","docId":"guides/basics/getting-started"},{"type":"link","label":"Architecture Overview","href":"/7.x/guides/basics/architecture-overview","docId":"guides/basics/architecture-overview"},{"type":"link","label":"Render Loop","href":"/7.x/guides/basics/render-loop","docId":"guides/basics/render-loop"},{"type":"link","label":"Scene Graph","href":"/7.x/guides/basics/scene-graph","docId":"guides/basics/scene-graph"}],"collapsible":true},{"type":"category","label":"Components","collapsed":true,"items":[{"type":"link","label":"Assets","href":"/7.x/guides/components/assets","docId":"guides/components/assets"},{"type":"link","label":"Containers","href":"/7.x/guides/components/containers","docId":"guides/components/containers"},{"type":"link","label":"Display Objects","href":"/7.x/guides/components/display-object","docId":"guides/components/display-object"},{"type":"link","label":"Graphics","href":"/7.x/guides/components/graphics","docId":"guides/components/graphics"},{"type":"link","label":"Interaction","href":"/7.x/guides/components/interaction","docId":"guides/components/interaction"},{"type":"link","label":"Sprites","href":"/7.x/guides/components/sprites","docId":"guides/components/sprites"},{"type":"link","label":"Spritesheets","href":"/7.x/guides/components/sprite-sheets","docId":"guides/components/sprite-sheets"},{"type":"link","label":"Text","href":"/7.x/guides/components/text","docId":"guides/components/text"},{"type":"link","label":"Textures","href":"/7.x/guides/components/textures","docId":"guides/components/textures"}],"collapsible":true},{"type":"category","label":"Production","collapsed":true,"items":[{"type":"link","label":"Performance Tips","href":"/7.x/guides/production/performance-tips","docId":"guides/production/performance-tips"}],"collapsible":true},{"type":"category","label":"Migrations","collapsed":true,"items":[{"type":"link","label":"Upgrading PixiJS","href":"/7.x/guides/migrations/upgrading","docId":"guides/migrations/upgrading"},{"type":"link","label":"v7 Migration Guide","href":"/7.x/guides/migrations/v7","docId":"guides/migrations/v7"},{"type":"link","label":"v6 Migration Guide","href":"/7.x/guides/migrations/v6","docId":"guides/migrations/v6"},{"type":"link","label":"v5 Migration Guide","href":"/7.x/guides/migrations/v5","docId":"guides/migrations/v5"}],"collapsible":true}],"examplesSidebar":[{"type":"link","label":"Examples","href":"/7.x/examples/","docId":"examples/index"},{"type":"category","label":"Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Container","href":"/7.x/examples/basic/container","docId":"examples/basic/container"},{"type":"link","label":"Transparent Background","href":"/7.x/examples/basic/transparent-background","docId":"examples/basic/transparent-background"},{"type":"link","label":"Tinting","href":"/7.x/examples/basic/tinting","docId":"examples/basic/tinting"},{"type":"link","label":"Cache As Bitmap","href":"/7.x/examples/basic/cache-as-bitmap","docId":"examples/basic/cache-as-bitmap"},{"type":"link","label":"Particle Container","href":"/7.x/examples/basic/particle-container","docId":"examples/basic/particle-container"},{"type":"link","label":"Blend Modes","href":"/7.x/examples/basic/blend-modes","docId":"examples/basic/blend-modes"},{"type":"link","label":"Simple Plane","href":"/7.x/examples/basic/simple-plane","docId":"examples/basic/simple-plane"}]},{"type":"category","label":"Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Slots","href":"/7.x/examples/advanced/slots","docId":"examples/advanced/slots"},{"type":"link","label":"Scratch Card","href":"/7.x/examples/advanced/scratch-card","docId":"examples/advanced/scratch-card"},{"type":"link","label":"Star Warp","href":"/7.x/examples/advanced/star-warp","docId":"examples/advanced/star-warp"},{"type":"link","label":"Mouse Trail","href":"/7.x/examples/advanced/mouse-trail","docId":"examples/advanced/mouse-trail"},{"type":"link","label":"Screen Shot","href":"/7.x/examples/advanced/screen-shot","docId":"examples/advanced/screen-shot"},{"type":"link","label":"Collision Detection","href":"/7.x/examples/advanced/collision-detection","docId":"examples/advanced/collision-detection"},{"type":"link","label":"Spinners","href":"/7.x/examples/advanced/spinners","docId":"examples/advanced/spinners"}]},{"type":"category","label":"Sprite","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/7.x/examples/sprite/basic","docId":"examples/sprite/basic"},{"type":"link","label":"Texture Swap","href":"/7.x/examples/sprite/texture-swap","docId":"examples/sprite/texture-swap"},{"type":"link","label":"Animated Sprite Explosion","href":"/7.x/examples/sprite/animated-sprite-explosion","docId":"examples/sprite/animated-sprite-explosion"},{"type":"link","label":"Animated Sprite Jet","href":"/7.x/examples/sprite/animated-sprite-jet","docId":"examples/sprite/animated-sprite-jet"},{"type":"link","label":"Animated Sprite Animation Speed","href":"/7.x/examples/sprite/animated-sprite-animation-speed","docId":"examples/sprite/animated-sprite-animation-speed"},{"type":"link","label":"Tiling Sprite","href":"/7.x/examples/sprite/tiling-sprite","docId":"examples/sprite/tiling-sprite"},{"type":"link","label":"Video","href":"/7.x/examples/sprite/video","docId":"examples/sprite/video"}]},{"type":"category","label":"Text","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Pixi Text","href":"/7.x/examples/text/pixi-text","docId":"examples/text/pixi-text"},{"type":"link","label":"Bitmap Text","href":"/7.x/examples/text/bitmap-text","docId":"examples/text/bitmap-text"},{"type":"link","label":"From Font","href":"/7.x/examples/text/from-font","docId":"examples/text/from-font"},{"type":"link","label":"Web Font","href":"/7.x/examples/text/web-font","docId":"examples/text/web-font"}]},{"type":"category","label":"Graphics","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Simple","href":"/7.x/examples/graphics/simple","docId":"examples/graphics/simple"},{"type":"link","label":"Advanced","href":"/7.x/examples/graphics/advanced","docId":"examples/graphics/advanced"},{"type":"link","label":"Dynamic","href":"/7.x/examples/graphics/dynamic","docId":"examples/graphics/dynamic"}]},{"type":"category","label":"Events","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Click","href":"/7.x/examples/events/click","docId":"examples/events/click"},{"type":"link","label":"Interactivity","href":"/7.x/examples/events/interactivity","docId":"examples/events/interactivity"},{"type":"link","label":"Dragging","href":"/7.x/examples/events/dragging","docId":"examples/events/dragging"},{"type":"link","label":"Custom Mouse Icon","href":"/7.x/examples/events/custom-mouse-icon","docId":"examples/events/custom-mouse-icon"},{"type":"link","label":"Custom Hitarea","href":"/7.x/examples/events/custom-hitarea","docId":"examples/events/custom-hitarea"},{"type":"link","label":"Logger","href":"/7.x/examples/events/logger","docId":"examples/events/logger"},{"type":"link","label":"Nested Boundary With Projection","href":"/7.x/examples/events/nested-boundary-with-projection","docId":"examples/events/nested-boundary-with-projection"},{"type":"link","label":"Pointer Tracker","href":"/7.x/examples/events/pointer-tracker","docId":"examples/events/pointer-tracker"},{"type":"link","label":"Slider","href":"/7.x/examples/events/slider","docId":"examples/events/slider"}]},{"type":"category","label":"Masks","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Graphics","href":"/7.x/examples/masks/graphics","docId":"examples/masks/graphics"},{"type":"link","label":"Sprite","href":"/7.x/examples/masks/sprite","docId":"examples/masks/sprite"},{"type":"link","label":"Filter","href":"/7.x/examples/masks/filter","docId":"examples/masks/filter"}]},{"type":"category","label":"Filters Basic","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Blur","href":"/7.x/examples/filters-basic/blur","docId":"examples/filters-basic/blur"},{"type":"link","label":"Color Matrix","href":"/7.x/examples/filters-basic/color-matrix","docId":"examples/filters-basic/color-matrix"},{"type":"link","label":"Displacement Map Crawlies","href":"/7.x/examples/filters-basic/displacement-map-crawlies","docId":"examples/filters-basic/displacement-map-crawlies"},{"type":"link","label":"Displacement Map Flag","href":"/7.x/examples/filters-basic/displacement-map-flag","docId":"examples/filters-basic/displacement-map-flag"}]},{"type":"category","label":"Filters Advanced","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Mouse Blending","href":"/7.x/examples/filters-advanced/mouse-blending","docId":"examples/filters-advanced/mouse-blending"},{"type":"link","label":"Custom","href":"/7.x/examples/filters-advanced/custom","docId":"examples/filters-advanced/custom"},{"type":"link","label":"Shader Toy Filter Render Texture","href":"/7.x/examples/filters-advanced/shader-toy-filter-render-texture","docId":"examples/filters-advanced/shader-toy-filter-render-texture"}]},{"type":"category","label":"Mesh And Shaders","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Textured Mesh Basic","href":"/7.x/examples/mesh-and-shaders/textured-mesh-basic","docId":"examples/mesh-and-shaders/textured-mesh-basic"},{"type":"link","label":"Textured Mesh Advanced","href":"/7.x/examples/mesh-and-shaders/textured-mesh-advanced","docId":"examples/mesh-and-shaders/textured-mesh-advanced"},{"type":"link","label":"Triangle","href":"/7.x/examples/mesh-and-shaders/triangle","docId":"examples/mesh-and-shaders/triangle"},{"type":"link","label":"Triangle Color","href":"/7.x/examples/mesh-and-shaders/triangle-color","docId":"examples/mesh-and-shaders/triangle-color"},{"type":"link","label":"Triangle Textured","href":"/7.x/examples/mesh-and-shaders/triangle-textured","docId":"examples/mesh-and-shaders/triangle-textured"},{"type":"link","label":"Uniforms","href":"/7.x/examples/mesh-and-shaders/uniforms","docId":"examples/mesh-and-shaders/uniforms"},{"type":"link","label":"Sharing Geometry","href":"/7.x/examples/mesh-and-shaders/sharing-geometry","docId":"examples/mesh-and-shaders/sharing-geometry"},{"type":"link","label":"Shared Shader","href":"/7.x/examples/mesh-and-shaders/shared-shader","docId":"examples/mesh-and-shaders/shared-shader"},{"type":"link","label":"Merging Geometry","href":"/7.x/examples/mesh-and-shaders/merging-geometry","docId":"examples/mesh-and-shaders/merging-geometry"},{"type":"link","label":"Interleaving Geometry","href":"/7.x/examples/mesh-and-shaders/interleaving-geometry","docId":"examples/mesh-and-shaders/interleaving-geometry"},{"type":"link","label":"Instanced Geometry","href":"/7.x/examples/mesh-and-shaders/instanced-geometry","docId":"examples/mesh-and-shaders/instanced-geometry"},{"type":"link","label":"Shader Toy Mesh","href":"/7.x/examples/mesh-and-shaders/shader-toy-mesh","docId":"examples/mesh-and-shaders/shader-toy-mesh"},{"type":"link","label":"Multi Pass Shader Generated Mesh","href":"/7.x/examples/mesh-and-shaders/multi-pass-shader-generated-mesh","docId":"examples/mesh-and-shaders/multi-pass-shader-generated-mesh"}]},{"type":"category","label":"Textures","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Texture Rotate","href":"/7.x/examples/textures/texture-rotate","docId":"examples/textures/texture-rotate"},{"type":"link","label":"Render Texture Basic","href":"/7.x/examples/textures/render-texture-basic","docId":"examples/textures/render-texture-basic"},{"type":"link","label":"Render Texture Advanced","href":"/7.x/examples/textures/render-texture-advanced","docId":"examples/textures/render-texture-advanced"},{"type":"link","label":"Gradient Basic","href":"/7.x/examples/textures/gradient-basic","docId":"examples/textures/gradient-basic"},{"type":"link","label":"Gradient Resource","href":"/7.x/examples/textures/gradient-resource","docId":"examples/textures/gradient-resource"}]},{"type":"category","label":"Assets","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Promise","href":"/7.x/examples/assets/promise","docId":"examples/assets/promise"},{"type":"link","label":"Async","href":"/7.x/examples/assets/async","docId":"examples/assets/async"},{"type":"link","label":"Multiple","href":"/7.x/examples/assets/multiple","docId":"examples/assets/multiple"},{"type":"link","label":"Background","href":"/7.x/examples/assets/background","docId":"examples/assets/background"},{"type":"link","label":"Bundle","href":"/7.x/examples/assets/bundle","docId":"examples/assets/bundle"}]},{"type":"category","label":"Offscreen Canvas","collapsible":true,"collapsed":true,"items":[{"type":"link","label":"Basic","href":"/7.x/examples/offscreen-canvas/basic","docId":"examples/offscreen-canvas/basic"}]}]},"docs":{"branding":{"id":"branding","title":"Branding","description":"Below are links to assorted PixiJS branding assets usable for including on your site, game, or app. All assets here are free-to-use. If you have any questions or requests, please file an issue."},"examples/advanced/collision-detection":{"id":"examples/advanced/collision-detection","title":"Collision Detection","description":"","sidebar":"examplesSidebar"},"examples/advanced/mouse-trail":{"id":"examples/advanced/mouse-trail","title":"Mouse Trail","description":"","sidebar":"examplesSidebar"},"examples/advanced/scratch-card":{"id":"examples/advanced/scratch-card","title":"Scratch Card","description":"","sidebar":"examplesSidebar"},"examples/advanced/screen-shot":{"id":"examples/advanced/screen-shot","title":"Screen Shot","description":"","sidebar":"examplesSidebar"},"examples/advanced/slots":{"id":"examples/advanced/slots","title":"Slots","description":"","sidebar":"examplesSidebar"},"examples/advanced/spinners":{"id":"examples/advanced/spinners","title":"Spinners","description":"","sidebar":"examplesSidebar"},"examples/advanced/star-warp":{"id":"examples/advanced/star-warp","title":"Star Warp","description":"","sidebar":"examplesSidebar"},"examples/assets/async":{"id":"examples/assets/async","title":"Async","description":"","sidebar":"examplesSidebar"},"examples/assets/background":{"id":"examples/assets/background","title":"Background","description":"","sidebar":"examplesSidebar"},"examples/assets/bundle":{"id":"examples/assets/bundle","title":"Bundle","description":"","sidebar":"examplesSidebar"},"examples/assets/multiple":{"id":"examples/assets/multiple","title":"Multiple","description":"","sidebar":"examplesSidebar"},"examples/assets/promise":{"id":"examples/assets/promise","title":"Promise","description":"","sidebar":"examplesSidebar"},"examples/basic/blend-modes":{"id":"examples/basic/blend-modes","title":"Blend Modes","description":"","sidebar":"examplesSidebar"},"examples/basic/cache-as-bitmap":{"id":"examples/basic/cache-as-bitmap","title":"Cache As Bitmap","description":"","sidebar":"examplesSidebar"},"examples/basic/container":{"id":"examples/basic/container","title":"Container","description":"","sidebar":"examplesSidebar"},"examples/basic/particle-container":{"id":"examples/basic/particle-container","title":"Particle Container","description":"","sidebar":"examplesSidebar"},"examples/basic/simple-plane":{"id":"examples/basic/simple-plane","title":"Simple Plane","description":"","sidebar":"examplesSidebar"},"examples/basic/tinting":{"id":"examples/basic/tinting","title":"Tinting","description":"","sidebar":"examplesSidebar"},"examples/basic/transparent-background":{"id":"examples/basic/transparent-background","title":"Transparent Background","description":"","sidebar":"examplesSidebar"},"examples/events/click":{"id":"examples/events/click","title":"Click","description":"","sidebar":"examplesSidebar"},"examples/events/custom-hitarea":{"id":"examples/events/custom-hitarea","title":"Custom Hitarea","description":"","sidebar":"examplesSidebar"},"examples/events/custom-mouse-icon":{"id":"examples/events/custom-mouse-icon","title":"Custom Mouse Icon","description":"","sidebar":"examplesSidebar"},"examples/events/dragging":{"id":"examples/events/dragging","title":"Dragging","description":"","sidebar":"examplesSidebar"},"examples/events/interactivity":{"id":"examples/events/interactivity","title":"Interactivity","description":"","sidebar":"examplesSidebar"},"examples/events/logger":{"id":"examples/events/logger","title":"Logger","description":"","sidebar":"examplesSidebar"},"examples/events/nested-boundary-with-projection":{"id":"examples/events/nested-boundary-with-projection","title":"Nested Boundary With Projection","description":"","sidebar":"examplesSidebar"},"examples/events/pointer-tracker":{"id":"examples/events/pointer-tracker","title":"Pointer Tracker","description":"","sidebar":"examplesSidebar"},"examples/events/slider":{"id":"examples/events/slider","title":"Slider","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/custom":{"id":"examples/filters-advanced/custom","title":"Custom","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/mouse-blending":{"id":"examples/filters-advanced/mouse-blending","title":"Mouse Blending","description":"","sidebar":"examplesSidebar"},"examples/filters-advanced/shader-toy-filter-render-texture":{"id":"examples/filters-advanced/shader-toy-filter-render-texture","title":"Shader Toy Filter Render Texture","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/blur":{"id":"examples/filters-basic/blur","title":"Blur","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/color-matrix":{"id":"examples/filters-basic/color-matrix","title":"Color Matrix","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-crawlies":{"id":"examples/filters-basic/displacement-map-crawlies","title":"Displacement Map Crawlies","description":"","sidebar":"examplesSidebar"},"examples/filters-basic/displacement-map-flag":{"id":"examples/filters-basic/displacement-map-flag","title":"Displacement Map Flag","description":"","sidebar":"examplesSidebar"},"examples/graphics/advanced":{"id":"examples/graphics/advanced","title":"Advanced","description":"","sidebar":"examplesSidebar"},"examples/graphics/dynamic":{"id":"examples/graphics/dynamic","title":"Dynamic","description":"","sidebar":"examplesSidebar"},"examples/graphics/simple":{"id":"examples/graphics/simple","title":"Simple","description":"","sidebar":"examplesSidebar"},"examples/index":{"id":"examples/index","title":"Examples","description":"Welcome to the PixiJS Examples page! Here you can find a variety of demos and code snippets to help you get started with PixiJS.","sidebar":"examplesSidebar"},"examples/masks/filter":{"id":"examples/masks/filter","title":"Filter","description":"","sidebar":"examplesSidebar"},"examples/masks/graphics":{"id":"examples/masks/graphics","title":"Graphics","description":"","sidebar":"examplesSidebar"},"examples/masks/sprite":{"id":"examples/masks/sprite","title":"Sprite","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/instanced-geometry":{"id":"examples/mesh-and-shaders/instanced-geometry","title":"Instanced Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/interleaving-geometry":{"id":"examples/mesh-and-shaders/interleaving-geometry","title":"Interleaving Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/merging-geometry":{"id":"examples/mesh-and-shaders/merging-geometry","title":"Merging Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/multi-pass-shader-generated-mesh":{"id":"examples/mesh-and-shaders/multi-pass-shader-generated-mesh","title":"Multi Pass Shader Generated Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shader-toy-mesh":{"id":"examples/mesh-and-shaders/shader-toy-mesh","title":"Shader Toy Mesh","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/shared-shader":{"id":"examples/mesh-and-shaders/shared-shader","title":"Shared Shader","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/sharing-geometry":{"id":"examples/mesh-and-shaders/sharing-geometry","title":"Sharing Geometry","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-advanced":{"id":"examples/mesh-and-shaders/textured-mesh-advanced","title":"Textured Mesh Advanced","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/textured-mesh-basic":{"id":"examples/mesh-and-shaders/textured-mesh-basic","title":"Textured Mesh Basic","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle":{"id":"examples/mesh-and-shaders/triangle","title":"Triangle","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-color":{"id":"examples/mesh-and-shaders/triangle-color","title":"Triangle Color","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/triangle-textured":{"id":"examples/mesh-and-shaders/triangle-textured","title":"Triangle Textured","description":"","sidebar":"examplesSidebar"},"examples/mesh-and-shaders/uniforms":{"id":"examples/mesh-and-shaders/uniforms","title":"Uniforms","description":"","sidebar":"examplesSidebar"},"examples/offscreen-canvas/basic":{"id":"examples/offscreen-canvas/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-animation-speed":{"id":"examples/sprite/animated-sprite-animation-speed","title":"Animated Sprite Animation Speed","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-explosion":{"id":"examples/sprite/animated-sprite-explosion","title":"Animated Sprite Explosion","description":"","sidebar":"examplesSidebar"},"examples/sprite/animated-sprite-jet":{"id":"examples/sprite/animated-sprite-jet","title":"Animated Sprite Jet","description":"","sidebar":"examplesSidebar"},"examples/sprite/basic":{"id":"examples/sprite/basic","title":"Basic","description":"","sidebar":"examplesSidebar"},"examples/sprite/texture-swap":{"id":"examples/sprite/texture-swap","title":"Texture Swap","description":"","sidebar":"examplesSidebar"},"examples/sprite/tiling-sprite":{"id":"examples/sprite/tiling-sprite","title":"Tiling Sprite","description":"","sidebar":"examplesSidebar"},"examples/sprite/video":{"id":"examples/sprite/video","title":"Video","description":"","sidebar":"examplesSidebar"},"examples/text/bitmap-text":{"id":"examples/text/bitmap-text","title":"Bitmap Text","description":"","sidebar":"examplesSidebar"},"examples/text/from-font":{"id":"examples/text/from-font","title":"From Font","description":"","sidebar":"examplesSidebar"},"examples/text/pixi-text":{"id":"examples/text/pixi-text","title":"Pixi Text","description":"","sidebar":"examplesSidebar"},"examples/text/web-font":{"id":"examples/text/web-font","title":"Web Font","description":"","sidebar":"examplesSidebar"},"examples/textures/gradient-basic":{"id":"examples/textures/gradient-basic","title":"Gradient Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/gradient-resource":{"id":"examples/textures/gradient-resource","title":"Gradient Resource","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-advanced":{"id":"examples/textures/render-texture-advanced","title":"Render Texture Advanced","description":"","sidebar":"examplesSidebar"},"examples/textures/render-texture-basic":{"id":"examples/textures/render-texture-basic","title":"Render Texture Basic","description":"","sidebar":"examplesSidebar"},"examples/textures/texture-rotate":{"id":"examples/textures/texture-rotate","title":"Texture Rotate","description":"","sidebar":"examplesSidebar"},"faq":{"id":"faq","title":"FAQ","description":"What is PixiJS for?"},"guides/basics/architecture-overview":{"id":"guides/basics/architecture-overview","title":"Architecture Overview","description":"OK, now that you\'ve gotten a feel for how easy it is to build a PixiJS application, let\'s get into the specifics. For the rest of the Basics section, we\'re going to work from the high level down to the details. We\'ll start with an overview of how PixiJS is put together.","sidebar":"guidesSidebar"},"guides/basics/getting-started":{"id":"guides/basics/getting-started","title":"Getting Started","description":"In this section we\'re going to build the simplest possible PixiJS application. In doing so, we\'ll walk through the basics of how to build and serve the code.","sidebar":"guidesSidebar"},"guides/basics/render-loop":{"id":"guides/basics/render-loop","title":"Render Loop","description":"Now that you understand the major parts of the system, let\'s look at how these parts work together to get your project onto the screen. Unlike a web page, PixiJS is constantly updating and re-drawing itself, over and over. You update your objects, then PixiJS renders them to the screen, then the process repeats. We call this cycle the render loop.","sidebar":"guidesSidebar"},"guides/basics/scene-graph":{"id":"guides/basics/scene-graph","title":"Scene Graph","description":"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.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is":{"id":"guides/basics/what-pixijs-is","title":"What PixiJS Is","description":"So what exactly is PixiJS? At its heart, PixiJS is a rendering system that uses WebGL (or optionally Canvas) to display images and other 2D visual content. It provides a full scene graph (a hierarchy of objects to render), and provides interaction support to enable handling click and touch events. It is a natural replacement for Flash in the modern HTML5 world, but provides better performance and pixel-level effects that go beyond what Flash could achieve. It is perfect for online games, educational content, interactive ads, data visualization... any web-based application where complex graphics are important. And coupled with technology such as Cordova and Electron, PixiJS apps can be distributed beyond the browser as mobile and desktop applications.","sidebar":"guidesSidebar"},"guides/basics/what-pixijs-is-not":{"id":"guides/basics/what-pixijs-is-not","title":"What PixiJS Is Not","description":"While PixiJS can do many things, there are things it can\'t do, or that require additional tools to accomplish. Newcomers to PixiJS often struggle to identify which tasks PixiJS can solve, and which require outside solutions. If you\'re about to start a project, it can be helpful to know if PixiJS is a good fit for your needs. The following list is obviously incomplete - PixiJS is also not, for example, a duck - but it includes many common tasks or features that you might expect us to support.","sidebar":"guidesSidebar"},"guides/components/assets":{"id":"guides/components/assets","title":"Assets","description":"The Assets package","sidebar":"guidesSidebar"},"guides/components/containers":{"id":"guides/components/containers","title":"Containers","description":"The Container 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.","sidebar":"guidesSidebar"},"guides/components/display-object":{"id":"guides/components/display-object","title":"Display Objects","description":"DisplayObject 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 to understand how to move, scale, rotate and compose the visual elements of your project.","sidebar":"guidesSidebar"},"guides/components/graphics":{"id":"guides/components/graphics","title":"Graphics","description":"Graphics 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?","sidebar":"guidesSidebar"},"guides/components/interaction":{"id":"guides/components/interaction","title":"Interaction","description":"PixiJS is primarily a rendering system, but it also includes support for interactivity. Adding support for mouse and touch events to your project is simple and consistent.","sidebar":"guidesSidebar"},"guides/components/sprite-sheets":{"id":"guides/components/sprite-sheets","title":"Spritesheets","description":"Now that you understand basic sprites, it\'s time to talk about a better way to create them - the Spritesheet class.","sidebar":"guidesSidebar"},"guides/components/sprites":{"id":"guides/components/sprites","title":"Sprites","description":"Sprites are the simplest and most common renderable object in PixiJS. They represent a single image to be displayed on the screen. Each Sprite contains a Texture to be drawn, along with all the transformation and display state required to function in the scene graph.","sidebar":"guidesSidebar"},"guides/components/text":{"id":"guides/components/text","title":"Text","description":"Whether it\'s a high score or a diagram label, text is often the best way to convey information in your projects. Surprisingly, drawing text to the screen with WebGL is a very complex process - there\'s no built in support for it at all. One of the values PixiJS provides is in hiding this complexity to allow you to draw text in diverse styles, fonts and colors with a few lines of code. In addition, these bits of text are just as much scene objects as sprites - you can tint text, rotate it, alpha-blend it, and otherwise treat it like any other graphical object.","sidebar":"guidesSidebar"},"guides/components/textures":{"id":"guides/components/textures","title":"Textures","description":"We\'re slowly working our way down from the high level to the low. We\'ve talked about the scene graph, and in general about display objects that live in it. We\'re about to get to sprites and other simple display objects. But before we do, we need to talk about textures.","sidebar":"guidesSidebar"},"guides/index":{"id":"guides/index","title":"Welcome","description":"PixiJS is an open source, web-based rendering system that provides blazing fast performance for games, data visualization, and other graphics intensive projects. These guides are designed to be a companion to the API documentation, providing a structured introduction to using the API to solve problems and build projects.","sidebar":"guidesSidebar"},"guides/migrations/upgrading":{"id":"guides/migrations/upgrading","title":"Upgrading PixiJS","description":"PixiJS uses a lot of peerDependencies internally to define the relationship between packages. This has created unpredictable errors because of how npm resolves peers when bumping/upgrading (e.g., #8382, #8268, #8144, #7209).","sidebar":"guidesSidebar"},"guides/migrations/v5":{"id":"guides/migrations/v5","title":"v5 Migration Guide","description":"This document is useful for developers who are attempting to upgrading from v4 to v5. This includes gotchas and important context for understanding why your v4 code made need some subtle changes. In general, we\'ve try to be as backward-compatible in v5 with the use of deprecation warnings in the console. There are, however, sometimes when changes are too substantial and require some additional help.","sidebar":"guidesSidebar"},"guides/migrations/v6":{"id":"guides/migrations/v6","title":"v6 Migration Guide","description":"PixiJS 6 comes with few surface-level breaking changes. This document is not complete.","sidebar":"guidesSidebar"},"guides/migrations/v7":{"id":"guides/migrations/v7","title":"v7 Migration Guide","description":"First and foremost, PixiJS v7 is a modernization release that reflects changes in the ecosystem since PixiJS was first published over six years ago. Browsers have gotten better, but PixiJS hasn\'t really taken advantage of some of the new features like fetch, Workers, modern JavaScript language syntax. This release keeps intact much of the high-level DisplayObjects (e.g., Sprite, Graphics, Mesh, etc). Aside from a few things, this release should be medium to low impact for most users.","sidebar":"guidesSidebar"},"guides/production/performance-tips":{"id":"guides/production/performance-tips","title":"Performance Tips","description":"General","sidebar":"guidesSidebar"},"playground/index":{"id":"playground/index","title":"index","description":""},"tutorials/getting-started":{"id":"tutorials/getting-started","title":"getting-started","description":""},"tutorials/index":{"id":"tutorials/index","title":"Tutorials","description":"Welcome to the tutorials page! Here you can find hand-crafted exercises to get you started with the PixiJS."}}}')}}]); \ No newline at end of file diff --git a/assets/js/b07297df.ad40539e.js b/assets/js/b07297df.ad40539e.js new file mode 100644 index 000000000..db431455d --- /dev/null +++ b/assets/js/b07297df.ad40539e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkpixi_docusaurus=self.webpackChunkpixi_docusaurus||[]).push([[4366],{2769:(e,t,a)=>{a.r(t),a.d(t,{assets:()=>l,contentTitle:()=>i,default:()=>h,frontMatter:()=>s,metadata:()=>n,toc:()=>u});var o=a(7462),r=(a(7294),a(3905));const s={},i="Textures",n={unversionedId:"guides/components/textures",id:"guides/components/textures",title:"Textures",description:"We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures.",source:"@site/docs/guides/components/textures.md",sourceDirName:"guides/components",slug:"/guides/components/textures",permalink:"/8.x/guides/components/textures",draft:!1,editUrl:"https://github.com/pixijs/pixijs.com/tree/main/docs/guides/components/textures.md",tags:[],version:"current",frontMatter:{},sidebar:"guidesSidebar",previous:{title:"Text",permalink:"/8.x/guides/components/text"},next:{title:"Performance Tips",permalink:"/8.x/guides/production/performance-tips"}},l={},u=[{value:"Life-cycle of a Texture",id:"life-cycle-of-a-texture",level:2},{value:"Serving the Image",id:"serving-the-image",level:3},{value:"Loading the Image",id:"loading-the-image",level:3},{value:"TextureSources Own the Data",id:"texturesources-own-the-data",level:3},{value:"Textures are a View on BaseTextures",id:"textures-are-a-view-on-basetextures",level:3},{value:"Loading Textures",id:"loading-textures",level:2},{value:"Unloading Textures",id:"unloading-textures",level:2},{value:"Beyond Images",id:"beyond-images",level:2}],d={toc:u};function h(e){let{components:t,...a}=e;return(0,r.kt)("wrapper",(0,o.Z)({},d,a,{components:t,mdxType:"MDXLayout"}),(0,r.kt)("h1",{id:"textures"},"Textures"),(0,r.kt)("p",null,"We're slowly working our way down from the high level to the low. We've talked about the scene graph, and in general about display objects that live in it. We're about to get to sprites and other simple display objects. But before we do, we need to talk about textures."),(0,r.kt)("p",null,"In PixiJS, textures are one of the core resources used by display objects. A texture, broadly speaking, represents a source of pixels to be used to fill in an area on the screen. The simplest example is a sprite - a rectangle that is completely filled with a single texture. But things can get much more complex."),(0,r.kt)("h2",{id:"life-cycle-of-a-texture"},"Life-cycle of a Texture"),(0,r.kt)("p",null,"Let's examine how textures really work, by following the path your image data travels on its way to the screen."),(0,r.kt)("p",null,"Here's the flow we're going to follow: Source Image > Loader > BaseTexture > Texture"),(0,r.kt)("h3",{id:"serving-the-image"},"Serving the Image"),(0,r.kt)("p",null,"To start with, you have the image you want to display. The first step is to make it available on your server. This may seem obvious, but if you're coming to PixiJS from other game development systems, it's worth remembering that everything has to be loaded over the network. If you're developing locally, please be aware that you ",(0,r.kt)("em",{parentName:"p"},"must")," use a webserver to test, or your images won't load due to how browsers treat local file security."),(0,r.kt)("h3",{id:"loading-the-image"},"Loading the Image"),(0,r.kt)("p",null,"To work with the image, the first step is to pull the image file from your webserver into the user's web browser. To do this, we can use ",(0,r.kt)("inlineCode",{parentName:"p"},"Assets.load('myTexture.png')"),". ",(0,r.kt)("inlineCode",{parentName:"p"},"Assets")," wraps and deals with telling the browser to fetch the image, convert it and then let you when that has been completed. This process is ",(0,r.kt)("em",{parentName:"p"},"asynchronous")," - you request the load, then time passes, then a proimise completes to let you know the load is completed. We'll go into the loader in a lot more depth in a later guide."),(0,r.kt)("pre",null,(0,r.kt)("code",{parentName:"pre",className:"language-ts"},"const texture = await Assets.load('myTexture.png');\n\n// pass a texture explicitly\nconst sprite = new Sprite(texture);\n// as options\nconst sprite2 = new Sprite({texture});\n// from the cache as the texture is loaded\nconst sprite3 = Sprite.from('myTexture.png')\n")),(0,r.kt)("h3",{id:"texturesources-own-the-data"},"TextureSources Own the Data"),(0,r.kt)("p",null,"Once the texture has loaded, the loaded ",(0,r.kt)("inlineCode",{parentName:"p"},"")," element contains the pixel data we need. But to use it to render something, PixiJS has to take that raw image file and upload it to the GPU. This brings us to the real workhorse of the texture system - the ",(0,r.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/TextureSource.html"},"TextureSource")," class. Each TextureSource manages a single pixel source - usually an image, but can also be a Canvas or Video element. TextureSources allow PixiJS to convert the image to pixels and use those pixels in rendering. In addition, it also contains settings that control how the texture data is rendered, such as the wrap mode (for UV coordinates outside the 0.0-1.0 range) and scale mode (used when scaling a texture)."),(0,r.kt)("p",null,"TextureSource are automatically cached, so that calling ",(0,r.kt)("inlineCode",{parentName:"p"},"Texture.from()")," repeatedly for the same URL returns the same TextureSource each time. Destroying a TextureSource frees the image data associated with it."),(0,r.kt)("h3",{id:"textures-are-a-view-on-basetextures"},"Textures are a View on BaseTextures"),(0,r.kt)("p",null,"So finally, we get to the ",(0,r.kt)("inlineCode",{parentName:"p"},"Texture")," class itself! At this point, you may be wondering what the ",(0,r.kt)("inlineCode",{parentName:"p"},"Texture")," object ",(0,r.kt)("em",{parentName:"p"},"does"),". After all, the BaseTexture manages the pixels and render settings. And the answer is, it doesn't do very much. Textures are light-weight views on an underlying BaseTexture. Their main attribute is the source rectangle within the TextureSource from which to pull."),(0,r.kt)("p",null,"If all PixiJS drew were sprites, that would be pretty redundant. But consider ",(0,r.kt)("a",{parentName:"p",href:"./sprite-sheets"},"SpriteSheets"),". A SpriteSheet is a single image that contains multiple sprite images arranged within. In a ",(0,r.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/assets.Spritesheet.html"},"Spritesheet")," object, a single TextureSource is referenced by a set of Textures, one for each source image in the original sprite sheet. By sharing a single TextureSource, the browser only downloads one file, and our batching renderer can blaze through drawing sprites since they all share the same underlying pixel data. The SpriteSheet's Textures pull out just the rectangle of pixels needed by each sprite."),(0,r.kt)("p",null,"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."),(0,r.kt)("h2",{id:"loading-textures"},"Loading Textures"),(0,r.kt)("p",null,"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."),(0,r.kt)("p",null,"here's a quick cheat sheet of one good solution:"),(0,r.kt)("ol",null,(0,r.kt)("li",{parentName:"ol"},"Show a loading image"),(0,r.kt)("li",{parentName:"ol"},"Use ",(0,r.kt)("a",{parentName:"li",href:"/8.x/guides/components/assets"},"Assets")," to ensure that all textures are loaded"),(0,r.kt)("li",{parentName:"ol"},"optionally update your loading image based on progress callbacks"),(0,r.kt)("li",{parentName:"ol"},"On loader completion, run all objects and use ",(0,r.kt)("inlineCode",{parentName:"li"},"Texture.from()")," to pull the loaded textures out of the texture cache"),(0,r.kt)("li",{parentName:"ol"},"Prepare your textures (optional - see below)"),(0,r.kt)("li",{parentName:"ol"},"Hide your loading image, start rendering your scene graph")),(0,r.kt)("p",null,"Using this workflow ensures that your textures are pre-loaded, to prevent pop-in, and is relatively easy to code."),(0,r.kt)("p",null,"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 ",(0,r.kt)("a",{parentName:"p",href:"https://pixijs.download/release/docs/rendering.PrepareSystem.html"},"Prepare")," plugin, which allows you to pre-load textures in a final step before displaying your project."),(0,r.kt)("h2",{id:"unloading-textures"},"Unloading Textures"),(0,r.kt)("p",null,"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 ",(0,r.kt)("inlineCode",{parentName:"p"},"destroy()")," on the BaseTexture that owns the data. Remember that Textures don't manage pixel data!"),(0,r.kt)("p",null,"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 ",(0,r.kt)("inlineCode",{parentName:"p"},"Assets")," then the assets class will automatically remove it from the cache for you."),(0,r.kt)("h2",{id:"beyond-images"},"Beyond Images"),(0,r.kt)("p",null,"As we alluded to above, you can make a Texture out of more than just images:"),(0,r.kt)("p",null,"Video: Pass an HTML5 ",(0,r.kt)("inlineCode",{parentName:"p"},"