본문 바로가기

무제_LR

UI,,,,,구조,,,,및,,,,,매니저,,,인터페이스화,,,,,,,,

반갑읍니다,,,`~~~

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 만들기

 

스테이지 플로우 짜기