僕が考えた最強のゲーム制作フロー
アドカレ
今年もアドベントカレンダー(以下アドカレ)の季節がやってきました。
アドカレでは、12/1〜12/25までの間、毎日ブログを更新していきます。
今年もたくさんの面白いブログが投稿されるので、楽しみにしていてください‼︎
はじめに
こんにちは!
Coading課 3回生のguuです。
この記事では、僕が考えた最強のゲーム作りの流れを解説しています。
長くなってしまったので、ざっくりみたい人は、下の資料を見てみてください。
資料
全体の流れをざっくり解説
僕が考えた最強のゲームフローはこれです。

企画
案出し
案出しのコツは、何のためにゲームを作るのか決めることです。
あなたのゲームは、誰のために作るゲームですか?
自分のため?、誰かのため?
あなたのゲームをプレイした人にどのように感じて欲しいですか?
何のためにゲームを作るのか決めておけば、ゲームの軸ができるので、ゲームの必要な機能や素材などが決めやすくなります。
企画を詰める、文書化する
企画の細かい部分を決めながら、どのようなゲームなのか文書化しましょう。
文書化をすると、自分の作りたいゲームがより明確になったり、チームメンバーにゲームのイメージを伝えやすくなります。
文書化のコツとしては、「できるだけ細かく」、「画像も用意する」です。
例えば、「ゴール扉に入るとステージクリア」という仕様を考えたとします。
しかし、これだけではゴールの扉にプレイヤーが触ったらクリアなのか、ゴールの扉の前で特定の操作をしたらクリアなのかなど、複数の解釈ができてしまいます。
複数の解釈は、チームメンバー間のイメージの齟齬を生み、「せっかく機能を作ってもらったのに思ってたのと違った」となってしまいます。
仕様はできるだけ複数の解釈を生まないように、
- ゴールの扉にプレイヤーが触れたら、扉に入る動作をしてリザルト画面を表示
- プレイヤーは空中にいてもよい
- プレイヤーはいかなる動作中でもそれを中断し、扉の地面に移動した後、扉にはいる動作を行う
のように細かく書きましょう。
また、画像はチームメンバー同士のイメージの齟齬を減らしてくれるので、入れられるところには積極的に用意しましょう。
文章として残しておくと良いものは、以下のものです。
- コンセプト
- ゲームジャンル
- ゲームプレイの流れ
- ゲームの仕様
- 必要な素材
- コーディング規約
特に、コンセプトは重要でこのゲームは、どういうゲームなのか、ゲームプレイで何を感じて欲しいのかを共有するためのものです。
チームメンバー間で認識の違いが発生しないように、きちんと定義して共有しておきましょう。
おすすめツール


- miro
企画の案出しに - GoogleDrive
文書の共有に
設計
画面設計

クラス設計
ゲームのシステムをクラスに分割して、文書化しましょう。
クラス図は、後述するゲームシステムの考え方などを参考にしてやってみるといいかもです。

役割分担
設計とは少しずれますが、このタイミングで役割分担もしておきましょう。

おすすめツール


実装

見直し

ゲームシステムの考え方じっくり解説
横スクロールアクションゲームの歩き移動を例に説明していきます。
ゲームの機能を考えよう
プレイヤーにはどのような機能があるでしょうか?
歩いたり、走ったりジャンプしたりいろいろあります。
このように、必要な機能を洗い出すのがクラス設計の第一歩です。

洗い出した機能を細分化しよう
必要な機能を洗い出したら、その機能について深堀りしていこう!!
例えば、「歩く」という機能について考えてみましょう。
歩くとは実際にはどのような処理になるでしょうか?
横方向の入力を受け付けている間、入力方向に毎フレーム0.1ずつx座標を更新する。
今回は、このように定義してみました。

細分化した機能をさらに分割しよう
では、先ほど定義した「歩き」の機能をさらに分割しましょう。
横方向の入力を受け付けている間、入力方向に毎フレーム0.1ずつx座標を更新する。
入力を受け付けるという機能とキャラクターを実際に動かす機能は別の機能です。
なので、「歩き」の機能は、
- 横方向の入力を受け付ける
- 入力方向に毎フレーム0.1ずつx座標を更新する
という2つの機能に分割できました。

機能をグループ化しよう
作成した機能をグループ分けしましょう。
今回作成した「歩き」の機能は、入力と座標変更の二つのグループに分割できます。

機能を関数に変えよう
先ほど分割した「歩き」の2つの機能を関数に書き換えましょう!!
例えば、下記のスライドのような関数が作成できると思います。
IsHorizontalKeyDown関数が入力を受け取り、Move関数が移動の操作を行っています。
関数化するときに考えるべきは、リテラルの存在です。
リテラルとは、数字や文字列など生の値を直接変数に代入しているものです。
こういったリテラルは、後から数値や文字列を変更しずらくなる原因です。
そのため、あらかじめ関数外の変数として定義したり、引数にしたりするなどして対処しましょう。
今回は、HORIZONTAL_KEY_PARAM変数やspeed引数などで対応しています。

作った変数、関数をクラスにしよう
IsHorizontalKeyDown関数とMove関数をクラスにしましょう!!
クラスとは、変数と関数が集まってできたものです。
実は、Unityでスクリプトを作ると勝手にスクリプト名のクラスが生成されていました。
public class Player: MonoBehaviour
{
private const string HORIZONTAL_KEY_PARAM = "Horizontal";
float IsHorizontalKeyDown()
{
return Input.GetAxisRaw(HORIZONTAL_KEY_PARAM );
}
void Move(Vector3 direction, float speed)
{
transform.position += direction * speed;
}
}
これでクラスは完成‼
なわけはなく、最悪のクラスが完成しました
クラスを作るうえで大切なことは、適切な命名です。
プログラマーがバグや仕様変更に対応するときには、たいていクラス名から探します。
今回作ったPlayerクラスを見てみましょう。
Playerクラスという名前からプレイヤーに関することが入っているということが推測できます。
ん?プレイヤーに関することが書いてあるってことは、初めに決めたプレイヤーの機能全部が書いてるってコト!?
こんなクラスから、移動の処理の修正や入力の修正など探してられません。
クラスだけでなく、関数や変数などきちんと機能やデータが推測できる名前にしましょう。
今回作成したクラスでは、入力を受け取る機能とモデルを動かす機能が含まれています。
つまり、PlayerInputReaderAndMoverクラスってことですね。
こんなクラスがいいクラスなわけないんですが…
うまいクラス名が思いつかないということは、クラスに機能が多すぎるということです。
オブジェクト指向の原則であるSOLID原則には、「1つのクラスには、1つの責任」という文言があります。
クラスに複数の機能があるとクラスを作った時のあなたしかわからないクラスになりがちです。
きちんと、クラスを分けることを意識しましょう‼

公開する範囲を考える
作成した関数や変数を外部から使えるようにするのかどうか考えましょう。
すべての変数や関数、クラスには、アクセス修飾子というものがつけられます。
アクセス修飾子というのは、publicやprivateといったもので、その変数や関数をどこまで公開するのか決められます。
アクセス修飾子 | 説明 |
---|---|
private | クラス内でのみ使用可能 何か理由がない限り、全変数や関数が使うべき |
protected | クラス内と継承先で使用可能 継承先で使う場合のみ使いましょう |
public | 全クラスが使用可能 外部に公開するものにのみ使いましょう |
internal | 同じモジュール内で使用可能 アセンブリ分割をしだしたら使いましょう 使えるようになったら、Unity中級者脱却です |
この表のとおり、特別な理由がない限りprivateを使うべきです。
例えば、privateな変数、関数でエラーが発生した場合に影響があるのは、その変数や関数を持つクラスだけです。
しかし、publicな変数や関数でエラーが発生した場合、その変数や関数を持つクラスを使っているクラスだけでなく、そのクラスを使ったクラスにもどんどん影響が伝播していきます。
そうなった時の修正はとても面倒なものです。
できる限り、影響範囲が狭いアクセス修飾子を使うように意識しましょう。

クラスをグループ化しよう
作成したクラスは、どちらもPlayerのものです。
であれば、これらのクラスをPlayerグループに所属させましょう。
クラスのグループ分けには、名前空間というものを使います。
名前空間を使うことで、名前の衝突を回避したり、クラスがどのグループのものなのか分かりやすくすることができます。
このようなカテゴリをモジュールと呼ぶことがあります。厳密にモジュール化するには、アセンブリ分割する必要がありますが…
モジュールをイメージできるようになると、「このモジュール単体だとちゃんと動く」、「このモジュールはほかのモジュールとつながりが強すぎるな」というように、開発の効率や質が上がるので、まずは名前空間でグループ分けをしてみて、モジュールとは何なのかイメージをつかんでみましょう。


クラスをつなげよう
プレイヤーの歩きに関するクラスは、出そろいました。
次に、このクラスたちを実際に動かせるようにつなげる必要があります。
この時に、入力クラス内で座標変更クラスを使ったりするとクラスを分けた意味がなくなってしまうので、クラス同士をつなぐためのクラスを作成しましょう。
このクラス同士をつなぐクラスは、あくまでプレイヤーに関するクラスだけつなげるようにしましょう。
例えば、すべてのクラスに関して、クラス同士をつなぐようにしてしまうと、だれも読めないクラスが爆誕します。
逆に、動きに関するだけであったり、扱うクラス数を減らせば減らすほどより安全なクラス設計になります。

同じものは共通化する
同じ処理・データがコピペされて、様々な場所にあるとその部分でエラーが発生したときに、そのコピペしたすべての場所を修正する必要があります。
このような問題を防ぐために、同じ処理・データは共通化しましょう。
同じものの共通化のやり方で、よく使うものは2通りあります。
staticクラス
一番簡単な方法は、よく使う処理・データをstaticクラスにまとめる方法です。
staticクラスとは、ゲーム中ずっと存在しているただ1つのクラスです。
いつでもだれでも使えるので便利そうですが、全クラスに影響するクラスになっているので、慎重に扱う必要があります。
staticクラスに処理をまとめる場合は、絶対にstaticクラスが使われる側になることを意識しましょう。
また、staticクラスの変数は絶対に変わらないようにしましょう。
ベースクラス
ベースクラスにまとめる方法もよく使います。
ベースクラスとは、継承される前提のクラスです。
例えば、MonoBehaviourもベースクラスの1種です。
MonoBehaviourは、ゲームオブジェクトにアタッチできるクラスで、Unityでスクリプトを作成すると勝手に継承しています。
MonoBehaviourでは、gameObjectやtransfomrmといったゲームオブジェクトが持っているデータの記述があり、継承したクラスは何も書かなくても使用することができます。
このような、ベースクラスを作成し、共通した処理・データを書いておくことで、クラスを継承するだけで同じ処理を書く必要がなくなります。
継承については、今後のアドカレでも書く予定なので、そこで理解してみてください。

おわりに
長々とお付き合いいただきありがとうございました。
今回紹介したゲームフローはあくまで僕が最強と思っているものなので、皆さんも個人個人で最強のゲーム制作フローを確立してみてください!!
これからも、よりつよつよのUnityエンジニアを目指して頑張りましょう‼
明日は、UnityのおすすめAssetを紹介していきます。