RPGツクールMVのプラグインをTypeScriptで開発する（令和版）


同様の記事はたくさんあるのだが、微妙に情報が古かったりして四苦八苦したので、自分なりに良いと思った方法を共有する。Windows向け。

## 方針

型の恩恵を受けるのがメイン。
ガッチリファイルを分けてwebpackでバンドルするような開発も可能ではあると思うが、個人的には1ファイル1プラグインの対応がわかりやすく、生成後ファイルも見づらくなくて良い……（と思っている。これは自分の開発が既存プラグインの改造がメインだからかもしれない）。
なので、プラグインの内容は1つのtsファイルで完結させ、それをコンパイルしたらjsファイルが出力されて、それがそのままプラグインとして使える、という状態を目指す。

## 導入
とりあえず[node.js](https://nodejs.org/ja/) を導入する。リンク先に飛んで、LTSと書いてあるバージョンのインストーラをDLし、ウイザードに従ってインストールすればよい。
また、今回はエディタとしてVSCodeを使う。別のエディタを使っている人は自分でなんとかなると思うので、適宜読み替えてほしい。DLしてインストールする。

### 初期設定

好きなフォルダ（デスクトップなど）に作業用のフォルダを作る。名前は何でもよい。
作ったら、VSCodeを開いて、ファイル→フォルダを開く で今作ったフォルダを開く。下の方にターミナルというのが出ていなければ、表示→ターミナル でターミナルを開き、下のコマンドを一行ずつ実行する。

```bash

npm init -y # typescriptを使うのに必要。初期設定を自動でやってくれる
npm i -D typescript # typescriptをインストールする。

npx tsc --init # typescriptのコンパイル用の設定ファイルを生成する
```


編集するソースを入れるフォルダ`src`を作り、ここに拡張子`.ts`でプラグインファイルを作る。名前は好きなのにしてください。
また、.jsにコンパイルされたファイルが吐かれるフォルダ`dist`、後で使うフォルダ`@types`を作る。ここまで終わると、フォルダの構成は以下のようになるはず。

![20220807_134229_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696043/a08768ac-b96a-158b-7311-a429868eb1ad.png)

ファイル→名前をつけてワークスペースの保存 でデスクトップ等に保存すると、`.code-workspace`という、開くと一瞬でこの状態を復元してくれるファイルができる。便利。

### package.jsonの設定
フォルダにpackage.jsonというのがあると思うので、それを開き、以下の行を追加する。
```diff
  "scripts": {
+    "build": "tsc",
  },
```
これでターミナルに`npm run build`と打つとtypescriptのコンパイルをしてくれる。が、いちいち打つのは面倒なので、VSCodeのGUIが便利。
サイドバーのメニュー的なところを右クリックすると、NPM スクリプトという項目が現れる。

![20220517_222105_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696043/1790ae0b-6ca5-0a02-2fda-e01bf9f57ba7.png)

これにチェックを入れるとこんな感じのメニューが現れる。

![20220724_102929_00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/696043/53489494-68c9-8568-8cd8-c54b95d53b4e.png)

カーソルを合わせると▶ボタンが出現するので、それをクリックすればビルドが出来る。


### 自動でゲームに反映する設定
プラグインは.jsファイルでdistフォルダの中に生成されるが、これをゲームのプラグインフォルダにいちいちコピペするのは面倒なので、これもコマンドに含めてしまいたい。
まずはターミナルで
```bash
npm i -D cpx
```
を実行する。次にpackage.jsonに次の行を追加する。

```diff
  "scripts": {
     "build": "tsc",
+    "build2": "tsc && cpx dist/[ファイル名].js [ゲームのフォルダのパス]/js/plugins"
  },

```

これで `build2`を実行すればコンパイルと生成ファイルのゲームへの反映を両方やってくれる。build2はあんまりな名前なので、好きな名前にしてください。

### tsconfigの設定
tsconfig.jsonというファイルをちょっと編集する。初期状態だとファイルのほぼすべてがコメントアウトされているので、必要なプロパティだけコメントアウトを解除する。
strict周りはわかる人は好みの設定にしてください。


```json
    "compilerOptions": {
        "target": "es2018",
        "module": "ESNext",
        "outDir": "./dist",
        "strictPropertyInitialization": false,
        "strictBindCallApply": false,
    }
```

- targetは**ES2018**。これは、ソースが古い環境でも動くように、自動でコードを書き直してくれる機能。ツクールMVはes2019以上の新しい機能が使えないため、ES2018に書き直されるように設定する。これより古いぶんには問題がないが、生成後のソースが見づらいだけなのでおすすめできない。
- moduleは**ESNEXT**がよいと思う。`export`関連の変なコードがくっついてこないため。
- outdirはコンパイルしたファイルをどこに出力するか。先ほど作ったdistフォルダを設定する。
- `strictPropertyInitialization`はクラスのコンストラクタで未定義を許容するオプション。ツクールMVのソースは`class`を用いて書かれてはいないため、typescriptにコンストラクタでのプロパティ未定義を怒られる事がある。それを黙らせる。typescriptの堅牢性を緩める行為なので、採用はお好みで。
- `strictBindCallApply`は`Function.call()`や`Function.apply()`にargumentsを使うためのオプション。typescriptの`arguments`は型が緩いため、callやapplyで厳密に型チェックをさせると怒られる。それを黙らせる。typescriptの堅牢性を(ry

## ツクールMVのコマンドの型定義

RPGツクールは基本的にグローバルにゲームに使用するいろんなものを生やしていて、それらの動作を上書きすることでプラグインを実現している。型定義については、素晴らしき先駆者様が公開してくださっているので、これを活用しよう。以上。
https://github.com/Narazaka/rpgmakermv_typescript_dts

……と言いたいところなのだが、`///<reference>`の記述が必要だったり、定義が誤っていたときに修正できなくて面倒だったりと、ちょっと使いづらいので、カスタマイズする。
まずは上のリポジトリのとおりに次のコマンドを実行する。

```bash
npm install rpgmakermv_typescript_dts
```
次に`@types`フォルダを作り、`node_modules\rpgmakermv_typescript_dts\typings\rpgmakermv\rpgmakermv.d.ts`をコピーする。
そうしたらコピーしたファイルを開き、

- 2行目 `/// <reference path="../lib/lib.dom.d.ts" />`は消去してよい。
- すべての`declare`を消去する。
- ファイル全体を `declare global{}`で囲う。
- ファイル末尾に `export {}`を記述する。

こうすることで快適に型が効くようになる。

### 型定義の書き換え

場合によっては型定義を書き換えなければならないこともある。この型定義だと$gameVariablesはnumberしか扱えないようになっているが、他の型の値を扱えるようにするにはrpgmaker.d.tsを次のように書き換える。


```diff 
    class Game_Variables {
        constructor()
        initialize(): void
        clear(): void
-       value(variableId: number): number
-       setValue(variableId: number, value: number): number
+       value(variableId: number): unknown
+       setValue(variableId: number, value: any): number
        onChange(): void

        _data: number[]
    }
```

unknown型の値をそのまま使おうとするとエラーになるので、使うときは次のようにして、型を絞り込んであげるとよい。

```ts
let someParam = $gameVariables.value(1) as string
```

## まとめ
ここまでの設定ができれば、typescriptでプラグインを開発し、npmタスクランナーから`build2`を行うことでプラグインがゲームに反映される、という流れが出来ているはず。良きツクールライフを！

## 豆知識
StorageManagerというデータを保存するクラス（環境に合わせてlocalStorageとファイル保存を使い分けてくれるっぽい）があるのだが、これがなんとJS側のStorageManagerと完全に名前が一緒で、サジェストをしてくれない。どうにかならんかなあ。