# Assets management (/start/assets-management) To load and manipulate assets (images, gifs, videos, etc.) you will need to use `Assets`. `Assets` is a class with many features and comes from the PixiJS library. For more information, read [here](https://pixijs.com/8.x/guides/components/assets). In all Pixi’VN functions, you can directly use the image URL, even if it is not defined in `Assets`. ```ts let alien1 = await showImage("alien", "https://pixijs.com/assets/eggHead.png"); ``` This method has some drawbacks: * Changing the URL of an asset from one version to another may cause incompatibilities. * The player will have to wait for a short loading time every time they press "continue" and a `step` is started that uses assets. * Writing the entire URL in code will increase its length and make it less readable. For these reasons, it is recommended to handle assets in the following ways. ## Initialize the asset matrix at project start Initializing the asset matrix at the beginning of the project allows you to reference assets by a unique alias without having to use the URL/path. Using the alias you can change the URL of an asset without having to worry about version compatibility. To do this, it is recommended to create an asynchronous function `defineAssets` that will be called at the start of the project. In the `defineAssets` function, use `Assets` (e.g. `Assets.add`, `Assets.addBundle`, and `Assets.init`) to assign aliases to URLs. You can find more information about them [here](https://pixijs.com/8.x/guides/components/assets) to assign an alias to each asset. utils/defineAssets.ts assets/manifest.ts ```ts import { Assets, sound } from "@drincs/pixi-vn"; import manifest from "../assets/manifest"; export async function defineAssets() { // manifest await Assets.init({ manifest }); // single asset Assets.add({ alias: 'eggHead', src: "https://pixijs.com/assets/eggHead.png" }) Assets.add({ alias: 'flowerTop', src: "https://pixijs.com/assets/flowerTop.png" }) Assets.add({ alias: 'video', src: "https://pixijs.com/assets/video.mp4" }) sound.add('bird', 'https://pixijs.io/sound/examples/resources/bird.mp3'); sound.add('musical', 'https://pixijs.io/sound/examples/resources/musical.mp3'); // bundle Assets.addBundle('liam', { "liam-head": 'liam_head.png', "liam-body": 'liam_body.png', "liam-arms": 'liam_arms.png', }); } ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ // screens { name: "main_menu", assets: [ { alias: "background_main_menu", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png", }, ], }, // labels { name: "start", assets: [ { alias: "bg01-hallway", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp", }, ], }, ], }; export default manifest; ``` ## Load assets By default, assets are loaded only when needed. But **if the assets are not saved locally**, but in an "assets hosting", their loading may take some time. This means that starting a `step` may not be immediate. So, after starting the execution of the next `step` (for example with the "next" button), the player may have to wait some time to "view" the changes and be able to run another `step`. Performing these loadings at each `step` may be annoying to the player, even if they are very short. To solve this problem, the developer can group multiple loadings at a certain stage of the game. In this way, the player will not have continuous loadings, but fewer, even if longer. Here are various ways to load the assets: ### Load assets at project start It is possible to load assets at project startup before the player can interact with the project, for example during the startup loading screen. It is suggested to use this procedure only for assets used in the main page or for assets used frequently, and not to exceed 100MB. To do this, you must use the `Assets.load` function and wait for it to finish (with `await`) when the project starts. You can learn more about the `Assets.load` function [here](https://pixijs.com/8.x/guides/components/assets). utils/defineAssets.ts assets/manifest.ts ```ts import { Assets, sound } from "@drincs/pixi-vn"; import manifest from "../assets/manifest"; export async function defineAssets() { // manifest await Assets.init({ manifest }); // single asset Assets.add({ alias: 'eggHead', src: "https://pixijs.com/assets/eggHead.png" }) Assets.add({ alias: 'flowerTop', src: "https://pixijs.com/assets/flowerTop.png" }) Assets.add({ alias: 'video', src: "https://pixijs.com/assets/video.mp4" }) sound.add('bird', 'https://pixijs.io/sound/examples/resources/bird.mp3'); sound.add('musical', 'https://pixijs.io/sound/examples/resources/musical.mp3'); // bundle Assets.addBundle('liam', { "liam-head": 'liam_head.png', "liam-body": 'liam_body.png', "liam-arms": 'liam_arms.png', }); // The game will not start until these assets are loaded. // [!code focus] await Assets.load('eggHead') // [!code focus] await Assets.loadBundle('liam') // [!code focus] } ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ // screens { name: "main_menu", assets: [ { alias: "background_main_menu", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png", }, ], }, // labels { name: "start", assets: [ { alias: "bg01-hallway", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp", }, ], }, ], }; export default manifest; ``` ### Load assets in the background at project start It is possible to load assets in the background when the project starts, reducing loading times when your project starts. It is recommended not to exceed 2GB. To do this, you can use the `Assets.backgroundLoad` function when the project starts. You can learn more about the `Assets.backgroundLoad` function [here](https://pixijs.com/8.x/guides/components/assets). utils/defineAssets.ts assets/manifest.ts ```ts import { Assets, sound } from "@drincs/pixi-vn"; import manifest from "../assets/manifest"; export async function defineAssets() { // manifest await Assets.init({ manifest }); // single asset Assets.add({ alias: 'eggHead', src: "https://pixijs.com/assets/eggHead.png" }) Assets.add({ alias: 'video', src: "https://pixijs.com/assets/video.mp4" }) sound.add('bird', 'https://pixijs.io/sound/examples/resources/bird.mp3'); sound.add('musical', 'https://pixijs.io/sound/examples/resources/musical.mp3'); // bundle Assets.addBundle('liam', { "liam-head": 'liam_head.png', "liam-body": 'liam_body.png', "liam-arms": 'liam_arms.png', }); // The game will not start until these assets are loaded. await Assets.load('eggHead') // The game will start immediately, but these assets will be loaded in the background. // [!code focus] Assets.backgroundLoadBundle(['liam']); // [!code focus] // Load an individual asset in the background // [!code focus] Assets.backgroundLoad({ alias: 'flowerTop', src: 'https://pixijs.com/assets/flowerTop.png' }); // [!code focus] } ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ // screens { name: "main_menu", assets: [ { alias: "background_main_menu", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/main-menu.png", }, ], }, // labels { name: "start", assets: [ { alias: "bg01-hallway", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp", }, ], }, ], }; export default manifest; ``` ### Load assets at label start You can use this method with the *ink* syntax. See more here. To group the loadings from one `step` to another of a `label` into a single loading, it is possible to load all or part of the assets used by the `label` before it starts. Since this does not reduce the waiting times for the player, but adds them to a single loading, it is recommended to [load them in the background](#load-assets-in-the-background-at-label-start). To do this, you must use the `Assets.load` function and wait for it to finish (with `await`) when the `label` starts. You will use the `onLoadingLabel` function. For cleaner code, it is recommended to define a bundle in your manifest with the id corresponding to the `label` id and define within it the assets that will be used in that `label`. ```ts title="labels/startLabel.ts" import { newLabel, showImage, Assets } from "@drincs/pixi-vn"; const startLabel = newLabel("start", [ () => { await showImage("eggHead") }, () => { await showImage("flowerTop") }, ], { onLoadingLabel: async (_stepId, label) => { // [!code focus] // The label will not start until these assets are loaded. // [!code focus] await Assets.loadBundle(label.id) // [!code focus] } // [!code focus] }) export default startLabel; ``` assets/manifest.ts ```ts import { AssetsManifest } from "@drincs/pixi-vn"; import startLabel from "../labels/startLabel"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ // labels { name: startLabel.id, assets: [ { alias: "eggHead", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flowerTop", src: "https://pixijs.com/assets/flowerTop.png", }, ], }, ], }; export default manifest; ``` ### Load assets in the background at label start In all templates, when a `label` is started, the background loading of the bundle (if it exists) with the id corresponding to the `label` will be started. So, it is enough to **create a manifest with a bundle for each `label`**. Here is the implementation used to allow this: ```ts title="main.ts" import { Assets, Game } from "@drincs/pixi-vn"; Game.onLoadingLabel((_stepId, { id }) => Assets.backgroundLoadBundle(id)); ``` To make the game smoother by trying to remove asset loading times from one `step` to another, it is possible to start a "loading group" at the beginning of a `label`. This means that you may potentially not feel any loading, especially in the later `steps` of the `label`. To do this, you can use the `Assets.backgroundLoad` function when the `label` starts. You can learn more about the `Assets.backgroundLoad` function [here](https://pixijs.com/8.x/guides/components/assets). And, you will use the `onLoadingLabel` function. For cleaner code, it is recommended to define a bundle in your manifest with the id corresponding to the `label` id and define within it the assets that will be used in that `label`. ```ts title="labels/startLabel.ts" import { newLabel, showImage, Assets } from "@drincs/pixi-vn"; const startLabel = newLabel("start", [ () => { await showImage("eggHead") }, () => { await showImage("flowerTop") }, ], { onLoadingLabel: async (_stepId, label) => { // [!code focus] // The label will start immediately, but these assets will be loaded in the background. // [!code focus] Assets.backgroundLoadBundle(label.id) // [!code focus] } // [!code focus] }) export default startLabel; ``` assets/manifest.ts ```ts import { AssetsManifest } from "@drincs/pixi-vn"; import startLabel from "../labels/startLabel"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ // labels { name: startLabel.id, assets: [ { alias: "eggHead", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flowerTop", src: "https://pixijs.com/assets/flowerTop.png", }, ], }, ], }; export default manifest; ``` # Assets (/start/assets) **What are assets?** Assets are all files that are not code, such as images, sounds, and videos. You can use assets saved locally in the project or online (for the second option, you need to make sure that the cloud service you are using allows *CORS requests*). If your assets are online, your game will require an internet connection. You should notify the user and block the game if there is no connection. If you are creating a visual novel, it is recommended to keep frequently used assets locally. For assets used only once in the game, it is better to publish them online. To load and manipulate assets (images, gifs, videos, etc.) you will need to use `Assets`. `Assets` is a class with many features and comes from the PixiJS library. For more information, read [here](https://pixijs.com/8.x/guides/components/assets). It is also very important that you read this documentation to better organize the uploading of your assets. You mainly have two choices for where to save your assets: locally or online. ## Local assets To save and use assets locally, you can use any folder—there are no restrictions. However, it is recommended to use the `assets` folder. Inside this folder, you can create subfolders to better organize your assets. Here is an example of how to import and load an asset into your project: ```ts title="utils/assets.ts" import { Assets } from "@drincs/pixi-vn"; import bg01hallway from "../assets/images/bg01-hallway.webp"; Assets.add({ alias: "bg01-hallway", src: bg01hallway, }); ``` AssetPack is a tool for optimizing local assets for the web. It can be used to transform, combine, and compress assets. If you want to use AssetPack, you can find the documentation [here](https://pixijs.io/assetpack). ## Assets hosting You can save your assets online. This is a good option if you want to save space on your computer. You can use any cloud service that allows you to upload files and generate a public URL (CORS enabled). Here is an example of how to import and load an asset into your project: ```ts title="utils/assets.ts" import { Assets } from "@drincs/pixi-vn"; Assets.add({ alias: "bg01-hallway", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/bg01-hallway.webp", }); ``` You can save your assets as you like, with complete freedom. If you plan to save your assets online, here are some of the options: You can use Github to host your assets. You can use the raw link of the file in your project. The link will be in the following format: ```text https://raw.githubusercontent.com/[repository]/raw/refs/heads/main/[file path] ``` * **Price**: Completely free. * **Space limits**: No space limits, but each single file must not exceed 100 MB. * **Type of files**: You can upload any type of file. * **Traffic**: Speed is not the best. * **Edit assets**: You can edit the file while keeping the same URL. Image hosting is a service that allows you to upload images. There are many sites to upload images for free, for example [imgbb](https://imgbb.com/), [imgix](https://www.imgix.com/), [imgur](https://imgur.com/). You can use the link of the image in your project. * **Price**: Completely free, but you can pay for more features. * **Space limits**: No space limits, but each single file can have a maximum size. * **Type of files**: You can upload only images. * **Traffic**: Speed is good. * **Edit assets**: You can't edit the file while keeping the same URL. Cloud storage is a service that allows you to upload files online. * **Price**: Usually paid or with a free version with limits. * **Space limits**: Monthly cost based on space used (usually free if you do not exceed a certain threshold). * **Type of files**: You can upload any type of file. * **Traffic**: Speed is good. * **Edit assets**: You can edit the file while keeping the same URL. Here is a list of some of the most popular cloud storage services: * firebase-text **Firebase Storage** is a cloud service that is very easy to use. Firebase has two plans: Spark (free) and Blaze (pay as you go). You can find more information [here](https://firebase.google.com/pricing).\ **Solving Firebase Storage CORS Issue**: * Install [gcloud CLI](https://cloud.google.com/sdk/docs/install) * Read this [documentation](https://medium.com/@we.viavek/setting-cors-in-firebase-19a2cce2fe28) to solve the CORS issue. * aws-text **Amazon S3** is a cloud service. Compared to its competitors, it has many settings, but it may be more difficult to use. There is a payment plan to use Amazon S3. You can find more information [here](https://aws.amazon.com/s3/pricing/). * supabase-text **Supabase** is an open-source Firebase alternative. Supabase has two plans: Free and Pay as you go. You can find more information [here](https://supabase.io/pricing). * **Convex** is a cloud service that allows you to store and serve user-generated content, such as photos, videos, or other files. Convex has two plans: Free and Pay as you go. You can find more information [here](https://www.convex.dev/pricing). ## Other features In all templates, a service worker is included to cache external assets (images, sounds, videos, etc.) used in your game. This improves performance and reduces loading times for players. For assets hosted online, the service worker caches them locally on the user's device after the first download. This means that subsequent requests for the same asset will be served from the cache, resulting in faster load times and reduced data usage. public/service-worker.js main.ts ```js const CACHE_NAME = "external-assets-v1"; const MAX_AGE_DAYS = 7; const MAX_AGE_MS = MAX_AGE_DAYS * 24 * 60 * 60 * 1000; const lastUsed = {}; // The fetch event handler below implements a simple cache-for-host strategy: // - It only attempts to cache responses for requests whose hostname is explicitly allowed in the switch below. // - Cached entries are kept in the "external-assets-v1" cache and tracked via `lastUsed` timestamps. // - If a cached response exists and is not older than MAX_AGE_DAYS, it is served directly. // - Otherwise the service worker fetches from the network, caches the successful response, and returns it. // - If the network fails and a cached response exists, the cached response is returned as a fallback. // NOTE: Add the hostnames of external asset providers you want to cache (CDNs, raw asset hosts, etc.) in the switch below. self.addEventListener("fetch", (event) => { const request = event.request; const url = new URL(request.url); switch (url.hostname) { case "raw.githubusercontent.com": break; // Add asset hostnames here to enable caching for them. // Examples: // case "raw.githubusercontent.com": // case "cdn.jsdelivr.net": // case "your.cdn.domain.com": // break; default: return; } event.respondWith( (async () => { const cache = await caches.open(CACHE_NAME); const now = Date.now(); const cached = await cache.match(request); if (cached && lastUsed[url.href]) { const age = now - lastUsed[url.href]; if (age < MAX_AGE_MS) { lastUsed[url.href] = now; return cached; } await cache.delete(request); delete lastUsed[url.href]; } try { const response = await fetch(request); if (response && response.type === "cors" && response.status < 400) { await cache.put(request, response.clone()); lastUsed[url.href] = now; } return response; } catch (err) { if (cached) return cached; return fetch(request); } })() ); }); ``` ```ts // Register service worker if ("serviceWorker" in navigator) { window.addEventListener("load", () => { navigator.serviceWorker.register("/service-worker.js").catch(console.error); }); } ``` # Canvas component aliases (/start/canvas-alias) import { HeredityFactorExample } from "@/components/examples"; Each component added into the canvas must be assigned an alias. An alias is a way to refer to a component by a unique string. If a component is added by assigning an existing alias, the new component will replace the old one. The alias corresponds to `PixiJSComponent.label`, so do not change the `label`, but use the methods provided by Pixi’VN to change the alias. ## Heredity factor If a component is added using an existing alias, the new component, in addition to replacing the old one, will inherit the properties, the `zIndex`, and the tickers of the old component. ## Edit canvas component alias To edit the alias of a canvas component, you can use `canvas.editAlias`. If the alias has one or more tickers associated, it will be automatically updated in the ticker. The `editAlias` method has the following parameters: * `oldAlias`: The old alias of the component to edit. * `newAlias`: The new alias of the component. ```typescript import { canvas } from '@drincs/pixi-vn' canvas.editAlias('sprite1', 'sprite2') ``` ## Game layer alias In Pixi’VN, the game layer is a special component that represents the main game area where all the game elements are rendered. This component has been assigned a special alias, `const CANVAS_APP_GAME_LAYER_ALIAS = "__game_layer__"`, which is used to reference the game layer in the script. This is very useful if you want to run some animations or effects on the entire layer. Some features are not allowed on this item, such as deletion. ```typescript import { CANVAS_APP_GAME_LAYER_ALIAS, shakeEffect } from '@drincs/pixi-vn' shakeEffect(CANVAS_APP_GAME_LAYER_ALIAS) ``` # Animations and transitions (/start/canvas-animations-effects) Animations are a key part of any video game, helping to create a more engaging and immersive experience. In Pixi’VN, animations are managed using tickers and the `animate` function. The `animate` function is a wrapper around the [`motion`](https://motion.dev/) library, providing a simple yet powerful way to animate canvas components. You can define animations with properties such as duration, `easing`, and more. Pixi’VN also provides several functions to perform transitions between `steps`. All transitions are built on top of the `animate` function, making it easy to create custom transitions as well. All animations in Pixi’VN are triggered by tickers, which are classes that run on every frame and execute functions leveraging PixiJS. # Articulated animations (/start/canvas-articulated-animations-effects) import { ShakeExample } from "@/components/examples"; Articulated animations are functions that use the `canvas.animate` function to create animations that can be applied to canvas components. These functions are typically used to create effects like shaking, bouncing, or other complex animations that involve multiple steps or components. ## Shake The `shakeEffect` function is an articulated animation that shakes a component. This function has the following parameters: * `alias`: The alias to identify the component. * `options` (Optional): Animation options, matching the `options` of animate function. Additional properties: * `shocksNumber` (Optional): The number of shocks. * `shakeType` (Optional): The type of shake effect. Possible values are `vertical` and `horizontal`. * `maxShockSize` (Optional): The maximum size of the shock. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { CANVAS_APP_GAME_LAYER_ALIAS, newLabel, shakeEffect, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("bg", "bg", { scale: 1.3 }); await showImage("alien", "alien", { align: 0.5 }); shakeEffect("alien"); // [!code focus] }, async () => { shakeEffect(CANVAS_APP_GAME_LAYER_ALIAS); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "bg", src: "https://pixijs.com/assets/bg_grass.jpg", }, ], }, ], }; export default manifest; ``` # Canvas components (/start/canvas-components) Pixi’VN provides a set of canvas components. These components extend the [Pixi.js](https://pixijs.com/) components, adding various features, such as the ability to save their current state. **What are canvas components?** Components are reusable building blocks for canvas apps, allowing app makers to create custom controls to use inside an app or across apps using a component library. Components can use advanced features such as custom properties and enable complex capabilities. ## Base components The available components are: * `Sprite` corresponds to the component [`PixiJS.Sprite`](https://pixijs.com/8.x/guides/components/sprites). * `Container` corresponds to the component [`PixiJS.Container`](https://pixijs.com/8.x/guides/components/containers). * ImageSprite is a component introduced by Pixi’VN. * ImageContainer is a component introduced by Pixi’VN. * VideoSprite is a component introduced by Pixi’VN. * Text is a component introduced by Pixi’VN. ## Custom components You can create custom components by extending the base components. To do this, you need to use the decorator `@canvasComponentDecorator`. `@canvasComponentDecorator` is a decorator that saves the canvas component in memory (How to enable decorators in TypeScript?). This function has the following parameters: * `name`: The id used by Pixi’VN to refer to this class (must be unique). If you don't pass the id, the class name will be used as the id. * `getInstance`: A function that returns an instance of the canvas component. This function is used when the canvas state is reset. * `copyProperty`: A function that copies the properties of the canvas component. This function is used when copying the properties of the canvas component into another instance of the same canvas component. It is necessary to override the `memory` property to store the custom component properties. In `get memory()`, it is very important to return the `className` property; this property must be equal to the id used in the decorator. For example, you can create an `AlienTinting` class that extends the `Sprite` class to manage the direction and speed of each individual alien in an animation. ```ts title="canvas/components/AlienTinting.ts" const ALIEN_TINTING_TEST_ID = "AlienTintingTest" @canvasComponentDecorator({ name: ALIEN_TINTING_TEST_ID, }) class AlienTintingTest extends Sprite { readonly pixivnId: string = CANVAS_SPINE_ID; override get memory() { return { ...super.memory, pixivnId: CANVAS_SPINE_ID, direction: this.direction, turningSpeed: this.turningSpeed, speed: this.speed, } } override async setMemory(memory: IAlienTintingMemory) { await super.setMemory(memory) this.direction = memory.direction this.turningSpeed = memory.turningSpeed this.speed = memory.speed } direction: number = 0 turningSpeed: number = 0 speed: number = 0 static override from(source: Texture | TextureSourceLike, skipCache?: boolean) { let sprite = Sprite.from(source, skipCache) let mySprite = new AlienTintingTest() mySprite.texture = sprite.texture return mySprite } } ``` # Filters (/start/canvas-filters) Currently, this functionality is not available in Pixi’VN, but we plan to implement it. You can follow the development and show your interest in the following thread [discussion#286](https://github.com/DRincs-Productions/pixi-vn/discussions/286). Having the ability to filter the entire canvas or a specific component can be very useful in many cases. image The [PixiJS Filters](https://pixijs.io/filters/docs/) library provides this capability to PixiJS. # Components functions (/start/canvas-functions) import { AddCanvasComponents, GetCanvasComponents, RemoveCanvasComponents, RemoveAllCanvasComponents, AddListenerGivenEvent } from "@/components/examples"; ## Add To add a component to the game canvas, you can use `canvas.add`. This function has the following parameters: * `alias`: The alias to identify the component. * `component`: The component to add. labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel, Sprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const sprite = new Sprite(); const texture = await Assets.load("egg_head"); sprite.texture = texture; canvas.add("sprite", sprite); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Get To get a component from the game canvas, you can use `canvas.find`. If the component does not exist, it will return `undefined`. This function has the following parameters: * `alias`: The alias to identify the component. labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel, Sprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const sprite = new Sprite(); const texture = await Assets.load("egg_head"); sprite.texture = texture; canvas.add("sprite", sprite); }, () => { const sprite = canvas.find("sprite"); // [!code focus] if (sprite) { sprite.x = 100; sprite.y = 100; } }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Remove To remove a component from the game canvas, you can use `canvas.remove`. This function has the following parameters: * `alias`: The alias to identify the component. labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel, Sprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const sprite = new Sprite(); const texture = await Assets.load("egg_head"); sprite.texture = texture; canvas.add("sprite", sprite); }, () => { canvas.remove("sprite"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Remove all To remove all components from the game canvas, you can use `canvas.removeAll`. labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel, Sprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const texture = await Assets.load("egg_head"); for (let i = 0; i < 3; i++) { const sprite = new Sprite(); sprite.texture = texture; sprite.x = i * 150; canvas.add(`sprite${i}`, sprite); } }, () => { canvas.removeAll(); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Add a listener to an event If possible, try using HTML and PixiJS UI to add buttons or other elements with events. In Pixi’VN, compared to PixiJS, you can use a [listener with the `on` method](https://pixijs.com/8.x/examples/events/click), but if you don't use the `@eventDecorator` decorator, the event will not be registered in the Pixi’VN event system, so if you load a save where that event was used, it will be lost. `@eventDecorator` is a decorator that saves the event in memory (How to enable decorators in TypeScript?). This function has the following parameters: * `name`: The id used by Pixi’VN to refer to this function (must be unique). If you don't pass the id, the function name will be used as the id. canvas/events.ts labels/startLabel.ts assets/manifest.ts ```ts import { eventDecorator, FederatedEvent, Sprite } from "@drincs/pixi-vn"; export default class Events { @eventDecorator() static buttonEvent(event: FederatedEvent, sprite: Sprite): void { switch (event.type) { case "pointerdown": sprite.scale.x *= 1.25; sprite.scale.y *= 1.25; break; } } } ``` ```ts import { newLabel, showImage } from "@drincs/pixi-vn"; import Events from "../canvas/events"; export const startLabel = newLabel("start_label", [ async () => { const bunny = await showImage("bunny", "bunny", { align: 0.5, anchor: 0.5, }); // Opt-in to interactivity bunny.eventMode = "static"; // Shows hand cursor bunny.cursor = "pointer"; // Pointers normalize touch and mouse (good for mobile and desktop) bunny.on("pointerdown", Events.buttonEvent); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [{ alias: "bunny", src: "https://pixijs.com/assets/bunny.png" }], }, ], }; export default manifest; ``` TODO ### access to PIXI.Application # ImageContainer (/start/canvas-image-container) import { ShowImageContainerExample, AddImageContainerExample } from "@/components/examples"; The `ImageContainer` component extends the [`PixiJS.Container`](https://pixijs.com/8.x/guides/components/containers) component, so you can use all the methods and properties of `PixiJS.Container`. It allows you to group multiple images into a single component and manipulate them as one. The children of the `ImageContainer` are `ImageSprite` components. To initialize this component, you must pass the following parameters: * `options` (Optional): The options for the component. * `imageUrls` (Optional): An array of image URLs or paths. If you have initialized the asset matrix, you can use the alias of the texture. ```ts import { canvas, ImageContainer } from "@drincs/pixi-vn" let james = new ImageContainer({ anchor: { x: 0.5, y: 0.5 }, x: 100, y: 100, }, [ 'https://image.com/body.webp', 'https://image.com/head.webp', 'https://image.com/eyes.webp', ]) await james.load() canvas.add("james", james) ``` Compared to the `Container` component, `ImageContainer` adds the following features: * `load()`: Loads all image URLs and sets the resulting textures in the children. * Additional positioning: align and position with percentage. ## Show The simplest way to show a group of images on the canvas is to use the `showImageContainer` function. This function combines `load` and `canvas.add`. This function returns an `ImageContainer` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `imageUrls` (Optional): An array of image URLs or paths. If you have initialized the asset matrix, you can use the alias of the texture. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { canvas, ImageContainer, newLabel, showImageContainer } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { let james = await showImageContainer("james", ["m01-body", "m01-eyes", "m01-mouth"], { // [!code focus] xAlign: 0.5, // [!code focus] yAlign: 1, // [!code focus] }); // [!code focus] }, () => { canvas.removeAllTickers(); let tickerId = canvas.animate("james", { xAlign: 0, yAlign: 1 }); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` ## Add To add a group of images to the canvas, use the `addImageCointainer` function. This function only adds the component to the canvas; it does **not** show it or load its texture. It uses `canvas.add` to add the component to the canvas. This function returns an `ImageContainer` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `imageUrls` (Optional): An array of image URLs or paths. If you have initialized the asset matrix, you can use the alias of the texture. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { addImageCointainer, canvas, ImageContainer, newLabel } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ () => { let james = await addImageCointainer("james", ["m01-body", "m01-eyes", "m01-mouth"], { // [!code focus] xAlign: 0.5, // [!code focus] yAlign: 1, // [!code focus] }); // [!code focus] }, async () => { let james = canvas.find("james"); james && (await james.load()); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` ## Remove As with other canvas components, you can remove this component using the `canvas.remove` function. # ImageSprite (/start/canvas-image) import { ImageSpriteAdd, ImageSpriteShow } from "@/components/examples"; The `ImageSprite` component extends the [`PixiJS.Sprite`](https://pixijs.com/8.x/guides/components/sprites) component, so you can use all the methods and properties of `PixiJS.Sprite`. It is used to display a single image on the canvas. To initialize this component, you must pass the following parameters: * `options` (Optional): The options for the component. * `imageUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. ```ts import { canvas, ImageSprite } from "@drincs/pixi-vn" let alien = new ImageSprite({ anchor: { x: 0.5, y: 0.5 }, x: 100, y: 100, }, 'https://pixijs.com/assets/eggHead.png') await alien.load() canvas.add("alien", alien) ``` Compared to the `Sprite` component, `ImageSprite` adds the following features: * `load()`: Loads the image URL and sets the resulting texture to the component. * Additional positioning: align and position with percentage. ## Show The simplest way to show an image on the canvas is to use the `showImage` function. This function combines `load` and `canvas.add`. This function returns an `ImageSprite` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `imageUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. If you don't provide the URL, then the alias is used as the URL. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { let alien1 = await showImage("alien"); // [!code focus] let alien2 = await showImage("alien2", "alien", { // [!code focus] xAlign: 0.5, // [!code focus] }); // [!code focus] let alien3 = await showImage("alien3", "https://pixijs.com/assets/eggHead.png", { // [!code focus] xAlign: 1, // [!code focus] }); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Add To add an image to the canvas, use the `addImage` function. This function only adds the component to the canvas; it does **not** show it or load its texture. It uses `canvas.add` to add the component to the canvas. This function returns an `ImageSprite` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `imageUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. If you don't provide the URL, then the alias is used as the URL. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { addImage, canvas, ImageSprite, newLabel } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ () => { let alien1 = addImage("alien"); // [!code focus] let alien2 = addImage("alien2", "alien", { // [!code focus] xAlign: 0.5, // [!code focus] }); // [!code focus] let alien3 = addImage("alien3", "https://pixijs.com/assets/eggHead.png", { // [!code focus] xAlign: 1, // [!code focus] }); // [!code focus] }, async () => { let alien1 = canvas.find("alien"); let alien2 = canvas.find("alien2"); let alien3 = canvas.find("alien3"); // Load the textures alien1 && (await alien1.load()); alien2 && (await alien2.load()); alien3 && (await alien3.load()); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Remove As with other canvas components, you can remove this component using the `canvas.remove` function. # Lights (/start/canvas-lights) Currently, this functionality is not available in Pixi’VN, but we plan to implement it. You can follow the development and show your interest in the following thread [discussion#283](https://github.com/DRincs-Productions/pixi-vn/discussions/283). Having the ability to create light and shadow effects can be very useful in many cases. 357854550-af5a80c0-6996-4b74-a8b8-36b63c8b825c The [PixiJS Lights](https://userland.pixijs.io/lights/docs/index.html) library provides this capability to PixiJS. # Animate (motion) (/start/canvas-motion) import { MoveExample, RotateExample, FadeExample, ZoomExample, MirrorExample, SequenceExample, MotionSequenceExample } from "@/components/examples"; Pixi’VN allows developers to animate canvas components using a function called `animate`. This function is a re-implementation of the [`animate` function from the `motion` library](https://motion.dev/docs/animate), adapted to use PixiJS tickers for triggering animation events. **What is `motion`?**\ `motion` is a popular JavaScript library that provides a simple and powerful way to create animations. You can read more about it [here](https://motion.dev/). There are two variants of this function: * `animate`: Intended for animating PixiJS or Pixi’VN elements used for UI. Pixi’VN does not save the current state of animations created with this function. Since it is identical to [motion’s animate function](https://motion.dev/docs/animate), it will not be explained further here. * `canvas.animate`: Designed for animating Pixi’VN canvas components. It saves the current state of animations, allowing you to restore the state of an animation from a save. This function has some differences from the original `animate` function, which will be explained in detail below. ## Use The `canvas.animate` function works as follows: The developer defines a component state to be reached (for example, its x-y position). Once the animation starts, an event is continuously triggered to update the component until it reaches the desired state. You can further customize the animation with various options. This function has the following parameters: * `components`: The PixiJS component(s) to animate. This can be a single component, an array of components, or a string representing the component's alias. * `keyframes`: This is an object containing the properties to animate and the values to reach. * `options` (Optional): [`motion` options](https://motion.dev/docs/animate#options) for the animation, including duration, `easing`, and ticker. The following options extend those from `motion`: * `aliasToRemoveAfter` (Optional): An array of strings containing the aliases of the canvas components to remove after the animation completes. * `tickerIdToResume` (Optional): A string containing the ticker ID to resume after the animation completes. * `tickerAliasToResume` (Optional): If you want to resume tickers that were previously paused, provide the aliases of the canvas components whose tickers should be resumed. * `completeOnContinue` (Optional): A boolean indicating whether the animation must complete before the next `step` of the game. If `true`, the game will force the animation to finish before proceeding. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The function returns the ID of the ticker created to animate the component(s). Here are some examples: labels/startLabel.ts assets/manifest.ts ```ts import { canvas, ImageSprite, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien"); // [!code focus] canvas.animate(alien, { xAlign: 1, yAlign: 0 }, { ease: "easeOut" }); // [!code focus] }, () => canvas.animate("alien", { xAlign: 1, yAlign: 1 }, { ease: "backOut" }), // [!code focus] () => canvas.animate("alien", { xAlign: 0, yAlign: 1 }, { ease: "circIn" }), // [!code focus] () => canvas.animate("alien", { xAlign: 0, yAlign: 0 }, { ease: "linear" }), // [!code focus] ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien", "alien", { align: 0.5, anchor: 0.5 }); // [!code focus] canvas.animate(alien, { angle: 360 }, { duration: 1, type: "spring", repeat: Infinity, repeatDelay: 0.2 }); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien", "alien", { align: 0.5, anchor: 0.5, alpha: 0 }); // [!code focus] canvas.animate(alien, { alpha: 1 }, { ease: "linear", duration: 1 }); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien", "alien", { align: 0.5, anchor: 0.5, scale: 0 }); // [!code focus] canvas.animate(alien, { scaleX: 1, scaleY: 1 }, { ease: "circInOut", duration: 1 }); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien", "alien", { align: 0.5, anchor: 0.5 }); // [!code focus] canvas.animate(alien, { scaleX: -1 }); // [!code focus] }, () => canvas.animate("alien", { scaleX: 1 }), // [!code focus] ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Sequence The `canvas.animate` function can also be used to create sequences of animations. To create a sequence, you can pass arrays as properties values in the keyframes object. In this case, you can use the [`times` property](https://motion.dev/docs/animate#times) to specify the timing of each keyframe. For example: labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien"); // [!code focus] canvas.animate( // [!code focus] alien, // [!code focus] { // [!code focus] xAlign: [0, 1, 1, 0, 0], // [!code focus] yAlign: [0, 0, 1, 1, 0], // [!code focus] }, // [!code focus] { repeat: Infinity, duration: 10 } // [!code focus] ); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ### Timeline sequences This method has some limitations compared to the previous one, such as restrictions on the `repeat` property due to the original \`motion' library. Another way to create animation sequences with `canvas.animate` is by using a [timeline](https://motion.dev/docs/animate#timeline-sequences). This is useful when you want to chain multiple animations that are not strictly linear. You can provide an array of keyframes, where each keyframe is an object with the properties to animate and their values, along with optional animation options. For example: labels/startLabel.ts assets/manifest.ts ```ts import { canvas, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { const alien = await showImage("alien"); // [!code focus] canvas.animate( // [!code focus] alien, // [!code focus] [ // [!code focus] [{ xAlign: 0, yAlign: 0 }, { ease: "circInOut" }], // [!code focus] [{ xAlign: 1, yAlign: 0 }, { ease: "backInOut" }], // [!code focus] [{ xAlign: 1, yAlign: 1 }, { ease: "linear" }], // [!code focus] [{ xAlign: 0, yAlign: 1 }, { ease: "anticipate" }], // [!code focus] [{ xAlign: 0, yAlign: 0 }, { ease: "easeOut" }], // [!code focus] ], // [!code focus] { repeat: 10, duration: 10 } // [!code focus] ); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "alien", src: "https://pixijs.com/assets/eggHead.png", }, ], }, ], }; export default manifest; ``` ## Methods The `canvas.animate` function is built on Pixi’VN Tickers, so you can use all the functions available to control Pixi’VN Tickers. # Properties for positioning (/start/canvas-position) import { PositionwithpercentageExample, AlignExample } from "@/components/examples"; Most of the texts and images on this page were copied from Position Properties – [Pos and Anchor](https://feniksdev.com/renpy-position-properties-pos-and-anchor/) and [align, xycenter, and offset](https://feniksdev.com/renpy-position-properties-align-xycenter-and-offset/). Feniks in these two pages explained very well the properties of Ren'Py positioning, common to many other canvases including Pixi’VN. Before we get into the different positioning properties, note that Pixi’VN considers the default for all position properties to be `{ x: 0, y: 0 }`, which corresponds to the top-left of the element you’re positioning. Positive numbers move the element to the right and down. So, something at a position of `{ x: 200, y: 300 }` in the game will be 200 pixels from the left edge of the screen and 300 pixels from the top edge of the screen. Negative numbers move the element left and up relative to their starting position. [Position](#position-pixel) and [anchor](#anchor-and-pivot) are the main properties you use to move elements around on the screen. It’s important to understand how they work, because most other positioning properties act as some combination of these two. ## Position (pixel) Position is used to position the component using pixel units. You can modify it with these properties: * `x`: moves the component left-to-right (along the x-axis) * `y`: moves the component top-to-bottom (along the y-axis) * `position`: an object `{ x: number, y: number }`. You can also set both x and y to the same value, e.g. `component.position = 200`. ## Anchor and pivot The pivot is an offset, expressed in pixels, from the top-left corner of the component. If you have a component 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. Anchors are specified in percentages, from 0.0 to 1.0, in each dimension. It has the same utility as the pivot, but to deduce the point where it is located it calculates the percentage of the height and width of the texture. For example, to rotate around the center point of a texture using anchors, you'd set your component's anchor to (0.5, 0.5) - 50% in width and height. Anchors compared to Pivot are easier to use. You can modify it with these properties: * `anchor`: an object `{ x: number, y: number }`. You can also set both x and y to the same value, e.g. `conponent.anchor = 0.5`. * `pivot`: is an object that corresponds to `{ x: number, y: number }`. *** **What, exactly, is anchor/pivot?** Let’s think of it in terms of something you may be more familiar with. Instead of positioning an element on a screen, you are trying to pin a photo onto a cork board. You have three things: * a cork board * a push pin * a photograph Let’s pretend that 1mm is equal to 1 pixel on a computer screen. 17351596389764883495402859713640 * The cork board is the screen, or the container you’re trying to position the element inside. * The photograph is the element. * Where you put the pin on the photo is the anchor/pivot of the photograph. * Where you push the pin into on the cork board is the position of the photograph. By default in Pixi’VN, the push pin always starts in the top left corner of the photo, so to speak. If you want the top-left corner of the photo 200mm from the left side of the cork board, you will put it at x 200. If you also want the top-left corner 300mm down from the top of the board, you will put it at y 300. 17351597056618553068745888144175 What if you want the center of the photo at 200mm x 300mm? This means you need to move where the pin is relative to the photo. The pin will stay at the point (200, 300) on the cork board – you just need to center the photo around that point as well. This means you need to change the anchor/pivot of the photo. To set the anchor point of the photo to the center of the photo, you can use anchor (0.5, 0.5) or pivot (100, 150) ## Position with percentage Pixi’VN introduces the ability to position a component by percentage. Its operation is very similar to that of html. In practice, the percentage will be multiplied by the height or width of the parent component to calculate the position in pixels. You can modify it with these properties: * `percentageX`: for moving things left-to-right (along the x-axis) * `percentageY`: for moving things top-to-bottom (along the y-axis). * `percentagePosition`: an object `{ x: number, y: number }`. You can also set both x and y to the same value, e.g. `conponent.align = 0.5`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { percentagePosition: 0.5, anchor: 0.5, }); await showImage("flower_top", "flower_top", { percentagePosition: 0, }); await showImage("panda", "panda", { percentageX: 1, percentageY: 0, anchor: { x: 1, y: 0 }, }); await showImage("skully", "skully", { percentageX: 0, percentageY: 1, anchor: { x: 0, y: 1 }, }); await showImage("helmlok", "helmlok", { percentagePosition: 1, anchor: 1, }); await showImage("bunny", "bunny", { percentageX: 0.5, percentageY: 1, anchor: { x: 0.5, y: 1 }, }); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png" }, { alias: "panda", src: "https://pixijs.com/assets/panda.png" }, { alias: "skully", src: "https://pixijs.com/assets/skully.png" }, { alias: "helmlok", src: "https://pixijs.com/assets/helmlok.png" }, { alias: "bunny", src: "https://pixijs.com/assets/bunny.png" }, ], }, ], }; export default manifest; ``` ## Align Until now we have seen positioning methods influenced by [anchor/pivot](#anchor-and-pivot). The disadvantage of these methods is that if for example you want to add your component to the center of the screen you will first have to set the anchor to 0.5 and then set the position to half the width and height of the screen. This is where the align property comes in. Align is a feature originally created for ***Ren'Py***, which was also introduced in Pixi’VN. Align combines [position](#position-pixel) and [anchor/pivot](#anchor-and-pivot) to give you a more intuitive way to position your components at the beginning, in the center or in the end of the screen. Align are specified in percentages, from 0.0 to 1.0, in each dimension. For example if you use 0.25 as a percentage, your component will be positioned at 25% of the screen with anchor at 0.25. The calculation that is used to calculate the location is the following: ```ts myComponent.x = (align * (fatherComponent.width - myComponent.width)) + myComponent.pivot + (myComponent.anchor * myComponent.width) myComponent.y = (align * (fatherComponent.height - myComponent.height)) + myComponent.pivot + (myComponent.anchor * myComponent.height) ``` You can modify it with these properties: * `xAlign`: for moving things left-to-right (along the x-axis) * `yAlign`: for moving things top-to-bottom (along the y-axis). * `align`: an object `{ x: number, y: number }`. You can also set both x and y to the same value, e.g. `conponent.align = 0.5`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { align: 0.5 }); await showImage("flower_top", "flower_top", { align: 0 }); await showImage("panda", "panda", { xAlign: 1, yAlign: 0 }); await showImage("skully", "skully", { xAlign: 0, yAlign: 1 }); await showImage("helmlok", "helmlok", { align: 1 }); await showImage("bunny", "bunny", { xAlign: 0.5, yAlign: 1 }); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png" }, { alias: "panda", src: "https://pixijs.com/assets/panda.png" }, { alias: "skully", src: "https://pixijs.com/assets/skully.png" }, { alias: "helmlok", src: "https://pixijs.com/assets/helmlok.png" }, { alias: "bunny", src: "https://pixijs.com/assets/bunny.png" }, ], }, ], }; export default manifest; ``` # Spine 2D (/start/canvas-spine2d) import { AnimationExample, MotionExample, AnimationSequenceExample } from "@/components/spine-examples"; This library is currently in **testing phase**, so it may change significantly between versions. **What is Spine 2D?** Spine 2D is a powerful 2D animation software specifically designed for game development. It uses a skeletal animation system, meaning that characters and objects are animated through a hierarchy of **bones** that control the movement of attached parts. You can learn more about Spine 2D on the [official Spine 2D website](https://it.esotericsoftware.com/). Within your **Pixi’VN** project, you can use the Spine 2D integration to create complex and smooth animations for your characters and objects.\ This integration is essentially a wrapper around the official [Spine 2D runtime for PixiJS](https://it.esotericsoftware.com/spine-pixi), allowing you to use all Spine 2D features directly inside your Pixi’VN project. ## Why? By using the Spine 2D integration in Pixi’VN, you can easily add animated components while taking advantage of both Pixi’VN and Spine 2D features, such as: * Saving the current animation state within game save files. * Using the additional Pixi’VN positioning properties. * Starting animation sequences quickly and easily. * Accessing additional features shared with other Pixi’VN components. ## Installation To install the Spine 2D package in an existing JavaScript project, use one of the following commands: npm yarn pnpm bun deno ```sh npm install @drincs/pixi-vn-spine ``` ```sh yarn add @drincs/pixi-vn-spine ``` ```sh pnpm add @drincs/pixi-vn-spine ``` ```sh bun add @drincs/pixi-vn-spine ``` ```sh deno install npm:@drincs/pixi-vn-spine ``` ### Initialize Due to compatibility issues with Lazy components, the library must be imported when the game is initialized, so that it can be used inside Lazy components. ```ts title="main.ts" import "@drincs/pixi-vn-spine"; // [!code focus] Game.init(body, { // ... }) ``` ## Usage You can use the `Spine` component just like any other Pixi’VN component.\ However, you must first load the Spine 2D assets (**skeleton** and **atlas**) before using it. This component has two required properties: `skeleton` and `atlas`, which must match the names of the loaded assets. For example: main.ts assets/manifest.ts ```ts import { Assets, canvas } from "@drincs/pixi-vn"; import { Spine } from "@drincs/pixi-vn-spine"; await Assets.load(["spineboySkeleton", "spineboyAtlas"]); const spine = new Spine({ atlas: "spineboyAtlas", skeleton: "spineboySkeleton" }); canvas.add("spine", spine); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "spineboySkeleton", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel", }, { alias: "spineboyAtlas", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas", }, ], }, ], }; export default manifest; ``` ### Animation labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel } from "@drincs/pixi-vn"; import { Spine } from "@drincs/pixi-vn-spine"; export const startLabel = newLabel("start", [ async () => { await Assets.load(["spineboySkeleton", "spineboyAtlas"]); const spine = new Spine({ atlas: "spineboyAtlas", skeleton: "spineboySkeleton", xAlign: 0.5, yAlign: 1 }); spine.addAnimation("idle", { loop: true }); canvas.add("spine", spine); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "spineboySkeleton", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel", }, { alias: "spineboyAtlas", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas", }, ], }, ], }; export default manifest; ``` ### Spine + motion.js animations labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel } from "@drincs/pixi-vn"; import { Spine } from "@drincs/pixi-vn-spine"; export const startLabel = newLabel("start", [ async () => { await Assets.load(["spineboySkeleton", "spineboyAtlas"]); const spine = new Spine({ atlas: "spineboyAtlas", skeleton: "spineboySkeleton", xAlign: 0, yAlign: 1 }); spine.addAnimation("walk", { loop: true }); canvas.add("spine", spine); canvas.animate( spine, [ [{ xAlign: 1 }, { duration: 1, ease: "linear" }], [{ scaleX: -1 }, { duration: 0.2 }], [{ xAlign: 0 }, { duration: 1, ease: "linear" }], [{ scaleX: 1 }, { duration: 0.2 }], ], { repeat: Infinity }, ); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "spineboySkeleton", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel", }, { alias: "spineboyAtlas", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas", }, ], }, ], }; export default manifest; ``` ### Animations sequence labels/startLabel.ts assets/manifest.ts ```ts import { Assets, canvas, newLabel } from "@drincs/pixi-vn"; import { Spine } from "@drincs/pixi-vn-spine"; export const startLabel = newLabel("start", [ async () => { await Assets.load(["spineboySkeleton", "spineboyAtlas"]); const spine = new Spine({ atlas: "spineboyAtlas", skeleton: "spineboySkeleton", xAlign: 0.5, yAlign: 1 }); spine.playSequence([ ["idle", { loop: true, duration: 2 }], "jump", ], { repeat: Infinity, }); canvas.add("spine", spine); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "spineboySkeleton", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pro.skel", }, { alias: "spineboyAtlas", src: "https://raw.githubusercontent.com/pixijs/spine-v8/main/examples/assets/spineboy-pma.atlas", }, ], }, ], }; export default manifest; ``` # Text (/start/canvas-text) import { TextCanvas, TextCanvasStyle } from "@/components/examples"; The `Text` component extends the [`PixiJS.Text`](https://pixijs.com/8.x/guides/components/scene-objects/text) component, so you can use all the methods and properties of `PixiJS.Text`. It is used to display text on the canvas. To initialize this component, you must pass the following parameters: * `options`: The options for the component, the `text` property is required. ```ts import { canvas, Text } from "@drincs/pixi-vn"; const basicText = new Text({ text: "Basic text in pixi", align: 0.5 }); canvas.add("text", basicText); ``` Compared to the `PixiJS.Text` component, `Text` adds the following features: * Additional positioning: align and position with percentage. ## Show The simplest way to show text on the canvas is to use the `showText` function. This function returns a `Text` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `text`: The text to display. * `options` (Optional): The options for the component. labels/startLabel.ts ```ts import { newLabel, showText } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { let text = await showText("text", "Hello World!", { // [!code focus] xAlign: 0.5, // [!code focus] yAlign: 0.5, // [!code focus] }); // [!code focus] text.style.fontSize = 30; // [!code focus] }, ]); ``` ## Remove As with other canvas components, you can remove this component using the `canvas.remove` function. ## Style To style the text, use `TextStyle`. This class allows you to customize font family, size, color, stroke, shadow, and more. labels/startLabel.ts ```ts import { canvas, newLabel, Text, TextStyle } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ () => { const skewStyle = new TextStyle({ // [!code focus] fontFamily: "Arial", // [!code focus] dropShadow: { // [!code focus] alpha: 0.8, // [!code focus] angle: 2.1, // [!code focus] blur: 4, // [!code focus] color: "0x111111", // [!code focus] distance: 10, // [!code focus] }, // [!code focus] fill: "#ffffff", // [!code focus] stroke: { color: "#004620", width: 12, join: "round" }, // [!code focus] fontSize: 60, // [!code focus] fontWeight: "lighter", // [!code focus] }); // [!code focus] const skewText = new Text({ // [!code focus] text: "SKEW IS COOL", // [!code focus] style: skewStyle, // [!code focus] align: 0.5, // [!code focus] skew: { x: 0.65, y: -0.3 }, // [!code focus] }); // [!code focus] canvas.add("text", skewText); // [!code focus] }, ]); ``` # Three.js (/start/canvas-threejs) Currently, this functionality is not available in Pixi’VN, but we plan to implement it. You can follow the development and show your interest in the following thread [discussion#347](https://github.com/DRincs-Productions/pixi-vn/discussions/347). **What is Three.js?** Three.js is a JavaScript library that makes it easy to render 3D graphics in a web browser. It uses WebGL to render 3D graphics in the browser. Three.js is a popular choice for creating 3D graphics on the web. You can learn more about Three.js on the [Three.js website](https://threejs.org/). Having the ability interact with 3D elements can be very useful in many cases. The [three-pixi](https://pixijs.com/8.x/guides/advanced/mixing-three-and-pixi#example-combining-3d-and-2d-elements) library provides this capability to PixiJS. # Tickers functions (/start/canvas-tickers-functions) To play, pause, or stop a ticker, you must use the functions of the `canvas`. It is important to keep the following behaviors in mind: * If a ticker does not have any canvas components associated with it, it will be deleted. * If you remove a canvas component, your alias will be unlinked from the ticker. * If you add a canvas component with an alias that already exists, the new component will replace the old one. The new component will inherit the tickers of the old component. ## Find a ticker To find a ticker, you must use the `canvas.findTicker` function. This function receives the following parameters: * `tickerId`: The id of the ticker to be found. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { yAlign: 0.5, xAlign: 0.25, anchor: 0.5 }); await showImage("flower_top", "flower_top", { yAlign: 0.5, xAlign: 0.75, anchor: 0.5 }); let tikerId = canvas.addTicker(["egg_head", "flower_top"], new RotateTicker({})); let ticker = canvas.findTicker(tikerId); // [!code focus] console.log(ticker); }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); Assets.add({ alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png" }); } ``` ## Remove a ticker To remove a ticker, you must use the `canvas.removeTicker` function. This function receives the following parameters: * `tikerId`: The id or an array of ids of the ticker to be removed. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, RotateTicker, showImage, storage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { yAlign: 0.5, xAlign: 0.25, anchor: 0.5 }); await showImage("flower_top", "flower_top", { yAlign: 0.5, xAlign: 0.75, anchor: 0.5 }); let tikerId = canvas.addTicker(["egg_head", "flower_top"], new RotateTicker({})); storage.set("tiker_id", tikerId); }, () => { let tikerId = storage.get("tiker_id"); tikerId && canvas.removeTicker(tikerId); // [!code focus] }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); Assets.add({ alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png" }); } ``` ## Pause and resume a ticker To pause a ticker, you must use the `canvas.pauseTicker` function. This function receives the following parameters: * `alias`: The alias of the canvas element that will use the ticker. * `tickerIdsExcluded`: The tickers that will not be paused. To resume a paused ticker, you must use the `canvas.resumeTicker` function. This function receives the following parameters: * `alias`: The alias of the canvas element that will use the ticker. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, narration, newLabel, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { align: 0.5, anchor: 0.5 }); let tikerId = canvas.addTicker(["egg_head"], new RotateTicker({})); narration.dialogue = "start"; }, () => { canvas.pauseTicker("egg_head"); // [!code focus] narration.dialogue = "pause"; }, () => { canvas.resumeTicker("egg_head"); // [!code focus] narration.dialogue = "resume"; }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); } ``` ## Force completion of the transition at the end of the step When the animation has a goal to reach, such as a destination, we sometimes need the animation to reach the goal before the current `step` ends. To do this, you can use the `canvas.completeTickerOnStepEnd` function. This function receives the following parameters: * `step`: The `step` that the ticker must be completed before the next `step`. It receives an object with the following properties: * `id`: The id of the `step`. * `alias`: If it is a sequence of tickers, the alias of the [sequence of tickers](#sequence-of-tickers). labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, narration, newLabel, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { yAlign: 0, xAlign: 0, anchor: 0 }); let tikerId = canvas.addTicker(["egg_head"], new MoveTicker({ destination: { x: 1, y: 0, type: "align" }, speed: 1, }) ); tikerId && canvas.completeTickerOnStepEnd({ id: tikerId }); // [!code focus] }, () => { narration.dialogue = "complete"; }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); } ``` # Ticker (/start/canvas-tickers) Pixi’VN allows you to animate canvas components using tickers. **What is a ticker?**\ A ticker is a class that runs on every frame and executes a function. Tickers can be used to animate components, perform transitions, or run any logic that needs to update regularly. Compared to `PixiJS.tickers`, Pixi’VN tickers are classes with a `fn` method that is called every frame. This method is used to animate canvas components. Pixi’VN manages all running tickers, detects when they are no longer needed, and lets you pause, resume, or delete them using various methods. ## Create your own ticker Creating your own ticker is simple: extend the TickerBase class, override the `fn` method, and implement your logic. Then, decorate the class with the `@tickerDecorator` decorator. The decorator can take a string as the ticker's alias; if not provided, the class name is used. For example: ```typescript title="canvas/tickers/RotateTicker.ts" import { canvas, Container, TickerBase, tickerDecorator, TickerValue } from "@drincs/pixi-vn"; @tickerDecorator() // or @tickerDecorator('RotateTicker') export default class RotateTicker extends TickerBase<{ speed?: number; clockwise?: boolean }> { fn( t: TickerValue, args: { speed?: number; clockwise?: boolean; }, aliases: string[] ): void { let speed = args.speed === undefined ? 0.1 : args.speed; let clockwise = args.clockwise === undefined ? true : args.clockwise; aliases.forEach((alias) => { let component = canvas.find(alias); if (component && component instanceof Container) { if (clockwise) component.rotation += speed * t.deltaTime; else component.rotation -= speed * t.deltaTime; } }); } } ``` ## Run a ticker To add a ticker you must use the `canvas.addTicker` function. This function receives the following parameters: * `canvasElementAlias`: The alias of the canvas element that will use the ticker. You can pass a string or an array of strings. If you pass an array of strings, the ticker will be associated with all canvas components. * `ticker`: The ticker instance to be run. The function returns the id of the ticker that was added. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { yAlign: 0.5, xAlign: 0.25, anchor: 0.5 }); await showImage("flower_top", "flower_top", { yAlign: 0.5, xAlign: 0.75, anchor: 0.5 }); let tikerId = canvas.addTicker(["egg_head", "flower_top"], new RotateTicker({})); // [!code focus] }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); Assets.add({ alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png" }); } ``` ## Sequence of tickers If you want to run a sequence of tickers, you can use the `canvas.addTickersSequence` function. This function receives the following parameters: * `canvasElementAlias`: The alias of the canvas element that will use the ticker. Please note that a component alias can only have one sequence sequence of tickers to it. If you add a new sequence of tickers to the same alias, the new sequence will replace the old one. * `tickers`: An array of tickers to be run in sequence. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { anchor: 0.5 }); let tikerId = canvas.addTickersSequence("egg_head", [ // [!code focus] new MoveTicker({ // [!code focus] destination: { x: 0.5, y: 0.5, type: "align" }, // [!code focus] }), // [!code focus] new RotateTicker({ speed: 2, clockwise: false }, 2), // [!code focus] ]); // [!code focus] }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); } ``` ### Pause If you want to pause the steps for a while, you can use the `Pause` token. The `Pause` token receives the time in seconds to pause. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, Pause, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { anchor: 0.5, align: 0.5 }); let tikerId = canvas.addTickersSequence("egg_head", [ // [!code focus] new RotateTicker({ speed: 1, clockwise: true }, 2), // [!code focus] Pause(1), // [!code focus] new RotateTicker({ speed: 1, clockwise: false }, 2), // [!code focus] ]); // [!code focus] }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); } ``` ### Repeat If you want to repeat the steps, you can use the `Repeat` token. labels/startLabel.ts utils/defineAssets.ts ```ts import { canvas, newLabel, Repeat, RotateTicker, showImage } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showImage("egg_head", "egg_head", { anchor: 0.5, align: 0.5, }); let tikerId = canvas.addTickersSequence("egg_head", [ // [!code focus] new RotateTicker({ speed: 1, clockwise: true }, 2), // [!code focus] new RotateTicker({ speed: 2, clockwise: false }, 2), // [!code focus] Repeat, // [!code focus] ]); // [!code focus] }, ]); ``` ```ts import { Assets } from "@drincs/pixi-vn"; export async function defineAssets() { Assets.add({ alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png" }); } ``` # Transitions (/start/canvas-transition) import { DissolveTransitionExample, FadeTransitionExample, MoveinTransitionExample, PushinTransitionExample, ZoominTransitionExample } from "@/components/examples"; Pixi’VN provides various transition effects to show or remove a canvas component, as well as the ability to [create your own transitions](#create-your-own-transition). The dissolve transition: * When showing a component, gradually increases its `alpha`. If a component with the same alias exists, it will be removed when the new component's transition is complete. * When removing a component, gradually decreases its `alpha`. The `showWithDissolve` function displays a canvas element with a dissolve transition. This function has the following parameters: * `alias`: The alias to identify the component. * `image` (Optional): The image to show. If not provided, the alias is used as the URL. It can be: * a URL/path (video: VideoSprite, otherwise ImageSprite) * an array of URLs/paths (ImageContainer) * `{ value: string, options: ImageSpriteOptions }` (video: VideoSprite, otherwise ImageSprite) * `{ value: string[], options: ImageContainerOptions }` (ImageContainer) * a canvas component * `options` (Optional): Animation options, matching the `options` of `animate` function. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The `removeWithDissolve` function removes a canvas element with a dissolve transition. This function has the following parameters: * `alias`: The alias of the component to remove. * `options` (Optional): Animation options, matching the `options` of `animate` function. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, removeWithDissolve, showWithDissolve } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showWithDissolve("alien", "egg_head"); // [!code focus] await showWithDissolve("human", { // [!code focus] value: ["m01-body", "m01-eyes", "m01-mouth"], // [!code focus] options: { scale: 0.5, xAlign: 0.7 }, // [!code focus] }); // [!code focus] }, async () => { await showWithDissolve("alien", "flower_top"); // [!code focus] removeWithDissolve("human"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png", }, { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` The fade transition: * When showing a component, gradually increases its `alpha`. If a component with the same alias exists, the existing component will be removed with a fade-out effect before the new component is shown. * When removing a component, gradually decreases its `alpha`. The `showWithFade` function displays a canvas element with a fade transition. This function has the following parameters: * `alias`: The alias to identify the component. * `image` (Optional): The image to show. If not provided, the alias is used as the URL. It can be: * a URL/path (video: VideoSprite, otherwise ImageSprite) * an array of URLs/paths (ImageContainer) * `{ value: string, options: ImageSpriteOptions }` (video: VideoSprite, otherwise ImageSprite) * `{ value: string[], options: ImageContainerOptions }` (ImageContainer) * a canvas component * `options` (Optional): Animation options, matching the `options` of `animate` function. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The `removeWithFade` function removes a canvas element with a fade transition. This function has the following parameters: * `alias`: The alias of the component to remove. * `options` (Optional): Animation options, matching the `options` of `animate` function. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, removeWithFade, showWithFade } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await showWithFade("alien", "egg_head", { duration: 5 }); // [!code focus] await showWithFade("human", { // [!code focus] value: ["m01-body", "m01-eyes", "m01-mouth"], // [!code focus] options: { scale: 0.5, xAlign: 0.7 }, // [!code focus] }); // [!code focus] }, async () => { await showWithFade("alien", "flower_top"); // [!code focus] removeWithFade("human"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png", }, { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` The move in/out transition: * When showing a component, moves it from outside (right, left, top, or bottom) to its position on the canvas. If a component with the same alias exists, the existing component will be removed with a move-out effect before the new component is shown. * When removing a component, moves it from its position to outside the canvas. The `moveIn` function displays a canvas element with a move-in transition. This function has the following parameters: * `alias`: The alias to identify the component. * `image` (Optional): The image to show. If not provided, the alias is used as the URL. It can be: * a URL/path (video: VideoSprite, otherwise ImageSprite) * an array of URLs/paths (ImageContainer) * `{ value: string, options: ImageSpriteOptions }` (video: VideoSprite, otherwise ImageSprite) * `{ value: string[], options: ImageContainerOptions }` (ImageContainer) * a canvas component * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `removeOldComponentWithMoveOut` (Optional): If true, removes the existing component with a move-out effect after the new component transition. Default: `false`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The `moveOut` function removes a canvas element with a move-out transition. This function has the following parameters: * `alias`: The alias of the component to remove. * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { moveIn, moveOut, newLabel } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await moveIn("alien", "egg_head", { direction: "up" }); // [!code focus] await moveIn("human", { // [!code focus] value: ["m01-body", "m01-eyes", "m01-mouth"], // [!code focus] options: { scale: 0.5, xAlign: 0.7 }, // [!code focus] }); // [!code focus] }, async () => { await moveIn("alien", "flower_top", { direction: "up" }); // [!code focus] moveOut("human"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png", }, { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` The push in/out transition: * When showing a component, moves it from outside (right, left, top, or bottom) to its position on the canvas. If a component with the same alias exists, the existing component will be removed with a push-out effect while the new component is moving in. * When removing a component, moves it from its position to outside the canvas. The `pushIn` function displays a canvas element with a push-in transition. This function has the following parameters: * `alias`: The alias to identify the component. * `image` (Optional): The image to show. If not provided, the alias is used as the URL. It can be: * a URL/path (video: VideoSprite, otherwise ImageSprite) * an array of URLs/paths (ImageContainer) * `{ value: string, options: ImageSpriteOptions }` (video: VideoSprite, otherwise ImageSprite) * `{ value: string[], options: ImageContainerOptions }` (ImageContainer) * a canvas component * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The `pushOut` function removes a canvas element with a push-out transition. This function has the following parameters: * `alias`: The alias of the component to remove. * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, pushIn, pushOut } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await pushIn("alien", "egg_head"); // [!code focus] await pushIn("human", { // [!code focus] value: ["m01-body", "m01-eyes", "m01-mouth"], // [!code focus] options: { scale: 0.5, xAlign: 0.7 }, // [!code focus] }); // [!code focus] }, async () => { await pushIn("alien", "flower_top", { direction: "up" }); // [!code focus] pushOut("human"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png", }, { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` The zoom in/out transition: * When showing a component, scales it from 0 (from outside the canvas) to its original scale and position. If a component with the same alias exists, the existing component will be removed when the new component's transition is complete. * When removing a component, scales it from its original size to 0, moving it outside the canvas. The `zoomIn` function displays a canvas element with a zoom-in transition. This function has the following parameters: * `alias`: The alias to identify the component. * `image` (Optional): The image to show. If not provided, the alias is used as the URL. It can be: * a URL/path (video: VideoSprite, otherwise ImageSprite) * an array of URLs/paths (ImageContainer) * `{ value: string, options: ImageSpriteOptions }` (video: VideoSprite, otherwise ImageSprite) * `{ value: string[], options: ImageContainerOptions }` (ImageContainer) * a canvas component * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `removeOldComponentWithZoomOut` (Optional): If true, removes the existing component with a zoom-out effect after the new component transition. Default: `false`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. The `zoomOut` function removes a canvas element with a zoom-out transition. This function has the following parameters: * `alias`: The alias of the component to remove. * `options` (Optional): Animation options, matching the `options` of `animate` function. Additional properties: * `direction`: Direction of the move (`right`, `left`, `top`, `bottom`). Default: `right`. * `priority` (Optional): The priority of the PixiJS ticker. This parameter sets the ticker's priority. The default is `UPDATE_PRIORITY.NORMAL`. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, zoomIn, zoomOut } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { await zoomIn("alien", "egg_head"); // [!code focus] await zoomIn("human", { // [!code focus] value: ["m01-body", "m01-eyes", "m01-mouth"], // [!code focus] options: { scale: 0.5, xAlign: 0.7 }, // [!code focus] }); // [!code focus] }, async () => { await zoomIn("alien", "flower_top"); // [!code focus] zoomOut("human"); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "egg_head", src: "https://pixijs.com/assets/eggHead.png", }, { alias: "flower_top", src: "https://pixijs.com/assets/flowerTop.png", }, { alias: "m01-body", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-body.webp", }, { alias: "m01-eyes", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-eyes-smile.webp", }, { alias: "m01-mouth", src: "https://raw.githubusercontent.com/DRincs-Productions/pixi-vn-bucket/refs/heads/main/breakdown/m01/m01-mouth-smile00.webp", }, ], }, ], }; export default manifest; ``` ## Custom class The Pixi’VN Team welcomes new proposals and contributions to make this library even more complete. You can create a [discussion](https://github.com/DRincs-Productions/pixi-vn/discussions/categories/show-and-tell) to share or propose your transition. Creating your own transition is simple: use `canvas.animate` to define custom effects. To help you get started, here is a simplified version of [`showWithDissolve`](#dissolve): ```ts title="canvas/transitions/showWithDissolve.ts" import { AnimationOptions, canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn"; export default async function showWithDissolve( alias: string, component: ImageSprite, props: AnimationOptions = {}, priority?: UPDATE_PRIORITY ): Promise { let { completeOnContinue = true, ...options } = props; // add the new component canvas.add(alias, component) // edit the properties of the new component component.alpha = 0; // create the ticker and play it let id = canvas.animate( alias, { alpha: 1, }, { ...options, completeOnContinue, }, priority ); // load the image if the image is not loaded if (component.haveEmptyTexture) { await component.load(); } // return the ids of the tickers if (id) { return [id]; } } ``` ### Replace or remove the previous component If a component with the same alias already exists, you can let it be replaced, or: To remove the previous component when the new component's transition is complete, use the `aliasToRemoveAfter` property. ```ts title="canvas/transitions/showWithDissolve.ts" import { AnimationOptions, canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn"; export default async function showWithDissolve( alias: string, component: ImageSprite, props: AnimationOptions = {}, priority?: UPDATE_PRIORITY ): Promise { let { completeOnContinue = true, ...options } = props; // check if the alias is already exist // [!code ++] let oldComponentAlias: string | undefined = undefined; // [!code ++] if (canvas.find(alias)) { // [!code ++] oldComponentAlias = alias + "_temp_disolve"; // [!code ++] canvas.editAlias(alias, oldComponentAlias); // [!code ++] } // [!code ++] // add the new component and transfer the properties of the old component to the new component canvas.add(alias, component) oldComponentAlias && canvas.copyCanvasElementProperty(oldComponentAlias, alias); // [!code ++] oldComponentAlias && canvas.transferTickers(oldComponentAlias, alias, "duplicate"); // [!code ++] // edit the properties of the new component component.alpha = 0; // create the ticker and play it let id = canvas.animate( alias, { alpha: 1, }, { ...options, completeOnContinue, }, priority ); // load the image if the image is not loaded if (component.haveEmptyTexture) { await component.load(); } // return the ids of the tickers if (id) { return [id]; } } ``` To remove the previous component with a transition, run another animation with `canvas.animate` and use the `tickerIdToResume` property. ```ts title="canvas/transitions/showWithFade.ts" import { AnimationOptions, canvas, ImageSprite, UPDATE_PRIORITY } from "@drincs/pixi-vn"; export default async function showWithDissolve( alias: string, component: ImageSprite, props: AnimationOptions = {}, priority?: UPDATE_PRIORITY ): Promise { let { completeOnContinue = true, ...options } = props; let res: string[] = []; // [!code ++] // check if the alias is already exist // [!code ++] if (!canvas.find(alias)) { // [!code ++] return showWithDissolve(alias, component, props, priority); // [!code ++] } // [!code ++] let oldComponentAlias = alias + "_temp_fade"; // [!code ++] canvas.editAlias(alias, oldComponentAlias); // [!code ++] // add the new component and transfer the properties of the old component to the new component // [!code ++] canvas.add(alias, component); oldComponentAlias && canvas.copyCanvasElementProperty(oldComponentAlias, alias); // [!code ++] oldComponentAlias && canvas.transferTickers(oldComponentAlias, alias, "duplicate"); // [!code ++] // edit the properties of the new component component.alpha = 0; // create the ticker and play it let id = canvas.animate( alias, { alpha: 1, }, { ...options, completeOnContinue, }, priority ); if (id) { // [!code ++] // pause the ticker // [!code ++] canvas.pauseTicker({ id: id }); // [!code ++] // remove the old component // [!code ++] canvas.animate( // [!code ++] alias, // [!code ++] { // [!code ++] alpha: 0, // [!code ++] }, // [!code ++] { // [!code ++] ...options, // [!code ++] tickerIdToResume: id, // [!code ++] aliasToRemoveAfter: oldComponentAlias, // [!code ++] completeOnContinue, // [!code ++] }, // [!code ++] priority // [!code ++] ); // [!code ++] } // [!code ++] // load the image if the image is not loaded if (component.haveEmptyTexture) { await component.load(); } // return the ids of the tickers if (id) { return [id]; } } ``` # VideoSprite (/start/canvas-video) import { VideoSpriteAdd, VideoSpriteLooping, VideoSpritePlayPause, VideoSpriteRestart, VideoSpriteShow } from "@/components/examples"; The `VideoSprite` component extends the `ImageSprite` component, so you can use all the methods and properties of `ImageSprite`. It is used to display a single video on the canvas. To initialize this component, you must pass the following parameters: * `options` (Optional): The options for the component. * `videoUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. ```ts import { canvas, VideoSprite } from "@drincs/pixi-vn" let video = new VideoSprite({ anchor: { x: 0.5, y: 0.5 }, x: 100, y: 100, }, 'https://pixijs.com/assets/video.mp4') await video.load() canvas.add("my_video", video) ``` Compared to the `Sprite` component, `ImageSprite` adds the following features: * `loop`: Indicates if the video should loop after it finishes. * `paused`: Indicates if the video is paused. * `pause()`: Pause the video. * `play()`: Play the video. * `currentTime`: The current time of the video. * `restart()`: Restart the video. ## Show The simplest way to show a video on the canvas is to use the `showVideo` function. This function combines `load` and `canvas.add`. This function returns a `VideoSprite` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `videoUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. If you don't provide the URL, then the alias is used as the URL. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, showVideo } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { let video1 = await showVideo("video"); // [!code focus] let video2 = await showVideo("video2", "video", { // [!code focus] xAlign: 0.5, // [!code focus] }); // [!code focus] let video3 = await showVideo("video3", "https://pixijs.com/assets/video.mp4", { // [!code focus] xAlign: 1, // [!code focus] }); // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "video", src: "https://pixijs.com/assets/video.mp4", }, ], }, ], }; export default manifest; ``` ## Add To add an video to the canvas, use the `addVideo` function. This function only adds the component to the canvas; it does **not** show it or load its texture. It uses `canvas.add` to add the component to the canvas. This function returns a `VideoSprite` that you can use to manipulate the component. This function has the following parameters: * `alias`: The alias to identify the component. * `videoUrl` (Optional): The URL or path. If you have initialized the asset matrix, you can use the alias of the texture. If you don't provide the URL, then the alias is used as the URL. * `options` (Optional): The options for the component. labels/startLabel.ts assets/manifest.ts ```ts import { addVideo, canvas, VideoSprite, newLabel } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ () => { let video1 = addVideo("video"); // [!code focus] let video2 = addVideo("video2", "video", { // [!code focus] xAlign: 0.5, // [!code focus] }); // [!code focus] let video3 = addVideo("video3", "https://pixijs.com/assets/video.mp4", { // [!code focus] xAlign: 1, // [!code focus] }); // [!code focus] }, async () => { let video1 = canvas.find("video"); let video2 = canvas.find("video2"); let video3 = canvas.find("video3"); // Load the textures video1 && (await video1.load()); video2 && (await video2.load()); video3 && (await video3.load()); }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "video", src: "https://pixijs.com/assets/video.mp4", }, ], }, ], }; export default manifest; ``` ## Remove As with other canvas components, you can remove this component using the `canvas.remove` function. ## Play and pause Use `play()` and `pause()` methods, or set the `paused` property. labels/startLabel.ts assets/manifest.ts ```ts import { canvas, narration, newLabel, showVideo, VideoSprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { narration.dialogue = "add video"; await showVideo("video"); }, async () => { narration.dialogue = "pause video"; let video = canvas.find("video"); if (video) { video.pause(); // [!code focus] // or: video.paused = true // [!code focus] } }, async () => { narration.dialogue = "resume video"; let video = canvas.find("video"); if (video) { video.play(); // [!code focus] // or: video.paused = false // [!code focus] } }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "video", src: "https://pixijs.com/assets/video.mp4", }, ], }, ], }; export default manifest; ``` ## Looping Set the `loop` property to make the video repeat. labels/startLabel.ts assets/manifest.ts ```ts import { newLabel, showVideo } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { let video = await showVideo("video"); video.loop = true; // [!code focus] }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "video", src: "https://pixijs.com/assets/video.mp4", }, ], }, ], }; export default manifest; ``` ## Restart Use the `restart()` method to restart playback. labels/startLabel.ts assets/manifest.ts ```ts import { canvas, narration, newLabel, showVideo, VideoSprite } from "@drincs/pixi-vn"; export const startLabel = newLabel("start_label", [ async () => { narration.dialogue = "add video"; await showVideo("video"); }, async () => { narration.dialogue = "restart video"; let video = canvas.find("video"); if (video) { video.restart(); // [!code focus] } }, ]); ``` ```ts import { AssetsManifest } from "@drincs/pixi-vn"; /** * Manifest for the assets used in the game. * You can read more about the manifest here: https://pixijs.com/8.x/guides/components/assets#loading-multiple-assets */ const manifest: AssetsManifest = { bundles: [ { name: "start", assets: [ { alias: "video", src: "https://pixijs.com/assets/video.mp4", }, ], }, ], }; export default manifest; ``` # Canvas (WebGL) (/start/canvas) Pixi’VN uses [PixiJS](https://pixijs.com/8.x/guides/basics/what-pixijs-is) for rendering. You can use the Pixi’VN API to add images, text, and animations to the PixiJS canvas (or, more precisely, to the 2D WebGL rendering engine). You can use this method with the *ink* syntax. See more here. pixijs-h2 ## What is PixiJS? PixiJS is the fastest, most lightweight 2D library available for the web, working across all devices and allowing you to create rich, interactive graphics and cross-platform applications using WebGL and WebGPU. It is fast, flexible, and easy to use. PixiJS is used in games like [Good Pizza, Great Pizza](https://www.goodpizzagreatpizza.com/) and [The Enchanted Cave 2](https://store.steampowered.com/app/368610/The_Enchanted_Cave_2/). You can learn more about PixiJS on the [PixiJS website](https://www.pixijs.com/). ## Differences between Pixi’VN and PixiJS PixiJS is not designed to support multiple instances. For this reason, in non-CDN builds, `pixi.js` is treated as an **external dependency**. If you are using build tools such as Vite.js or similar systems, you can directly install and use the `pixi.js` package. However, this approach is not supported in CDN-based environments such as CodePen. If you need direct access to PixiJS while working with Pixi’VN, it is recommended to use the `@drincs/pixi-vn/pixi.js` submodule. This submodule exposes the PixiJS application instance that is internally used by Pixi’VN, ensuring full compatibility and avoiding multiple-instance issues. ```ts import { Graphics } from "@drincs/pixi-vn/pixi.js"; ``` This is useful when creating UI layers with PixiJS or for creating minigames. Pixi’VN provides an API object called `canvas` to interface with its PixiJS application. You do not need to install pixi.js separately, and Pixi’VN's features are not identical to those of PixiJS. Although not recommended, you can use `canvas.app` to directly access the PixiJS application used by Pixi’VN. Rendering in Pixi’VN is very similar to PixiJS, with the following differences: * All components added to the canvas are linked to an alias of your choice. This alias is used to identify and manipulate the component. * Pixi’VN saves the current canvas state at each `step`. **Note:** Only components linked to an alias are saved. If you add components directly to `PixiJS.Application`, they will not be included in the saved state. * Pixi’VN provides various functions to add, remove, find, and manipulate components in the canvas. * Pixi’VN offers custom components, some of which correspond to PixiJS components, while others add new features. * Tickers are managed by Pixi’VN. If you use a PixiJS ticker, its state will not be saved. * To add event listeners and save their state in saves, it is recommended to read here. ## Use PixiJS DevTools with Pixi’VN [**PixiJS DevTools**](https://pixijs.io/devtools/) is a [Chrome extension](https://chromewebstore.google.com/detail/pixijs-devtools/dlkffcaaoccbofklocbjcmppahjjboce) that allows you to inspect and debug PixiJS applications. You can use it to view the display list, inspect textures, and debug your PixiJS application. PixiJS DevTools works with Pixi’VN, allowing you to inspect the canvas. devtools After installing PixiJS DevTools, open Chrome DevTools (F12) and go to the `PixiJS` tab. image # Characters (/start/character) **What are `characters`?** Characters are the actors that appear in a visual novel. In Pixi’VN, characters are created using the `CharacterBaseModel` class or a [custom class](#custom-class). ## Initialize To initialize a character, create a new instance of the `CharacterBaseModel` class (or your [custom class](#custom-class)) and add it to the game character dictionary when the game is initialized. It is recommended to import the instances at project startup, see the `src/main.ts` file. To create a new instance of `CharacterBaseModel`, you need the following parameters: * `id`: A unique identifier (string). It is used to reference the character in the game (must be unique). If you want to create a [character with an "emotion", you can pass an object](#character-emotions). * `props`: An object with the character's properties: * `name`: The character's name. * `surname` (Optional): The character's surname. * `age` (Optional): The character's age. * `icon` (Optional): The character's icon image URL. * `color` (Optional): The character's color. values/characters.ts src/main.ts ```ts import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn"; export const liam = new CharacterBaseModel('liam', { name: 'Liam', surname: 'Smith', age: 25, icon: "https://example.com/liam.png", color: "#9e2e12" }); export const emma = new CharacterBaseModel('emma', { name: 'Emma', surname: 'Johnson', age: 23, icon: "https://example.com/emma.png", color: "#9e2e12" }); RegisteredCharacters.add([liam, emma]); ``` ```ts import "./values/characters"; // ... ``` `RegisteredCharacters.add` is **required** to save the characters in the game. You can also create a function to load characters. The important thing is that it is called at least once before the characters are used in the game, otherwise they will not be available. ## Get To get a character by its `id`, use the `RegisteredCharacters.get` function. ```typescript import { RegisteredCharacters } from "@drincs/pixi-vn"; const liam = RegisteredCharacters.get('liam'); ``` ## Get all To get all characters, use the `RegisteredCharacters.values` function. ```typescript import { RegisteredCharacters } from "@drincs/pixi-vn"; const characters = RegisteredCharacters.values(); ``` ## Use You can use this method with the *ink* syntax. See more here. You can use a game character, for example, to link it to the current dialogue. You can use the character's `id` or the character's instance, but it is recommended to use the instance. ```ts title="values/characters.ts" export const liam = new CharacterBaseModel('liam_id', { name: 'Liam', surname: 'Smith', age: 25, icon: "https://example.com/liam.png", color: "#9e2e12" }); RegisteredCharacters.add([liam]); ``` ```typescript narration.dialogue = { character: liam, text: "Hello" } // or narration.dialogue = { character: "liam_id", text: "Hello" } ``` ## Edit You can use this method with the *ink* syntax. See more here. `CharacterBaseModel` is a stored class, which means its properties are saved in game storage. If the character's name is changed during the game, the new name will be saved in the game storage and linked to its `id`. If the **character's `id` is changed** from one version to another, the system will **not** move the data linked from the previous `id` to the new `id`. The properties of the `CharacterBaseModel` that are stored in the game storage are: * `name`: The character's name. * `surname`: The character's surname. * `age`: The character's age. To get the properties used when instantiating the class, you can use: * `defaultName`: The character's name. * `defaultSurname`: The character's surname. * `defaultAge`: The character's age. Here's a simplified implementation of the `CharacterBaseModel` class for better understanding of the properties that are stored in the game storage: ```typescript title="CharacterBaseModel.ts" export default class CharacterBaseModel extends StoredClassModel implements CharacterBaseModelProps { constructor(id: string, props: CharacterBaseModelProps) { super( // ... ) this.defaultName = props.name this.icon = props.icon // ... } // name property is stored in the game storage private defaultName: string = "" get name(): string { return this.getStorageProperty("name") || this.defaultName } set name(value: string) { this.setStorageProperty("name", value) } // icon property is not stored in the game storage icon: string = "" // ... } ``` ## Character emotions You can use this method with the *ink* syntax. See more here. It is often useful to have multiple types of the same character. For example, a character "Alice" and a subtype related to her emotional state, like "Angry Alice". The character and the subtype have the same characteristics, except for one or more properties, such as the icon. With Pixi’VN, you can create a "character with an emotion" by passing an object instead of the id, with the following properties: * `id`: The id of an existing character. * `emotion`: The character's subcategory (e.g. the character's emotion). ```ts title="values/characters.ts" import { CharacterBaseModel, RegisteredCharacters } from "@drincs/pixi-vn"; export const alice = new CharacterBaseModel('alice', { name: 'Alice', icon: "https://example.com/alice.png", color: "#9e2e12" }); export const angryAlice = new CharacterBaseModel({ id: 'alice', emotion: 'angry' }, { icon: "https://example.com/angryAlice.png", }); RegisteredCharacters.add([alice, angryAlice]); ``` ```typescript console.log(alice.name); // Alice alice.name = "Eleonora"; console.log(alice.name); // Eleonora console.log(angryAlice.name); // Eleonora angryAlice.name = "Angry Eleonora"; console.log(alice.name); // Eleonora console.log(angryAlice.name); // Angry Eleonora ``` ## Custom class In all templates, the `Character` class is already defined in the file `models/Character.ts`. You can use it directly or modify it to suit your needs. It is recommended to create your own class `Character` that extends `CharacterStoredClass` and "override" the interface `CharacterInterface` to add, edit, or remove properties or methods. For example, if you want to create a class `Character`, you must "override" the interface `CharacterInterface` to use your properties or methods. (See the file `pixi-vn.d.ts`) Now you can create a class `Character` that extends `CharacterStoredClass` and implements the `CharacterInterface`. (For more information on how to create a class in TypeScript, read [the official documentation](https://www.typescriptlang.org/docs/handbook/2/classes.html)) To create a property that stores its value in the game storage, you can create [Getters/Setters](https://www.typescriptlang.org/docs/handbook/2/classes.html#getters--setters) and use the `this.getStorageProperty()`/`this.setStorageProperty()` methods. (See the file `Character.ts`) models/Character.ts pixi-vn.d.ts ```ts import { CharacterInterface, CharacterStoredClass } from "@drincs/pixi-vn"; export class Character extends CharacterStoredClass implements CharacterInterface { constructor(id: string | { id: string, emotion: string }, props: CharacterProps) { super(typeof id === "string" ? id : id.id, typeof id === "string" ? "" : id.emotion) this._icon = props.icon this._color = props.color this.defaultName = props.name this.defaultSurname = props.surname this.defaultAge = props.age } // Not stored properties readonly icon?: string; readonly color?: string | undefined; // Stored properties private defaultName?: string get name(): string { return this.getStorageProperty("name") || this.defaultName || this.id } set name(value: string | undefined) { this.setStorageProperty("name", value) } private defaultSurname?: string get surname(): string | undefined { return this.getStorageProperty("surname") || this.defaultSurname } set surname(value: string | undefined) { this.setStorageProperty("surname", value) } private defaultAge?: number | undefined get age(): number | undefined { return this.getStorageProperty("age") || this.defaultAge } set age(value: number | undefined) { this.setStorageProperty("age", value) } } interface CharacterProps { /** * The name of the character. */ name?: string; /** * The surname of the character. */ surname?: string; /** * The age of the character. */ age?: number; /** * The icon of the character. */ icon?: string; /** * The color of the character. */ color?: string; } ``` ```ts declare module '@drincs/pixi-vn' { interface CharacterInterface { /** * The name of the character. * If you set undefined, it will return the default name. */ name: string; /** * The surname of the character. * If you set undefined, it will return the default surname. */ surname?: string; /** * The age of the character. * If you set undefined, it will return the default age. */ age?: number; /** * The icon of the character. */ readonly icon?: string; /** * The color of the character. */ readonly color?: string; } } ``` # Choice menus (/start/choices) import { ChoiceMenus, ChoicesAlreadyMade } from "@/components/examples"; You can find an example of the choice menu UI screen in the interface examples section. You can use this method with the *ink* syntax. See more here. In visual novels, choice menus allow the player to make decisions that affect the story. In Pixi’VN, you can prompt the player to make a choice. Each choice can either start a `label` or close the choice menu. ## Require the player to make a choice To require the player to make a choice, set `narration.choices` to an array of `StoredChoiceInterface`. To create a `StoredChoiceInterface` object, use: In Pixi’VN, you can create a choice menu option using the `newChoiceOption` function. This function has the following parameters: * `text`: The text displayed in the choice menu. * `label`: The `label` to call when the player selects the option. * `props`: The properties passed to the `label`. If the `label` does not require parameters, pass an empty object `{}`. * `options` (Optional): An object with the `choice`'s options: * `type`: How the label will be performed. Can be `call` or `jump`. Default is `call`. * `oneTime`: If `true`, the choice can only be made once. * `onlyHaveNoChoice`: If `true`, the choice is shown only if there are no other choices. * `autoSelect`: If `true` and it is the only choice, it will be selected automatically. In addition to `newChoiceOption`, you can use `newCloseChoiceOption` to create a closing option. This closes the choice menu and continues with the steps, without calling any label. This function has the following parameters: * `text`: The text displayed in the choice menu. * `options` (Optional): An object with the `choice`'s options: * `closeCurrentLabel`: If `true`, the current `label` will be closed. Default is `false`. * `oneTime`: If `true`, the choice can only be made once. * `onlyHaveNoChoice`: If `true`, the choice is shown only if there are no other choices. * `autoSelect`: If `true` and it is the only choice, it will be selected automatically. ```ts title="labels/startLabel.ts" import { newChoiceOption, newCloseChoiceOption, narration, newLabel } from "@drincs/pixi-vn" export const startLabel = newLabel("start_label", [ async () => { narration.dialogue = "Choose a fruit:" narration.choices = [ // [!code focus] newChoiceOption("Orange", orangeLabel, {}), // by default, the label will be called with "call" // [!code focus] newChoiceOption("Banana", bananaLabel, {}, { type: "jump" }), // [!code focus] newChoiceOption("Apple", appleLabel, { quantity: 5 }, { type: "call" }), // [!code focus] newCloseChoiceOption("Cancel"), // [!code focus] ] // [!code focus] }, () => { narration.dialogue = "Restart" }, async (props) => await narration.jump("start_label", props) ], ) ``` ## Get To get the current choice menu, use `narration.choices`. This returns an array of `StoredChoiceInterface`. ```typescript const menuOptions: StoredChoiceInterface[] = narration.choices; ``` ## Request To select a choice, use `narration.selectChoice`. ```typescript const item = narration.choices![0]; // get the first item narration.selectChoice(item, { // Add StepLabelProps here navigate: navigate, // example // And the props to pass to the label ...item.props }) .then(() => { // ... }) .catch((e) => { // ... }) ``` ## Remove To clear the choice options, set `narration.choices = undefined`. ```typescript narration.choices = undefined; ``` ## Custom class You can customize a choice menu option by adding properties to the `ChoiceInterface` interface. For example, add an `icon` property to display an icon. Override the `ChoiceInterface` interface in your `.d.ts` file: pixi-vn.d.ts labels/startLabel.ts screens/ChoiceMenu.tsx ```ts declare module '@drincs/pixi-vn' { interface ChoiceInterface { icon?: string } } ``` ```ts narration.choices = [ newChoiceOption("Orange", orangeLabel, {}, { icon: "orange.png" }), newChoiceOption("Banana", bananaLabel, {}, { icon: "banana.png" }), newChoiceOption("Apple", appleLabel, {}, { icon: "apple.png" }), ] ``` ```tsx function ChoiceMenu({ choices }: { choices: StoredIndexedChoiceInterface[] }) { return (
{choices.map((choice, index) => (
{choice.icon && {choice.text}}
))}
) } ```
## Other features To get the choices already made in the current `step`, use `narration.alreadyCurrentStepMadeChoices`. hooks/useQueryInterface.ts screens/ChoiceMenu.tsx ```ts import { narration } from "@drincs/pixi-vn"; import { useQuery } from "@tanstack/react-query"; export const INTERFACE_DATA_USE_QUEY_KEY = "interface_data_use_quey_key"; const CHOICE_MENU_OPTIONS_USE_QUEY_KEY = "choice_menu_options_use_quey_key"; export function useQueryChoiceMenuOptions() { return useQuery({ queryKey: [INTERFACE_DATA_USE_QUEY_KEY, CHOICE_MENU_OPTIONS_USE_QUEY_KEY], queryFn: async () => narration.choices?.map((option) => ({ ...option, text: typeof option.text === "string" ? option.text : option.text.join(" "), alreadyChosen: // [!code ++] narration.alreadyCurrentStepMadeChoices?.find((index) => index === option.choiceIndex) !== // [!code ++] undefined, // [!code ++] })) || [], }); } ``` ```tsx import useNarrationFunctions from "../hooks/useNarrationFunctions"; import { useQueryChoiceMenuOptions } from "../hooks/useQueryInterface"; export default function ChoiceMenu() { const { data: menu = [] } = useQueryChoiceMenuOptions(); const { selectChoice } = useNarrationFunctions(); return (
0 ? "auto" : "none", }} > {menu?.map((item, index) => ( ))}
); } ```
# Dialogue (/start/dialogue) import { CurrentDialogueExample, DialogueGlueExample } from "@/components/examples"; You can find the example of the narrative dialogue UI screen in the interface examples section. **What is dialogue?** A written composition in which two or more characters are represented as conversing. In Pixi’VN, `dialogue` is an object that contains information about *who* and *what* is currently being said. Its functionality can be broader, as it can also be used for other purposes, such as monologues, soliloquies, or to display a message to the player. For this reason, it is more appropriate to consider it as a text that can be linked to a character. ## Set To set the current dialogue, you can use `narration.dialogue`. ```ts title="labels/startLabel.ts" import { narration, newLabel } from "@drincs/pixi-vn" import { eggHead } from "../values/characters" // What is a Label? https://pixi-vn.web.app/start/labels.html export const startLabel = newLabel("start_label", [ () => { // in this example, not exists a character with id 'Alice' // [!code focus] // so when you get the current dialogue, the character is a fake character with the name 'Alice' // [!code focus] narration.dialogue = { // [!code focus] character: "Alice", // [!code focus] text: "Hello, world!" // [!code focus] } // [!code focus] }, () => { // in this example, exists a character with id 'egg-head' // [!code focus] // so when you get the current dialogue, the character is the character with id 'egg-head' // [!code focus] narration.dialogue = { // [!code focus] character: 'egg-head', // [!code focus] text: "Hello, world!" // [!code focus] } // [!code focus] // or better // [!code focus] narration.dialogue = { // [!code focus] character: eggHead, // [!code focus] text: "Hello, world!" // [!code focus] } // [!code focus] }, // if don't want to set a character, you can set a string // [!code focus] () => narration.dialogue = "Hello, world!", // [!code focus] ], ) ``` ## Get To get the current dialogue, use `narration.dialogue`. The return value is a `DialogueInterface`. ```typescript const currentDialogue: DialogueInterface = narration.dialogue; ``` ## Delete To clear the current dialogue, set `narration.dialogue = undefined`. ```typescript narration.dialogue = undefined; ``` ## Custom class You can customize the dialogue interface by adding additional properties to the `DialogueInterface`. For example, you can add a `color` property to change the color of the text. To do this, "override" the `DialogueInterface` interface in your `.d.ts` file: pixi-vn.d.ts labels/startLabel.ts ```ts declare module '@drincs/pixi-vn' { interface DialogueInterface { color?: string } } ``` ```ts narration.dialogue = { character: "Alice", text: "Hello, world!", color: "#ff0000" } ``` ## Other features Dialogue glue is a feature originally created for ***ink***, which was also introduced in Pixi’VN. When "glue" is enabled, the next dialogue will be appended after the current dialogue. ```ts title="labels/startLabel.ts" import { narration, newLabel } from "@drincs/pixi-vn"; const startLabel = newLabel("start", [ () => { narration.dialogue = `Hello, my name is Alice and ...` // [!code focus] }, () => { narration.dialogGlue = true // [!code focus] narration.dialogue = `I am a character in this game.` // [!code focus] }, ]) ``` # Desktop & mobile devices (/start/distribution-desktop-mobile) There are several ways to distribute your game for desktop and mobile platforms. Common choices include [Tauri](https://v2.tauri.app/), [Ionic](https://ionicframework.com/), [Electron](https://www.electronjs.org/), [NW.js](https://nwjs.io/), and [React Native for Windows + Mac](https://microsoft.github.io/react-native-windows/). If you don't want to manage a heavily customized native project, consider using the multi-device templates. Those templates include Tauri so you can develop a web app and also build desktop and mobile apps from the same codebase. tauri-h2 ## Tauri Tauri is a framework for building desktop and mobile applications with web technologies. It leverages **Rust** to produce secure, lightweight, and fast native binaries, while a WebView renders your HTML, CSS, and JavaScript. Learn more on the [Tauri website](https://v2.tauri.app/). ## Distributing your game with Tauri Creating releases manually for every platform is difficult: it requires many tools to be installed, and building iOS apps requires a Mac. For these reasons, manual release generation is generally not recommended. Tauri supports using GitHub Actions to automate release builds. GitHub Actions runs jobs on virtual machines (runners) that you configure with YAML workflow files. In a workflow you define the events that trigger the pipeline (for example, pushing a tag) and the list of commands the runner should execute. Currently, mobile builds via GitHub Actions are experimental. The multi-device templates include the file `/.github/workflows/tauri.yml`. That workflow builds release artifacts (installation packages) for multiple operating systems. ```yml title=".github/workflows/tauri.yml" # https://tauri.app/distribute/pipelines/github/ name: 'publish' on: push: tags: - 'v*' jobs: publish-tauri: permissions: contents: write strategy: fail-fast: false matrix: include: - platform: 'macos-latest' # for Arm based macs (M1 and above). args: '--target aarch64-apple-darwin' - platform: 'macos-latest' # for Intel based macs. args: '--target x86_64-apple-darwin' - platform: 'ubuntu-22.04' args: '' - platform: 'windows-latest' args: '' - platform: 'macos-latest' # iOS args: '--target x86_64-apple-darwin' mobile: "ios" - platform: 'ubuntu-22.04' # Android args: '' mobile: "android" runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - name: install dependencies (ubuntu only) if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. run: | sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - name: setup node uses: actions/setup-node@v6 with: node-version: lts/* - name: Update Java (only for Android builds) if: matrix.mobile == 'android' uses: actions/setup-java@v5 with: distribution: 'temurin' java-version: '21' - name: setup Android SDK (only for Android builds) if: matrix.mobile == 'android' uses: android-actions/setup-android@v3 - name: install Rust stable uses: dtolnay/rust-toolchain@stable # Set this to dtolnay/rust-toolchain@nightly with: # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} - name: install frontend dependencies # If you don't have `beforeBuildCommand` configured you may want to build your frontend here too. run: yarn install # change this to npm or pnpm depending on which one you use. - name: Upload Tauri icons run: npm run tauri icon public/pwa-512x512.png - name: Rust cache uses: swatinem/rust-cache@v2 with: workspaces: './src-tauri -> target' - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tagName: app-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. releaseName: 'App v__VERSION__' releaseBody: 'See the assets to download this version and install.' releaseDraft: true prerelease: false args: ${{ matrix.args }} mobile: ${{ matrix.mobile || null }} ``` To trigger this workflow you need a GitHub repository for your project and a Git tag that starts with `v`. For example: ```bash git tag v1.0.0 git push origin v1.0.0 ``` You can follow the workflow runs in the repository's Actions tab. Actions section At the end of a successful run a GitHub Release will be created. Read more about GitHub Releases [here](https://docs.github.com/repositories/releasing-projects-on-github/viewing-your-repositorys-releases-and-tags). # itch.io (/start/distribution-itchio) You can distribute your game on [itch.io](https://itch.io/). It is a platform that allows you to upload your game and distribute it to the public. It is a great platform to distribute your game and get feedback from the community. ## Game playable in browser On itch.io you can enable the "This file will be played in the browser" flag for an uploaded file to make it playable in the browser. But only a single html or js file can be executed and not a directory. For example, if you upload a zip of the generated folder after a build, you will see a result of something like this: image The solution is to host the game on a server and then create an iframe on itch.io to show the game. For example, after hosting the game on a server, you can create a index.html file with the following content: ```html title="index.html"
``` Then you can upload the index.html file to itch.io. # Website (/start/distribution-website) Your Pixi’VN game can be distributed as a website, allowing players to access it directly in their web browsers. This is a great way to reach a wider audience, as no downloads or installations are required.\ To do this, you need to [host the game on a server](#hosting-and-deploying). ## Hosting and deploying You can use various hosting services like [Firebase](https://firebase.google.com/), [Vercel](https://vercel.com/), or [Netlify](https://www.netlify.com/). These platforms offer free plans and have similar setup processes. This guide uses Firebase as an example for hosting.\ Firebase offers additional features such as database hosting and authentication, and is a reliable Google product. 1. [Create a Firebase project](https://console.firebase.google.com/) if you haven't already. 2. Install the Firebase CLI: ```bash npm install -g firebase-tools ``` 1. Log in to your Google account (if first time): ```bash firebase login ``` 1. Initialize Firebase in your project directory: ```bash firebase init ``` Follow the CLI prompts: * **Features**: Choose `Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys`. * **Default project**: Select your Firebase project. * **Detected Vite codebase**: Enter `no`. * **Use a web framework?**: Enter `no`. * **Public directory**: Enter `dist`. * **Single-page app?**: Enter `yes` (ensures routing and deep linking work). * **Overwrite dist/index.html?**: Enter `no`. * **Set up GitHub deploys?**: Enter `no` (Optional). A `firebase.json` config file will be generated. Example `firebase.json`: ```json title="firebase.json" { "hosting": { "public": "dist", "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], "rewrites": [ { "source": "**", "destination": "/index.html" } ] } } ``` See the [Firebase documentation](https://firebase.google.com/docs/hosting/full-config#section-firebase-json) for more details. To deploy your app: ```bash npm run build firebase deploy ``` ## Enable "Add to Home Screen" (PWA Plugin) image Enabling installation as a "browser application" allows users to use your game as a standalone app on their device. If you use [Vite js](https://vitejs.dev/) as your build tool, you can add the [PWA Vite Plugin](https://vite-pwa-org.netlify.app/). See this [example](https://vite-pwa-org.netlify.app/guide/pwa-minimal-requirements.html#web-app-manifest). Otherwise, create a `manifest.json` file according to your framework's requirements. # Distribution (/start/distribution) A very important point to take into account before developing a game is the distribution of the game. It is important to know how the game will be distributed and how it will be played by the users. Potentially all JavaScript/Typescript projects can be distributed in **all platforms**. For example, Discord is a JavaScript/Typescript project that have a desktop app, a web app and a mobile app. * Website (native support) * itch.io * Desktop & Mobile devices # Flags management (/start/flags) Pixi’VN provides functions to manage "game flags". Game flags are boolean values used to control the flow of the game or to store other boolean value in the game storage. This mechanic has much less impact on save size than saving a boolean in game storage. ## Set To set a flag, use the `storage.setFlag` function. This function has the following parameters: * `name`: The flag name. * `value`: The flag value. ```typescript import { storage } from '@drincs/pixi-vn' storage.setFlag('flag1', true) ``` ## Get To get a flag, use the `storage.getFlag` function. This function has the following parameters: * `name`: The flag name. ```typescript import { storage } from '@drincs/pixi-vn' const flag1 = storage.getFlag('flag1') ``` ## Development possibilities ### Connect a flag to a class boolean property If you are creating a class with a boolean property, you can connect it to a flag. This way, the property will automatically reflect the flag's value. This can simplify your code and make it more readable. ```typescript import { storage } from '@drincs/pixi-vn' class ButtonClass { private _disabled: boolean | string get disabled() { if (typeof this._disabled === 'string') { return storage.getFlag(this._disabled) } return this._disabled } set disabled(value: boolean | string) { this._disabled = value } } ``` ```typescript // Button to go to school const goToSchoolButton = new ButtonClass() goToSchoolButton.disabled = 'weekend' function afterNewDay() { storage.setFlag('weekend', // Check if it is Saturday or Sunday ) } ``` # History (/start/history) You can find an example of the history UI screen in the interface examples section. Pixi’VN saves all dialogues, choices, responses and more, at every `step` executed during the game. ## Get The narration timeline is a list of all dialogues and choices shown to the player.\ To get the narrative history, use `stepHistory.narrativeHistory`. It returns a list of `NarrativeHistory[]`. ```typescript const dialogues: NarrativeHistory[] = stepHistory.narrativeHistory; ``` ## Remove To delete all narrative history, use `stepHistory.removeNarrativeHistory()`. ```typescript stepHistory.removeNarrativeHistory(); ``` To delete part of the narrative history, pass a number to remove the first N elements: ```typescript // Delete the first 2 elements stepHistory.removeNarrativeHistory(2); ``` ## Other features At each `step`, all information about the current game state is saved. To prevent the save file from growing too large, there is a limit on the number of `steps` saved. By default, only the last 20 `steps` are saved, but you can increase this limit (e.g., to 100). When the limit is reached, only essential information from older `steps` is kept. This allows you to display the full narrative history, but you cannot return to a specific `step` beyond the limit. You can change the `step` save limit by setting the `stepLimitSaved` property in the `stepHistory` object. ```typescript import { stepHistory } from '@drincs/pixi-vn' stepHistory.stepLimitSaved = 100 ``` To disable the `step` save limit, set `stepLimitSaved` to `Infinity`. ```typescript import { stepHistory } from '@drincs/pixi-vn' stepHistory.stepLimitSaved = Infinity ``` TODO: add addCurrentStepToHistory ## Add current state into step history Every # label history # Quick Start (/start) You can start using Pixi’VN by [initializing a new project](#project-initialization) or [installing the package](#installation) in an existing project. ## Prerequisites Before starting, you must have the following tools installed: * [Node.js](https://nodejs.org/) version 18 or higher. * Text editor with TypeScript support, such as: * [Visual Studio Code](https://code.visualstudio.com/) * [Cursor](https://www.cursor.com/) * [VSCodium](https://vscodium.com/) * (Recommended) [Git](https://git-scm.com/) * A [GitHub account](https://github.com/) ## Project Initialization If you want to start from a new project, you can use the following command to initialize a new project with the Pixi’VN templates: npm yarn pnpm bun deno ```sh npm create pixi-vn@latest ``` ```sh yarn create pixi-vn ``` ```sh pnpm create pixi-vn ``` ```sh bun create pixi-vn ``` ```sh deno init --npm pixi-vn ``` You can see the list of available templates and interactive demos here. After the project is initialized, open the project directory with your text editor (VSCode is recommended) and start developing your project. All templates include a `README.md` file with more information about the project. ## Installation To install the Pixi’VN package in an existing JavaScript project, use one of the following commands: npm yarn pnpm bun deno ```sh npm install @drincs/pixi-vn ``` ```sh yarn add @drincs/pixi-vn ``` ```sh pnpm add @drincs/pixi-vn ``` ```sh bun add @drincs/pixi-vn ``` ```sh deno install npm:@drincs/pixi-vn ``` You can also use the CDN version of this plugin: script tag map import js import ```html title="index.html" ``` ```html title="index.html" ``` ```js title="index.js" import pixivn from "https://cdn.jsdelivr.net/npm/@drincs/pixi-vn@/+esm"; ```