UI変数
TanStack Query/Store を使ってUIコンポーネントを設定、読み取り専用のゲームデータ、読み書き可能なゲームストレージ変数に接続する方法、および storage.setStorageHandler を使ってUIをストレージと同期させ続ける方法。
Pixi’VN のUIでは、表示または編集する変数は基本的に3つのカテゴリーに分類され、それぞれに推奨されるパターンが異なります。 Pixi’VN のテンプレートでは、src/lib/stores/ と src/lib/query/ の随所でこれらのパターンがすでに使われています — 実際に動作する例として開いてみる価値があります。
設定変数
テキスト速度、フォントサイズ、オートフォワードの遅延時間などは、ゲームストレージの一部ではありません。プレイスルーをまたいで(セーブが存在する前でも)永続化する必要があるため、localStorage に保存され、コンポーネントが変更に反応できるよう TanStack Store にミラーリングされます。
import { Store } from "@tanstack/store";
type AutoSettingsStore = {
enabled: boolean;
time: number;
};
export namespace AutoSettings {
export const store = new Store<AutoSettingsStore>({
enabled: Boolean(localStorage.getItem("auto_forward_enabled") ?? false),
time: Number(localStorage.getItem("auto_forward_second") ?? 1),
});
export function setEnabled(value: boolean) {
localStorage.setItem("auto_forward_enabled", value.toString());
store.setState((state) => ({ ...state, enabled: value }));
}
export function setTime(value: number) {
localStorage.setItem("auto_forward_second", value.toString());
store.setState((state) => ({ ...state, time: value }));
}
}import { useSelector } from "@tanstack/react-store";
import { AutoSettings } from "@/lib/stores/auto-settings-store";
function AutoForwardToggle() {
const enabled = useSelector(AutoSettings.store, (state) => state.enabled);
return (
<Switch checked={enabled} onCheckedChange={AutoSettings.setEnabled} />
);
}読み取り専用のゲーム変数
表示するだけでよい変数(ステータス、フラグ、セリフの一部など)については、TanStack Query の queryFn 内でゲームストレージから直接読み取ります。
import { useQuery } from "@tanstack/react-query";
import { storage } from "@drincs/pixi-vn";
export function useQueryAffection() {
return useQuery({
queryKey: ["affection_use_query_key"],
queryFn: async () => storage.get<number>("affection") ?? 0,
});
}ゲームストレージの変数が変化するのは、ナラティブステップ(step)/ go backの間、ナラティブノード(label)の実行時、またはセーブの読み込み時のみです — Pixi’VN は、そのデータに依存するクエリがあるかどうかを知る方法がないため、UI側でこれらのイベントの後にTanStack Queryに再取得を依頼する必要があります。各クエリキーを1つずつ無効化するよりも、いくつかの呼び出し箇所(narration.continue()/goNext の後、stepHistory.back()、narration.call()/jump、およびセーブの復元後)でまとめて一括で無効化する方がシンプルです。
const queryClient = useQueryClient();
narration.continue({}).then(() => {
queryClient.invalidateQueries();
});読み書き可能なゲーム変数
UI自体が変更できる変数(選択されたオプション、クエストフラグに紐づくトグルなど)については、設定用と同じTanStack Storeラッパーを使用しますが、localStorage の代わりにゲームストレージを裏側に使うことで、値がセーブと go back を越えて保持されるようにします。
import { storage } from "@drincs/pixi-vn";
import { Store } from "@tanstack/store";
const SELECTED_QUEST_KEY = "selectedQuestId";
export namespace Memo {
export const store = new Store<{ selectedQuestId: string | undefined }>({
selectedQuestId: storage.get<string>(SELECTED_QUEST_KEY),
});
export function setSelectedQuestId(id: string | undefined) {
storage.set(SELECTED_QUEST_KEY, id);
store.setState((state) => ({ ...state, selectedQuestId: id }));
}
}セッターがStoreを直接更新するため、この同じセッターを通じて行われた変更についてはUIが自動的に同期されます — 手動でのリフレッシュは不要です。
他の場所で行われたストレージの変更にUIを同期させ続ける
上記のStoreパターンは、UI自体が storage.set を呼び出している場合にのみUIを同期させます。ナラティブノード(label)やナラティブステップ(step)、あるいはゲームの他の部分が同じゲームストレージ変数を変更した場合、そのStore — またはそのキーを読み取る useQuery — に更新を促す仕組みはありません。
すべてのゲームストレージへの書き込みを1か所で捕捉するには、storage.setStorageHandler を使用します。これにより、変数が設定されたとき、削除されたとき、または一時変数の有効期限が切れたときに発火するコールバックを登録できます — UIからだけでなく、ゲーム内のどこからでも対象になります。
import { storage } from "@drincs/pixi-vn";
storage.setStorageHandler({
onSetVariable: (key, value) => {
queryClient.invalidateQueries();
},
onRemoveVariable: (key) => {
queryClient.invalidateQueries();
},
onClearOldTempVariable: (key) => {
queryClient.invalidateQueries();
},
});一度に1つのハンドラーのみ
setStorageHandler はハンドラーを積み重ねません — 内部的に単一のハンドラーのみを保持し、呼び出すたびに前のものを置き換えます。複数のファイルから呼び出した場合、実際に実行されるのは最後に登録されたものだけです。それ以前のものは何の警告もなく発火しなくなります。アプリ起動時に近い1か所(例えばルートプロバイダーなど)で、UIが必要とするすべてのこと(クエリの無効化、Storeの更新など)を行う1つのハンドラーを使って、一度だけ設定してください。これはゲーム全体のすべてのストレージ書き込みのたびに再実行されるため、個別に対象を絞った多数のハンドラーを散りばめるよりも、この広範囲で一元化されたハンドラーを使う方が望ましいです — 挙動を把握しやすく、互いに上書きし合うリスクもありません。
アプリ起動時に近い1か所(例えばルートプロバイダーなど)で、UIが必要とするすべてのこと(クエリの無効化、Storeの更新など)を行う1つのハンドラーを使って、一度だけ設定してください。これはゲーム全体のすべてのストレージ書き込みのたびに再実行されるため、個別に対象を絞った多数のハンドラーを散りばめるよりも、この広範囲で一元化されたハンドラーを使う方が望ましいです — 挙動を把握しやすく、互いに上書きし合うリスクもありません。