ModelとViewを意識して作ってみる

はいどーもこんばんはニートです。
本日はツクマテで拝見した「タイルを光らす」プラグインを自作してみたいと思います。
これを作る際にModelとViewを意識してみましたよ。
ちゃっちゃかコードを見たいという方は、以下のURLから見れます(2つのバージョンを用意しています)。
https://github.com/Tsumio/rmmv-plugins/blob/master/plugins/TileHighLighterPolling.js
https://github.com/Tsumio/rmmv-plugins/blob/master/plugins/TileHighLighter.js
なお、タイルを光らせるプラグインが欲しいという方はサンシロさんのSAN_TileToner.jsがすでにありますので、こちらを使用するのがよいと思います。
今回の記事はあくまでも「ModelとViewを意識してプラグインを作ってみる」というお題もとい練習です。

1.ModelとViewが密結合した状態

僕が最初に作ったバージョンはViewの中にModelも含まれていました。
つまりSpriteを直接操作して何やら変更するわけですね。
これだと例えばModelだけをセーブデータとして保存したい場合に困るかなと思いました(普通Viewはセーブに含めないため)。
また、一つのクラスが巨大で管理もしにくいという印象です。
パフォーマンス的にはまあいい感じでした。
何も考えずに作ると恐らくこのバージョンになるんじゃないかなーと思います。

2.ModelとViewを分離(ポーリング)

次に作ったバージョンではModelとViewを分離しました。
こうすることで、先ほども書いた通り「例えばModelだけをセーブデータとして保存したい」という要求にも応えられるようになりました。
ただModelの変更をViewへ通知する方法で悩みました。
Observerパターンを使って通知するのが一般的かと思いますが、ツクールのコアスクリプトってそういう書き方で作られてないんですよね。
というわけで僕もObserverパターンはやめて、変更を常に監視(ポーリング)してModelの変更をViewへ通知するようにしました(ツクールのコアスクリプトもこれ)。
ただこの方法だと、けっこう無駄な処理が走ります。
実際、最初に作ったこのバージョンはめちゃくちゃ重くて使い物になりませんでしたw
その後ダーティフラグやらvisible状態を監視する方法を用いてかなり軽くなりましたが、処理自体は複雑になってしまいました。
「Modelの変更をViewへ通知したいだけなのに……」という感は否めません。
書いたコードは以下のURLから見れます。
https://github.com/Tsumio/rmmv-plugins/blob/master/plugins/TileHighLighterPolling.js

3.一枚かませる

次のバージョンではModelとViewを仲介させるクラスを一つ用意しました。
MVCやらMVPやらと言われているパターンだとは思うのですが、いまいち区別がついてないのでここでは単に「一枚かませる」といった程度の表現にしておきますw
さて仲介するクラスを一つ用意することでポーリングが不要になり、結果として処理が軽くなりました。
ただこの場合でも変更の通知があんまり綺麗じゃないです。
クラス名はPresenterとしていますが、ViewがModelを保持しちゃってます。
ツクールMVでうまいことModelの変更をViewへ通知する方法って何かありますかねえ。
やっぱりModelにObservableを用意して、PresenterでObserverを登録するのがいいですかね……。

あと、この方法だとセーブデータへの保存が面倒です。
2の「ModelとViewを分離(ポーリング)」なら単にModelを保存し、データ読み込み時にViewに値を適用させればよかったのですが、PresenterはViewを保持しているため、Presenterをそのまま保存するわけにはいきません(保存してもいいけどデータ量が増える)。
そこでまたPresenterからModelを分離し、Model単体だけ保存する必要がでてきます。
この処理はぶっちゃけ面倒なので、それなら2のポーリングでいいんじゃないかなあ……と思いました。
書いたコードは以下のURLから見れます。
https://github.com/Tsumio/rmmv-plugins/blob/master/plugins/TileHighLighter.js

結論

以下が結論とまとめです。

1のModelとViewが密結合したバージョンは、Modelをセーブデータに保存するのが困難でかつ、管理も煩雑です。ただしパフォーマンス的にはわりと優れていました。作るのも簡単かなと思います。

2のModelとViewを分離(ポーリング)したバージョンは、パフォーマンスはやや落ちますし、ポーリングの処理の可読性が低いです。ただModelをセーブデータとして保存するのが簡単なので、セーブデータに含めたいならこのバージョンがよいのではないでしょうか。作るのは1に比べたらちょっと面倒。

3の一枚かませるバージョンは、パフォーマンスは優れていますし、可読性も結構いい感じでしたが、セーブデータに含めたい場合はかなり面倒なことになりそうです。作るのは2に比べても面倒です。

それぞれ一長一短で、目的にあった用途で使い分けるのがよさそうですね。
ModelとViewをわけるのはごった煮になってほしくないということ以上に、ツクールMVの場合だとセーブデータとして保存したいからな場合が多いでしょうから、総合的に見て2が一番使いやすいんじゃないでしょうか。
ウーン、もっといい方法ないですかね……(コアスクリプトに合わせるのがやっぱ一番いいかな……)。

補足:セーブデータについて

セーブデータについて補足です。
今回のプラグインは愚直にマップ1マスに対して1つのスプライトを生成しています。
とどのつまりスプライト=Viewなので、一つのスプライトに対して一つのModelが生成されることになります。
これの何がいけないかと言えば、めちゃくちゃセーブデータが大きくなっちゃうんですね。

で、各マップごとにModelを保存していたらあっという間にセーブデータ容量限界を超えてしまうので、今回は「現在いるマップ」のデータのみを保持しています(ポーリングバージョンの話です。一枚かませるバージョンはセーブデータに保存しません。ただゲーム実行中にデータを保持しているだけです。試してませんがこれひょっとしたら別のセーブデータ読み込んだときに問題出るかもw)。
つまりマップを移動したらデータは破棄され、新たなModelが生成され、それをセーブデータとして保持します。
これは場合によっては具合が悪いかもしれません。
特にタイルの状態をセーブデータに保存したい場合、普通はマップごとに保存しておきたいんじゃないかなーと思いました。

補足:解決策

今回のプラグインは「タイルの色を変える」ものです。
であるならば「タイルの色が変わらない」場合もあるはずです。
実際、よく使用しても1マップあたりせいぜい50個もタイルの色を変えればいい方じゃないでしょうか(ゲームによっては色を変えてすぐ破棄する場合もありそうですし)。
そこで「あらかじめ1タイルに1つのスプライトを生成」するのではなく、「ユーザーからのアクションによって1タイルに1つのスプライトを生成する」方法を取るのはどうでしょうか。
これなら例えば40*40の大きさのマップの場合、今の作りだと1600個の情報を必ず保存しなければならないのに対し、「変更したタイルのModelだけを保存する」だけで済みます(例えば3つのタイルを染めたなら3つだけセーブデータに保存する)。
明らかにセーブデータの容量が少なくなりますね。
また、動的に生成するので生成する瞬間は重くなりますが(と言っても1つや2つなら大したことないと思いますが)、全体として見れば動作も軽くなるんじゃないでしょうか。

あるいはマップ全体に対して1つのModelで対応するのもありかもしれません。
ただこの場合はセーブデータに何を保存し、どうやって復元するのかとかが面倒な気も……。

今回はもともとセーブデータに保存するつもりがなかったのと、2日くらいでパパっと作りたいなあと思ったのでスプライトの生成周りが雑になっていましたが、パフォーマンスやセーブデータの容量を気にするなら「動的にスプライトを生成→タイルマップへ追加」することを検討してもよさそうですね。
やるかどうかは未定(結構コードも変更しないとですし……)。

終わりに

コアスクリプトがModelとViewでわかれてるとかいう話は、プログラミングを始めて一年くらいは意味がよくわかりませんでした。
最近は他の人が書いたプログラムを読んで「おっ、これはあのパターンだな」とわかることが増えてきて嬉しい限りです。
ですが次々と新しいアーキテクチャやパターンが出てきているので、ついていくどころか追いつくのすらなかなか難しいのが現状ですね……。
ウーンもうちょっとがんばりたい。

ほなそんな感じでまた。

フォローする