public interface IDamagable
{
public GameObject gameObject { get; }
public void TakeDamage(GameObject dealer, int damage);
}
위 코드처럼 인터페이스로 구성되었을 때 인터페이스(계약서)를 지켰을 때
public class Bullet : MonoBehaviour
{
[Header("Components")]
[SerializeField] Rigidbody rigid;
[Header("Propertis")]
[SerializeField] GameObject explosionEffetPrefab;
public int attackPoint;
private void OnCollisionEnter(Collision collision)
{
Destroy(gameObject);
Instantiate(explosionEffetPrefab, transform.position, transform.rotation);
IDamagable damagable = collision.gameObject.GetComponent<IDamagable>();
if (damagable != null)
{
Attack(damagable);
}
}
private void Attack(IDamagable damagable)
{
damagable.TakeDamage(gameObject, attackPoint);
}
}
GetComponent 로 인터페이스를 받는 것도 가능하다.
GetComponent를 사용할 때 주의점은 같은 컴포넌트가 있을 때, 가장 위의 컴포넌트를 가져온다.
Adapter Pattern이란, 서로 호환되지 않는 인터페이스를 가진 클래스들을 중간에서 연결해주는 ‘변환기 역할’ 클래스를 만들어 호환 가능하게 만드는 구조적 디자인 패턴이다.
장점으로는 기존 코드를 수정하지 않기 때문에 해당 코드가 변경 불가능하거나 업데이트 될 때에도 대응할 수 있고, 최소한의 변경으로 기존 코드를 재사용할 수 있다.
단점으로는 클래스가 많아져 사용에 혼란스러움이 생기거나 인스펙터를 보아야 상호작용에 대해 알 수 있고, 스크립트만 보고는 무엇으로 상호작용하는지 파악하기 힘들어진다.
어댑터 패턴 코드 예시
public class Switch : MonoBehaviour
{
[SerializeField] GameObject door;
private bool IsDoorOpened => door.activeSelf == false;
[ContextMenu("TestDoorAction")]
public void SwitchAction()
{
if (IsDoorOpened)
{
Close();
}
else
{
Open();
}
}
public void Open()
{
door.SetActive(false);
}
public void Close()
{
door.SetActive(true);
}
}
public class DamageAdapter : MonoBehaviour, IDamagable
{
public UnityEvent<GameObject, int> OnDamaged;
public void TakeDamage(GameObject dealer, int damage)
{
OnDamaged?.Invoke(dealer, damage);
}
}
싱글톤 패턴이란 오직 한 개의 클래스 인스턴스만을 갖도록 보장하고 이에 대해 전역적인 접근점을 제공하는 디자인 패턴이다.
public class Singleton : MonoBehaviour
{
private static Singleton instance;
public static Singleton GetInstance() // 여기만 public
{
if (instance == null)
{
instance = new Singleton();
}
return instance;
}
private Singleton() { } // private 생성자로 만들어야 외부에서 여러개 만드는 것을 방지
}
구현 방법은 다음과 같다.
1. 단일 인스턴스를 유지하기 위해 생성자의 접근권한을 외부에서 생성할 수 없도록 제한
2. 단일 인스턴스를 보관할 정적 변수를 구성
3. 외부에서 접근이 가능한 GetInstance 함수를 구성
4. GetInstance 함수에서 단일 인스턴스가 없을 경우 인스턴스를 생성하여 정적 변수에 참조
5. GetInstance 함수에서 단일 인스턴스가 있을 경우 정적 변수에 참조된 인스턴스를 반환
장점으로는 하나의 인스턴스를 전역적으로 관리하기에 주요 클래스, 관리자 역할로 적합하며 참조에 필요한 작업 없이 빠른 접근이 가능하다는 점이다. 또한 인스턴스들이 싱글톤을 통하여 데이터를 공유하기 쉬워진다.
그러나 단점으로는 너무 많은 책임을 짊어지는 경우 단일책임원칙을 위반하게 되며 전역 접근으로 코드의 결합도를 높이기에 남발 및 오용하지 말아야 한다. 또한 단위 테스트를 어렵게한다.
싱글톤은 씬에 종속적이지 않아야한다.
싱글톤이 씬에 종속적이라면, 씬이 전환되면 사라지는 오브젝트 즉, 특정 씬 안에 배치되어 있고, 씬이 바뀌면 함께 삭제되는 오브젝트가 된다면 데이터를 유지할 수 없게되고, 씬마다 GameManager를 새로 만들어 버그를 유발하게 된다. 따라서 DontDestroyOnLoad(gameObject)를 사용해야 한다.
OnDestroy()는 순서를 보장하지 않음
OnDestroy()
gameManager.exp++;
Unity에서 씬이 종료되거나 애플리케이션이 종료될 때는, 오브젝트의 OnDestroy() 순서를 보장하지 않는다. 따라서 GameManager부터 삭제 후 몬스터의 OnDestroy()가 실행되면 NullReferenceException이 발생하게 된다.
게임매니저와 같은 싱글톤 스크립트를 프리팹화하여 Resources 폴더(프로젝트 내에 이름이 Resources인 폴더를 만들면 Resources.Load<T>()를 통해 런타임에 리소스를 불러올 수 있는 폴더이다.)에 넣고 Resources.Load를 통해 불러오는 방법이다.
공통적으로 어떤 씬에나 필요한 것들을 사용하면 좋지만 이 방식의 단점으로는 남용하게 된다면 게임의 크기가 커진다.
public class GameManager : MonoBehaviour
{
private static GameManager instance;
public static GameManager Instance => instance;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void Init()
{
if (instance == null)
{
GameManager prefab = Resources.Load<GameManager>("GameManager");
if (prefab != null)
{
GameObject obj = Instantiate(prefab.gameObject);
instance = obj.GetComponent<GameManager>();
DontDestroyOnLoad(obj);
}
else
{
Debug.LogError("GameManager 프리팹을 Resources 폴더에서 찾을 수 없습니다!");
}
}
}
private void Awake()
{
if (instance != null && instance != this)
{
Destroy(gameObject); // 중복 제거
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
}
}
Scene 생성법:
Project - Create - Scene
실행 파일을 만들 때 포함된 씬만 빌드
인덱스 중 0번 씬이 시작하자마자 실행될 씬
Main Menu - File - Build Settings - Scenes in Build
SceneChanger 코드 예시
private void Update()
{
if (Input.GetKeyDown(KeyCode.Alpha0))
{
SceneManager.LoadScene("TitleScene");
}
else if (Input.GetKeyDown(KeyCode.Alpha1))
{
SceneManager.LoadScene("GameScene");
}
}
옵저버 패턴이란 한 객체의 상태 변화를 의존 관계에 있는 여러 객체에게 자동으로 알리고, 그 객체들이 자동으로 반응하도록 만드는 패턴이다.
Subject (Publisher): 주시대상이 되는 데이터와 옵저버들을 가지고 있는 주체이다. 데이터 변경시 등록된 여러 옵저버들에게 메서드를 통해 메시지를 전달한다.
Observer (Subscriber): Subject를 주시하고 있는 관찰자이며, 데이터 변경에 대한 메시지 수신 시 자신이 해야 할 동작을 수행한다.
중계자 방법을 사용하면 좋다.(코루틴 등을 이용하여 나눠서 제공하는 것)A -> B 발송, B -> A 발송