Pixi’VN

UI変数

TanStack Query/Store を使ってUIコンポーネントを設定、読み取り専用のゲームデータ、読み書き可能なゲームストレージ変数に接続する方法、および storage.setStorageHandler を使ってUIをストレージと同期させ続ける方法。

Pixi’VN のUIでは、表示または編集する変数は基本的に3つのカテゴリーに分類され、それぞれに推奨されるパターンが異なります。 Pixi’VN のテンプレートでは、src/lib/stores/ と src/lib/query/ の随所でこれらのパターンがすでに使われています — 実際に動作する例として開いてみる価値があります。

設定変数

テキスト速度、フォントサイズ、オートフォワードの遅延時間などは、ゲームストレージの一部ではありません。プレイスルーをまたいで(セーブが存在する前でも)永続化する必要があるため、localStorage に保存され、コンポーネントが変更に反応できるよう TanStack Store にミラーリングされます。

src/lib/stores/auto-settings-store.ts
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つのハンドラーを使って、一度だけ設定してください。これはゲーム全体のすべてのストレージ書き込みのたびに再実行されるため、個別に対象を絞った多数のハンドラーを散りばめるよりも、この広範囲で一元化されたハンドラーを使う方が望ましいです — 挙動を把握しやすく、互いに上書きし合うリスクもありません。

このページの内容