최종프로젝트 4주차 - 키 리바인딩
<오늘 작업> 7/18
//public enum InputIndex
//{
// Q,
// E,
// Space,
// LeftClick,
// RightClick,
//}
public enum InputIndex
{
Skill1,
Skill2,
Skill3,
Skill4,
Interact,
Escape,
Potion1,
Potion2
}
먼저 기존 enum을 바꿔주었다. 이제 직관적으로 어떤 것의 키를 바꿀 것인지 볼 수 있게 되었다.
SaveManager
//키 설정
public PlayerInputActions inputActions { get; private set; }
public Dictionary<InputIndex, string> keyBindings = new();
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
path = Path.Combine(Application.persistentDataPath, "saves.json");
//BuildDataMap();
LoadAll();
//InputActions 생성 및 활성화
inputActions = new PlayerInputActions();
inputActions.Enable();
//기본 바인딩 읽어서 딕셔너리에 채우기
foreach (InputIndex idx in Enum.GetValues(typeof(InputIndex)))
{
var act = GetActionFor(idx);
if (act != null && act.bindings.Count > 0)
keyBindings[idx] = act.bindings[0].effectivePath;
}
}
public void ApplyKeyBindings()
{
foreach (var kv in keyBindings)
{
var act = GetActionFor(kv.Key);
if (act != null)
act.ApplyBindingOverride(0, kv.Value);
}
}
// InputIndex 에 대응하는 InputAction 추출
public InputAction GetActionFor(InputIndex idx)
{
var p = inputActions.Player;
switch (idx)
{
case InputIndex.Skill1: return p.Use_Skill_Q;
case InputIndex.Skill2: return p.Use_Skill_E;
case InputIndex.Skill3: return p.Use_Skill_Space;
case InputIndex.Skill4: return p.Use_Skill_LeftClick;
case InputIndex.Potion1: return p.Use_Potion_1;
case InputIndex.Potion2: return p.Use_Potion_2;
case InputIndex.Interact: return p.NPCInteration;
case InputIndex.Escape: return p.Paused;
default: return null;
}
}
먼저
public PlayerInputActions inputActions { get; private set; }
public Dictionary<InputIndex, string> keyBindings = new();
이 코드를 추가해
InputAction을 코드로 다루기 위한 인스턴스를 보관InputIndex에 대응하는 바인딩 경로를 런타임에 저장, 관리하기 위해 keyBindings를 선언GetActionFor에서 inputindex에 대응하는 inputAction을 추출하였다. 실제 사용하는 action의 이름을 적어야한다. 앞으로 상호작용 키가 추가되면 여기에 또 넣어주면 된다. 키를 바꿀 필요가 없는 I(인벤토리)는 넣지 않았다.
ApplykeyBindings는 keyBindings 딕셔너리에 저장된 모든 경로를 액션의 첫 번째 바인딩 인덱스(0번)에 오버라이드로 적용한다. 이 메서드를 호출하면 런타임에서 누르는 키가 즉시 변경된 바인딩 대로 입력을 받아 처리하도록 해준다.
그리고 Awake에서 Enable을 호출해 키 입력을 받을 준비를 마치고 foreach문에서 에디터에 설정된 기본 바인딩(effectivePath)를 읽어 keyBindings에 채워 넣는다. 이렇게 해야 사용자가 아직 리바인딩을 하지 않은 상태에서도 어떤 키가 할당되어 있는지 코드가 알고 있게 된다.
슬롯 프리팹에 붙여준 스크립트이다. 인스펙터 창에서 SkillIndex와 그에 맞게 Text를 수정해주어야 한다. 동작과정은 다음과 같다.
(확인 대기) 상태로 표시 private void StartRebind()
{
// 리바인드 시작 전 disable
action.Disable();
bindingText.text = "입력 대기";
rebindButton.interactable = false;
action.PerformInteractiveRebinding(0)
// 마우스 포인터 입력 제외
.WithControlsExcluding("<Mouse>/position")
.WithControlsExcluding("<Mouse>/delta")
// WASD 제외
.WithControlsExcluding("<Keyboard>/w")
.WithControlsExcluding("<Keyboard>/a")
.WithControlsExcluding("<Keyboard>/s")
.WithControlsExcluding("<Keyboard>/d")
// anyKey 제외
.WithControlsExcluding("<Keyboard>/anyKey")
.OnComplete(op => HandleRebindComplete(op))
.Start();
}
HandleRebindComplete로 제어 흐름 이동 private void RefreshDisplay()
{
var current = Bindings[slotIndex];
var toShow = pendingPath ?? current;
var human = InputControlPath.ToHumanReadableString(
toShow,
InputControlPath.HumanReadableStringOptions.OmitDevice
);
bindingText.text = pendingPath != null
? $"{human}\n확인 대기"
: human;
}
확인 대기 문구를 표시 private void HandleRebindComplete(InputActionRebindingExtensions.RebindingOperation op)
{
var newPath = action.bindings[0].effectivePath;
// 중복 검사
bool isDuplicate = Bindings
.Any(kv => kv.Key != slotIndex && kv.Value == newPath);
if (isDuplicate)
{
Debug.LogWarning("이미 사용 중인 키입니다. 다시 입력해주세요.");
op.Dispose();
action.RemoveBindingOverride(0);
action.Enable();
// 다시 리바인드 대기 상태로 진입
StartRebind();
return;
}
// 정상 후보
pendingPath = newPath;
op.Dispose();
action.Enable();
confirmButton.interactable = true;
RefreshDisplay();
}
StartRebind 재호출로 재입력을 유도pendingPath에 저장 -> 확인 대기 상태로 넘어감 private void ConfirmRebind()
{
if (pendingPath == null) return;
// Dictionary 업데이트
Bindings[slotIndex] = pendingPath;
//런타임에 바로 적용
SaveManager.Instance.ApplyKeyBindings();
// 상태 초기화
pendingPath = null;
rebindButton.interactable = true;
confirmButton.interactable = false;
RefreshDisplay();
}
keyBindings를 갱신해 slotIndex에 새 경로 저장ApplyKeyBingings를 호출해 모든 액션에 오버라이드를 반영해 즉시 바뀐 키를 적용아직 해야 할 기능은 많이 남았다. 기억나는 걸로는
1. 한번에 여러 키를 수정가능한 현상 막기
2. 확인 버튼을 누르지 않으면 계속 입력 대기 상태인 현상 막기
3. 키 설정이 저장되게 하기
4. 확인 버튼을 눌렀을 때 이미지가 달라지게 하기
정도가 되겠다.
작업을 하면서 생각했던 건 현재 스킬, 포션, 상호작용이렇게 크게 세가지가 키 입력이 되는 상태인데 다른 방식으로 코드가 작성되어 있어 통일할 필요가 있어보인다는 점이었다. 그런데 통일하느라 고칠 코드가 연계되어 너무 많아져 버린다면... 그냥 지금 동작 잘만 되는데 냅둬도 되지 않을까?