ツクールMZ RFC

fspace

ユーザー
このスレッドはツクールMZを改善するための提案をする非公式の場所です。

自分の考えている改善案がどの程度の賛同を得られるかを確かめたり、より良い方法を議論するために使ってください。

また、雑多な内容が入り混じることを防ぐため、このスレッドで扱う内容はプラグイン開発者の観点によるものに限定します。プラグインとは無関係な同梱素材やUI等の改善案については別のスレッドを立ててください。

改善案の提案は必ず規定のフォーマットに従って記述してください。テンプレートに含まれる各項目についてよく考えて記述し、必ず具体的な解決方法を提示してください。問題の認識のみで具体的な改善案がない場合には、専用のスレッドを立てて改善案を募った後にこのスレッドに提案するようにしてください。

ちなみに、RFCとはRequest for Commentsの略で「コメント募集」の意味であり、IETFがそれを目的として公開する技術仕様のことであり、それを真似た仕様改善のための提案文書やらプロセスやらのことです。技術系の提案をするためのスレッドとしてそれっぽいかなと思って名付けてみました。なんとなく参考にはしてますが雰囲気だけなのであまり深くは考えないこと。
 

fspace

ユーザー

#00 提案のテンプレート​

  • バージョン:1.0.0
  • 状態:提案
  • 依存:#00, #00
  • 対立:#00, #00
  • 関連:#00, #00

バージョンはメジャー、マイナー、パッチの順で三つの数値をドット区切りで記入する。誤字脱字の修正でパッチ、内容の追加・削除・変更でマイナー、全体の刷新でメジャーのバージョンを上げる。必ず1.0.0から開始する。

状態は「提案」を初期値として、実際に実装された際に「完了」、議論の結果取り下げる際に「棄却」など。

依存、対立、関連は関係する他の提案が存在する場合にそのIDを記入する。存在しない場合には「なし」と記入する。

概要​

提案の概要。

数行以内で記述する。

背景・動機​

提案の背景や動機。

現状の問題点について可能な限り具体例を上げて説明する。

内容​

提案の内容。

必ず具体的な実現方法を示す。

懸念点​

提案の懸念点。

提案内容によって生じうる問題とその解決方法あるいは対応について説明する。

存在しない場合には「なし」と記入する。

代替案​

提案の代替案。

問題を解決する別の方法やバリエーションについて説明し、提案した方法と比較する。

存在しない場合には「なし」と記入する。

変更履歴​

提案文書の変更履歴。

いつどのような変更を行ったかを記録する。

版​
日付​
変更内容​
1.0.0​
YYYY-MM-DD​
初版。​
1.0.1​
YYYY-MM-DD​
誤字の修正。​
1.1.0​
YYYY-MM-DD​
懸念点に○○を追加。​
2.0.0​
YYYY-MM-DD​
仕様を刷新。​
 

fspace

ユーザー

#01 拡張データ(メタデータの改善)​

  • バージョン:1.0.0
  • 状態:提案
  • 依存:なし
  • 対立:なし
  • 関連:なし

概要​

データベース内の各データに設定可能なメタデータについて、プラグインパラメータやプラグインコマンドと同様の入力方法を導入する。

背景・動機​

ツクールMZでは、ツクールMVで文字列として表現していたプラグインコマンドに対してアノテーションを導入し、プラグインパラメータと同様の入力方法が可能となった。これにより、プラグインコマンドの文字列をプラグイン内で解析する手間が大幅に削減され、プラグインコマンドが活用しやすくなった。

しかし、ツクールMVで同様に文字列として表現されているメモ欄のメタデータについては、ツクールMZでもその仕様を引き継いでいる。そのため、各プラグインは依然としてメモ欄のメタデータを文字列として解析しなければならない。これはプラグイン開発者にとって手間であると同時に、ユーザにも特別な記法を学ぶ手間を強いる。

また、メモ欄のメタデータ解析を各プラグインが独自に行うと、メタデータの記述形式の一貫性が失われる問題がある。例えば、次のような記述がどのように解釈されるかはプラグインによって様々である。

JavaScript:
// 前後に空白を含む場合
// trimを呼び出しているか否かによって空白の有無が変化
"<foo: bar >"

// 数値の前に0を付けた場合
// Numberで解析した場合は10進数、evalで解析した場合は8進数として評価される
"<foo:0042>"

// 数値の後に数字以外の文字を付けた場合
// Numberで解析した場合はNaNとなり、parseIntで解析した場合は後ろの文字は無視される
"<foo:42%>"

// カンマとスペースで要素を区切った場合
// splitの区切り文字の指定と各要素の解析方法の組み合わせによって結果は様々
"<foo:1 , 2 , 3>"

また、文字列の解析では未使用素材削除機能への対応も不十分となってしまう。例えば、オーディオファイルを指定する場合、プラグインではオーディオファイルとともに音量・ピッチ・位相も設定できるのが通例となっているが、これを単一のメタデータで行おうとすると未使用素材削除機能への対応ができなくなってしまう。

さらに、メタデータとして使用可能な文字に関する問題もある。例えば、カスタマイズ性の高いプラグインを製作する場合、メタデータとして計算式を設定したい場合があるが、メタデータ内では>の文字が利用できないため比較演算を直感的に表現できない。

加えて、複数行に及ぶデータを設定する場合の記法の視認性もよくない。複数行のデータ記述を避けるために、メモ欄を独自に解析して、同名のメタデータを複数設定可能としたり、XML風の記法を導入しているプラグインも散見される。しかし、プラグイン側でこのような独自記法を導入してしまうと、データベースを解析するツールの導入が困難になってしまう。また、同一のメモ欄に複数の独自記法が混在することで競合を起こす可能性もある。

メタデータに対して、プラグインパラメータ同様の入力方法を導入することでこれらの問題は解決できる。

内容​

プラグインパラメータ同様の入力方法が可能なメタデータを「拡張データ」と呼称し、以下のように実装する。

プラグインに対して次のアノテーションを追加する。

アノテーション​
説明​
@extdata
拡張データID。当該拡張データに対する記述の開始を宣言。​
@for
拡張データを付加可能な対象データのリスト。@noteDataと同様の値。​
@type
拡張データの種類。プラグインパラメータの@typeと同様の値。追加制約も同様。​
@default
拡張データの既定値。拡張データが設定されなかった場合の値ではなく設定時の初期値。​
@text
拡張データ表示名。​
@desc
拡張データ説明文。​

JavaScript:
/*:
 * ...
 *
 * @extdata PassiveEffectState
 * @for skills items
 * @type state
 * @default 0
 * @text パッシブ効果ステート
 * @desc スキルの習得中あるいはアイテムの所持中に付加されるステート。
 */

エディタは各プラグインに記述されたこれらのアノテーションを解析し、メモ欄を有する各データに対して拡張データ設定用のUIを追加する。ユーザは拡張データの追加を指示し、プラグイン名、拡張データ名の順に選択することで既定値の拡張データを追加できる。その後、追加された拡張データの値を編集して設定が完了する。値の編集はプラグインパラメータと同様の方法により行う。

追加された拡張データは各対象データのextプロパティにオブジェクトとして値を保存する。オブジェクトはプロパティとして、プラグイン名をキーとしたオブジェクトを持ち、さらにそのオブジェクトは、拡張データIDをキーとして設定値を保持する。設定値の形式はプラグインパラメータの形式に準ずる。

各プラグインは各データオブジェクトからこれらの値を読み込む。

JavaScript:
const PLUGIN_NAME = "MyPlugin";
const EXT_PASSIVE_EFFECT_STATE = "PassiveEffectState";

const parsePassiveState = item => {
    const extData = item.ext[PLUGIN_NAME]?.[EXT_PASSIVE_EFFECT_STATE];
    const stateId = extData !== undefined ? Number.parseInt(extData, 10) : 0;
    return stateId;
};

これにより前述した現行メタデータの問題点は次のように解決される。

問題点​
解決理由​
開発者の手間 ​
複雑なデータに対してもプラグインパラメータ同様の解析が可能となる。​
ユーザの手間 ​
プラグインごとの記法を覚える必要がなく、GUIに従った入力が可能となる。​
記法の一貫性 ​
文字列ではなくGUIによる指定が可能となるため、入力方法が統一される。​
未使用素材解析​
プラグインパラメータと同様の解析が可能となる。​
使用可能文字 ​
メタデータ範囲の解析が不要なため、任意の文字が使用可能となる。​
複数行データ ​
配列や構造体による整理が可能となる。​

懸念点​

既存のプラグインへの対応​

拡張データと現行メタデータはそれぞれ独立して存在が可能である。

そのため、現行メタデータを非推奨としつつ機能としては存続させることで、既存のプラグインの挙動を壊さずに拡張データの導入が可能である。

汎用拡張データ​

現行メタデータはまれにスクリプトコマンドから直接取得して利用される。また、複数のプラグインでメタデータを共有するケースも存在する。このような場合、プラグインのアノテーションによる拡張データでは直接的には対応できない。

これらのケースに対応するために、特定のプラグインに関連付けられない汎用の拡張データを設定可能とする方法が考えられる。予約した名前でextプロパティに拡張データを保存することで、これらの拡張データは表現できる。ただし、エディタのUI上で拡張データのIDや種類を設定できる必要があるため、特別なUIの実装が必要である。あるいは、共有するケースに限れば、@scope@namespaceのようなアノテーションを追加し、プラグイン名以外のIDで拡張データを設定可能とする方法も考えられる。この場合、複数のプラグインが同一のIDで異なる種類の拡張データを指定した場合の対応が必要となる。

そもそもスクリプトコマンドから直接メタデータを取得するのは特殊なテクニックであるため、拡張データでは対応しないという方針も考えられる。複数のプラグインで共有するケースにしても、拡張データ管理用のプラグインとそれを使用するプラグインとに分割し、管理用プラグインを介して取得する方式とすることで共有は可能である。

代替案​

複数値の拡張データ​

拡張データは単一の値としたが、プラグインパラメータやプラグインコマンドと同様に複数の値を指定可能とすることも考えられる。

JavaScript:
/*:
 * ...
 *
 * @extdata PassiveEffect
 * @for skills items
 * @text パッシブ効果
 * @desc スキルの習得中あるいはアイテムの所持中に発生する効果。
 *
 * @param StateId
 * @type state
 * @default 0
 * @text ステート
 * @desc パッシブ効果発生時に付加されるステート。
 *
 * @param Condition
 * @type switch
 * @default 0
 * @text 条件
 * @desc ステートが付加される条件を表すスイッチ。
 */

しかし、拡張データは単純なデータであるケースが多く、このような指定方法をとった場合、エディタUIによる設定手順が1ステップ増えてしまう可能性がある(あるいはエディタUIに工夫が必要で実装コストがかかる)。複数値の指定は構造体で代替できるため、拡張データは単一の値としてモデル化した方がよいと考える。

名称​

「拡張データ」は「メタデータ」の改良であり、機能の目的は同一である。そのため、単に「新形式のメタデータ」と呼称した方が理解はしやすい。

しかし、互換性のために併存が必要なため、同一名称では混乱する可能性があり、コード上での名前の衝突回避も必要となる。

また、現行メタデータの用途は、データ自体に関するデータを意味する「メタデータ」という言葉にマッチしていない。そのため、標準のデータを拡張するデータという意味で「拡張データ」を採用した。前述の汎用拡張データを考慮しないのであれば、「プラグインデータ」という名称も考えられる。

変更履歴​

版​
日付​
変更内容​
1.0.0​
2021-02-24​
初版。​
 

fspace

ユーザー

#02 直和型パラメータ​

  • バージョン:1.0.0
  • 状態:提案
  • 依存:なし
  • 対立:なし
  • 関連:なし

概要​

プラグインコメントにより直和型を定義し、プラグインパラメータの@typeとして定義した直和型を指定可能にする。

背景・動機​

直和型(sum types)は、有限個の構造のうちのいずれかの構造であるような型であり、判別共用体(discriminated union types)、タグ付き共用体(tagged union types)、ヴァリアント型(variant types)とも呼ばれる。また、構造体、レコード、タプルなどの直積型(product types)と合わせて、代数的データ型(algebraic data type, ADT)とも呼ばれる。

JavaScriptは言語機能としては直和型を提供しないが、タグを表すプロパティを用いて直和型を次のように表現できる。

JavaScript:
// 構造A
const valueA = {
    tag: 'typeA',
    number: 42,
};

// 構造B
const valueB = {
    tag: 'typeB',
    string: "foo",
};

// 構造Aまたは構造Bのどちらか
const sumType = Math.random() < 0.5 ? valueA : valueB;

// タグによってどちらの構造か判定
switch (sumType.tag) {
    case 'typeA':
        console.log(`number: ${sumType.number}`);
        break;
    case 'typeB':
        console.log(`string: ${sumType.string}`);
        break;
    default: throw new Error(`unknown tag: ${sumType.tag}`);
}

プラグインパラメータでは複数の異なる構造のデータのうち、いずれかの構造のみの指定が必要なケースがしばしば存在する。

例えば、複数のスイッチや変数の値によって画像表示の有無を切り替えるプラグインを考える。

現在の仕様でこのプラグインのパラメータを設計すると次のようになる。

JavaScript:
/*:
 * ...
 *
 * @param ImageFile
 * @type file
 * @dir img/pictures/
 * @text 画像ファイル
 * @desc 表示する画像ファイル。
 * 
 * @param Conditions
 * @type struct<Condition>[]
 * @default []
 * @text 表示条件
 * @desc 画像を表示するAND結合の条件。
 */

/*~struct~Condition:
 * @param Type
 * @type select
 * @option スイッチ
 * @value switch
 * @option 変数
 * @value variable
 * @default switch
 * @text 条件タイプ
 * @desc 条件判定の方法。
 * 
 * @param SwitchValue
 * @type boolean
 * @on ON
 * @off OFF
 * @default true
 * @text スイッチ値
 * @desc 条件を満たすときのスイッチの値。
 * 条件タイプが「スイッチ」のときのみ指定。
 * 
 * @param VariableValue
 * @type number
 * @default 0
 * @text 変数値
 * @desc 条件を満たすときの変数の値。
 * 条件タイプが「変数」のときのみ指定。
 */

このときCondition構造体は、「条件タイプ」の値によって、「スイッチ値」か「変数値」のどちらかの値しか必要としないにも関わらず、両方の値を保存してしまう。また、エディタ上でも両方の入力項目が表示されてしまい、ユーザを混乱させてしまう。これは「条件タイプ」の種類が増えるほど顕著な問題となる。

また、別の実現方法として次のような設計も考えられる。

JavaScript:
/*:
 * ...
 *
 * @param ImageFile
 * @type file
 * @dir img/pictures/
 * @text 画像ファイル
 * @desc 表示する画像ファイル。
 * 
 * @param SwitchConditions
 * @type struct<SwitchCondition>[]
 * @default []
 * @text 表示スイッチ条件
 * @desc 画像を表示するAND結合のスイッチ条件。
 * 
 * @param VariableConditions
 * @type struct<VariableCondition>[]
 * @default []
 * @text 表示変数条件
 * @desc 画像を表示するAND結合の変数条件。
 */

/*~struct~SwitchCondition:
 * @param Value
 * @type boolean
 * @on ON
 * @off OFF
 * @default true
 * @text 値
 * @desc 条件を満たすときのスイッチの値。
 */

/*~struct~VariableCondition:
 * @param Value
 * @type number
 * @default 0
 * @text 値
 * @desc 条件を満たすときの変数の値。
 */

この場合、不要な値が保存されることはないが、二項目への分割により最終的な表示条件がわかりづらくなってしまっている。また、条件の種類を増やすにつれて多数の設定項目を並べなければならない。

直和型パラメータにより、いずれかの構造をとるパラメータが表現可能になるとこれらの問題は解決される。

内容​

プラグイン中のコメントによって直和型を次のように定義する。

JavaScript:
/*~union~SumTypeName:
 * @tag typeA
 * @type struct<SumTypeName$typeA>
 * @text 構造A
 * @desc 数値を記憶する構造。
 * 
 * @tag typeB
 * @type struct<SumTypeName$typeB>
 * @text 構造B
 * @desc 文字列を記憶する構造。
 */

/*~struct~SumTypeName$typeA:
 * @param number
 * @type number
 * @default 0
 * @text 数値
 * @desc 構造Aの数値。
 */

/*~struct~SumTypeName$typeB:
 * @param string
 * @type string
 * @text 文字列
 * @desc 構造Bの文字列。
 */

このうち、SumTypeName$typeAおよびSumTypeName$typeBは単なる識別子であり、~struct~以下は既存の構造体の定義と同一である。

直和型の定義は~union~から始まるブロックコメントにより開始し、~union~の直後に直和型の識別子を指定した後、:で識別子およびヘッダの定義を完了する(構造体と同様に:の直後に言語コードを指定可能とする)。

直和型の各構造の定義は@tagによりタグ名を指定することにより開始する。各構造にはパラメータ定義と同様に、@type@text@descを指定可能とする。ただし、@typeに構造体以外が指定された場合はエラーとし、@typeが省略された場合には空の構造体が指定されたものとして解釈する。

このように定義された直和型は@typeによって@type union<SumTypeName>のように指定する。

エディタUIは直和型パラメータの入力の際、構造を選択するドロップダウンリストと選択された構造に対応する構造体の入力UIを表示する。

エディタによって入力された直和型パラメータは、次のプロパティが定義されたオブジェクトのJSON文字列としてシリアライズする。

key​
value​
tag
選択された構造に設定されているタグ名。​
value
選択された構造に対応する構造体の入力値をシリアライズしたJSON文字列。​

典型的な直和型パラメータの解析処理は次の通りである。

JavaScript:
const parseSumType = s => {
    const { tag, value } = JSON.parse(s);
    switch (tag) {
        case "typeA": return { tag, ...parseTypeA(value) };
        case "typeB": return { tag, ...parseTypeB(value) };
        default: throw new Error(`unknown tag: ${tag}`);
    }
};

const parseTypeA = s => JSON.parse(s, (key, value) => {
    switch (key) {
        case "": return value;
        case "number": return Number(value);
        default: throw new Error(`unknown property: ${key}`);
    }
});

const parseTypeB = s => JSON.parse(s, (key, value) => {
    switch (key) {
        case "": return value;
        case "string": return value;
        default: throw new Error(`unknown property: ${key}`);
    }
});

const TYPE_A_DATA = `{"tag":"typeA","value":"{\\"number\\":\\"42\\"}"}`;
const TYPE_B_DATA = `{"tag":"typeB","value":"{\\"string\\":\\"foo\\"}"}`;

console.log(parseSumType(TYPE_A_DATA)); // { tag: "typeA", number: 42 }
console.log(parseSumType(TYPE_B_DATA)); // { tag: "typeB", string: "foo" }

副次的効果​

直和型パラメータは@type selectを一般化した概念である。そのため、直和型パラメータは@type selectと同様の表現が可能である。

@type select@optionおよび@valueにより選択肢とその値を定義できるが、これには次の問題がある。

  • 複数のパラメータで選択肢を共有できない。
  • パラメータ等がID(@param)の後に表示名(@text)を指定するのに対し、
    選択肢は表示名(@option)の後にID(@value)を指定するようになっており、一貫性がない。

直和型パラメータは@type selectのこれらの問題を解決するものにもなっている。

懸念点​

直和型パラメータの既定値​

直和型パラメータの値はやや複雑であり、@defaultにより既定値を指定する際には注意して記述する必要がある。しかし、これは直和型パラメータに限った問題ではなく、複雑な構造体パラメータにも同様の問題があるため、@defaultの仕様改善により別途対応するのがよいと考える。

代替案​

型の定義方法​

提案手法では直和型の定義に複数の構造体定義が必要となる。

次のような定義方法によりこれらはひとつにまとめることもできる。

JavaScript:
/*~union~SumTypeName:
 * @tag typeA
 * @text 構造A
 * @desc 数値を記憶する構造。
 * 
 * @param number
 * @type number
 * @default 0
 * @text 数値
 * @desc 構造Aの数値。
 *
 * @tag typeB
 * @text 構造B
 * @desc 文字列を記憶する構造。
 *
 * @param string
 * @type string
 * @text 文字列
 * @desc 構造Bの文字列。
 */

しかし、このように定義した場合、ひとつひとつの構造が大きくなった際に全体を把握しづらくなるおそれがある。また、複数の似た直和型を定義する際に構造の再利用もできない。

また、次のような定義方法も考えられる。

JavaScript:
/*~union~SumTypeName.typeA:
 * @typetext 構造A
 * @typedesc 数値を記憶する構造。
 *
 * @param number
 * @type number
 * @default 0
 * @text 数値
 * @desc 構造Aの数値。
 */

/*~union~SumTypeName.typeB:
 * @typetext 構造B
 * @typedesc 文字列を記憶する構造。
 *
 * @param string
 * @type string
 * @text 文字列
 * @desc 構造Bの文字列。
 */

こちらは構造としての把握はしやすいが、やはり複数の直積型での再利用はできない。また、プラグイン中のコメント全体を確認しなければ型の定義が確定しないため、構文としてやや解析しづらい。

これらの欠点より、多少記述が増えても構造体を利用した方法がより適切と考える。

シリアライズ形式​

提案手法では直和型パラメータをtagvalueの二つのプロパティを有するオブジェクトとしてシリアライズした。しかし、パラメータの解析処理として示した例では、valueのデシリアライズ結果にtagの値を埋め込むようにデシリアライズしている。ならば、最初からtagを埋め込んだオブジェクトをシリアライズする方法も考えられる。

しかし、この方法にはタグを示すプロパティ名と同名のプロパティを構造体に含むことができないという問題がある。そのため、一般的に使用されうるtagというプロパティ名は避け、$tagのようなプロパティ名でタグを表すようにした上で、構造体がこのプロパティ名を含んでいないことを確認する必要がある。

方法の利点として、シリアライズした文字列がいくらか単純とはなるが、欠点と比較してそれを上回るものではないと考える。

変更履歴​

版​
日付​
変更内容​
1.0.0​
2021-02-24​
初版。​
 

fspace

ユーザー

#03 JsExtensionsの段階的削除​

  • バージョン:1.0.0
  • 状態:提案
  • 依存:なし
  • 対立:なし
  • 関連:なし

概要​

JavaScriptのビルトインプロトタイプを拡張するJsExtensionsを将来的に削除するための準備をする。

背景・動機​

コアスクリプトではJsExtensionsとして、JavaScriptのビルトインプロトタイプの拡張を行っているが、一般にこれはバッドプラクティスとして知られている。

将来JavaScriptに追加されるAPIの挙動を壊してしまう、ビルトインプロトタイプの拡張を行うライブラリ同士が競合してしまう、などがその理由である。

また、プログラミング初心者が多く参加するツクールのプラグイン開発においては、JavaScriptに標準で存在する機能のように見えるために、初心者を混乱させてしまう問題もある。例えば、「JavaScript Array clone」のようにウェブで検索すると、多数の検索結果がヒットするが、この中から本当に求めている情報を見つけることは困難である。

加えて、コアスクリプトによる拡張にはJavaScriptのバージョンアップ等により、もはや利点に乏しいものも存在する。例えば、clonepadZeroはそのまま記述しても可読性はあまり変わらない。

JavaScript:
// clone
array.clone();
array.slice();

// padZero
string.padZero(4);
string.padStart(4, "0");

また、可読性の点で有意なものであっても、それをプロトタイプの拡張によって実現する必要はない。例えば、clampmodは容易に実装が可能である上、コアスクリプトの機能として提供するとしても通常の関数でよい。

JavaScript:
const clamp = (x, min, max) => Math.min(Math.max(x, min), max);
const mod = (x, n) => ((x % n) + n) % n;

// clamp
x.clamp(min, max);
clamp(x, min, max);

// mod
x.mod(n);
mod(x, n);

このようにJsExtensionsの内容は整理が必要であり、現在のプロトタイプを拡張する関数についてはすべて削除すべきである。

しかし、プラグインの互換性を考慮すると性急には削除できないため、将来的な削除に向けて、まずはプラグイン開発者がこれらを使用しないようにする準備が必要である。

内容​

JsExtensionsの各関数について、contains同様に@deprecatedを付加して非推奨とし、将来的に次の対応を予定する。

関数​
対応​
代替​
Array.prototype.clone
削除​
slice
Array.prototype.contains
削除​
includes
Array.prototype.equals
変更​
Array.prototype.remove
削除​
filter
Math.randomInt
移動​
Number.prototype.clamp
移動​
Number.prototype.mod
移動​
Number.prototype.padZero
削除​
padStart
String.prototype.contains
削除​
includes
String.prototype.format
移動​
String.prototype.padZero
削除​
padStart

これらの対応に向けた準備を以降に示す。

削除予定の関数​

削除予定の関数については、非推奨とするのみで他の変更は行わない。

プラグイン開発者には代替の関数を用いるように周知させる。

randomIntclampmod

Math.randomIntNumber.prototype.clampNumber.prototype.modの三つの関数は、新たにMathExtオブジェクトを定義し、その関数として再定義する。

JavaScript:
globalThis.MathExt = (() => {
    const randomInt = max => Math.floor(Math.random() * max);
    const clamp = (x, min, max) => Math.min(Math.max(x, min), max);
    const mod = (x, n) => ((x % n) + n) % n;
    return { randomInt, clamp, mod };
})();

equals

Array.prototype.equalsUtilsの関数として、arrayEqualsに名前を変更して次の通り再定義する。

JavaScript:
Utils.arrayEquals = function (a, b) {
    if (a === b) return true;
    if (!Array.isArray(a) || !Array.isArray(b)) return false;
    if (a.length !== b.length) return false;
    return a.every((v, i) => this.arrayEquals(v, b[i]));
};

format

String.prototype.formatTextManagerの関数として再定義する。

JavaScript:
TextManager.format = function (template, ...args) {
    return template.replace(/%([0-9]+)/g, (_, n) => args[Number(n) - 1]);
};

関数呼び出し箇所の書き換え​

コアスクリプト中でJsExtensionsの関数を呼び出している部分を代替関数の呼び出しに置き換える。

ただし、String.prototype.formatの呼び出し箇所のうち、文字列リテラルを置換元文字列としている箇所についてはテンプレートリテラルによって置き換える。

懸念点​

filterによるremoveの代替​

Array.prototype.removeArray.prototype.filterにより代替するが、これらの挙動には元の配列を書き換えるかどうかの違いがある。そのため、配列を参照として扱う必要のあるコードではfilterによる単純な代替はできない。

しかし、このようなコードはあまり多くなく、少なくともコアスクリプト中のremovefilterによる単純な置き換えが可能である。また、filterによる代替が不可能な場合でも、各プラグインでremoveに相当する関数は容易に実装できる。

したがって、これによる影響はあまり大きくないと考える。

formatの定義場所​

String.prototype.formatTextManagerに再定義するが、formatの呼び出し場所によってはTextManagerへの依存が好ましくない可能性もある。TextManagerではなくUtilsに定義するとこの問題は解決できる。

しかし、formatはメッセージデータの形式に依存した仕様となっており、任意の文字列の表現や数値等の形式指定ができないなど、Utilsに定義するにはやや汎用性に欠ける。

そのため、formatはメッセージの生成を目的とする関数としてTextManagerに定義し、それ以外を目的とする場合には、各プラグインで独自の関数を定義する方がよいと考える。

削除のタイミング​

本提案にはJsExtensionsを削除する具体的な時期は含まれない。

プラグインには継続的に保守されていないものも多く、またビルトインプロトタイプの拡張が直ちに問題を起こすこともないため、削除のタイミングは具体的な問題が発生した際でよいと考える。

本提案は問題が発生した際の被害を最小限に抑えるものであり、また、JavaScriptのバッドプラクティスにプラグイン開発者を誘導しないためのものである。

代替案​

MathExtの有無​

MathExtに含まれる各関数はすべて一行で容易に実装可能である。そのため、これらの関数はすべて削除し、各プラグインで必要に応じて定義する方針も考えられる。

しかし、これらはコアスクリプト中でも利用箇所が多く、実装がやや直感的でない関数も含まれるため、MathExtとして定義する方がよいと考える。

命名​

本提案ではMathExtarrayEqualsについて新たに命名した。

MathExtは数学関連関数を含むMathの拡張(Extension)を意味する。"extension"の略語としては"ext"が一般的であるが、同じくコアスクリプトに含まれるJSONの拡張と思われるクラスはJsonExと命名されている。そのため、MathExという名前も考えられる。

arrayEqualsは元となった関数のequalsに、移動によって失われた「配列」の文脈をarrayとして連結したものである。しかし、二引数関数となり、thisの性質を示すものではなくなるため、"s"を除いてarrayEqualという名前も考えられる。また、略語を用いてarrayEqでもよい。

本提案ではより一般的と考える名前を採用した。

変更履歴​

版​
日付​
変更内容​
1.0.0​
2021-02-24​
初版。​
 

fspace

ユーザー
とりあえずサンプルとして三つ書いてみました。

小さなことでもいいので興味あればどうぞ。
 

laputa

ユーザー
お疲れさまです。

プラグインはいまだつくれない素人ですが、個人的にプラグインの作成が簡素化され、
競合問題が回避しやすくなり、より多面的なプラグイン効果が期待できそうな気がします。

今後のMZに期待を込めて。
 

fspace

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

ツクールはあちこちで「あんな機能が欲しい」とか「こうだったらよかったのに」みたいな意見を見かけるわりには、こういったことを真剣に議論しているのを見かけないので、それができる場所になればいいなと思ってスレを立ててみました。開発部にフィードバックするにしても、ここへのリンクで済ませられれば楽ですしね。

まあ、現在の反応を見る限りちょっと望み薄ですが……。
 
プラグインパラメータにおいて、@selectの@optionに@valueを指定すると
@valueのほうが表示されてしまうんですよね。
例:
上・下・左・右という4項目から選びたいとして「上」に「@value 1」と設定した場合
プラグインパラメータに「1」と表示されるのを何とかしてほしいです。
この場合は「上」と表示された方が見やすいですよね。
 

fspace

ユーザー
ドロップダウンリストの部分じゃなくて、パラメータ一覧の値の欄のことですかね。確かにあそこは実際の値よりもプラグインのユーザにわかりやすい表示の方がいいですよね。

もし時間があれば、各@typeごとにどう表示すべきか考えて、上のサンプルみたいな感じでまとめてもらえると助かります。それをもとにあれこれ言えるので。
 
専門的な書き口がちょっとわからないので、どう書けばいいか見当もつきません。
ともかく自分の提案としては「プラグインパラメータに「値」ではなく「名称」を表示してほしい」です。
もちろん内部的には、値を取得することができます。
 

fspace

ユーザー
了解です。

もし他に同じようなことを考えている人がいたら、仕様としてまとめてもらえると嬉しいです。
 
トップ