はじめていらすとやの素材を探してみたけど、何でもあるんですねぇ。びっくり。
それはそうと、少し雑記的に昨日ふと思ったことを書き連ねようと思いました。
テーマは「ゲームプログラミングとMVC」についてって感じですが、なんとなくゲームプログラミングって流しそうめんに似てるんじゃないか、いやむしろゲームって流しそうめんなんじゃないかって気がしてきたってお話です。どうしてそうなった。
いや、別に変に考えが煮詰まってるなんてことないっすよ多分(´・ω・`)
ところでぼくはよくMVCモデルを利用してます
ぼくは普段会社ではWebアプリや業務系アプリを主に作っていたりします。言語は色々とあるのですが、比較的Javaで作ることが多く、特にフレームワークの指定がない場合はSpring3 MVCフレームワークを利用して開発することが多いです。PHPの場合はCakePHPですかね。いずれにしてもMVCフレームワークです。
会社でiOSアプリやAndroidアプリを開発する際も、基本的には同じように分離するか、難しい場合もプレゼンテーション層、ビジネスロジック層、データアクセス層といったレイヤで分離して開発することが殆どです。
とりあえず分離するっていうのは基本的にいつも行います。
MVCモデルとは何か
一応MVC(Model-View-Controller)パターンについて軽く説明しておくと、オブジェクト群をモデル(またはビジネスロジック)、ビュー、コントローラ(またはプレゼンテーション)といった層に分類し、それぞれ疎結合の状態を保つことで保守性や柔軟性や拡張性を高めるというパターンになります。
この際、それぞれが「レイヤ層」として分離できているかが重要となります。レイヤはペイントエディタなどにあるレイヤとほぼ同じようなものという認識でいいかなって思います。
コントローラはビジネスロジックとビューに何があるかをあまり知っててはいけないし、ビジネスロジックもビューも、同じように知っていてはいけません。これは責務の分離というオブジェクト指向の基本に沿っています。
そしてMVCフレームワークとは、そのMVCパターンに従う形で作成されたフレームワークです。
iOSを作成する際のCocoaフレームワークはMVCモデルとは言えないかもしれませんが、ドキュメントには言及されています(左記リンク45ページあたり)。
ちなみに、MVCモデルを流しそうめん的に言い換えると何になるでしょうか。
そうだね、「わんこそば」だね!
ゲームプログラミングとMVC
さてそんなわけなので、ぼくはレイヤ層に分離するというのに慣れてしまっていて、できればゲームプログラミングもMVCモデルで実装したいとずっと考えていました。
ただ、「ゲームプログラミング MVC」でググるとわかるのですが、適用する方法が紹介されている一方で、「ゲームプログラミングとMVCは相性が悪い」という記事も多いです。実際にcocos2d-xもUnityもMVCモデルを採用していませんし。
ただ、それでも諦めきれなかったのでcocos2d-xでレイヤ層の分離というのを色々と模索していました。
模索してみた
ちなみに、cocos2d-xは基本的にこんな構造になってます(だいぶ端折ってますが)。
基本的にScene、Layer、Spriteは全部Renderの為に利用することが多く、全てがViewとしての役割が強い気がするのですが、Scene、Layerは基本的には下位Nodeを格納するだけのことが多いため、Mediatorパターンが適応できそうです。
あとはSpriteやNodeが受け取ったイベントを上位に通知する方法として、Observerパターンを使い(Cocos2d-xにEventDispacherというものが既にあります)、Cocos2d-xにはないサービスロジック部分は自作することでうまくMVCモデルが適応できそうです。
やってみた結果
途中で挫折しました(´・ω・`)
いや、分離という点ではたしかにできたんです。ある程度疎結合になっていたようにも思えるし、拡張性と保守性も高くなるように設計できたんじゃないかなって思ってます。
じゃあ、何で挫折したかというと、「効率が悪かった」の一言です。もちろんそれも設計が悪いんじゃないのと言われればそれまでですが。
たとえばモデルに位置情報を持たせてSpriteで参照する例を考えます。
最初は別に何も問題ありません。モデルからcocos2d::Pointを受け取り、それをセットすれば済みます。
しかしその際にNode内にあるpositionとモデルのpositionは同期されません。
「ならsetPositionをオーバーライドして常に同期をとればいいじゃない」
となるかもしれませんが、そもそもとして同じ意味を持つ値を別のインスタンスが別のcocos2d::Pointインスタンスで保持しつづけるというのはどうなのよという問題に直面します。それってかなり密な結合ですよね。
上の問題は、Webアプリケーションや普通のアプリなんかではあんまり発生しないんじゃないかって思います。
たとえばゲームアプリケーション以外の場合、
- Controllerにイベントが渡される
- ビジネスロジックを呼び出して新しいモデルのインスタンスを作成する
- ビューにセットする
というように、モデルはリクエストのたびに新しいインスタンスが生成されるケースが殆どだからです。もしかしたら例外もあるかもしれないけど。
これはそうだね、わんこそばスタイルだね!
メモ帳やスケジュール帳といった他のアプリケーションも、ユーザのアクションがトリガとなってイベントが渡されるという点では同様です。その際モデルは使いまわされることもあるかもしれませんが、コントローラ→ロジック→ビューという流れは変化しません。
ゲームプログラミングも一応同じようにはできる
ゲームプログラミングも「ユーザのアクションがトリガとなる」部分に関して言えば同様になるでしょう。毎回モデルを新規作成してもいいだろうし、
- イベント通知の際にビューがモデルを渡す
- コントローラがロジックを呼び出す
- ロジックでデータを変更する
- ビューはそれをそのままセットする
というように、まるで流しそうめんのように上から下に流していく感じでやれば上で書いた問題は解決ですね。
え、わんこそば? なにそれ?
ゲームプログラミングはわんこそばではない
単純にユーザのアクションがトリガとなるだけなら多分うまくMVCモデルでできると思うのですが、効率が悪くなる一番の理由は、状態変化がイベントのトリガだけではないという部分にあります。
たぶん、普通のアプリケーションとゲームアプリケーションの一番の違いは以下になるでしょう。
- ユーザの操作以外にも状態変化のトリガが多数存在する
- インスタンスの状態は時間によって刻々と変化していく
- ビュー同士は複雑に関与しあう
どこかのネットの記事で「ゲームプログラミングとは時間を操るプログラミング」みたいな内容を読んだのですが(どこだったか失念しちゃいました)、ホントそうなんですね。
これはもうまさに流しそうめんですね。わんこそばは常にお椀の中にい続けるのに、流しそうめんは常に位置が変化し続けるんですからね。しかも竹の節にからまって長い間下まで流れてこないやつもいたり途中でそうめん同士が絡まったりとそりゃあ好き放題暴れ回ってくれるんですからね。というか、ゲームって流しそうめんですね。流しそうめん以外の何物でもないくらいですね。くどいですか。そうですか。
さて、それらを全て「コントローラ→ロジック→ビュー」という流れでできなくはないと思いますが、効率はやっぱり悪いんじゃないかと思うんです。
むしろ時間とともにそうめんが流れ続けるのなら、自分の次の位置は自分で決めさせる(ロジックをビューに持たせる)方が効率がいいんじゃないかなと考えるようになりました。
なお補足ですが、ビューにロジックそのものを記述すべきという話じゃないです。
それは開放閉鎖原則を満たさない可能性が高くなるし、著しく拡張性は落ちるかと思いますので。
要はロジックを担当するインスタンスをビューに保持させるってことですね。そして、そのロジック担当が常にビューを操作し続ければいいんです。まるで流しそうめんの中の人のように。
ここまで書いておいてなんだけど
実はよくよく考えたら、「ロジックを担当するインスタンスをビューに保持させる」っていうの、既にCocos2d-xが用意してくれてました。
ここまで書いておいてなんだよって感じですね。ごめんなさい(´・ω・`)
- Actionクラス
- Componentクラス
Actionクラスはアクションまとめ1、アクションまとめ2、アクションまとめ3にまとめてます。2.x系のだけど、「CC」部分をなくすとそのままCocos2d-x3.x系でも使えます。
Componentクラスというのは、UnityのComponentと殆ど同じらしいです。以下のサイトに詳しい使い方が載っています。
cocos2d-xのComponentパタン – 株式会社BEFOOL
物理エンジンのChipmunkも「cocos2d::PhysicsBody」というComponentが用意されていて、それを追加するだけで利用できるようになるみたいです。
ゲームプログラミングにおいては、MVCみたいにレイヤで処理を分離するようにするよりは、デザインパターンを駆使してそれぞれのクラスに頻繁に連携を取り合わせながら動かす方が時間を操るプログラミングとしては良いんじゃないかな、きっと。
そんなわけで、結論みたいなの
とりあえず以下のように今は考えるようになりました。
- ゲームプログラミングでMVCみたくレイヤ層を分けるのはあまり効率よくない
- 部分的な分離は意義がないことはないかも
- デザインパターンの適用または責任の分離は当然有要
- 流しそうめん食べたい
流しそうめん食べたい。