Pixi'VN Minigames
SkillDev toolsUse when adding a minigame (snake, quiz, puzzle, timing/reaction game, ...) to a Pixi'VN visual novel — the `useMinigame` lifecycle hook pattern, using PixiJS directly on its own layer instead of `gameLayer`, hotkeys for controls, linking the minigame to a route, and why saving/restoring minigame state is the developer's own responsibility.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Pixi'VN Minigames skill
What this skill tells your AI
The instructions your AI receives, as published by drincs-productions/pixi-vn in skills/minigames/SKILL.md and read by ahel’s review.
Official docs: pixi-vn.com/start/minigames.
Pixi'VN has no dedicated minigame API — a minigame is just PixiJS code (and, for its HUD/UI, a regular framework UI) wired into the game through the same primitives used elsewhere: canvas layers, hotkeys, and navigation. This skill is about how those pieces fit together for a minigame specifically, not about introducing new APIs.
When to use this skill
Load this skill whenever a task involves building or wiring in a minigame: a snake/arcade-style
game, a timing/reaction challenge, a puzzle, a quiz mini-mode, or anything else the player breaks
away from the main narration to play. Not for the narrative canvas itself (pixi-vn-canvas) or for
general app UI screens unrelated to a minigame (pixi-vn-ui).
Core mental model
- A minigame renders on its own PixiJS layer, not on
canvas.gameLayer.gameLayeris the save-able narrative scene graph (backgrounds, characters — seepixi-vn-canvas); a minigame's moving pieces (snake segments, falling blocks, ...) are transient and don't belong there. Usecanvas.layers.add(name, new Container())to get a separate, non-save-able layer for the minigame, exactly like a PixiJS UI layer (pixi-vn-ui) — the official template even reserves a constant for it,CANVAS_MINIGAME_LAYER_NAME. - The minigame's HUD/menus (score, game-over overlay, control buttons) should be regular UI, not
PixiJS — built with React/Vue/whatever the project's UI already uses (
pixi-vn-ui), positioned over the canvas the same way any other UI is. - A minigame is launched from the narrative, then the player returns to it. The recommended
approach is to link the minigame to its own route (e.g.
/minigame/snake) and navigate to it from a step, then navigate back when it ends — seepixi-vn-ui's "Navigating between UI screens" section and interface-navigate. Every minigame example below is written as a route file for this reason, not a bare component. - Saving and restoring the minigame's current state (score, board, position, ...) is entirely the
developer's responsibility.
Game.exportGameState()does not know about a minigame in progress — seepixi-vn-saves. If a minigame's progress must survive a save/reload, persist it yourself (e.g. via game storage, seepixi-vn-storage).
Routing structure: one route per minigame, plus a shared layout route
With file-based routing (e.g. TanStack Router, used by the
official templates), each minigame gets its own file under src/routes/minigame/, so a project with
several minigames ends up with /minigame/snake, /minigame/quiz, etc.:
import { createFileRoute } from "@tanstack/react-router";
import SnakeGame from "@/screens/minigames/SnakeGame";
export const Route = createFileRoute("/minigame/snake")({
component: SnakeGame,
});
Anything shared by every minigame (pausing narration/audio while one is open, common hotkeys, a
shared HUD wrapper) doesn't belong copy-pasted into each of those files. Factor it into a single
pathless layout route, src/routes/minigame.tsx, that renders an <Outlet /> for whichever
minigame route is active — the same pattern the official React template already uses for /game
(src/routes/game.tsx wraps every /game/* screen with usePauseGameWhenMenuIsOpen +
useGameHotkeys + <Outlet />):
import { createFileRoute, Outlet } from "@tanstack/react-router";
export const Route = createFileRoute("/minigame")({
component: MinigameLayout,
});
function MinigameLayout() {
// hooks/providers shared by every minigame go here
return <Outlet />;
}
What actually goes in src/routes/minigame.tsx is a judgment call to make while building the
first minigame, not a file to scaffold reflexively — work through these questions before adding
anything to it:
- Is there actually anything shared across minigames? If the game's settings menu, pause menu,
or quick-actions wheel aren't reachable while a minigame is open, and there's no common
hotkey/HUD/provider every minigame needs,
src/routes/minigame.tsxhas nothing to do — leave it empty (just the<Outlet />) or skip creating it at all and let each minigame route stand alone. - Are there menus specific to minigames only (a minigame pause menu, a "quit minigame and return
to story" confirmation, a minigame-specific settings panel)? If so, that's exactly what belongs in
the shared layout, the same way
src/routes/game.tsxcentralizesusePauseGameWhenMenuIsOpenanduseGameHotkeysfor every/game/*screen. - Should the same menus as
src/routes/game.tsx(settings, save/load, quick actions) be reachable from inside a minigame? If yes, don't assume they behave identically — check case by case whether each one still makes sense mid-minigame. Saving is the clearest example:Game.exportGameState()knows nothing about in-progress minigame state (see the Gotchas section below), so a save triggered from inside a minigame either needs to be disabled/hidden there, or the minigame needs to persist its own state first (pixi-vn-storage) so the save is still meaningful. Don't wire in the game's menus unchanged and assume they "just work" inside a minigame.
The useMinigame lifecycle hook
Every official template ships a useMinigame hook (e.g. src/lib/hooks/minigame-hooks.ts) that
handles the repetitive part of wiring a minigame into a layer: creating the layer once, running an
optional async onStart before the game begins, calling back into your game(layer) function, and
cleaning the layer up (plus your onExit) when the screen unmounts.
import { CANVAS_MINIGAME_LAYER_NAME } from "@/constants";
import { canvas, type Layer } from "@drincs/pixi-vn";
import { Container } from "pixi.js";
import { useEffect, useRef } from "react";
export function useMinigame(
game: (layer: Layer) => void,
props?: {
onStart?: () => Promise<void>;
onExit?: (layer: Layer) => void;
},
) {
const loading = useRef(false);
useEffect(() => {
loading.current = true;
const layer = canvas.layers.add(CANVAS_MINIGAME_LAYER_NAME, new Container());
if (!layer) {
console.error("Failed to create UI layer for minigame");
return;
}
let cancelled = false;
(props?.onStart ?? (async () => {}))().then(() => {
if (cancelled) return;
loading.current = false;
game(layer);
});
return () => {
cancelled = true;
canvas.layers.remove(CANVAS_MINIGAME_LAYER_NAME);
props?.onExit?.(layer);
};
}, [game]);
return { loading };
}
Usage inside the minigame's own route file:
import { Layer } from "@drincs/pixi-vn";
import { Graphics, Ticker } from "@drincs/pixi-vn/pixi.js";
import { createFileRoute } from "@tanstack/react-router";
import { useCallback, useMemo, useState } from "react";
import { useHotkey } from "@tanstack/react-hotkeys";
import useMinigame from "@/lib/hooks/minigame-hooks";
export const Route = createFileRoute("/minigame/example")({
component: MiniGame,
});
function MiniGame() {
const [score, setScore] = useState(0);
const [gameOver, setGameOver] = useState(false);
const ticker = useMemo(() => {
const ticker = new Ticker();
ticker.add(({ deltaMS }) => {
// update game logic; call setGameOver(true) + ticker.stop() to end
});
return ticker;
}, []);
useHotkey(
"ArrowUp",
() => {
/* handle input */
},
{ enabled: !gameOver },
);
const game = useCallback(
(layer: Layer) => {
ticker.start();
},
[ticker], // must NOT change during the game or it restarts
);
const options = useMemo(
() => ({
onExit() {
ticker.stop();
ticker.destroy();
},
}),
[ticker], // must NOT change during the game or it restarts
);
useMinigame(game, options);
return (
<>{/* score HUD, game-over overlay, control buttons — regular UI */}</>
);
}
game(layer)is where you build the actual PixiJS content (Graphics,Sprite,Text, aTicker) and add it to the givenlayer. It runs once, afteronStartresolves.onExit(layer)is where you tear down anythinggamestarted — stop anddestroy()anyTicker, remove event listeners.useMinigamealready removes the layer itself.gameand theoptionsobject (specifically itsonExit) must keep a stable identity across re-renders (useCallback/useMemowith the right deps) — since they're the effect's dependency, changing identity mid-game tears down and restarts the whole minigame.
Controls and rendering
- Use hotkeys for keyboard input (see
pixi-vn-ui/hotkeys) rather than rawwindow.addEventListener("keydown", ...), and gate them withenabled: !gameOverso input stops mounting a listener that outlives the round. - Build the actual gameplay visuals with PixiJS directly — either the
pixi.jspackage or the@drincs/pixi-vn/pixi.jsre-export — usingGraphics/Sprite/Textand aTickerfor the game loop, same primitives aspixi-vn-canvasbut on the dedicated minigame layer instead ofgameLayer. - On-screen touch controls, score displays, and game-over messages should still be plain UI
components (buttons, divs) layered over the canvas with
pointerEvents: "auto"where they need to be clickable — seepixi-vn-ui's "Enabling interaction" section.
Gotchas
- Adding game content to
canvas.gameLayerinstead of a dedicated layer makes it save-able and mixes it with narrative elements — usecanvas.layers.addfor the minigame instead (pixi-vn-canvashas the fullgameLayervs. layer distinction). - An unstable
game/onExitreference restarts the minigame on every render — see theuseCallback/useMemonote above; this is the single most common bug when porting the pattern. - Nothing about minigame progress is captured by
Game.exportGameState()— a mid-round save/ reload will silently drop it unless you persist state yourself through game storage (pixi-vn-storage/pixi-vn-saves). - Forgetting to stop/destroy the
TickerinonExitleaves the game loop running (and potentially erroring against removed objects) after the player navigates away.
Related skills
- pixi-vn-canvas: the PixiJS rendering primitives (
Graphics,Sprite,Text, tickers,canvas.animate) a minigame is built from, and thegameLayer/layer distinction. - pixi-vn-ui:
canvas.layers.add/htmlLayers.add, hotkeys, and navigating between UI screens/routes — how the minigame's HUD and route linkage actually work. - pixi-vn-storage: persisting minigame progress (score, board state) in game storage if it needs to survive a save/reload.
- pixi-vn-saves: confirms
Game.exportGameState()has no built-in notion of in-progress minigame state.
Signals
- GitHub stars
- 145
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
pixi-vn-minigames- Source
- github.com/drincs-productions/pixi-vn