using UnityEngine; using System.Collections.Generic; using System.Linq; using Streamingle; using UnityRawInput; public class ItemController : MonoBehaviour, IController { #region Classes [System.Serializable] public class ItemGroup { [Header("Group Settings")] public string groupName = "New Item Group"; public GameObject[] itemObjects = new GameObject[0]; [Header("Hotkey Settings")] public List hotkeys = new List(); [System.NonSerialized] public bool isRecording = false; [System.NonSerialized] private List unityKeys = new List(); [System.NonSerialized] private float recordStartTime; [System.NonSerialized] private const float MAX_RECORD_TIME = 2f; public ItemGroup(string name) { groupName = name; hotkeys = new List(); } public void StartRecording() { isRecording = true; recordStartTime = Time.time; hotkeys.Clear(); } public void StopRecording() { isRecording = false; InitializeUnityKeys(); } public void UpdateRecording() { if (!isRecording) return; if (Time.time - recordStartTime > MAX_RECORD_TIME) { StopRecording(); return; } foreach (KeyCode keyCode in System.Enum.GetValues(typeof(KeyCode))) { if (Input.GetKeyDown(keyCode) && KeyMapping.TryGetRawKey(keyCode, out RawKey rawKey)) { if (!hotkeys.Contains(rawKey)) { hotkeys.Add(rawKey); } } } bool allKeysReleased = hotkeys.Any() && hotkeys.All(key => !Input.GetKey(KeyMapping.TryGetKeyCode(key, out KeyCode keyCode) ? keyCode : KeyCode.None)); if (allKeysReleased) { StopRecording(); } } public void InitializeUnityKeys() { unityKeys.Clear(); if (hotkeys == null || !hotkeys.Any()) return; foreach (var hotkey in hotkeys) { if (KeyMapping.TryGetKeyCode(hotkey, out KeyCode keyCode) && keyCode != KeyCode.None) { unityKeys.Add(keyCode); } } } public bool IsTriggered() { if (isRecording) return false; if (hotkeys == null || !hotkeys.Any()) return false; bool allHotkeysPressed = hotkeys.All(key => RawInput.IsKeyDown(key)); if (allHotkeysPressed) return true; if (unityKeys.Any()) { return unityKeys.All(key => Input.GetKey(key)); } return false; } public void SetActive(bool active) { if (itemObjects == null) return; foreach (var obj in itemObjects) { if (obj != null) { obj.SetActive(active); } } } public bool IsActive() { if (itemObjects == null || itemObjects.Length == 0) return false; // 모든 오브젝트가 활성화되어 있으면 true return itemObjects.All(obj => obj != null && obj.activeSelf); } public override string ToString() => hotkeys?.Any() == true ? $"{groupName} ({string.Join(" + ", hotkeys)})" : $"{groupName} (설정되지 않음)"; } private static class KeyMapping { private static readonly Dictionary _mapping; static KeyMapping() { _mapping = new Dictionary(RawKeySetup.KeyMapping); } public static bool TryGetRawKey(KeyCode keyCode, out RawKey rawKey) { return _mapping.TryGetValue(keyCode, out rawKey); } public static bool TryGetKeyCode(RawKey rawKey, out KeyCode keyCode) { var pair = _mapping.FirstOrDefault(x => x.Value == rawKey); keyCode = pair.Key; return keyCode != KeyCode.None; } public static bool IsValidRawKey(RawKey key) { return _mapping.ContainsValue(key); } } #endregion #region Events public delegate void ItemGroupChangedEventHandler(ItemGroup oldGroup, ItemGroup newGroup); public event ItemGroupChangedEventHandler OnItemGroupChanged; #endregion #region Fields [SerializeField] public List itemGroups = new List(); [Header("Item Control Settings")] [SerializeField] private bool autoFindGroups = true; [SerializeField] private string groupTag = "ItemGroup"; private ItemGroup currentGroup; private StreamDeckServerManager streamDeckManager; #endregion #region Properties public ItemGroup CurrentGroup => currentGroup; public int CurrentIndex => itemGroups.IndexOf(currentGroup); #endregion #region Unity Messages private void Awake() { InitializeItemGroups(); InitializeRawInput(); // StreamDeckServerManager 찾기 streamDeckManager = FindObjectOfType(); if (streamDeckManager == null) { Debug.LogWarning("[ItemController] StreamDeckServerManager를 찾을 수 없습니다. 스트림덱 연동이 비활성화됩니다."); } } private void OnDestroy() { if (RawInput.IsRunning) { RawInput.OnKeyDown -= HandleRawKeyDown; RawInput.Stop(); } } private void Update() { UpdateHotkeyRecording(); HandleHotkeys(); } #endregion #region Initialization public void InitializeItemGroups() { if (itemGroups == null) { itemGroups = new List(); } if (autoFindGroups && itemGroups.Count == 0) { // "ItemGroup" 태그가 붙은 모든 오브젝트를 찾아서 그룹으로 등록 var groupObjects = GameObject.FindGameObjectsWithTag(groupTag); foreach (var groupObj in groupObjects) { var group = new ItemGroup(groupObj.name); group.itemObjects = new GameObject[] { groupObj }; itemGroups.Add(group); } Debug.Log($"[ItemController] {itemGroups.Count}개의 아이템 그룹을 자동으로 찾았습니다."); } // 유효하지 않은 그룹 제거 itemGroups.RemoveAll(group => group == null || group.itemObjects == null || group.itemObjects.Length == 0); Debug.Log($"[ItemController] 총 {itemGroups.Count}개의 아이템 그룹이 등록되었습니다."); } private void InitializeRawInput() { if (!RawInput.IsRunning) { RawInput.Start(); RawInput.WorkInBackground = true; } // 중복 구독 방지를 위해 먼저 해제 후 구독 RawInput.OnKeyDown -= HandleRawKeyDown; RawInput.OnKeyDown += HandleRawKeyDown; } #endregion #region Hotkey Management private void UpdateHotkeyRecording() { foreach (var group in itemGroups) { if (group?.isRecording == true) { group.UpdateRecording(); } } } private void HandleRawKeyDown(RawKey key) { // 핫키 레코딩 중이면 무시 if (itemGroups.Any(g => g?.isRecording == true)) return; } private void HandleHotkeys() { foreach (var group in itemGroups) { if (group?.IsTriggered() == true) { ToggleGroup(group); Debug.Log($"[ItemController] 핫키로 그룹 토글: {group.groupName}"); } } } #endregion #region Public Methods public void Set(int index) { if (index < 0 || index >= itemGroups.Count) { Debug.LogWarning($"[ItemController] 잘못된 인덱스: {index}, 유효 범위: 0-{itemGroups.Count - 1}"); return; } var oldGroup = currentGroup; currentGroup = itemGroups[index]; // 모든 그룹 비활성화 후 현재 그룹만 활성화 foreach (var group in itemGroups) { group.SetActive(false); } currentGroup.SetActive(true); // 이벤트 발생 OnItemGroupChanged?.Invoke(oldGroup, currentGroup); // StreamDeck에 알림 NotifyItemChanged(); Debug.Log($"[ItemController] 그룹 변경: {currentGroup.groupName} (인덱스: {index})"); } public void ToggleGroup(int index) { if (index < 0 || index >= itemGroups.Count) { Debug.LogWarning($"[ItemController] 잘못된 인덱스: {index}, 유효 범위: 0-{itemGroups.Count - 1}"); return; } var group = itemGroups[index]; bool newState = !group.IsActive(); group.SetActive(newState); // StreamDeck에 알림 NotifyItemChanged(); Debug.Log($"[ItemController] 그룹 토글: {group.groupName} -> {(newState ? "활성" : "비활성")}"); } public void ToggleGroup(ItemGroup group) { if (group == null) return; int index = itemGroups.IndexOf(group); if (index >= 0) { ToggleGroup(index); } } public void ToggleCurrentGroup() { if (currentGroup != null) { ToggleGroup(CurrentIndex); } } public void ActivateAllGroups() { foreach (var group in itemGroups) { group.SetActive(true); } NotifyItemChanged(); Debug.Log("[ItemController] 모든 그룹 활성화"); } public void DeactivateAllGroups() { foreach (var group in itemGroups) { group.SetActive(false); } NotifyItemChanged(); Debug.Log("[ItemController] 모든 그룹 비활성화"); } public void AddGroup(string groupName, GameObject[] objects = null) { var newGroup = new ItemGroup(groupName); if (objects != null) { newGroup.itemObjects = objects; } itemGroups.Add(newGroup); NotifyItemChanged(); Debug.Log($"[ItemController] 그룹 추가: {groupName}"); } public void RemoveGroup(int index) { if (index < 0 || index >= itemGroups.Count) return; var removedGroup = itemGroups[index]; itemGroups.RemoveAt(index); // 현재 그룹이 제거된 경우 첫 번째 그룹으로 변경 if (removedGroup == currentGroup) { currentGroup = itemGroups.Count > 0 ? itemGroups[0] : null; } NotifyItemChanged(); Debug.Log($"[ItemController] 그룹 제거: {removedGroup.groupName}"); } public void StartRecordingHotkey(int groupIndex) { if (groupIndex < 0 || groupIndex >= itemGroups.Count) return; var group = itemGroups[groupIndex]; group.StartRecording(); Debug.Log($"[ItemController] 핫키 레코딩 시작: {group.groupName}"); } public void StopRecordingHotkey(int groupIndex) { if (groupIndex < 0 || groupIndex >= itemGroups.Count) return; var group = itemGroups[groupIndex]; group.StopRecording(); Debug.Log($"[ItemController] 핫키 레코딩 완료: {group.groupName} -> {group}"); } #endregion #region StreamDeck Integration private void NotifyItemChanged() { if (streamDeckManager != null) { var updateMessage = new { type = "item_changed", timestamp = System.DateTime.UtcNow.ToString("o"), version = "1.0", data = new { item_data = GetItemListData(), current_item = GetCurrentItemState() } }; string json = JsonUtility.ToJson(updateMessage); streamDeckManager.BroadcastMessage(json); Debug.Log("[ItemController] 아이템 변경 알림 전송됨"); } } public ItemListData GetItemListData() { return new ItemListData { item_count = itemGroups.Count, items = itemGroups.Select((g, i) => new ItemPresetData { index = i, name = g.groupName, isActive = g.IsActive(), hotkey = g.ToString() }).ToArray(), current_index = CurrentIndex }; } public ItemStateData GetCurrentItemState() { if (currentGroup == null) return null; return new ItemStateData { current_index = CurrentIndex, item_name = currentGroup.groupName, isActive = currentGroup.IsActive(), total_items = itemGroups.Count }; } public string GetItemListJson() { return JsonUtility.ToJson(GetItemListData()); } public string GetItemStateJson() { return JsonUtility.ToJson(GetCurrentItemState()); } #endregion #region Data Classes [System.Serializable] public class ItemPresetData { public int index; public string name; public bool isActive; public string hotkey; } [System.Serializable] public class ItemListData { public int item_count; public ItemPresetData[] items; public int current_index; } [System.Serializable] public class ItemStateData { public int current_index; public string item_name; public bool isActive; public int total_items; } #endregion #region IController Implementation public string GetControllerId() { return "item_controller"; } public string GetControllerName() { return "Item Controller"; } public object GetControllerData() { return GetItemListData(); } public void ExecuteAction(string actionId, object parameters) { switch (actionId) { case "toggle_item": { if (parameters is int toggleIndex) { ToggleGroup(toggleIndex); } } break; case "set_item": { if (parameters is int setIndex) { Set(setIndex); } } break; case "activate_all": ActivateAllGroups(); break; case "deactivate_all": DeactivateAllGroups(); break; case "start_recording_hotkey": if (parameters is int recordIndex) { StartRecordingHotkey(recordIndex); } break; case "stop_recording_hotkey": if (parameters is int stopIndex) { StopRecordingHotkey(stopIndex); } break; default: Debug.LogWarning($"[ItemController] 알 수 없는 액션: {actionId}"); break; } } #endregion }