RPGツクールMZ を TypeScript + Vite + Electron で動かす
やったこと
- RPGツクールMZ プロジェクトを Git 管理外に置きつつ Electron から読み込める構成を設計する
- TypeScript プラグインのビルド出力先を確定させる
-
pnpm dev一発でプラグインの watch ビルドと Electron 起動が同時に走る状態にする - 環境変数(
.env)でゲームフォルダ名を管理し、プロジェクト名を柔軟に変えられるようにする
1. ディレクトリ構成の設計
最終的なディレクトリ構成
save-the-guild/
├── public/save-the-guild/ ← RPGツクールMZプロジェクト(.gitignore 対象)
│ ├── index.html
│ ├── js/plugins/ ← プラグインのビルド出力先(直接編集しない)
│ ├── img/pictures/ ← APNG・PNG 立ち絵・背景画像
│ └── data/
├── src/
│ ├── electron/ ← Electron メインプロセス・preload
│ └── plugins/ ← TypeScript プラグインのソース
├── vite.config.ts ← プラグインビルド設定
├── vite.electron.config.ts ← Electron ビルド設定
└── package.json
設計の要点
RPGツクールMZ プロジェクトの配置について:
配置場所はリポジトリルート上であればどこでも構いません。専用ディレクトリを用意してその中に RPGツクールMZ のプロジェクトを配置するだけです。
- Electron は指定したディレクトリ内の
index.htmlをエントリポイントとして読み込みます。 - RPGツクールMZエディタで開くときもプロジェクトフォルダを指定するだけなので、場所が変わっても影響はありません。
プラグインのビルドフロー:
src/plugins/{プラグイン名}.ts
↓ pnpm build / pnpm watch
public/save-the-guild/js/plugins/{プラグイン名}.js
↓ RPGツクールMZエディタ
プラグイン管理で有効化
この処理フローでviteでtsからjsにビルドして
ゲームプロジェクトのプラグインフォルダに出力します。
js/plugins/ は Vite のビルド出力先として指定します。
直接編集するとpnpm buildで上書きされてしまうため src/plugins/ のtsだけ
追加や編集するようにしてください
2. 環境構築
以下の流れで構築しました。
- Node.js 公式サイト から v20 以上をインストール(パッケージマネージャーは pnpm を使用)
- TypeScript・Vite・Electron・electron-builder などの必要パッケージをインストールし、
package.jsonのビルドスクリプトを設定
{
"scripts": {
"dev": "concurrently -k \"pnpm watch\" \"vite build --config vite.electron.config.ts --mode development --watch\" \"wait-on dist/dev/main.cjs && electron .\"",
"watch": "vite build --watch --mode development",
"build:release": "vite build --config vite.electron.config.ts --mode production && vite build --mode production && node src/scripts/bundle-assets.cjs && electron-builder --config electron-builder.config.cjs",
"typecheck": "tsc -p tsconfig.plugins.json --noEmit && tsc -p tsconfig.electron.json --noEmit"
}
}
- VS Code 拡張機能(ESLint・Biome・RPG Maker MZ)をインストール
RPGツクールMZ で public にゲームプロジェクトを作成
RPGツクールMZエディタで新規プロジェクト作成時に、保存先を public/{{プロジェクト名}}/ に指定する
env の設定
ゲーム固有の値や秘密情報は .env で管理し、コードに直書きしない。
# .env.production
VITE_GAME_NAME=save-the-guild # public/ 配下のプロジェクトフォルダ名
VITE_WINDOW_WIDTH=1280 # Electron ウィンドウ幅
VITE_WINDOW_HEIGHT=720 # Electron ウィンドウ高さ
VITE_ENCRYPTION_KEY=(32文字の16進数) # アセット暗号化キー
BUILD_ENCRYPT_ASSETS=true # 本番ビルド時にアセットを暗号化するか
VITE_ENCRYPTION_KEYはRPGツクールMZのデプロイ時のアセットの暗号化を
Electronでも同様にするため設定しておく
.env.development と .env.production を使い分けることで、開発時はアセット暗号化をスキップして高速化できる。
Electronの設定
src/electron/main.ts で BrowserWindow を生成し、MZ の index.html を読み込みます。
これで開発中のゲームプロジェクトを読み込むことが可能になります。
// src/electron/main.ts
const GAME_ENTRY = path.resolve(
__dirname,
`../../public/${GAME_NAME}/index.html`,
);
// 中略
const win = new BrowserWindow({
width: Number(import.meta.env.VITE_WINDOW_WIDTH) || 1280,
height: Number(import.meta.env.VITE_WINDOW_HEIGHT) || 720,
webPreferences: {
nodeIntegration: false, // レンダラーで Node.js API を直接使わせない
contextIsolation: true, // メインプロセスとレンダラーを分離
sandbox: false, // preload スクリプトを有効にするために必要
preload: PRELOAD_PATH,
},
});
win.loadFile(GAME_ENTRY);
nodeIntegration: false / contextIsolation: true は
Electron のセキュリティモデルの推奨なのでこの設定にします。
注意点
Electron 起動前にビルドが完了していないとクラッシュする
pnpm dev は concurrently で watch ビルドと Electron を並列起動します。
"dev": "concurrently -k \"pnpm watch\" \"vite build --config vite.electron.config.ts --mode development --watch\" \"wait-on dist/dev/main.cjs && electron .\""
ビルド完了前に Electron が先に立ち上がると成果物が存在せずクラッシュします。
wait-on dist/dev/main.cjs でビルド完了を待ってから Electron を起動することで解決しました。
nodeIntegration: false にすると MZ のコアが動かない
nodeIntegration: false にするとElectron上で require / process がレンダラーから見えなくなる。
MZ のコアファイル rmmz_core.js はこれらを使って実行環境をNW.jsか判定しているようです。
// rmmz_core.js
Utils.isNwjs = function() {
return typeof require === "function" && typeof process === "object";
};
Utils.isOptionValid = function(name) {
if (this.isNwjs() && nw.App.argv.length > 0) { // ← nw が存在しない
nodeIntegration: false で require と process が両方消えるので isNwjs() は false になり、この問題自体は発生しないはずだった。ところが実際に試すと ReferenceError: nw is not defined の例外エラーになります。
原因は preload スクリプト内では require と process が使えるため、preload 実行後のタイミングで MZ コアが isNwjs() === true と判定し、存在しない nw グローバルにアクセスしてしまうため
解決策: preload で nw スタブを注入する
MZ が参照する nw.App.argv と nw.Window.get の最小限のスタブを globalThis に注入することで、例外エラーを回避できました。
// src/electron/preload.ts
type NwStub = {
App: { argv: string[]; quit: () => void };
Window: { get: () => { on: () => void } };
};
(globalThis as typeof globalThis & { nw?: NwStub }).nw = {
App: { argv: [], quit: () => undefined },
Window: { get: () => ({ on: () => undefined }) },
};
window.nw = ... ではなく globalThis を拡張しているのは、preload スクリプトが Node.js コンテキストで動作するため、実行タイミングでは window がまだ存在しないためです。
MZ のコアを直接触らずスタブだけ差し込むことで、ツクールのアップデートがあっても影響を受けません。
なお nw スタブで解消できるのはこのエラーのみです。MZ の NW.js 誤判定が引き起こす他のエラーの詳細については次の章で解説します。
まとめ
pnpm dev 一発で watch ビルドと Electron 起動が同時に走る環境が完成しました。
ディレクトリ構成・ビルド設定・環境変数の土台が整い、TypeScript でプラグインを書いて即テストプレイできる状態までいけました。
次は、この環境で MZ を起動した直後に遭遇するエラーについてです。
nodeIntegration: false を採用したことで MZ のコアがエラー多発で動かなかったため
コアファイルを直接編集せずに解決する方法についての解説になります。