ツクールMVでもmix-inしたかった話

12 月 17 日になりました。今日は何の日でしょうか。そうですね。今日は RPG ツクール MV の発売日です。発売おめでとうございます。どうもこんにちは aoitaku です。

この記事は RPG ツクール MV Advent Calendar 2015 の 17 日目です。

突然ですが「フィールドはトップビュー、街やダンジョンは横スクロールアクション」みたいなことをやりたいと思ったことはありませんか。ぼくはあります。

そんなときはフィールドは `Scene_Map` をそのまま活かしつつ、街やダンジョン用のシーンクラスをあたらしく `Scene_Map` をベースにして作ろうということになると思うんですが、`Scene_Map` はグローバル変数に入ってる `Game_Map` と `Game_Player` を参照してるだけだったりするので、独自のマップシーンを作るためには `Game_Map` と `Game_Player`（と `Game_Event` などとそれらが継承する `Game_Character`、`Game_CharacterBase` ）も専用のものが必要になります。
元のマップシーンがいらないなら定義を上書きするなりして書き換えてしまえばいいんですが、フィールドと街やダンジョンとで別のマップシーンを使いたいとなったときは話が別です。  
そういうわけで元のオブジェクトやメソッドはそのままに、もともとあったやつと同等の機能を実装した別のオブジェクトやメソッドを用意したい。

きょうはそんなシチュエーションで使えるっぽい話をします。  

具体的な横スクロールアクションの実装方法とかの話は今回はしません。あしからず。

## 独自マップシーン専用のオブジェクトを作る

前述のとおり `Game_Player` は `Game_Character` を継承していて、`Game_Character` は `Game_CharacterBase` を継承しています。  
移動とかそのへんの挙動は `Game_CharacterBase` に書いてあるので、`Game_CharacterBase` を継承したオブジェクトを作って該当のメソッドをオーバーライドしていくことにします。
名前は `SideScrollView.CharacterBase` とかにしましょうか。そんでこの `SideScrollView.CharacterBase` を継承した `SideScrollView.Character` を作ります。`SideScrollView.Character` に `Game_Character` のメソッドを全部移植すればオッケーです。

手で書くのは大変なのでコピーします。

```js
var character = function() {
    this.initialize.apply(this, arguments);
};
character.prototype = Object.create(SideScrollView2D.CharacterBase.prototype);
var props = Object.getOwnPropertyNames(Game_Character.prototype);
for (var i = 0, prop; i < props.length; i++) {
    prop = props[i];
    character.prototype[prop] = Game_Character.prototype[prop];
}
character.prototype.constructor = character;
SideScrollView.Character = character;
```

元のオブジェクトの方で `defineProperty` されたものは `defineProperty` しないといけない気がしますがとりあえずこのままにしときます。ゆるしてほしい。

何をやっているかというと、`SideScrollView.CharacterBase` のプロトタイプを継承した上で、`Game_Character` のプロトタイプの中身をコピーしています。

こうすることで `SideScrollView.CharacterBase` を継承しつつ `Game_Character` であらたに定義されたメソッドやオーバーライドしたメソッドを `SideScrollView.Character` に生やすことができます。

同じように `SideScrollView.Character` を継承しながら `Game_Player` のプロトタイプをコピーして `SideScrollView.Player` を作ります。

さて、こんなふうにコピーすると困ることがあります。

## Game_Player の継承元メソッド呼び出しが rpg_objects で決め打ちにされている件

既知のことではありますが。

具体的に言うとこういうことです。

```js:rpg_objects.js
Game_Player.prototype.initialize = function() {
    Game_Character.prototype.initialize.call(this);

    ...

```

とか、

```js:rpg_objects.js
Game_Player.prototype.initMembers = function() {
    Game_Character.prototype.initMembers.call(this);

    ...

```

とか……。  
けっこういっぱいある。

もちろん `SideScrollView.Player.prototype.initialize` は `SideScrollView.Character.prototype.initialize` を呼び出したいので困ります。

最初は Game_Character の中身を一時的に置き換えちゃおって思ってたんですがデバッグするときに苦労するなって思ったのでやめました。

```js:rpg_objects.js
Game_Player.prototype.initialize = function() {
    this.prototype.super.initialize.call(this);

    ...

```

こういうのに置き換えて `super` のところを後挿しできるようにしたほうがいいと思います。  
けっこういっぱいある……。

`Game_Player.prototype.super` には `Game_Character.prototype` を、`SideScrollView.Player.prototype.super` には `SideScrollView.Character.prototype` を入れてやれば、オブジェクトに応じて適切な継承元メソッドを呼び出せるようになります。

`prototype.super` を後から書き換えられると死にそう。

## コピーを楽にできるようにした

都度 for 文書いてとかやるのは大変なので関数にしました。

```js
function extend(base, mixin) {
    var newObject = function() {
        this.initialize.apply(this, arguments);
    };
    prototype = Object.create(base.prototype);
    var props = Object.getOwnPropertyNames(mixin.prototype);
    for (var i = 0, prop; i < props.length; i++) {
        prop = props[i];
        prototype[prop] = mixin.prototype[prop];
    }
    prototype.constructor = newObject;
    prototype.super = base.prototype
    newObject.prototype = prototype;
    return newObject;
}
```

関数名とか引数名が JavaScript の文脈で適切かどうか自信がないんですがぼくがやりたかったことは Ruby の `module` の `include` のような気がします。いわゆる mix-in だと思う。ってタイトルに書いちゃったけどちょっと自信ない。これで mix-in として充分ではない気がする……。

この関数をどのオブジェクトに生やすのがいいのか判断できなかったので適宜好きなオブジェクトに生やしてください。

こんな感じで `Game_Event` とか、必要であれば `Game_Vehicle` とか `Game_Follower` も、おんなじように `SideScrollView.Event` とか `SideScrollView.Vehicle` とか `SideScrollView.Follower` って作ってあげればよいです。

## マップシーンから参照するオブジェクトを切り替える

`Game_Player` は `SideScrollView.Player` に差し替えればいいんですが、`SideScrollView.Event` なんかは `Game_Map` の中身をいじらないとだめです。というわけで `SideScrollView.Map` を作ります。  
単に `Game_Map` を継承して必要なメソッドを書き換えていけばいいです。

```js:rpg_objects.js
Game_Map.prototype.setupEvents = function() {
    this._events = [];
    for (var i = 0; i < $dataMap.events.length; i++) {
        if ($dataMap.events[i]) {
            this._events[i] = new Game_Event(this._mapId, i);
        }
    }
    this._commonEvents = this.parallelCommonEvents().map(function(commonEvent) {
        return new Game_CommonEvent(commonEvent.id);
    });
    this.refreshTileEvents();
};
```

この辺とかで `new Game_Event` ってやってるところを `new SideScrollView.Event` とかに差し替えればオッケーです。

後は `Scene_Map` を継承した `Scene_SideScrollMap` とか作って、シーン切替時に `$gamePlayer` に `SideScrollView.Player` を、`$gameMap` に `SideScrollView.Map` を入れてやればいいと思います。

## 今回はここまで

今回の話はたぶんマップシーン以外にもメニューシーンとかでも応用できる気がしていますがどういうふうに便利なのか自分でちゃんと言語化できていません……。それでも記事にして起こしておけば、これを読んだ人にとって何か手がかりを得るきっかけになるのではと思ったのでこうして記事にしておきます。

というわけで RPG ツクール MV Advent Calendar 2015 の 17 日目の記事でした。それでは皆様、たのしいツクールライフを！