NarraLeaf

スクリプト

スロットのロジックをプロジェクトが所有する TypeScript のファイルとして書く——ファイルの置き場所、スクリプトへの入り口、そして届く範囲

ページ、コンポーネント、ストーリーの行のロジックはレイヤーの並びで、各レイヤーはブループリント——キャンバス上のグラフ——か、スクリプト——プロジェクトが所有する TypeScript のファイル——のどちらかです。並びにあるレイヤーはすべて動くので、同じ場所でスクリプトとグラフが隣り合い、両方が同じイベントに応えます。

スクリプトはブループリントの一種ではなく、この2つの語が互いを修飾することもありません。ブループリントはグラフであり、Studio のキャンバス上で編集します。スクリプトはディスク上のファイルであり、作者が自分で選んだエディターで編集します。キャンバスの隣のレイヤー一覧は、各レイヤーがどちらを持つかを示します。

ファイルの置き場所

<project>/scripts/ は、プロジェクトの中で唯一 Studio が所有しないディレクトリです。この中ではディスクが正本です——Studio は読み取りと監視だけを行い、書き戻すための複製を持つことは決してありません。この外側では Studio が正本です。

Studio がこのディレクトリの中で使う名前はちょうど2つで、そのほかに1つのファイルを生成します。

scripts/ の中の名前所有者内容
.narraleaf/Studio生成された型宣言。
node_modules/作者作者自身がインストールしたもの。
tsconfig.jsonStudio生成されたファイル。編集しないよう伝えるヘッダーが付いています。
package.json作者依存関係の一覧として読むだけで、書き込むことはありません。
その他すべて作者作者が自由に並べます。スクリプトは scripts/title.ts、フォルダーに分けたければ scripts/menus/title.ts です。

Studio はパッケージマネージャーを実行しない

インストールは依存パッケージの postinstall スクリプトを実行してしまいます。「ビルドはサードパーティのコードを1行も実行しない」は、この機能が手放さない保証です。Studio はディスクにすでにあるものをバンドルするだけで——esbuild はそのバイト列を読むのであって、実行はしません——足りない依存関係は、実行すべきコマンドを示す診断として報告されます。

TypeScript は必須ではない

.ts.js もスクリプトのソースです。esbuild は型を取り除くだけで、型検査は一切しません。つまり .js のファイルは、型検査を辞退したスクリプトです。この2つを見分けるのは拡張子だけで、ほかに違いはありません。

スクリプトの追加と削除

ブループリントエディターでは、レイヤー一覧の上の新規がレイヤーを1つ追加し、どちらであるかを尋ねます。スクリプトを選ぶと scripts/ にある全ファイルが並び、先頭は新規スクリプト…、残りはそれぞれ何がすでに動かしているかを示します——1つのファイルを2つのレイヤーが共有するのは誤りではなく、まっとうな使い方です。

**新規スクリプト…**は、埋めるスロットの名前を付けた開始ファイルを1つ書き出し——Key art という名前のコントロールなら scripts/key-art.ts——それ以降このファイルを書き直すことはありません。すでにあるファイルを選んだ場合は、ファイルを1つも書きません。

レイヤーはスロットから削除できます。ファイルはディスクに残り——Studio が書いたのは一度きりで、その時点からプロジェクトのものです——その後はスクリプトの区画に、どこからも使われていないファイルとして並びます。

スクリプトレイヤーは scripts/ の別のファイルに指し直すこともでき、入り口はその行の別のファイルを使うです。作者が自分のエディターで名前を変えたファイルは、こうしてつなぎ直します。

編集

スクリプトレイヤーを選ぶと、グラフレイヤーならキャンバスが出る場所に、そのソースが読み取り専用で表示されます。Studio 自身のエディターはありません。ディスクが所有するファイルの上にもう1つエディターを置くことは、書き込み手を2つ持つことだからです。

エディターで開くscripts/ フォルダー全体を開き、その中でそのファイルを選択した状態にします。これはどの対象でも同じです——検出されたエディター、ファイルマネージャー、あるいはシステムの関連付け。ファイルではなくフォルダーを開くのは、型がスクリプトの隣にある tsconfig.json.narraleaf/ から解決されるためで、1つのファイルだけを開いてもそれらは1つも解決されません。

見つけ方

アセットパネルスクリプトの区画には、scripts/ にあるすべてのソース、それぞれをどのロジックが動かすか、そしてどれがどこからも使われていないかが並びます。

これはアセットの分類ではありません。スクリプトには id もメタデータもなく、アセットセットに入る場所もありません。

スクリプトへの入り口

スクリプトには、エクスポートした関数から入ります。エクスポート名はイベント名から1つの規則で決まります——mouseClick なら onMouseClick です。

この位置がどの名前を呼ぶかは、それがどこに置かれているかで決まり、開始ファイルの冒頭2行にその一覧があります。

ロジックのある位置呼ばれるエクスポート名(一部)
プロジェクトonAppBootonGameReadyonKeyDownonKeyUponPreferenceChangedonFullscreenChangedonWindowFocusChangedonWindowCloseRequestedonAction
ページonSurfaceInitonSurfaceUnmountonBeforeSurfaceExitonAfterSurfaceEnteronBroadcastonElementClick
ウィジェットonInitonUnmountonFlushonMouseClickonMouseEnteronMouseLeave、およびその型の残りのイベント

その位置が呼ばない名前でエクスポートされたものは、単に一度も呼ばれません。

ストーリーの行

ストーリーの行は例外です。行にはイベントがないため、デフォルトエクスポートから入ります。

アクションは await できます。インラインの値と分岐の条件は、ストーリーが待てない場所で評価されるため、値を待つのではなく値を返します。どちらかが promise を返した場合、それは描画されるのではなく、診断とともに拒否されます。

型はスクリプトの隣に生成された宣言から来ます。@narraleaf/script から import type で読み込むため、ビルドが解決すべきパッケージを探すことはありません。

コンテキストの型渡される先
GlobalCtxプロジェクトのスクリプト
SurfaceCtxページのスクリプト
WidgetCtx<W>ウィジェットのスクリプト
ComponentWidgetCtx<W>コンポーネント内のウィジェットのスクリプト
StoryCtxストーリーのアクション
StorySyncCtxインラインの値、または分岐の条件

ハンドラーはそれぞれ自分の型を明示します。

import type { WidgetCtx, ScriptEvent } from "@narraleaf/script";

export function onMouseClick(ctx: WidgetCtx<"nl.button">, event: ScriptEvent<"mouseClick">): void {
    ctx.host.devtools.log("info", "clicked");
}

スクリプトにできること

スクリプトにできることは、そのスロットにできることです。コンテキストが与える能力は、同じスロットをグラフで書いたときに与えられるものとまったく同じで、スクリプト独自のものは1つもありません。

  • ページとウィジェットのスクリプトは、ホスト API、自分自身の vars ストア、AbortSignal、そしてスロットが Surface 上にある場合は broadcast と Surface の遷移状態のリーダーを受け取ります。
  • ストーリーのスクリプトが受け取るのは、ストーリー自身のシーン変数とセーブ変数、アプリケーションの永続化、そして 1 行のログを書くための ctx.devtools です。この段には ctx.host がないため、触れられるものはコンテキストに直接並びます。それ以外はありません。ナビゲーションもゲームの制御もなく、ウィジェットにも触れません。

Blueprint Value

Blueprint Value はブループリントだけです。値バインディングは依存が変わるたびに再評価され、必ず値を返さなければならないため、このスロットはスクリプトを提供しません。

うまくいかないとき

コンパイルに失敗したファイルと、この位置が呼ぶ名前を1つもエクスポートしないファイルは、どちらも Dev Mode で、問題のあるファイルに対して報告されます。

Dev Mode のブループリント一覧は、スロットが動かしているファイル名、モジュールが読み込まれたかどうか、そしてそのファイルが何をエクスポートしているかを、この位置が実際に呼ぶ名前と並べて示します。これが「なぜ何も起きなかったのか」の答えのすべてです。コンパイルの失敗と、onClik と綴られたハンドラーは、ほかの場所からは見分けがつかないからです。

スクリプトは、ほかのロジックと同じようにビルドへコンパイルされます。開発時にしか使えない部分はありません。

このページの目次