RPGツクールMV

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

More than 3 years have passed since last update.

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

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

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

そんなときはフィールドは Scene_Map をそのまま活かしつつ、街やダンジョン用のシーンクラスをあたらしく Scene_Map をベースにして作ろうということになると思うんですが、Scene_Map はグローバル変数に入ってる Game_MapGame_Player を参照してるだけだったりするので、独自のマップシーンを作るためには Game_MapGame_Player(と Game_Event などとそれらが継承する Game_CharacterGame_CharacterBase )も専用のものが必要になります。

元のマップシーンがいらないなら定義を上書きするなりして書き換えてしまえばいいんですが、フィールドと街やダンジョンとで別のマップシーンを使いたいとなったときは話が別です。


そういうわけで元のオブジェクトやメソッドはそのままに、もともとあったやつと同等の機能を実装した別のオブジェクトやメソッドを用意したい。

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

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


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

前述のとおり Game_PlayerGame_Character を継承していて、Game_CharacterGame_CharacterBase を継承しています。


移動とかそのへんの挙動は Game_CharacterBase に書いてあるので、Game_CharacterBase を継承したオブジェクトを作って該当のメソッドをオーバーライドしていくことにします。

名前は SideScrollView.CharacterBase とかにしましょうか。そんでこの SideScrollView.CharacterBase を継承した SideScrollView.Character を作ります。SideScrollView.CharacterGame_Character のメソッドを全部移植すればオッケーです。

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

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 で決め打ちにされている件

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

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


rpg_objects.js

Game_Player.prototype.initialize = function() {

Game_Character.prototype.initialize.call(this);

...


とか、


rpg_objects.js

Game_Player.prototype.initMembers = function() {

Game_Character.prototype.initMembers.call(this);

...


とか……。


けっこういっぱいある。

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

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


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 文書いてとかやるのは大変なので関数にしました。

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 の moduleinclude のような気がします。いわゆる mix-in だと思う。ってタイトルに書いちゃったけどちょっと自信ない。これで mix-in として充分ではない気がする……。

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

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


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

Game_PlayerSideScrollView.Player に差し替えればいいんですが、SideScrollView.Event なんかは Game_Map の中身をいじらないとだめです。というわけで SideScrollView.Map を作ります。


単に Game_Map を継承して必要なメソッドを書き換えていけばいいです。


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 とか作って、シーン切替時に $gamePlayerSideScrollView.Player を、$gameMapSideScrollView.Map を入れてやればいいと思います。


今回はここまで

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

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