본문 바로가기

무제_LR

스테이지 생성(트리거 타일)

public interface ITriggerEventSubscriber
{
  public void SubscribeOnEnter(UnityAction<Collider2D> onEnter);

  public void UnsubscribeOnEnter(UnityAction<Collider2D> onEnter);

  public void SubscribeOnExit(UnityAction<Collider2D> onExit);

  public void UnsubscribeOnExit(UnityAction<Collider2D> onExit);
}

public interface ITriggerTileEnabler
{
  public void Enable(bool enabled);
}

트리거 인터페이스를 만들던 도중 presenter랑 view에서 공통으로 사용할 OnEnter, OnExit 구독 및 활성화/비활성화 인터페이스를 따로 분리했습니다.

 

public interface ITriggerTilePresenter: ITriggerEventSubscriber, ITriggerTileEnabler
{
  public void Initialize(object model, ITriggerTileView view);
}
public interface ITriggerTileView: ITriggerEventSubscriber, ITriggerTileEnabler
{
  public TriggerTileType GetTriggerType();
}

덕분에 presenter, view 인터페이스가 상당히 깔끔해졌습니다.

 

public enum TriggerTileType
{
    LeftClearTrigger,
    RightClearTrigger,
    Spike,
}

트리거 타일이 단순히 클리어 외에 장애물, 신호 등등 다양한 트리거가 생길 것 같아 외부에서 확인할 수 있게 종류를 enum으로 구분하고 게임오브젝트로서 접근 가능한 view에 GetTriggerType()도 붙였습니다.

public class TriggerTileSetupService : IStageObjectSetupService<ITriggerTilePresenter>
{

//중략

  public async UniTask<List<ITriggerTilePresenter>> SetupAsync(object data)
  {
    var presenters = new List<ITriggerTilePresenter>();
    var setupData = data as SetupData;
    foreach(var view in setupData.existViews)
    {
      switch (view.GetTriggerType())
      {
        case TriggerTileType.LeftClearTrigger:
          {
            var presenter = new ClearTriggerTilePresenter();
            var model = new ClearTriggerTileModel();
            presenter.Initialize(model, view);
            presenter.SubscribeOnEnter(OnLeftClearEnter);
            presenter.SubscribeOnExit(OnLeftClearExit);
            presenters.Add(presenter);
            cachedTriggers.Add(presenter);
          }
          break;

        case TriggerTileType.RightClearTrigger:
          {
            var presenter = new ClearTriggerTilePresenter();
            var model = new ClearTriggerTileModel();
            presenter.Initialize(model, view);
            presenter.SubscribeOnEnter(OnRightClearEnter);
            presenter.SubscribeOnExit(OnRightClearExit);
            presenters.Add(presenter);
            cachedTriggers.Add(presenter);
          }
          break;

        case TriggerTileType.Spike:
          {
            var presenter = new SpikeTriggerTilePresenter();
            var model = new SpikeTriggerTileModel();
            presenter.Initialize(model, view);
            presenter.SubscribeOnEnter(OnSpikeEnter);
            presenters.Add(presenter);
            cachedTriggers.Add(presenter);
          }
          break;
      }
    }

    await UniTask.CompletedTask;
    return presenters;
  }
//생략
}

암튼 StageManager에서 사테이지 오브젝트를 생성하고 StageDataContainer에서 해당 맵의 트리거 view들을 긁어오면

TriggerTileSetupService에서 각 view.GetTriggerType()마다 적절한 presenter, model을 생성해 넣어줍니다.

 

  private void OnLeftClearEnter(Collider2D collider2D)
  {
    isLeftEnter = true;

    if (CheckBothClearEnter())
      LocalManager.instance.StageManager.Complete();
  }

  private void OnLeftClearExit(Collider2D collider2D)
  {
    isLeftEnter = false;
  }

  private void OnRightClearEnter(Collider2D collider2D)
  {
    isRightEnter = true;

    if (CheckBothClearEnter())
      LocalManager.instance.StageManager.Complete();
  }

  private void OnRightClearExit(Collider2D collider2D)
  {
    isRightEnter=false;
  }

  private bool CheckBothClearEnter()
    => isLeftEnter && isRightEnter;

좌/우 클리어 트리거는 예측하시는 대로 양쪽 동시에 들어오면 게임 승리(StageManager.Complete();)

 

  private void OnSpikeEnter(Collider2D collider2D)
  {
    if(collider2D.gameObject.TryGetComponent<IPlayerView>(out var playerView))
    {
      var failType = playerView.GetPlayerType() switch
      {
        PlayerType.Left => StageFailType.LeftPlayerDied,
        PlayerType.Right => StageFailType.RightPlayerDied,
        _ => throw new System.NotImplementedException()
      }; ;

      LocalManager.instance.StageManager.Fail(failType);
    }
  }

가시 트리거는 접촉 시 해당 오브젝트로부터 IPlayerView를 가져와 좌/우 타입에 따라

좌/우 플레이어 사망 신호를 보냅니다.

 

레이어 세팅을 해놨기 때문에 태그 검사는 필요없는데스

 

 

아직 패배/승리 UI를 딱히 만들지 않아서 F1을 누르면 씬이 재시작되도록 했습니다.

public class SceneProvider : MonoBehaviour, ISceneProvider
{
  [SerializeField] private AssetReference gameSceneReference;

  readonly private Dictionary<AssetReference, AsyncOperationHandle<SceneInstance>> cachedHandled = new();
  private SceneType currentScene;

  public async UniTask LoadSceneAsync(
    SceneType sceneType,
    CancellationToken token,
    UnityAction<float> onProgress = null, 
    UnityAction onComplete = null, Func<UniTask> 
    waitUntilLoad = null)
  {
    var handle = LoadSceneHandle(sceneType);
    try
    {
      while (!handle.IsDone)
      {
        token.ThrowIfCancellationRequested();

        onProgress?.Invoke(handle.PercentComplete);
        await UniTask.Yield(PlayerLoopTiming.Update);
      }
      onProgress?.Invoke(1.0f);

      if (waitUntilLoad != null)
        await waitUntilLoad.Invoke();

      currentScene = sceneType;
      await handle.Result.ActivateAsync();
    }
    catch (OperationCanceledException e) { Debug.Log(e); }
  }

  public async UniTask ReloadCurrentSceneAsync(
    CancellationToken token, 
    UnityAction<float> onProgress = null, 
    UnityAction onComplete = null, 
    Func<UniTask> waitUntilLoad = null)
  {
    await LoadSceneAsync(currentScene, token, onProgress, onComplete, waitUntilLoad);
  }

  private AssetReference ParseSceneReference(SceneType sceneType)
    => sceneType switch
    {
      SceneType.Initialize => throw new System.NotImplementedException(),
      SceneType.Game => gameSceneReference,
      _ => throw new System.NotImplementedException(),
    };

  private AsyncOperationHandle<SceneInstance> LoadSceneHandle(SceneType sceneType)
  {
    var sceneReference = ParseSceneReference(sceneType);
    if (cachedHandled.TryGetValue(sceneReference, out var existHandle)== false)    
    existHandle = Addressables.LoadSceneAsync(sceneReference, LoadSceneMode.Single, false);

    return existHandle;
  }
}

씬 호출에 cancellationToken과 onComplete를 추가했습니다.

씬 재시작은 그냥 적당히 현재 씬 다시 만들기만 합니다.

 

다음 목표:

구조 다이어그램 재검토

UI 구조

트리거 타일 데이터 테이블화

플레이어 세세한 데이터 테이블화