이전 포스팅으로부터 아주 약간의 텀이 있었는데요,
작업이 개발보다는 기획에 가까웠던지라 시간이 조금 더 걸렸습니다.
위 영상은 마우스 및 클릭을 사용하지 않고
WASD(보라색 인디케이터 이동으로 확인 가능),
↑→↓←(주황색 FillAmount로 확인 가능)으로만 작동하는 UI 로직을 만들었습니다.
좌측 상하좌우(WASD)로 SelectedGameObject를 이동, 우측 상하좌우( ↑→↓←)로 Submit하도록 스크립트를 짰습니다.
public interface IUIProgressSubmitView
{
public void Perform(Direction direction); //외부에서 Perform,
public void Cancel(Direction direction); //Cancel만 받음
//이하 구독 메소드
public void SubscribeOnPerformed(Direction direction, UnityAction onPerformed);
public void SubscribeOnCanceled(Direction direction, UnityAction onCanceled);
public void SubscribeOnProgress(Direction direction, UnityAction<float> onProgress);
public void SubscribeOnComplete(Direction direction, UnityAction onComplete);
public void UnsubscribeOnPerformed(Direction direction, UnityAction onPerformed);
public void UnsubscribeOnCanceled(Direction direction, UnityAction onCanceled);
public void UnsubscribeOnProgress(Direction direction, UnityAction<float> onProgress);
public void UnsubscribeOnComplete(Direction direction, UnityAction onComplete);
public void UnsubscribeAll();
public void ResetProgress(Direction direction);
public void ResetAllProgress();
}
IUIProgressSubmitView가 기반이 되는데요,

대충 Manager 수준의 클래스를 통해 Perform과 Cancel을 입력받습니다.
상하좌우 방향마다 개별적으로 Progress를 진행시키는데 위 스크린샷을 보다시피 전부 긁어오기엔 약간 스압이 있어 넘깁니다.
https://github.com/ClapJeong/LRGame
GitHub - ClapJeong/LRGame: A game requires left and right
A game requires left and right. Contribute to ClapJeong/LRGame development by creating an account on GitHub.
github.com
오픈소스로 돌려놨으니 전문이 궁금하신 분들은 한번 확인해보세요.
암튼 이 IUIProgressSubmit을 누가 접근하고 관리하느냐!
public class UIProgressSubmitController: IUIProgressSubmitController
{ //인터페이스 설명은 생략
private readonly IUISelectedGameObjectService selectedGameObjectService;
private readonly Dictionary<Direction, InputAction> rightInputActions = new();
private readonly List<Direction> currentPerforming = new();
private IUIProgressSubmitView selectedView;
public UIProgressSubmitController(IUISelectedGameObjectService selectedGameObjectService, InputActionFactory inputActionFactory)
{
this.selectedGameObjectService = selectedGameObjectService;
var table = GlobalManager.instance.Table.UISO.InputPaths;
rightInputActions[Direction.Up] = inputActionFactory.Get(table.RightUPPath, context => OnInputAction(Direction.Up, context));
rightInputActions[Direction.Right] = inputActionFactory.Get(table.RightRightPath, context => OnInputAction(Direction.Right, context));
rightInputActions[Direction.Down] = inputActionFactory.Get(table.RightDownPath, context => OnInputAction(Direction.Down, context));
rightInputActions[Direction.Left] = inputActionFactory.Get(table.RightLeftPath, context => OnInputAction(Direction.Left, context));
//InputAction의 Factory로부터 상하좌우 InputAction을 미리 만들어둡니다.
//그리고 해당 InputAction에게 아래 보이는 OnInputAction을 구독시킵니다.
selectedGameObjectService.SubscribeEvent(IUISelectedGameObjectService.EventType.OnEnter, OnSelectedGameObjectEnter);
selectedGameObjectService.SubscribeEvent(IUISelectedGameObjectService.EventType.OnExit, OnSelectedGameObjectExit);
//그리고 EventSystem.current.currentSelectedGameObject를 추적하는
//selectedGameObjectService에게 OnEnter랑 OnExit를 구독하는데요,
//해당 ProgressSubmit 선택이 취소되었을 때 자동으로 Cancel을 호출하기 위해서입니다.
}
private void OnInputAction(Direction direction, InputAction.CallbackContext context)
{
//해당 Direction의 Perform 이벤트와 Cancel 이벤트를 관리합니다.
switch (context.phase)
{
case InputActionPhase.Performed:
{
selectedView?.Perform(direction);
currentPerforming.Add(direction);
}
break;
case InputActionPhase.Canceled:
{
selectedView?.Cancel(direction);
currentPerforming.Remove(direction);
}
break;
}
}
private void OnSelectedGameObjectEnter(GameObject gameObject)
{
if (gameObject.TryGetComponent<IUIProgressSubmitView>(out var view))
selectedView = view;
}//EventSystem.current.currentSelectedGameObject를 그대로 참조하지 않고
//해당 객체가 변경됐을 때 IUIProgressSubmitView 여부를 확인하고 캐싱해둡니다.
private void OnSelectedGameObjectExit(GameObject gameObject)
{
foreach(var direction in currentPerforming)
selectedView?.Cancel(direction);
currentPerforming.Clear();
}//결국 '현재 선택된 View'를 조작하는 것이기 때문에
//'현재 선택된 View'가 변경됐을 때 자동으로 Cancel하도록 마무리까지 해둡니다.
}

완벽한 다이어그램이라고 하기엔 무리가 있을지도 모르겠지만 이정도면 충분한 것 같습니다.

Q1: IUIProgressSubmitView에서 Perform이나 Cancel을 받는건 알겠는데 0.0 ~ 1.0 Progress는 어디있나요?
A1: BaseUIProgressSubmitView(스크린샷으로 대체한 그 긴거)에 나와있습니다. 대충 Perform 받으면 0.0 ~ 1.0까지 올라가고, 도중에 Cancel가 호출되면 진행도가 싸그리 날라갑니다.
Q2: 그럼 완료됐을 때 이벤트 그런거 어디 없나요?
A2: 있습니다. 외부에서 이벤트 구독 하는 방식으로 넣어줍니다.
viewContainer.progressSubmitView.SubscribeOnProgress(direction, value =>
{
viewContainer.imageView.SetFillAmount(value);
//Progress 이벤트에는 normalizedValue를 인자로 받습니다.
//위 예시에서는 FillAmount를 통해 Progress 진행도를 보여줍니다.
});
viewContainer.progressSubmitView.SubscribeOnComplete(direction, () =>
{
//progress가 1.0까지 도달하면 onComplete가 호출됩니다.
//해당 코드는 스테이지 이동 버튼입니다.
//바로 씬을 변경할 것이기 때문에 다른 모든 이벤트를 깔끔하게 해제해줍니다.
viewContainer.progressSubmitView.UnsubscribeAll();
model.onComplete?.Invoke();
model.gameDataService.SetSelectedStage(model.chapter, model.stage);
model.sceneProvider.LoadSceneAsync(
SceneType.Game,
CancellationToken.None,
onProgress: null,
onComplete: null).Forget();
});
적당히 해당 View를 가지고 있는 곳에서 이벤트를 구독해줍시다.
영상에서는 첫 십자 패널을 활성화하면 이후 상하좌우 버튼이 있는 패널에서는 Indicator가 위치한 곳에서만 ProgressSubmitView가 작동하도록 했는데,
해당 부분은 사실 딱히 Naviagtion을 설정한 것은 아니고 스크립트를 통해 WASD 인풋을 따라 Indicator가 움직이고, 해당 ProgressSubmitView만 작동하도록 설정한 것입니다.

용수철이나 오뚝이처럼 버튼을 누른 상태에서는 해당 위치로 이동,
버튼을 때면 원래 자리(중심부)로 이동하도록 InputAction에 이벤트를 구독하기만 했습니다.
스크린샷의 UIInputAction Enum의 LeftUp LeftRight는 대체 뭐냐:
Left Up/Right/Down/Left -> WASD
Right Up/Right/Down/Left -> ↑→↓←
'무제_LR' 카테고리의 다른 글
| Indicator 보강 (0) | 2025.12.07 |
|---|---|
| Progress UI - Stage UI에 적용 (0) | 2025.12.04 |
| UI Indicator 만들기 만들기(Selctable, Navigation) (0) | 2025.11.26 |
| 피격 후 무적 시간 적용 (0) | 2025.11.22 |
| 플레이어에 FSM 적용에 FSM 적용 (0) | 2025.11.21 |