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
のメソッドを全部移植すればオッケーです。
手で書くのは大変なのでコピーします。
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 で決め打ちにされている件
既知のことではありますが。
具体的に言うとこういうことです。
Game_Player.prototype.initialize = function() {
Game_Character.prototype.initialize.call(this);
...
とか、
Game_Player.prototype.initMembers = function() {
Game_Character.prototype.initMembers.call(this);
...
とか……。
けっこういっぱいある。
もちろん SideScrollView.Player.prototype.initialize
は SideScrollView.Character.prototype.initialize
を呼び出したいので困ります。
最初は Game_Character の中身を一時的に置き換えちゃおって思ってたんですがデバッグするときに苦労するなって思ったのでやめました。
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 の 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
を継承して必要なメソッドを書き換えていけばいいです。
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 日目の記事でした。それでは皆様、たのしいツクールライフを!