

내가 쓰지 않은 코드를 쓰고 싶은데, 호환이 되지 않을 경우 어떻게 해야될까? 코드를 수정하자니, 어떻게 수정할지 막막하고 남의 코드를 건드리자니 이해를 할 수 없다. 이럴 때 쓰는 디자인 패턴이 어댑터 패턴이다
주의점
기존 코드를 변환하는 일 없이 받아들일 수 있게, 다른 여러 형식들을 받아 들일 수 있게 어댑터를 만든다. 어댑터가 많을수록 성능저하가 일어나니 최소한으로 만들어야 한다
public interface IDamagable
{
// 게임 오브젝트와 같이 추가적인 정보를 가져오려고 하는 경우에는 해당 부분을 추가해야 된다
// Monster 클래스에는 상위 클래스인 Component에 이미 프로퍼티 get이 구현 되어 있어 생략 가능하다
// 상위 클래스들에 get이 구현 되어 있지 않다면 추가하자
public GameObject gameObject { get; }
public Transform transform { get; }
public void TakeDamage(GameObject attacker, int attackPoint);
}
// 공격력
public int attackPoint;
// 충돌 시작 시
OnCollisionEnter(Collision collision)
{
// collision이 IDamagable 인터페이스를 가지고 있으면, 해당 인터페이스를 가져온다. 즉, Monster 컴포넌트 안에서 구현된 함수를 가져온다
IDamagable damagable = collision.gameObject.GetComponent<IDamagable>();
// 컴포넌트나 변수들도 가져올 수 있다
Rigidbody damagableRig = damagable.GetComponent<Rigidbody>();
// 요 경우는 깊은 복사라서 데미지 함수 수행을 해도 몬스터에는 적용되지 않는다. 함수를 사용하자. 정보를 가져와야 되는 경우에만 사용한다
int damagableHP = damagable.HP;
if(damagable != null)
{
Attack(damagable);
}
}
void Attack(IDamagable damagable)
{
// 인터페이스에 포함된 함수를 호출하여 수행. 몬스터에서 수행하는 것처럼 된다 damagable.TakeDamage(gameObject, attackPoint);
}
// 인터페이스 추가
public class Monster : MonoBehaviour, IDamagable
{
int HP;
// 데미지를 구현하는 것은 몬스터 컴포넌트 쪽에서 구현한다
void TakeDamage(GameObject attacker, int attackPoint)
{
HP -= attackPoint;
Debug.Log($"{attacker.name}에게 공격받음. {attacker.attackPoint} 데미지");
}
}
Bullet을 쐈을 때, Monster의 컴포넌트에 IDamagable 인터페이스가 포함되어 있어서 IDamagable형 변수 damagable로 컴포넌트를 대입 할 수 있다. 그 외의 오브젝트들은 해당 인터페이스 컴포넌트가 없으므로 자연스럽게 걸러진다IDamagable damagable 에는 인터페이스로 구현된 함수만 포함된다. 참조 타입의 얕은 복사이기 때문에, damagable.TakeDamage를 써도 해당 몬스터의 Monster 컴포넌트 내부에서 수행된다IDamagable 인터페이스를 상속받지 않는 컴포넌트를 작성해본다. 상호작용 시 ON / OFF 기능을 수행한다public class Switch : MonoBehaviour
{
[SerializeField] GameObject door;
private bool IsDoorOpened => door.activeSelf == false;
// 이 어트리뷰트를 사용하면 에디터 상에서 함수를 실행 시켜볼 수 있다
[ContextMenu("TestDoorAction")]
public void SwitchAction()
{
// if(door.activeSelf)
if(IsDoorOpened)
{
Close();
}
else
{
Open();
}
}
public void Open()
{
door.SetActive(false);
}
public void Close()
{
door.SetActive(true);
}
}
에디터에서 함수 실행해보기
- 컴포넌트에서 오른쪽 클릭하면 실행 가능한 함수를 에디터 상에서 실행 해볼 수 있다
IDamagable 인터페이스를 추가 해야할까? 그러기에는 뭔가 이상하다. 이럴때 어댑터가 등장한다유니티 이벤트로 구현한다. 해당 방법 말고 원리만 알고 있으면 다른 방식으로 구현해도 된다어댑터 이벤트에 수행하고자 하는 컴포넌트의 함수를 추가한다IDamagable 인터페이스를 통해 여닫기 기능으로 바꿔주면 된다public class SwitchAdapter : MonoBehaviour, IDamagable
{
// 이벤트의 매개변수는 인터페이스 함수의 매개변수에 맞춘다
public UnityEvent<GameObject, int> OnDamaged;
public void TakeDamage(GameObject attacker , int damage)
{
// if(OnDamaged != null) {OnDamaged.Invoke()} 의 간략화
OnDamaged?.Invoke(attacker,damage);
}
}

드래그&드롭 한다SwitchDoor()를 선택하면 어댑터 구현이 완료된다Bullet 스크립트의 OnCollisionEnter() 에서 Switch의 IDamagable 인터페이스를 가져와서 수행하는 TakeDamage() 함수가 어댑터의 TakeDamage()로 대체된다. 유니티 에디터 상에서 OnDamaged 이벤트에 SwitchDoor() 함수를 등록 했으므로, 해당 함수가 실행되며 스위치 기능이 수행된다레이캐스트로 일정 거리 안에 상호작용 가능한 물체가 있으면 G키를 눌러 상호작용하는 기능을 구현해보자void update()
{
if(Input.GetKeyDown(KeyCode.G))
{
TryInteract();
}
}
void TryInteract()
{
if (Physics.Raycast(muzzlePoint.position, muzzlePoint.forward, out RaycastHit hitInfo, 3f))
{
Debug.DrawLine(muzzlePoint.position, hitInfo.point, Color.green, 0.5f);
IInteractable interactObject = hitInfo.collider.gameObject.GetComponent<IInteractable>();
if (interactObject != null)
{
interactObject.Interact();
}
}
else
{
Debug.DrawLine(muzzlePoint.position, muzzlePoint.position + muzzlePoint.forward * 5f, Color.red, 0.5f);
}
}
G 키를 누르면 TryInteract() 함수로 상호작용을 시도한다public interface IInteractable
{
public void Interact();
}
스위치와 인터페이스를 연결할 어댑터의 스크립트 작성한다public class InteractAdapter : MonoBehaviour, IInteractable
{
public UnityEvent OnInteraction;
public void Interact()
{
OnInteraction?.Invoke();
}
}
SwitchDoor() 함수를 추가하면 된다

G 키를 눌렀을 때, 상호작용 거리가 기즈모로 표시된다. 현재는 닿은 물체가 없어 빨간 선이 나온다
TIP
해당 기능으로NPC와 상호작용, 보물 상자와 상호작용, 스위치, 문 등과 상호작용 등 다양하게 사용할 수 있다
참고
- 어댑터 패턴
- Head First Design Pattern : Adapter pattern