この記事は、ツクールアドベントカレンダー2022 4日目の記事になります。
アドベントカレンダーの性質上、本記事は本ci-enをフォローしていなくても、誰でも読むことができます。
はじめに
皆さん、型安全性を意識していますか?
なんて言われてもなんのこっちゃという方も多かろうと思います。
言い方を変えましょう。
プラグインに型起因のバグを仕込んでいますか?
ある変数が数値型だと思っていたが、実際に動かすと文字列型だった。
そのため、意図した分岐を通らずにバグった。
生JSあるあるですね。こういうくだらないバグ、消し去りたいと思いませんか?
実は、ニフラムを唱えて祈るよりも確実に消し去ることができるんです。そう、TypeScriptならね。
というわけで、今回は1から始めるTypeScriptによるRPGツクールMZのプラグイン開発環境構築編です。
MV向けにも応用できるようには書いていますが、適宜読み替えてください。
前提
- VS Codeがインストールしてあること。
- 2022/12/01現在、サポート期間中のWindows OSを利用していること。
- プラグインの導入方法(js/plugins下に入れてMZエディタから読み込む)を知っていること。
TypeScriptで開発する意義
なぜTypeScriptを使うべきか - サバイバルTypeScript
とにかく、型安全に書ける。これが最大のメリットであると、Plasmaは考えています。
型というものがふわっと、大雑把に、曖昧に運用されがちなJavaScriptに対して、TypeScriptではそこをきっちりとトランスパイラでチェックしてもらうことにより、型に起因するバグを実行前につぶしておけます。
エディタの補完機能も、単にJavaScriptでIntelliSenseを入れた場合よりも強力に効きます。(特に、ツクールのプラグインではprototypeに対して関数式を代入することが多いため、TypeScriptのほうが補完が効きやすいシーンは多いです)
サバイバルTypeScriptでは大規模なアプリケーション開発を対象とすると書いてますが、同時に数百行規模のコードでも十分に恩恵を享受できるとも書いています。
数行程度の規模のコードを数えるほどしか書かないのであれば、流石に導入コストのほうが上回るかもしれませんが、ゲームシステムのメインとなるロジックを書いたり、複数のプラグインを書いてそれをメンテナンスしていくといった場合には、TypeScriptのパワーを存分に感じられることと思います。
先人の功績
ツクールMV/MZのプラグインをTypeScriptで書くための資料は、先人によっていくらか残されています。
F_さんによるTypeScriptでMVプラグインを開発するための環境構築+αの記事です。
2016年12月なので、MVを対象としています。
当時はまだ、MVのバージョンが1.3系で、NWjsのバージョンが0.13のままでした。
Chromiumのバージョンは49であり、JavaScriptで使える構文にはかなりの制限があります。
そのため、コンパイラオプションのtargetはes5となっています。
トランスパイル(コンパイル)の実行をVS Codeのタスクランナーで実行させる仕組みや、モジュールローダを自作してプラグインの記述をシンプルにするようなテクニックが紹介されています。
現在でも応用は可能だと思われますが、本記事執筆の6年前の記事のため、古くなっていてそのままでは使えない可能性もあります。
網羅性の高い型定義ファイルも公開してくださっています。MVであればこれをベースに使ってみるのも悪くないと思います。
特に、lib下の型定義を網羅してくれているので(あまりそういう機会はないかもしれませんが)fpsmeterやlz-string周りが扱いたくなったときには重宝します。
PlasmaはMZプラグインの開発においても、この型定義を自前でMZ向けに改修して利用しています。
Narazakaさんが公開してくださっている型定義ファイルです。
F_さんのものと比べて新しいMV(1.5系)にも対応してくれており、 npm install
一発で導入可能なところが魅力です。
libの定義はないものの、これもよく使うpixi.jsについてはnpmで解決できるように依存が定義されています。
MV/MZ両方の型定義ファイルを提供してくれています。
巷にある型定義ファイルの中では最も最近までメンテナンスされているので、今新しく導入するならこれが良いのではないかと思われます。
2022/07に書かれた、かなり最近の記事です。
Narazakaさんの型定義ファイルを利用して環境構築する手順が簡潔にまとまっています。
2022/09に書かれた、おそらく2022/12/01現在最新のTypeScript+ツクールに関する記事です。
webpackを使ってビルドしているようです。
細かい手順が書かれているわけではないので、ある程度の知識と経験がある人向けの記事ですね。
MZ(MV)のプラグインをTypeScriptで書く環境を作る
さて、ここからは、TypeScriptを使ってプラグインを書くための環境の作り方を書きます。
構成
ツクールのプロジェクトとは別にプラグイン開発用のディレクトリを作って、そこにTypeScriptでプラグインを書きます。
tscでトランスパイルして、生成された.jsファイルをプラグインとしてツクールのプロジェクトにコピーして利用します。
プラグイン開発用のディレクトリ構成は以下の通り。
MZ-TypeScript/
├ src/
│ └ hogehoge.ts
├ _dist/
└ @types/
└ *.d.ts
親ディレクトリの名前は自由です。今回はMZのプラグインをTypeScriptで書くので、こんな名前にしました。以下、このディレクトリをMZ-TypeScriptと呼びます。
src
は、プラグインの元となるTypeScriptを書くためのディレクトリです。
_dist
は、トランスパイルした成果物、すなわち実際にツクールで読み込むためのプラグインを出力するためのディレクトリです。
@types
は、型定義ファイルを入れるためのディレクトリです。
Narazakaさんの .d.ts を利用する場合は単に npm install
するだけでも利用できるので必ずしも必要ではありませんが、今回はLunaLiteを利用するため、作成しています。
とりあえず、空でディレクトリだけ作った状態です。
では、次のステップへ進みましょう。
型定義ファイルのダウンロード
LunaLiteを利用します。
lunalite-pixi-mz.d.tsから、Downloadボタン(Rawボタン)を右クリックして保存しましょう。
MVの場合は、lunalite-pixi-mv.d.tsです。
先程作った、 @types
ディレクトリ下に放り込みます。
Node.jsのインストール
Node.jsをインストールしてください。
バージョンは、最新のLTSで良いでしょう。(2022/12/01時点では18.12.1が最新LTS)
上記ダウンロードページで推奨版と書かれているボタンのほうです。
Node.jsとは、JavaScriptの実行環境です。ツクールMZ(MV)のローカル実行環境であるNW.jsも、Node.jsを利用しています。
余談 Node.jsのバージョン
Node.jsは頻繁に更新されており、特に長期間稼働するようなアプリケーションを開発するエンジニアは、プロダクトの保守のために古いバージョンのNode.jsを利用しなければならない機会もあります。
そういった開発者の間では、Windowsに直接Node.jsをインストールするよりは、Node.js自体のバージョンを手軽に切り替えられる管理ツールを間に挟むことが推奨されています。
今回の場合、Node.jsの用途はツクールのプラグイン開発のみと仮定しているので、直接インストールしてしまっても良いでしょう。
もし気になる場合は、 Node.js バージョン切替 などで検索してみてください。
最近では Volta が推奨される傾向にあるようです。
TypeScriptのインストール
さて、Node.jsをインストールしたので、npmが使えるようになったはずです。
VS Codeを再起動し、VS CodeでMZ-TypeScriptフォルダを開いてCtrl+@を押してみてください。
右下にコマンドラインが表示されます。ここにコマンドを入力して色々操作します。
手始めに、 npm -v
と入力しましょう。
NPM(Node Package Manager: Node.jsのパッケージ管理ツール)のバージョンが表示されるはずです。
(Plasmaの環境では、VS Codeから開くコマンドラインツールをPowerShellにしています)
では、MZ-TypeScriptディレクトリでTypeScriptを使う準備をしていきましょう。
npm init -y
まずはこう入力して、MZ-TypeScriptディレクトリにプロジェクトを作ります。
ディレクトリに、package.jsonが出力されるはずです。ツクールでも見覚えがあるファイル名ですね。
では、いよいよTypeScriptをインストールします。
npm install -D typescript
node_modulesディレクトリが作られ、そこにTypeScriptがインストールされました。
package.jsonの依存関係にもtypescriptが追加されています。
余談 パッケージマネージャ
今回はnpmをそのまま使っています。
yarnやpnpmを推奨する人もいるでしょう。好みで使い分けてください。
動作速度は pnpm > yarn > npm というデータがあるようですが、バージョンアップで変わりうるものですし、何より追加でインストールが必要なパッケージマネージャを入れると初心者に優しくないので、今回はnpmを使っています。
npmとyarnとpnpmの違い2021など、比較記事は調べれば出てくるので、気になる方は検索してみてください。
Plasmaはyarnを使うことが多いです。
TypeScriptプロジェクトの設定を行う
今回作ろうとしている、ツクールプラグイン用のTypeScriptプロジェクトの設定を行います。
npx tsc --init
こう入力することで、TypeScriptプロジェクトの初期設定がtsconfig.jsonの形で出力されます。
このファイルは特殊なjsonで、コメントアウトが使えます。
いろいろな設定が書かれていますが、最終的には以下のような形になれば良いです。
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020"],
"module": "ESNext",
"outDir": "./_dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"strictPropertyInitialization": false,
"alwaysStrict": false,
"skipLibCheck": true
}
}
MZはおそらくES2021辺りまで対応していますが、念のため2020にしています。
MV1.6系の場合はES2018を指定すると良いでしょう。1.5系以前はes5指定で使うことになると思います。
target
で指定するバージョンは、どのバージョンのECMAScript準拠にビルドするかを指定します。
ECMAScriptはJavaScriptの標準仕様を定める規格で、年々新しい機能を追加しています。
ツクールが使用するNW.jsで処理可能なバージョンは、MZ1.2以降ではES2021、MV1.6系ではES2018、MV1.5系ではES5です。(ごく一部に例外もありますが、ツクールのプラグインで使うことはないと思われる機能なので除外して考えています)
今後リリースが予定されているMZ1.6系では更にNW.jsのバージョンが上がることがわかっており、もっと新しい機能を試せるかもしれません。
lib
はlib.dom.d.ts
を除外するために、明示的に指定します。今回は target
に合わせていますが、もっと新しいバージョンでも良いと思います。
module
は ESNext
を指定します。これは import
export
を開発用プロジェクトでどう扱うか次第ですが、どちらも使わないのであれば ESNext
にしておいて良いと思います。
strictPropertyInitialization
は、有効になっている場合、コンストラクタでインスタンス変数を初期化しないとコンパイルエラーにしてくれます。
ツクールのコアスクリプトに従って initialize
メソッドで初期化しようとするとこれに引っかかるため、ここでは無効にしています。
alwaysStrict
はトランスパイル結果の先頭に必ず 'use strict';
を足してくれます。
今回はIIFEの中に自分で書くので無効にしています。
それ以外のオプションはデフォルトです。
TypeScriptを書く
いよいよ、TypeScriptを生かして、簡単なプラグインを書いてみましょう。
src下に sample.tsを作り、コードを書きます。
今回は、指定した数値を指定した変数に代入してメッセージを表示するだけの、MZ向けのプラグインコマンドを書きました。
/*:ja
* @plugindesc TypeScriptサンプル
* @author DarkPlasma
*
* @target MZ
* @url https://ci-en.net/creator/15997
*
* @command substitute
* @text 数字代入
* @arg x
* @text 数字
* @desc この数字を変数に代入する
* @type number
* @arg v
* @text 変数
* @type variable
*
* @help
* TypeScriptによるプラグインサンプル
*/
(() => {
'use strict';
type args = {
v: string;
x: string;
};
PluginManager.registerCommand('sample', 'substitute', function(args: args) {
const variableId = Number(args.v);
const number = Number(args.x);
$gameVariables.setValue(variableId, number);
$gameMessage.add(`変数 ${variableId} に ${number} を代入したよ`);
});
})();
こう書いてsample.tsを保存したら、コマンドラインに以下のように入力します。
npx tsc
これがTypeScriptからJavaScriptに変換するトランスパイル(コンパイルとも呼ばれる)のコマンドです。
うまくいけば、 _dist下に sample.js が生成されているはずです。
これをツクールのプロジェクトにコピーして使うことで、プラグインとして動作させることができます。
既存クラスを拡張する
既存のクラスにメソッドを追加したいこともあるかもしれません。
そういう場合は、型定義を拡張する必要があります。
@types下にsample.d.tsを作りましょう。
declare interface Game_Message {
showVariable(variableId: number): void;
}
Game_Message
クラスに showVariable
メソッドを追加します。
sample.tsに、実装を足します。
(() => {
'use strict';
type args = {
v: string;
x: string;
};
PluginManager.registerCommand('sample', 'substitute', function(args: args) {
const variableId = Number(args.v);
const number = Number(args.x);
$gameVariables.setValue(variableId, number);
$gameMessage.showVariable(variableId);
});
function Game_Message_SampleMixIn(gameMessage: Game_Message) {
gameMessage.showVariable = function (variableId) {
this.add(`変数 ${variableId} の値は ${$gameVariables.value(variableId)} だ。`);
};
}
Game_Message_SampleMixIn(Game_Message.prototype);
})();
(長いので、ヘッダのコメント部分は省略しています)
これを先程と同じように npx tsc
でトランスパイルして、生成された sample.js をツクールのプロジェクトに放り込みましょう。
ちゃんと、メッセージが表示されていますね。
おわりに さらなる環境整備
今回は、TypeScriptでツクールMZのプラグインを書くための最低限の環境を整え、簡単なプラグインを実際に書いてみました。
ここからは、個人の裁量で、自分で使いやすいように開発環境を整えていくことになります。
例えば、Plasmaはrollup.jsやzxを利用して、プラグインのヘッダに書くコメントや、パラメータの読み込みコードをYAMLから自動生成し、それをtscでトランスパイルしたjsと結合して成果物としています。
簡単なところでは、トランスパイルして生成したjsファイルを自動的に指定したプロジェクトの js/plugins 下にコピーしてくれる仕組みでしょうか。
特定のゲームプロジェクトに特化した開発環境を作るもよし、広く一般に使ってもらうためのプラグインをたくさん書くためのリポジトリを用意するもよし、ここからは開発者の数だけ開発用プロジェクトの形があります。
ぜひ、自分だけの最強のプラグイン開発環境を整えて、爆速に型安全なプラグインを書いてみてください。
宣伝
本ci-enの有料プランでは、RPGツクールMZ(MV)のプラグイン開発のための知識・技術をできるだけ丁寧に解説しています。
自分でプラグインが書けるようになりたい、書いたプラグインをしっかり管理したい、なんとなくではなく、ちゃんとコアスクリプトを読んで理解したい。
そういったご希望に添えるような解説を目指していますので、よろしければフォローやプラン加入をお願い致します。
また、MZ(MV)に関する技術的なご相談もTwitterのリプライやDM、マシュマロで受け付けています。
@elleonard_f (全年齢)
@plasma_dark (R18)
マシュマロ
ご相談の内容は、本ci-enのフォロワーの皆様が無料プランの範囲内で読めるように公開させていただくことがあります。
ぜひ、お気軽にご相談ください。