#概要
プログラミングを学び始めて3カ月くらい.
自分では結構頑張って勉強したつもりはある.
サンプルプログラムの意味は分かるし,プログラミングの教本の問題は割と解ける気がする.
CodeIQやABCとか競技プログラミングもちょっとは解ける気がする.
でも,ゲームを作ろうとすると何を手に付けていいか分からない.
何かオブジェクト指向ってものが必要らしいが,必要性を感じ取れない.
実際作ってみては見たものの,何か形にならないし,何か自分の作りたいものと違う.
そんな悩める脱初心者したい方に個人的な設計手法について語る文章です.
これは大いなる私見を含む,体験からくる設計法なので間違った部分も多々ありますので,ご指摘をいただければ幸いです.
#要点
設計が上達するために超えるべきステップが3つあります.
- 小さく作る
- 作るものを明確にする
- 変更を予測する
これらを体験することで,
設計とはプログラムの管理にすぎない
ということを感じていただきたいです.
以下の文章では,これらが分かるために簡単な仮想ゲームを作ることを例にあげて解説します.
#ぼくの つくる ゲームは サイコーさ!
しょ,しょぼい・・・
まず初めにどのようなゲームを作るかを考えます.そこで,一番簡単な方法,既存のゲームの真似をすることです.ポケモンでもいいですし,ドラクエでもいい.ここでは,スーパーマリオブラザーズを例に挙げます.
そこで考えなければならないものは,スーパーマリオブラザーズを構成する要素についてです.
- マリオ(操作キャラクター)
- クリボー(敵キャラ)
- ノコノコ(敵キャラ)
- コイン(アイテム)
- 土管
- 穴
- 地面
- はてなブロック
- ゴール
1-1を構成するもののみを書いてみましたが,色々ありますね.これと似たようなゲームを作りましょう.
この時に考えることは,**何を実装するか.**です.1.~9.の要素をすべて実装することは諦めましょう.
私が,実装するとした場合,
- マリオ
- ゴール
のみを作り始めます.
仮想的なコードを書くと,このような感じ.
mario_x = 0
mario_y = 0
goal_x = 10
goal_y = 0
//左キーを押したとき
if(key.left){
mario_x -= 1
}
//右キーを押したとき
if(key.right){
mario_x += 1
}
//ゴールの座標とマリオの座標が一致したらゴール
if(goal_x == mario_x && goal_y == mario_y){
callGoal()
}
とても基礎的なマリオです.ジャンプすらできません.敵も出てきません.
まず初めに目指すことは動くことです.これが最初の要点の小さく作るというところのポイントです.完璧なゲームは存在しない.という話もあるのですが,往々にして何も形にならずにゲームを作ることを諦めることがあるので,とりあえず動くものを作ることは大切です.
##あなたは ゲームについて なにもしらない!
先ほどのコードを見て,「ちょっとコードがレガシーすぎるな・・・」とか「設計って言うにもおこがましいレベルのコードではないか・・・」と感じられると思います.そういった方は以下のようなコードを書くかもしれません.
Interface Character{
bool isAlive(); //キャラクターが生きているか死んでいるか
void kill(); //キャラクターを殺す処理
void move(); //キャラクターを移動する処理
void attack(); //キャラクターが攻撃する処理
};
Class Mario : implements Character{ /* 内部の実装は省略 */ }
Class Kuribo : implements Character{ /* 内部の実装は省略 */ }
なるほど.Interfaceを利用することで,それを実装したクラスの仕様を規定し,使いまわしやすい実装にしているのか・・・と考えることが出来ます.
ここであなたは意気揚々とどんどんキャラクターの実装をすすめていきます.そこで,大きな壁にぶち当たります.
あなたはノコノコを実装するために以下のようにクラスを作り始めます.
Class NokoNoko : implements Character{
bool isAlive(){ /* 省略 */ }
void kill(){ /* !?!?!?!?!?!? */ }
void move(){ /* 省略 */ }
void attack(){ /* 省略 */ }
}
たぶん最初の壁はノコノコを殺す処理です.ここで解説をする必要はないかもしれませんが,**ノコノコはマリオに踏まれると,甲羅がステージに残される.**という仕様があります.しかし,現在の実装ではkill関数の引数には何もなく,ノコノコからフィールドへアイテムを書き込むためのメソッドは実装次第ではないかもしれません.
でも,あなたは頑張りました.そこは何とかノコノコからフィールドのデータに書き込むメソッドを作ることが出来ました.
しかし,ここであなたは気づいてしまいました.
ノコノコは常に甲羅を生み出すわけではない
具体的には以下の分岐でノコノコは甲羅を生み出しません
- ファイアーボールで殺された時
- マリオが蹴った甲羅で殺された時
- マリオがスター状態で轢かれた時
そうするとノコノコのkillメソッドは「誰がノコノコを殺したのか?」という情報を必要とします.そうするとおのずと,killメソッドに誰によって殺されたか?という引数が必要になります.
Class NokoNoko : implements Character{
bool isAlive(){ /* 省略 */ }
void kill(Character c){ /* 省略 */ }
void move(){ /* 省略 */ }
void attack(){ /* 省略 */ }
}
こうなるとパニック状態になります.kill関数の引数が変わってしまったことにより,Interfaceまで影響が波及し,それらを利用しているマリオ,クリボークラスにまで影響が及びます!
Interface Character{
bool isAlive(); //キャラクターが生きているか死んでいるか
void kill(Character c); //キャラクターを殺す処理 **修正**
void move(); //キャラクターを移動する処理
void attack(); //キャラクターが攻撃する処理
};
Class Mario : implements Character{ /* kill関数の実装を変更 */ }
Class Kuribo : implements Character{ /* kill関数の実装を変更 */ }
そして,もう一段絶望します.
マリオがスター状態で轢かれた時
という条件を見てしまいます.そうマリオの状態を公開メソッドから確認できるような仕組みが必要なのです!そうなると,
Interface Character{
bool isAlive(); //キャラクターが生きているか死んでいるか
void kill(Character c); //キャラクターを殺す処理
int getStatus(); //ステータスを取得する関数
void move(); //キャラクターを移動する処理
void attack(); //キャラクターが攻撃する処理
};
Class Mario : implements Character{ /* getStatus関数を実装 */ }
Class Kuribo : implements Character{ /* getStatus関数を実装 */ }
そして,getStatus関数を作り,マリオ,クリボークラスに修正が入ることになります.そういった変更がゲーム開発が終わるまで延々と続き,やる気を失って完成しないゲームが生まれます.
##ふこう ってのは なぜ おこるんだい?
なぜこのようなことが起きてしまったか?
- 仕様が大きい
- プログラマーの仕様把握能力に限界がある
この2点です.
たかだかマリオとスターとノコノコとクリボーが出てきただけで設計が破たんするときは破たんします.
ありがちな話ですが,「ゲームをとりあえず作りたい!」という気持ちだけでゲームを作り始めてしまい,仕様というものを,あまりにもおろそかにしすぎるために設計が破たんしてしまいます.そこで,一番やるべきなのは,出来る限り小さく作るということです.思い切って,小さく作ってしまいましょう.クソゲーでも構わないです.それを作りあげることに専念しましょう.**たぶんそれでも失敗します.**ただ救いがあるのが,仕様が小さいことです.そうすると最終的には力業で何とかなったりします.このような経験をすることで,
- 自分が最初思ってた仕様はどうだったか
- 最終的な仕様とどこに齟齬があったか
- 齟齬のあった差分は設計段階で埋められたのではないか
という見当ができるようになります.ただこれはある程度作り上げないと身に付きにくいです.だから,作り上げることが重要です.
そのように訓練をしており,練度が上がってくると,作るものが明確にすることができるようになってきます.したがって,先ほどの例では,
- マリオの状態
- チビ
- 普通
- ファイア
- スター
というマリオの状態にまで気を配れるようになり,その時,敵を倒したらどうなるか?その時,水に入った場合どうなるか?等に意識が向いてくるようになります.そこに意識が向いてくるようになると,後々の変更点が見えるようになり,設計で変更がカバーできるようになり,実際の変更タイミングでもスムーズに変更できるようになってきます.小さな仕様の小さなゲームの作り,自分の中での仕様の把握能力の上限値を上げる訓練していく必要があります.
#あれ? さっきまで あんなに はれてたのに・・・
##うわっ ぶつかる!!
「ゲームシステムの氷山」という絵を示します.これは適当に私が書いたものです.
"ゲーム"という大きな氷山を見たとき,プレイヤーは"楽しさ"というものを感じながらプレイをします.しかし,これは大きなゲームというものを見たとき,水の上に出てる小さな領域に過ぎません.
その下にあるのが"企画の視点"です.どういうゲームにすればプレイヤーは楽しめるか?ゲームはどのようなストーリーにすればいいか?クリボーだけでいいのか?ノコノコも出したほうが良いのか?そういった,"プレイヤー"が"楽しさ"を感じて貰うために,ゲーム自体のデザイン,"構成"を行います.
その下にあるのが,プログラマーの視点です.企画の方が考えたゲームデザインに対し,プログラムを書いていきます.どのようにプログラムを設計していけば,ゲームが完成するか?どのようにモジュール同士を結合させ,"管理"していけば,ゲームが簡単に構成可能か?そのようなことを考えてプログラムを作っていきます.
そして,最後の層がマネージャーの視点です.大体,プログラマーというのはプログラムを作るだけでいっぱいいっぱいになります.そこで,助っ人的に開発に入ったり,業務が"効率"的に動くように,ボトルネックを解消したりするのが役割になってきます.ここでいう効率的というものはゲーム開発のディレクションという側面もありますが,データの管理や,あるいはツール開発もこの部分に入ってきます.(この層は実際私には経験がないので,予測の範疇で書いてます)
注:あくまでこれはゲーム開発に限った話で,ビジネス的なところは横に置いています.
今,「これからゲームを作ろう!」と考えている人に必要なものは,「プログラマーの視点」です.
しかし,一方で経験があるのは「プレイヤーの視点」の部分です.この差があることが,うまくプログラムを組めない原因の1つです.自分の経験が「プレイヤーの視点」だけで,「企画の視点」「プログラマーの視点」というものが抜けているため,手戻りが発生し,完成しない.という現象が発生します.
おそらくゲーム作りたい方に経験があると思うのは,**「Unityとか使いにくい!」「RPGツクールとかもめんどくさいし使えない!」「そうか,ツールが悪いせいだ!だったら自分でツールを自作すればいいんだ!」**となります.「自分が作りたいものがふわふわ」「仕様がふわふわ」「設計がふわふわ」なゲームのために「効率化のためにツールを作ろう!」というのが無謀なことだというのがお分かりいただけると思います.そもそも,「企画の視点」「プログラマーの視点」が足りずゲームを開発できないのに,その下の"効率化"のためのツール開発は,ほぼ不可能です.
##いまだ! くらえ!
あなたが,だんだんゲームを企画したり,作ったりしているうちに,気づいてくることがあります.
なんとなくやばい予感がする
という第六感が働くようになってきます.
例えば,あなたがスーパーマリオブラザーズっぽいゲームを作りました.そうすると,このゲームの面白さ,難しさ,物足りなさなどが分かってくるようになります.そうすると改造したい欲求がふつふつと上がってきます.あなたはたまたま「自分の作ったマリオには爽快感が足りない.」と思いました.その時に,マリオに爽快感を足すためにはどうすればよいでしょう?
- 倍速マリオというゲームにして,敵の速度はすべて同じだが,自分だけ速度が倍になるゲームにする.
- 倍速帽子というアイテムを作り,ステージの要所要所に置く.
- 実は速度感は足りていた.演出の問題なのでモーションブラーをかけよう.
- いや,速度感をプラスするとただのソニックだ.このちょっとゆっくり目の速度感こそマリオだ.
この辺がゲーム開発が魔境化するかの境目になってきます.
いわゆる仕様変更というやつです.一番簡単なのは4.です.現状維持なので何もすることはありません.その次はおそらく3.です.演出面だけなのでコード上も,それほど問題になりません.その次が2.です.倍速帽子は面白い案かもしれません.スターのようなアイテムで,プレイヤーの状態を変える処理があったので,それを流用して使える可能性があります.また,倍速帽子は,そのアイテム専用のコースを新たに用意することで,既存のゲームのステージの影響を局所化できます.一番やばいのが1.です.これは,どこまでステージを作りこんでいるか.にもよりますが,ほぼ全部のステージが影響を受けます.もしかしたら倍速マリオでは,既存のステージがクリア不可になっているかもしれません.そのため,全ステージをテストし直し,バランス調整が必要になってきます.
こういう場合,私なら2.を選ぶと思います.上に書いた通りですが,「スターのようなアイテムで,プレイヤーの状態を変える処理があったので,それを流用して使える可能性があります.」と考えることが出来るので,要所要所に倍速の楽しいステージを作ることが出来ると思います.
ただ大前提として,アイテムによって汎用的にマリオの挙動を変えられる設計になっていることです
これはある意味予測しやすい問題かもしれません.今まで,歴代マリオシリーズをやっていると,タヌキマリオや,メタルマリオ,羽マリオ,スケスケマリオと色々あったと思います.その中で,そのような**"帽子によるマリオの挙動の変化"というのは設計段階で予測がつきます.**
あまり言われていないですが,このように**ゲームデザインの面から,企画の面から,ある程度,ゲームの変更点は予測できるものだと思います.**これが要点の3つめの
変更を予測する
という話です.ただこれは難しいです.私も,いまだにこれは失敗することも多いですし,用意したものの無駄になってお蔵入りすることも多いです.これができるようになると表現の幅が広がったり,複雑な分岐であっても,読みやすいコード,管理しやすいプログラムのフローにすることが出来ます.
#まとめ
ゲームを作り上げてみたい方は,まず
- ゲームは小さく作りましょう
ゲームには「プレイヤーの視点」「企画の視点」「プログラマーの視点」の3か所の視点が必要になってきます.大きなゲームを一気に作ることは難しいです.そのため,小さなゲームで訓練を積むことで,自分が見れる仕様の大きさの上限を広げる訓練をしましょう.この段階では拡張性などは度外視してください.そうすると
- 作るものを明確にする
ことが出来ます.どちらかというと,「作るものが明確になってくる」というイメージでしょうか.だんだん自分が作りたいゲームがどんなものか,何が必要か見えてきます.そして,何が楽しいゲームに必要かが分かるようになってくると,
- 変更を予測
できるようになります.これは難しいですが,ここが見えてくると,効率的にゲームを開発し,拡張も可能になってきます.
ここまで書いてきましたが,**プログラムというのは些末なことで魔境化し,コードのフローが追えなくなることが多々あります.**そのため,きっちりと仕様を把握し,設計することで,クリーンなコードを保つことが必要になります.そこで,最初に書いた,設計はプログラムの管理にすぎないということに繋がります.フローが追えるように,仕様変更に強いようにするための管理をするために,設計というものは必要だと私は思います.
これはゲームに限って書いていますが,大体のソフトはこんな感じで作れると思います.
あくまで目安ですが,最初に作るゲームは1000行以内,4ファイル(C/C++だとヘッダを合わせて8)以内ぐらいが良いのかもしれません.私が始めて作ったのはC/C++でテトリスでしたが,半年かかってそれくらいの規模だったと思います.
なかなかゲーム開発は長い道のりですし,設計というのはいまだに頭を悩ませる部分でもあります.しかし,そんな中でも,ゲーム製作をする人が増えれば,うれしいと思っております.
長文失礼いたしました.