Fix : 스트림덱 코드 추가 이벤트 컨트롤러 추가 및 자동화 세팅 기능 추가

This commit is contained in:
KINDNICK 2025-07-09 00:01:00 +09:00
parent 624e117a2a
commit cbdd8228ce
12 changed files with 1985 additions and 267 deletions

View File

@ -0,0 +1,493 @@
using UnityEngine;
using UnityEditor;
namespace Streamingle.Editor
{
public class StreamingleControllerSetupToolAdvanced : EditorWindow
{
private bool createCameraManager = true;
private bool createItemController = true;
private bool createEventController = true;
private bool createStreamDeckManager = true;
private string parentObjectName = "Streamingle 컨트롤러들";
private bool createParentObject = true;
// 고급 옵션
private bool autoConnectExistingControllers = true;
private bool setupDefaultSettings = true;
private bool createSampleData = false;
private bool moveExistingControllersToParent = true;
// 기존 컨트롤러 참조
private StreamDeckServerManager existingStreamDeckManager;
private CameraManager existingCameraManager;
private ItemController existingItemController;
private EventController existingEventController;
[MenuItem("Tools/Streamingle/고급 컨트롤러 설정 도구")]
public static void ShowWindow()
{
GetWindow<StreamingleControllerSetupToolAdvanced>("고급 Streamingle 설정");
}
private void OnEnable()
{
FindExistingControllers();
}
private void OnGUI()
{
GUILayout.Label("고급 Streamingle 컨트롤러 설정 도구", EditorStyles.boldLabel);
GUILayout.Space(10);
// 기존 컨트롤러 상태 표시
ShowExistingControllersStatus();
GUILayout.Space(10);
// 부모 오브젝트 설정
EditorGUILayout.BeginVertical("box");
GUILayout.Label("부모 오브젝트 설정", EditorStyles.boldLabel);
createParentObject = EditorGUILayout.Toggle("부모 오브젝트 생성", createParentObject);
if (createParentObject)
{
parentObjectName = EditorGUILayout.TextField("부모 오브젝트 이름", parentObjectName);
}
EditorGUILayout.EndVertical();
GUILayout.Space(10);
// 컨트롤러 선택
EditorGUILayout.BeginVertical("box");
GUILayout.Label("생성할 컨트롤러들", EditorStyles.boldLabel);
createStreamDeckManager = EditorGUILayout.Toggle("StreamDeck 서버 매니저", createStreamDeckManager);
createCameraManager = EditorGUILayout.Toggle("카메라 매니저", createCameraManager);
createItemController = EditorGUILayout.Toggle("아이템 컨트롤러", createItemController);
createEventController = EditorGUILayout.Toggle("이벤트 컨트롤러", createEventController);
EditorGUILayout.EndVertical();
GUILayout.Space(10);
// 고급 옵션
EditorGUILayout.BeginVertical("box");
GUILayout.Label("고급 옵션", EditorStyles.boldLabel);
autoConnectExistingControllers = EditorGUILayout.Toggle("기존 컨트롤러 자동 연결", autoConnectExistingControllers);
setupDefaultSettings = EditorGUILayout.Toggle("기본 설정 구성", setupDefaultSettings);
createSampleData = EditorGUILayout.Toggle("샘플 데이터 생성", createSampleData);
moveExistingControllersToParent = EditorGUILayout.Toggle("기존 컨트롤러를 부모 하위로 이동", moveExistingControllersToParent);
EditorGUILayout.EndVertical();
GUILayout.Space(20);
// 버튼들
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("선택된 컨트롤러들 생성", GUILayout.Height(30)))
{
CreateControllers();
}
if (GUILayout.Button("기존 컨트롤러들 연결", GUILayout.Height(30)))
{
ConnectExistingControllers();
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("컨트롤러 상태 새로고침", GUILayout.Height(25)))
{
FindExistingControllers();
}
if (GUILayout.Button("기존 컨트롤러들을 부모 하위로 이동", GUILayout.Height(25)))
{
MoveExistingControllersToParent();
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(10);
// 정보 표시
EditorGUILayout.HelpBox(
"자동 감지 및 연결 기능이 포함된 고급 설정 도구입니다.\n" +
"'기존 컨트롤러들 연결'을 사용하여 찾은 컨트롤러들을 StreamDeck 매니저에 연결하세요.\n" +
"'기존 컨트롤러들을 부모 하위로 이동'을 사용하여 발견된 컨트롤러들을 정리하세요.",
MessageType.Info
);
}
private void ShowExistingControllersStatus()
{
EditorGUILayout.BeginVertical("box");
GUILayout.Label("기존 컨트롤러 상태", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("StreamDeck 서버 매니저:", GUILayout.Width(200));
if (existingStreamDeckManager != null)
{
string parentInfo = GetParentInfo(existingStreamDeckManager.transform);
EditorGUILayout.LabelField($"✓ 발견됨 {parentInfo}", EditorStyles.boldLabel);
}
else
{
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("카메라 매니저:", GUILayout.Width(200));
if (existingCameraManager != null)
{
string parentInfo = GetParentInfo(existingCameraManager.transform);
EditorGUILayout.LabelField($"✓ 발견됨 {parentInfo}", EditorStyles.boldLabel);
}
else
{
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("아이템 컨트롤러:", GUILayout.Width(200));
if (existingItemController != null)
{
string parentInfo = GetParentInfo(existingItemController.transform);
EditorGUILayout.LabelField($"✓ 발견됨 {parentInfo}", EditorStyles.boldLabel);
}
else
{
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
GUILayout.Label("이벤트 컨트롤러:", GUILayout.Width(200));
if (existingEventController != null)
{
string parentInfo = GetParentInfo(existingEventController.transform);
EditorGUILayout.LabelField($"✓ 발견됨 {parentInfo}", EditorStyles.boldLabel);
}
else
{
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private string GetParentInfo(Transform transform)
{
if (transform.parent == null)
{
return "(루트)";
}
else
{
return $"({transform.parent.name} 하위)";
}
}
private void FindExistingControllers()
{
existingStreamDeckManager = FindObjectOfType<StreamDeckServerManager>();
existingCameraManager = FindObjectOfType<CameraManager>();
existingItemController = FindObjectOfType<ItemController>();
existingEventController = FindObjectOfType<EventController>();
}
private void CreateControllers()
{
GameObject parentObject = null;
// 부모 오브젝트 생성
if (createParentObject)
{
parentObject = new GameObject(parentObjectName);
UnityEngine.Debug.Log($"부모 오브젝트 생성됨: {parentObjectName}");
}
// StreamDeck Server Manager 생성
if (createStreamDeckManager && existingStreamDeckManager == null)
{
CreateStreamDeckManager(parentObject);
}
// Camera Manager 생성
if (createCameraManager && existingCameraManager == null)
{
CreateCameraManager(parentObject);
}
// Item Controller 생성
if (createItemController && existingItemController == null)
{
CreateItemController(parentObject);
}
// Event Controller 생성
if (createEventController && existingEventController == null)
{
CreateEventController(parentObject);
}
// 기존 컨트롤러들을 부모 하위로 이동
if (moveExistingControllersToParent && parentObject != null)
{
MoveExistingControllersToParent(parentObject);
}
// 생성된 오브젝트들을 선택
if (parentObject != null)
{
Selection.activeGameObject = parentObject;
EditorGUIUtility.PingObject(parentObject);
}
// 기존 컨트롤러 상태 업데이트
FindExistingControllers();
UnityEngine.Debug.Log("Streamingle 컨트롤러 설정 완료!");
}
private void MoveExistingControllersToParent(GameObject parent = null)
{
if (parent == null)
{
// 기존 부모 오브젝트 찾기 또는 생성
parent = GameObject.Find(parentObjectName);
if (parent == null)
{
parent = new GameObject(parentObjectName);
UnityEngine.Debug.Log($"부모 오브젝트 생성됨: {parentObjectName}");
}
}
int movedCount = 0;
// StreamDeck Server Manager 이동
if (existingStreamDeckManager != null && existingStreamDeckManager.transform.parent != parent.transform)
{
existingStreamDeckManager.transform.SetParent(parent.transform);
movedCount++;
UnityEngine.Debug.Log($"StreamDeck 서버 매니저를 {parent.name} 하위로 이동");
}
// Camera Manager 이동
if (existingCameraManager != null && existingCameraManager.transform.parent != parent.transform)
{
existingCameraManager.transform.SetParent(parent.transform);
movedCount++;
UnityEngine.Debug.Log($"카메라 매니저를 {parent.name} 하위로 이동");
}
// Item Controller 이동
if (existingItemController != null && existingItemController.transform.parent != parent.transform)
{
existingItemController.transform.SetParent(parent.transform);
movedCount++;
UnityEngine.Debug.Log($"아이템 컨트롤러를 {parent.name} 하위로 이동");
}
// Event Controller 이동
if (existingEventController != null && existingEventController.transform.parent != parent.transform)
{
existingEventController.transform.SetParent(parent.transform);
movedCount++;
UnityEngine.Debug.Log($"이벤트 컨트롤러를 {parent.name} 하위로 이동");
}
if (movedCount > 0)
{
UnityEngine.Debug.Log($"{movedCount}개의 컨트롤러를 {parent.name} 하위로 이동했습니다.");
// 부모 오브젝트 선택
Selection.activeGameObject = parent;
EditorGUIUtility.PingObject(parent);
}
else
{
UnityEngine.Debug.Log("이동할 컨트롤러가 없습니다.");
}
}
private void ConnectExistingControllers()
{
if (existingStreamDeckManager == null)
{
UnityEngine.Debug.LogError("StreamDeck 서버 매니저를 찾을 수 없습니다! 먼저 생성해주세요.");
return;
}
// StreamDeck Manager에 컨트롤러들 연결
SerializedObject serializedObject = new SerializedObject(existingStreamDeckManager);
serializedObject.Update();
// Camera Manager 연결
if (existingCameraManager != null)
{
var cameraManagerProperty = serializedObject.FindProperty("cameraManager");
if (cameraManagerProperty != null)
{
cameraManagerProperty.objectReferenceValue = existingCameraManager;
}
}
// Item Controller 연결
if (existingItemController != null)
{
var itemControllerProperty = serializedObject.FindProperty("itemController");
if (itemControllerProperty != null)
{
itemControllerProperty.objectReferenceValue = existingItemController;
}
}
// Event Controller 연결
if (existingEventController != null)
{
var eventControllerProperty = serializedObject.FindProperty("eventController");
if (eventControllerProperty != null)
{
eventControllerProperty.objectReferenceValue = existingEventController;
}
}
serializedObject.ApplyModifiedProperties();
UnityEngine.Debug.Log("기존 컨트롤러들을 StreamDeck 서버 매니저에 연결했습니다!");
}
private void CreateStreamDeckManager(GameObject parent)
{
GameObject streamDeckObject = new GameObject("StreamDeck 서버 매니저");
if (parent != null)
{
streamDeckObject.transform.SetParent(parent.transform);
}
// StreamDeckServerManager 스크립트 추가
var streamDeckManager = streamDeckObject.AddComponent<StreamDeckServerManager>();
// 기본 설정
SerializedObject serializedObject = new SerializedObject(streamDeckManager);
serializedObject.Update();
// 포트 설정 (기본값: 10701)
var portProperty = serializedObject.FindProperty("port");
if (portProperty != null)
{
portProperty.intValue = 10701;
}
serializedObject.ApplyModifiedProperties();
UnityEngine.Debug.Log("StreamDeck 서버 매니저 생성됨");
}
private void CreateCameraManager(GameObject parent)
{
GameObject cameraObject = new GameObject("카메라 매니저");
if (parent != null)
{
cameraObject.transform.SetParent(parent.transform);
}
// CameraManager 스크립트 추가
var cameraManager = cameraObject.AddComponent<CameraManager>();
// 기본 설정
SerializedObject serializedObject = new SerializedObject(cameraManager);
serializedObject.Update();
// 카메라 프리셋 리스트 초기화
var cameraPresetsProperty = serializedObject.FindProperty("cameraPresets");
if (cameraPresetsProperty != null)
{
cameraPresetsProperty.ClearArray();
cameraPresetsProperty.arraySize = 0;
}
serializedObject.ApplyModifiedProperties();
UnityEngine.Debug.Log("카메라 매니저 생성됨");
}
private void CreateItemController(GameObject parent)
{
GameObject itemObject = new GameObject("아이템 컨트롤러");
if (parent != null)
{
itemObject.transform.SetParent(parent.transform);
}
// ItemController 스크립트 추가
var itemController = itemObject.AddComponent<ItemController>();
// 기본 설정
SerializedObject serializedObject = new SerializedObject(itemController);
serializedObject.Update();
// 아이템 그룹 리스트 초기화
var itemGroupsProperty = serializedObject.FindProperty("itemGroups");
if (itemGroupsProperty != null)
{
itemGroupsProperty.ClearArray();
itemGroupsProperty.arraySize = 0;
}
serializedObject.ApplyModifiedProperties();
UnityEngine.Debug.Log("아이템 컨트롤러 생성됨");
}
private void CreateEventController(GameObject parent)
{
GameObject eventObject = new GameObject("이벤트 컨트롤러");
if (parent != null)
{
eventObject.transform.SetParent(parent.transform);
}
// EventController 스크립트 추가
var eventController = eventObject.AddComponent<EventController>();
// 기본 설정
SerializedObject serializedObject = new SerializedObject(eventController);
serializedObject.Update();
// 이벤트 그룹 리스트 초기화
var eventGroupsProperty = serializedObject.FindProperty("eventGroups");
if (eventGroupsProperty != null)
{
eventGroupsProperty.ClearArray();
eventGroupsProperty.arraySize = 0;
}
// 자동 찾기 비활성화
var autoFindEventsProperty = serializedObject.FindProperty("autoFindEvents");
if (autoFindEventsProperty != null)
{
autoFindEventsProperty.boolValue = false;
}
serializedObject.ApplyModifiedProperties();
UnityEngine.Debug.Log("이벤트 컨트롤러 생성됨");
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 84a4d570d6f8faa44bafc90ed4780108

View File

@ -14,6 +14,7 @@ public class StreamDeckServerManager : MonoBehaviour
private List<StreamDeckService> connectedClients = new List<StreamDeckService>();
public CameraManager cameraManager { get; private set; }
public ItemController itemController { get; private set; }
public EventController eventController { get; private set; }
// 싱글톤 패턴으로 StreamDeckService에서 접근 가능하도록
public static StreamDeckServerManager Instance { get; private set; }
@ -44,6 +45,13 @@ public class StreamDeckServerManager : MonoBehaviour
Debug.LogWarning("[StreamDeckServerManager] ItemController를 찾을 수 없습니다. 아이템 컨트롤 기능이 비활성화됩니다.");
}
// EventController 찾기
eventController = FindObjectOfType<EventController>();
if (eventController == null)
{
Debug.LogWarning("[StreamDeckServerManager] EventController를 찾을 수 없습니다. 이벤트 컨트롤 기능이 비활성화됩니다.");
}
StartServer();
}
@ -152,7 +160,9 @@ public class StreamDeckServerManager : MonoBehaviour
camera_data = cameraManager.GetCameraListData(),
current_camera = cameraManager.GetCurrentCameraState(),
item_data = itemController?.GetItemListData(),
current_item = itemController?.GetCurrentItemState()
current_item = itemController?.GetCurrentItemState(),
event_data = eventController?.GetEventListData(),
current_event = eventController?.GetCurrentEventState()
}
};
@ -221,6 +231,28 @@ public class StreamDeckServerManager : MonoBehaviour
Debug.Log("[StreamDeckServerManager] 아이템 변경 알림 전송됨");
}
// 이벤트 변경 시 모든 클라이언트에게 알림
public void NotifyEventChanged()
{
if (eventController == null) return;
var updateMessage = new
{
type = "event_changed",
timestamp = DateTime.UtcNow.ToString("o"),
version = "1.0",
data = new
{
event_data = eventController.GetEventListData(),
current_event = eventController.GetCurrentEventState()
}
};
string json = JsonConvert.SerializeObject(updateMessage);
BroadcastMessage(json);
Debug.Log("[StreamDeckServerManager] 이벤트 변경 알림 전송됨");
}
// 메시지 처리 로직을 여기로 이동
private void ProcessMessage(string messageData, StreamDeckService service)
{
@ -250,6 +282,18 @@ public class StreamDeckServerManager : MonoBehaviour
HandleGetItemList(service);
break;
case "execute_event":
HandleExecuteEvent(message);
break;
case "set_event":
HandleSetEvent(message);
break;
case "get_event_list":
HandleGetEventList(service);
break;
case "test":
// 테스트 메시지 에코 응답
var response = new
@ -587,6 +631,202 @@ public class StreamDeckServerManager : MonoBehaviour
string json = JsonConvert.SerializeObject(response);
service.SendMessage(json);
}
private void HandleExecuteEvent(Dictionary<string, object> message)
{
Debug.Log($"[StreamDeckServerManager] 이벤트 실행 요청 수신");
if (eventController == null)
{
Debug.LogError("[StreamDeckServerManager] eventController가 null입니다!");
return;
}
try
{
if (message.ContainsKey("data"))
{
var dataObject = message["data"];
if (dataObject is Newtonsoft.Json.Linq.JObject jObject)
{
if (jObject.ContainsKey("event_index"))
{
var eventIndexToken = jObject["event_index"];
if (int.TryParse(eventIndexToken?.ToString(), out int eventIndex))
{
if (eventIndex >= 0 && eventIndex < (eventController.eventGroups?.Count ?? 0))
{
Debug.Log($"[StreamDeckServerManager] 이벤트 {eventIndex}번 실행");
eventController.ExecuteEvent(eventIndex);
}
else
{
Debug.LogError($"[StreamDeckServerManager] 잘못된 이벤트 인덱스: {eventIndex}, 유효 범위: 0-{(eventController.eventGroups?.Count ?? 0) - 1}");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] event_index 파싱 실패: {eventIndexToken}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] data에 'event_index' 키가 없습니다");
}
}
else if (dataObject is Dictionary<string, object> data)
{
if (data.ContainsKey("event_index"))
{
var eventIndexObj = data["event_index"];
if (int.TryParse(eventIndexObj?.ToString(), out int eventIndex))
{
if (eventIndex >= 0 && eventIndex < (eventController.eventGroups?.Count ?? 0))
{
Debug.Log($"[StreamDeckServerManager] 이벤트 {eventIndex}번 실행");
eventController.ExecuteEvent(eventIndex);
}
else
{
Debug.LogError($"[StreamDeckServerManager] 잘못된 이벤트 인덱스: {eventIndex}, 유효 범위: 0-{(eventController.eventGroups?.Count ?? 0) - 1}");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] event_index 파싱 실패: {eventIndexObj}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] data에 'event_index' 키가 없습니다");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] data가 예상된 타입이 아닙니다: {dataObject?.GetType().Name}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] 메시지에 'data' 키가 없습니다");
}
}
catch (Exception ex)
{
Debug.LogError($"[StreamDeckServerManager] 이벤트 실행 실패: {ex.Message}");
}
}
private void HandleSetEvent(Dictionary<string, object> message)
{
Debug.Log($"[StreamDeckServerManager] 이벤트 설정 요청 수신");
if (eventController == null)
{
Debug.LogError("[StreamDeckServerManager] eventController가 null입니다!");
return;
}
try
{
if (message.ContainsKey("data"))
{
var dataObject = message["data"];
if (dataObject is Newtonsoft.Json.Linq.JObject jObject)
{
if (jObject.ContainsKey("event_index"))
{
var eventIndexToken = jObject["event_index"];
if (int.TryParse(eventIndexToken?.ToString(), out int eventIndex))
{
if (eventIndex >= 0 && eventIndex < (eventController.eventGroups?.Count ?? 0))
{
Debug.Log($"[StreamDeckServerManager] 이벤트 {eventIndex}번으로 설정");
eventController.Set(eventIndex);
}
else
{
Debug.LogError($"[StreamDeckServerManager] 잘못된 이벤트 인덱스: {eventIndex}, 유효 범위: 0-{(eventController.eventGroups?.Count ?? 0) - 1}");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] event_index 파싱 실패: {eventIndexToken}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] data에 'event_index' 키가 없습니다");
}
}
else if (dataObject is Dictionary<string, object> data)
{
if (data.ContainsKey("event_index"))
{
var eventIndexObj = data["event_index"];
if (int.TryParse(eventIndexObj?.ToString(), out int eventIndex))
{
if (eventIndex >= 0 && eventIndex < (eventController.eventGroups?.Count ?? 0))
{
Debug.Log($"[StreamDeckServerManager] 이벤트 {eventIndex}번으로 설정");
eventController.Set(eventIndex);
}
else
{
Debug.LogError($"[StreamDeckServerManager] 잘못된 이벤트 인덱스: {eventIndex}, 유효 범위: 0-{(eventController.eventGroups?.Count ?? 0) - 1}");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] event_index 파싱 실패: {eventIndexObj}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] data에 'event_index' 키가 없습니다");
}
}
else
{
Debug.LogError($"[StreamDeckServerManager] data가 예상된 타입이 아닙니다: {dataObject?.GetType().Name}");
}
}
else
{
Debug.LogError("[StreamDeckServerManager] 메시지에 'data' 키가 없습니다");
}
}
catch (Exception ex)
{
Debug.LogError($"[StreamDeckServerManager] 이벤트 설정 실패: {ex.Message}");
}
}
private void HandleGetEventList(StreamDeckService service)
{
if (eventController == null) return;
var response = new
{
type = "event_list_response",
timestamp = DateTime.UtcNow.ToString("o"),
version = "1.0",
data = new
{
event_data = eventController.GetEventListData(),
current_event = eventController.GetCurrentEventState()
}
};
string json = JsonConvert.SerializeObject(response);
service.SendMessage(json);
}
}
public class StreamDeckService : WebSocketBehavior

View File

@ -1,259 +0,0 @@
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(ItemController))]
public class ItemControllerEditor : Editor
{
private ItemController itemController;
private bool showGroupSettings = true;
private bool showGroupList = true;
private bool showTestActions = true;
private void OnEnable()
{
itemController = (ItemController)target;
}
public override void OnInspectorGUI()
{
DrawDefaultInspector();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Item Controller Tools", EditorStyles.boldLabel);
// 현재 상태 표시
DrawCurrentState();
// 그룹 설정 섹션
showGroupSettings = EditorGUILayout.Foldout(showGroupSettings, "Group Management");
if (showGroupSettings)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Quick Group Actions", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Add Selected Objects as New Group"))
{
AddSelectedObjectsAsGroup();
}
if (GUILayout.Button("Refresh Item Groups"))
{
itemController.InitializeItemGroups();
}
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("Clear All Groups"))
{
if (EditorUtility.DisplayDialog("Clear Groups", "모든 그룹을 삭제하시겠습니까?", "Yes", "No"))
{
itemController.itemGroups.Clear();
Debug.Log("[ItemControllerEditor] 모든 그룹이 삭제되었습니다.");
}
}
EditorGUILayout.EndVertical();
}
// 그룹 리스트 섹션
showGroupList = EditorGUILayout.Foldout(showGroupList, "Current Group List");
if (showGroupList)
{
EditorGUILayout.BeginVertical("box");
if (itemController.itemGroups != null && itemController.itemGroups.Count > 0)
{
for (int i = 0; i < itemController.itemGroups.Count; i++)
{
var group = itemController.itemGroups[i];
if (group != null)
{
DrawGroupItem(i, group);
}
}
}
else
{
EditorGUILayout.LabelField("No groups registered", EditorStyles.centeredGreyMiniLabel);
}
EditorGUILayout.EndVertical();
}
// 테스트 액션 섹션
showTestActions = EditorGUILayout.Foldout(showTestActions, "Test Actions");
if (showTestActions)
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Activate All Groups"))
{
itemController.ActivateAllGroups();
}
if (GUILayout.Button("Deactivate All Groups"))
{
itemController.DeactivateAllGroups();
}
EditorGUILayout.EndHorizontal();
if (itemController.itemGroups.Count > 0)
{
EditorGUILayout.LabelField("Set Active Group:", EditorStyles.miniLabel);
EditorGUILayout.BeginHorizontal();
for (int i = 0; i < Mathf.Min(3, itemController.itemGroups.Count); i++)
{
if (GUILayout.Button($"Group {i}"))
{
itemController.Set(i);
}
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
}
}
private void DrawCurrentState()
{
EditorGUILayout.BeginVertical("box");
EditorGUILayout.LabelField("Current State", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Total Groups: {itemController.itemGroups?.Count ?? 0}");
EditorGUILayout.LabelField($"Current Group: {(itemController.CurrentGroup?.groupName ?? "None")}");
EditorGUILayout.LabelField($"Current Index: {itemController.CurrentIndex}");
if (itemController.CurrentGroup != null)
{
EditorGUILayout.LabelField($"Active: {itemController.CurrentGroup.IsActive()}",
itemController.CurrentGroup.IsActive() ? EditorStyles.boldLabel : EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void DrawGroupItem(int index, ItemController.ItemGroup group)
{
EditorGUILayout.BeginVertical("box");
// 그룹 헤더
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"{index + 1}. {group.groupName}", EditorStyles.boldLabel);
// 활성화 상태 토글
bool isActive = group.IsActive();
bool newActive = EditorGUILayout.Toggle(isActive, GUILayout.Width(20));
if (newActive != isActive)
{
group.SetActive(newActive);
}
// 토글 버튼
if (GUILayout.Button("Toggle", GUILayout.Width(50)))
{
itemController.ToggleGroup(index);
}
// 핫키 레코딩 버튼
if (GUILayout.Button(group.isRecording ? "Stop" : "Record", GUILayout.Width(60)))
{
if (group.isRecording)
{
itemController.StopRecordingHotkey(index);
}
else
{
itemController.StartRecordingHotkey(index);
}
}
// 삭제 버튼
if (GUILayout.Button("X", GUILayout.Width(20)))
{
if (EditorUtility.DisplayDialog("Delete Group", $"그룹 '{group.groupName}'을 삭제하시겠습니까?", "Yes", "No"))
{
itemController.RemoveGroup(index);
return;
}
}
EditorGUILayout.EndHorizontal();
// 핫키 표시
if (group.hotkeys != null && group.hotkeys.Count > 0)
{
EditorGUILayout.LabelField($"Hotkey: {string.Join(" + ", group.hotkeys)}", EditorStyles.miniLabel);
}
else
{
EditorGUILayout.LabelField("Hotkey: 설정되지 않음", EditorStyles.miniLabel);
}
// 아이템 오브젝트들
EditorGUILayout.LabelField("Items:", EditorStyles.miniLabel);
if (group.itemObjects != null)
{
for (int j = 0; j < group.itemObjects.Length; j++)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($" {j + 1}.", GUILayout.Width(30));
GameObject obj = group.itemObjects[j];
GameObject newObj = (GameObject)EditorGUILayout.ObjectField(obj, typeof(GameObject), true);
if (newObj != obj)
{
group.itemObjects[j] = newObj;
}
// 아이템 삭제 버튼
if (GUILayout.Button("X", GUILayout.Width(20)))
{
var newArray = new GameObject[group.itemObjects.Length - 1];
int newIndex = 0;
for (int k = 0; k < group.itemObjects.Length; k++)
{
if (k != j)
{
newArray[newIndex] = group.itemObjects[k];
newIndex++;
}
}
group.itemObjects = newArray;
break;
}
EditorGUILayout.EndHorizontal();
}
// 아이템 추가 버튼
if (GUILayout.Button("Add Item"))
{
var newArray = new GameObject[group.itemObjects.Length + 1];
group.itemObjects.CopyTo(newArray, 0);
group.itemObjects = newArray;
}
}
EditorGUILayout.EndVertical();
EditorGUILayout.Space();
}
private void AddSelectedObjectsAsGroup()
{
GameObject[] selectedObjects = Selection.gameObjects;
if (selectedObjects.Length > 0)
{
string groupName = $"Group {itemController.itemGroups.Count + 1}";
itemController.AddGroup(groupName, selectedObjects);
Debug.Log($"[ItemControllerEditor] {selectedObjects.Length}개의 선택된 오브젝트를 새 그룹 '{groupName}'으로 추가했습니다.");
}
else
{
Debug.LogWarning("[ItemControllerEditor] 선택된 오브젝트가 없습니다.");
}
}
}

View File

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 31447d4896db6c9458ce66dae88feec8

View File

@ -0,0 +1,491 @@
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using Streamingle;
using UnityRawInput;
using UnityEngine.Events;
public class EventController : MonoBehaviour, IController
{
#region Classes
[System.Serializable]
public class EventGroup
{
[Header("Event Settings")]
public string groupName = "New Event Group";
public UnityEvent unityEvent = new UnityEvent();
[Header("Hotkey Settings")]
public List<RawKey> hotkeys = new List<RawKey>();
[System.NonSerialized] public bool isRecording = false;
[System.NonSerialized] private List<KeyCode> unityKeys = new List<KeyCode>();
[System.NonSerialized] private float recordStartTime;
[System.NonSerialized] private const float MAX_RECORD_TIME = 2f;
public EventGroup(string name)
{
groupName = name;
hotkeys = new List<RawKey>();
unityEvent = new UnityEvent();
}
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 ExecuteEvent()
{
if (unityEvent != null)
{
unityEvent.Invoke();
}
}
public override string ToString() =>
hotkeys?.Any() == true ? $"{groupName} ({string.Join(" + ", hotkeys)})" : $"{groupName} (설정되지 않음)";
}
private static class KeyMapping
{
private static readonly Dictionary<KeyCode, RawKey> _mapping;
static KeyMapping()
{
_mapping = new Dictionary<KeyCode, RawKey>(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 EventGroupExecutedEventHandler(EventGroup eventGroup);
public event EventGroupExecutedEventHandler OnEventExecuted;
#endregion
#region Fields
[SerializeField] public List<EventGroup> eventGroups = new List<EventGroup>();
[Header("Event Control Settings")]
[SerializeField] private bool autoFindEvents = false; // 태그 기능 비활성화
private EventGroup currentGroup;
private StreamDeckServerManager streamDeckManager;
#endregion
#region Properties
public EventGroup CurrentGroup => currentGroup;
public int CurrentIndex => eventGroups.IndexOf(currentGroup);
#endregion
#region Unity Messages
private void Awake()
{
InitializeEventGroups();
InitializeRawInput();
// StreamDeckServerManager 찾기
streamDeckManager = FindObjectOfType<StreamDeckServerManager>();
if (streamDeckManager == null)
{
Debug.LogWarning("[EventController] StreamDeckServerManager를 찾을 수 없습니다. 스트림덱 연동이 비활성화됩니다.");
}
}
private void OnDestroy()
{
if (RawInput.IsRunning)
{
RawInput.OnKeyDown -= HandleRawKeyDown;
RawInput.Stop();
}
}
private void Update()
{
UpdateHotkeyRecording();
HandleHotkeys();
}
#endregion
#region Initialization
public void InitializeEventGroups()
{
// 태그 기능 제거 - 수동으로 이벤트 그룹을 추가해야 함
// 첫 번째 그룹을 기본으로 설정
if (eventGroups.Count > 0 && currentGroup == null)
{
currentGroup = eventGroups[0];
}
Debug.Log($"[EventController] {eventGroups.Count}개의 이벤트 그룹이 초기화되었습니다.");
}
private void InitializeRawInput()
{
if (!RawInput.IsRunning)
{
RawInput.Start();
RawInput.OnKeyDown += HandleRawKeyDown;
}
}
private void UpdateHotkeyRecording()
{
foreach (var group in eventGroups)
{
if (group.isRecording)
{
group.UpdateRecording();
}
}
}
private void HandleRawKeyDown(RawKey key)
{
// 핫키 레코딩 중일 때는 처리하지 않음
if (eventGroups.Any(g => g.isRecording)) return;
}
private void HandleHotkeys()
{
foreach (var group in eventGroups)
{
if (group.IsTriggered())
{
ExecuteEvent(group);
}
}
}
#endregion
#region Public Methods
public void Set(int index)
{
if (index < 0 || index >= eventGroups.Count)
{
Debug.LogWarning($"[EventController] 유효하지 않은 인덱스: {index}");
return;
}
currentGroup = eventGroups[index];
Debug.Log($"[EventController] 현재 이벤트 그룹: {currentGroup.groupName}");
// StreamDeck에 알림
if (streamDeckManager != null)
{
streamDeckManager.NotifyEventChanged();
}
}
public void ExecuteEvent(int index)
{
if (index < 0 || index >= eventGroups.Count)
{
Debug.LogWarning($"[EventController] 유효하지 않은 인덱스: {index}");
return;
}
ExecuteEvent(eventGroups[index]);
}
public void ExecuteEvent(EventGroup group)
{
if (group == null)
{
Debug.LogWarning("[EventController] 이벤트 그룹이 null입니다.");
return;
}
Debug.Log($"[EventController] 이벤트 실행: {group.groupName}");
group.ExecuteEvent();
// 이벤트 발생 알림
OnEventExecuted?.Invoke(group);
// StreamDeck에 알림
if (streamDeckManager != null)
{
streamDeckManager.NotifyEventChanged();
}
}
public void ExecuteCurrentEvent()
{
if (currentGroup != null)
{
ExecuteEvent(currentGroup);
}
else
{
Debug.LogWarning("[EventController] 현재 이벤트 그룹이 설정되지 않았습니다.");
}
}
public void AddGroup(string groupName, GameObject targetObject = null)
{
var newGroup = new EventGroup(groupName);
if (targetObject != null)
{
// 대상 오브젝트에 UnityEvent 컴포넌트가 있다면 연결
var eventComponent = targetObject.GetComponent<UnityEvent>();
if (eventComponent != null)
{
newGroup.unityEvent = eventComponent;
}
}
eventGroups.Add(newGroup);
Debug.Log($"[EventController] 이벤트 그룹 추가: {groupName}");
}
public void RemoveGroup(int index)
{
if (index < 0 || index >= eventGroups.Count)
{
Debug.LogWarning($"[EventController] 유효하지 않은 인덱스: {index}");
return;
}
var groupToRemove = eventGroups[index];
eventGroups.RemoveAt(index);
if (currentGroup == groupToRemove)
{
currentGroup = eventGroups.Count > 0 ? eventGroups[0] : null;
}
Debug.Log($"[EventController] 이벤트 그룹 제거: {groupToRemove.groupName}");
}
public void StartRecordingHotkey(int groupIndex)
{
if (groupIndex < 0 || groupIndex >= eventGroups.Count)
{
Debug.LogWarning($"[EventController] 유효하지 않은 인덱스: {groupIndex}");
return;
}
eventGroups[groupIndex].StartRecording();
Debug.Log($"[EventController] 핫키 레코딩 시작: {eventGroups[groupIndex].groupName}");
}
public void StopRecordingHotkey(int groupIndex)
{
if (groupIndex < 0 || groupIndex >= eventGroups.Count)
{
Debug.LogWarning($"[EventController] 유효하지 않은 인덱스: {groupIndex}");
return;
}
eventGroups[groupIndex].StopRecording();
Debug.Log($"[EventController] 핫키 레코딩 종료: {eventGroups[groupIndex].groupName}");
}
private void NotifyEventChanged()
{
// StreamDeck에 이벤트 변경 알림
if (streamDeckManager != null)
{
streamDeckManager.NotifyEventChanged();
}
}
#endregion
#region StreamDeck Integration
public EventListData GetEventListData()
{
var eventData = new EventPresetData[eventGroups.Count];
for (int i = 0; i < eventGroups.Count; i++)
{
var group = eventGroups[i];
eventData[i] = new EventPresetData
{
index = i,
name = group.groupName,
hotkey = group.hotkeys.Any() ? string.Join(" + ", group.hotkeys) : "설정되지 않음"
};
}
return new EventListData
{
event_count = eventGroups.Count,
events = eventData,
current_index = CurrentIndex
};
}
public EventStateData GetCurrentEventState()
{
return new EventStateData
{
current_index = CurrentIndex,
event_name = currentGroup?.groupName ?? "없음",
total_events = eventGroups.Count
};
}
public string GetEventListJson()
{
return JsonUtility.ToJson(GetEventListData());
}
public string GetEventStateJson()
{
return JsonUtility.ToJson(GetCurrentEventState());
}
#endregion
#region Data Classes
[System.Serializable]
public class EventPresetData
{
public int index;
public string name;
public string hotkey;
}
[System.Serializable]
public class EventListData
{
public int event_count;
public EventPresetData[] events;
public int current_index;
}
[System.Serializable]
public class EventStateData
{
public int current_index;
public string event_name;
public int total_events;
}
#endregion
#region IController Implementation
public string GetControllerId()
{
return "event_controller";
}
public string GetControllerName()
{
return "Event Controller";
}
public object GetControllerData()
{
return GetEventListData();
}
public void ExecuteAction(string actionId, object parameters)
{
switch (actionId)
{
case "execute_event":
if (parameters is int executeIndex)
{
ExecuteEvent(executeIndex);
}
break;
case "set_event":
if (parameters is int setIndex)
{
Set(setIndex);
}
break;
default:
Debug.LogWarning($"[EventController] 알 수 없는 액션: {actionId}");
break;
}
}
#endregion
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6f1686151b323be4ba49f1ec12f88471

Binary file not shown.

Binary file not shown.

View File

@ -8,6 +8,7 @@ let unitySocket = null;
let isUnityConnected = false;
let cameraList = []; // 카메라 목록 저장
let itemList = []; // 아이템 목록 저장
let eventList = []; // 이벤트 목록 저장
const fs = require('fs');
@ -85,19 +86,26 @@ function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inAction
break;
case 'willAppear':
console.log('👀 버튼 나타남:', jsonObj.context);
console.log('🔍 Action UUID:', jsonObj.action);
let settings = jsonObj.payload?.settings || {};
console.log('⚙️ 초기 설정:', settings);
// action UUID로 actionType 결정
if (jsonObj.action === 'com.mirabox.streamingle.item') {
settings.actionType = 'item';
console.log('🎯 아이템 컨트롤러 등록:', jsonObj.context);
} else if (jsonObj.action === 'com.mirabox.streamingle.event') {
settings.actionType = 'event';
console.log('🎯 이벤트 컨트롤러 등록:', jsonObj.context);
} else {
settings.actionType = 'camera';
console.log('📹 카메라 컨트롤러 등록:', jsonObj.context);
}
console.log('🎯 최종 actionType:', settings.actionType);
buttonContexts.set(jsonObj.context, settings);
console.log('💾 설정 저장됨:', settings);
updateButtonTitle(jsonObj.context);
// Unity가 이미 연결되어 있다면 Property Inspector에 상태 전송
@ -107,6 +115,8 @@ function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inAction
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
}
sendToPropertyInspector(jsonObj.context, 'unity_connected', { connected: true }, actionUUID);
@ -122,6 +132,23 @@ function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inAction
handlePropertyInspectorMessage(jsonObj.payload, jsonObj.context, jsonObj.action);
break;
case 'setSettings':
console.log('💾 setSettings 이벤트 수신:', jsonObj.payload, 'context:', jsonObj.context);
if (jsonObj.payload && jsonObj.context) {
const currentSettings = getCurrentSettings(jsonObj.context);
console.log('📋 저장 전 설정:', currentSettings);
// actionType을 유지하면서 설정 업데이트
const updatedSettings = { ...currentSettings, ...jsonObj.payload };
setCurrentSettings(jsonObj.context, updatedSettings);
const newSettings = getCurrentSettings(jsonObj.context);
console.log('✅ 설정 저장 완료:', newSettings);
console.log('🎯 액션 타입:', newSettings.actionType);
updateButtonTitle(jsonObj.context);
}
break;
default:
console.log('❓ 알 수 없는 이벤트:', jsonObj.event);
break;
@ -170,6 +197,13 @@ function connectToUnity() {
unitySocket.send(message);
console.log('📋 아이템 목록 요청:', message);
}, 200);
// 이벤트 목록 요청
setTimeout(() => {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📋 이벤트 목록 요청:', message);
}, 300);
};
unitySocket.onmessage = function(event) {
@ -195,6 +229,8 @@ function connectToUnity() {
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
}
sendToPropertyInspector(context, 'unity_disconnected', { connected: false }, actionUUID);
}
@ -233,6 +269,9 @@ function handleButtonClick(context) {
case 'item':
handleItemAction(settings);
break;
case 'event':
handleEventAction(settings);
break;
default:
console.log('⚠️ 알 수 없는 액션 타입:', actionType);
// 기본적으로 카메라 액션으로 처리
@ -310,6 +349,44 @@ function handleItemAction(settings) {
}
}
// 이벤트 액션 처리
function handleEventAction(settings) {
let eventIndex = settings.eventIndex;
const eventAction = settings.eventAction || 'execute'; // 기본값은 execute
// 이벤트 인덱스가 설정되지 않았으면 0 사용
if (typeof eventIndex !== 'number') {
eventIndex = 0;
console.log('⚠️ 이벤트 인덱스가 설정되지 않음, 기본값 0 사용');
}
console.log('🎯 이벤트 액션:', eventAction, '인덱스:', eventIndex);
let messageType = 'execute_event';
if (eventAction === 'set') {
messageType = 'set_event';
}
// Unity에 이벤트 액션 요청
const message = JSON.stringify({
type: messageType,
data: {
event_index: eventIndex
}
});
console.log('📤 Unity에 이벤트 액션 요청 전송:', message);
console.log('🔍 Unity 연결 상태:', isUnityConnected);
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
unitySocket.send(message);
console.log('✅ 메시지 전송 완료');
} else {
console.error('❌ Unity 소켓이 연결되지 않음');
}
}
// Property Inspector 메시지 처리
function handlePropertyInspectorMessage(payload, context, actionUUID) {
const command = payload.command;
@ -341,6 +418,15 @@ function handlePropertyInspectorMessage(payload, context, actionUUID) {
currentIndex: currentItemIndex
}, actionUUID);
}
if (eventList.length > 0) {
// 현재 활성 이벤트 인덱스 찾기
const currentEventIndex = eventList.findIndex(event => event.isActive) || 0;
console.log('📡 현재 활성 이벤트 인덱스:', currentEventIndex);
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentEventIndex
}, actionUUID);
}
} else {
// Unity가 연결되지 않은 경우에도 빈 목록 전송
console.log('📡 Unity 연결 안됨 - 빈 목록 전송');
@ -352,6 +438,10 @@ function handlePropertyInspectorMessage(payload, context, actionUUID) {
items: [],
currentIndex: 0
}, actionUUID);
sendToPropertyInspector(context, 'event_list', {
events: [],
currentIndex: 0
}, actionUUID);
}
break;
case 'get_camera_list':
@ -370,6 +460,14 @@ function handlePropertyInspectorMessage(payload, context, actionUUID) {
console.log('📤 Unity에 아이템 목록 요청 전송');
}
break;
case 'get_event_list':
console.log('🎯 이벤트 목록 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📤 Unity에 이벤트 목록 요청 전송');
}
break;
case 'switch_camera':
console.log('📹 카메라 전환 요청:', payload.cameraIndex);
if (isUnityConnected && unitySocket) {
@ -403,6 +501,28 @@ function handlePropertyInspectorMessage(payload, context, actionUUID) {
console.log('📤 Unity에 아이템 설정 요청 전송');
}
break;
case 'execute_event':
console.log('🎯 이벤트 실행 요청:', payload.eventIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'execute_event',
data: { event_index: payload.eventIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 이벤트 실행 요청 전송');
}
break;
case 'set_event':
console.log('🎯 이벤트 설정 요청:', payload.eventIndex);
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({
type: 'set_event',
data: { event_index: payload.eventIndex }
});
unitySocket.send(message);
console.log('📤 Unity에 이벤트 설정 요청 전송');
}
break;
case 'refresh_camera_list':
console.log('🔄 카메라 목록 새로고침 요청');
if (isUnityConnected && unitySocket) {
@ -419,10 +539,19 @@ function handlePropertyInspectorMessage(payload, context, actionUUID) {
console.log('📤 Unity에 아이템 목록 요청 전송');
}
break;
case 'refresh_event_list':
console.log('🔄 이벤트 목록 새로고침 요청');
if (isUnityConnected && unitySocket) {
const message = JSON.stringify({ type: 'get_event_list' });
unitySocket.send(message);
console.log('📤 Unity에 이벤트 목록 요청 전송');
}
break;
case 'update_title':
console.log('🏷️ 버튼 제목 업데이트 요청');
updateButtonTitle(context);
break;
default:
console.log('❓ 알 수 없는 Property Inspector 명령:', command);
}
@ -454,6 +583,8 @@ function sendToPropertyInspector(context, event, payload, actionUUID = null) {
if (actionType === 'item') {
action = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
action = 'com.mirabox.streamingle.event';
}
}
@ -509,6 +640,18 @@ function handleUnityMessage(data) {
}
}
// 이벤트 데이터 처리
if (data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
if (Array.isArray(events)) {
eventList = events;
console.log('🎯 이벤트 목록 저장됨:', eventList.length, '개');
} else {
eventList = [];
console.log('⚠️ 이벤트 데이터가 배열이 아님:', events);
}
}
updateAllButtonTitles();
// Property Inspector들에게 Unity 연결 상태 알림
@ -528,6 +671,8 @@ function handleUnityMessage(data) {
let actionUUID = 'com.mirabox.streamingle.camera';
if (actionType === 'item') {
actionUUID = 'com.mirabox.streamingle.item';
} else if (actionType === 'event') {
actionUUID = 'com.mirabox.streamingle.event';
}
console.log('🔍 컨텍스트 분석:', context, 'Action Type:', actionType, 'Action UUID:', actionUUID);
@ -556,6 +701,17 @@ function handleUnityMessage(data) {
items: itemList,
currentIndex: currentItemIndex
}, actionUUID);
} else if (actionUUID === 'com.mirabox.streamingle.event') {
// 이벤트 컨트롤러에는 이벤트 데이터만 전송
let currentEventIndex = 0;
if (typeof data.data.event_data?.current_index === 'number' && data.data.event_data.current_index >= 0) {
currentEventIndex = data.data.event_data.current_index;
}
console.log('🎯 이벤트 컨트롤러에 이벤트 데이터 전송:', context, '이벤트 수:', eventList.length);
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentEventIndex
}, actionUUID);
}
}
console.log('✅ Property Inspector들에게 Unity 연결 알림 전송 완료');
@ -716,6 +872,76 @@ function handleUnityMessage(data) {
}
break;
case 'event_changed':
console.log('🎯 이벤트 변경 알림');
console.log('📋 이벤트 데이터 구조:', JSON.stringify(data.data, null, 2));
if (data.data && data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
console.log('🎯 추출된 이벤트 배열:', events);
console.log('🎯 이벤트 배열 타입:', Array.isArray(events) ? 'Array' : typeof events);
if (Array.isArray(events)) {
eventList = events;
console.log('🎯 이벤트 목록 업데이트됨:', eventList.length, '개');
console.log('🎯 이벤트 목록 내용:', JSON.stringify(eventList, null, 2));
updateAllButtonTitles();
// Property Inspector들에게 이벤트 목록 전송 (이벤트 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
console.log('🔍 컨텍스트 분석:', context, 'Action Type:', actionType);
if (actionType === 'event') {
console.log('🎯 이벤트 컨트롤러 발견 - Property Inspector에 데이터 전송');
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: data.data.event_data?.current_index || 0
}, 'com.mirabox.streamingle.event');
} else {
console.log('⚠️ 이벤트 컨트롤러가 아님 - Action Type:', actionType);
}
}
} else {
console.log('⚠️ 이벤트 목록 응답에서 이벤트 데이터가 배열이 아님');
console.log('📋 events:', events);
}
}
break;
case 'event_list_response':
console.log('🎯 이벤트 목록 수신');
if (data.data && data.data.event_data) {
let events = data.data.event_data.events || data.data.event_data;
if (Array.isArray(events)) {
eventList = events;
const currentIndex = data.data.current_event ?? data.data.event_data?.current_index ?? 0;
console.log('🎯 이벤트 목록 업데이트됨:', eventList.length, '개');
updateAllButtonTitles();
// Property Inspector들에게 이벤트 목록 전송 (이벤트 컨트롤러만)
for (const context of buttonContexts.keys()) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
if (actionType === 'event') {
sendToPropertyInspector(context, 'event_list', {
events: eventList,
currentIndex: currentIndex
}, 'com.mirabox.streamingle.event');
}
}
} else {
console.log('⚠️ 이벤트 목록 응답에서 이벤트 데이터가 배열이 아님');
console.log('📋 events:', events);
}
}
break;
default:
console.log('❓ 알 수 없는 Unity 메시지 타입:', data.type);
}
@ -740,6 +966,10 @@ function updateButtonTitle(context) {
let title = 'Camera';
let isActive = true; // 아이템 활성화 상태 (기본값: 활성)
console.log('🏷️ 버튼 제목 업데이트 시작 - Context:', context);
console.log('🏷️ 설정:', settings);
console.log('🏷️ 액션 타입:', actionType);
if (actionType === 'camera') {
const cameraIndex = typeof settings.cameraIndex === 'number' ? settings.cameraIndex : 0;
title = `카메라 ${cameraIndex + 1}`;
@ -787,6 +1017,25 @@ function updateButtonTitle(context) {
}
}
}
} else if (actionType === 'event') {
const eventIndex = typeof settings.eventIndex === 'number' ? settings.eventIndex : 0;
title = `이벤트 ${eventIndex + 1}`;
// 이벤트 목록에서 이름 찾기
if (eventList && eventList.length > eventIndex) {
const event = eventList[eventIndex];
if (event && (event.name || event.groupName)) {
// 이벤트 이름에서 불필요한 부분 제거하고 짧게 만들기
let eventName = event.name || event.groupName;
let shortName = eventName
.replace('Event', '')
.replace('Group', '')
.replace('_', ' ')
.substring(0, 10); // 최대 10글자
title = shortName || `이벤트 ${eventIndex + 1}`;
}
}
}
// StreamDock에 제목 업데이트 요청
@ -807,12 +1056,13 @@ function updateButtonTitle(context) {
websocket.send(JSON.stringify(message));
console.log('🏷️ 버튼 제목 업데이트:', title, '(액션 타입:', actionType, ', 활성:', isActive, ')');
// 아이템이나 카메라가 비활성화되어 있으면 아이콘을 어둡게 표시
// 아이템이나 카메라가 비활성화되어 있으면 아이콘을 어둡게 표시 (이벤트는 제외)
if ((actionType === 'item' || actionType === 'camera') && !isActive) {
setButtonState(context, false); // 비활성 상태로 설정
} else {
} else if (actionType === 'item' || actionType === 'camera') {
setButtonState(context, true); // 활성 상태로 설정
}
// 이벤트 컨트롤러는 활성/비활성 상태 변경 없음 (항상 활성)
}
// 버튼 상태 설정 (활성/비활성)
@ -825,7 +1075,7 @@ function setButtonState(context, isActive) {
const settings = getCurrentSettings(context);
const actionType = settings.actionType || 'camera';
// 아이템 컨트롤러와 카메라 컨트롤러 모두 상태 변경 적용
// 아이템 컨트롤러와 카메라 컨트롤러만 상태 변경 적용 (이벤트 컨트롤러는 제외)
if (actionType === 'item' || actionType === 'camera') {
// 방법 1: setState 이벤트 사용
const stateMessage = {

View File

@ -0,0 +1,190 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Streamingle Event Inspector</title>
<style>
body {
background: #222;
color: #fff;
font-family: Arial, sans-serif;
font-size: 13px;
margin: 0;
padding: 16px;
min-height: 300px;
}
.status {
display: flex;
align-items: center;
margin-bottom: 12px;
padding: 8px;
background: #333;
border-radius: 4px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
transition: background-color 0.3s;
}
.dot.green { background: #28a745; }
.dot.red { background: #dc3545; }
.section {
margin-bottom: 16px;
padding: 8px;
background: #333;
border-radius: 4px;
}
label {
display: block;
margin-bottom: 4px;
font-weight: bold;
color: #ddd;
}
select, input[type="checkbox"] {
margin-right: 8px;
background: #444;
color: #fff;
border: 1px solid #555;
border-radius: 3px;
padding: 4px;
}
select:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.current {
margin-top: 8px;
color: #17a2b8;
font-weight: bold;
}
button {
padding: 6px 12px;
background: #007bff;
color: #fff;
border: none;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background: #0056b3;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
background: #666;
}
.connected { color: #28a745; font-weight: bold; }
.disconnected { color: #dc3545; font-weight: bold; }
/* 로그 영역 스타일 */
.log-section {
margin-top: 16px;
border-top: 1px solid #444;
padding-top: 12px;
}
.log-toggle {
background: #555;
color: #fff;
border: none;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
font-size: 11px;
margin-bottom: 8px;
}
.log-area {
background: #111;
color: #0f0;
font-family: 'Courier New', monospace;
font-size: 11px;
padding: 8px;
border-radius: 3px;
height: 120px;
overflow-y: auto;
display: none;
border: 1px solid #333;
}
.log-area.show {
display: block;
}
/* 로딩 상태 */
.loading {
color: #ffc107;
font-style: italic;
}
/* 이벤트 목록 스타일 */
.event-list {
max-height: 150px;
overflow-y: auto;
}
</style>
</head>
<body>
<!-- 연결 상태 -->
<div class="status">
<div id="statusDot" class="dot red"></div>
<span id="connection-status" class="disconnected">Unity 연결 안됨</span>
</div>
<!-- 이벤트 그룹 선택 섹션 -->
<div class="section">
<label for="event-select">이벤트 그룹 선택</label>
<div class="event-list">
<select id="event-select" disabled>
<option value="">이벤트 그룹 목록 로딩 중...</option>
</select>
</div>
<div class="current" id="current-event">현재 이벤트 그룹: -</div>
</div>
<!-- 설정 섹션 -->
<div class="section">
<input type="checkbox" id="autoExecute" checked>
<label for="autoExecute" style="display:inline; font-weight:normal;">자동 실행</label>
</div>
<!-- 액션 섹션 -->
<div class="section">
<button id="refresh-button" disabled>이벤트 그룹 목록 새로고침</button>
<button id="execute-button" disabled>이벤트 실행</button>
</div>
<!-- 디버그 로그 섹션 -->
<div class="log-section">
<button class="log-toggle" onclick="toggleLog()">📋 디버그 로그 보기</button>
<div id="logArea" class="log-area"></div>
</div>
<script>
// 로그 토글 함수
function toggleLog() {
const logArea = document.getElementById('logArea');
logArea.classList.toggle('show');
}
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', function() {
console.log('📋 Event Property Inspector HTML 로드됨');
// 초기 상태 설정
const statusDot = document.getElementById('statusDot');
const connectionStatus = document.getElementById('connection-status');
const eventSelect = document.getElementById('event-select');
const refreshButton = document.getElementById('refresh-button');
const executeButton = document.getElementById('execute-button');
if (statusDot) console.log('✅ statusDot 요소 찾음');
if (connectionStatus) console.log('✅ connection-status 요소 찾음');
if (eventSelect) console.log('✅ event-select 요소 찾음');
if (refreshButton) console.log('✅ refresh-button 요소 찾음');
if (executeButton) console.log('✅ execute-button 요소 찾음');
});
</script>
<script src="index.js"></script>
</body>
</html>

View File

@ -0,0 +1,308 @@
// Event Controller Property Inspector
let websocket = null;
let uuid = null;
let settings = {};
let eventList = [];
let isUnityConnected = false;
// DOM 요소들
let eventSelect = null;
let statusDot = null;
let connectionStatus = null;
let currentEvent = null;
let refreshButton = null;
let executeButton = null;
let autoExecute = null;
let logArea = null;
function logToScreen(msg, color = "#fff") {
let logDiv = document.getElementById('logArea');
if (!logDiv) return;
const line = document.createElement('div');
line.style.color = color;
line.textContent = `[${new Date().toLocaleTimeString()}] ${msg}`;
logDiv.appendChild(line);
logDiv.scrollTop = logDiv.scrollHeight;
}
console.log = function(...args) {
logToScreen(args.map(a => (typeof a === 'object' ? JSON.stringify(a) : a)).join(' '), '#0f0');
};
console.error = function(...args) {
logToScreen(args.map(a => (typeof a === 'object' ? JSON.stringify(a) : a)).join(' '), '#f55');
};
// DOM 초기화
window.addEventListener('DOMContentLoaded', function() {
eventSelect = document.getElementById('event-select');
statusDot = document.getElementById('statusDot');
connectionStatus = document.getElementById('connection-status');
currentEvent = document.getElementById('current-event');
refreshButton = document.getElementById('refresh-button');
executeButton = document.getElementById('execute-button');
autoExecute = document.getElementById('autoExecute');
logArea = document.getElementById('logArea');
if (refreshButton) refreshButton.addEventListener('click', onRefreshClicked);
if (executeButton) executeButton.addEventListener('click', onExecuteClicked);
if (eventSelect) eventSelect.addEventListener('change', onEventSelectionChanged);
if (autoExecute) autoExecute.addEventListener('change', onAutoExecuteChanged);
console.log('✅ Event Property Inspector 준비 완료');
});
// StreamDeck 연결
window.connectElgatoStreamDeckSocket = function(inPort, inUUID, inEvent, inInfo, inActionInfo) {
uuid = inUUID;
console.log('🔌 StreamDeck 연결 시작:', inPort, inUUID);
try {
if (inActionInfo) {
const actionInfo = JSON.parse(inActionInfo);
settings = actionInfo.payload?.settings || {};
console.log('⚙️ 초기 설정:', settings);
}
} catch (e) {
console.error('ActionInfo 파싱 오류:', e);
}
if (!websocket) {
websocket = new WebSocket('ws://localhost:' + inPort);
websocket.onopen = function() {
console.log('✅ StreamDeck 연결됨');
websocket.send(JSON.stringify({ event: inEvent, uuid: inUUID }));
// Unity 상태 요청
setTimeout(() => {
sendToPlugin('get_unity_status');
}, 500);
};
websocket.onmessage = function(evt) {
try {
const jsonObj = JSON.parse(evt.data);
handleMessage(jsonObj);
} catch (e) {
console.error('메시지 파싱 오류:', e);
}
};
websocket.onclose = function() {
console.log('❌ StreamDeck 연결 끊어짐');
websocket = null;
};
websocket.onerror = function(e) {
console.error('WebSocket 오류:', e);
};
}
};
function sendToPlugin(command, data = {}) {
if (!websocket) return;
const message = {
action: 'com.mirabox.streamingle.event',
event: 'sendToPlugin',
context: uuid,
payload: { command, ...data }
};
websocket.send(JSON.stringify(message));
console.log('📤 Plugin으로 메시지 전송:', command, data);
}
function handleMessage(jsonObj) {
console.log('📨 메시지 수신:', jsonObj.event);
if (jsonObj.event === 'sendToPropertyInspector' && jsonObj.payload && jsonObj.payload.event) {
const innerEvent = jsonObj.payload.event;
console.log('📨 Property Inspector 이벤트:', innerEvent);
switch (innerEvent) {
case 'unity_connected':
updateUnityConnection(true);
break;
case 'unity_disconnected':
updateUnityConnection(false);
break;
case 'event_list':
updateEventList(jsonObj.payload.events, jsonObj.payload.currentIndex);
break;
case 'event_changed':
updateEventState(jsonObj.payload.currentIndex);
break;
case 'camera_list':
console.log('📹 카메라 목록 수신 (이벤트 Property Inspector에서는 무시)');
break;
case 'item_list':
console.log('🎯 아이템 목록 수신 (이벤트 Property Inspector에서는 무시)');
break;
}
}
if (jsonObj.event === 'didReceiveSettings' && jsonObj.payload && jsonObj.context) {
settings = jsonObj.payload.settings || {};
console.log('⚙️ 설정 수신:', settings);
if (settings.eventIndex !== undefined && eventSelect) {
eventSelect.value = settings.eventIndex;
console.log('🎯 이벤트 인덱스 설정됨:', settings.eventIndex);
}
if (settings.autoExecute !== undefined && autoExecute) {
autoExecute.checked = settings.autoExecute;
}
updateCurrentEvent();
}
}
function updateUnityConnection(connected) {
isUnityConnected = connected;
if (statusDot) {
statusDot.className = 'dot ' + (connected ? 'green' : 'red');
}
if (connectionStatus) {
connectionStatus.textContent = connected ? 'Unity 연결됨' : 'Unity 연결 안됨';
connectionStatus.className = connected ? 'connected' : 'disconnected';
}
if (eventSelect) {
eventSelect.disabled = !connected;
}
if (refreshButton) {
refreshButton.disabled = !connected;
}
if (executeButton) {
executeButton.disabled = !connected;
}
console.log('🔗 Unity 연결 상태 변경:', connected);
}
function updateEventList(events, currentIndex) {
eventList = events || [];
console.log('🎯 이벤트 목록 업데이트:', eventList.length, '개');
if (!eventSelect) return;
eventSelect.innerHTML = '';
if (eventList.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = '이벤트 그룹이 없습니다';
eventSelect.appendChild(option);
} else {
eventList.forEach((event, index) => {
const option = document.createElement('option');
option.value = index;
option.textContent = event.name || event.groupName || `이벤트 그룹 ${index + 1}`;
eventSelect.appendChild(option);
});
}
// 현재 선택된 이벤트 설정
if (settings.eventIndex !== undefined) {
eventSelect.value = settings.eventIndex;
} else if (currentIndex !== undefined) {
eventSelect.value = currentIndex;
}
updateCurrentEvent();
}
function updateCurrentEvent() {
if (!currentEvent) return;
const selectedIndex = eventSelect ? parseInt(eventSelect.value) : -1;
if (selectedIndex >= 0 && selectedIndex < eventList.length) {
const selectedEvent = eventList[selectedIndex];
currentEvent.textContent = `현재 이벤트 그룹: ${selectedEvent.name || selectedEvent.groupName || `이벤트 그룹 ${selectedIndex + 1}`}`;
} else {
currentEvent.textContent = '현재 이벤트 그룹: -';
}
}
function updateEventState(currentIndex) {
console.log('🎯 이벤트 상태 업데이트:', currentIndex);
if (eventSelect && currentIndex !== undefined) {
eventSelect.value = currentIndex;
updateCurrentEvent();
}
}
function onEventSelectionChanged() {
if (!eventSelect) return;
const selectedIndex = parseInt(eventSelect.value);
console.log('🎯 이벤트 선택 변경:', selectedIndex);
if (selectedIndex >= 0) {
settings.eventIndex = selectedIndex;
// StreamDeck에 설정 저장 (카메라 컨트롤러와 동일한 방식)
if (websocket && uuid) {
websocket.send(JSON.stringify({
action: 'com.mirabox.streamingle.event',
event: 'setSettings',
context: uuid,
payload: {
eventIndex: selectedIndex,
actionType: 'event' // actionType 명시적으로 설정
}
}));
console.log('💾 설정 저장됨:', { eventIndex: selectedIndex, actionType: 'event' });
}
// Unity에 이벤트 설정 요청 (자동 실행 여부와 관계없이)
if (isUnityConnected) {
console.log('📤 Unity에 이벤트 설정 전송:', selectedIndex);
sendToPlugin('set_event', { event_index: selectedIndex });
} else {
console.log('⚠️ Unity가 연결되지 않아 이벤트 설정을 전송할 수 없습니다');
}
updateCurrentEvent();
}
}
function onAutoExecuteChanged() {
if (!autoExecute) return;
settings.autoExecute = autoExecute.checked;
console.log('⚙️ 자동 실행 설정 변경:', settings.autoExecute);
// StreamDeck에 설정 저장 (카메라 컨트롤러와 동일한 방식)
if (websocket && uuid) {
websocket.send(JSON.stringify({
action: 'com.mirabox.streamingle.event',
event: 'setSettings',
context: uuid,
payload: { autoExecute: settings.autoExecute }
}));
console.log('💾 자동 실행 설정 저장됨:', { autoExecute: settings.autoExecute });
}
}
function onRefreshClicked() {
console.log('🔄 이벤트 목록 새로고침 요청');
sendToPlugin('get_event_list');
}
function onExecuteClicked() {
if (!eventSelect) return;
const selectedIndex = parseInt(eventSelect.value);
if (selectedIndex >= 0) {
console.log('▶️ 이벤트 실행 요청:', selectedIndex);
sendToPlugin('execute_event', { event_index: selectedIndex });
} else {
console.log('⚠️ 이벤트가 선택되지 않았습니다');
}
}