MZ用 基本機能ライブラリ Fs (β版)

fspace

ユーザー
ツクールMZの公式プラグイン"PluginCommonBase"に触発されて、プラグインを簡単に書くためのプラグイン"Fs"をつくってみました。

本体

型定義ファイル

現在はβ版として広く意見を集めるために公開しています。「ここをこうしてほしい」「こんな機能がほしい」などの意見をもらえると嬉しいです。正式リリースまでは互換性は一切考慮しませんのでご注意ください。

また、諸事情により、ツクールMZ標準のChromium 80ではなく、Chromium 81で動作確認しているので、もしかしたら動かない部分があるかもしれません。動かない場合には連絡をもらえると助かります。

概要

Fs は Functions の略として名付けたもので、名前の通り、プラグインの開発を容易にするための多くの『関数』を提供します。

一方で、Fsはコアスクリプトには一切干渉しません。そのため、Fsの導入自体が他のプラグインと競合を起こすことはまずありません(唯一考えられるのはグローバル変数名"Fs"が被った場合です)。プラグイン開発者に特定の書き方を強制する「フレームワーク」ではなく、あくまでも記述を補佐する「ライブラリ」として設計しました。


Fsが唯一実行する処理はグローバル変数"Fs"を定義することです。"Fs"には、そのプロパティとして大文字一文字の名前を持つ複数の名前空間が定義されています。使用側のプラグインは"Fs"から使用したい名前空間のプロパティを展開して利用します。

JavaScript:
const { P, M, N, Z } = Fs;


P:プラグインパラメータのパース

名前空間Pにはプラグインパラメータをパースするための関数が定義されています。

"PluginCommonBase"と同様の機能ですが、次のような点で異なります。

* パラメータ名によって挙動が変化することはありません。
* 「変数による置き換え」のような指示されていない動作は行いません。
* パース結果はプリミティブ値やシンプルなオブジェクト(POJO)です。
* パース結果の変換や検証といった機能を提供します。

ツクールMZのプラグインパラメータは、その仕様上、自動的にその型を判別することはできません。"PluginCommonBase"は不完全ながらも推論によって型を決定しますが、"Fs"では推論ミスによるバグの発生を避けるため推論はしません。代わりに"Fs"では開発者がパラメータの型を指定します。

Pによるプラグインパラメータの基本的なパース処理は次の通りです。

JavaScript:
const PLUGIN_NAME = Z.pluginName();
const PARAMS = PluginManager.parameters(PLUGIN_NAME);
const INT_PARAM = P.parse(PARAMS["IntParam"], P.integer);
const STR_PARAM = P.parse(PARAMS["StrParam"], P.string);
const BOOL_PARAM = P.parse(PARAMS["BoolParam"], P.boolean);
const ARRAY_PARAM = P.parse(PARAMS["ArrayParam"], P.array(P.integer));
const STRUCT_PARAM = P.parse(PARAMS["StructParam"], P.make({ foo: P.integer }));
const COMPLEX_PARAM = P.parse(PARAMS["ComplexParam"], P.make({
    foo: [{ s: P.string, b: P.boolean }],
    bar: {
        baz: P.number,
    },
}));


"PluginCommonBase"のように一括でパースしたい場合には次のようにも書けます。

JavaScript:
const PLUGIN_NAME = Z.pluginName();
const PARAMS = P.parseAll(PluginManager.parameters(PLUGIN_NAME), {
    IntParam: P.integer,
    StrParam: P.string,
    BoolParam: P.boolean,
    ArrayParam: P.array(P.integer),
    StructParam: P.make({ foo: P.integer }),
    ComplexParam: P.make({
        foo: [{ s: P.string, b: P.boolean }],
        bar: {
            baz: P.number,
        },
    }),
});


P.integerP.stringP.booleanなどがそれぞれ型を表し、それらの値をもつ配列やオブジェクトを生成することで複雑な型も表現できます。

基本型以外の形式のパラメータをパースしたい場合にはP.customを使用します。

JavaScript:
const rangeParser = P.custom(s => {
    const RE = /^\d+\.\.\d+$/;
    const parse = s => s.split("..").map(x => Number.parseInt(x, 10));
    return RE.test(s) ? R.ok(parse(s)) : R.err("range");
});
const RANGE_PARAM = P.parse(PARAMS["RangeParam"], rangeParser);


空欄の許可(withDefault)

指定しても指定しなくてもいいという場合にはP.withDefaultを使用すると、空欄時にデフォルト値を使用することができます。

JavaScript:
const OPT_PARAM = P.parse(PARAMS["OptParam"], P.withDefault(P.integer, 0));


値の変換(map)

「%値で指定させたいけど使うときには1/100した値として参照したい」など、値の変換が必要な場合にはP.mapを使用します。

JavaScript:
const PERCENT_PARAM = P.parse(PARAMS["PercentParam"], P.map(P.integer, x => x / 100));


値の検証(validate)

「偶数以外の数値を受け取りたくない」など、値に制限が必要な場合にはP.validateを使用します。

JavaScript:
const EVEN_PARAM = P.parse(PARAMS["EvenParam"], P.validate(P.integer, x => x % 2 === 0 ? R.ok(x) : R.err("not even.")));


M、N:メタデータのパース

名前空間Mと名前空間Nにはメタデータをパースするための関数が定義されています。

Mはメタデータ自体のパースを、Nはメタデータに記述された内容のパースを担当します。Nでメタデータの内容に対するパーサを作成し、Mでそれを利用してメタデータ全体をパースします。

M、Nによるメタデータの基本的なパース処理は次の通りです。

JavaScript:
const myMeta = M.meta(M.attrN("my-meta", N.integer));

Game_Actor.prototype.doSomething = function() {
    const value = myMeta(this.actor().meta);

    /* ... */
};


Nの概要

NはPと同様に、N.integerN.numberN.booleanなどが定義されていて、これによって記法と型を指定します。ただし、N.string はありません。代わりに、引用符に囲まれたテキストをパースするN.text や、特定の文字列をパースするN.symbol 、正規表現にマッチする文字列をパースするN.regexp などがあります。また、特定の型のリストをパースしたい場合にはN.list を、それぞれ型の違う複数の値をパースしたい場合にはN.tuple を使用します。

JavaScript:
const intParser = N.integer;   // <xxx: 42>
const numParser = N.number;   // <xxx: 0.42>
const boolParser = N.boolean;   // <xxx: true>
const textParser = N.text;   // <xxx: "foo">
const fooParser = N.symbol("foo");   // <xxx: foo>
const rangeParser = N.regexp('range', /^\d+\.\.\d+/, (_, fst, snd) => {
    return [fst, snd].map(x => Number.parseInt(x, 10));
});   // <xxx: 12..34>

const listParser = N.list(N.integer);   // <xxx: 1 2 3 4>
const tupleParser = N.tuple([N.number, N.boolean, N.text]);   // <xxx: 42 true "foo">


P同様、N.withDefaultN.mapN.validate も利用できます。ただし、N.withDefault はメタデータ自体が存在しない場合のデフォルト値ではないことに注意してください。

括弧による整形(parens, braces, brackets)

メタデータでは単純に値を並べるのではなく、括弧をつけて表す内容をわかりやすくしたい場合があります。N.parensN.bracesN.brackets を使用すると、括弧に囲われた値のパースが可能になります。

JavaScript:
// <xxx: [ 1 2 3 ] >
const arrayParser = N.brackets(N.list(N.integer));

// <xxx: { 1 :: "foo" 2 :: "bar" } >
const mapParser = N.map(
    N.braces(N.list(
        N.map(
            N.tuple([N.integer, N.symbol("::"), N.text]),
            ([key, , value]) => [key, value],
        )
    )),
    entries => new Map(entries),
);


パーサの選択(oneOf)

ひとつのメタデータに複数の記法がある場合には、N.oneOf によってパーサの選択ができます。

JavaScript:
// <xxx: on> or <xxx: off>
const switchParser = N.oneOf([N.symbol("on"), N.symbol("off")]);


Mの概要

Mでは、Nで生成したパーサを利用して、メタデータの解析をします。

メタデータには追加情報を持たずにマーカーとして機能するもの(<xxx>)と、データに追加情報を与えるもの(<xxx: yyy>)の二種類があります。MではこれらをそれぞれM.flagM.attrN で表します。

JavaScript:
const myFlagParser = M.flag("my-flag");   // <my-flag>
const myAttrParser = M.attrN("my-attr", N.integer);   // <my-attr: 42>


これらによって生成されたパーサをM.meta に渡すと、メタデータ解析用の関数が返ってきます。この関数にメタデータオブジェクトを渡すと、解析結果の値を取得することができます。解析結果は自動的にキャッシュされ、二回目以降は解析を行うことなく瞬時に値を取得することができます。

JavaScript:
const myAttrMeta = M.meta(myAttrParser);

const data = { meta: { "my-attr": "42" } };
const value = myAttrMeta(data.meta);


メタデータが存在しない場合

メタデータが存在しなかった場合、メタデータの値はM.flag ではfalseM.attrN ではundefined になります。ただし、M.attrNM.withDefault によってデフォルト値を設定することもできます。また、複数のメタデータのうち、ひとつでも設定されていればいいという場合には、M.oneOf によってパーサを選択することもできます。

JavaScript:
const fooParser = M.withDefault(M.attrN("foo", N.integer), 0);
const barOrBazParser = M.oneOf([
    M.attrN("bar", N.number),
    M.attrN("baz", N.text),
]);
 
最後に編集:

fspace

ユーザー
Z:プラグイン開発特化のユーティリティ

名前空間Zはプラグイン開発でよく使われるパターンに特化したユーティリティ関数を提供します。

プラグイン名の取得(pluginName)

プラグインパラメータの例ですでに使用していましたが、Z.pluginNameでプラグイン名を取得できます。

JavaScript:
const PLUGIN_NAME = Z.pluginName();


Document.currentScriptを利用して取得しているため、ゲームの途中で取得することはできません。プラグインの始めに取得して変数に代入し、以降はそれを参照するようにしてください。

処理の再定義(redef)

Z.redefは、特定のオブジェクトのプロパティを元々定義されていた値を利用して置き換えます。主にプロトタイプのメソッドを書き換えるのに使用します。

JavaScript:
Z.redef(Game_Actor.prototype, base => ({
    name() {
        return `†††${base(this).name()}†††`;
    },
    gainExp(exp) {
        base(this).gainExp(Math.floor(exp * Math.random()));
    },
    /* ... */
}));


クラス構文のsuperbase(this)に置き換えるような形式で記述すると、元々定義されていたメソッドを呼び出すことができます。ただし、この方法で呼び出すことのできるメソッドは再定義したメソッドと同名のものに限られます。

外部管理プロパティの定義(extProp, extend)

Z.extPropは、WeakMapまたはMapによって、オブジェクトに疑似的にプロパティを作成します。Game_XXX 系のオブジェクトにはプロパティの内容をそのままシリアライズして保存してしまうものがあるため、保存したくない一時的なプロパティを設定したい場合などに使用します。

JavaScript:
const nameProp = Z.extProp("foo");
const nonWeakProp = Z.extProp("bar", true);

Z.redef(Game_Actor.prototype, () => ({
    name() {
        return nameProp.get(this);
    },
    setName(name) {
        nameProp.set(this, name);
    },
}));


第一引数は未設定時のデフォルト値、第二引数はWeakMap でなくMap を利用するかどうかです。戻り値は取得(get)、設定(set)、削除(delete)のための関数を含むオブジェクトで、Map を利用する場合には、クリア(clear)が追加でできます(ただし、代わりにメモリリークが発生しないように注意する必要があります)。

Z.extProp で生成した関数をZ.extend に渡すと、アクセサを定義することができます。

JavaScript:
Z.extend(Game_Actor.prototype, "foo", Z.extProp("foo"));

Z.redef(Game_Actor.prototype, base => ({
    name() {
        return this.foo;
    },
    setName(name) {
        this.foo = name;
    },
}));


一時的な値の置き換え(swapper)

プラグインでは時折、あるメソッドを呼び出したいけれどそれによる副作用は避けたい、という場合があります。Z.swapperはそれを実現するための手段のひとつです。Z.swapperは、オブジェクトの特定のプロパティの値を一時的に別の値に置き換えて、指定した処理の実行後に元に戻す、という動作をする関数を生成します。

JavaScript:
const swapResult = Z.swapper("_result");

Z.redef(Game_Battler.prototoype, base => ({
    doSomething() {
        const dummy = new Game_ActionResult();
        swapResult(this, dummy, () => {
            this.addState(STATE_ID);
        });
    },
}));


文脈依存の動作(context)

プラグインでは可能な限り既存の処理を利用して拡張するという性質上、メソッドがどのような経路で呼び出されたかによって挙動を切り替えたい場合があります。Z.contextはそれをサポートするための関数です。

JavaScript:
const turnContext = Z.context(1);

Z.redef(Game_Actor.prototype, base => ({
    turnEndOnMap() {
        turnContext.enter(this, Math.random(), () => {
            base(this).turnEndOnMap();
        });
    },
    gainHp(value) {
        base(this).gainHp(Math.floor(value * turnContext.value(this)));
    },
}));


Z.contextで作成したオブジェクトにはいくつかの関数が定義されています。enter は値とともに文脈を生成し、指定した処理をその文脈のもとで実行します。value は現在の文脈の値を取得します(文脈が存在しない場合にはZ.contextで指定したデフォルト値が返ります) 。単に文脈が存在するかどうか確認したい場合にはexists も利用できます。

その他の名前空間

主に内部実装に使用している名前空間ですが、その他の名前空間も利用できます。

O:Option<T> 型の実装
R:Result<T, E> 型の実装
L:単方向連結リスト(Consセル)の実装
S:文字列操作関数(現在はデバッグ用途のみ)
U:汎用ユーティリティ関数
G:汎用パーサ
E:式の評価
 
最後に編集:

げれげれ

ユーザー
いつも知見の共有ありがとうございます。
自分の力量からするとかなり高度かつ難解なので一つ一つ調べながら読み解いている最中ですが、
まずは謝意を表明させていただきます。
 

fspace

ユーザー
ありがとうございます。

自分以外がソースコードを読むことはあまり想定していなかったので、前提知識がないと読み解くのはけっこう大変かもしれません。個々のコードについてはまた時間のとれる時にちょくちょく書いていこうかと思いますが、とりあえず全体に関することだけいくつか書いておきます。もし理解できないようであればこれらについて調べてみてください。


関数プログラミング(FP)

Fsのコードが一般的なプラグインと大きく違うのは、プロトタイプやクラス構文のようなものが一切使われていないことだと思います。これはツクールで使用されている『オブジェクト指向プログラミング(OOP)』というスタイルではなく、『関数プログラミング(FP)』というスタイルを基調として書かれているからです(厳密にはFPを中心としつつ部分的にOOPを利用しています)。

OOPではオブジェクトと呼ばれる、データと処理が一体となった概念を操作するようにプログラミングしますが、FPではあるデータを別のデータに変換するようにプログラミングします。例えば、OOPではキャラクターはオブジェクトで、moveという処理はオブジェクトの状態を変化させることで位置を移動させます。一方で、FPではキャラクターはデータで、moveという処理は移動前のデータから移動後のデータを計算することで位置移動を表現します。OOPではオブジェクトが操作を持っているという考えなので、プロトタイプ等によってobj.move(x, y)のように書きますが、FPではオブジェクト(レコード)もデータのひとつでしかないので、newObj = move(obj, x, y)のように書きます。

FPで重要な概念としては次のようなものがあります。

* イミュータブル
* 純粋関数
* 高階関数
* カリー化と部分適用

イミュータブルは変化しないという意味で、FPではオブジェクトのプロパティを書き換えることはしません。そのため、プロパティの値を変更したい場合には{ ...obj, foo: newValue }のように、値をコピーして新しいオブジェクトをつくります。これによって、いつの間にか値が変わっていたというありがちなバグが回避できます。

純粋関数は入力(引数)の値が同じなら常に同じ出力(戻り値)を返すという性質を持った関数のことです。入力以外のもの(グローバル変数など)を参照していたり、入力がイミュータブルでない場合には、この性質は満たされません。純粋関数の何がうれしいかというと、入力と出力を見るだけでその関数が正しく動いているかどうかがわかることです。また、入力に対して出力が一意に決まるため、内部でどれだけ複雑な計算をしていたとしても、一度した計算は入力を見るだけで出力がわかります。これを利用した高速化手法をメモ化(memoization)と言います。

高階関数は関数を引数として受け取ったり、関数を戻り値として返す関数のことです。OOPでもイベントハンドラの設定などで使用したりしますが、FPではより多くの場面でこれを利用します。

カリー化は複数の引数を持つ関数をひとつの引数だけを持つ高階関数に変換することです。例えば、(a, b) => { ... }のような関数をa => b => { ... }のように変換します。カリー化をすることでいくつかの引数だけを予め適用することが可能になります(部分適用)。JavaScriptではbindという関数でカリー化されていない関数にも同様のことができます。

FPには他にも様々な概念がありますが(モナドなど)、すべて書くと終わらないのでこれくらいにしておきます。いろいろ書いた後でなんですが、あまり深入りするつもりがなければ、そういうスタイルもあるくらいに捉えた方がいいかもしれません。

各名前空間の参考情報

各名前空間の実装で参考にした内容についてです。

O:RustのOption<T>、その他OCamlのoptionやHaskellのMaybeなど。
R:RustのResult<T, E>、その他HaskellのEitherやElmのResultなど。
L:HaskellのListやLispのConsセルなど。
S:debugはNode.jsのREPLの表示を参考。
U:メモ化(memoization)。
G:PEG、packrat parser、parser combinator。
P、M、N:parser combinator。
Z:contextはReactのContext APIから。

MZで使用できるJavaScriptの機能

MZでChromiumのバージョンが上がったので使えるJavaScriptの機能もまた少し増えました。大きなところでは「Optional Chaining(?.)」と「null合体演算子(??)」かなと思います。これらについてはWeb上にたくさん記事があるのでそちらを参照してください。
 

げれげれ

ユーザー
とりあえず全体に関することだけいくつか書いておきます。もし理解できないようであればこれらについて調べてみてください。

あぁっ、何から何まで本当にすみません、ありがとうございます!!!
かけていただいた手間と時間を無駄にしないよう役立てさせていただきます!
 

fspace

ユーザー
好きでやってるので手間とかは気にしないでください。興味を持ってもらえただけでも嬉しいので。

何かわからない部分があれば質問ください。

あと、書き忘れてましたが、同リポジトリ内のテストを読むとそれぞれの関数がどんな挙動を想定しているかわかりやすいかもしれません(テストの読み方がわからない場合には"Jest"を調べてみてください)。

 
最後に編集:

fspace

ユーザー
TypeScriptの型定義ファイルを追加しました。


結構しっかりと書いたので複雑な推論もしてくれるはず。

TypeScript 4.0 の機能を利用しているので安定最新版のVSCodeだと一部エラーになります。次のアップデートで対応すると思いますが、その前に使用する場合には、ローカルインストールしたTypeScriptを利用するか、次の拡張機能を利用してください。

 

fspace

ユーザー
昨日、現在のVSCodeだと型定義がエラーになると書いたばかりですが、今日VSCodeの更新があって動くようになったみたいです。
 

fspace

ユーザー
v0.2.0 になりました。

主な変更点は次の通りです。
  • Pに配列や構造体をパースするarraystructentryを追加しました。関数的に処理したい場合のローレベルAPIで、P.makeによる略記は依然として利用できます。
  • parseで自動的にmakeを呼び出さなくなりました。そのため、P.parseで構造体をパースしたい場合には手動でP.makeを呼び出さなければならなくなりました。
  • M.attrNを追加し、N.makeを明示的に呼び出す必要がなくなりました。Nを利用せずにパースしたい場合のため、M.attrは依然として利用できます。
  • M.metaがオブジェクトではなく関数を返すようになりました。メモ化することにより、parsegetの流れを強制しなくなりました。
  • Z.extPropが関数の配列ではなく、オブジェクトを返すようになりました。
 

fspace

ユーザー
v0.3.0 になりました。

主な変更点は次の通りです。
  • P.expressionおよびN.expressionを追加しました。これによりevalFunctionコンストラクタを使わずにJavaScript風の式を関数化できるようになりました。
  • N.regexpのコールバック関数のシグネチャを変更しました。

P/N.expressionの仕様​


expressionはJavaScript風の式として記述された文字列を解析して、評価するための関数を返します。

JavaScript:
const { E, P } = Fs;
const f = P.parse("foo + bar", P.expression(E.NUMBER));

f({ foo: 1, bar: 2 });  // 3

expressionの引数には評価結果の型に応じて、E名前空間に定義されている次のいずれかの値を指定します。
  • NUMBER(数値)
  • BOOLEAN(真偽値)
  • ANY(任意)

v0.3.0で対応している式は次の通りです。
  • 数値リテラル
    • 指数表記 12e+34
    • 二進表記 0b00101010
    • 八進表記 0o52
    • 十六進表記 0x2A
    • 区切り文字 123_456_789
  • 真偽値リテラル
    • true または false
  • 変数
    • /[a-z$][a-z0-9$_]*/i
    • _から始まる識別子や日本語等は不可
    • 組み込み変数
      • Infinity
      • NaN
      • Math
  • 単項演算子
    • +
    • -
    • !
  • 二項演算子
    • +
    • -
    • *
    • /
    • %
    • **
    • ===
    • !==
    • <=
    • >=
    • <
    • >
    • &&(真偽値のみ)
    • ||(真偽値のみ)
  • 三項演算子
    • ?:
  • プロパティ参照
    • foo.bar
  • 配列参照
    • foo[42]
    • 同形式でのプロパティ参照は不可
  • 関数呼び出し
    • foo(bar, baz)
    • foo.bar()
expressionはJavaScriptとは異なり、暗黙の型変換を行いません。実行時に型検査を行い、期待する型でなければエラーとなります。

また、セキュリティ上の理由により次のことは禁止しています。
  • グローバル変数の参照
  • 特定の名前のプロパティの参照
    • prototype
    • constructor
    • _から始まるプロパティ(__proto__等)
  • プロパティ参照、配列参照、関数呼び出しの評価による特定の値の取得
    • globalThis
    • Object
    • Object.prototype
    • Function
    • Function.prototype
 

ノロワレ

ユーザー
MZの「次」ははじめから TypeScript で書けるようになって、ツクール側でよしなにcompileしてくれるとかだったらいいなぁと妄想してたらこのスッドレを見つけました。いやースゴいですね。力作ですね。
 

fspace

ユーザー
どもです。

なかなか時間をとれなくて一年たってもまだベータ版だったりしますが、一応今でもちょくちょく書いてます。次で少し大きめの変更を加えて、その後にひとつ機能追加、あとは少し様子見て正式リリースの予定なんですがいつになることやら……。

MZの次はTypeScript書けるといいですね。ただ、型システムとかモジュールとかはプロトタイプ書き換えによる今の拡張方法とは相性が悪いような気もしていて、まずはコアスクリプトを刷新して拡張用のインターフェースとかを整備してほしいかなとも思います。まあ、ツクールがそんな手間のかかることをする気はまったくしませんが……。
 

ノロワレ

ユーザー
ツクールが TypeScript 採用してくれたら勉強するのになーっていう他力本願なアレなんでアレなんですが、Ruby から js に変えた判断には「モバイル側にも進出したい」ということと同じくらい「敷居を下げたい」っていう意志は感じるので、またあれこれ敷居を高くするようになことはしたくないだろうなとは思います。
「プログラミングを勉強しなくても手軽にRPGをつくれる」っていうウリはシリーズのアイデンティティとしつつも、敢えてユーザーにコードを書かせる口をつくったので、そこの敷居も可能な限り下げたいのかなぁみたいな。

ただそれをやるんだったら js に変えるときに Ruby-like な後方互換(?)は捨てていちからフレームワークを起こすのがスジだったんじゃないの、とも思いますが。

MV は触ってないので知らないんですが、VX のときは使う *.rb のパスをリストファイルか何かに書いておいたら bat でツクール側によしなに流し込んでくれるやつとかはあったので、たとえば TypeScript を書く環境は自前で整備して、吐き出された js をよしなにMZ側に流し込む……とかならいけんくないのかなぁとか、まぁ誰かやってそうな気はしますけどどうなんでしょうね。

コーダー向けツクールとライト向けツクールを分離とかしたらいいんですかねぇ(苦笑
 

fspace

ユーザー
ツクールが TypeScript 採用してくれたら勉強するのになーっていう他力本願なアレなんでアレなんですが、Ruby から js に変えた判断には「モバイル側にも進出したい」ということと同じくらい「敷居を下げたい」っていう意志は感じるので、またあれこれ敷居を高くするようになことはしたくないだろうなとは思います。
自分はJSに変えたのは単にマルチプラットフォーム対応のために仕方なくじゃないかと思ってます。MVリリース当時のJSはかなり使いづらい言語でしたし、プロトタイプやthisの扱いなど、初心者にはわかりづらい概念を使いこなさないといけません。基礎的な機能の範囲ではJSよりもRubyの方がわかりやすいんじゃないかと思います。

MV は触ってないので知らないんですが、VX のときは使う *.rb のパスをリストファイルか何かに書いておいたら bat でツクール側によしなに流し込んでくれるやつとかはあったので、たとえば TypeScript を書く環境は自前で整備して、吐き出された js をよしなにMZ側に流し込む……とかならいけんくないのかなぁとか、まぁ誰かやってそうな気はしますけどどうなんでしょうね。
TypeScriptで書く環境を整えること自体はできますよ。ただTypeScriptをそのまま実行できるわけではないので、JavaScriptに変換して配布する必要があり、そのままだと読みづらいのでフォーマッタをかけたりソースマップを付けたりする必要があり、といろいろ面倒くさいですが。ライブラリとして配布するなら型定義ファイルも必要ですしね。

コーダー向けツクールとライト向けツクールを分離とかしたらいいんですかねぇ(苦笑
自分はコーダー向けの道具がプラグインで、ライト向けの道具がイベントコマンドだと思ってます。プラグインはもう少しプログラミングのための環境を重視してもいいんじゃないかと。また、少し話はずれますが、今のツクールはここの境界が曖昧なために、プログラミングの入門書も読まないままにプラグインで試行錯誤したり、プログラミングはできないからと言いつつスクリプトコマンドでどうにかしようとしたりする人が多くて、無駄な苦労をしてるなぁという印象があります。

あと本当にプラグイン開発のハードルを下げたいのなら、真っ先にすべきことはまともなドキュメントを用意することですよね。できる人でさえコアスクリプトを解析しないといけなくてつらいのに、それを初心者にやらせるのは無理があると思います。
 
トップ