유니티에서 옵저버 패턴을 사용하여 프로그래밍한 것은 매우 중요한데,
만약 두 개의 컴포넌트가 서로의 정보(변수, 메서드 등)를 공유해야하는 상황에서 서로 결합(tightly-coupled)되어있다면,
서로가 영향을 미치기에 하나의 컴포넌트를 수정하면 다른 컴포넌트에서 에러가 발생하기에 수정에 불리하다.
이는 객체지향 원칙에 걸맞지 않는 상황인 것이다.
디자인 패턴 중 옵저버 패턴(Observer Pattern)은 시스템을 확장성(Scalable), 유지성(Maintainable), 비결합성(Loosely-coupled) 모두를 충족하게 만들어준다.
OOP 코드를 작성할 때, 비결합(Decoupled)으로 코드를 작성해야 에러가 날 확률도 적고 모듈화하기도 쉬우며,
디버깅하기도 쉽다.
아래 코드는 싱글톤 패턴을 이용하여 플레이어 컨트롤러 내에 있는 변수 혹은 메서드에 접근하여
값을 받고 변화하는 코드이다.
서로의 Class는 매우 의존성과 결합성이 강해서 객체지향원칙과는 거리가 있는 코드라고 볼 수 있다.
MyPlayerController에 있는 StopPicking이라는 메서드가 이름이 변하거나 한다면 이를 사용하기 있는
모든 코드에서도 변경해야하며, 관리하기도 어렵고 에러가 나기 매우 쉬운 상황인 것이다.
// GameManager Script
public Teleport(place: string) {
MyZepetoCharacter.Instance.IsPickingOnFloor = false;
MyZepetoCharacter.Instance.IsPickingOnTree = false;
MyZepetoCharacter.Instance.IsInLake = false;
MyZepetoCharacter.Instance.IsPickingOnLake = false;
MyPlayerController.Instance.StopPicking();
MyPlayerController.Instance.StopCheckPickCoroutine();
// ...
// MyPlayerController Script
// property - instance
private static _instance: MyPlayerController = null;
// ...
if (!MyPlayerController._instance) {
MyPlayerController._instance = this;
}
// ...
public StopPicking() {
this.StopFarmCoroutine();
this.farmGuage.SetActive(false);
if (!this.myZepetoCharacter.IsPickingOnLake) {
Manager.UI.SetJump(true);
Manager.UI.SetSneakBtn(true);
}
// ...
위와 같은 예시뿐만 아니라, 플레이어가 데미지를 받을 때 UI에 적용하는 상황, SFX를 실행하는 상황,
카메라를 줌인 하는 상황, 포스트프로세싱 효과를 적용해야 하는 상황 등 여러 메소드를 동시에 실행해야하는 상황에서
여러 메소드를 받는 것은, Player Class가 외부 클래스(SFX, UI ...)들에 매우 의존적인 상황이 되며
이는 수정하거나 유지하기 매우 어렵다.
옵저버 패턴을 사용한다면 이러한 문제를 해결할 수 있다.
옵저버 패턴은,
- 객체들 사이에서 일대 다수의 관계를 형성하게 해준다.
- Subject라는 하나의 객체는 Observers라는 다수의 객체들에게 신호를 보낸다. (공격당했다라는 식으로 Broadcast)
- Observers들은 이벤트가 발생했다거나 상태가 변했다라는 신호를 받게 된다. (React)
- Observers들은 신호에 맞춰 각자의 로직을 실행함으로써 반응을 하게 된다.
- Observers들은 다른 Observers와 반응할 필요없이 받은 신호에 따라 자신들의 로직만 실행해주면 되기에
OOP의 원칙과 맞게 된다.
옵저버 패턴을 구현해보자면,
우선 Subject라는 추상클래스를 하나 만들어서, 다른 클래스들이 상속받아 사용할 수 있도록 한다.
using System.Collections.Generic;
using UnityEngine;
public abstract class Subject : MonoBehaviour
{
// a collection of all the observers of this subject
private List<IObserver> _observers = new List<IObserver>();
// or list set
// private HashSet<IObserver> _observers = new HashSet<IObserver>();
// or type of dictionary
// private Dictionary<int, IObserver> _observers = new Dictionary<int, IObserver>();
// add the observer to the subjects' collection
public void AddObserver(IObserver observer)
{
_observers.Add(observer);
}
// remove the observer from the subject's collection
public void RemoveObserver(IObserver observer)
{
_observers.Remove(observer);
}
// notify each observer that an event has occured
protected void NotifyObservers()
{
_observers.ForEach((_observer) =>
{
_observer.OnNotify();
})
}
}
IObserver라는 인터페이스로 리스트를 만들었기에, IObserver라는 인터페이스도 만들어준다.
public interface IObserver
{
// subject uses this method to communicate with the observer
// must implement the OnNotify method
public void OnNotify()
{
// do something when the event happens!
}
}
이렇게 함으로써, Subject로 활동할 Class는 Subject를 상속받아 사용하면 되고,
신호에 맞춰 실행할 Observer들은 IObserver 인터페이스를 Implement받아서 사용하면 된다.
Observers 객체들은 Subject 객체를 구독(Subscribe)하게 되는 것이고,
Subject 객체는 Observers 객체들의 이벤트에게 신호를 주는 역할을 한다.
참고자료 : https://www.youtube.com/watch?v=NY_fzd8g5MU
'게임 개발 학습 > Unity' 카테고리의 다른 글
[Unity] TimeCheck for Reset (0) | 2023.09.26 |
---|---|
[Unity] Color 값 받아오고 설정할 때의 주의점 (0) | 2023.09.26 |
[Unity] GameObject.FindWithTag Target Priority (0) | 2023.09.26 |
[Unity] Interpolation (0) | 2023.09.25 |
[Unity] LookAtConstraint Class 구현 및 비교 (0) | 2023.09.25 |