-
-
Notifications
You must be signed in to change notification settings - Fork 2
Graphs ja
English | 日本語
実験的な機能です。 フレームワーク 1.4.1 で入ったもので、この先のバージョンで変わったり、無くなったりするかもしれません。
Graphs(DragNWash.ModFramework.Graphs、バージョン 1.5.0)は、コードを書かずに「何かをする」Mod を動かすためのライブラリです。これが起きたら、これをする という形で書きます。たとえば「シーンが読み込まれたら、1 秒待って、何があるか数えて、ログに 1 行書く」とか、「この会話のノードが始まったら、あのオブジェクトを隠す」といった具合です。グラフは mod.json のあるフォルダーに置く JSON のファイルで、Overrides の Mod と同じ形なので、1 つの Mod に両方入れても構いません。コンパイルは要らず、あなたのコードが走ることもありません。グラフが呼べるのは各ライブラリが登録した操作だけで、Console の op コマンドや Bridge で使えるものと同じです。
グラフは、Bridge のページにあるエディターで作ります。コードのグラフの隣で、Scratch のようにブロックを並べます。
設計と調査の経緯は docs/GRAPHS.ja.md にあります。
グラフの Mod は、1 つのフォルダーです。ゲームの BepInEx/plugins フォルダーに入れて、次のような形にします。
BepInEx/plugins/Scene notes/
mod.json
graphs/scene-notes.json
フレームワークと Graphs ライブラリが要ります。グラフを 動かす だけなら Developer tools は要りませんが、作る ときはエディターが Bridge のページにあるので要ります。ゲームを起動すると、その Mod がほかの Mod と同じように、名前と作者、説明つきで Mods 画面(Options → Mods)に並びます。
Mod の詳細には Graphs のタブがあって(1.5.0。それまではボタンで開くページでした)、グラフごとに何をするかが言葉で並びます。
Scene notes (graphs/scene-notes.json)
Answers: scene loaded, save written
Reads: inspector.objects.children, saves.flags.list
Changes: nothing
Needs: Inspector
大事なのは Changes の行です。そのグラフが何かを変えるのに使うかもしれない操作が、全部ここに並びます。動く前にファイルから読み取ったものなので、Changes: nothing と出ていれば、そのグラフにできるのは見ることとログに書くことだけです。動いているかどうかや、何回始まったかもここに出ますし、Stop for this session のボタンを押せば、そのグラフを止めて、変えたものを元に戻せます。
入っていないライブラリ(たとえば Inspector)を使うグラフは、needs Inspector と出て動きませんが、そのライブラリを入れれば動き始めます。
- 1 つのグラフを今すぐ止めるなら、Mod の Graphs タブの Stop for this session を押すか、Console で
graphs stop <ファイル名>と打ちます。変えたものは元に戻ります。 - 1 つの Mod を止めるなら、ほかの Mod と同じく Mods 画面でオフにします。効くのは次にゲームを起動したときからです(
mod.jsonの名前がmod.json.disabledに変わります)。 - グラフをまとめて止めるなら、Mods → Drag'n Wash ModFramework: Graphs → Settings →
[General] Enabledです(初期設定はオン)。 - 消したいときは、その Mod のフォルダーを削除します。
これは「しない約束」ではなく、そもそも仕組みとしてできないようになっています。グラフには、ゲームのメソッドを名前で呼ぶことも、リフレクションも、自前のファイルの読み書きも、通信も、プログラムの起動もできませんし、いつまでも走り続けることもできません。できるのはグラフ向けに登録された操作を呼ぶことだけで、グラフが新しく足すのは いつ やるか、だけです。変えたものは、止めたときや読み込み直したとき、オフにしたときに元へ戻り、セッションを越えて残るものはありません。このバージョンのグラフは、セーブデータにも触れません。
3 回続けて失敗したグラフは、そのセッションのあいだオフになります。変えたものは元に戻り、Mods 画面に印が付きます。例外を投げ続ける Mod のイベントハンドラーと同じ扱いです。
BepInEx/plugins/<Mod>/
mod.json 名前、作者、説明、バージョン(overrides と同じ)
graphs/*.json グラフ 1 つにつき 1 ファイル
overrides/*.json 任意:1 つの Mod に両方入れられます
mod.json は Overrides のページにあるものと同じです。どちらのデータ Mod も中核にある 1 つのローダーが見つけるので、両方入ったフォルダーは Mods 画面では 1 つの Mod として出て、オフにするのも 1 回で済みます。
{
"format": 1,
"name": "Scene notes",
"description": "シーンの最上位に何があるかをログに書きます。",
"variables": { "scenes": 0 },
"on": [ { "id": "h1", "event": "scene.loaded", "do": [] } ]
}| キー | 意味 | |
|---|---|---|
format |
必須 |
1。これより新しい形式のファイルは読まず、フレームワークを更新するように言います |
name、description
|
任意 | Mods 画面とエディター用。無ければファイル名 |
variables |
任意 | グラフの変数と最初の値(文字列・数値・true/false・null)。ゲームが動いているあいだだけ生き、保存はされません |
on |
必須 | 答えるイベント=ハンドラーの一覧 |
layout |
任意 | エディターがブロックを置いた場所。ゲームは読みませんし、いまのエディターは書きません |
ほかのキーはエラーになるので、書き間違いがあっても黙って無視されず、読み込んだ時点で分かります。ハンドラーと文にはファイル内で重複しない id が必ず付きます(エディターは h1、s12 のように付けます)。エラーもログも layout も文を id で指すので、ブロックを動かしてもずれません。
{ "id": "h1", "event": "scene.loaded", "when": true, "overlap": "skip", "do": [] }when は任意の絞り込みで、真のときだけ始まります。overlap は、実行が終わらないうちに同じイベントがまた来たときの動きです。skip(初期値)なら無視して、queue ならもう 1 つ始めます。1 つのグラフで同時に走れるのは 8 本までです。
グラフが答えられるイベントは次のとおりです。
| イベント | 渡される値 | 出どころ |
|---|---|---|
game.started |
中核 | |
scene.loaded |
scene、mode
|
中核 |
scene.unloaded |
scene |
中核 |
dialogue.node.started |
node |
Dialogue |
dialogue.line.showing |
line_id、speaker、text
|
Dialogue |
dialogue.option.showing |
line_id、text
|
Dialogue |
saves.written |
slot |
Flags and saves |
timer.every |
Graphs ライブラリ:seconds(0.5 以上)ごと、実時間 |
|
key.pressed |
key |
Graphs ライブラリ:指定したキー(F8 など)。F1 は使えません |
キーは誰のものでもありません。 ほかの Mod が同じキーに設定を持っていれば、両方が反応します。1.4.2 からは、ほかに誰がそのキーを使っているかを、グラフがログと Console の graphs、その Mod の Graphs タブで教えてくれます。たとえば F6 is also Drag'n Wash Localization: [Debug] DumpDialogueKey; both answer it のように出ます。1 つのキーに 2 つの仕事をさせたい人もいるので、拒否はしません。その行を読んで、意図しない重なりなら別のキーにしてください。なお Drag'n Wash Localization は F6 と F7 をダンプに使っています。
実行はイベントの中では始まりません。値を写しておいて、ライブラリの次のフレームから始まります。そのためグラフが、ほかのライブラリのフックの途中(会話の行を出している最中や、シーンが半分できたところ)で動くことはありませんし、遅いグラフがほかの Mod のイベントを遅らせることもありません。代わりに 1 フレーム遅れるので、行が出る前に書き換えることはできません。それは書き換え役(GameText.AddRewriter、Text)の仕事です。
文は id と、次のキーのうちちょうど 1 つを持つオブジェクトです。
| 文 | 書き方 | 意味 |
|---|---|---|
| call | {"call": "saves.flags.list", "args": {}, "as": "flags", "onError": []} |
操作を呼びます。名前はそのまま書くもので、実行中に組み立てることはできないので、何を呼ぶかは動く前に分かります。as は結果に名前を付け、同じ実行の後の文から使えます。onError が無ければ失敗した呼び出しは実行全体の失敗で、あれば代わりにその中の文が動き、error にメッセージが入ります |
| set | {"set": "count", "to": 1} |
variables にある変数に入れます |
| if | {"if": true, "then": [], "else": []} |
|
| wait | {"wait": 1.5} |
実時間の秒数、0〜600。0 は次のフレーム |
| repeat | {"repeat": 5, "do": []} |
1〜1000 回 |
| while | {"while": true, "max": 100, "do": []} |
真のあいだ、最大 max 周(必須)。max 周のあとも真なら失敗です |
| each | {"each": [], "as": "item", "max": 50, "do": []} |
リストの要素ごとに 1 回。max より長いリストは失敗です |
| log | {"log": "hello", "level": "Info"} |
その Mod の名前でログに 1 行(Info、Warning、Error)。1 グラフにつき毎秒 20 行まで |
| stop | {"stop": true} |
この実行を終えます(グラフは止まりません) |
goto も、ほかのハンドラーを動かす文も、再帰もありません。実行は必ず終わり、どれくらいで終わるかは下の上限で決まります。
そのままの値("text"、3、true、null)か、演算子を 1 つ持つオブジェクトです。
{"var": "name"} |
変数、またはこの実行で先に as が付けた結果(onError の中では error) |
{"event": "scene"} |
イベントが渡した値 |
{"get": [{"var": "root"}, "children"]} |
オブジェクトの項目、リストの要素(0 が先頭、-1 が末尾)。無ければ null |
eq、ne、lt、le、gt、ge
|
{"gt": [a, b]}。数どうしは数として、ほかは文字列として比べます |
add、sub、mul、div
|
数だけです。数でないもの、0 で割ることは失敗です |
and、or、not
|
{"and": [a, b]}、{"not": a}
|
join |
{"join": ["Scene ", {"event": "scene"}]}。リストやオブジェクトは JSON として書かれます |
contains |
文字列の中の文字列(大文字小文字は区別しません)、またはリストの中の要素 |
length |
文字列・リスト・オブジェクトの長さ。ほかは 0 |
false、0、""、null、空のリストと空のオブジェクトは偽として扱われます。引数は Console と同じように変換されるので、数のところに "3" と書いても通ります。
{
"format": 1,
"name": "Scene notes",
"description": "シーンの最上位に何があるかをログに書きます。",
"variables": { "scenes": 0 },
"on": [
{
"id": "h1",
"event": "scene.loaded",
"do": [
{ "id": "s1", "set": "scenes", "to": { "add": [{ "var": "scenes" }, 1] } },
{ "id": "s2", "wait": 1 },
{ "id": "s3", "call": "inspector.objects.children", "as": "roots" },
{ "id": "s4", "log": { "join": ["Scene ", { "event": "scene" }, ": ", { "length": { "var": "roots" } }, " root objects"] } },
{ "id": "s5", "each": { "var": "roots" }, "as": "root", "max": 200, "do": [
{ "id": "s6", "if": { "gt": [{ "get": [{ "var": "root" }, "children"] }, 50] }, "then": [
{ "id": "s7", "level": "Warning", "log": { "join": [{ "get": [{ "var": "root" }, "path"] }, " has ", { "get": [{ "var": "root" }, "children"] }, " children"] } }
] }
] }
]
}
]
}ブロックで見ると、このハンドラーは帽子ブロックの下の 1 本の積み重ねです。
シーンが読み込まれたら
scenes を (scenes + 1) にする
1 秒待つ
roots = inspector › objects › children
ログ "Scene " (scene) ": " (roots の長さ) " root objects"
roots の root ごとに(最大 200)
もし (root › children) > 50 なら
警告ログ (root › path) " has " (root › children) " children"
グラフが呼べるのは、操作の登録簿にある 読み取り の操作のうち、ページ専用のもの(code.*、graphs.*)以外の全部です。いまのところ、中核の mods.list・game.info・scene.list・mods.network、Tool window の log.read、Assets の一覧、Dialogue の dialogue.current・dialogue.recent、Text の text.rewriters・text.shown、Flags and saves の saves.list・saves.flags.list・saves.flags.get、Inspector の inspector.objects.find・inspector.objects.children・inspector.components.list・inspector.member.get・inspector.selection.get です。
それに加えて、Overrides ライブラリの 書き込み の操作 3 つも呼べます。overrides の 1 行がすることを、グラフが選んだタイミングでやるものです。
| 操作 | 何を | 引数 |
|---|---|---|
objects.member.set |
コンポーネントのフィールドやプロパティ |
path、component、member、value。ほかに index、private、scene
|
objects.material.set |
マテリアルのシェーダープロパティ |
path、material、property、value。ほかに component、index、scene
|
objects.active.set |
オブジェクトの表示・非表示 |
path、active。ほかに scene
|
objects.writes(読み取り) |
このライブラリが変えたものと、誰が頼んだか。同じところを変えた別の Mod も出ます |
値は文字列で、Overrides のページの表のとおりに書きます(2.5、true、#RRGGBB、1, 0.5, 2)。書き込みはそれぞれ、書く前の値を覚えていて、グラフを止めたときや読み込み直したとき、失敗したときに、新しいものから順に戻します。ログには graph:<mod>/<ファイル> という名前で Info として残ります。Inspector の History には、手で直した分と並んで「誰が変えたか」つきで出ます。その行から、グラフを止めずに 1 件だけ戻すこともできます。(Undo last と Ctrl+Z は自分の編集だけが対象で、ほかの Mod の変更は飛ばします。Export as overrides にも入りません。)
グラフが呼んだものは、どれもセッションを越えて残りません。セーブデータにフラグを書く操作(saves.flags.set)は、ファイルを変えるもので別に設計が要るので、今はわざと出していません。
自分の Mod で操作を登録すれば(操作の登録簿)、こちらを直さなくてもグラフから呼べるようになります。こうして Mod は、コードを書かない人がその上に何かを作れる足場を用意できます。
エディターは、Bridge がこのパソコンの中で出しているページの Graphs タブです。F1 のウィンドウの Bridge タブ(Developer tools)で Bridge をオンにし、そこの Graphs を押すと、エディターを開いた状態でページが出ます。隣の Open page を押すと、同じページがコードのグラフの表示で開きます。2 つの表示はページの中で切り替えられます。
- 左は読み込まれているグラフの一覧と、New graph です。エディターの外でファイルが消されたグラフは、開こうとして失敗するのではなく、一覧でそうと分かるようになっています。Reload でファイルを読み直すと、一覧から消えます。
- 中央はグラフをブロックで見せます。ハンドラーごとに帽子ブロック、文ごとにブロック、
callには操作の引数が差し込み口として並びます。中身は登録簿から来るので、どのライブラリが足した操作でも、引数と説明と種別つきでそのまま出ます。書き込みの操作は色が違います。 - 入力するたびに、ゲームと同じ規則で 検査 します。問題は文の id つきで並びます(s2: objects.active.set needs path)。そのグラフが何を変えるかも、Mods 画面と同じ言い方で出ます。
- グラフのブロックには、名前、説明(Mods 画面に出ます)、そして変数が並びます。
set文はここに並んだ名前しか使えないので、変数の追加や名前の変更、削除、初期値の指定はここでします。 - ハンドラーのブロックには、イベントに加えて only if(真のときだけ実行)と again while running(
skipかqueue)があります。 - 値が色ならカラーピッカーが付きます。
objects.member.setの場合は member の型をゲームに聞いて、欄の横にColorやSingleと出します。ピッカーをドラッグすると、その色がそのままゲームに反映されます(グラフがするのと同じ書き込みなので、Inspector の History に並び、そこから戻せます)。 -
Save は、データ Mod の
graphs/フォルダーにファイルを書きます。Mod がまだ無ければmod.jsonごと作り、置き換えたファイルは<名前>.json.bakとして残してから、グラフを読み込み直します。BepInEx/plugins/<フォルダー>/graphs/の外には書きませんし、DLL のあるフォルダーには書きません。 - Run は、隣に表示されているハンドラーを、イベントを待たずに始めます(先頭に限りません)。止めてあるグラフは Run で動き直します。Stop はグラフを止めて、変えたものを元に戻します。
-
Rename はファイルを移動します(複製は残りません)。Save はいつも、グラフがいまある場所に書きます。Delete はそのグラフを Mod から外し、ファイルを
<名前>.json.bakとして同じ場所に残します。 - Log の欄には、ライブラリのログがそのまま流れます。Clear でこの欄だけ空にできます(ゲーム側のログは触りません)。
バーの右にある Blocks と Nodes は、同じファイルの 2 つの見せ方です。ノード表示は、ハンドラーと文を箱にして描きます。流れは辺をたどって下りていき(next、then、else、do、on error)、as で名前を付けた結果は、それを読む文への破線のワイヤーになります。書き込みの操作は色が違います。形を眺めるための表示で、ノードをクリックするとそのブロックが開きます(項目はそちらにあります)。
ブロックは頭をつかんでドラッグします。別のブロックの上や下、並びの末尾の + add の行に落とすと、そこへ移ります(if の then の中へ、ループの外へ、文が置けるところならどこへでも)。ハンドラーはハンドラーの並びにしか入りませんし、自分の中には落とせません。↑ ↓ ✕ のボタンも今までどおり使えます。
ページは窓の幅に合わせて組み替わります。狭いときは Problems と Log がブロックの下に回り、さらに狭いと 1 カラムになります。
このページはすべて自前のコードで描いています。インターネットから何も取ってきませんし、ライブラリも同梱していません。
Tool window が入っていれば、Console に次が増えます。
| コマンド | 動き |
|---|---|
graphs |
グラフごとに、答えるイベント・読むもの・変えるもの・必要なもの・問題・動き具合 |
graphs reload |
変えたものを戻し、ファイルを読み直して動かします。ゲームを動かしたままファイルを編集するとき用 |
graphs stop <ファイル名か名前> |
グラフを 1 つ、このセッションのあいだ止めて、変えたものを戻します |
C# からは GameGraphs.Loaded、GameGraphs.Reload()、GameGraphs.Stop(which)、それに [BepInDependency] 用の GameGraphs.Guid が使えます。
グラフはゲームの 1 フレームをほかのものと分け合うので、次の上限があります。
| グラフ全体 | 1 フレームに 1 ms([Graphs] FrameBudgetMs)。使い切ったら次のフレームに続きます |
| 1 実行あたり | 10,000 ステップ、ループは最大 1,000 周、待ちは最大 600 秒 |
| 1 グラフあたり | 同時に 8 実行、ログは毎秒 20 行 |
| 1 ファイルあたり | 2,000 文、入れ子は 32 段、256 KB |
| 失敗 | 3 回続いたらそのセッションはオフ。変えたものは元に戻ります |
ファイルは、何かが動く前に丸ごと検査されます。見るのは、形と、call がちゃんと存在してグラフに開かれている操作を指しているか、必須の引数、それに {"var"} と {"event"} です。問題のあるファイルはまったく動かず、問題は Mods 画面に並びます。
同じものを変える 2 つのグラフは、どちらも書き込んで、あとに書いたほうが残ります。ただ、なぜ自分の変更が効かないのか分からない、ということにはなりません。
- ログに両方の名前が 1 度だけ出ます。X と Y が両方 cars/car_3 (2) active を変えています。あとに書いた Y の値が残ります。
- その Mod の Graphs タブに Also changed の行が出ます(どのメンバーを、どのグラフと、が分かります)。
- 取り消しは、自分が書いた値だけを戻します。あとから誰かが書き換えていたら、その値には触らずログに残します。停止したときに出る件数も、実際に戻した数です(1 つも戻さなければ件数は付きません)。
overrides の行とグラフが同じメンバーを触ったときも同じです。どちらも同じ台帳を通るので、パスの書き方が違っていても互いに気づきます。
グラフに入っているのは、人が書いたもの(イベント、手順、オブジェクトを見つけるための名前)です。コンテンツの方針のとおり、手で作ったものは構いませんが、ゲームのデータをそのまま写したものはいけません。
Players
Mod authors
- Getting started
- Playing well with others (the guide)
- Going online
- Installer
- Mod reload
- Overrides (no code)
- Graphs (no code, makes things happen)
Tools (F1, developer tools)
- Inspector
- Console
- Code graph
- Bridge (AI clients, MCP)
API
日本語
- ホーム
- プレイヤー向け · ランチャー · FAQ · クラッシュレポート
- はじめての Mod · ほかの Mod と一緒に動かす · 外と通信する Mod · インストーラー · Mod の再読み込み · Overrides · Graphs
- Inspector · Console · コードのグラフ · Bridge
- 中核 API · 操作の登録簿 · Text · Dialogue · Tool window · Assets · Flags and saves · GameEvents · SettingMeta
Links
- Repository
- Releases
- Changelog
- Design records: DESIGN · ROADMAP · CONTENT_POLICY