前書き
新ライブラリ『RpgTypes』の説明を始める前に少し。
この記事で使われているプログラム言語はTypeScriptである。
RPGツクールのプラグインはJavaScriptで書かれているが、手順を整えればTypeScriptでも書ける。
またツクール界隈にはテストコードを書く文化は無いが、これについてはChatGPTなどにこの記事を放り込むなどして調べてほしい。
テストコードさえあれば大きなプラグインでも安定してメンテナンスできるようになる。
まずレガシーコードとは何か
いくつかの書籍で扱われているが、概ね以下のような基準だろう。
- テストコードが無いコード
- グローバル変数への依存が強いコード
- 状態が多いコード
- マジックナンバーの多いコード
残念ながらRPGツクールMZのコアスクリプトはレガシーコードの条件に該当してしまう。
とはいえ現在でもRPGツクール製のゲームが一定数リリースされているのは、長く使われることによってゲームエンジンとして枯れて安定しいるからであり、古くなりながらもゲーム作りの基盤としてある程度戦えるのは基礎設計が優れていたことの裏返しでもある。
新たな問題と行われた改善
RPGツクールの基本設計が2020年代のプログラム・パラダイムに追いつけてない。
ノンコード・ツールとして見ればイベントコマンドの内容は2000年の『RPGツクール2000』と同じである。好意的に見れば完成度の高い基礎設計が20年も通用したということになるが、それでも設計の陳腐化は認めるしかない。時の経過は残酷である。
またバトル関連の機能が特徴+使用効果で整理されたのは2011年発売の『RPGツクールVXace』で、現在のツクールMZでもこの設計を踏襲している。この設計自体は優れていたが、各種プラグインがメモ欄を利用した無秩序な拡張を行ったため、機能不足が目立つようになった。
一方で画期的と言える改良も施されており、その好例として挙げられるのはプログラマブルな改造が『プラグイン』として規格化されことだろう。
アニメーション(エフェクト)がEffkeseerに変わったことは導入当初多数の批判があったが、現在は概ね受け入れられていると思われる。他社製のゲームツールでもEffkeseerが導入される事例は散見されていることから、これは正解だったと思う。
JavaScriptのライブラリとしてツクールMZを分析する
状況を箇条書きすると以下の通り。
- npmで管理されてない
- 生のJavaScriptしか提供されてない
- 様々な事情によりライブラリのコードを修正することが不可能
- プログラムによる拡張の際に独自スキーマを要求する(プラグインパラメータ)
これらの解決のために専用ライブラリ『RpgTypes』を開発した。
TypeScriptで多数の型とFactory関数を定義
RPGツクールMZのデータはJSONで定義されているため型定義を作成可能である。
この定義をベースにFactory関数を作成し、部分的な初期データから完全な型を構築できるようにした。
以下はサンプルコード。
const actor: Data_Actor = makeActorData({
nickname: "ニックネーム",
name: "アクター",
profile: "プロフィール",
id: 1,
});
export const makeActorData = (actor: Partial<Data_Actor> = {}): Data_Actor => ({
name: actor.name ?? "",
id: actor.id ?? 0,
battlerName: actor.battlerName ?? "",
characterName: actor.characterName ?? "",
characterIndex: actor.characterIndex ?? 0,
faceName: actor.faceName ?? "",
faceIndex: actor.faceIndex ?? 0,
traits: [], // コピー方針未定につき空配列
note: actor.note ?? "",
classId: actor.classId ?? 0,
nickname: actor.nickname ?? "",
profile: actor.profile ?? "",
equips: [],
initialLevel: actor.initialLevel ?? 0,
maxLevel: actor.maxLevel ?? 0,
});
これらの関数群は特にテストコード作成時に大いに役立つ。
型チェック関数を作成
RPGツクールMZのデータ型は決まっているが、型チェックが無く読み込み段階の問題でもそのまま素通ししてしまう。
この問題を作成するためにJSON Schema + Ajvでバリデーション関数を作成した。
import type { Data_Actor } from "@RpgTypes/rmmz/rpg";
import validate from "./actorValidate";
export const isDataActor = (data: unknown): data is Data_Actor => {
return validate(data);
};
関数の実体は.cjsで書かれた自動生成コードにある。
JSON Schemaを使っているので移植性も高い。
ゲームデータの解析
テストコードを見れば早いが、下記のようなテキスト抽出関数を作成した。
describe("extractTextFromActor", () => {
const expected: ExtractedText<Data_Actor> = {
main: [
{ key: "name", text: "アクター", id: 1 },
{ key: "nickname", text: "ニックネーム", id: 1 },
{ key: "profile", text: "プロフィール", id: 1 },
],
note: [],
};
const actor = makeActorData({
nickname: "ニックネーム",
name: "アクター",
profile: "プロフィール",
id: 1,
});
test("extract", () => {
const result: ExtractedText<Data_Actor> = extractTextFromActor(actor);
expect(result).toEqual(expected);
});
});
このような便利関数を多数作成しており、これによりRPGツクールMZを外部ツールで改変しやすくなる。
イベントコマンドの生成
RPGツクールMZのイベントコマンドは複雑怪奇である。
全イベントコマンドの基底タイプを定義すると以下のようになる。
export interface EventCommandUnknown {
code: number;
indent: number;
parameters: unknown[];
}
export interface EventCommandLike<
Code extends number,
Param extends unknown[] = unknown[]
> extends EventCommandUnknown {
code: Code;
indent: number;
parameters: Param;
}
見ての通りパラメータが配列で無造作に格納され、パラメータの意味を調べるには型定義を読む必要がある。
同じ配列にnumberとstringが混在している時点で嫌な予感を感じるだろう。
そこで以下のような関数を用意し、名前付きで初期化できるようにした。
export const makeCommandChangeActorName = (
param: ParamObject_ChangeActorName,
indent: number = 0
): Command_ChangeActorName => ({
code: CHANGE_NAME,
parameters: [param.actorId, param.name],
indent,
});
テストコードは以下の通り
describe("makeCommandChangeActorName", () => {
test("should create a command to change actor name", () => {
const param: ParamObject_ChangeActorName = { actorId: 1, name: "New Name" };
const expected: Command_ChangeActorName = {
code: CHANGE_NAME,
parameters: [1, "New Name"],
indent: 2,
};
const result: Command_ChangeActorName = makeCommandChangeActorName(
param,
2,
);
expect(result).toEqual(expected);
});
});
このようにパラメータを名前付きにできるだけで可読性を改善できる。
終わりに
RPGツクールMZの界隈は旧来のJavaScriptが未だに現役である。
それ故に敬遠されて新規プログラマの流入は期待できないが、このライブラリがその改善の一助になれば幸いである。
これとは別のリポジトリでプラグインの解析も行っているが、こちらの説明は長いので別の機会にする。
開発期間はフル稼働3か月と非常に長かったが、プラグインパラメータ解析は半分ほど成功している。