기존 코드를 수정하지 않고 다른 코드와 호환되게 해주는 중간 다리
어댑터 패턴은 기존 코드의 동작을 유지하면서,
다른 객체와 상호작용할 수 있도록 중간에서 기능을 변환해주는 구조다.

예를 들면,
포탄은 몬스터에게 대미지를 주는 역할을 하지만,

어댑터 패턴을 활용하면 같은 포탄이 타워에 맞았을 때는
몬스터들이 추격을 시작하거나 멈추는 행동으로 작동하게 만들 수 있다.
public interface IAttackable
{
public void Hit(GameObject gameObject, int damage);
}
IAttackable 을 상호작용을 할 클래스에 상속하면 된다.
현재 Monster와 Tower 클래스에 상속했다.
void OnTriggerEnter(Collider other)
{
// 다른 기능들...
IAttackable attackable = other.GetComponent<IAttackable>();
if (attackable != null)
{
attackable.Hit(gameObject, damage);
}
}
총알이 OnTriggerEnter 이벤트함수가 작동하면 닿은 객체에
GetComponent를 통해 IAttackable 인터페이스가 상속된 컴포넌트(클래스) 를 가져올 수 있다.
상속된 컴포넌트가 있으면 예외처리를 통해 인터페이스의 Hit 함수를 실행하는데
// Monster Class
public void Hit(GameObject gameObject, int damage)
{
Debug.Log("대미지입음");
hp -= damage;
if (hp <= 0)
{
Debug.Log("몹주금");
pool.Release(this);
}
}
포탄이 몬스터 클래스에 닿으면 포탄의 대미지만큼 체력이 감소되고,
// Tower Class
public void Hit(GameObject gameObject, int damage)
{
if (monsterManager == null) return;
bool nextState = !monsterManager.isChase;
monsterManager.SetChase(nextState);
if (nextState)
meshRen.material = states[0];
else
meshRen.material = states[1];
}
포탄이 타워 클래스에 닿으면 MonsterManager 와 연동해서 몬스터들이 플레이어를 추격한다.
즉, 총알은 항상 같은 방식으로 작동하지만,
타워나 몬스터에서 각각의 코드대로 기능을 하게 된다.
이렇게 하면 기존 코드를 바꾸지 않고도 다양한 기능을 구현할 수 있다.
하나의 인스턴스를 전역에서 공유하고, 중복 생성을 방지하는 구조
싱글톤 패턴은 게임 내에서 하나만 존재하고,
여러 스크립트에서 접근해야 하기 때문에 전역 접근이 가능해야 한다.
주로 게임 매니저같은 게임을 관리하는 매니저를 만들 때 사용하며,
게임에 사용하는 데이터들을 보관할 수도 있다.
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
}
Instance 정적(static) 변수를 통해
어디서든 GameManager.Instance 로 접근 가능하며,
Awake에서 중복된 인스턴스를 제거하여 단일 인스턴스를 보장한다.
DontDestroyOnLoad()는 씬이 전환돼도 오브젝트가 사라지지 않도록 해준다.
단점은 모든 코드에서 접근이 가능하므로
데이터가 의도치 않게 변경될 가능성이 있고,
여러 객체가 이 인스턴스에 의존하게 되면 구조가 복잡해질 수 있다.
또한 너무 많은 싱글톤을 만들면 정적 메모리 사용량 증가하므로
필요한 최소한의 객체만 싱글톤으로 사용하는 것이 좋다.
어떤 객체의 상태 변화가 있을 때, 다른 객체들이 자동으로 반응할 수 있도록 해주는 구조
전에 배웠던 Unity Event, Delegate 들이 옵저버 패턴이다.

public class Field : MonoBehaviour
{
public UnityEvent chaseEvent;
public UnityEvent stopEvent;
[SerializeField] GameObject displayer;
[SerializeField] Material[] states;
void OnTriggerEnter(Collider other)
{
if (other.gameObject.tag == "Player")
{
Debug.Log("몹들이 추격합니다");
SetState(true);
}
}
void OnTriggerStay(Collider other)
{
if (other.gameObject.tag == "Player")
{
SetState(true);
}
}
void OnTriggerExit(Collider other)
{
if (other.gameObject.tag == "Player")
{
Debug.Log("추격을 종료합니다");
SetState(false);
}
}
void SetState(bool isChase)
{
if (isChase)
{
chaseEvent.Invoke();
displayer.GetComponent<MeshRenderer>().material = states[0];
}
else
{
stopEvent.Invoke();
displayer.GetComponent<MeshRenderer>().material = states[1];
}
}
}
플레이어가 Field 클래스가 있는 오브젝트와 닿았을 경우
void ChasePlayer()
{
isChase = true;
while (poolMonsters.Count < maxMobCount)
{
pool.Get();
}
foreach (var monster in poolMonsters)
{
monster.GetComponent<Monster>().target = player;
}
}
chaseEvent.Invoke() 이벤트를 실행하고,
오브젝트와 떨어질 경우
void ChaseStop()
{
isChase = false;
foreach (var monster in poolMonsters)
{
monster.GetComponent<Monster>().target = null;
}
}
stopEvent.Invoke() 이벤트를 실행한다.
옵저버의 기본 구조는 주시 대상(Subject) 과 관찰자(Observer) 구조지만,
중간에 중계자(Mediator) 를 추가하여 유연하고 확장 가능한 구조로 확장할 수 있다.
Subject ↔ Mediator ↔ Observer
유니티에서 인터페이스를 어떻게 사용할 수 있을지 잘 생각이 안났지만
어댑터 패턴을 보니 되게 유연하게 사용할 수 있을 것 같다.