키보드 탭 내비게이션은 키보드 사용자가 UI 요소를 효율적으로 탐색하고 상호작용할 수 있도록 도와주는 접근성 기능입니다. 이 기능을 구현하고, 쉽게 설정할 수 있는 도구를 함께 제작하여 정리하였습니다.
📺 미리 보기
📖 구현 내용
- Tab을 누르면 다음 입력필드로, Shift + Tab을 누르면 이전 입력필드로 이동합니다.
- 특정한 입력필드(마지막 입력필드)에서 설정한 키를 누르면 버튼 이벤트를 호출합니다.
- 입력필드 설정을 쉽고 간편하게 하기위해 자동으로 설정해주는 에디터 Tool을 구현하였습니다.
⚒️ 구현
- 에디터 기능과 런타임 도중 실행될 두개의 스크립트가 있습니다.
· UiTabNavi
- 런타임도중 실행되어 키보드의 입력을 확인하여 탭 내비게이션을 수행합니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
[RequireComponent(typeof(TMP_InputField))]
public class UiTabNavi : MonoBehaviour
{
/// <summary>
/// 키보드를 이용한 포커싱 이동이 활성화 되어있는가?
/// </summary>
public static bool IsTabEnabled = true;
private TMP_InputField mCurrentInputField;
[Header("이전 단계의 입력필드(nullable)")]
[SerializeField] TMP_InputField? mPrevInputField = null;
[Header("다음 단계의 입력필드(nullable)")]
[SerializeField] TMP_InputField? mNextInputField = null;
[Space(50)]
[Header("버튼 누름 이벤트를 발생시킬 KeyCode")]
[SerializeField] private KeyCode mEventButtonKeyCode = KeyCode.Return;
[Header("Key Code 이벤트 발생 시 누를 버튼")]
[SerializeField] Button? mEventButton = null;
private void Awake()
{
mCurrentInputField = GetComponent<TMP_InputField>();
// 만약 KeyCode가 엔터(Return)라면, onSubmit에 이벤트를 추가
if (mEventButtonKeyCode == KeyCode.Return)
mCurrentInputField.onSubmit.AddListener((text) =>
{
mEventButton?.onClick.Invoke();
});
}
// Update is called once per frame
void Update()
{
// 현재 이 입력필드가 포커싱(활성화) 되어있는가?
if (mCurrentInputField.isFocused)
// 탭 내비게이션이 활성화 되어있는가?
if (IsTabEnabled)
// 탭키를 눌렀다면?
if (Input.GetKeyDown(KeyCode.Tab))
{
// 탭키를 누른 상태에서 LeftShift를 눌렀다면?
if (Input.GetKey(KeyCode.LeftShift))
{
// 이전 입력필드로 포커싱
mPrevInputField?.ActivateInputField();
}
// 탭키만 누른 상태라면?
else
{
// 다음 입력필드로 포커싱
mNextInputField?.ActivateInputField();
}
}
// 이벤트 코드를 눌렀다면?
else if (Input.GetKeyDown(mEventButtonKeyCode))
{
// 연결된 버튼을 클릭
mEventButton?.onClick.Invoke();
}
}
#if UNITY_EDITOR
/// <summary>
/// Tools/Bonnate/Ui Tab Navigation Simple Setting에서 호출되어 자동으로 탭 내비게이션을 설정합니다.
/// </summary>
/// <param name="prev"></param>
/// <param name="next"></param>
public void InitFromEditor(TMP_InputField prev, TMP_InputField next)
{
mPrevInputField = prev;
mNextInputField = next;
}
#endif
}
[Header("이전 단계의 입력필드(nullable)")]
[SerializeField] TMP_InputField? mPrevInputField = null;
[Header("다음 단계의 입력필드(nullable)")]
[SerializeField] TMP_InputField? mNextInputField = null;
- 이전, 다음 단계의 입력 필드를 인스펙터에서 지정합니다.
- 또는 자동 설정 도구를 이용하여 지정할 수 있습니다.
[Space(50)]
[Header("버튼 누름 이벤트를 발생시킬 KeyCode")]
[SerializeField] private KeyCode mEventButtonKeyCode = KeyCode.Return;
[Header("Key Code 이벤트 발생 시 누를 버튼")]
[SerializeField] Button? mEventButton = null;
- 현재 입력필드가 활성화 되어 있는 상태에서 mEventButtonKeyCode에 해당하는 키보드 이벤트가 발생하면 mEventButton에 있는 버튼이 클릭되는 이벤트가 처리됩니다.
private void Awake()
{
mCurrentInputField = GetComponent<TMP_InputField>();
// 만약 KeyCode가 엔터(Return)라면, onSubmit에 이벤트를 추가
if (mEventButtonKeyCode == KeyCode.Return)
mCurrentInputField.onSubmit.AddListener((text) =>
{
mEventButton?.onClick.Invoke();
});
}
- 입력필드에서 엔터를 입력했을 때 포커싱이 풀리기 때문에 onSubmit 이벤트에 버튼 이벤트를 추가합니다.
void Update()
{
...
}
- 키보드 입력에 따라 이벤트를 처리합니다.
#if UNITY_EDITOR
/// <summary>
/// Tools/Bonnate/Ui Tab Navigation Simple Setting에서 호출되어 자동으로 탭 내비게이션을 설정합니다.
/// </summary>
/// <param name="prev"></param>
/// <param name="next"></param>
public void InitFromEditor(TMP_InputField prev, TMP_InputField next)
{
mPrevInputField = prev;
mNextInputField = next;
}
#endif
- 유니티 에디터에서 사용할 함수로, 입력필드 자동지정 Tool을 사용하면 호출됩니다.
- prev, next를 자동으로 설정해주어 편리하게 사용이 가능합니다.
· UiTabNavigationSimpleSetting
#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using TMPro;
using UnityEngine.UI;
using UnityEditor;
using UnityEngine;
public class UiTabNavigationSimpleSetting : EditorWindow
{
[MenuItem("Tools/Bonnate/Ui Tab Navigation Simple Setting")]
private static void OpenWindow()
{
// 현재 선택된 게임 오브젝트 가져오기
GameObject selectedObject = Selection.activeGameObject;
if (selectedObject == null)
{
Debug.LogWarning("게임 오브젝트를 선택해주세요.");
return;
}
// 선택된 게임 오브젝트의 1단계 자식 오브젝트들 중 TMP_InputField 찾기
List<TMP_InputField> inputFields = new List<TMP_InputField>();
for (int i = 0; i < selectedObject.transform.childCount; ++i)
{
TMP_InputField? inputField = null;
if (selectedObject.transform.GetChild(i).TryGetComponent<TMP_InputField>(out inputField))
inputFields.Add(inputField);
}
if (inputFields.Count == 0)
{
Debug.Log("TMP_InputField를 찾을 수 없습니다.");
return;
}
if (inputFields.Count == 1)
{
Debug.Log("TMP_InputField가 하나밖에 없습니다.");
return;
}
// 찾은 입력필드가 두개인경우
if (inputFields.Count == 2)
{
UiTabNavi? prevUiTabNavi = null, nextUiTabNavi = null;
if(inputFields[0].TryGetComponent<UiTabNavi>(out prevUiTabNavi) == false)
prevUiTabNavi = inputFields[0].gameObject.AddComponent<UiTabNavi>();
if(inputFields[1].TryGetComponent<UiTabNavi>(out nextUiTabNavi) == false)
nextUiTabNavi = inputFields[1].gameObject.AddComponent<UiTabNavi>();
prevUiTabNavi.InitFromEditor(null, inputFields[1]);
nextUiTabNavi.InitFromEditor(inputFields[0], null);
}
for (int i = 0; i < inputFields.Count; ++i)
{
UiTabNavi? uiTabNavi = null;
if (inputFields[i].TryGetComponent<UiTabNavi>(out uiTabNavi) == false)
uiTabNavi = inputFields[i].gameObject.AddComponent<UiTabNavi>();
uiTabNavi.InitFromEditor(i == 0 ? null : inputFields[i - 1], i == inputFields.Count - 1 ? null : inputFields[i + 1]);
}
}
}
#endif
[MenuItem("Tools/Bonnate/Ui Tab Navigation Simple Setting")]
- Tools에서 이 스크립트를 호출할 도구를 찾을 수 있습니다.
if (inputFields.Count == 0)
{
Debug.Log("TMP_InputField를 찾을 수 없습니다.");
return;
}
if (inputFields.Count == 1)
{
Debug.Log("TMP_InputField가 하나밖에 없습니다.");
return;
}
- 현재 선택한 오브젝트의 자식 오브젝트가 한개 이하인경우, 이 기능을 사용할 수 없기에 로그를 찍으며 리턴합니다.
✅ 사용 방법
- 자동 탭 네비게이션을 사용하기위해서는 입력필드(TMP_INPUTFIELD)가 있는 오브젝트의 부모 오브젝트를 선택합니다.
- Tools/Bonnate/Ui Tab Navigation Simple Setting(기본값)을 선택합니다.
- 입력필드 오브젝트를 클릭하면 UiTabNavi 스크립트가 추가되어 자동으로 값이 설정되어 있습니다.
🕹️ Unity Affiliate
- Unity Affiliate Program 파트너로서 아래의 배너를 통해 접속하신 경우 수수료를 받을 수 있습니다.
- 아래 배너의 에셋들은 '실시간 무료 에셋 랭킹'을 나타냅니다.
'unity tools & functions' 카테고리의 다른 글
[유니티] 유니티 에디터 스크린샷 캡쳐 도구 (0) | 2023.06.28 |
---|---|
[유니티] Hex를 RGB Color로 변환 (0) | 2023.06.27 |
[유니티] 오브젝트 위치 자동 정렬 (0) | 2023.05.09 |
[유니티] 파일 암호화 (json 암호화) (0) | 2023.04.03 |
[유니티] UI 창 드래그 (0) | 2023.04.03 |