- 다른 분야에서는 논란이 있는 패턴이지만, 유니티에서는 정말 많이 쓰이는 패턴
- 개별 매니저 클래스에서 핵심 시스템을 래핑하고 관리함
- 게임 내부를 캡슐화. 직관적인 인터페이스
- 핵심 구성 요소 간의 결합을 강력하게 하여 유닛 테스트를 어렵게 만듬
싱글턴 패턴은 유일성을 보장하는 것
런타임 동안 메모리에 오로지 하나의 인스턴스만 존재한다.
전역적으로 접근할 수 있는 시스템이다.
싱글턴 인스턴스는 메모리 안에 한번 생성, 같은 유형의 인스턴스가 있으면 삭제한다.
전역적 접근 가능
공유 자원에 동시 접근을 제한하거나 사용할 수 있다.
유닛 테스트가 어렵다.
잘못된 프로그래밍 습관을 유발한다.
public class Singleton <T>: MonoBehaviour where T: Component
{
private static T_instance;
public static T instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType<T>();
if (_instance == null)
{
GameObject obj = new GameObject();
obj.name = typeof(T).Name;
_instance = obj.AddComponent<T>();
}
}
return instance;
}
}
public void Awake()
{
if (_instance == null)
{
_instance = this as T;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
public class GameManager : Singleton<GameManager>
{
void Start()
{
}
}
빈 유니티 씬을 만든다.
GameObject를 추가하고 GameManager클래스를 추가한다.
원하는 만큼의 유니티 씬을 File ➔ Build Settings 목록에 추가
- 캐릭터는 상태가 끊임없이 전환된다.
- 상태를 관리하는게 상태 패턴
객체가 내부 상태를 기반으로 움직이는 시스템
Context 클래스는 객체의 내부 상태를 변경하도록 요청하는 클래스
IState 인터페이스는 구체적인 상태 클래스로 연결할 수 있도록 설정한다.
Context 오브젝트가 IState 인터페이스를 구현한 클래스들을 호출한다.
캡슐화가 잘 되어있다.
긴 조건문이나 방대한 클래스를 구현하는 것을 막는다.
public class CharaccterStateContext
{
public ICharacterState CurrentState
{
get; set;
}
private readonly CharacterController _characterController;
public CharacterStateContext(CharacterController characterController)
{
_characterController = characterController;
}
public void Transition()
{
CurrentState.Handle(_characterController);
}
public void Transition(ICharacterState state)
{
CurrentState = state;
CurrentState.Handle(_characterController);
}
}
public class CharacterController : MonoBehaviour {
private ICharacterState
_startState, _stopState, _turnState;
private CharacterStateContext _characterStateContext;
private void Start() {
_chracterStateContext =
new CharacterStateContext(this);
_startState =
gameObject.AddComponent<CharacterStartState>();
_stopState =
gameObject.AddComponent<CharacterStopState>();
_turnState =
gameObject.AddComponent<CharacterTurnState>();
_characterStateContext.Transition(_stopState);
}
public void StartCharacter() {
_characterStateContext.Transition(_startState);
}
public void StopCharacter() {
_chracterStateContext.Transition(_stopState);
}
}
public class CharacterStartState : MonoBehaviour, ICharacterState
{
private CharacterController _characterController;
public void Handle(CharacterController characterController)
{
if (!_characterController)
_characterController = characterController;
_characterController.CurrentSpeed = 10.0f;
}
}
public interface ICharacterState
{
void Handle(CharacterController controller);
}
- 이벤트 버스는 전역 이벤트를 관리하는 중앙 허브 개념
- 게임에서 월드 이벤트 발생시 캐릭터들에게 이벤트를 발송하는 식
- 구현하기 의외로 매우 간단해서 많이 쓰인다.
어떤 객체에서 이벤트가 발생하면 다른 구독자가 신호를 받는 시스템
발행 / 구독 패턴
발행자랑 구독자는 서로 인식하지 못한다. 중간에 이벤트 버스가 이를 관리하기 때문이다.
오브젝트를 직접 참조하지 않고, 이벤트를 통신할 수 있다.
이벤트 버스는 구독 시스템을 쉽게 구현하게 만든다.
게임 프로토타입 만들때 많이 쓰인다. 쉽고 빠르기 때문.
약간의 성능 비용
이벤트 버스가 static 전역 변수라서, 전역변수의 단점을 모두 가지게 된다.
public class WorldEventBus
{
private static readonly
IDictionary<WorldEventType, UnityEvent>
Events = new Dictionary<WorldEventType, UnityEvent>();
public static void Subscribe
(WorldEventType eventType, UnityAction listener) {
UnityEvent thisEvent;
if (Events.TryGetValue(eventType, out thisEvent)) {
thisEvent.AddListener(listener);
}
else {
thisEvent = new UnityEvent();
thisEvent.AddListener(listener);
Events.Add(eventType, thisEvent);
}
}
public static void Unsubscribe
(WorldEventType type, UnityAction listener) {
UnityEvent thisEvent;
if (Events.TryGetValue(type, out thisEvent)) {
thisEvent.RemoveListener(listener);
}
}
public static void Publish(WorldEventType type) {
UnityEvent thisEvent;
if (Events.TryGetValue(type, out thisEvent)) {
thisEvent.Invoke();
}
}
}
public enum WorldEventType
{
COUNTDOWN, START, PAUSE, STOP, FINISH, RESTART, QUIT
}