5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Craft EggAdvent Calendar 2021

Day 10

FactoryMethodパターンでボタン生成をしてみる@Unity

Last updated at Posted at 2021-12-20

本記事はCraft Egg Advent Calendar 2021の12/10分の記事です(投稿が遅れ申し訳ありません)。
12/9の記事は@aluminum1981さんの「瓶の中に入っている液体の簡易表現を実装してみる@Unity」でした。

FactoryMethodについて

「Virtual Constructor」とも呼ばれ、コンストラクタの代わりになるメソッドを作成、利用するパターンです。

Wikipediaでは、

Factory Method パターンは、他のクラスのコンストラクタをサブクラスで上書き可能な自分のメソッドに置き換えることで、 アプリケーションに特化したオブジェクトの生成をサブクラスに追い出し、クラスの再利用性を高めることを目的とする。

とあります。

生成するオブジェクトごとにファクトリがあり、生成処理はサブクラスで行うため、生成処理を簡潔にすることができます。

クラス図

([Wikipedia](https://ja.wikipedia.org/wiki/Factory_Method_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)より)

FactoryMethodパターンを用いる上で、以下の4つの要素が登場します。

  • 抽象クラス
    • Creator

      作成者(ファクトリ)のことで、Productをインスタンス化するためのインタフェースを定義する
    • Product

      ファクトリで生成したいオブジェクトのインタフェースを定義する
  • 具象クラス
    • ConcreteCreator

      生成したいオブジェクトをインスタンス化する(生成するConcreateProductクラスの種類だけある)
    • ConcreteProduct

      ファクトリで生成したいオブジェクトの実装をしているクラス

実装してみる

ボタンを例に、シンプルなC#コードで実装してみます

Product

製品の抽象クラスです。

public abstract class ButtonBase
{
    public string Name { get; protected set; }
    public string Label { get; private set; }
    protected Action onClick;

    // ラベル設定
    public void SetLabel(string label)
    {
        Label = label;
    }

    // ボタン押下時コールバック設定
    public void RegisterOnClickCallback(Action onClick)
    {
        this.onClick = onClick;
    }

    // クリック処理
    public void Click()
    {
        onClick?.Invoke();
    }
}

Creator

製品を生成する抽象クラスです。
具体的な内容はContreteCreatorが実装し、実際に生成するConcreateCreatorの事は何も知りません

public abstract class ButtonFactory
{
    public ButtonBase Create(string label, Action onClick)
    {
        ButtonBase button = createButton(label, onClick);
        return button;
    }

    protected abstract ButtonBase createButton(string label, Action onClick);
}

ConcreateProduct

2種の製品を生成する比較のため、通常ボタンと特別ボタンを用意してみました。

public class NormalButton : ButtonBase
{
    public NormalButton()
    {
        Name = "通常ボタン";
    }
}

public class SpecialButton : ButtonBase
{
    public SpecialButton()
    {
        Name = "特別ボタン";
    }
}

ConcreteCreator

実際の生成クラスです。
通常ボタンと特別ボタンの生成の処理に違いを持たせています。

public class NormalButtonFactory : ButtonFactory
{
    protected override ButtonBase createButton(string label, Action onClick)
    {
        NormalButton button = new NormalButton();
        button.SetLabel(label);
        button.RegisterOnClickCallback(onClick);
        return button;
    }
}

public class SpecialButtonFactory : ButtonFactory
{
    protected override ButtonBase createButton(string label, Action onClick)
    {
        SpecialButton button = new SpecialButton();

        // 特別なボタンはラベルに☆が付きます
        button.SetLabel($"☆{label}☆");

        // 特別なボタンはクリック時音が鳴ります
        onClick += playSpecialSound;
        button.RegisterOnClickCallback(onClick);

        return button;
    }

    private void playSpecialSound()
    {
        Debug.Log("キラリと音が鳴った!");
    }
}

呼び出し側

private ButtonBase normalButton;
private ButtonBase specialButton;

public void Start()
{
    NormalButtonFactory normalButtonFactory = new NormalButtonFactory();
    normalButton = normalButtonFactory.Create("押してね", onClickNormalButton);
    Debug.Log($"{normalButton.Name}をラベル「{normalButton.Label}」と表示させ作成しました。");

    SpecialButtonFactory specialButtonFactory = new SpecialButtonFactory();
    specialButton = specialButtonFactory.Create("押してね", onClickSeecialButton);
    Debug.Log($"{specialButton.Name}をラベル「{specialButton.Label}」と表示させ作成しました");
}
}

public void ClickNormalButton()
{
    normalButton.Click();
}

public void ClickSpecialButton()
{
    specialButton.Click();
}

private void onClickNormalButton()
{
    Debug.Log("NormalButtonがクリックされました");
}

private void onClickSeecialButton()
{
    Debug.Log("SpecialButtonがクリックされました");
}

実行結果

Start()
通常ボタンをラベル「押してね」と表示させ作成しました。
特別ボタンをラベル「☆押してね☆」と表示させ作成しました
ClickNormalButton()
NormalButtonがクリックされました
ClickSpecialButton()
SpecialButtonがクリックされました
キラリと音が鳴った!

生成するにあたっての固有の処理をサブクラスであるConcreateCreatorに持たせ、呼び出し側やCreatorはその内容を知らずに済む実装ができました。

Unity上でボタンとして実装する

先述したコードをベースに、Unityに実際のボタンとして生成させてみます。

コードはConcreateCreatorと呼び出し側のみ紹介し、他は省略しますが、以下のような変更を行いました。

  • Product
    • SerializeFieldでuGUIのButton, Textをアタッチ
    • Unityの方法に従ってラベルやOnClickを登録(AddListenerなど)
  • Creator
    • 生成時の位置を指定できるように、Createの引数にTransformを追加
  • ConcreateProduct
    • コンストラクタの処理をUnityのAwake()に置き換え
    • SpecialButtonはエフェクトを出すメソッドを作成(詳細は後述)
ConcreateCreator
public class NormalButtonFactory : ButtonFactory
{
    private const string PrefabPath = "NormalButton";

    protected override ButtonBase createButton(Transform root, string label, Action onClick)
    {
        GameObject prefab = (GameObject)Resources.Load(PrefabPath);
        NormalButton button = GameObject.Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity).GetComponent<NormalButton>();
        button.transform.SetParent(root, false);

        button.SetLabel(label);
        button.RegisterOnClickCallback(onClick);
        return button;
    }
}

public class SpecialButtonFactory : ButtonFactory
{
    private const string PrefabPath = "SpecialButton";

    protected override ButtonBase createButton(Transform root, string label, Action onClick)
    {
        GameObject prefab = (GameObject)Resources.Load(PrefabPath);
        SpecialButton button = GameObject.Instantiate(prefab, new Vector3(0, 0, 0), Quaternion.identity).GetComponent<SpecialButton>();
        button.transform.SetParent(root, false);

        // 特別なボタンはラベルに☆が付きます
        button.SetLabel($"☆{label}☆");

        // 特別なボタンはクリック時エフェクトが出ます
        onClick += button.PlayEffect;
        button.RegisterOnClickCallback(onClick);

        return button;
    }
}

呼び出し側
[SerializeField]
private Transform normalButtonRoot;

[SerializeField]
private Transform specialButtonRoot;

public void Start()
{
    NormalButtonFactory normalButtonFactory = new NormalButtonFactory();
    ButtonBase normalButton = normalButtonFactory.Create(normalButtonRoot, "押してね", onClickNormalButton);
    Debug.Log($"{normalButton.Name}を作成しました。");

    SpecialButtonFactory specialButtonFactory = new SpecialButtonFactory();
    ButtonBase specialButton = specialButtonFactory.Create(specialButtonRoot, "押してね", onClickSeecialButton);
    Debug.Log($"{specialButton.Name}を作成しました");
}

private void onClickNormalButton()
{
    Debug.Log("NormalButtonがクリックされました");
}

private void onClickSeecialButton()
{
    Debug.Log("SpecialButtonがクリックされました");
}

GIF 2021-12-19 20-05-42.gif

音を鳴らすのはGIFでは伝わらないため、特別ボタンを押すとキラキラ画像が出るように仕様変更しました。

※画像素材は以下のサイト様よりお借りしました。

七三ゆきのアトリエ https://nanamiyuki.com/11631

終わりに

デザインパターンの一つであるFactoryMethodを実装してみました。
生成に関する処理をクラスごとに記述し、利用側はそれを意識したり書き換える必要がないのが便利です。

今回はボタンを紹介しましたが、アウトゲームにおけるUIパーツ(ダイアログ、サムネイル、アイテムのアイコン etc...)など、色々な種類があって頻繁に生成されるものに対して有効なパターンの一つだと思いました。

参考

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?