コンテンツにスキップ

読み込みとライフサイクル

エディターはプラグインフォルダーをスキャンして plugin.json を探します。検査に通り、有効になっているプラグインだけを読み込みます。プラグインのコードで例外が発生すると、そのプラグインだけを無効にし、エディターは動作を続けます。これを隔離といいます。

  1. フォルダーのスキャン。 各プラグインフォルダーの直下にあるサブフォルダーを名前順に確認します。plugin.json があるフォルダーだけがプラグインです。
  2. plugin.json の検査。 項目に誤りがあると「エラー (ファイル)」です。
  3. SDK バージョンの比較。 sdk 範囲にエディターの SDK バージョンが含まれない場合は「バージョン不一致」です。
  4. 有効状態の確認。 ユーザーが無効にしたプラグインは「オフ」です。
  5. 初回実行の警告。 プラグインを初めて見つけると、警告ウィンドウが 1 回表示されます。「信頼できる — 読み込む」を押すと記憶して読み込みます。「今回は読み込まない」を押すと、今回は何も読み込みません (「待機」のまま残ります)。
  6. ロードコンテキストの作成。 プラグインごとに個別に作成します。dll はバイトとして読み込むため、ファイルをロックしません。同じフォルダーに .pdb があれば一緒に読み込みます。
  7. エントリークラスの作成。 entry (なければ最初の INpEditorPlugin 実装) を引数なしコンストラクターで作成します。クラスが見つからない、または作成できない場合は「エラー (隔離済み)」です。
  8. Register(host) の呼び出し。 例外が発生すると何も登録されず、「エラー (隔離済み)」になります。
  9. エディターへの追加。 メニュー・モード・パネル・コマンドなどをエディターに追加します。追加中に例外が発生すると「エラー (隔離済み)」です。名前が既に存在するコマンド・アクタータイプは、その項目だけが追加されません。
  10. オン。 出力ログに [プラグイン <id>] <バージョン> 有効 — <登録の要約> が記録されます。

エディターの起動時に 1〜10 を 1 回行います。プラグインマネージャーの「再スキャン」やコンソールの plugins reload は 1〜10 をもう一度行います。既に有効なプラグインは再読み込みしません。

プラグインマネージャーの「状態」列と、コンソールの plugins の結果に表示されます。

状態意味対処
待機見つかったが、まだ読み込んでいない (警告の確認前など)警告を確認するか「再スキャン」
オン正常に動作中—
オフユーザーが「有効」のチェックを外したチェックを付け直す
エラー (ファイル)plugin.json の誤り・dll がない・同じ id下の詳細行に表示される理由を修正
バージョン不一致sdk 範囲がエディターの SDK を含まない。またはエディターの SDK にないメンバーを使っていて読み込みに失敗sdk を修正するか、エディターを更新
エラー (隔離済み)エントリークラスを作成できない・Register やコールバックで例外 → そのプラグインだけをアンロード出力ログの例外を修正し「再読み込み (選択)」

ツール › プラグインマネージャー… で開きます。

列・ボタン役割
有効チェックすると有効にし (読み込み)、外すとアンロードします。状態は plugins_state.json に保存されます。
名前・バージョン・状態plugin.json の name ・ version と現在の状態です。
登録有効なら登録の要約 (例 메뉴 1 · 빠른 추가 1)、そうでなければエラーの理由です。
再スキャンフォルダーをもう一度確認します。新しいプラグインを見つけ、なくなったフォルダーはアンロードします。
再読み込み (選択)選択したプラグインをアンロードし、plugin.json ・ dll を読み直して有効にします。ビルド後に使います。
フォルダーを開くユーザープラグインフォルダーを開きます。

無効化・再読み込み・アンロード

Section titled “無効化・再読み込み・アンロード”

次の場合にプラグインをアンロードします: 「有効」のチェックを外す・「再読み込み (選択)」・フォルダー削除後の「再スキャン」・エラーによる隔離。エディターを終了するときは、アンロード処理を経ずにプロセスが終了します。そのため、保存のように必ず行うべき処理を Dispose に置かないでください。

  1. エディターが登録されたものをすべて取り外します (メニュー・モード・パネル・コマンド・ショートカット・アクタータイプ・セルガード・対称)。そのプラグインのモードを使用中だった場合は、ブロックブラシモードに戻ります。
  2. エントリークラスが IDisposable であれば Dispose() を呼び出します。
  3. ワールドロジック type を登録簿から削除します。その type を使っていたルールは削除せず、「不明な type」の注意として残します。
  4. プレイカメラを保持していた場合は解放します (元の視点に戻ります)。
  5. host.Draw で描いたものを消去します。
  6. ロードコンテキストをアンロードします (collectible アンロード)。

host.Settings の値はファイルに残ります。再び有効にすると、そのまま読み込みます。

やるべきこと理由
静的イベント・タイマー・スレッドに登録したものは Dispose で解除します。残っているとロードコンテキストがメモリからアンロードされません。
Task.Run で実行した処理は Dispose で停止させます (CancellationToken)。無効化した後にワールドに触れてはいけません。
host.WorldChanged ・ Committed ・ View.PlayTicked などのホストイベントは解除しなくてもかまいません。アンロード時にホストがすべて破棄します。
Dispose で例外をスローしません。出力ログに記録され、アンロードは続行されます。
public sealed class Plugin : INpEditorPlugin, IDisposable
{
private readonly CancellationTokenSource _stop = new();
public void Register(INpEditorHost host)
{
// … 登録 …
}
public void Dispose() => _stop.Cancel(); // バックグラウンド処理を停止
}
  • すべてのコールバック (メニュー・コマンド・イベント・NpUi ・ Simulate ・ PlayTicked) は、エディターのメインスレッドで呼び出されます。
  • 時間のかかる計算は Task.Run に移します。
  • World.Edit と Draw はメインスレッド (コールバック内) でのみ呼び出します。
対象場所
ユーザープラグインフォルダー%APPDATA%\NPEditor\plugins\<フォルダー>\ (ツール › プラグインフォルダーを開く)
インストールフォルダーのプラグイン<インストールフォルダー>\plugins\<フォルダー>\
追加で確認するフォルダー環境変数 NP_EDITOR_PLUGINS (複数の場合は ; で区切る)
有効化・無効化・警告の確認%APPDATA%\NPEditor\plugins_state.json
プラグイン設定 (host.Settings)%APPDATA%\NPEditor\plugin_settings\<id>.json
ユーザーが変更したショートカット%APPDATA%\NPEditor\shortcuts.json
大きな独自データhost.PluginDir (= plugin.json があるフォルダー) の下の独自ファイル

フォルダーは上の表の順にスキャンします。同じ id は先に見つかったものを使います。

登録したものエディター内の名前衝突した場合
メニュー・クイック追加・インポート/エクスポート・パネル・モード・ショートカットext.<プラグイン id>.<自分の id>他のプラグイン・内蔵機能とは衝突しない
ショートカットの Mode自分のモード id をそのまま書けば、エディターが ext.<プラグイン id>. を付ける—
コンソールコマンド名そのまま (空白なし)既に存在すればそのコマンドだけ追加されない (先にあったものが優先)
ワールドロジック typeそのまま (同じ役割の中で)既に存在すれば (内蔵を含む) 登録時に例外 → そのプラグインを隔離
アクタータイプの Kindそのまま既に存在すればそのタイプだけ追加されない (先にあったものが優先)

コマンド名・ワールドロジック type・アクターの kind の前には自分の名前を付けます (例 road_build、myquest_start、myroad.zone)。

  • 無限ループ・スタックオーバーフロー・プロセスの終了 (Environment.Exit)
  • ファイル・ネットワークへのアクセス

プラグインはエディターと同じ権限で動作するコードです。隔離はミスを防ぐ仕組みであり、セキュリティ境界ではありません。