개요
4주차 학습을 진행하였다
학습 내용은 다음과 같다
- Lobby 씬 구조 및 디자인 패턴 (LobbyManager, LobbyUIController)
- 게임 설정 (UserSettingsData, SettingsUI)
- 뒤로가기(ESC/BACK) 처리 (LobbyUIController, ConfirmUI)
- 유저 재화 관리 및 커스텀 메뉴 (UserGoodsData, GoodsUI, CustomTools)
내용
5. Lobby 시스템 제작
Lobby 씬 구조 및 디자인 패턴 (LobbyManager, LobbyUIController)
강사의 핵심 아이디어는 다음과 같다
LobbyManager
- 비지니스 로직 제어 및 데이터 주입
LobbyUIController
- 사용자 인터렉션 관리 및 주입된 데이터를 유저에게 보여주는 로직 처리
예시 동작 플로우는 다음과 같다
- LobbyManager가 LobbyUIController 초기화
- LobbyUIController가 실제 Lobby에서 사용될 UI들을 초기화, 그리고 UI의 동작을 구현하여 UnityEvent로 연결
- 이후 유저의 인풋이 들어오면 LobbyUIController가 들어온 동작에 따라 UI 팝업
위 순서로 동작하게 된다
이렇게 구현한 의도는 다음과 같다고 한다
- 로비의 주요 로직은 LobbyManager가 처리
- UI와 사용자의 인터랙션은 LobbyUIController에 위임하여 데이터 처리와 시각적 요소에 대한 처리가 분리되게 만드는 것
- 핵심은 LobbyUIController와 UIManager를 분리하여 UI관리의 유연성과 안정성을 확보하는 것
강사가 로비 씬에서의 구조를 나눈 이유는 전역적인 UI와 특정 씬(UIManager vs. LobbyUIController)의 UI를 분리함으로써 유지보수와 기능 확장이 용이하게끔 하기 위함이다
이를 통해 게임의 다른 UI들과 로비에서만 쓰이는 UI의 혼동을 줄이고, 각 역할을 명확히 분리하려는 의도로 보인다
그런데 '디자인 패턴'이라는 소제목이 조금 모호하게 느껴진다
MVC와 MVP의 그 어딘가로 보이기는 하는데.. 내가 설계한 구조는 이렇고 이런 장점이 있다~ 느낌인데
명확히 어떤 문제를 해결하고자 어떤 패턴을 사용했는지에 대한 설명이 부족해 보인다. 프로젝트에 어떻게 적용했는지, 그리고 각 역할이 어떻게 구체적으로 나뉘는지를 명확히 구분해 설명했으면 더 효과적이었을 것 같다
또한, 'UIManager'와 'LobbyUIController'의 네이밍이 직관적으로 혼동을 줄 수 있다
비슷한 역할을 하는데 왜 한쪽은 매니저이고, 한쪽은 UIController인가?
각 UI의 참조를 관리하는 추상화된 UIManager와 실제 동작을 담당하는 UIController로 더 명확히 분리하여, 공통 UI(Common), 게임 내 UI(InGame), 로비 UI(OutGame)로 나눠 구조를 통일시켜주는 것이 혼란을 줄이고 유지보수를 더 원활하게 할 수 있는 방향일 것 같다
게임 설정(UserSettingsData, SettingsUI)
게임 설정을 관장하는 UserSettingsData와 SettingsUI를 구현하는 내용
구현 자체는 어느 게임에서나 볼 수 있는 평범한 Settings였고, 구현과는 별개로 몇가지 생각해볼 점이 있었다
1. 인디 게임 만들 때 보안 정책이라던지 준수해야 할 문서가 있다면 반드시 링크 추가하기
2. 강사의 코드를 보다보니 early return이 습관화 되지 않은 건지, 강의를 위해 그냥 프리하게 쓰는 건지 잘 모르겠는 데 개선 여지가 보인다
네트워크에서 받아오는 데이터가 아니라면 무조건 데이터가 들어와야 하는데 if문으로 null검사를 굳이 한번 더 해서 연산을 낭비할 필요가 있을까?
assert문을 걸어놓고 삭제해서 최적화를 꾀하는 게 더 좋은 방향성이라고 생각한다
3. 반복되는 코드 모듈화 했으면 좋겠다
다만 강의 목표 자체가 설계를 어떻게 했는지 보여주는 것이며 코드는 쉽게 이해시키기 위해 풀어 쓰는 것이라면 이해가 된다
4. 각 매니저 클래스가 비슷한 메서드를 구현하고 별개로 호출되는 형태로 구현이 되고 있는데, 차라리 이벤트 하나를 만들고 그 이벤트를 구독하는 형태로 바꾸는게 구현도 편하고 협업도 편해지지 않을까?
예를들어
// TitleManager.cs
private void Start()
{
//유저 데이터 로드
UserDataManager.Instance.LoadUserData();
//저장된 유저 데이터가 없으면 기본값으로 세팅 후 저장
if(!UserDataManager.Instance.ExistsSavedData)
{
UserDataManager.Instance.SetDefaultUserData();
UserDataManager.Instance.SaveUserData();
}
AudioManager.Instance.OnLoadUserData();
StartCoroutine(LoadGameCo());
}
이런 코드가 있었는데, UserDataManager와 AudioManager 둘 다 LoadUserData, OnLoadUserData를 구현하고 있다
강의 상에서 구현된 코드를 보면 무조건 Audiomanager.OnLoadUserData는 UserDataManager.LoadUserData 이후 실행되어야 한다
여기서 나는 두 가지 문제가 발생할 수 있다고 생각한다
- 협업자의 입장에서 코드를 보고 헷갈릴 여지가 있다
- 다른 클래스에서 LoadUserData, OnLoadUserData등의 메서드를 구현할 때마다 TitleManager에 코드가 추가되어야 한다
그래서 내가 제안하는 해결책은 다음과 같다
// EventManager.cs
private event Action _beforeLoadUserDataEvent;
public void InvokeBeforeLoadUserData()
{
_beforeLoadUserDataEvent?.Invoke();
}
public void SubscribeBeforeLoadUserData(Action action)
{
_beforeLoadUserDataEvent += action;
}
private event Action _afterLoadUserDataEvent;
public void InvokeAfterLoadUserData()
{
_afterLoadUserDataEvent?.Invoke();
}
public void SubscribeAfterLoadUserData(Action action)
{
_afterLoadUserDataEvent += action;
}
// ==================================================================
// TitleManager.cs
private void Start()
{
var eventManager = EventManager.Instance;
eventManager.InvokeBeforeLoadUserData();
// ...
eventManager.InvokeAfterLoadUserData();
StartCoroutine(LoadGameCo());
}
// ==================================================================
// UserDataManager.cs
protected override Init()
{
base.Init();
EvnetManager.Instance.SubscribeBeforeLoadUserData(LoadUserData);
}
// AudioManager.cs
protected override Init()
{
base.Init();
EvnetManager.Instance.SubscribeAfterLoadUserData(OnLoadUserData);
}
이런식으로 어느 타이밍에 호출이 필요한지 클래스 스스로가 결정하게 만들고, TitleManager를 복잡하지 않게 만드는 방법이
다른 사람이 코드를 봤을 때 알아보기 쉽고 새로운 UI도 초기화하기 쉬우며 문제가 생긴 부분을 찾기에도 편할 것이라 생각한다
뒤로가기 처리
뒤로가기 UI 로직을 구현하는 내용이었다
별 다른 점은 없고, 보면서 느꼈던 것이지만 Update에서 Input을 풀링하는 방식으로 구현하는 것은 연산에 비효율적이라 생각한다
이벤트 형식이 구현도 쉽고 최적화도 이점이 있기에 가능하다면 이벤트 쪽으로 가는 것이 맞는 것 같다
내가 속한 프로젝트에는 모두 Input System을 사용하도록 건의하거나 직접 구현하고 있다
또한, LobbyUIController.HandleInput 메서드의 if문 내부에서 데이터를 셋업하고 있는데 이 부분도 데이터를 관리하는 부분을 한 곳으로 모으고 거기에서 한번에 데이터를 관리하는 방향으로 가는 게 좋지 않을까 생각한다
개인적으로 데이터가 여러군데 퍼져있을 때 관리하기 어려웠던 경험이 있기 때문이다
차선책으로 현재 구조에선 LobbyUIController에 GetQuitUI를 구현하고, 이 메서드를 통해 가져오게 만드는 게 좋아보인다
유저 재화 관리 및 커스텀 메뉴
유저 재화인 Gem/Gold를 관리하는 GoodsUI를 구현하는 내용이다
GoodsUI 클래스를 구현하면서 강사는 BaseUI를 상속받지 않고 Monobehaviour로만 구현하였는데, 이러한 설계 포맷을 벗어난 예외를 만드는 것은 별로라고 생각한다. 개인적으로는 BaseUI와 BaseUIData를 이용하여 만드는 게 합리적이라고 생각한다
또한 앞서 3주차에서 생각했던 우려가 그대로 강의에서 나왔다
// 이전 코드
// 새로 팝업된 UI를 그냥 맨 마지막 인덱스로 할당해주면 끝났다
var siblingIndex = UICancasTr.childCount;
// 이번 파트에서 새로 바뀐 코드
// 항상 GoodsUI가 맨 위로 올라가야 하기 때문에 새로 팝업되는 UI의 인덱스를 조정해주는 모습이다
var siblingIndex = UICancasTr.childCount - 1;
GoodsUI의 경우 무조건 가장 위에 그려져야 해서 SiblingIndex를 관리하는 로직이 복잡해졌는데 이 경우 주석을 추가로 써야 하며, 코드를 이해하는 데 시간이 더 걸릴 것이다
따라서 SortingLayer 조정을 통하여 제일 위에 그려지게 만드는 방법이 좋아보인다
그 외로, UIManager.EnableGoodsUI 메서드도 리팩토링 할 수 있을 것 같다
// UIManager.cs
public void EnableGoodsUI(bool value)
{
_goodsUI.gameObject.SetActive(value);
if (value)
{
_goodsUI.SetValues();
}
}
현재 UIManager가 EnableGoodsUI의 구현까지 담당하고 있는데, 이러면 UIManager가 하는 일이 많아지고, 클래스 코드가 길어질 것 같다
코드가 무조건 길어지는게 나쁜건 아니지만, 이 로직을 굳이 UIManager가 가지고 있어야 할까?
// UIManager.cs
public void EnableGoodsUI(bool value) => _goodsUI.SetActiveUI(value);
// GoodsUI.cs
public void SetActiveUI(bool value)
{
gameObject.SetActive(value);
if (!value) return;
_goodsUI.SetValues();
}
이런식으로 스스로 상태를 관리하게 만들고, UIManager의 코드를 간략화 함으로써 가독성을 향상시킬 수 있다고 본다
마무리
배울점도 많고, 내 코드와 설계를 어떻게 하면 더 발전시킬 수 있을까 생각해볼 수 있었다
이전에 짰던 코드를 내가 왜 이렇게 짜기 시작했을까, 어떤 장단점이 있었지? 하며 되돌아 볼 수 있었으며
다른사람들을 설득해야 하는 상황이 생긴다면 예시 코드를 토대로 의견을 주장할 수 있을 것 같다
이번 스터디를 진행하면서 설계쪽에 대해 좀 더 깊은 생각을 해보고, 기준을 점검할 수 있어서 굉장히 좋은 것 같다
현재 내가 설계 및 코드를 짜는 기준은 다음과 같다
- 다른사람이 읽고 한번에 이해가 가는가?
- 기능을 추가하기 편한가?
- 유지보수가 편한가?
- 변경했을 때 다른곳에서 사이드 이펙트를 일으키지는 않는가?
- 최선의 최적화인가?
파고들면 더 있겠지만 대전제는 위와 같다
앞으로도 피드백 할 때 위의 기준으로 점검하고 다른사람의 작업물을 보며 배울점을 습득하고 나의 작업물을 피드백하며 협업하기 편하고 필요한 업무를 빠르게 해낼 수 있는 소프트웨어 엔지니어가 되고 싶다
'Unity' 카테고리의 다른 글
유니티 시스템 프로그래밍 Pt.1 - 7~8 주차 (3) | 2024.11.08 |
---|---|
Unity UniTask와 Coroutine (4) | 2024.10.04 |
유니티 시스템 프로그래밍 Pt.1 - 3주차 (2) | 2024.09.16 |
유니티 시스템 프로그래밍 Pt.1 - 2주차 (0) | 2024.09.16 |
유니티 시스템 프로그래밍 Pt.1 - 1주차 (4) | 2024.09.11 |