はじめに
Unity公式がプログラミングパターンのデモを説明書と共に全12種公開していたので、1つずつ読み取っていきたいと思います!
英語の説明書でしたので間違った解釈をしているかもしれませんが、ご了承下さい!
最初の5種は、SOLID原則に沿ったデモとなっており、コードのみが公開されています。
残り7種は、Scene付きのデモが入っていますので、ZIPファイルをダウンロードして試してみてください。
今回は、6つ目のFactoryについてです。
環境
OS : Windows10
Unity バージョン : 2021.3.8f1
Factoryパターンとは
Factoryパターンとは、オブジェクトの生成と生成に関する詳細を分けてしまうことです。
このパターンを用いる理由は、多くのオブジェクトが生成されながらも実際に必要になるまで実行時に分からないことがよくあるためです。
デモ
実際のデモを見ると理解しやすいので見てみましょう!
デモは、マウスでフィールド内をクリックし、クリックした場所から赤 or 黃のボールがランダムで生成されるようになっています。
また、各ボールは生成する際に以下の機能が付属しています。今回は、これらの機能が生成に関する詳細になります。
- 赤ボール → パーティクルが生成される
- 黃ボール → 音が鳴る(↑添付している動画に音は入っていません)
このゲームは以下のように作られています。
Hierarchy内にClickToCreateというオブジェクトが存在し、その子にFactoryA, FactoryBというオブジェクトが配置されています。
ClickToCreateというオブジェクトには、CrickToCreate.csがアタッチされています。
このクラスには、マウスのクリックを検知し、赤 or 黄のボールを生成する処理が書かれています。
public class ClickToCreate : MonoBehaviour
{
[SerializeField] private LayerMask layerToClick;
[SerializeField] private Vector3 offset;
[SerializeField] Factory[] factories;
private Factory factory;
private void Update()
{
GetProductAtClick();
}
private void GetProductAtClick()
{
// マウスクリック
if (Input.GetMouseButtonDown(0))
{
// FactoryA or Bランダムで選択
factory = factories[Random.Range(0, factories.Length)];
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hitInfo;
if (Physics.Raycast(ray, out hitInfo, Mathf.Infinity, layerToClick) && factory != null)
{
// 選択されたfactoryからボールを取得する処理
factory.GetProduct(hitInfo.point + offset);
}
}
}
}
各Factoryは、抽象クラスFactoryを継承しています。
public abstract class Factory : MonoBehaviour
{
public abstract IProduct GetProduct(Vector3 position);
}
public class ConcreteFactoryA : Factory
{
[SerializeField] private ProductA productPrefab;
public override IProduct GetProduct(Vector3 position)
{
GameObject instance = Instantiate(productPrefab.gameObject, position, Quaternion.identity);
ProductA newProduct = instance.GetComponent<ProductA>();
newProduct.Initialize();
return newProduct;
}
}
public class ConcreteFactoryB : Factory
{
[SerializeField] private ProductB productPrefab;
public override IProduct GetProduct(Vector3 position)
{
GameObject instance = Instantiate(productPrefab.gameObject, position, Quaternion.identity);
ProductB newProduct = instance.GetComponent<ProductB>();
newProduct.Initialize();
return newProduct;
}
}
抽象クラスのFactoryには、IProductを返すGetProductメソッドが実装されています。
この部分が、オブジェクトの生成部分であり、実際の生成に関する詳細の部分とは切り離して書かれています。
また、GetProductメソッドは抽象メソッドであるため各製品クラスで具体的に処理が書かれています。
FactoryAでは、ProductAの製造を、FactoryBでは、ProductBの製造を行うようにしています。
public interface IProduct
{
public string ProductName { get; set; }
public void Initialize();
}
public class ProductA : MonoBehaviour, IProduct
{
[SerializeField] private string productName = "ProductA";
public string ProductName { get => productName; set => productName = value ; }
private ParticleSystem particleSystem;
public void Initialize()
{
gameObject.name = productName;
particleSystem = GetComponentInChildren<ParticleSystem>();
particleSystem?.Stop();
particleSystem?.Play();
}
}
public class ProductB : MonoBehaviour, IProduct
{
[SerializeField] private string productName = "ProductB";
public string ProductName { get => productName; set => productName = value; }
private AudioSource audioSource;
public void Initialize()
{
audioSource = GetComponent<AudioSource>();
audioSource?.Stop();
audioSource?.Play();
}
}
各Productクラスには、音が出たりパーティクルが表示されたりと生成に関する詳細が書かれています。
長所と短所
Factoryを使用するに当たり長所と短所が以下に示したものになります。
個人的には短所はそれほど問題ないと感じています。
長所
- 新しいProductを作ることになっても既存のProductのコードを変更する必要がない
- 各Productごとにロジックを持つことでコードを短くできる
- 各FactoryではProductの詳細に関与することなく、各ProductのInitializeだけ
短所
- 多くのクラスとサブクラスを作成する必要がある
まとめ
Factoryパターンとは、オブジェクトの生成と生成に関する詳細を分けてしまうことです。
このパターンを利用すると、既存のコードを変更することなく、コードを短くすることができます。
また、Factory内ではオブジェクトのInitializeのみを行っているので、オブジェクトの詳細に関与しなくても良くなります。