반갑읍니다,,,`~~~
ui,,,구조와,,,mvp 關하여,,,,약간의,,,오해가 있으어서,,,시간이,,,조금,,,걸렸,,읍,니다,,~
각설,,,식사는,하,셨는지요...?
public interface ISceneProvider
{
public UniTask LoadSceneAsync(
SceneType sceneType,
CancellationToken token,
UnityAction<float> onProgress = null,
UnityAction onComplete = null,
Func<UniTask> waitUntilLoad = null);
public UniTask ReloadCurrentSceneAsync(
CancellationToken token,
UnityAction<float> onProgress = null,
UnityAction onComplete = null,
Func<UniTask> waitUntilLoad = null);
}
public interface IResourceManager
{
public UniTask LoadAssetsAsync(string key);
//중략
public UniTask<List<T>> CreateAssetsAsync<T>(IList<IResourceLocation> locations, Transform root = null) where T : UnityEngine.Object;
//중략
}
우선,,,기존 매니저들을,,,,인터페이스시켰읍니다,,,,
//씬매니저 호출을 하되 사용 객체는 인터페이스로 캐스팅
ISceneProvider sceneProvider = GlobalManager.instance.SceneProvider;
sceneProvider.LoadSceneAsync(SceneType.Lobby, CancellationToken.None).Forget();
IStageCreator stageCreator = StageManager;
stageCreator.ReStartAsync().Forget();
기존,,,호출하던,,,,,위치에서도,,,,,,,매니저 그대로 쓰는 것을 不하고,,인터페이스로,,,쓰,도,록,,,했지요...,
public class ResourceManager : IResourceManager, IDisposable
public class SceneProvider : MonoBehaviour, ISceneProvider
public class UIManager : MonoBehaviour, ICanvasProvider, IUIResourceService, IUIPresenterFactory, IUIPresenterContainer, IFirstUICreator
이렇게,,해야,,나,중에,,분리,유지보,수,등이,,쉬얼,,,것이라,,,생각했읍니다,,,))

UI MVP는,,, 纡餘曲折이 많았지만,,,만족할,,,구조를,,,,짰읍니다,,,,
public interface IUIVisibleStateContainer
{
public UIVisibleState GetVisibleState();
public void SetVisibleState(UIVisibleState visibleState);
}
public interface IUIPresenter: IUIVisibleStateContainer, IDisposable
{
public UniTask ShowAsync(bool isImmediately = false);
public UniTask HideAsync(bool isImmediately = false);
public IDisposable AttachOnDestroy(GameObject target);
}
Presenter는,,,IUIPresenter 고,정입니다,,,,,,
요 놈은,,,MonoBehaviour가 아 닙니,,다,,,, 그
리하여,,, GameObject에 부착,을,하는,,방식으로,,,자동,,,파괴를,,,,,,,만들었읍ㄴ,,,,ㅣ다,,,~~~~~~
public void Dispose()
{
IUIPresenterContainer presenterContainer = GlobalManager.instance.UIManager;
presenterContainer.Remove(this);
if(view)
GameObject.Destroy(view.gameObject);
}
public IDisposable AttachOnDestroy(GameObject target)
=> target
.OnDestroyAsObservable()
.Subscribe(_=>Dispose());
例示: Unirx로 OnDestroyAsObservable 부착, Dispose를 통해 presenter 생성 관리자에게 제거 신호 및 추후 서술할 MonoBehaviour 오브젝트 view도 같이 파괴
//Lobby에서 사용할 ButtonController, TMPcontroller가 달린 버튼 View
public class UILobbyBeginButtonView : MonoBehaviour, IButtonController, ITMPController
//해당 View를 저장 중인 Lobby의 ViewContainer
public class UILobbyViewContainer : MonoBehaviour
{
public UILobbyBeginButtonView beginButton;
}
//위 클래스들을 사용할 LobbyFirstPresenter
public class UILobbyFirstPresenter : IUIPresenter
{
public class Model
{
public readonly string beginButtonText;
public readonly string beginButtonSceneKey;
public Model(string beginButtonText, string beginButtonSceneKey)
{
this.beginButtonText = beginButtonText;
this.beginButtonSceneKey = beginButtonSceneKey;
}
}
private readonly Model model;
private readonly UILobbyViewContainer viewContainer;
private readonly IButtonController beginButton;
private readonly ITMPController beginButtonText;
public UILobbyFirstPresenter(Model model, UILobbyViewContainer viewContainer)
{
this.model = model;
this.viewContainer = viewContainer;
//ViewContainer에서 원하는 IView 꺼내 쓰기
this.beginButton = viewContainer.beginButton;
this.beginButtonText = viewContainer.beginButton;
beginButtonText.SetText(model.beginButtonText);
beginButton.SubscribeOnClick(OnBeginButtonClick);
}
//중략
private void OnBeginButtonClick()
{
var compositeDisposable = new CompositeDisposable();
compositeDisposable.Add(beginButton);
beginButton.SetInteractable(false);
beginButton.UnsubscribeOnClick(OnBeginButtonClick);
var sceneProvider = GlobalManager.instance.SceneProvider;
sceneProvider.LoadSceneAsync(
SceneType.Game,
CancellationToken.None,
onProgress: null,
onComplete: compositeDisposable.Dispose).Forget();
}
//이 Presenter의 패널이나 다름없는 ViewContainer와 생사고락을 같이 하도록
public IDisposable AttachOnDestroy(GameObject target)
=> target
.OnDestroyAsObservable()
.Subscribe(_ => Dispose());
public void Dispose()
{
IUIPresenterContainer presenterContainer = GlobalManager.instance.UIManager;
presenterContainer.Remove(this);
if(viewContainer)
GameObject.Destroy(viewContainer.gameObject);
}
}
이는,,,더 자세한 예시로,,,,, Lobby에서 사용하도록 IUIPresenter를 상속받아 구현된 UILobbyFirstPresenter,,,,,,,,,,,,,,,,와,,,,,,,,,,,,여기서,쓸,버튼,인,UILobbyButtonView,,,,를,,,,,저장한,,,,,UILobbyViewContainer,,,,,를,,,,,인,자로,,,,받는,,,,생성자를,,,,보여드렸,,,읍니다,,,,,,
이제,,,,,,,,,,,Presenter,,,,,,의,,Factory,,,를,,,보여드리겠읍니다,,,,,두근,두근,,~~
//Addressable을 통해 View를 가져오는 인터페이스
public interface IUIResourceService
{
public UniTask<GameObject> CreateViewAsync(string viewKey, UIRootType createRoot);
public void ReleaseView(GameObject view, bool releaseHandle = false);
}
//IUIPresenter을 상속받는 타입의 생성자를 기억하고 그대로 생성해주는 팩토리
public interface IUIPresenterFactory
{
public void Register<T>(Func<T> constructor) where T : IUIPresenter;
public T Create<T>() where T : IUIPresenter;
}
//캐싱해둔 IUIPresenter들을 접근시켜주는 인터페이스
public interface IUIPresenterContainer
{
public void Add(IUIPresenter presenter);
public void Remove(IUIPresenter presenter);
public IReadOnlyList<T> GetAll<T>() where T : IUIPresenter;
public T GetFirst<T>() where T : IUIPresenter;
public T GetLast<T>() where T : IUIPresenter;
}
IUIResourceService, IUIPresenterContainer는 대충,,,예상이,,,,,가실,듯,합니다,,,,,,,
IUIPresenterFactory의,,,사용,,,례를,,,,보여준,ㄴ,,특별,,,쑈쑈쑈~!~~!~!
private readonly Dictionary<Type, Func<IUIPresenter>> presenterRegisters = new();
public void Register<T>(Func<T> constructor) where T : IUIPresenter
{
var type = typeof(T);
presenterRegisters[type] = ()=> constructor();
}
public T Create<T>() where T : IUIPresenter
{
var type = typeof(T);
if (presenterRegisters.ContainsKey(type) == fal)
throw new System.NotImplementedException($"{type.Name} does not exist");
var presenter = (T)presenterRegisters[type]();
Add(presenter);
return presenter;
}
//Register, Create 사용 예시
{
//model 선언
var model = new UIPreloadingPresenter.Model("Preloading...?");
//view 가져오기
var view = firstUiVeiw.GetComponent<UIPreloadingView>();
//이 model과 view로 원하는 presenter의 생성자를 등록
presenterFactory.Register(() => new UIPreloadingPresenter(model, view));
//나와라 뿅
var presenter = presenterFactory.Create<UIPreloadingPresenter>();
presenter.AttachOnDestroy(gameObject);
}
여러분,,,만큼,,,관찰력,이,,,,『뛰어난』분 들이,,,있으싣면,,,,『눈치』챘겠죠...,,,
이것은,,,GPT가,,,아~~~~~~~주,,약,간의,,,도움을,,,,,,,주었읍,니다,
와타시는~~~~~~~~인공지능에게~~~~~~패배한데샤~~~~~~~~~~~
암튼 UIPresenter 생성할 때마다 해당 객체에서 저장해두고 필요하면 꺼내 쓸 수 있도록 어디서나 마음대로 생성하지 않고 웬만하면 Factory를 통해 생성하게 할 예정입니다.

UIManager에서 View(라는 이름의 GameObject)와 IUIPresenter는 이렇게 보이겠네요.
public class LocalManager : MonoBehaviour
{
//중략
private async void Awake()
{
instance = this;
InitializeManagers();
switch (sceneType)
{
case SceneType.Initialize:
{
GlobalManager.instance.SceneProvider.LoadSceneAsync(SceneType.Preloading, CancellationToken.None).Forget();
}
break;
case SceneType.Preloading:
{
await CreateFirstUIAsync();
await LoadPreloadAsync();
ISceneProvider sceneProvider = GlobalManager.instance.SceneProvider;
sceneProvider.LoadSceneAsync(SceneType.Lobby, CancellationToken.None).Forget();
}
break;
case SceneType.Lobby:
{
await CreateFirstUIAsync();
}
break;
case SceneType.Game:
{
await CreateFirstUIAsync();
}
break;
}
}
}
씬 진입 시 LocalManager에서 최초 UI를 생성합니다.
InitializeScene의 경우 GameManager 세팅용이니 생략 후 다음 씬(Preloading)으로,
Preloading에서 로딩 UI 생성 후 Addressable Preload 마치고 Lobby 씬으로
Lobby에서는 UILobbyPresenter를 생성해주기만 할 것입니다.
구조는 짜고 실제 생성도 되니 스테이지쪽을 만들어봐야 할 것 같습니다.
다음 목표:
트리거 타일 데이터 테이블화
플레이어 세세한 데이터 테이블화
UI 테이블화
기초적인 게임 UI 만들기
스테이지 플로우 짜기
'무제_LR' 카테고리의 다른 글
| ScriptableObject로 전역 이벤트 써먹기 (0) | 2025.11.12 |
|---|---|
| 간단한 Localization 및 언어별 Font 설정 (0) | 2025.11.11 |
| 스테이지 생성(트리거 타일) (0) | 2025.11.06 |
| 스테이지 생성(타일맵) (0) | 2025.11.06 |
| 스테이지 데이터 구조 (0) | 2025.11.03 |