【Unity】依存性の逆転サンプル

設計手法の1つについてのサンプルになります
このサンプルのメリットは、保守性の向上、拡張性の向上にあります

実行結果

まず、実行結果を見てみましょう
ゲームオブジェクトをマウスクリックした時、そのオブジェクトに何からの影響を与える仕様になっています
あらかじめ、仕様が提示されていないので、2つのオブジェクトのそれぞれの挙動からプロジェクトを作成すると、作成する人ごとにたくさんの様々なプロジェクトになるでしょう

考察

実行結果から、どのように実現されているのかを考えてみましょう

Input.GetMouseDown()メソッドを使いますか?
タグを使いますか?
Rayを使いますか?

では、オブジェクトを増やす仕様だった・・・としたら?

聞いていないよ・・・ですね
では、そのような拡張性やゲームオブジェクトごとの変更に耐えうる仕組みを作りたい場合、どのような定石があるのでしょうか

依存性逆転のプロジェクト

シーン構成

クラス図

スクリプト

using UnityEngine;

public class Manager : MonoBehaviour
{
    public EnemyBase SelectedEnemy;

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (SelectedEnemy == null)
            {
                Debug.Log("オブジェクトは選択されていません");
                return;
            }

            Debug.Log($"選ばれたオブジェクト名 {SelectedEnemy.name} , 裏表状態 {(SelectedEnemy.IsSelected ? "表" : "裏")}");
        }
    }
}
using UnityEngine;
using UnityEngine.EventSystems;

// インターフェースを実装します
public class ClickableObject : MonoBehaviour, IPointerClickHandler
{
    // ここに、クリックした時に実行したいコードを記述します。
    public void OnPointerClick(PointerEventData eventData)
    {
        Debug.Log("クリックイベントが発生");

        eventData.pointerClick.GetComponent<IClickable>()?.ClickAction();
    }
}
public interface IClickable
{
    void ClickAction();
}
using UnityEngine;

public abstract class EnemyBase : MonoBehaviour, IClickable
{
    Manager Manager;

    public int Hp;

    public bool IsSelected;

    public abstract void Attack();

    void Start()
    {
        Manager = GameObject.Find("Manager").GetComponent<Manager>();
    }

    public void ClickAction()
    {
        IsSelected = !IsSelected;

        Manager.SelectedEnemy = IsSelected ? GetComponent<EnemyBase>() : null;

        Attack();
    }
}
using UnityEngine;

class Slime : EnemyBase
{
    public override void Attack()
    {
        Hp = 10;
        Debug.Log("噛みつき攻撃");
        ChangeColor();
    }

    void ChangeColor()
    {
        GetComponent<Renderer>().material.color = IsSelected ? Color.red : Color.blue;
    }
}
using UnityEngine;

public class Skeleton : EnemyBase
{
    public override void Attack()
    {
        Hp = 20;
        Debug.Log("ほね投げ攻撃");
        Fade();
    }

    void Fade()
    {
        Color color = GetComponent<Renderer>().material.color;

        color.a = IsSelected ? 0.1f : 1.0f;

        GetComponent<Renderer>().material.color = color;
    }
}

参考

@startuml
class Skeleton {
    + <<override>> Attack() : void
    - Fade() : void
}
EnemyBase <|-- Skeleton
class Slime {
    + <<override>> Attack() : void
    - ChangeColor() : void
}
EnemyBase <|-- Slime
abstract class EnemyBase {
    + Hp : int
    + IsSelected : bool
    + {abstract} Attack() : void
    - Start() : void
    + ClickAction() : void
}
MonoBehaviour <|-- EnemyBase
IClickable <|-- EnemyBase
IClickable "use"<.. ClickableObject
EnemyBase --> "Manager" Manager
class Manager {
    - Update() : void
}
MonoBehaviour <|-- Manager
Manager --> "SelectedEnemy" EnemyBase
class ClickableObject {
    + OnPointerClick(eventData:PointerEventData) : void
}
MonoBehaviour <|-- ClickableObject
IPointerClickHandler <|-- ClickableObject
interface IClickable {
    ClickAction() : void
}
@enduml