<참고사이트>
https://welcomeheesuk.tistory.com/67
1. 옵저버 패턴?
- 옵저버 패턴에 대해 정리한 여러 사이트를 본 후 제가 이해한 옵저버 패턴은 객체끼리 연락(정보)을 주고 받으며 상호작용을 하는 디자인 패턴이라고 생각합니다.
- 옵저버 패턴에서는 주제(Subject)와 옵저버(Observer)가 느슨하게 결합되어 있는 구조이며, 한 객체의 정보가 바뀌면 연결된 다른 객체들에게 연결이 가서 정보가 갱신되는 일대다 의존성을 가진다고 합니다.
2. 왜 옵저버 패턴을 사용하는가?
- 메모리를 효율적으로 관리하기 위해서이다. 이는 거의 모든 디자인 패턴의 공통적인 이유인 것 같습니다.
- 옵저버 패턴의 핵심인 '느슨한 결합'으로 인해 상호의존성을 최소화할 수 있다.
이 부분에서 저는 처음에 의문을 품었습니다. 객체끼리 연락하며 정보를 갱신한다면서 왜 느슨한 결합이라는거지?
개인적으로 이해해봤습니다만 강한 결합(?)보다는 비교적 느슨한 편이라고 생각합니다. 예를들어 부모 클래스를 상속받는 자식 클래스의 경우에는 강한 결합이라고 생각합니다. 부모 클래스에서 체력, 공격력, 방어력 등을 전부 관리하고 그걸 그대로 받아쓰는 자식 클래스는 따로 독립할 수 없는 입장입니다. 그러나 옵저버 패턴은 언제든지 손절이 가능하다고 생각합니다. 약간 비즈니스 사이? 같은거죠.
위 밑줄친 부분에 대한 자세한 내용은 맨위 참고 사이트에서 특징 부분을 보면 될 듯 합니다.
3. 어떻게 옵저버 패턴을 사용하는가?
- 항상 이런 개념들을 공부할 때 궁금한 것은 "그래서 어떻게 구현하는 건데?!!!" 라고 생각합니다.
- 많은 사이트를 바탕으로 봤을 때 기본적으로 주제(Subject)와 옵저버(Observer)를 인터페이스로 생성한 후 시작됩니다. 저는 일단 어떻게 진행되는지 알아보기 위해 맨위 사이트와 비슷하게 만들어봤습니다.
1. ISubject(Interface Subject)와 IObserver(Interface Observer)를 만들어줍니다.
namespace Interface
{
public interface ISubject
{
// Observer 등록.
void RegistObserver(IObserver observer);
// Observer 해제
void RemoveObserver(IObserver observer);
// 모든 Observer 업데이트
void NotifyObserver();
}
public interface IObserver
{
// 정보 갱신.
void ObserverUpdate(float myHp, float enemyHp);
}
}
// 인터페이스는 따로 스크립트를 만들어준 후 작성했습니다.
// 혹시 인터페이스 사용법을 모르시는 분은 아래 유니티 사이트에서 확인해보시는 걸 추천드립니다.
https://learn.unity.com/tutorial/interfaces#
2. ISubject를 상속받는 HpSubject라는 클래스를 만들어줍니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// 만들어놓은 인터페이스를 가져옴.
using Interface;
public class HpSubject : MonoBehaviour,ISubject
{
// 옵저버 패턴의 핵심..리스트가 꼭 있음.
List<IObserver> observers = new List<IObserver>();
float myHp = 0f;
float enemyHp = 0f;
public void RegistObserver(IObserver observer)
{
// Observer 등록.
this.observers.Add(observer);
}
public void RemoveObserver(IObserver observer)
{
// Observer 제거.
this.observers.Remove(observer);
}
public void NotifyObserver()
{
// Observer 업데이트.
foreach (var item in this.observers)
{
item.ObserverUpdate(this.myHp, this.enemyHp);
}
}
public void Change(float _myHp, float _emenyHp)
{
this.myHp = _myHp;
this.enemyHp = _emenyHp;
// 업데이트 정보 갱신.
this.NotifyObserver();
}
}
3. IObserver를 상속받는 MyHp와 EnemyHp옵저버 클래스를 각각 만들어줍니다.
[ MyHpObserver.cs ] - myHp에 대한 수치를 체력바 이미지에 나타냄.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interface;
using UnityEngine.UI;
public class MyHpObserver : MonoBehaviour, IObserver
{
[SerializeField]
Image hpBar = null;
HpSubject subject = null;
public void Init(HpSubject _subject)
{
// Subject 초기화.
this.subject = _subject;
}
public void ObserverUpdate(float myHp, float enemyHp)
{
this.hpBar.fillAmount = myHp;
}
}
[ EnemyHpObserver.cs ] - enemyHp에 대한 수치를 체력바 이미지에 나타냄.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Interface;
using UnityEngine.UI;
public class EnemyHpObserver : MonoBehaviour,IObserver
{
[SerializeField]
Image hpBar = null;
HpSubject subject = null;
public void Init(HpSubject _subject)
{
// Subject 초기화.
this.subject = _subject;
}
public void ObserverUpdate(float myHp, float enemyHp)
{
this.hpBar.fillAmount = enemyHp;
}
}
4. 버튼을 누르면 랜덤으로 공격하게 되는 AttackManager스크립트를 만들어줬습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AttackManager : MonoBehaviour
{
// 오브젝트에 아래의 스크립트(HpSubject, MyHpObserver, EnemyHpObserver)를 연결시켜주고,
// 이 스크립트는 버튼오브젝트에 연결해주었다.
[SerializeField]
HpSubject hpSubject = null;
[SerializeField]
MyHpObserver myHp_Observer = null;
[SerializeField]
EnemyHpObserver enemyHp_Observer = null;
[SerializeField]
Text txtMyHp;
[SerializeField]
Text txtEnemyHp;
float maxMyHp;
float maxEnemyHp;
float currMyHp;
float currEnemyHp;
private void Start()
{
// 게임이 시작되면 체력바 초기화.
this.myHp_Observer.Init(this.hpSubject);
this.enemyHp_Observer.Init(this.hpSubject);
this.maxMyHp = 100f;
this.maxEnemyHp = 100f;
this.currMyHp = this.maxMyHp;
this.currEnemyHp = this.maxEnemyHp;
// 각 체력바 등록.
this.hpSubject.RegistObserver(this.myHp_Observer);
this.hpSubject.RegistObserver(this.enemyHp_Observer);
// 최대체력 대비 현재 체력 비율 정보 업데이트.
this.hpSubject.Change(this.currMyHp / this.maxMyHp, this.currEnemyHp / this.maxEnemyHp);
}
// 버튼을 누를 때마다 호출되는 함수.
public void Attack()
{
if (this.currMyHp > 0 && this.currEnemyHp > 0)
{
// 랜덤으로 서로 공격.
int numAttack = Random.Range(0, 2);
switch (numAttack)
{
case 0:
this.currEnemyHp -= 10;
Debug.Log("내 공격 성공");
break;
case 1:
this.currMyHp -= 10;
Debug.Log("적 공격 성공");
break;
default:
Debug.Log("에러발생");
break;
}
// 최대체력 대비 현재체력 비율 정보 업데이트.
this.hpSubject.Change(this.currMyHp / this.maxMyHp, this.currEnemyHp / this.maxEnemyHp);
this.txtMyHp.text = this.currMyHp.ToString();
this.txtEnemyHp.text = this.currEnemyHp.ToString();
}
// 둘 중 하나라도 피가 0이되면 전투 종료.
if (this.currMyHp <= 0)
{
Debug.Log("적의 승리ㅜㅜ");
Debug.Log("----전투 종료----");
return;
}
else if (this.currEnemyHp <= 0)
{
Debug.Log("나의 승리!!!");
Debug.Log("----전투 종료----");
return;
}
}
}
5. 해 봐
- 아직 옵저버 패턴을 활용해서 게임을 만드는 것에는 자신이 없다. 하지만 직접 이렇게 구현을 해보니 상당히 유용하게 쓰일 것 같다는 생각이 든다.
- 실제 다양한 게임이나 모듈에서 자주 사용된다고 한다. 좀 더 공부해서 마스터? 해보겠다.
'Unity > Unity 디자인 패턴' 카테고리의 다른 글
[Unity 디자인 패턴] 팩토리 매서드 패턴(Factory Method Pattern) (0) | 2024.04.25 |
---|---|
[Unity 디자인 패턴] 심플 팩토리 패턴(Simple Factory Pattern) (0) | 2024.04.25 |
[Unity 디자인 패턴] 컴포넌트(Component) 패턴 (0) | 2024.04.17 |
[Unity 디자인 패턴] 싱글톤(SingleTon)에 대해 알아보자! (0) | 2024.04.16 |