●はじめに
前: 【Unity】公式プロジェクトから、デザインパターンを学んでみよう(Part 0)
今回は、SOLID原則のSの部分である、単一責任の原則(SingleResponsibility)について、Unityの公式プロジェクトを参照しながら学んでいきます!(プロジェクトのリポジトリへのリンクは、前回の記事に記載しています。)
●単一責任の原則とは?
- めちゃくちゃ簡単に言うと、1つのクラスに詰め込み過ぎるなっていう原則です。(間違ってたら指摘ください)
- 詰め込みすぎずに細分化すれば、多人数での開発などで担当を分担したり、バグの数を減らすことができます。
●プロジェクトの内容
では、実際にUnity公式のプロジェクトから単一責任の原則のデザインパターンの例を見てみましょう。
-
Projectビューの、[1 SingleResponsibility]のScripts内に、今回のデザインパターンにならったスクリプトがあります。
-
内容はこんな感じです。
- Player
- PlayerAudio
- PlayerInput
- PlayerMovement
- UnrefactoredPlayer
○Player
- 以下3つのスクリプトの管理とかをするクラスっぽい。
- デフォルトだと、特に何もしないクラス。
○PlayerAudio
- プレイヤーに関する音声を再生するクラス。
○PlayerInput
- プレイヤーに関する入力判定をするクラス。
○PlayerMovement
- プレイヤーの移動に関するクラス。
○UnrefactoredPlayer
- 訳してみると、「整理されていないプレイヤー」 みたいな感じです。
- このスクリプトは、やってはいけないクラスの見本です。
●コードを見てみる
○UnrefactoredPlayer
まずは、ダメな例である「UnrefactoredPlayer」を見てみます。
クリックして展開
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DesignPatterns.SRP
{
// Even though this class is short, it violates single-responsibility.
// Too many things will cause the class to update, and extending the class will be more difficult.
public class UnrefactoredPlayer : MonoBehaviour
{
[SerializeField] private string _inputAxisName;
[SerializeField] private float _positionMultiplier;
private float _yPosition;
private AudioSource _bounceSfx;
private void Start()
{
_bounceSfx = GetComponent<AudioSource>();
}
private void Update()
{
float delta = Input.GetAxis(_inputAxisName) * Time.deltaTime;
_yPosition = Mathf.Clamp(_yPosition + delta, -1, 1);
transform.position = new Vector3(transform.position.x, _yPosition * _positionMultiplier, transform.position.z);
}
private void OnTriggerEnter(Collider other)
{
_bounceSfx.Play();
}
}
}
上部のコメントの翻訳を見てみましょう
このクラスは短いですが、単一責任に違反しています。
あまりにも多くのことがクラスの更新を引き起こし、クラスの拡張がより困難になります。
入力、移動、当たり判定などを1つのクラスにまとめちゃってる感じですね。
Unity始めたての頃はこんな感じでもいいと思いますが、本格的なゲームを作ろうとしたときにこんな感じだと、コメントの通り拡張性が失われちゃいます…。
入力は入力クラス、移動は移動クラス…などと細分化することが、単一責任の原則に則ったコードになります。
○Player
クリックして展開
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace DesignPatterns.SRP
{
[RequireComponent(typeof(PlayerAudio), typeof(PlayerInput), typeof(PlayerMovement))]
public class Player : MonoBehaviour
{
[SerializeField] private PlayerAudio playerAudio;
[SerializeField] private PlayerInput playerInput;
[SerializeField] private PlayerMovement playerMovement;
private void Start()
{
playerAudio = GetComponent<PlayerAudio>();
playerInput = GetComponent<PlayerInput>();
playerMovement = GetComponent<PlayerMovement>();
}
}
}
- [RequireComponent]で、このスクリプトをAddComponentしたときに、自動的に指定されたスクリプトもコンポーネントに追加するらしい。付け忘れ防止とかになるので、そこそこ便利かも。
- デフォルトの状態だと、ただコンポーネント取得するだけで、特に何もしてないです。
〇それ以外のクラス
- 中身は特に詳しく説明はしないです。細分化することが大切ってことが分かればいいので…。
●さいごに
よくあるのが、Managerっていう名前のクラスを作って、そこになんでも詰め込んじゃうっていうやつですが、単一責任の原則とは反しています(僕も現在進行形でそれしてます…。)
これも危険っぽいので、見直して設計してみます。
前の記事でも紹介しましたが、公式のちゃんとしたPDFの説明書があるので(※英語)、よろしければどうぞ↓