4. 神を殺す
SOLID原則などを学んだところで、早速実践してみましょう。最初にあなたが取り組むべきは、神クラスの抹殺です。
Unity初心者がやりがちなことは、GameManager なるクラスを作成し、とにかくゲームの全てをそこに書くということでしょう。あなたも最初はそのようにしていたはずです。そして、それによって非常に苦しい経験をしたこともあると思います。行数は何百、何千と膨れ上がり、手を入れようにもどこを修正すればいいのか分からない。少しコードを変えただけでバグで溢れかえり、新機能を少し追加するだけで日が暮れてしまったことでしょう。
これは明らかに単一責任の原則に違反しています。GameManager を見てみると、プレイヤーの移動、スコア管理、敵のスポーン、BGM制御…と、この一つのクラスの中にゲーム全体の責務を抱えていることが分かります。このように、あらゆる責務を持ち、何百行にもなるロジックが複雑に絡み合ったクラスのことを神クラスといいます。あなたがまずするべきことはこの神の抹殺です。
csharp
// ❌ Bad: あらゆる責務を詰め込んだ「神クラス」
public class GameManager : MonoBehaviour
{
// プレイヤーの移動
[SerializeField] float moveSpeed;
[SerializeField] Rigidbody playerRb;
// スコア管理
int score;
[SerializeField] Text scoreText;
// 敵のスポーン
[SerializeField] GameObject enemyPrefab;
float spawnTimer;
// BGM管理
[SerializeField] AudioSource bgmSource;
void Update()
{
// 移動処理
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
playerRb.MovePosition(transform.position + new Vector3(h, 0, v) * moveSpeed * Time.deltaTime);
// スコア表示更新
scoreText.text = $"Score: {score}";
// 敵スポーン処理
spawnTimer += Time.deltaTime;
if (spawnTimer > 3f)
{
Instantiate(enemyPrefab, GetRandomPosition(), Quaternion.identity);
spawnTimer = 0;
}
// BGM制御
if (!bgmSource.isPlaying) bgmSource.Play();
}
public void AddScore(int amount) { score += amount; scoreText.text = $"Score: {score}"; }
// ... まだまだ続く ...
}
// → 「移動の仕様を変えたい」だけなのに、スコアやBGMのコードの海を泳がなきゃいけないそこで、責務ごとにクラスを分割し、一つのクラスが一つだけの関心事を扱うようにします。
csharp
// ✅ Good: 責務ごとにクラスを分割
public class PlayerMover : MonoBehaviour
{
[SerializeField] float moveSpeed;
[SerializeField] Rigidbody rb;
// 自分ではUpdateしない。外から呼ばれることで動く
public void Move()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
rb.MovePosition(transform.position + new Vector3(h, 0, v) * moveSpeed * Time.deltaTime);
}
}
public class ScoreCounter
{
int score;
public int Score => score;
public void AddScore(int amount)
{
score += amount;
}
}
public class EnemySpawner : MonoBehaviour
{
[SerializeField] GameObject enemyPrefab;
[SerializeField] float spawnInterval = 3f;
float spawnTimer;
// 自分ではUpdateしない。外から呼ばれることで動く
public void SpawnUpdate()
{
spawnTimer += Time.deltaTime;
if (spawnTimer < spawnInterval) return;
Instantiate(enemyPrefab, GetRandomPosition(), Quaternion.identity);
spawnTimer = 0;
}
}
// → 移動の仕様を変えたい? PlayerMoverだけ見ればOK!場合によりますが、適切に責務を分割できているクラスは大体100~200行程度に収まります。そのくらいクラス一つ一つは小さなものになります。もちろんこれを超えていると即アウトというわけでも、この行数を目指してコードを書けという訳でもありませんが、目安の一つとして有効です。クラスに単一の責任を与え、小さく、明確に保ちましょう。