게임에서 키 설정은 플레이어의 선호와 스타일에 맞춰 게임을 개인화하고, 플레이어의 편의성과 게임의 즐거움을 높이기 위해 필요합니다. 이를 위해 키 설정 시스템을 구현하고 이를 정리하였습니다.
✅ 구현
- 키 설정 데이터를 관리하고 할당 여부를 검사하기위한 KeyManager와 키 설정을 하기위한 Ui슬롯 오브젝트를 관리하는 KeySettingController 두개의 클래스로 구현합니다.
· KeyManager
- 키 설정 데이터를 관리하고 할당 여부를 검사하기위한 KeyManager 입니다.
더보기
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using Newtonsoft.Json;
using System.Text;
[System.Serializable]
public class KeyData
{
//해당 키의 사용처(이름)
public string keyName;
//유니티에서 제공하는 KeyCode 값들
//https://gist.github.com/Extremelyd1/4bcd495e21453ed9e1dffa27f6ba5f69
public KeyCode keyCode; //json형태로 저장이 될 때는 KeyCode.I 가 아니라 106(숫자)로 저장이 된다. (enum)
//KeyData 생성자
public KeyData(string keyName, KeyCode keyCode)
{
this.keyName = keyName;
this.keyCode = keyCode;
}
}
/// <summary>
/// 키 입력에 대한 정보를 가지고있고, 특정한 기능에 대응하는 키를 관리하는 매니저 클래스
/// </summary>
public class KeyManager : Singleton<KeyManager>
{
private static string mOptionDataFileName = "/KeyData.json"; //키 데이터 파일 이름
private static string mFilePath;
private Dictionary<string, KeyCode> mKeyDictionary;
void Awake()
{
mKeyDictionary = new Dictionary<string, KeyCode>();
mFilePath = Application.persistentDataPath + mOptionDataFileName;
LoadOptionData();
}
private void LoadOptionData()
{
// 저장된 게임이 있다면
if (File.Exists(mFilePath))
{
string fromJsonData = File.ReadAllText(mFilePath);
List<KeyData> keyList = JsonConvert.DeserializeObject<List<KeyData>>(fromJsonData);
foreach (var data in keyList)
{
mKeyDictionary.Add(data.keyName, data.keyCode);
}
}
// 저장된 게임이 없다면
else
{
Debug.Log(GetType() + " 파일이 없음");
ResetOptionData();
}
}
/// <summary>
/// 프로젝트마다 별도로 해당 게임의 컨셉에 맞게 키를 설정한다.
/// 스크립트에서 지정한 키로 재설정된다.
/// </summary>
private void ResetOptionData()
{
mKeyDictionary.Clear();
//씬 내에서 사용할 키 데이터들//
mKeyDictionary.Add("Inventory", KeyCode.I); //아이템 인벤토리
mKeyDictionary.Add("Equipment", KeyCode.O); //장비 인벤토리
mKeyDictionary.Add("Stat", KeyCode.P); //스탯
mKeyDictionary.Add("Skill", KeyCode.K); //스킬
mKeyDictionary.Add("Quest", KeyCode.Q); //퀘스트
mKeyDictionary.Add("ItemQuickSlot0", KeyCode.Alpha1); //아이템 퀵슬롯 1번
mKeyDictionary.Add("ItemQuickSlot1", KeyCode.Alpha2); //아이템 퀵슬롯 2번
mKeyDictionary.Add("ItemQuickSlot2", KeyCode.Alpha3); //아이템 퀵슬롯 3번
mKeyDictionary.Add("ItemQuickSlot3", KeyCode.Alpha4); //아이템 퀵슬롯 4번
mKeyDictionary.Add("ItemQuickSlot4", KeyCode.Alpha5); //아이템 퀵슬롯 5번
mKeyDictionary.Add("SkillQuickSlot0", KeyCode.Z); //스킬 퀵슬롯 1번
mKeyDictionary.Add("SkillQuickSlot1", KeyCode.X); //스킬 퀵슬롯 2번
mKeyDictionary.Add("SkillQuickSlot2", KeyCode.C); //스킬 퀵슬롯 3번
mKeyDictionary.Add("SkillQuickSlot3", KeyCode.V); //스킬 퀵슬롯 4번
mKeyDictionary.Add("SkillQuickSlot4", KeyCode.B); //스킬 퀵슬롯 5번
Debug.Log(GetType() + " 초기화");
SaveOptionData();
}
public void SaveOptionData()
{
//딕셔너리에 있는 키 데이터들을 오브젝트 리스트를 이용하여 태그를 만들어서 직렬화시킨다.
//리스트를 사용하지 않고 딕셔너리만 직렬화하면 태그가 없기에 사용할 수 없다. 오브젝트 형태(KeyData)로 만들고, Object type의 json 파일로 만들었다.
//https://www.geeksforgeeks.org/json-data-types/#:~:text=JSON%20(JavaScript%20Object%20Notation)%20is,easy%20to%20understand%20and%20generate.
//KeyData를 오브젝트로 담을 리스트
List<KeyData> keys = new List<KeyData>();
//모든 딕셔너리에 있는 키 값을 리스트에 넣어준다.
foreach (KeyValuePair<string, KeyCode> keyName in mKeyDictionary)
{
keys.Add(new KeyData(keyName.Key, keyName.Value));
}
//List<KeyData>를 SeriaizeObject를 하면 Object type json이 나온다.
string jsonData = JsonConvert.SerializeObject(keys);
//파일로 쓰기
FileStream fileStream = new FileStream(mFilePath, FileMode.Create);
byte[] data = Encoding.UTF8.GetBytes(jsonData);
fileStream.Write(data, 0, data.Length);
fileStream.Close();
Debug.Log(GetType() + " 파일 쓰기");
}
/// <summary>
/// 키 이름을 기반으로 해당 키에 등록된 KeyCode를 리턴한다.
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
public KeyCode GetKeyCode(string keyName)
{
return mKeyDictionary[keyName];
}
/// <summary>
/// 해당 키에서 자기 자신을 제외한 키가 등록되어있는경우를 방지하고, 특정한 키 설정을 방지하기위해 키를 체크한다.
/// </summary>
/// <returns>할당 가능한 키인가?</returns>
public bool CheckKey(KeyCode key, KeyCode currentKey)
{
//예외1. 현재 할당된 키에 같은 키로 설정하도록 한 경우는 허용으로 리턴한다.
if(currentKey == key) { return true; }
//1차 키 검사.
//키는 아래의 키만 허용한다.
if
(
key >= KeyCode.A && key <= KeyCode.Z || //97 ~ 122 A~Z
key >= KeyCode.Alpha0 && key <= KeyCode.Alpha9 || //48 ~ 57 알파 0~9
key == KeyCode.Quote || //39
key == KeyCode.Comma || //44
key == KeyCode.Period || //46
key == KeyCode.Slash || //47
key == KeyCode.Semicolon || //59
key == KeyCode.LeftBracket || //91
key == KeyCode.RightBracket || //93
key == KeyCode.Minus || //45
key == KeyCode.Equals || //61
key == KeyCode.BackQuote //96
) { }
else { return false; }
//2차 키 검사.
//1차 키 검사를 포함한 키 중 다음 조건문 키는 설정할 수 없다.
if
(
//이동 키 WASD
key == KeyCode.W ||
key == KeyCode.A ||
key == KeyCode.S ||
key == KeyCode.D
) { return false; }
//3차 키 검사.
//현재 설정된 키들 중 이미 할당된 키가 있는경우는 설정할 수 없다.
foreach (KeyValuePair<string, KeyCode> keyPair in mKeyDictionary)
{
if (key == keyPair.Value)
{
return false;
}
}
//모든 키 검사를 통과하면 해당 키는 설정이 가능한 키.
return true;
}
/// <summary>
/// keyName에 해당하는 키를 KeyCode인 key로 변경시킨다.
/// </summary>
/// <param name="keyCode">새로 설정하는 키의 코드값(enum)</param>
/// <param name="keyName">설정된 키(keyCode)를 keyName에 할당한다</param>
public void AssignKey(KeyCode keyCode, string keyName)
{
//딕셔너리
mKeyDictionary[keyName] = keyCode;
//키 파일을 로컬에 저장
SaveOptionData();
}
}
[System.Serializable]
public class KeyData {}
- 키 정보를 문자열 이름과 할당된 키를 매칭하는 데이터를 담습니다.
void Awake()
- Scene이 처음으로 로딩되면 실행되며 키 매니저를 사용하기위한 준비를 합니다.
- LoadOptionData()를 호출하여 옵션파일로부터 데이터를 읽어 설정을 합니다.
private void LoadOptionData()
- 파일로부터 데이터를 읽어 설정을 합니다.
- 파일이 없을경우 리턴되고 초기화를 진행합니다.
private void ResetOptionData()
- 키 설정 옵션을 초기화합니다.
- 이곳에서는 프로그램에서 사용하는 여러 기능들의 키를 지정합니다.
public void SaveOptionData()
- 키 설정 옵션 데이터를 저장합니다.
- Json 형태로 변환을 한 후에 로컬에 파일로 저장합니다.
- 파일이 저장되는 형태는 다음과 같습니다.
public bool CheckKey(KeyCode key, KeyCode currentKey)
- 가장 핵심이 되는 함수로 현재 키가 할당이 가능한지 확인합니다.
- 다음 조건을 검사합니다.
- 현재 같은 키로 설정을 시도하는가?
- 허용하는 키 범위 이내인가?
- 허용 키 범위 중 독립적으로 허용하지 않는 키 인가?
- 다른 기능이 이미 이 키를 사용중인가?
public void AssignKey(KeyCode keyCode, string keyName)
- 키를 할당하고 저장합니다.
· KeySettingController
- 키 설정을 하기위한 Ui슬롯 오브젝트를 관리하는 KeySettingController 입니다.
더보기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using System;
public class KeySettingController : MonoBehaviour
{
private KeyCode mOriginKeyCode;
[SerializeField] private string mKeyBindingName;
//키 설정 버튼
[SerializeField] private Image mKeyButtonImage; //현재 할당된 키와 그 키를 수정할 수 있게 하는 버튼의 이미지
private Coroutine mKeyButtonColorCor; //키 수정 버튼의 색상 변경을 수행하는 코루틴을 담는 변수
//버튼 텍스트
[SerializeField] private TextMeshProUGUI mKeyButtonText; //버튼의 하위 자식의 텍스트필드
private void OnEnable()
{
mOriginKeyCode = KeyManager.Instance.GetKeyCode(mKeyBindingName);
mKeyButtonText.text = ((char)mOriginKeyCode).ToString().ToUpper();
}
public void BTN_ModifyKey()
{
mKeyButtonText.text = "< >";
StartCoroutine(CorAssignKey());
}
private IEnumerator CorAssignKey()
{
while (true)
{
if (Input.anyKeyDown)
{
foreach (KeyCode kcode in Enum.GetValues(typeof(KeyCode)))
{
if (Input.GetKey(kcode))
{
// 기존의 코루틴 제거
if (mKeyButtonColorCor != null) { StopCoroutine(mKeyButtonColorCor); }
// 키 설정을 할 수 있는경우?
if (KeyManager.Instance.CheckKey(kcode, mOriginKeyCode))
{
// 키 지정
KeyManager.Instance.AssignKey(kcode, mKeyBindingName);
mOriginKeyCode = kcode;
// 키 레이블을 변경
mKeyButtonText.text = ((char)kcode).ToString().ToUpper();
// 녹색으로 설정 완료됨을 연출
mKeyButtonColorCor = StartCoroutine(CorChangeButtonColor(Color.green));
}
else
{
// 키 레이블을 변경
mKeyButtonText.text = ((char)mOriginKeyCode).ToString().ToUpper();
// 빨간색으로 설정 완료됨을 연출
mKeyButtonColorCor = StartCoroutine(CorChangeButtonColor(Color.red));
}
}
}
yield break;
}
yield return null;
}
}
private IEnumerator CorChangeButtonColor(Color targetColor, float colorSpeed = 2.0f)
{
float progress = 0;
//targetColor로 변경
while (true)
{
mKeyButtonImage.color = Color.Lerp(mKeyButtonImage.color, targetColor, progress);
progress += colorSpeed * Time.deltaTime;
//progress가 1이면 > 보간 완료
if (progress > 1)
{
progress = 0;
//targetColor에서 다시 돌아오기
while (true)
{
mKeyButtonImage.color = Color.Lerp(mKeyButtonImage.color, Color.white, progress);
progress += colorSpeed * Time.deltaTime;
//색상 전환 완료
if (progress > 1)
{
yield break;
}
yield return null;
}
}
yield return null;
}
}
}
private void OnEnable()
{
mOriginKeyCode = KeyManager.Instance.GetKeyCode(mKeyBindingName);
mKeyButtonText.text = ((char)mOriginKeyCode).ToString().ToUpper();
}
- 키설정 옵션이 활성화되는경우 현재 슬롯에 맞는 키를 가져오고 텍스트로 표시합니다.
public void BTN_ModifyKey()
{
mKeyButtonText.text = "< >";
StartCoroutine(CorAssignKey());
}
- Ui로부터 호출되며 해당 슬롯에 키 설정을 시도합니다.
private IEnumerator CorAssignKey()
- 코루틴을 이용하여 키 입력을 기다리고, 키를 입력받은경우 키 유효성 검사를합니다.
- KeyManager.Instance.CheckKey(kcode, mOriginKeyCode)를 통해 현재 입력한 키가 설정 가능한 키인지 확인 후 설정이 가능하다면 해당 키로 슬롯에 지정된 키를 입력된 키로 변경합니다.
✅ UI 세팅
- 하나의 슬롯이 하나의 키를 설정하도록 구현합니다.
- 우측의 'I'라고 적혀있는 버튼은 키를 설정하기위한 버튼입니다.
- 아래의 그림과 같이 하나의 슬롯에 Key Setting Controller 스크립트를 넣고, 설정을 해줍니다.
- 버튼 Ui인 BTN_Assign Key 컴포넌트에 함수를 추가합니다.
- 전체적인 디자인은 다음과같습니다.
✅ 사용 방법
private void TryOpenInventory()
{
//옵션이 켜져있는경우 비활성화
if (GameMenuManager.IsOptionActive) { return; }
if (Input.GetKeyDown(KeyManager.Instance.GetKeyCode("Inventory")))
{
if (!IsInventoryActive)
OpenInventory();
else
CloseInventory();
ItemDescription.Instance.DisableToolTip();
}
}
- 인벤토리를 열기위해 키를 확인하는 코드입니다.
- Input.GetKeyDown을 그대로 사용하되, 인자를 KeyManager.Instance.GeyKeyCode("문자열")을 이용하여 키 이름에 해당하는 값의 KeyCode를 리턴하여 해당 키가 눌렸는지 확인합니다.
✅ 예시
- 처음에 인벤토리는 i키를 이용하여 열린것을 볼 수 있습니다.
- 키 설정 슬롯에서 i를 8로 설정을 시도했으나, 8은 이미 다른곳에 할당되어있어 불가능하여 붉은색으로 나타난것을 볼 수 있습니다.
- 후에 7로 설정을 완료한 후 i를 누르면 더이상 인벤토리창이 열리지 않고 7을 눌러야 창이 열리는것을 볼 수 있습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 미니맵 아이콘 (0) | 2023.03.25 |
---|---|
[유니티] 미니맵 (0) | 2023.03.25 |
[유니티] 스탯 시스템(2) - 플레이어 스탯관리 (0) | 2023.03.24 |
[유니티] 스탯 시스템(1) - 디자인 (0) | 2023.03.24 |
[유니티] 레벨, 경험치 시스템 (0) | 2023.03.24 |