-

タシラカ

ユーザー
RPGツクールMZではコアスクリプトをES6準拠で全面的に書き直している事が発表されました
MVではリリース当初は使えなかったもののいつだったかのアップデートでNW.jsが更新されて以降、
プラグイン作者の皆さんもES6構文を取り入れてる方も多いと思います。
ES6の機能を使うだけならNW.jsを更新するか、アツマールの場合は実行ブラウザが対応するだけでMVでも使用可能です。
ですがrpg_core.js,rpg_managers.js,rpg_objects.js,rpg_sprites.js,rpg_windows.js,rpg_scenes.js(以下纏めてコアスクリプト)
を全てES6の新機能に合わせて書き直した、というとどれだけ手間を掛けてくれたのか想像に難くないと思います。

まずES6とは何か?
javascript-ES5の次のバージョンがES6です。そのままですね。
2020/7現在でES10まで発表されている為最新ではないのですが、ES6を機に大幅に機能が追加された為このバージョンで一区切りされる事が多いです。
MVでもES6が使えるようにMZの環境下でも従来(ES5)の文法は使用可能です。
少し手直しすればそのまま使えるかもしれない、と皆さんが言っているのはそれも一因です。(整合性チェックに苦労するので大差ないと思いますけど)
しかしせっかく便利な機能が追加された、というのなら使ってみたいと思うのが技術者というものです。
自分はJSはツクール関係の知識しか持ち合わせていない上に、最近までMVから離れていたのでニワカ知識も良い所なのですが
自分自身の備忘録とES6の啓蒙も兼ねて、主な変更点(妄想予想)、押さえておきたいポイントを中心に書き連ねていきたいと思います。
理解が間違っているところがあれば指摘してもらえると幸いです。
一応、RGSSかMVプラグインの知識が一通りある事を前提に新旧の差異を中心に流していきます。車輪の再発明も多分に含みますがご容赦を。

7/31追記:重要:開発協力している海外スタッフからコアスクリプトではconst以外ES6の機能を使っていないとの通達がありました。
        プラグイン側では問題無く使用出来るので覚えておいて損はありませんが、
        MZのコアスクリプトを読み解くのに新機能を覚える必要は無いと思います。以降、芸の肥やし程度に気楽に読んでください。
 
最後に編集:

タシラカ

ユーザー
・class構文
ES6最大の特徴ですね。
以下では同じ処理をしています。(微妙に間違っているかもしれない)
JavaScript:
// ES5
function Base() {
    this.initialize.apply(this, arguments);
}
Base.prototype.initialize = function () {
};
function Derived() {
    this.initialize.apply(this, arguments);
}
Derived.prototype = Object.create(Base.prototype);
Derived.prototype.constructor = Derived;
Derived.prototype.initialize = function() {
    Base.prototype.initialize.call(this);
};
// ES6
class Base {
    constructor(...args){
        this.initialize(args)
    }
    initialize(...args) {
    }
}
class Derived extends Base {
    constructor(...args) {
        super(...args);
    }    
    initialize(...args) {
        super.initialize(...args);        
        // 任意の処理
    }
}
特にRGSSから入った人はES5構文は胸焼けするくらい冗長に感じたと思いますが
ES6ではかなりクラスベースに近い文法になっています。superは親クラスの参照です。
RGSSと違ってsuper()だと親クラスのコンストラクタ呼び出しになるので混同しないよう注意してください。(一敗)
ツクールMZのコアスクリプトでは、全てclass構文に置換していると予想します。これを使用するだけでも相当印象が変わります。

追記:7/27/class内の処理を訂正しました
 
最後に編集:

タシラカ

ユーザー
・let/const変数宣言
ES5では変数を宣言する時にvarを使用していました。
これには途中で変数宣言しても、スコープ最上部で宣言したものと見做されるという奇妙な特徴があります。
JavaScript:
console.log(hoge); // hoge === undefined
var hoge = 0;
console.log(hoge); // hoge === 0
実行するとundefinedと0が表示されるはず。
つまり最初のconsole.logの時点で初期値undefinedの変数hogeが存在している事になります。
この奇妙な挙動を解消したものがlet変数宣言です。
JavaScript:
console.log(hoge); // ここでエラーが出る
let hoge = 0;
console.log(hoge);
これを実行すると「hoge is not defined」というエラーが出ます。文法に即した挙動になりました。
このletに更に再代入禁止という特性を加えたものがconst変数宣言です。
JavaScript:
const hoge = 100;
hoge = 10; // ここでエラーが出る
これを実行すると「Assignment to constant variable.」というエラーが出ます。
つまりconst変数は宣言と同時に代入した初期値から変化しない事が保証されます。
小型のプラグインやスクリプトコマンドを使う分にはあまり意識する必要はありませんが規模が大きくなるとvar宣言はバグの温床になります。
ツクールMZのコアスクリプトでは基本的にvarの代わりにconstが、constでは代用出来ない箇所ではletに代替していると予想します。
おそらくvarを使っている箇所は一つも残っていないと思うので読み解く上で留意しておいた方が良いでしょう。

・テンプレートリテラル
これは文字列の中に変数を埋め込む事が出来る、という普通に便利な機能です。
「`文字列${変数}文字列`」。覚えておいて損はありません。
注意点として''(シングルクォート)でも""(ダブルクォート)でもなく``(バッククォート)を使用します。
Shiftを押しながら@ですね。
JavaScript:
console.log(`パーティリーダーは${$gameParty.leader().name()}です`);
 

タシラカ

ユーザー
・アロー関数
アロー関数について話す前にコンテナとイテレータについて少し触れます。
コンテナとは配列や連想配列のようにオブジェクトが詰まった容れ物、イテレータとはそれに干渉する手段です。
これは抽象的な概念で言語によって厳密な定義は異なるので「そんな感じなもの」という曖昧な認識で構いません。
以下の二つは同じ処理をしています。bindを使う適当な理由が思い浮かばない…
JavaScript:
// for文でアクセス
for(let i = 0; i < $gameParty.members().length; i++) {
    console.log(`マップID:${this._mapId}${$gameParty.members()[i].name()}`);
}
// イテレータ+function()でアクセス
$gameParty.members().forEach(function(member){
    console.log(`マップID:${this._mapId}${member.name()}`);
}.bind(this));
$gameParty.members()がコンテナ、forEach以降の処理がイテレータにあたります。
前者よりも後者の方が少し短くなった…と言っても、
内部でthisを参照する場合は末尾に.bind(this)と書かなきゃいけないし見易くなったとは言い切れませんね。
これを更に短縮するものがES6で追加されたアロー関数というもので、以下も同じ処理になります。
JavaScript:
// イテレータ+アロー関数でアクセス
$gameParty.members().forEach(member => {
    console.log(`マップID:${this._mapId}${member.name()}`);
});
// 一行であれば更に{}を外す事も可能。;も一緒に外す必要有
$gameParty.members().forEach(member =>
    console.log(`マップID:${this._mapId}${member.name()}`)
);
見ての通り末尾の.bind(this)が省略されて、先頭もfunctionの文字が無くなりました。
この時のthisは呼び出したスコープのthisを自動的に渡しています。
「function (引数) {処理}.bind(this)」を「(引数) => {処理}」に変形した、と考えてもらって差し支えないかと。
「えっこれだけ?」と思う方も多いでしょうが、実際使ってみるとこの字数と()の減少は馬鹿にならず
イテレータ自体頻繁に使用する為、アロー関数を使う機会はかなり多いと思います。
コンテナへのアクセスはfor文よりもイテレータの使用が推奨されているので
ツクールMZのコアスクリプトではイテレータ(+アロー関数)への置換が進められているのではないか、と予想します。

追記 8/1 一行アロー関数追加
 
最後に編集:

タシラカ

ユーザー
・プラグインの書き方
ツクールMVでプラグインを書く上で最初に書くおまじないがこれですね。
JavaScript:
(function() {
    // 処理
})();
これは即時関数と言い、内部で定義した変数や関数を外部で使用不可能にする境界線です。
ES5ではスコープの仕様上こう書く必要があったのですが、ES6ではこれで充分になります。
JavaScript:
{
    // 処理
}
・既存クラスの拡張(※使用非推奨)
試しにES6で書いてみた人は一度は詰まった経験があると思うんですけど(自分がそうです)
RGSSと同じ感覚で以下の書き方をするとエラーになります。
JavaScript:
class Animal {
    name() {
      return "ぽち";
    }
    cry() {
        console.log(`${this.name()}:わんわん`);
    }
}
// Alias --------------------------------
class Animal { // この時点でエラー
    const OLD_NAME = cry;
    name() {
        return "ミケ" + OLD_NAME.call(this);
    }
}
}
この方法はモンキーパッチと言って一般に推奨される手法ではなく資料も乏しいのですが
こちらでGalenmereth氏、並びにnio kasgami氏がES6ならではの解決方法をご教示してくださいました。
JavaScript:
// extends existing class --------------------------------
Animal = class extends Animal {
    name() {
        return "ミケ" + super.name();
    }
};
(new Animal()).cry();
これを実行すると「ミケぽち:わんわん」と表示されるようです。
ポイントは「Animal = class extends Animal」ですね。このお陰でRGSSに近い感覚で拡張出来る事が判明しました。
自分は今回調べていて初めてこの方法を知ったのでどんな副作用があるのか未だ不明ですが使いこなせればかなりの行数減を期待出来ます。
ちなみにこの書き方はMVプラグインにも応用出来るようです。

追記:7/26/21時、Aliasの方法を訂正しました
  :7/27 更に整形
  :7/29 既存クラス拡張の非推奨の注釈。理由は後述
 
最後に編集:

タシラカ

ユーザー
・プラグインコマンドの設定
MVではGame_Interpreter.prototype.pluginCommandを再定義して
引数のプラグインコマンドをif節やswitch節で判定し、判定処理が終わったら古い定義に回す、という処理でしたね。
JavaScript:
var OLD_PLUGIN_COMMAND = Game_Interpreter.prototype.pluginCommand;
Game_Interpreter.prototype.pluginCommand = function(command, args) {
    OLD_PLUGIN_COMMAND.call(this, command, args);
    if (command === "PluginCommandAllState") {
        $gameParty.allMembers().forEach(function(member) {
            member.addState(Number(args[0]));
        });
    }
};
例としてプラグインコマンドで「PluginCommandAllState 4」と入力するとパーティ全員が毒状態になります。

Kaliya氏の情報によるとMZではこの機構も大きく変わり 、
まず事前にプラグインコマンド用の入力ボックスの仕様を決めて
JavaScript:
* @command PluginCommandAllState
* @text オールステート
* @desc パーティ全員に指定のステートを付与します。
*
* @arg State
* @text ステートの指定
* @type state
* @desc 付与するステートです。デフォルトで毒(ID:4)。
* @default 4
次にプラグインファイル名とコマンド名をkeyとした連想配列にコールバック関数を登録する事でプラグインコマンドが使用可能になるようです。
JavaScript:
PluginManager.registerCommand("TestPlugin", "PluginCommandAllState", args => {
    const stateId = Number(args.State);
    $gameParty.allMembers().forEach(member => {
        member.addState(stateId);
    });
});
とりま今回は以上です。いかがだったでしょうか?
まだ紹介していない機能は沢山ありますが頻出、頻用するのはこの辺りではないかな、と思います。
特にラストとか初心者には何言ってるかちんぷんかんぷんだろうなぁとは思うけど、公式プラグインの改変から少しずつ始めりゃ良いんですよ。
弄ってれば自然と身に付くと思いますがES5と比較すると間違いなく読むのも書くのも楽になるので早めに覚えておいた方が良いです。
楽になるという事はそれだけヒューマンエラーが減り、バグを減らせるという事です。まぁ、実際見なきゃ分からないですけどね。
既に実践されてる方はAliasの書き方に興味があります。
 
最後に編集:
  • Like
Reactions: WTR

fspace

ユーザー
まとめ、お疲れ様です。

一応、何点か気になった部分の指摘と補足を。

・class構文
ES6最大の特徴ですね。
以下では同じ処理をしています。(微妙に間違っているかもしれない)
派生クラスのコンストラクタ内では基底クラスのコンストラクタを呼び出す必要があるため、このコードだと動かないと思います。次のように書く必要があります(明示的にコンストラクタを書いてますが、省略しても同じコードが生成されます)。
JavaScript:
class Derived extends Base {
    constructor(...args) {
        super(...args);
    }
    
    initialize(...args) {
        super.initialize(...args);
        
        // 任意の処理
    }
}

superは親クラスの参照です。
正確にはsuperは親クラスのプロパティを参照するための構文の一部なので、そう見せかけているだけですね。次のようなコードは動きません。
JavaScript:
// 構文エラー
this.foo(super);

// 親クラスの"super"という名前のプロパティ参照
// 親クラスのさらに親クラスのプロパティを参照できるわけではない
super.super.foo();

この奇妙な挙動を解消したものがlet変数宣言です。
正確にはconst/letも宣言より前の記述に含めることができます。ただし、そのコードの実行時点で初期化されていない場合にはエラーとなります。例えば、次のようなコードは正しく動作します。
JavaScript:
const foo = () => bar();
const bar = () => 42;

console.log(foo());
const/letがvarと最も大きく違うのは、変数のスコープ(その変数を参照できる範囲)にブロックスコープが加わったことです。varではグローバルスコープと関数スコープしかなかったため、グローバルスコープを避けるために即時関数で関数スコープを生成する必要がありましたが、const/letではブロックスコープが追加されたため"{ }"で囲むだけでよくなりました。ちなみに、クラス構文で定義されたクラスも同様のスコープになります。

余談ですが、グローバルスコープでvarによる変数宣言をすると、windowオブジェクト(globalThis)のプロパティになりますが、const/letで変数宣言をするとそうなりません。グローバルスコープには存在するものの、windowオブジェクトのプロパティにはなりません。


$gameParty.members()がコンテナ、forEach以降の処理がイテレータにあたります。
forEach以降の処理はイテレータではないと思います。言語によっては要素に対する処理がイテレータのメソッドとして定義されていることもありますが、イテレータは要素を順に列挙するオブジェクトのことであって、列挙して何をするかというところまでは含まれません。特にJavaScriptでは挙動の定義された「イテレータ」というものが存在しているため、なおさら別のものをイテレータと呼んでしまうのはマズいです。


見ての通り末尾の.bind(this)が省略されて、先頭もfunctionの文字が無くなりました。
この時のthisは呼び出したスコープのthisを自動的に渡しています。
「function (引数) {処理}.bind(this)」を「(引数) => {処理}」に変形した、と考えてもらって差し支えないかと。
うろ覚えですが、thisが自動的に渡されるというよりは、this(やその他いくつかの特殊変数)を特別な変数扱いしない、という挙動だったような気がします。特別な変数ではないため、通常の変数同様にキャプチャされてそのまま参照できます。基本的に略記としての意味合いが強いですが、thisなどを特別扱いする必要がない分、"function"による定義よりも軽量らしいです。

アロー関数は他の言語で似た記法の機能がそう呼ばれていることから「ラムダ式」と呼ばれることも多いです。


これは即時関数と言い、内部で定義した変数や関数を外部で使用不可能にする境界線です。
ES5ではスコープの仕様上こう書く必要があったのですが、ES6ではこれで充分になります。
「ES6では」と書くと誤解を招きそうな気がします。「varやfunctionによる関数定義(関数式は含まない)を使用しない場合は」と正確に書いた方がいいと思います。


こちらでGalenmereth氏、並びにnio kasgami氏がES6ならではの解決方法をご教示してくださいました。
JavaScript:
// Alias --------------------------------
const OLD_ANIMAL = Animal.prototype;
Animal = class extends Animal {
name() {
return "ミケ" + OLD_ANIMAL.name.call(this);
}
};
(new Animal()).cry();
callについては元スレッドでもそう書かれているように、superを利用した方がいいと思います。


自分は今回調べていて初めてこの方法を知ったのでどんな副作用があるのか未だ不明ですが使いこなせればかなりの行数減を期待出来ます。
副作用としては、クラスの継承階層が深くなることによるプロパティ探索対象の増加が考えられます。簡単に言うと、プロパティの参照が遅くなる可能性があります。JavaScriptではオブジェクトにプロパティが存在するかどうか確認するために、プロトタイプチェーンを辿るため、これが長くなるとその分だけ時間がかかります。

「可能性がある」と書いたのは、JavaScriptの実行エンジンによる最適化によって解消される可能性があるためです。愚直に実行するとクラス構文による方法は遅いですが、以前(MV)の方法よりもクラス構文の方が解析しやすいと考えられるため、実行エンジンによってはクラス構文の方が速くなる可能性も否定できません。こればかりは実際に試してみて確認しなければわからないと思います。
 

タシラカ

ユーザー
訂正ありがとうございます
基本的に独学な為細部はガバい認識で書いてるので誤報拡散防止は助かります
特に今回、Aliasの方法を見て詳しい情報を集める為にこのトピックを作ろうと思ったので補足説明有難いです
 

げれげれ

ユーザー
情報整理、ありがとうございます。
このような知見を求めてフォーラムを覗いているところが大きいので、
とても助かります。
いつも参考にさせていただいております。

誤りというほどではないと思うのでわざわざ書き込むべきか悩んだのですが、
自身が読み込んでいる最中に「ん?」となった箇所があったので、
後からこの記事に辿り着く人に向けての意味でも修正を提案させていただきます。

・項目見出し「Alias」 → 「既存クラスの書き換え」
訂正前の記述では「const OLD_ANIMAL = Animal.prototype;」というAliasに該当する箇所が
あったのでそのような見出しとしたのかと思われますが、訂正後は super で置き換えてあるため
既にAliasと呼べる箇所が残っていないです。
また、このくだり全体がAliasについてというよりは
「既存クラスの書き換え(元記事でいう“Overwrite existing class”)」
が主旨かと思うので、その観点からも見出しやコード内のコメントは差し替えた方が
良いように思いました。

・記述例の処理の相違
Aliasのくだりの「エラー例」と「その解決方法」の内容が記述方法だけでなく処理内容まで
異なってしまっているので、そこは統一しておいた方が記事の主旨が明確になるかと思いました。
(エラー例:cryメソッドを書き換えている
 解決方法の例:nameメソッドを書き換えている)
記事の主旨として super を使用した「解決方法」の方を尊重するとすれば、エラー例の側を
nameメソッド書き換えに合わせるのが自然でしょうか。

差し出がましいようですが、ご確認いただけると幸いです。
 
最後に編集:

タシラカ

ユーザー
・項目見出し「Alias」 → 「既存クラスの書き換え」
・記述例の処理の相違
それもそうですね。適当に整形しておきましょうか。
ただ「既存クラスの書き換え」と書くとまたややこしい話になりそうな気がするので「既存クラスの拡張」あたりにしておきます。
プロトタイプベース言語の厳密な内部挙動や定義を考え出すと頭が痛くなりますね…(思考停止)
 
ためになるまとめ、ありがとうございます。

さっそくいろいろと試していたところ、以下のような問題にあたりました。
JavaScript:
class Base {
    constructor(){
        this.initialize();
    }
    initialize() {
        this._name = "ぽち";
        this._number = 0;
    }
    name() {
      return this._name + this._number;
    }
}

class Animal extends Base {
    constructor() {
        super();
    }   
    initialize() {
        super.initialize();       
        this._number = 1;
    }
}

Base = class extends Base {
    initialize() {
        super.initialize();
        this._name += "たま";
    }
};

console.log((new Animal()).name()); // ぽち1
私のやりたいことは「ぽちたま1」という出力ですが、このやり方ではそうはなりませんでした。
Baseの再定義をAnimalの前に持ってくれば私の希望通りの出力となるのですが、それだとプラグイン素材では困るかと思います。

ちょっと例がわかりにくいかもしれませんが、要は親クラス側の再定義はどうしたら良いのだろうという相談です。
 

タシラカ

ユーザー
報告ありがとうございます。
そうかそうですね…class構文の仕様、というか「class Animal extends Base {」の時点で
Baseを引き継いでるのでその後にBaseに代入してもAnimalに反映されないって事ですかね。(盲点)
順番を前後すれば解決する、とは言っても
複数のクラスを一つのファイルに纏めるプラグイン事情を考えるとそんな事は出来ません。
まず対策の一つとして従来の処理を使う事。
JavaScript:
    const OLD_INITIALIZE = Base.prototype.initialize;
    Base.prototype.initialize = function () {
        OLD_INITIALIZE.call(this);
        this._name += "たま";
    };
この記法もclassに対しても使えるけど、個人的には他に何かスマートな方法はないものかって感じですね。
もう一つは今思いついた事なんですけどMix-Inからアプローチ出来ないかなぁ、
と思って色々試していたんですけどどうもしっくり来ませんね。
他のプラグインの競合を考えると解決策を考えない限りとりあえず使用は控えた方が良いでしょう。

おまけ:Mix-Inについて
継承を使わずにコードを再利用するテクニックの事です。
一応以下のコードも上記の方法と挙動は同じだと思います。
JavaScript:
    const OLD_INITIALIZE = Base.prototype.initialize;
    const baseMixin = {
        initialize() {
            OLD_INITIALIZE.call(this);
            this._name += "たま";
        }
    }
    Object.assign(Base.prototype, baseMixin);
予め上書き用のメソッド群を持つbaseMixinというオブジェクトを作り
Object.assignでBase.prototypeに関連付いた当該メソッドを全て上書きするという方法です。
JavaScript:
    // baseMixinは省略可能
    const OLD_INITIALIZE = Base.prototype.initialize;
    const OLD_NAME = Base.prototype.name;
    Object.assign(Base.prototype, {
        initialize() {
            OLD_INITIALIZE.call(this);
            this._name += "たま";
        }, // メソッドを続ける場合はカンマで区切る
        name() {
            return OLD_NAME.call(this) + "号";
        }
    });
上部にエイリアスを固めておけばどのメソッドを上書きしたのか見通しは良くなる…し、
二つ以上の上書きをするのならprototypeを書く量が地味に減りますけど
それでもsuper.initialize()と較べるとインパクトに欠けますねぇ。
やはりネックとなる部分は以前のメソッドに手軽にアクセスする手段ですかね。
 
最後に編集:

fspace

ユーザー
ちょっと例がわかりにくいかもしれませんが、要は親クラス側の再定義はどうしたら良いのだろうという相談です。
言われてみればその通りですね……。

一応、動作させるだけであれば setPrototypeOf でプロトタイプを繋ぎ変えれば動きます。
JavaScript:
Object.setPrototypeOf(Animal.prototype, Base.prototype);
ただ、setPrototypeOf は前に書いた実行エンジンの最適化を阻害してしまうそうなので、あまりいい方法ではないと思います。

現実的な対策としては、タシラカさんの言うとおり、従来の方法を使用するのがいいんじゃないでしょうか。

結局のところ、ツクールの拡張方法自体に問題があるので、ある程度微妙な書き方になってしまうのはしょうがないですね……。
 

タシラカ

ユーザー

本日ドキュメントが公開されたみたいですね。
いわゆるrpg_core.jsの内容でプラグイン作者でもあまり触れない所だと思いますけど。
_付きの内部メソッドやCache関連等、外部の人間が触る事を想定しない部分は載せてないようなので
大まかな事しか分かりませんが目に付いた変更点だと

・Array#contains,String#containsの非推奨化
MVでは配列(文字列)の中に特定の要素があれば真偽を返すcontainsというメソッドがincludesというメソッド名に変更されます。

・ColorFilterクラスの新設
Spriteクラスを内部的に整理した可能性

・Videoの新設
Graphicsに内包されていた映像再生関連の処理を分離したようです

Graphicsの変更
・静的プロパティappの追加。内容はPIXI.Application。

・静的プロパティeffekseerの追加。
アニメーション表示の変更に伴うeffekseer関連のAPIオブジェクト?

・Utilsへの機能集約
特に Utils.canUseIndexedDB が気になる…アツマール関連で使用している?

文法を変えるついでに構造も軽くリファクタリングしている印象がありますけど大きな変更は無さそうですね。
プラグイン制作者側が注意する点は今後はMVで独自定義していたcontainsを使わないでくださいって事くらいだと思います。
includesはIE環境では使用出来ないJS標準機能ですが、対応ブラウザから正式に外した為に大手を振って使えるようです。
書き方的にMZでも一応containsは使えるかな?
 
最後に編集:

himeworks

ユーザー
ためになるまとめ、ありがとうございます。

さっそくいろいろと試していたところ、以下のような問題にあたりました。
JavaScript:
class Base {
    constructor(){
        this.initialize();
    }
    initialize() {
        this._name = "ぽち";
        this._number = 0;
    }
    name() {
      return this._name + this._number;
    }
}

class Animal extends Base {
    constructor() {
        super();
    }
    initialize() {
        super.initialize();    
        this._number = 1;
    }
}

Base = class extends Base {
    initialize() {
        super.initialize();
        this._name += "たま";
    }
};

console.log((new Animal()).name()); // ぽち1
私のやりたいことは「ぽちたま1」という出力ですが、このやり方ではそうはなりませんでした。
Baseの再定義をAnimalの前に持ってくれば私の希望通りの出力となるのですが、それだとプラグイン素材では困るかと思います。

ちょっと例がわかりにくいかもしれませんが、要は親クラス側の再定義はどうしたら良いのだろうという相談です。

JavaScript:
class Base {
    constructor(){
        this.initialize();
    }
    initialize() {
        this._name = "ぽち";
        this._number = 0
    }
    name() {
      return this._name + this._number;
    }
}

class Animal extends Base {
    constructor() {
      super();
    }
  
    initialize() {
        Base.prototype.initialize.call(this) // <------ instead of "super.initialize()"
        this._number += 1
    }
}

console.log((new Animal()).name()); // ぽち1

// Plugin 1: alias Base
Base = class extends Base {
    initialize() {
        super.initialize();
        this._name += "たま";
    }
};

console.log((new Animal()).name()); // ぽちたま1

// Plugin 2: alias Animal
Animal = class extends Animal {
  initialize() {
    super.initialize();
    this._number += 2
  }
}

console.log((new Animal()).name()); // ぽちたま3
 
最後に編集:

タシラカ

ユーザー
ご指摘ありがとうございます。私の英語は未熟なので簡易な日本語で書きます。

「Base.prototype.initialize.call(this)」と「super.initialize()」では挙動が変わるんですね。
知らなかったので勉強になりました。

ただしこれは例え話なので
Game_ActorやScene_Map等、私たちが関与出来ない
コアスクリプトのソースコードを実際に見ない事には実用出来るか判断出来ません。
JavaScript:
// rpg_objects.js(RPGMakerMZ)

    class Game_Actor extends Game_Battler {
        initialize(actorId) {
            ????; // <-- Game_Battler.prototype.initialize.call(this)? or super.initialize()??
            this.setup(actorId);
        }   
    }
個人的にはoverrideを省略した場合、superを使った時と同じ挙動になるので対応している可能性は低いと思います。
JavaScript:
    class Base {
        constructor(){
            this.initialize();
        }
        initialize() {
            this._name = "ぽち";
            this._number = 0
        }
        name() {
            return this._name + this._number;
        }
    }

    class Animal extends Base {
        constructor() {
            super();
        } 
        // initialize() {
        //     Base.prototype.initialize.call(this)
        //     this._number += 1
        // }
    }

    console.log((new Animal()).name()); // ぽち0
       
    Base = class extends Base {
        initialize() {
            super.initialize();
            this._name += "たま";
        }
    };

    console.log((new Animal()).name()); // ぽち0
 

fspace

ユーザー
MZのコアスクリプト、クラス構文じゃないらしいですね……。

ES6で書き直したと宣伝していたのにクラス構文すら使っていないということで、海外のフォーラムが荒れてるみたいです。
 

タシラカ

ユーザー
情報ありがとうございます。今読んできました
これはあまりにも…あまりにも衝撃的ですね:eek:
対応ブラウザ(ゲーム)※最新OS推奨
Google Chrome
Safari
Firefox
iOS Safari iOS 11以降対応
Chrome for Android
と公式サイトに記載されているので完全に古い環境は切り捨てたものだと思ってましたよ。
このトピックに書いたものはかなり控えめに書いたつもりでしたが
class構文どころかconstしか使っていない可能性があるって思いもしませんでした。
でも新プラグインコマンド設定の例文を見る限りアロー関数は使ってるんですよね…いやあの部分はプラグイン側か。

自由に書きなぐったら生産性のない言葉に終始しそうなので慎みますが一言だけ言うなら「どんな判断だ」としか
 

fspace

ユーザー
Game Engine code entirely rewritten to follows current JS standards (ES6)
と書かれてましたが、Core APIの範囲を見る限りでは間違いなく書き直してはいないでしょうね。せいぜいvarをconst/letで置き換えたくらいで、自動ツールでほぼ完了するレベルだと思います。変更の大部分は新機能やPixi 5に対応するためのものに見えます。ES6に対応したというよりはES5縛りをやめたくらいの感覚だと思います。

自由に書きなぐったら生産性のない言葉に終始しそうなので慎みますが一言だけ言うなら「どんな判断だ」としか
良く言えば「コストカット」、悪く言えば「手抜き」としか……。親クラスの書き換えの問題のように従来のプロトタイプ式の記述とクラス構文が混ざることを嫌ったという考えもできますが、エディタサポートのことを考えるとデメリットの方がはるかに大きいように感じますね。正直なところ、発売当初からしてもやや時代遅れなツクールMVの設計が一新される可能性まで密かに期待していただけに、結構がっかりしました。中途半端な互換性や価格よりもこの先五年の発展性をとって欲しかったですね……。

Core APIのコードに次のようなコードをみかけて、もう不安しかないです。
JavaScript:
Array.prototype.remove = function(element) {
    for (;;) {
        const index = this.indexOf(element);
        if (index >= 0) {
            this.splice(index, 1);
        } else {
            return this;
        }
    }
};
 
トップ