-
-
Notifications
You must be signed in to change notification settings - Fork 1
interface
Pixi’VN offers the possibility of adding an HTML Element with the same dimensions as the PixiJS Canvas to add an interface with JavaScript frameworks.
By "Interface" is meant the elements that are above the canvas, such as buttons, forms, etc.
This allows the use of systems such as React, Vue, Angular, etc. to create much more complex interface screens with excellent performance.
Interface and canvas are two different things. The interface is above the canvas and is used to create buttons, forms, etc. The canvas is used to display images, videos, etc.
All canvas information is included in saves and Pixi’VN manages going back and forth between the different steps. The interface is not included in the saves and is not managed by Pixi’VN, so you have to manage it yourself saving information you care about in game storage or browser storage.
In the canvas you can add elements during each step. In the interface you can't do that, you can create several "screens" and navigate between them.
In the canvas you can only add PixiJS elements, they are usually composed of images and are very simple. In the interface you can add any HTML element or use any UI component library, so you can create much more complex interfaces.
To switch between interface screens (without interrupting the canvas), you can use popups and modals, or navigate between different paths/routes.
The URL Path is the part of the URL that comes after the domain. For example, in the URL https://example.com/path/to/page
, the path is /path/to/page
.
A routering system can be used to manage navigation between URL Paths. For example you can use:
By default, all HTML elements of the interface have the pointer-events: none
style.
The reason is that because the html interface is above the canvas, all clicks are intercepted by the interface and not by the canvas.
So you must set the pointer-events: auto
style only for the elements (example a button, a form, etc...) that you want to interact with the user.
What is an Atom? An Atom (or Atomics) is a JavaScript object which gives atomic tasks to proceed as static strategies. Much the same as the strategies for Math object, the techniques, and properties of Atomics are additionally static. Atomics are utilized with SharedArrayBuffer objects.
There are more npm packages that can be used to manage the interface with the game storage, such as: Recoil, Redux, MobX, etc. We will use Recoil because it has "Selectors" which is right for us.
The purpose of the Atom + Selector will be to set the Atom and game storage to a value changed by the interface, and update the Atom when the value in game storage is updated.
Here is an example:
In our example we would have a variable text
saved in the game storage, this variable will be updated either by an input in the interface or by a label step.
Taking into account that a storage variable can only be changed during a next step, go back, run label or loading a save (outside the interface), we will create an Atom called "reloadInterfaceDataEventAtom" that will be updated after each next step, go back, run label or loading a save. reloadInterfaceDataEventAtom
will be used to trigger the update of olther Atoms.
// reloadInterfaceDataEventAtom.ts
import { atom } from "recoil";
export const reloadInterfaceDataEventAtom = atom<number>({
key: 'reloadInterfaceDataEventAtom',
default: 0,
});
We will create a Selector called textSelector
that will be used to set and get the value of the text
variable in the game storage.
// textSelectorState.ts
import { storage } from "@drincs/pixi-vn";
import { atom, selector } from "recoil";
import { reloadInterfaceDataEventAtom } from "./reloadInterfaceDataEventAtom";
// questo atom attiverà l'aggiornamento di textSelectorState quando modificherò il valore di textSelectorState
const textAtom = atom<string>({
key: 'textAtom',
default: "",
});
export const textSelectorState = selector<string>({
key: 'textSelector',
get: ({ get }) => {
// This will trigger the update of the Atom when the value in game storage is updated
get(reloadInterfaceDataEventAtom)
get(textAtom)
return storage.getVariable<string>("text") || "";
},
set: ({ set }, newValue) => {
set(textAtom, newValue);
storage.setVariable("text", newValue as string);
},
});
Remember to update the reloadInterfaceDataEventAtom
after each next step, go back or loading a save.
Even after use run label out of the step label.
This seems like a very time consuming process, but in reality if you have designed your game well or used a template, you will only need to update the Atom in one place.
import { useSetRecoilState } from 'recoil';
import { reloadInterfaceDataEventAtom } from './reloadInterfaceDataEventAtom';
const notifyReloadInterfaceDataEvent = useSetRecoilState(reloadInterfaceDataEventAtom);
narration.goNext({})
.then((result) => {
notifyReloadInterfaceDataEvent((value) => value + 1);
});
narration.goBack({})
.then((result) => {
notifyReloadInterfaceDataEvent((value) => value + 1);
});
loadSaveJson(jsonString, navigate)
.then(() => {
notifyReloadInterfaceDataEvent((value) => value + 1);
})
// only if you are not in a label step
narration.callLabel("myLabel", {})
.then((result) => {
notifyReloadInterfaceDataEvent((value) => value + 1);
});
Ok now we have the Atom and Selector, we can use the textSelector
in the interface.
// react example
import { useRecoilState } from 'recoil';
import { textSelectorState } from './textSelectorState';
import { Input } from '@mui/joy';
export function MyComponent() {
const [text, setText] = useRecoilState(textSelectorState);
return (
<Input
sx={{
pointerEvents: "auto",
}}
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
}