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 구조
트리거 타일 데이터 테이블화
플레이어 세세한 데이터 테이블화
'무제_LR' 카테고리의 다른 글
| 간단한 Localization 및 언어별 Font 설정 (0) | 2025.11.11 |
|---|---|
| UI,,,,,구조,,,,및,,,,,매니저,,,인터페이스화,,,,,,,, (0) | 2025.11.10 |
| 스테이지 생성(타일맵) (0) | 2025.11.06 |
| 스테이지 데이터 구조 (0) | 2025.11.03 |
| Addressable 구조 구현, 테이블 적용 (0) | 2025.11.02 |