AI 에이전트로 Unity 인게임 UI 내비게이션 자동 탐색과 포커스 트리 검증하기
Unity uGUI·UI Toolkit과 Unreal CommonUI에서 AI 에이전트가 인게임 UI를 자동 탐색하고 포커스 트리, 입력 경로, 접근성 오류를 검증하는 구현 전략을 설명합니다.

핵심 요약
AI 에이전트 기반 UI 테스트는 화면을 임의로 클릭하는 방식이 아니라 UI를 의미 있는 노드와 전이 그래프로 모델링한 뒤 탐색하는 방식으로 구현할 수 있습니다. 각 노드의 표시 상태, 상호작용 가능 여부, 포커스 이동 경로를 관찰하고 BFS 또는 DFS로 모든 입력 경로를 실행하면 포커스 누락, 막힌 메뉴, 잘못된 뒤로 가기 동작을 자동으로 찾을 수 있습니다.
핵심은 세 가지입니다.
- UI 계층 구조와 내비게이션 연결을
FocusNode와FocusEdge로 정규화합니다. - 에이전트가 관찰하고 행동하고 검증하는 루프를 반복합니다.
- 탐색 결과를 포커스 트리 검증 규칙과 커버리지 리포트로 저장합니다.
인게임 UI 내비게이션 자동화가 필요한 이유
컨트롤러와 키보드를 함께 지원하는 게임에서는 UI가 단순한 버튼 목록이 아닙니다. 메뉴가 열릴 때 첫 포커스가 어디로 이동하는지 팝업이 닫힌 뒤 이전 포커스를 복원하는지 비활성화된 항목을 건너뛰는지 씬 전환 후 입력 대상이 남아 있는지를 모두 관리해야 합니다.
수동 테스트만으로는 다음 문제가 자주 누락됩니다.
| 문제 | 일반적인 증상 | 자동 검증 방법 |
|---|---|---|
| 포커스 고립 | 특정 버튼에서 더 이상 이동하지 않음 | 방향별 전이 존재 여부 검사 |
| 숨겨진 노드 연결 | 보이지 않는 버튼으로 포커스 이동 | visible, enabled, interactable 상태 검증 |
| 잘못된 팝업 복귀 | 팝업 종료 후 엉뚱한 컨트롤에 포커스 | 포커스 스택과 복귀 대상 비교 |
| 순환 참조 | 메뉴 이동이 무한 반복됨 | 방문 상태와 최대 단계 제한 적용 |
| 플랫폼 입력 불일치 | 게임패드에서는 이동하지만 키보드에서는 실패 | 입력 장치별 경로를 별도로 탐색 |
따라서 테스트 대상은 화면 이미지가 아니라 UI의 의미 구조와 상태 변화가 되어야 합니다.
AI 에이전트는 UI를 어떻게 탐색하는가?
여기서 AI 에이전트는 반드시 대규모 언어 모델을 뜻하지 않습니다. 게임 테스트에서 더 안정적인 형태는 관찰된 UI 상태를 바탕으로 다음 행동을 선택하는 결정론적 탐색기입니다. 필요하다면 LLM은 실패 로그 요약이나 새로운 검증 규칙 생성에만 보조적으로 사용할 수 있습니다.
에이전트의 기본 루프는 다음과 같습니다.
flowchart LR
A[UI 상태 관찰] --> B[포커스 트리 생성]
B --> C[가능한 행동 선택]
C --> D[입력 실행]
D --> E[상태 변화 검증]
E --> F{탐색 종료?}
F -- 아니오 --> A
F -- 예 --> G[리포트 저장]
관찰 단계에서는 현재 씬의 UI 루트, 활성 패널, 선택된 컨트롤, 컨트롤별 방향 내비게이션을 수집합니다. 행동은 Up, Down, Left, Right, Submit, Cancel처럼 추상화된 입력으로 표현합니다.
상태는 다음과 같이 정의할 수 있습니다.
V: 현재 표시되고 활성화된 UI 노드 집합F: 현재 포커스를 가진 노드P: 활성 팝업과 메뉴의 포커스 스택I: 현재 입력 장치와 입력 모드
행동 a를 실행한 뒤 상태가 정상적으로 바뀌었는지는 다음 조건으로 확인합니다.
포커스 트리를 어떤 데이터로 표현할까?
Unity uGUI, Unity UI Toolkit, Unreal CommonUI는 실제 구현 방식이 다르지만 테스트용 중간 표현은 통일할 수 있습니다. 중요한 것은 시각적 위치보다 개발자가 의도한 내비게이션 연결을 우선 보존하는 것입니다.
The following UI graph/state models use C# 10 record/init syntax and target .NET 6 or later as engine-independent QA-tool code. They are not paste-ready for the default C# 9 compiler in Unity 2022.3; use a separate .NET 6+ test tool or replace these model declarations with Unity-compatible classes.
public enum UiAction
{
Up,
Down,
Left,
Right,
Submit,
Cancel
}
public sealed class FocusNode
{
public string Id { get; init; }
public string Role { get; init; }
public bool IsVisible { get; init; }
public bool IsEnabled { get; init; }
public bool IsInteractable { get; init; }
public IReadOnlyDictionary<UiAction, string> Links { get; init; }
}
public sealed record FocusState(
string FocusedId,
IReadOnlyList<string> OpenPanels,
string InputMode
);
각 노드의 Id는 런타임 인스턴스 주소가 아니라 안정적인 테스트 식별자여야 합니다. 예를 들어 프리팹 이름만 사용하면 동일한 슬롯이 여러 개 생성될 때 충돌할 수 있으므로 Inventory.Panel.Item.03.EquipButton처럼 경로와 슬롯 인덱스를 함께 기록하는 편이 좋습니다.
명시적 내비게이션과 공간 내비게이션 비교
| 방식 | 장점 | 위험 | 권장 용도 |
|---|---|---|---|
| 명시적 링크 | 재현성과 검증 가능성이 높음 | 설정 누락 시 이동 불가 | 메뉴, 팝업, 설정 화면 |
| 공간 기반 탐색 | UI 배치 변경에 유연함 | 의도하지 않은 후보가 선택될 수 있음 | 동적 그리드, 인벤토리 |
| 혼합 방식 | 고정 경로와 동적 배치를 함께 지원 | 우선순위 규칙이 필요함 | 대부분의 실제 게임 UI |
혼합 방식에서는 명시적 링크를 먼저 사용하고 링크가 없을 때만 공간 기반 후보를 계산합니다. 공간 기반 후보는 현재 포커스와의 거리뿐 아니라 방향 벡터, 겹침 정도, 동일 패널 여부를 함께 평가해야 합니다.
후보 점수의 예시는 다음과 같습니다.
점수가 가장 낮은 후보를 선택하되 방향 반대편에 있는 컨트롤은 후보에서 제외합니다.
Unity에서 UI 포커스 정보를 수집하는 방법
uGUI에서는 Selectable과 Navigation 정보를 이용할 수 있습니다. EventSystem.current.currentSelectedGameObject는 현재 선택된 게임 오브젝트를 확인하는 시작점입니다. 다만 Selectable만 조회하면 비활성 오브젝트나 커스텀 입력 시스템의 상태가 빠질 수 있으므로 프로젝트의 UI 컨트롤러에서 테스트 어댑터를 제공하는 것이 안전합니다.
다음 코드는 개념을 보여 주는 간단한 수집기입니다.
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
public sealed class UnityFocusTreeCollector
{
public IReadOnlyList<FocusNode> Collect(Transform root)
{
var result = new List<FocusNode>();
var selectables = root.GetComponentsInChildren<Selectable>(true);
foreach (var selectable in selectables)
{
var navigation = selectable.navigation;
var links = new Dictionary<UiAction, string>
{
[UiAction.Up] = GetId(navigation.selectOnUp),
[UiAction.Down] = GetId(navigation.selectOnDown),
[UiAction.Left] = GetId(navigation.selectOnLeft),
[UiAction.Right] = GetId(navigation.selectOnRight)
};
result.Add(new FocusNode
{
Id = GetId(selectable),
Role = selectable.GetType().Name,
IsVisible = selectable.gameObject.activeInHierarchy,
IsEnabled = selectable.enabled,
IsInteractable = selectable.IsInteractable(),
Links = links
});
}
return result;
}
private static string GetId(Selectable selectable)
{
return selectable == null ? null : selectable.gameObject.name;
}
}
실제 프로젝트에서는 이름 대신 UiTestId 컴포넌트나 Addressable 경로를 사용하십시오. UI Toolkit을 사용한다면 VisualElement.name, pickingMode, focusable, enabledSelf와 포커스 컨트롤러의 현재 요소를 테스트 어댑터에서 같은 FocusNode 형식으로 변환하면 됩니다.
Unreal에서는 CommonUI의 UCommonActivatableWidget, 입력 라우팅, 활성 위젯 스택 정보를 어댑터에서 수집하고 Unity와 동일한 중간 표현으로 변환할 수 있습니다. 엔진별 런타임 API를 테스트 로직에 직접 섞지 않는 것이 핵심입니다.

AI 에이전트 기반 자동 탐색을 어떻게 구현할까?
다음 4단계로 구현하면 테스트 로직과 게임 UI를 분리할 수 있습니다.
1단계: 관찰 가능한 UI 어댑터 만들기
어댑터는 에이전트가 다음 질문에 답할 수 있도록 해야 합니다.
- 현재 포커스는 어느 노드인가?
- 현재 표시 중인 패널과 팝업은 무엇인가?
- 각 방향으로 이동 가능한 노드는 무엇인가?
Submit과Cancel을 실행했을 때 기대되는 상태는 무엇인가?- 로딩 중이거나 입력이 잠긴 상태인가?
UI가 애니메이션 중일 때는 즉시 다음 행동을 실행하지 말고 안정화 조건을 기다려야 합니다. 예를 들어 포커스 ID가 두 프레임 이상 동일하고 입력 잠금 플래그가 해제된 경우를 안정 상태로 정의할 수 있습니다.
2단계: 탐색 상태와 방문 키 정의하기
같은 포커스라도 열린 팝업과 선택된 탭이 다르면 다른 상태입니다. 따라서 방문 키를 포커스 ID 하나로만 만들면 유효한 경로를 누락합니다.
public readonly record struct VisitKey(
string FocusedId,
string PanelSignature,
string InputMode
);
public sealed record AgentStep(
VisitKey Before,
UiAction Action,
VisitKey After,
bool Passed,
string Failure
);
PanelSignature는 활성 패널 ID를 정렬한 문자열이나 해시로 만들 수 있습니다. 네트워크 상태나 시간에 따라 UI가 바뀌는 게임에서는 테스트 시드와 고정된 플레이어 상태도 방문 키에 포함해야 재현성이 높아집니다.
3단계: BFS 또는 DFS로 행동 실행하기
짧은 경로에서 오류를 우선 찾으려면 BFS가 적합합니다. 전체 상태를 적은 메모리로 깊게 탐색하려면 DFS를 사용할 수 있습니다. 실무에서는 BFS로 기본 경로를 찾고 특정 메뉴만 DFS로 상세 탐색하는 혼합 구성이 유용합니다.
using System.Collections.Generic;
public sealed class UiAgent
{
private readonly Queue<IReadOnlyList<UiAction>> queue = new();
private readonly HashSet<VisitKey> visited = new();
public void Run(IUiTestDriver driver, int maxSteps)
{
queue.Clear();
visited.Clear();
queue.Enqueue(new List<UiAction>());
var steps = 0;
while (queue.Count > 0 && steps < maxSteps)
{
var path = queue.Dequeue();
driver.ResetToInitialState();
var replaySucceeded = true;
foreach (var priorAction in path)
{
if (!driver.ExecuteAndObserve(priorAction).Passed)
{
replaySucceeded = false;
break;
}
}
if (!replaySucceeded)
continue;
var before = driver.Observe();
if (!visited.Add(before.Key))
continue;
foreach (var action in driver.GetCandidateActions(before))
{
if (steps >= maxSteps)
break;
driver.ResetToInitialState();
replaySucceeded = true;
foreach (var priorAction in path)
{
if (!driver.ExecuteAndObserve(priorAction).Passed)
{
replaySucceeded = false;
break;
}
}
if (!replaySucceeded)
continue;
var branchBefore = driver.Observe();
var nextPath = new List<UiAction>(path) { action };
var result = driver.ExecuteAndObserve(action);
steps++;
if (!result.Passed)
driver.SaveFailure(nextPath, branchBefore, result);
if (result.IsStable && visited.Count < maxSteps)
queue.Enqueue(nextPath);
}
}
}
}
ResetToInitialState는 테스트 씬 재로드나 저장된 체크포인트 복원으로 구현할 수 있습니다. 테스트가 길어질수록 전체 씬 재로드 비용이 커지므로 메뉴 단위의 빠른 리셋 지점을 마련하는 것이 좋습니다.
4단계: 행동 이후의 불변식 검증하기
행동이 실행되었다는 사실만으로 성공을 판단하면 안 됩니다. 다음과 같은 불변식을 함께 검사해야 합니다.
| 불변식 | 검증 조건 |
|---|---|
| 포커스 유일성 | 활성 포커스 노드 수가 정확히 1개 |
| 포커스 유효성 | 포커스 노드가 표시되고 활성화되어 있음 |
| 방향 이동 | 명시된 링크가 존재하면 해당 노드로 이동 |
| 비활성 건너뛰기 | 비활성 노드가 포커스 경로에 포함되지 않음 |
| 제출 결과 | Submit 후 기대 패널 또는 상태가 열림 |
| 취소 결과 | Cancel 후 팝업이 닫히거나 이전 상태로 복귀 |
| 입력 안정성 | 전환 중 중복 입력으로 상태가 두 번 바뀌지 않음 |
포커스 트리 검증 규칙은 어떻게 설계할까?
포커스 트리를 그래프 G = (V, E)로 보면 노드는 UI 컨트롤이고 간선은 방향 입력에 따른 이동입니다. 검증기는 그래프 구조와 실제 런타임 상태를 모두 검사해야 합니다.
구조 검증
구조 검증은 플레이어 입력을 실행하지 않고도 빠르게 수행할 수 있습니다.
- 활성화된 모든 상호작용 노드가 최소 하나의 진입 간선을 가지는지 확인합니다.
Up링크가 연결된 노드의 반대 방향에Down링크가 필요한지 프로젝트 규칙에 따라 검사합니다.- 링크 대상이 존재하지 않는 오브젝트를 가리키지 않는지 확인합니다.
- 동일 패널에서 의도하지 않은 자기 자신 링크가 있는지 찾습니다.
- 탭이나 팝업별 루트 포커스가 정의되어 있는지 검사합니다.
모든 노드가 반드시 양방향 연결을 가져야 하는 것은 아닙니다. 예를 들어 세로 목록의 첫 항목에서 Up을 누르면 포커스가 유지되거나 마지막 항목으로 순환할 수 있습니다. 이 동작은 오류가 아니라 프로젝트 정책으로 명시해야 합니다.
런타임 검증
런타임에서는 구조상 올바른 링크가 실제 상태에서도 유효한지 확인합니다. 특히 다음 조건을 별도로 다뤄야 합니다.
- 비동기 로딩이 끝나기 전에 포커스가 사라지는 경우
- 동적으로 생성된 슬롯이 트리에는 있지만 아직 화면에 표시되지 않는 경우
- 팝업이 닫혔는데도 이전 팝업의 노드가 입력을 받는 경우
- 컨트롤러와 마우스 사이에서 포커스 소유권이 바뀌는 경우
- 씬 전환 뒤 이전 씬의
EventSystem또는 입력 라우터가 남는 경우
에이전트는 실패 시 현재 상태 스냅샷, 마지막 입력, 포커스 트리, 패널 스택, 화면 캡처를 함께 저장해야 합니다. 그래야 동일한 시드와 경로로 문제를 재현할 수 있습니다.

탐색 범위와 실행 시간을 어떻게 제어할까?
UI 상태 공간은 메뉴 수와 동적 데이터에 따라 빠르게 커집니다. 탐색을 무제한으로 실행하면 자동화 테스트가 느려지고 결과도 불안정해집니다.
실행 한도는 다음처럼 분리하는 것이 좋습니다.
| 제한 | 목적 | 예시 |
|---|---|---|
| 최대 깊이 | 지나치게 긴 입력 경로 제한 | 30 actions |
| 최대 상태 수 | 상태 폭발 방지 | 2,000 states |
| 시간 제한 | CI 작업 시간 보장 | 120 seconds |
| 패널별 예산 | 특정 메뉴의 상세 탐색 | 20 seconds |
| 재시도 횟수 | 일시적인 로딩 오류 분리 | 1 retry |
동일한 상태를 다시 방문하지 않도록 방문 키를 사용하고 동적 리스트는 대표 슬롯만 선택하는 추상화도 고려할 수 있습니다. 다만 대표 슬롯 추상화는 실제 인덱스 관련 오류를 놓칠 수 있으므로 별도의 경계값 테스트에서 첫 항목, 중간 항목, 마지막 항목을 검증해야 합니다.
CI에서는 테스트를 다음 세 종류로 나누면 효율적입니다.
- 모든 커밋에서 실행하는 빠른 구조 검증
- 병합 전에 실행하는 주요 메뉴 BFS 탐색
- 야간 빌드에서 실행하는 전체 상태 공간 탐색
자주 묻는 질문 (FAQ)
LLM이 직접 버튼을 보고 클릭하게 해야 하나요?
필수는 아닙니다. 포커스와 입력 경로 검증은 결정론적 그래프 탐색이 더 빠르고 재현성이 높습니다. LLM은 실패 원인 요약이나 테스트 케이스 추천처럼 비결정적 판단이 필요한 부분에 제한적으로 사용하는 편이 안전합니다.
명시적 내비게이션 링크가 없으면 자동 검증이 불가능한가요?
아닙니다. 컨트롤의 화면 좌표와 크기로 공간 내비게이션 후보를 계산할 수 있습니다. 다만 자동 계산 결과가 기획 의도와 일치하는지 확인하려면 중요한 메뉴에는 명시적 링크나 우선순위 태그를 함께 두는 것이 좋습니다.
포커스 트리 검증과 스냅샷 테스트의 차이는 무엇인가요?
스냅샷 테스트는 특정 시점의 화면이나 UI 구조를 비교합니다. 포커스 트리 검증은 입력 행동에 따른 상태 전이와 도달 가능성을 검사합니다. 둘을 함께 사용하면 시각적 회귀와 조작 불가능한 UI를 동시에 찾을 수 있습니다.
정리
AI 에이전트 기반 인게임 UI 자동화의 핵심은 화면을 해석하는 모델보다 테스트 가능한 UI 계약을 만드는 데 있습니다. Unity uGUI·UI Toolkit과 Unreal CommonUI에서 수집한 UI 정보를 공통 FocusNode 그래프로 변환하고 BFS 또는 DFS 탐색을 실행하면 포커스 고립, 숨겨진 링크, 팝업 복귀 실패를 반복 가능하게 검증할 수 있습니다.
처음 적용할 때는 전체 게임보다 설정 화면이나 일시정지 메뉴처럼 상태가 제한된 영역부터 시작하십시오. 이후 안정적인 테스트 ID, 패널 스택, 입력 잠금 상태, 실패 스냅샷을 표준화하면 CI에서 실행 가능한 클라이언트 UI 회귀 테스트로 확장할 수 있습니다.



