NarraLeaf

変数

値がどこに存在し、どのくらい生き続け、どのスコープを選ぶべきか

プロジェクト内のすべての値はどこかに存在しなければならず、その選択は実質的に寿命の選択です——それが数えている画面と運命を共にするカウンター、プレイヤーのセーブに乗って運ばれなければならないフラグ、あらゆるプレイスルーより長生きしなければならない設定。スコープは4種類あり、間違ったものを選ぶことが「値が消えた」の最も多い原因です。

ピンごとのシグネチャは変数ノードにあります。このページで扱うのは、どのスコープを選ぶべきか、そしてその後値がどうなるかです。

4つのスコープ

スコープ宣言方法値の保存先いつ振り出しに戻るか
ブループリント変数グラフ内の Var ノードメモリ。オーナーインスタンスごとに1つの storeそのオーナーインスタンスが消えたとき
シーン変数シーン内の /localセーブファイル。そのシーンの Scene.local としてそのシーンに入ったとき
セーブ変数ストーリー内の /saveセーブファイル。1つの Storable 名前空間として新しいゲームが始まったとき
持続変数ブループリントエディタのメンバーパネル、または /globalプロジェクトの外にあるホストストレージ。storageKey をキーとする決して戻らない——あなた自身が消さない限り

ブループリント変数

ブループリントのどこかに Var ノードを1つ置くと、それはブループリント全体に対して変数を宣言したことになります。Var はピンを持たず、決して実行されません——ランタイムは最初のイベントが発火する前に、ブループリント内のすべてのグラフ(event、function、macro を問わず)を走査し、見つけた Var はそれぞれそのブループリントの store 内の1つのスロットになります。NameData typeDefault を与えてください。widgetValue を除くすべてのオーナーがこれを宣言できます。

値は、グラフにでもイベントチェーンにでもなく、オーナーインスタンスに紐づく store に存在します。同じボタンを2回クリックしても、読まれるのは同じカウンターです。何がインスタンスとみなされるかは、オーナーによって異なります。

オーナーstore の単位何が振り出しに戻すか
globalMainグローバルブループリントアプリケーションが実行されている間は何も起きない
surfaceMainその Surface がナビゲーションスタックにプッシュされるたびにその Surface を離れて再び開いたとき
widgetMain · componentWidgetMainウィジェットインスタンス——リストで繰り返される項目もそれぞれ独自のインスタンスそのウィジェットがアンマウントされたとき
sharedAsset共有ブループリントアプリケーションが実行されている間は何も起きない
storyActionグラフの1回の呼び出し呼び出されるたびに

Get VarSet Var は常に同じ順序で3つのグループを提供します——Page(このグラフが属する Surface の surfaceMain ブループリント)、Blueprint(現在編集しているもの)、GlobalglobalMain ブループリント)。すでに他のいずれかと同じブループリントは、2回ではなく1回だけ表示されます。名前は各グループ内でソートされ、スコープラベルは、アクセス可能な2つの変数が同じ名前を共有している場合に限り名前の横に表示されます——そのため通常のリストはすっきりしたままで、本物の衝突は見逃しようがありません。

グラフを編集しても、実行中の値は消えません。store が再取得されるとき、新しく宣言された変数はデフォルト値で初期化され、もはや宣言されていない変数は破棄され、それ以外のすべてのスロットは保持していた値をそのまま保ちます。

ブループリント変数はセーブに書き込まれることが決してありません。1つのオーナーインスタンスが存続する間だけ値を保持し、それ以上は一瞬たりとも保持しません——セーブとロード、新しいゲーム、ウィンドウを閉じること、そのどれを経ても何も生き残りません。値がプレイヤーとともに戻ってこなければならないなら、それはシーン変数、セーブ変数、または持続変数の役目です。

シーン変数

シーン変数は1つのストーリーシーンに属し、NarraLeaf の Scene.local に裏打ちされます。シーン内の1行として宣言してください。その行が変数そのものです——分岐の中にネストされた宣言でも、シーン全体に対して宣言されたことに変わりはなく、その行を無効化しても宣言が取り消されることはありません。宣言は実行される文ではないからです。

Scene.local はそのシーンに入るたびにクリアされ、宣言されたデフォルト値が入場時に再適用されます。つまりシーン変数はセーブファイルに含まれ、シーンの途中で取ったセーブをロードすれば戻ってきますが、そのシーンにもう一度歩いて入ると、デフォルト値から始まり直します。

セーブ変数

セーブ変数はどの1つのシーンにも属さずストーリードキュメントに属し、そのストーリー内のすべてのシーンから見えます。宣言済みのデフォルト値すべてを持って作成される単一の Storable 名前空間に裏打ちされており、新しいゲームはそれらのデフォルト値から始まり、ロードするとセーブが保持していた内容が復元されます。

セーブ変数と持続変数の値はシリアライズ可能でなければなりません——function、symbol、bigint の書き込みは、読み戻せないセーブを黙って生成する代わりに、実行時に拒否されます。

持続変数

持続変数はプロジェクトレベルです——1つの定義があらゆるブループリントとあらゆるストーリーから見え、その値はセーブファイルの外に完全に存在します。言語選択、「イントロを見たかどうか」、ギャラリーの解放などがこれにあたります。

定義はプロジェクトの変数レジストリ(editor/variables.json)に存在し、ブループリントエディタのメンバーパネルにある Persistent variables グループから作成します。そこでの編集は、編集中のブループリントと同じ undo スタックに乗るため、Ctrl+Z を1回押すだけで変数の変更とノードの変更が一緒に取り消されます。レジストリは Dev Mode バンドルとゲームパックに焼き込まれて運ばれます——ランタイムはそれを読むだけで、決して書き込みません。

値は、その項目の storageKey をキーとするホスト管理のストレージに存在し、決してプロジェクトフォルダの中には入りません。作業中は Studio 自身のユーザーデータ内にプロジェクトごとのキーで保存され、出荷済みのゲームではそのゲームのユーザーデータ内に保存されます。プロジェクトを別のマシンにコピーしても、これらは一緒には運ばれません。

Get PersistentSet Persistent は latent です——ホストへの読み書きが非同期だからです。これは、これらが function グラフに置けない理由でもあります。Get Persistent は、そのキーの下に一度も何も保存されていない場合は編集済みのデフォルト値にフォールバックし、そのディープコピーを渡します。そのため、読み取ったものを変更しても定義そのものを編集することはできません。

storageKey は一度だけ発行され、変数名を変更しても変わりません——これがプレイヤーの保存済みの値をリネーム後も解決できるようにしている理由です。裏を返せば、持続変数を削除しても、そのキーの下にすでに保存されている値は削除されません。同じ名前で作り直した代わりの変数は新しいキーを得て、デフォルト値を読むことになります。削除して作り直すのではなく、リネームしてください。

どのスコープを選ぶか

上から順に読み、最初に当てはまった行で止まってください。

  • その値がこの画面、ウィジェット、または action が生きている間しか意味を持たない → ブループリント変数
  • それが、あるシーン内でプレイヤーがどこに立っているかを表す → シーン変数
  • ロードでそれを失うとプレイヤーが不満に思う → セーブ変数
  • プレイヤーが持つすべてのセーブファイルを横断して保持されなければならない → 持続変数

用が足りる中で最も狭いスコープを選んでください。広いスコープはただではありません——セーブ変数はどれもすべてのセーブファイルを肥大化させ、持続変数はどれも、その意味が変わった日に手作業で移行しなければならない値になります。

何が生き残るか

スコープセーブとロード新しいゲームプロジェクトの再構築
ブループリント変数消える——セーブに書き込まれたことがない消える何も保存されていなかった
シーン変数セーブとともに復元され、そのシーンに次に入るときに再びリセットされる宣言されたデフォルト値に戻る既存のセーブはそれぞれの値を保つ
セーブ変数セーブとともに復元される宣言されたデフォルト値に戻る既存のセーブはそれぞれの値を保つ
持続変数影響を受けない——セーブの中にはない影響を受けないstorageKey が変わらない限り保持される

Var は9種類の型を提供します。選んだ型によって、カード上の Default エディタと、Get Var / Set Var に投影される型が決まります。

valueTypeデフォルト備考
string""
integer0float または string 入力に接続できる
float0string 入力に接続できる
booleanfalse
json{}array を接続元として受け付ける
array[]json 入力に接続できる。json は逆に接続できない
TimernullDelay が返すトークンを保持する
AnimationTokennullアニメーションが返すトークンを保持する
anynullDefault フィールドは無効化されている——下記を参照

数値から string への接続は、値が読まれるときに変換されます。それ以外の許可された組み合わせはすべて、完全一致か拡大のどちらかです——あなたの知らないところで縮小されることはありません。

シーン変数、セーブ変数、持続変数は、より狭い閉じた集合——booleannumberstringjson——を使います。これらの値はセーブファイルやホストストレージにシリアライズされるためで、動作中の Timer をシリアライズする先はどこにもないからです。

デフォルト値

デフォルト値は、文字列化された JSON としてではなく、本物の JSON 値として保存されます。{"hp": 3} という json のデフォルト値はドキュメント内ではオブジェクトであり、array のデフォルト値は配列です。これにより、エディタは入力したリテラルを型チェックでき、ランタイムは間にパース処理を挟まずそれをそのまま渡せます。

デフォルト値は store が作成されるときにディープクローンされます。すべてのオーナーインスタンスは自分自身のコピーから始まるため、実行時に json 変数へ書き込んでも、編集済みのデフォルト値まで遡って書き換えることは決してなく、1つのテンプレートを共有する2つのリスト項目が1つのオブジェクトを共有することもありません。

デフォルト値がまったく指定されずに宣言された変数は、null から始まります。

any は、開始値を自分で選べない唯一の型です。常に null であり、カードはそれを無効化された null フィールドとして示します。値を与えるには Set Var を使ってください。

Get VarSet Var の型推論

どちらのノードも value ピンを any として宣言しており、エディタは選択された変数の型をそこに投影します。Get PersistentSet Persistent も同じ仕組みです。この1回の投影が下流のすべてを決定します——ノードカードに何が表示されるか、エッジをドラッグしている間どのピンがハイライトされるか、手を離したときにパレットがどのノードを提供するか、接続がそもそも受け入れられるかどうか、そして graph validation が何をチェックするか。

VarData type を変更すると、そのグラフ内でその変数を指すすべての Get VarSet Var の投影が書き換えられます。

この書き換えによって既存のエッジが非互換になっても、そのエッジは残ります。validation はそこに型の不一致を報告し、グラフの中に残したままにします——1秒前に自分で変えた型を強制するために作者の接続を黙って削除すれば、救う以上のものを壊してしまいます。エッジを直すか、型を元に戻してください。

選択している変数がもう存在しない Get VarSet Var——削除されたか、そのノードの位置からもう到達できなくなった場合——も同じように扱われます。グラフを書き換えるのではなく、ノード上に診断を出します。

レジストリと衝突

持続変数には2つの作成面があります——プロジェクトレジストリと、ストーリーの宣言行です。あらゆる消費者——ストーリーコンパイラ、ストーリー変数パネル、ブループリントメンバーパネル——は、storageKey をキーとしてこの2つをマージした1つのビューを読むため、どの変数が存在するかについて食い違うことはあり得ません。

両方の面に現れる表示名は本物の曖昧さです——作者は1つの名前をまとった2つの変数を目にすることになるからです。コンパイルはこれを、その変数名を挙げ、それへの参照が曖昧であると告げる警告として報告します。同じ面で名前が重複する2つの行は、そこまで進むことすらありません——重複は入力されたその場で拒否されます。

Blueprint Value

Blueprint Value は、1つのウィジェットプロパティにバインドされた widgetValue ブループリントであり、そのパレットは意図的に安全な値生成ノードだけに制限されています。変数について、3つの帰結があります。

  • Var を宣言できません。 このノードはパレットに存在せず、変数宣言のスキャンは widgetValue オーナーを最初から完全にスキップします。
  • 持続変数を読み書きできません。 Get PersistentSet Persistent は latent であり、値バインディングは求められたそのティックのうちに値を出さなければなりません——保留中の promise を渡す相手がいないのです。
  • 見える変数は読み書きできます。 Get VarSet Var はホワイトリストに含まれており、Page と Global のブループリントに対して解決されます——値ブループリントは自分自身では何も宣言しないため、これがアクセス可能な集合のすべてです。

ストーリー変数と式

ストーリー変数とブループリント変数は別々の仕組みであり、ちょうど1点だけで交わります——持続スコープです。どちらの側もそこへ宣言し、どちらの側もそこから読みます。

シーンエディタでコマンド行を入力して、ストーリー変数を宣言します。

/local hp 100 type=number desc='Player health'
/save chapter 1 type=number
/global seenIntro false type=boolean

/local はシーン変数を、/save はセーブ変数を、/global は持続変数を宣言します。/var/persis は今も /save/global のエイリアスとして解決されます。明示的な type= がない場合はデフォルト値自身の型が決め手になり、デフォルト値がまったくない宣言は boolean になります。

ストーリー式——/set の右辺、/if の判定、そして行内のテキスト補間——は名前で変数を参照します。裸の名前はシーン → セーブ → 持続の順にたどり、最初に見つかったところで止まります。つまり最も狭いスコープが優先されます。名前が隠されている場合は、スコープを直接指定してください。

/set gold gold + 10
/if scene.gold >= 100

接頭辞は scene. / local.saved. / var.、そして persis. / persistent. / global. です。式は入力したその瞬間に解析され、ドキュメントはテキストではなく構文木を保存するため、実行時に再解析されることはありません。この言語にはホストオブジェクトを名指しする手段もないため、脱出すべきサンドボックスもありません。

ストーリー action ブループリントは、3つのストーリースコープすべてに到達できます——Get Scene Var / Set Scene VarGet Saved Var / Set Saved Var、そして Get Persistent / Set Persistent。この4つのストーリーノードは storyAction オーナーに対してのみ存在します——実行中のストーリーの外には、対話すべきシーンも Storable もないからです。

このページの目次