키보드·패드 입력 리바인딩 충돌을 그래프로 탐지하는 설정 UI 설계
Unity Input System과 Unreal Enhanced Input에서 키보드·패드 중복 바인딩을 그래프로 탐지하고 충돌 범위, 우선순위, 해결 방법까지 안내하는 설정 UI 구현 전략입니다.

핵심 요약
키보드·패드 리바인딩은 바인딩 문자열을 단순 비교하는 대신 입력 액션을 정점으로 만들고 같은 컨트롤과 충돌 범위를 공유하는 액션 사이에 간선을 추가하면 안정적으로 중복 바인딩을 탐지할 수 있습니다.
설정 UI에서는 충돌 정점과 간선을 즉시 갱신하고 오류·경고·허용 상태를 구분해 저장 가능 여부와 해결 방법을 함께 보여 주는 것이 핵심입니다.
왜 단순한 중복 키 검사는 입력 충돌을 놓치는가?
Space처럼 표시 이름만 비교하면 실제 충돌 여부를 정확히 판단하기 어렵습니다. 같은 키라도 다음 조건에 따라 의도적으로 공존할 수 있기 때문입니다.
| 판단 요소 | 예시 | 충돌 판정에 미치는 영향 |
|---|---|---|
| 장치 그룹 | Keyboard, Gamepad | 서로 다른 장치라면 기본적으로 충돌하지 않음 |
| 입력 액션 맵 | Gameplay, Menu | 동시에 활성화되지 않으면 허용 가능 |
| 게임플레이 레이어 | OnFoot, Vehicle | 활성 레이어가 겹칠 때만 충돌 |
| 컨트롤 경로 | <Keyboard>/space | 정규화 후 동일해야 충돌 |
| 입력 단계 | Press, Hold, Release | 같은 입력이라도 처리 단계에 따라 경고 수준이 달라질 수 있음 |
| 우선순위 | Unreal Mapping Context Priority | 동시에 활성화되면 우선순위가 낮은 매핑이 가려질 수 있음 |
충돌은 다음 세 조건이 모두 참일 때 발생한다고 정의할 수 있습니다.
여기서 Scope는 프로필, 장치 그룹, 액션 맵, 게임플레이 레이어를 묶은 충돌 범위입니다. 같은 키를 사용하더라도 범위가 겹치지 않으면 오류가 아니라 허용 상태로 처리합니다.
입력 바인딩을 어떻게 그래프로 모델링할까?
각 입력 액션의 바인딩을 그래프의 정점으로 만들고 충돌 관계를 간선으로 표현합니다.
- 정점:
Jump:Keyboard,Dodge:Keyboard같은 액션 바인딩 - 정점 속성: 액션 ID, 바인딩 인덱스, 컨트롤 경로, 장치 그룹, 충돌 범위
- 간선: 두 정점이 동시에 활성화되며 같은 입력을 사용하는 관계
- 간선 속성:
Error,Warning,Allowed, 충돌 원인
flowchart LR
A[입력 액션] --> B[컨트롤 경로 정규화]
B --> C[충돌 범위 계산]
C --> D{같은 입력과 겹치는 범위인가?}
D -- 예 --> E[충돌 그래프에 간선 추가]
E --> F[오류 또는 경고 표시]
D -- 아니오 --> G[허용 상태]
그래프를 사용하면 단순히 “Space가 중복되었습니다”라고 알리는 데서 끝나지 않습니다. Jump가 여러 액션과 연결되어 있다면 어떤 액션을 변경할 때 충돌이 연쇄적으로 해소되는지도 표시할 수 있습니다.
충돌 키는 어떻게 정규화해야 하는가?
Unity Input System에서는 바인딩의 표시 이름이 아니라 control path를 기준으로 비교하는 편이 안전합니다. 예를 들어 다음 두 값은 같은 키를 가리킬 수 있으므로 내부적으로 같은 키로 취급해야 합니다.
<Keyboard>/space- 사용자가 입력한 Space의 표시 문자열
다만 게임패드의 buttonSouth처럼 플랫폼 또는 레이아웃에 따라 의미가 달라질 수 있는 입력은 장치 그룹과 컨트롤 스킴을 함께 저장해야 합니다.
복합 바인딩도 별도로 처리해야 합니다.
| 바인딩 종류 | 내부 비교 방식 |
|---|---|
| 단일 키 | 정규화된 control path 비교 |
| Shift + 키 | modifier 집합과 주 키를 함께 비교 |
| 2D Vector Composite | Up, Down, Left, Right 각 파트를 하나의 그룹으로 비교 |
| 게임패드 버튼 | 장치 그룹과 control scheme을 포함해 비교 |
| Unreal 키 매핑 | FKey와 활성 UInputMappingContext 우선순위 비교 |
C#으로 충돌 그래프를 어떻게 구현할까?
다음 코드는 .NET 6 / C# 10을 대상으로 한 독립적인 모델 예시이며 Unity 스크립트에 그대로 붙여 넣는 코드는 아닙니다. Unity 프로젝트에서는 대상 에디터와 Input System 버전에 맞는 일반 클래스로 모델을 옮기고, InputActionAsset에서 필요한 데이터를 추출할 수 있습니다. Unreal에서는 UInputMappingContext의 매핑을 같은 논리 모델로 변환할 수 있습니다.
using System;
using System.Collections.Generic;
using System.Linq;
public enum ConflictSeverity
{
Allowed,
Warning,
Error
}
public readonly record struct BindingKey(
string ControlPath,
string DeviceGroup,
string ScopeId,
string Phase);
public sealed record BindingNode(
string ActionId,
int BindingIndex,
BindingKey Key);
public sealed record ConflictEdge(
BindingNode A,
BindingNode B,
ConflictSeverity Severity,
string Reason);
충돌 검사는 정규화된 control path별로 후보를 버킷으로 묶은 뒤, 프로젝트가 정의한 장치 그룹·범위·단계 겹침 규칙을 적용합니다. ScopeId와 Phase 문자열 자체는 의미상 겹침을 보장하지 않으므로 호출 측이 판정 함수를 제공합니다.
public static class ConflictGraph
{
public static List<ConflictEdge> FindConflicts(
IReadOnlyList<BindingNode> nodes,
Func<string, string, bool> deviceGroupsOverlap,
Func<string, string, bool> scopesOverlap,
Func<string, string, bool> phasesOverlap)
{
var result = new List<ConflictEdge>();
var buckets = nodes
.GroupBy(node => node.Key.ControlPath)
.Select(group => group.ToList());
foreach (var bucket in buckets)
{
for (int i = 0; i < bucket.Count; i++)
{
for (int j = i + 1; j < bucket.Count; j++)
{
var a = bucket[i];
var b = bucket[j];
if (!deviceGroupsOverlap(a.Key.DeviceGroup, b.Key.DeviceGroup) ||
!scopesOverlap(a.Key.ScopeId, b.Key.ScopeId) ||
!phasesOverlap(a.Key.Phase, b.Key.Phase))
continue;
result.Add(new ConflictEdge(
a,
b,
ConflictSeverity.Error,
"겹치는 장치·범위·단계에서 동일한 입력을 사용합니다."));
}
}
}
return result;
}
}
이 방식의 평균적인 비용은 버킷 크기를 라고 할 때 O(n + Σk_i²)입니다. 모든 바인딩을 서로 비교하는 O(n²) 방식보다 입력 수가 많아졌을 때 유리합니다. 설정 화면에서 한 바인딩만 수정한다면 해당 control path의 버킷만 다시 계산해 더 빠르게 갱신할 수 있습니다.
충돌 표시 설정 UI는 어떻게 설계할까?
리바인딩 화면은 다음 세 영역으로 나누면 사용자가 충돌 원인을 빠르게 이해할 수 있습니다.
- 왼쪽: 액션 목록과 현재 바인딩
- 중앙: 키보드 또는 게임패드 입력을 기다리는 리바인딩 컨트롤
- 오른쪽: 충돌한 액션, 충돌 범위, 해결 버튼을 보여 주는 진단 패널

충돌 상태는 색상만으로 구분하지 말고 아이콘과 텍스트를 함께 사용해야 합니다.
| 상태 | 의미 | UI 동작 |
|---|---|---|
| 오류 | 동시에 활성화되는 액션이 동일 입력을 사용 | 저장 버튼 비활성화 또는 확인 절차 요구 |
| 경고 | 우선순위나 액션 맵에 따라 한쪽 입력이 가려질 수 있음 | 저장은 허용하되 원인과 우선순위 표시 |
| 허용 | 장치나 활성 범위가 달라 충돌하지 않음 | 별도 경고 없이 저장 |
새 입력을 받으면 전체 그래프를 다시 그리지 말고 다음 순서로 변경분만 반영합니다.
Step 1. 새 입력을 정규화한다
키보드 입력은 control path로, 패드 입력은 control scheme과 함께 정규화합니다. Shift 같은 modifier가 있으면 순서를 정렬해 동일한 조합이 항상 같은 서명을 갖게 합니다.
Step 2. 기존 간선을 제거하고 새 간선을 계산한다
수정 전 바인딩이 연결된 간선을 그래프에서 제거합니다. 그다음 같은 입력 버킷과 충돌 범위가 겹치는 정점만 비교해 새 간선을 추가합니다.
Step 3. UI와 저장 정책을 갱신한다
충돌 상대 액션을 목록으로 보여 주고 해당 행을 강조합니다. 오류가 있으면 저장 버튼을 비활성화하고 사용자가 기존 바인딩 교체, 새 입력 선택, 충돌 무시 중 가능한 작업을 선택하게 합니다.
사용자가 충돌을 어떻게 해결하게 할까?
자동 교체는 편리하지만 기존 조작을 예고 없이 잃게 만들 수 있습니다. 따라서 다음처럼 명시적인 해결 흐름을 제공하는 것이 안전합니다.
- 기존 바인딩 교체: 충돌 상대의 입력을 해제하고 새 입력을 적용
- 새 입력 선택: 현재 입력 대기를 취소하고 다른 키를 기다림
- 범위 변경: 특정 장치나 액션 맵에서만 사용하는 입력으로 이동
- 경고 무시:
Warning만 허용하며 오류는 계속 차단 - 기본값 복원: 해당 액션 또는 전체 프로필의 기본 바인딩으로 복원
Unreal Enhanced Input을 사용하는 경우에는 UInputMappingContext의 우선순위를 함께 표시하는 것이 좋습니다. 같은 FKey가 여러 컨텍스트에 있으면 실제로 어떤 컨텍스트가 먼저 적용되는지 알 수 있어야 하기 때문입니다.
런타임 성능과 저장 데이터는 어떻게 관리할까?
충돌 그래프는 설정 화면이 열렸을 때 한 번 만들고 바인딩 변경 이벤트가 발생할 때 부분 갱신합니다. 매 프레임 검사하면 입력 설정 수가 작더라도 불필요한 할당과 UI 갱신이 발생합니다.
저장 데이터에는 표시 문자열보다 안정적인 식별자를 저장합니다.
{
"profile": "default",
"bindings": [
{
"action": "jump",
"deviceGroup": "Keyboard",
"controlPath": "<Keyboard>/space",
"scope": "Gameplay"
}
]
}
권장 검증 항목은 다음과 같습니다.
- 존재하지 않는 control path가 저장되지 않았는가?
- 동일 액션에 같은 입력이 중복 등록되지 않았는가?
- 현재 활성 범위에서
Error간선이 남아 있지 않은가? - 장치가 연결 해제되었을 때 대체 입력을 제공하는가?
- 키보드 배열이나 게임패드 레이아웃이 바뀌어도 기본값을 복원할 수 있는가?
자주 묻는 질문(FAQ)
같은 키를 두 액션에 할당하면 항상 오류인가요?
아닙니다. 장치 그룹이나 액션 맵이 다르고 동시에 활성화되지 않는다면 허용할 수 있습니다. 충돌 범위가 겹치는지를 먼저 판단해야 합니다.
그래프 정점은 액션 단위로 만들어야 하나요, 바인딩 단위로 만들어야 하나요?
바인딩 단위가 적합합니다. 하나의 액션에 기본 키와 대체 키가 여러 개 있을 수 있으므로 액션만 정점으로 만들면 어느 입력이 충돌하는지 표현하기 어렵습니다.
충돌 검사를 매 프레임 실행해도 되나요?
권장하지 않습니다. 설정 화면 진입 시 전체 그래프를 생성하고 바인딩 변경 이벤트가 발생한 경우에만 해당 버킷을 부분 갱신하는 편이 효율적입니다.
정리
입력 리바인딩 충돌은 키 이름이 같은지만 비교해서 해결하기 어렵습니다. 컨트롤 경로를 정규화하고 장치 그룹, 활성 범위, 입력 단계를 포함한 충돌 키를 만든 뒤 바인딩 간 관계를 그래프로 관리하면 오류와 경고를 정확히 구분할 수 있습니다.
설정 UI에서는 충돌 정점과 간선을 사용자에게 이해 가능한 액션 이름으로 변환하고 저장 차단 여부와 해결 방법을 함께 제공해야 합니다. 이 구조는 Unity Input System뿐 아니라 Unreal Enhanced Input에도 적용할 수 있으며 런타임에서는 변경된 바인딩 주변만 다시 검사해 성능을 유지할 수 있습니다.



