はじめに
本記事はUnityAdventCalendar2024の21日目の記事です。
本記事では主にUnityでゲームを作るうえでヤバいコードを書かないうえで
重要なことをまとめた記事です。
本記事の対象読者は以下のような方です。
・ミニゲームを数本作ったがコードがぐちゃぐちゃで機能追加などがやりづらい
・C#関連の書籍やサイト一通り読み基礎的なことは最低限分かっているが
Unityに活用できない
・中規模なゲームを作りたいが特大の神クラスが出来て困ってしまう
今回は簡易的なシューティングゲームを元に解説をおこないます。
ソースコードは以下URLにて公開しております。
プロジェクト概要
今回のプロジェクトではBadCodeフォルダとGoodCodeフォルダに分かれます。
まずはBadCodeフォルダで書かれているコードを元にどういったところが悪いかを
話してからどのように解消できるかをGoodCodeフォルダ内で書かれたコードを元に
解説していきます。
また全ての悪い内容を記事にまとめるのは難しいためBadCodeでは
悪いポイントをコメントで記述しています。
①命名
まずは以下のコードを見てください。
namespace BadCode
{
public class BulletManager : MonoBehaviour
{
public GameObject _kaihukuItem;
public GameManager gm;
}
}
namespace BadCode
{
public class EnemyManager : MonoBehaviour
{
float sp;
GameObject pl;
public int Co;
private GameManager _gm;
}
}
namespace BadCode
{
public class PlayerManager : MonoBehaviour
{
public GameManager n1;
public GameObject n2;
private int n3 = 10;
private float n4;
}
}
まず命名するときは他者が見ても分かりやすい名前にしましょう。
上記コードのように変な略語や無意味な命名”pl”や”sp”はダメですし、
"n1"や"n2"といった連番命名もダメです。
特にメンバ変数は多少長くなってもきちんと命名した方が良いと思います。
また命名規則はスネークケースなのか、パスカルケースなのかは統一しましょう。
日本語命名ですがプロジェクト内で全員が納得しているかつ統一出来るなら
個人的に良いと思います。
またクラスの命名などはルールを統一するようにしましょう。
今回のプロジェクトは○○Manager
といった命名で統一するようにしています。
他にも○○Controller
、○○
といった候補もありますがプロジェクト内でちゃんと
規則を持たせるようにすればよいと私は思っています。
②アクセス修飾子
もう一度上のコードを見てください。
変数によってpublicやprivateがバラバラだったり記述していないものもあります。
まずフィールドは基本的にprivateにしましょう。
UnityのInspector上で設定したいならA[SerializeField]を活用しましょう。
外部から値を取得したいならgetterを活用し、設定したいときは無効な値や細かな設定を
おこないたい場合が多いのでsetterではなくメソッド経由でおこなった方がいいです。
メソッドも外部から呼ばれない場合はprivateにしておきましょう。
③相互参照
namespace BadCode
{
public class GameManager : MonoBehaviour
{
public PlayerManager _player;
}
}
namespace BadCode
{
public class PlayerManager : MonoBehaviour
{
public GameManager _gameManager;
public EnemyManager _enemy;
}
}
namespace BadCode
{
public class EnemyManager : MonoBehaviour
{
public PlayerManager _player;
}
}
相互参照は避けるようにし、参照は一方向になるようにしましょう。
特にマネージャークラスとプレイヤー、敵とプレイヤーあたりは
何も考えずにコードを組むと相互参照になりがちです。
何故相互参照はダメなのか?をまず解説していきます。
まず処理を追うのが大変になります。プレイヤー側でマネージャーのメソッドを呼び、
さらにその中でプレイヤーの別メソッドが呼ばれるという風に1つの処理の流れ
を追うだけでも様々なクラスを行ったり来たりする必要が出てきます。
また上記のようなプログラムだと仮にプレイヤーが存在してマネージャーがいらないシーン
や敵がいらないシーンを作りづらくなってしまいます。
では相互参照はどうすれば解決できるのでしょうか?
答えはコールバック処理を活用しましょう。今回のプロジェクトでは
event Action
を使って解決しています。
他にもR3(UniRx)
やdelegate
なども活用できます。
神クラスについて
マネージャークラスは色々なことをおこなうためよく神クラスという巨大なクラスに
なりがちです。神クラスはよく避けるべきだ!という意見をよく見ますが個人的には
以下のようなことを守っていれば使ってもよいと思います。
・ゲーム全体の管理をしっかり一任させる
・他のクラスから参照されない
より詳しく解説していきます。
まずはしっかりとゲーム全体の管理を一任させましょう。間違っても神クラスを複数
存在するような作りにしてはいけません。神クラスが複数存在すると神クラスを管理する
大神クラスの誕生や神クラス同士の相互参照を生んでしまうためしっかりと1つの神クラスに
一任するようにしましょう。
また神クラスは他のクラスから絶対に参照されないようにしましょう。
仮に神クラスが他のクラスから参照されてしまうと実質複数の神クラスがあることと
同意儀になってしまい、参照を持つクラスが好き勝手出来てしまうからです。
そのため神クラスは他のクラスから参照されないように必ずしましょう。
string型を避ける
string型は出来るだけ避けた方が好ましいです。
Unityではアニメーションやシーン遷移、入力周りでstring型を使うことが多いですが
string型は変更が起きてもエラーが起きずらいところ難点です。
たとえばもともとHoge
という値でアニメーションを設定したのに
Animation側でHogeHoge
という名前に変更してもプログラム上ではエラーが起きず、
実行して初めて動かないことが確認できます。
そのため修正がとてもしずらいためstring型は避け、使う場合も一つのクラスに
まとめた方が良いです。
最後に
だいぶ駆け足になってしまいましたが今回はUnityでの設計に関する入門的な
記事を書きました。
他にもデザインパターンやSOLID原則、R3(Unirx)、UniTaskなど様々な紹介しきれていない
モノがあるので是非調べてより良い設計を目指してください。
(この記事が好評ならデザインパターンやSOLID原則などの記事も書くかもです)
参考記事・サイト