神無月サスケの波瀾万丈な日常

神無月サスケのツイッター(@ktakaki00)を補完する長文を書きます。

ムンホイXPの地味な技術:その2.ビューポート分割

導入動機:天候を導入しようと思って出てきた障壁

ムンホイのXP版にあって、オリジナルになかった要素のひとつに、天候があります。雨が降ったり、雪が降ったりする演出です。これを導入した最初の動機は単純で、せっかくRPGツクールXPに、そのような機能が準備されているのだし、導入すればささやかな演出の華になるだろう、と思ったことでした。

しかし、そこにひとつの障壁がありました。それは本作のマップの構成です。

本作のマップ構成:屋内でも屋外部分が描かれている

ムンホイの屋内マップ構成の一例を次に挙げます。

ご覧になっての通り、周囲に外の景色が描かれています。これは、RPGツクール95時代には比較的一般的なやり方でした。一方、RPGツクール2000以降は屋内では周囲が黒い壁に囲まれており、屋内マップでは外の景色は描かれないのが一般的になりました。

これには理由があります。ツクール2000で初めて天候が導入されたのですが、もしこれを導入する場合、屋内に外の景色が描かれていると、その外の部分にも雨を降らせないと不自然になるからです。ツクール2000には(それ以降のツクールでも)「屋内部分では雨が降らず、外の部分にだけ雨を降らせる」といった機能はないため、屋内では単に雨をやませる、その際のマップで外の景色は描かない、というやり方が定着したのでしょう。

要求:「屋内から見える外の部分だけ雨が降っている」を実現できないか

さて。オリジナルのムンホイは天候のシステムが無かったRPGツクール95作品です。このため、天候のことを意識しないマップ構成になっています。このため、天候などは入れずに行くべきか? と思いました。

あるいは、マップの方を書き換えて外の部分を塗りつぶすという手もあるでしょう。しかしそれはしたくありませんでした。あくまでオリジナルの雰囲気を変えたくなかったし、そこに手を加えるくらいなら、天候など入れない方がいい、と。

そのように考えたのですが、最終的には「屋内から見える外の部分だけ、天候のアニメーションを表示する」という処理が実現できました。これは試行錯誤して出来たというよりは、あるとき急にひらめいて、その考えに基づいて入れてみたらすんなりうまく行った、という性質のものです。そのメカニズムを紹介していきたいと思います。

実例

カニズムの説明の前に、先にこの実装によって、具体的にどういう風になったかを、スクリーンショットを使って紹介します。百聞は一見にしかず。

この「屋内マップで描かれている屋外の部分にだけ画面処理を加える」というシステムの実装により、天気のほかに、夕焼けの描画もオリジナル版から変更することが出来ました。オリジナルの95版では、屋内でも全体が夕焼けのパレットになっていましたが、リメイク版では、屋外部分のみが夕焼けになっています。次のような感じです:

パレットが変わっているのが、屋外部分というわけです。雨のアニメーションなども、この部分にだけ描画されるのです。

具体的にどうしたのか、RGSSでの実装やマップエディタでの設定方法を紹介します。

実装:屋内と屋外でビューポートを分ける

屋外用のビューポート(名づけてビューポート0)を作成

基本的な考え方は、屋外のあるマップでは、屋内用と屋外用に、スプライトを表示するビューポートをそれぞれ用意する、というものです。

既存のRGSSでは、ビューポートは、viewport1〜viewport3 が用意されていて、viewport3 が一番手前にあり、viewport1 が奥です。そして、通常のマップ(Tilemap)は、viewport1 に表示されています。(奥にある=z座標が小さい、ということです)

今回は、そこに拡張を行います。屋内マップで屋外も表示する必要がある場合、屋内部分と屋外部分を分けて表示するために、屋内部分を既存のviewport1に、屋外部分をそれより奥にある新設したビューポートに表示することにします。この新設したビューポートは、viewport1 より奥にあるため、viewport0 と呼んでいます。

マップの屋内用と屋外用の Tilemap オブジェクトを作成し、それぞれのビューポートに置きます(どうやって屋内と屋外を分けるかは後述)。そして、天候の処理、雨や夕焼けなど、外だけに処理が必要になった時は、屋外用(ビューポート0)に対してだけその処理を行うことによって、「外側だけ、天候やパレットが変更される」という処理が実現できます。

タイルマップの要素を屋内と屋外に分ける

このように「屋内と屋外の部分でビューポートを分けて表示する」ことにしました。では、マップの要素で、どこが屋内でどこが屋外かを設定するにはどうすればいいでしょう。

マップエディタを見ると、以下のように設定されています:

マップの外側を示す部分に×、窓の部分に\という記号が置いてあるのが見えると思います。結論から言うと、これらの記号は3つあるレイヤのうちの最も手前(レイヤ2)に置かれています。×の記号は、残りの2つは外側に、\の記号はレイヤ1を内側に、一番奥(レイヤ0)を外側に置く、という意味です。(特に記号が無ければ、全て内側です)

内側と外側に分かれたタイルマップを作成する処理は、Game_AnotherMapData クラスによって行われています。Game_AnotherMapData#setup メソッドを見てもらうと分かりやすいと思います。マップから読み込んだ マップデータオブジェクト(Table)をもとに、inner および outer と名づけられたマップデータ(Table)を作成しています。これらが、内部用、外部用の Tilemap で使用されるのです。

なお、このように「屋内と屋外で表示を分ける処理を行う必要があるマップかどうか」の設定は、初期化イベントというもので設定しています。原則、マップの座標(0,0)に置かれているイベントで、イベントの名前に「!init」が含まれているものが初期化イベントと見なされます。初期化イベントのグラフィックにタイルを設定すると、そのタイルIDが、特別な処理をするものになります。ここで、×マークの描かれたタイルが設定されています。(\は、×マークの次のIDのタイルです)

このようにして、屋内用と屋外用の2枚のTilemapオブジェクトを作成し、それぞれ別のビューポートに所属させることによって、屋内と屋外を表現しているのです。

おさらい:エディタで中を見ながら確認

さて、ここまで読んで理解出来ているでしょうか。RPGツクールXPで実際にムンホイXPを読み込みスクリプトエディタを起動して、該当箇所と付き合わせながら読んでいくと理解しやすいと思います。

ここまでの理解を元に、Spriteset_Map クラスを読んでみましょう。簡略化のため、タイルマップ、パノラマ、天候の処理は、Spriteset_MapElements クラスを作成し、そちらに処理を移しているため、それらを合わせて読んでいってください。

Spriteset_MapElements#initialize メソッドを見てください。屋内と屋外に分けたデータ(AnotherMapData クラスのオブジェクト $game_map.am_data)を元に、ビューポート0とビューポート1に分けて作成している様子が分かりやすいと思います。

屋外部分にあるイベントの設定

タイルセットのビューポートを分けることが出来ました。次は、イベントの設定です。

イベントの中には、屋外部分に配置されている物もあります。例えば、上記のスクリーンショットの中では、外にカプセル(宝箱)があるのが見えると思います。ゲームをプレイした方ならお分かりでしょうが、建物の北の駐車場から回り込めば取ることが出来るのですよね!

このように、外側部分にあるイベントはビューポート0に置く必要があります。そのようなイベントは、名前に「!v0」という文字を含ませることによって設定するようにしています。具体的には、Game_Event#viewport0? というメソッドで判定しています。

そして、このメソッドでビューポート0用と判定されたイベントのスプライトはSpriteset_MapElements#add_sprite_to_viewport0 メソッドでビューポート0に配置されています。

このように、ビューポート0に配置するイベントは手動で設定する必要があるため、若干面倒ですが、これは仕方が無いですね。自動判別する仕組みが作れたら便利だったのですが。

プレイヤーが外側の部分に出るときの処理

次に考慮するべきなのは、プレイヤーが外側、つまりビューポート0の部分に出る場合です。

この場合、ビューポート0に配置するイベントと同様、プレイヤーをビューポート0に配置しなければ、パレットの変更などに対処できません。

このため、外に出るマップの場合、通常(ビューポート1)用と、外に出た時(ビューポート0)用の2種類のスプライトを作成し、現在位置のビューポートに合致する方だけを表示するようにしています。つまり、屋内にいる時は、ビューポート1用のスプライトだけを表示し、ビューポート0用は消去、逆に屋外に出たらビューポート0用を表示し、ビューポート1用は消去するということです。

この処理を実装したのが、Sprite_Character2 です。通常の Sprite_Character にビューポートの監視を行う処理を追加し、異なるときは表示を行わないようにしています。

このビューポートの監視に使う変数が、Game_Player に追加された @viewport_id というインスタンス変数です。プレイヤーが内側にいる時(あるいはビューポートを分けない場合)は 1 を、外側の時は 0 を取るようにしています。

ビューポートの切り替え(内側と外側を切り替える)は、イベントで行っています。コモンイベントの12番と13番に「ビューポート0」と「ビューポート1」というのがありますが、これをイベントから呼び出すことで、切り替えているのです。

結論:限りなく地味な技術。僕自身、他の作品で採用するかどうか不明

解説は以上ですが、いかがだったでしょうか。この「外だけ雨が降っている」という処理、スーファミなどのRPGでは、普通に見られた処理なので、本作で見かけても目新しいとは思えなかったと思いますが、ツクールの他の作品で見かけたことがないという人も少なくないのではないでしょうか。

無理もありません。外と内を分ける為には、マップ設定から若干ややこしい処理を行う必要があるので、なかなかやる人はいないでしょう。現にこの僕も、リメイクということで元のマップを変更したくないために行った処理です。1から新作を作る場合は、こんなややこしい処理を入れる代わりに、外側を黒く塗りつぶしていたと思うのです。

このため、この技術は本作のみのものになるかもしれません。しかしこの「Tilemapを2枚作り別々のビューポートを作る」という発想は、何かあなたの制作のヒントになるかもしれないと思い、紹介させていただきました。RGSSでこのあたりを大胆にいじっている人は少ないのですが、このあたりの改変こそが、RGSSの醍醐味だと僕は思っているのです。