-
유니티 쉽게 배우는 디자인 패턴 - 옵저버 패턴 / Unity Design Pattern - Observer Pattern유니티 2023. 10. 18. 13:21
디자인 패턴이란?
디자인 패턴이란 코드 구조를 설계하는 패턴입니다.
제일 중요한 건 디자인 패턴을 맹신하여 굳이 필요없는 부분에 강제로 쓰지 않는 것입니다.
왜 이 디자인패턴이 이 상황에 효율적인지 설명하지 못하면 실패한 설계입니다.
옵저버 패턴이란?
옵저버 패턴은 이벤트를 주체에서 발생시키고
관찰하는 대상은 주체를 구독함으로 실행 시점을 이벤트로 받아올 수 있습니다.
음악이 끝남을 알려주는 이벤트
광고를 다 봤음을 알려주는 이벤트
게임이 종료되었음을 알려주는 이벤트 등
많은 부분에서 이미 사용이 되고 있습니다.
UI의 버튼을 누르면 OnClick() 이벤트를 발생시켜 클릭할 때 변화를 알려주는 것에도 사용이 되고 있습니다.
GameManager 클래스에서 Moved라는 함수를 실행했다고 해봅시다.
Moved는 사용자의 입력이 있을때만 실행되기에 항상 실행되는게 아니라고 구상해봅시다.
이때 각 플레이어들이 GameManager의 Moved의 실행시점을 똑같이 이벤트를 받고 싶을때
GameManager안의 함수를 구독하면
GameManager에는 관찰하는 대상인 Player들은 모르지만 이벤트를 전달할 수 있게됩니다.
1. System.Action을 사용한 방법
GameManager.cs
using UnityEngine; public class GameManager : MonoBehaviour { public event System.Action<float> Moved; void Update() { float horizontal = Input.GetAxis("Horizontal"); if (horizontal != 0) { Moved?.Invoke(horizontal); } } }
GameManager.cs에는 이벤트를 발생시키는 주체로 여기에 System.Action을 만듭니다.
event 키워드는 이 클래스 밖에서는 += 또는 -=으로 추가 제거만 할 수 있고 Invoke 실행은 할 수 없게 제한합니다.
매개변수로 값도 전달할 수 있습니다.
그리고 Update에서 키보드의 좌우키를 누를 때만 이벤트를 발생시킵니다.
?.을 한 이유는 Moved에 아무것도 구독한 게 없으면 함수를 실행시키지 않아 Null Reference 에러를 막기 위해서입니다.
Player.cs
using UnityEngine; public class Player : MonoBehaviour { [SerializeField] GameManager gameManager; void OnMoved(float horizontal) { print($"움직임 {horizontal}"); } void OnEnable() { gameManager.Moved += OnMoved; } void OnDisable() { if (gameManager) gameManager.Moved -= OnMoved; } }
Player.cs에는 이벤트를 구독하는 대상으로 먼저 GameManager 객체를 참조합니다.
이 스크립트가 활성화 될 때 (OnEnable : Instantiate로 생성될 때도 포함됨) +=으로 이벤트 등록을,
이 스크립트가 비활성화 될 때 (OnDisable : Destroy로 파괴될 때도 포함됨) -=으로 이벤트 해제를 합니다.
함수 이름을 지을 때는 구독됨이라고 알수 있도록 앞에 On을 붙힙니다.
하이어라키에 두 게임오브젝트를 만들고 각각의 스크립트를 넣습니다.
그리고 플레이해서 좌우 키보드 방향키를 누를 때만 로그가 출력됨을 알 수 있습니다.
그러면 GameManager가 Player를 알고있고 Player 객체를 참조하여 Moved함수를 전달하는 것에 비해 어떤 장점이 있을까요?
GameManager는 앞으로 일어날 등록 과정에 참조를 계속 할 일이 없어지게 됩니다.
2. delegate를 사용한 방법
GameManager.cs
using UnityEngine; public class GameManager : MonoBehaviour { public delegate void delegateMoved(float horizontal); public event delegateMoved Moved; void Update() { float horizontal = Input.GetAxis("Horizontal"); if (horizontal != 0) { Moved?.Invoke(horizontal); } } }
GameManager만 delegate형태로 바꾸고 Player는 그대로입니다.
한 줄이 더 늘어났는데 어떤 장점이 있기에 이 방법도 소개할까요?
바로 구독하는 대상인 Player에서 구현할 때 += 이후 On함수를 작성하고 Ctrl + .을 눌러 메서드 생성부분을 선택하면
자동으로 매개변수의 이름인 horizontal이 들어가 더 명확해지는 장점이 있습니다.
3. UnityEngine.Events.UnityAction을 사용한 방법
GameManager.cs
using UnityEngine; public class GameManager : MonoBehaviour { public event UnityEngine.Events.UnityAction<float> Moved; void Update() { float horizontal = Input.GetAxis("Horizontal"); if (horizontal != 0) { Moved?.Invoke(horizontal); } } }
GameManager만 UnityAction으로 바꿨습니다.
UnityAction은 매개변수가 최대 4개까지 가능합니다.
마무리
이렇게 옵저버패턴을 살펴봤는데요.
1번방법에서 System을 using으로 빼도 되며, 3번 방법에서 UnityEngine.Events를 using으로 빼도 됩니다.
1, 2, 3 방법 중 취향껏 원하는 걸 사용하시면 됩니다.
참고로 저는 1번 방법을 제일 자주 사용해왔으나, 앞으로 개발시 2번 방법으로 나아갈까 고민하고 있습니다.
'유니티' 카테고리의 다른 글