Humility

아무리 노력해도 최고가 되지 못할 수 있다⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀그럼에도 노력하는자가 가장 겸손한 것 아닌가

공부하는 블로그

유니티/디자인패턴

[Unity] 유니티 C#) Observer 패턴

새벽_글쓴이 2024. 12. 5. 10:38
반응형

옵저버 패턴

객체들 간의 일대다 의존 관계를 정의하여,

한 객체의 상태가 변경되면 그것을 구독하는 모든 객체들이 자동으로 알림을 받고 업데이트되는 패턴

 

기본 구현 방법

1. 이벤트를 정의할 인터페이스

public interface ISubject
{
    void AddObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

public interface IObserver
{
    void OnNotify(string eventType, object data);
}

 

2. 게임 이벤트를 관리하는 Subject 클래스

public class PlayerSubject : MonoBehaviour, ISubject
{
    private List<IObserver> observers = new List<IObserver>();
    private int health = 100;
    private int score = 0;

    public void AddObserver(IObserver observer)
    {
        observers.Add(observer);
    }

    public void RemoveObserver(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void NotifyObservers()
    {
        foreach (var observer in observers)
        {
            observer.OnNotify("PlayerUpdate", new PlayerData { health = health, score = score });
        }
    }

    // 플레이어 상태 변경 메서드
    public void TakeDamage(int damage)
    {
        health -= damage;
        NotifyObservers();
    }

    public void AddScore(int points)
    {
        score += points;
        NotifyObservers();
    }
}

 

3. 데이터 구조체

public struct PlayerData
{
    public int health;
    public int score;
}

 

4. 옵저버 구현 클래스들

public class UIObserver : MonoBehaviour, IObserver
{
    public Text healthText;
    public Text scoreText;

    public void OnNotify(string eventType, object data)
    {
        if (eventType == "PlayerUpdate")
        {
            PlayerData playerData = (PlayerData)data;
            UpdateUI(playerData);
        }
    }

    private void UpdateUI(PlayerData data)
    {
        healthText.text = $"Health: {data.health}";
        scoreText.text = $"Score: {data.score}";
    }
}

public class AchievementObserver : MonoBehaviour, IObserver
{
    public void OnNotify(string eventType, object data)
    {
        if (eventType == "PlayerUpdate")
        {
            PlayerData playerData = (PlayerData)data;
            CheckAchievements(playerData);
        }
    }

    private void CheckAchievements(PlayerData data)
    {
        if (data.score >= 1000)
        {
            UnlockAchievement("Score Master");
        }
        if (data.health <= 10)
        {
            UnlockAchievement("Close Call");
        }
    }

    private void UnlockAchievement(string achievementName)
    {
        Debug.Log($"Achievement Unlocked: {achievementName}");
    }
}

public class SoundObserver : MonoBehaviour, IObserver
{
    private int lastHealth = 100;

    public void OnNotify(string eventType, object data)
    {
        if (eventType == "PlayerUpdate")
        {
            PlayerData playerData = (PlayerData)data;
            CheckHealthForSound(playerData.health);
        }
    }

    private void CheckHealthForSound(int newHealth)
    {
        if (newHealth < lastHealth)
        {
            PlayDamageSound();
        }
        lastHealth = newHealth;
    }

    private void PlayDamageSound()
    {
        // 사운드 재생 로직
        Debug.Log("Playing damage sound");
    }
}

 

사용예시

// 게임 매니저나 초기화 스크립트에서
void Start()
{
    PlayerSubject player = FindObjectOfType<PlayerSubject>();
    
    // 옵저버들을 등록
    player.AddObserver(FindObjectOfType<UIObserver>());
    player.AddObserver(FindObjectOfType<AchievementObserver>());
    player.AddObserver(FindObjectOfType<SoundObserver>());
}

// 플레이어 상태 변경 시
player.TakeDamage(10);
player.AddScore(100);

 

핵심 개념

  • Subject(관찰 대상)와 Observer(관찰자) 간의 일대다 관계 구현
  • Subject의 상태 변화를 Observer들에게 자동으로 알림
  • Observer들은 Subject에 의존하지 않는 느슨한 결합 유지
  • 등록/해제를 통한 동적인 Observer 관리

 

사용 목적

  • 객체 간 결합도 감소
  • 시스템 확장성 향상
  • 코드 재사용성 증가
  • 상태 변화에 따른 자동 업데이트 구현

 

주요 사용 사례

  • 플레이어 상태 변화 감지 (체력, 점수 등)
  • UI 업데이트
  • 업적 시스템
  • 사운드 시스템
  • 게임 진행 상태 관리

 

사용 사례 예시

기본 인터페이스

public interface IObserver
{
    void OnNotify(string eventType, object data);
}

 

플레이어 상태 감시

public class PlayerHealth : MonoBehaviour
{
    private List<IObserver> observers = new List<IObserver>();
    private int health = 100;

    public void TakeDamage(int damage)
    {
        health -= damage;
        NotifyObservers("HealthChanged", health);
    }

    private void NotifyObservers(string eventType, object data)
    {
        foreach (var observer in observers)
        {
            observer.OnNotify(eventType, data);
        }
    }
}

 

UI 업데이트 옵저버

public class HealthUI : MonoBehaviour, IObserver
{
    private Text healthText;

    public void OnNotify(string eventType, object data)
    {
        if (eventType == "HealthChanged")
        {
            int health = (int)data;
            healthText.text = $"Health: {health}";
        }
    }
}

 

업적 시스템 옵저버

public class AchievementSystem : MonoBehaviour, IObserver
{
    public void OnNotify(string eventType, object data)
    {
        if (eventType == "HealthChanged")
        {
            int health = (int)data;
            if (health <= 10)
            {
                UnlockAchievement("Survivor");
            }
        }
    }
}

 

결론

 

1. 장점

  • 시스템 간 결합도 감소로 유지보수 용이
  • 새로운 Observer 추가가 쉬움
  • 코드의 재사용성 향상
  • 변경사항 자동 전파 가능

2. 주의사항

  • Observer 등록/해제 관리 필요
  • 너무 많은 Observer는 성능에 영향
  • 순환 참조 방지 필요
  • 적절한 사용 범위 설정 중요

 

옵저버 패턴은 복잡한 시스템에서 객체 간의 상호작용을 체계적으로
관리하고 싶을 때 매우 유용한 디자인 패턴인 것 같다

 

반응형