ADD : 스트림덱 기능 추가 패치
This commit is contained in:
parent
128e8771ed
commit
d01b815746
BIN
.claude/settings.local.json
(Stored with Git LFS)
BIN
.claude/settings.local.json
(Stored with Git LFS)
Binary file not shown.
@ -493,6 +493,35 @@ public class UnityRecieve_FACEMOTION3D_and_iFacialMocap : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 페이셜 모션 캡처 재접속
|
||||
/// </summary>
|
||||
public void Reconnect()
|
||||
{
|
||||
Debug.Log("[iFacialMocap] 재접속 시도 중...");
|
||||
|
||||
try
|
||||
{
|
||||
// 기존 연결 종료
|
||||
StopUDP();
|
||||
|
||||
// 잠시 대기
|
||||
Thread.Sleep(500);
|
||||
|
||||
// 플래그 리셋
|
||||
StartFlag = true;
|
||||
|
||||
// 재시작
|
||||
StartFunction();
|
||||
|
||||
Debug.Log("[iFacialMocap] 재접속 완료");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[iFacialMocap] 재접속 실패: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasBlendShapes(SkinnedMeshRenderer skin)
|
||||
{
|
||||
if (!skin.sharedMesh)
|
||||
|
||||
7
Assets/External/websocket-sharp/websocket-sharp.csproj.meta
vendored
Normal file
7
Assets/External/websocket-sharp/websocket-sharp.csproj.meta
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae0a68acee725e141b02318f249f7990
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset_Renderer.asset
(Stored with Git LFS)
BIN
Assets/Resources/Settings/Streamingle Render Pipeline Asset_Renderer.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Background/Greenhouse Garden/Scene/Greenhouse Flower/GlobalVolumeProfile.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Background/Greenhouse Garden/Scene/Greenhouse Flower/GlobalVolumeProfile.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Background/Greenhouse Garden/Scene/Greenhouse Flower_Night.unity
(Stored with Git LFS)
BIN
Assets/ResourcesData/Background/Greenhouse Garden/Scene/Greenhouse Flower_Night.unity
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyA_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyA_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyB_eyepatch_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyB_eyepatch_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyB_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/bodyB_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/emissionA_NoOutline_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/emissionA_NoOutline_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/emissionB_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/emissionB_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eyeGuruguru_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eyeGuruguru_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eyeKakusei_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eyeKakusei_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eye_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/eye_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/face_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/face_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/glowA_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/glowA_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/glowB_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/glowB_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/hair_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/hair_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/skinB_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/skinB_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/thunder_mtoon.asset
(Stored with Git LFS)
BIN
Assets/ResourcesData/Character/00.R&D/TestVRM/Zonko_VRM.Materials/thunder_mtoon.asset
(Stored with Git LFS)
Binary file not shown.
@ -10,6 +10,7 @@ namespace Streamingle.Editor
|
||||
private bool createEventController = true;
|
||||
private bool createStreamDeckManager = true;
|
||||
private bool createAvatarOutfitController = true;
|
||||
private bool createSystemController = true;
|
||||
|
||||
private string parentObjectName = "Streamingle 컨트롤러들";
|
||||
private bool createParentObject = true;
|
||||
@ -26,6 +27,7 @@ namespace Streamingle.Editor
|
||||
private ItemController existingItemController;
|
||||
private EventController existingEventController;
|
||||
private AvatarOutfitController existingAvatarOutfitController;
|
||||
private SystemController existingSystemController;
|
||||
|
||||
[MenuItem("Tools/Streamingle/고급 컨트롤러 설정 도구")]
|
||||
public static void ShowWindow()
|
||||
@ -65,6 +67,7 @@ namespace Streamingle.Editor
|
||||
GUILayout.Label("생성할 컨트롤러들", EditorStyles.boldLabel);
|
||||
|
||||
createStreamDeckManager = EditorGUILayout.Toggle("StreamDeck 서버 매니저", createStreamDeckManager);
|
||||
createSystemController = EditorGUILayout.Toggle("시스템 컨트롤러", createSystemController);
|
||||
createCameraManager = EditorGUILayout.Toggle("카메라 매니저", createCameraManager);
|
||||
createItemController = EditorGUILayout.Toggle("아이템 컨트롤러", createItemController);
|
||||
createEventController = EditorGUILayout.Toggle("이벤트 컨트롤러", createEventController);
|
||||
@ -198,7 +201,20 @@ namespace Streamingle.Editor
|
||||
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.Label("시스템 컨트롤러:", GUILayout.Width(200));
|
||||
if (existingSystemController != null)
|
||||
{
|
||||
string parentInfo = GetParentInfo(existingSystemController.transform);
|
||||
EditorGUILayout.LabelField($"✓ 발견됨 {parentInfo}", EditorStyles.boldLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.LabelField("✗ 발견되지 않음", EditorStyles.boldLabel);
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
@ -221,6 +237,7 @@ namespace Streamingle.Editor
|
||||
existingItemController = FindObjectOfType<ItemController>();
|
||||
existingEventController = FindObjectOfType<EventController>();
|
||||
existingAvatarOutfitController = FindObjectOfType<AvatarOutfitController>();
|
||||
existingSystemController = FindObjectOfType<SystemController>();
|
||||
}
|
||||
|
||||
private void CreateControllers()
|
||||
@ -239,7 +256,13 @@ namespace Streamingle.Editor
|
||||
{
|
||||
CreateStreamDeckManager(parentObject);
|
||||
}
|
||||
|
||||
|
||||
// System Controller 생성
|
||||
if (createSystemController && existingSystemController == null)
|
||||
{
|
||||
CreateSystemController(parentObject);
|
||||
}
|
||||
|
||||
// Camera Manager 생성
|
||||
if (createCameraManager && existingCameraManager == null)
|
||||
{
|
||||
@ -337,7 +360,15 @@ namespace Streamingle.Editor
|
||||
movedCount++;
|
||||
UnityEngine.Debug.Log($"아바타 의상 컨트롤러를 {parent.name} 하위로 이동");
|
||||
}
|
||||
|
||||
|
||||
// System Controller 이동
|
||||
if (existingSystemController != null && existingSystemController.transform.parent != parent.transform)
|
||||
{
|
||||
existingSystemController.transform.SetParent(parent.transform);
|
||||
movedCount++;
|
||||
UnityEngine.Debug.Log($"시스템 컨트롤러를 {parent.name} 하위로 이동");
|
||||
}
|
||||
|
||||
if (movedCount > 0)
|
||||
{
|
||||
UnityEngine.Debug.Log($"{movedCount}개의 컨트롤러를 {parent.name} 하위로 이동했습니다.");
|
||||
@ -403,9 +434,19 @@ namespace Streamingle.Editor
|
||||
avatarOutfitControllerProperty.objectReferenceValue = existingAvatarOutfitController;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// System Controller 연결
|
||||
if (existingSystemController != null)
|
||||
{
|
||||
var systemControllerProperty = serializedObject.FindProperty("systemController");
|
||||
if (systemControllerProperty != null)
|
||||
{
|
||||
systemControllerProperty.objectReferenceValue = existingSystemController;
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
|
||||
UnityEngine.Debug.Log("기존 컨트롤러들을 StreamDeck 서버 매니저에 연결했습니다!");
|
||||
}
|
||||
|
||||
@ -563,8 +604,51 @@ namespace Streamingle.Editor
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
|
||||
UnityEngine.Debug.Log("아바타 의상 컨트롤러 생성됨");
|
||||
}
|
||||
|
||||
private void CreateSystemController(GameObject parent)
|
||||
{
|
||||
GameObject systemControllerObject = new GameObject("시스템 컨트롤러");
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
systemControllerObject.transform.SetParent(parent.transform);
|
||||
}
|
||||
|
||||
// SystemController 스크립트 추가
|
||||
var systemController = systemControllerObject.AddComponent<SystemController>();
|
||||
|
||||
// 기본 설정
|
||||
SerializedObject serializedObject = new SerializedObject(systemController);
|
||||
serializedObject.Update();
|
||||
|
||||
// OptiTrack 클라이언트 자동 찾기는 Start()에서 수행됨
|
||||
// Motion Recorder 자동 찾기 활성화
|
||||
var autoFindRecordersProperty = serializedObject.FindProperty("autoFindRecorders");
|
||||
if (autoFindRecordersProperty != null)
|
||||
{
|
||||
autoFindRecordersProperty.boolValue = true;
|
||||
}
|
||||
|
||||
// Facial Motion 클라이언트 자동 찾기 활성화
|
||||
var autoFindFacialMotionClientsProperty = serializedObject.FindProperty("autoFindFacialMotionClients");
|
||||
if (autoFindFacialMotionClientsProperty != null)
|
||||
{
|
||||
autoFindFacialMotionClientsProperty.boolValue = true;
|
||||
}
|
||||
|
||||
// 디버그 로그 활성화
|
||||
var enableDebugLogProperty = serializedObject.FindProperty("enableDebugLog");
|
||||
if (enableDebugLogProperty != null)
|
||||
{
|
||||
enableDebugLogProperty.boolValue = true;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
UnityEngine.Debug.Log("시스템 컨트롤러 생성됨");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -147,7 +147,7 @@ Material:
|
||||
- _BaseColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
- _Resolution: {r: 1101, g: 514, b: 0, a: 0}
|
||||
- _Resolution: {r: 1920, g: 1080, b: 0, a: 0}
|
||||
- _SpecColor: {r: 0.19999996, g: 0.19999996, b: 0.19999996, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
m_AllowLocking: 1
|
||||
|
||||
@ -157,6 +157,20 @@
|
||||
"m_SlotId": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"m_OutputSlot": {
|
||||
"m_Node": {
|
||||
"m_Id": "a545499797a14671a5d598434d7c85bc"
|
||||
},
|
||||
"m_SlotId": 4
|
||||
},
|
||||
"m_InputSlot": {
|
||||
"m_Node": {
|
||||
"m_Id": "ad863a97e6f14704b6137da83d1dba35"
|
||||
},
|
||||
"m_SlotId": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"m_OutputSlot": {
|
||||
"m_Node": {
|
||||
@ -346,6 +360,8 @@
|
||||
"overrideHLSLDeclaration": false,
|
||||
"hlslDeclarationOverride": 0,
|
||||
"m_Hidden": false,
|
||||
"m_PerRendererData": false,
|
||||
"m_customAttributes": [],
|
||||
"m_Value": {
|
||||
"x": 1920.0,
|
||||
"y": 1080.0,
|
||||
@ -465,12 +481,15 @@
|
||||
"overrideHLSLDeclaration": true,
|
||||
"hlslDeclarationOverride": 2,
|
||||
"m_Hidden": false,
|
||||
"m_PerRendererData": false,
|
||||
"m_customAttributes": [],
|
||||
"m_Value": {
|
||||
"m_SerializedTexture": "{\"texture\":{\"instanceID\":0}}",
|
||||
"m_SerializedTexture": "",
|
||||
"m_Guid": ""
|
||||
},
|
||||
"isMainTexture": false,
|
||||
"useTilingAndOffset": false,
|
||||
"useTexelSize": true,
|
||||
"m_Modifiable": true,
|
||||
"m_DefaultType": 0
|
||||
}
|
||||
@ -759,9 +778,9 @@
|
||||
"m_Position": {
|
||||
"serializedVersion": "2",
|
||||
"x": -1113.0,
|
||||
"y": 287.0,
|
||||
"y": 301.0,
|
||||
"width": 145.0,
|
||||
"height": 129.00003051757813
|
||||
"height": 128.0
|
||||
}
|
||||
},
|
||||
"m_Slots": [
|
||||
@ -845,7 +864,7 @@
|
||||
"m_StageCapability": 3,
|
||||
"m_BareResource": false,
|
||||
"m_Texture": {
|
||||
"m_SerializedTexture": "{\"texture\":{\"instanceID\":0}}",
|
||||
"m_SerializedTexture": "",
|
||||
"m_Guid": ""
|
||||
},
|
||||
"m_DefaultType": 0
|
||||
@ -1256,7 +1275,7 @@
|
||||
"m_StageCapability": 3,
|
||||
"m_BareResource": false,
|
||||
"m_Texture": {
|
||||
"m_SerializedTexture": "{\"texture\":{\"instanceID\":0}}",
|
||||
"m_SerializedTexture": "",
|
||||
"m_Guid": ""
|
||||
},
|
||||
"m_DefaultType": 0
|
||||
@ -1321,10 +1340,10 @@
|
||||
"m_Expanded": true,
|
||||
"m_Position": {
|
||||
"serializedVersion": "2",
|
||||
"x": -848.0000610351563,
|
||||
"y": 365.0000305175781,
|
||||
"x": -851.0,
|
||||
"y": 364.9999694824219,
|
||||
"width": 281.0,
|
||||
"height": 190.00003051757813
|
||||
"height": 190.00009155273438
|
||||
}
|
||||
},
|
||||
"m_Slots": [
|
||||
@ -1360,7 +1379,8 @@
|
||||
},
|
||||
"m_SourceType": 0,
|
||||
"m_FunctionName": "AAFromGreenChannel",
|
||||
"m_FunctionSource": "f61580200f158d84880df345e2130e9b",
|
||||
"m_FunctionSource": "7f3a4e8b9c2d1a5e6f8b9c2d1a5e6f8b",
|
||||
"m_FunctionSourceUsePragmas": true,
|
||||
"m_FunctionBody": "Enter function body here..."
|
||||
}
|
||||
|
||||
@ -1609,10 +1629,10 @@
|
||||
"m_Expanded": true,
|
||||
"m_Position": {
|
||||
"serializedVersion": "2",
|
||||
"x": -1099.0001220703125,
|
||||
"y": 569.0000610351563,
|
||||
"width": 131.00006103515626,
|
||||
"height": 33.99993896484375
|
||||
"x": -1099.0,
|
||||
"y": 498.0,
|
||||
"width": 131.0,
|
||||
"height": 34.0
|
||||
}
|
||||
},
|
||||
"m_Slots": [
|
||||
@ -1646,8 +1666,8 @@
|
||||
"m_Position": {
|
||||
"serializedVersion": "2",
|
||||
"x": -1145.0,
|
||||
"y": 444.0000305175781,
|
||||
"width": 176.99993896484376,
|
||||
"y": 429.0,
|
||||
"width": 177.0,
|
||||
"height": 34.0
|
||||
}
|
||||
},
|
||||
@ -1719,12 +1739,15 @@
|
||||
"overrideHLSLDeclaration": true,
|
||||
"hlslDeclarationOverride": 1,
|
||||
"m_Hidden": false,
|
||||
"m_PerRendererData": false,
|
||||
"m_customAttributes": [],
|
||||
"m_Value": {
|
||||
"m_SerializedTexture": "{\"texture\":{\"instanceID\":0}}",
|
||||
"m_SerializedTexture": "",
|
||||
"m_Guid": ""
|
||||
},
|
||||
"isMainTexture": false,
|
||||
"useTilingAndOffset": false,
|
||||
"useTexelSize": true,
|
||||
"m_Modifiable": true,
|
||||
"m_DefaultType": 1
|
||||
}
|
||||
@ -1763,8 +1786,8 @@
|
||||
"m_Position": {
|
||||
"serializedVersion": "2",
|
||||
"x": -1089.0,
|
||||
"y": 521.0000610351563,
|
||||
"width": 120.99993896484375,
|
||||
"y": 464.0,
|
||||
"width": 121.0,
|
||||
"height": 34.0
|
||||
}
|
||||
},
|
||||
@ -1873,12 +1896,24 @@
|
||||
"overrideHLSLDeclaration": false,
|
||||
"hlslDeclarationOverride": 0,
|
||||
"m_Hidden": false,
|
||||
"m_PerRendererData": false,
|
||||
"m_customAttributes": [],
|
||||
"m_Value": 0.0,
|
||||
"m_FloatType": 0,
|
||||
"m_RangeValues": {
|
||||
"x": 0.0,
|
||||
"y": 1.0
|
||||
}
|
||||
},
|
||||
"m_SliderType": 0,
|
||||
"m_SliderPower": 3.0,
|
||||
"m_EnumType": 0,
|
||||
"m_CSharpEnumString": "",
|
||||
"m_EnumNames": [
|
||||
"Default"
|
||||
],
|
||||
"m_EnumValues": [
|
||||
0
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
115
Assets/Scripts/SpoutOutputScript/Shaders/AAFromGreenChannel.hlsl
Normal file
115
Assets/Scripts/SpoutOutputScript/Shaders/AAFromGreenChannel.hlsl
Normal file
@ -0,0 +1,115 @@
|
||||
#ifndef AA_FROM_GREEN_CHANNEL_INCLUDED
|
||||
#define AA_FROM_GREEN_CHANNEL_INCLUDED
|
||||
|
||||
// Shader Graph Custom Function
|
||||
// UnityTexture2D와 UnitySamplerState를 받아서 G 채널에 안티에일리어싱 적용
|
||||
|
||||
void AAFromGreenChannel_float(
|
||||
float2 UV,
|
||||
UnityTexture2D Tex,
|
||||
UnitySamplerState Samp,
|
||||
float Strength,
|
||||
float2 Resolution,
|
||||
out float New)
|
||||
{
|
||||
// Texel Size 계산
|
||||
float2 texelSize = 1.0 / Resolution;
|
||||
|
||||
// 중앙값
|
||||
float center = SAMPLE_TEXTURE2D(Tex, Samp, UV).g;
|
||||
|
||||
if (Strength > 0.0)
|
||||
{
|
||||
// 주변 8방향 샘플링
|
||||
float left = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, 0)).g;
|
||||
float right = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, 0)).g;
|
||||
float top = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, texelSize.y)).g;
|
||||
float bottom = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, -texelSize.y)).g;
|
||||
|
||||
float topLeft = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, texelSize.y)).g;
|
||||
float topRight = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, texelSize.y)).g;
|
||||
float bottomLeft = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, -texelSize.y)).g;
|
||||
float bottomRight = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, -texelSize.y)).g;
|
||||
|
||||
// 9탭 박스 필터 평균
|
||||
float average = (center + left + right + top + bottom +
|
||||
topLeft + topRight + bottomLeft + bottomRight) / 9.0;
|
||||
|
||||
// Strength에 따라 블렌딩
|
||||
New = lerp(center, average, saturate(Strength));
|
||||
}
|
||||
else
|
||||
{
|
||||
New = center;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple 버전 - 5탭 크로스 필터
|
||||
void AAFromGreenChannelSimple_float(
|
||||
float2 UV,
|
||||
UnityTexture2D Tex,
|
||||
UnitySamplerState Samp,
|
||||
float Strength,
|
||||
float2 Resolution,
|
||||
out float New)
|
||||
{
|
||||
float2 texelSize = 1.0 / Resolution;
|
||||
|
||||
float center = SAMPLE_TEXTURE2D(Tex, Samp, UV).g;
|
||||
|
||||
if (Strength > 0.0)
|
||||
{
|
||||
float left = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, 0)).g;
|
||||
float right = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, 0)).g;
|
||||
float top = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, texelSize.y)).g;
|
||||
float bottom = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, -texelSize.y)).g;
|
||||
|
||||
float average = (center + left + right + top + bottom) / 5.0;
|
||||
|
||||
New = lerp(center, average, saturate(Strength));
|
||||
}
|
||||
else
|
||||
{
|
||||
New = center;
|
||||
}
|
||||
}
|
||||
|
||||
// Weighted 버전 - 가우시안 가중치
|
||||
void AAFromGreenChannelWeighted_float(
|
||||
float2 UV,
|
||||
UnityTexture2D Tex,
|
||||
UnitySamplerState Samp,
|
||||
float Strength,
|
||||
float2 Resolution,
|
||||
out float New)
|
||||
{
|
||||
float2 texelSize = 1.0 / Resolution;
|
||||
|
||||
float center = SAMPLE_TEXTURE2D(Tex, Samp, UV).g;
|
||||
|
||||
if (Strength > 0.0)
|
||||
{
|
||||
float left = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, 0)).g;
|
||||
float right = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, 0)).g;
|
||||
float top = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, texelSize.y)).g;
|
||||
float bottom = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(0, -texelSize.y)).g;
|
||||
|
||||
float topLeft = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, texelSize.y)).g;
|
||||
float topRight = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, texelSize.y)).g;
|
||||
float bottomLeft = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(-texelSize.x, -texelSize.y)).g;
|
||||
float bottomRight = SAMPLE_TEXTURE2D(Tex, Samp, UV + float2(texelSize.x, -texelSize.y)).g;
|
||||
|
||||
// 가우시안 가중치
|
||||
float weighted = center * 0.25;
|
||||
weighted += (left + right + top + bottom) * 0.125;
|
||||
weighted += (topLeft + topRight + bottomLeft + bottomRight) * 0.0625;
|
||||
|
||||
New = lerp(center, weighted, saturate(Strength));
|
||||
}
|
||||
else
|
||||
{
|
||||
New = center;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // AA_FROM_GREEN_CHANNEL_INCLUDED
|
||||
@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f3a4e8b9c2d1a5e6f8b9c2d1a5e6f8b
|
||||
ShaderIncludeImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,93 @@
|
||||
Shader "Hidden/AlphaFromNiloToon"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_MainTex ("Main Texture", 2D) = "white" {}
|
||||
_AlphaTex ("Alpha Texture (NiloToon Prepass)", 2D) = "white" {}
|
||||
_BlurRadius ("Blur Radius", Float) = 1.0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags { "RenderType"="Transparent" "Queue"="Transparent" }
|
||||
LOD 100
|
||||
|
||||
ZWrite Off
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
struct appdata
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float2 uv : TEXCOORD0;
|
||||
float4 vertex : SV_POSITION;
|
||||
};
|
||||
|
||||
sampler2D _MainTex;
|
||||
sampler2D _AlphaTex;
|
||||
float4 _MainTex_ST;
|
||||
float4 _AlphaTex_TexelSize;
|
||||
float _BlurRadius;
|
||||
|
||||
v2f vert (appdata v)
|
||||
{
|
||||
v2f o;
|
||||
o.vertex = UnityObjectToClipPos(v.vertex);
|
||||
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
|
||||
return o;
|
||||
}
|
||||
|
||||
// 3x3 가우시안 블러 커널 (매우 가벼운 블러)
|
||||
fixed GaussianBlurAlpha(float2 uv)
|
||||
{
|
||||
float2 texelSize = _AlphaTex_TexelSize.xy * _BlurRadius;
|
||||
|
||||
// 가우시안 가중치 (3x3 커널)
|
||||
fixed alpha = 0.0;
|
||||
|
||||
// 중앙 픽셀 (가장 높은 가중치)
|
||||
alpha += tex2D(_AlphaTex, uv).g * 0.25;
|
||||
|
||||
// 상하좌우 (중간 가중치)
|
||||
alpha += tex2D(_AlphaTex, uv + float2(0, texelSize.y)).g * 0.125;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(0, -texelSize.y)).g * 0.125;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(texelSize.x, 0)).g * 0.125;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(-texelSize.x, 0)).g * 0.125;
|
||||
|
||||
// 대각선 (낮은 가중치)
|
||||
alpha += tex2D(_AlphaTex, uv + float2(texelSize.x, texelSize.y)).g * 0.0625;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(-texelSize.x, texelSize.y)).g * 0.0625;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(texelSize.x, -texelSize.y)).g * 0.0625;
|
||||
alpha += tex2D(_AlphaTex, uv + float2(-texelSize.x, -texelSize.y)).g * 0.0625;
|
||||
|
||||
return alpha;
|
||||
}
|
||||
|
||||
fixed4 frag (v2f i) : SV_Target
|
||||
{
|
||||
// 메인 텍스처에서 RGB 가져오기
|
||||
fixed4 col = tex2D(_MainTex, i.uv);
|
||||
|
||||
// NiloToon Prepass 버퍼의 G 채널에서 알파 가져오기 (가우시안 블러 적용)
|
||||
fixed alpha = GaussianBlurAlpha(i.uv);
|
||||
|
||||
// RGB는 그대로, 알파는 블러 처리된 값 사용
|
||||
col.a = alpha;
|
||||
|
||||
return col;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bac6acd56892cc94ba00d208d4d82712
|
||||
ShaderImporter:
|
||||
externalObjects: {}
|
||||
defaultTextures: []
|
||||
nonModifiableTextures: []
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -16,6 +16,7 @@ public class StreamDeckServerManager : MonoBehaviour
|
||||
public ItemController itemController { get; private set; }
|
||||
public EventController eventController { get; private set; }
|
||||
public AvatarOutfitController avatarOutfitController { get; private set; }
|
||||
public SystemController systemController { get; private set; }
|
||||
|
||||
// 싱글톤 패턴으로 StreamDeckService에서 접근 가능하도록
|
||||
public static StreamDeckServerManager Instance { get; private set; }
|
||||
@ -60,6 +61,13 @@ public class StreamDeckServerManager : MonoBehaviour
|
||||
Debug.LogWarning("[StreamDeckServerManager] AvatarOutfitController를 찾을 수 없습니다. 아바타 의상 컨트롤 기능이 비활성화됩니다.");
|
||||
}
|
||||
|
||||
// SystemController 찾기
|
||||
systemController = FindObjectOfType<SystemController>();
|
||||
if (systemController == null)
|
||||
{
|
||||
Debug.LogWarning("[StreamDeckServerManager] SystemController를 찾을 수 없습니다. 시스템 컨트롤 기능이 비활성화됩니다.");
|
||||
}
|
||||
|
||||
StartServer();
|
||||
}
|
||||
|
||||
@ -335,6 +343,23 @@ public class StreamDeckServerManager : MonoBehaviour
|
||||
HandleGetAvatarOutfitList(service);
|
||||
break;
|
||||
|
||||
// SystemController 명령어들
|
||||
case "toggle_optitrack_markers":
|
||||
case "show_optitrack_markers":
|
||||
case "hide_optitrack_markers":
|
||||
case "reconnect_optitrack":
|
||||
case "reconnect_facial_motion":
|
||||
case "refresh_facial_motion_clients":
|
||||
case "start_motion_recording":
|
||||
case "stop_motion_recording":
|
||||
case "toggle_motion_recording":
|
||||
case "refresh_motion_recorders":
|
||||
case "capture_screenshot":
|
||||
case "capture_alpha_screenshot":
|
||||
case "open_screenshot_folder":
|
||||
HandleSystemCommand(message);
|
||||
break;
|
||||
|
||||
case "test":
|
||||
// 테스트 메시지 에코 응답
|
||||
var response = new
|
||||
@ -961,6 +986,43 @@ public class StreamDeckServerManager : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSystemCommand(Dictionary<string, object> message)
|
||||
{
|
||||
string messageType = message.ContainsKey("type") ? message["type"].ToString() : null;
|
||||
Debug.Log($"[StreamDeckServerManager] 시스템 명령어 실행: {messageType}");
|
||||
|
||||
if (systemController == null)
|
||||
{
|
||||
Debug.LogError("[StreamDeckServerManager] SystemController가 null입니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 파라미터 추출 (있을 경우)
|
||||
Dictionary<string, object> parameters = new Dictionary<string, object>();
|
||||
|
||||
if (message.ContainsKey("data"))
|
||||
{
|
||||
var dataObject = message["data"];
|
||||
if (dataObject is Newtonsoft.Json.Linq.JObject jObject)
|
||||
{
|
||||
foreach (var prop in jObject.Properties())
|
||||
{
|
||||
parameters[prop.Name] = prop.Value.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SystemController의 ExecuteCommand 호출
|
||||
systemController.ExecuteCommand(messageType, parameters);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"[StreamDeckServerManager] 시스템 명령어 실행 실패: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void HandleGetAvatarOutfitList(StreamDeckService service)
|
||||
{
|
||||
@ -1009,7 +1071,9 @@ public class StreamDeckService : WebSocketBehavior
|
||||
|
||||
protected override void OnMessage(WebSocketSharp.MessageEventArgs e)
|
||||
{
|
||||
Debug.Log($"[StreamDeckService] 원본 메시지 수신: {e.Data}");
|
||||
string timestamp = System.DateTime.Now.ToString("HH:mm:ss.fff");
|
||||
Debug.Log($"[{timestamp}] [StreamDeckService] 원본 메시지 수신: {e.Data}");
|
||||
Debug.Log($"[{timestamp}] [StreamDeckService] ⚠️ 지금 바로 버튼을 클릭했나요? 클릭했다면 어떤 버튼인지 확인하세요!");
|
||||
|
||||
// 메인 스레드에서 처리하도록 매니저에게 전달
|
||||
if (serverManager != null)
|
||||
|
||||
@ -0,0 +1,773 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.IO;
|
||||
using System;
|
||||
using Entum;
|
||||
|
||||
/// <summary>
|
||||
/// StreamDeck 단일 기능 버튼들을 통합 관리하는 시스템 컨트롤러
|
||||
/// 각 기능은 고유 ID로 식별되며, 확장이 용이한 구조
|
||||
/// </summary>
|
||||
public class SystemController : MonoBehaviour
|
||||
{
|
||||
[Header("OptiTrack 참조")]
|
||||
public OptitrackStreamingClient optitrackClient;
|
||||
|
||||
[Header("모션 녹화 설정")]
|
||||
[Tooltip("모션 녹화 시 OptiTrack Motive도 함께 녹화할지 여부")]
|
||||
public bool recordOptiTrackWithMotion = true;
|
||||
|
||||
[Header("EasyMotion Recorder")]
|
||||
[Tooltip("true면 씬의 모든 MotionDataRecorder를 자동으로 찾습니다")]
|
||||
public bool autoFindRecorders = true;
|
||||
|
||||
[Tooltip("수동으로 지정할 레코더 목록 (autoFindRecorders가 false일 때 사용)")]
|
||||
public List<MotionDataRecorder> motionRecorders = new List<MotionDataRecorder>();
|
||||
|
||||
[Header("Facial Motion Capture")]
|
||||
[Tooltip("true면 씬의 모든 페이셜 모션 클라이언트를 자동으로 찾습니다")]
|
||||
public bool autoFindFacialMotionClients = true;
|
||||
|
||||
[Tooltip("수동으로 지정할 페이셜 모션 클라이언트 목록 (autoFindFacialMotionClients가 false일 때 사용)")]
|
||||
public List<UnityRecieve_FACEMOTION3D_and_iFacialMocap> facialMotionClients = new List<UnityRecieve_FACEMOTION3D_and_iFacialMocap>();
|
||||
|
||||
[Header("Screenshot Settings")]
|
||||
[Tooltip("스크린샷 해상도 (기본: 4K)")]
|
||||
public int screenshotWidth = 3840;
|
||||
|
||||
[Tooltip("스크린샷 해상도 (기본: 4K)")]
|
||||
public int screenshotHeight = 2160;
|
||||
|
||||
[Tooltip("스크린샷 저장 경로 (비어있으면 바탕화면)")]
|
||||
public string screenshotSavePath = "";
|
||||
|
||||
[Tooltip("파일명 앞에 붙을 접두사")]
|
||||
public string screenshotFilePrefix = "Screenshot";
|
||||
|
||||
[Tooltip("알파 채널 추출용 셰이더")]
|
||||
public Shader alphaShader;
|
||||
|
||||
[Tooltip("NiloToon Prepass 버퍼 텍스처 이름")]
|
||||
public string niloToonPrepassBufferName = "_NiloToonPrepassBufferTex";
|
||||
|
||||
[Tooltip("촬영할 카메라 (비어있으면 메인 카메라 사용)")]
|
||||
public Camera screenshotCamera;
|
||||
|
||||
[Tooltip("알파 채널 블러 반경 (0 = 블러 없음, 1.0 = 약한 블러)")]
|
||||
[Range(0f, 3f)]
|
||||
public float alphaBlurRadius = 1.0f;
|
||||
|
||||
[Header("디버그")]
|
||||
public bool enableDebugLog = true;
|
||||
|
||||
private bool isRecording = false;
|
||||
private Material alphaMaterial;
|
||||
|
||||
// 싱글톤 패턴
|
||||
public static SystemController Instance { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// OptiTrack 클라이언트 자동 찾기
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
optitrackClient = FindObjectOfType<OptitrackStreamingClient>();
|
||||
}
|
||||
|
||||
// Motion Recorder 자동 찾기
|
||||
if (autoFindRecorders)
|
||||
{
|
||||
RefreshMotionRecorders();
|
||||
}
|
||||
|
||||
// Facial Motion 클라이언트 자동 찾기
|
||||
if (autoFindFacialMotionClients)
|
||||
{
|
||||
RefreshFacialMotionClients();
|
||||
}
|
||||
|
||||
// Screenshot 설정 초기화
|
||||
if (screenshotCamera == null)
|
||||
{
|
||||
screenshotCamera = Camera.main;
|
||||
}
|
||||
|
||||
if (alphaShader == null)
|
||||
{
|
||||
alphaShader = Shader.Find("Hidden/AlphaFromNiloToon");
|
||||
if (alphaShader == null)
|
||||
{
|
||||
LogError("알파 셰이더를 찾을 수 없습니다: Hidden/AlphaFromNiloToon");
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(screenshotSavePath))
|
||||
{
|
||||
screenshotSavePath = Path.Combine(Application.dataPath, "..", "Screenshots");
|
||||
}
|
||||
|
||||
// Screenshots 폴더가 없으면 생성
|
||||
if (!Directory.Exists(screenshotSavePath))
|
||||
{
|
||||
Directory.CreateDirectory(screenshotSavePath);
|
||||
Log($"Screenshots 폴더 생성됨: {screenshotSavePath}");
|
||||
}
|
||||
|
||||
Log("SystemController 초기화 완료");
|
||||
Log($"Motion Recorder 개수: {motionRecorders.Count}");
|
||||
Log($"Facial Motion 클라이언트 개수: {facialMotionClients.Count}");
|
||||
Log($"Screenshot 카메라: {(screenshotCamera != null ? "설정됨" : "없음")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 씬에서 모든 MotionDataRecorder를 다시 찾습니다
|
||||
/// </summary>
|
||||
public void RefreshMotionRecorders()
|
||||
{
|
||||
var allRecorders = FindObjectsOfType<MotionDataRecorder>();
|
||||
motionRecorders = allRecorders.ToList();
|
||||
Log($"Motion Recorder {motionRecorders.Count}개 발견");
|
||||
}
|
||||
|
||||
#region OptiTrack 마커 기능
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 마커 표시 토글 (켜기/끄기)
|
||||
/// </summary>
|
||||
public void ToggleOptitrackMarkers()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
LogError("OptitrackStreamingClient를 찾을 수 없습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
optitrackClient.ToggleDrawMarkers();
|
||||
Log($"OptiTrack 마커 표시: {optitrackClient.DrawMarkers}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 마커 표시 켜기
|
||||
/// </summary>
|
||||
public void ShowOptitrackMarkers()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
LogError("OptitrackStreamingClient를 찾을 수 없습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!optitrackClient.DrawMarkers)
|
||||
{
|
||||
optitrackClient.ToggleDrawMarkers();
|
||||
}
|
||||
Log("OptiTrack 마커 표시 켜짐");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 마커 표시 끄기
|
||||
/// </summary>
|
||||
public void HideOptitrackMarkers()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
LogError("OptitrackStreamingClient를 찾을 수 없습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (optitrackClient.DrawMarkers)
|
||||
{
|
||||
optitrackClient.ToggleDrawMarkers();
|
||||
}
|
||||
Log("OptiTrack 마커 표시 꺼짐");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region OptiTrack 재접속 기능
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 서버에 재접속 시도
|
||||
/// </summary>
|
||||
public void ReconnectOptitrack()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
LogError("OptitrackStreamingClient를 찾을 수 없습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log("OptiTrack 재접속 시도...");
|
||||
optitrackClient.Reconnect();
|
||||
Log("OptiTrack 재접속 명령 전송 완료");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 연결 상태 확인
|
||||
/// </summary>
|
||||
public bool IsOptitrackConnected()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return optitrackClient.IsConnected();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OptiTrack 연결 상태를 문자열로 반환
|
||||
/// </summary>
|
||||
public string GetOptitrackConnectionStatus()
|
||||
{
|
||||
if (optitrackClient == null)
|
||||
{
|
||||
return "OptiTrack 클라이언트 없음";
|
||||
}
|
||||
|
||||
return optitrackClient.GetConnectionStatus();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Facial Motion Capture 재접속 기능
|
||||
|
||||
/// <summary>
|
||||
/// 씬에서 모든 Facial Motion 클라이언트를 다시 찾습니다
|
||||
/// </summary>
|
||||
public void RefreshFacialMotionClients()
|
||||
{
|
||||
var allClients = FindObjectsOfType<UnityRecieve_FACEMOTION3D_and_iFacialMocap>();
|
||||
facialMotionClients = allClients.ToList();
|
||||
Log($"Facial Motion 클라이언트 {facialMotionClients.Count}개 발견");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모든 Facial Motion 클라이언트 재접속
|
||||
/// </summary>
|
||||
public void ReconnectFacialMotion()
|
||||
{
|
||||
if (autoFindFacialMotionClients)
|
||||
{
|
||||
RefreshFacialMotionClients();
|
||||
}
|
||||
|
||||
if (facialMotionClients == null || facialMotionClients.Count == 0)
|
||||
{
|
||||
LogError("Facial Motion 클라이언트가 없습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
Log($"Facial Motion 클라이언트 재접속 시도... ({facialMotionClients.Count}개)");
|
||||
|
||||
int reconnectedCount = 0;
|
||||
foreach (var client in facialMotionClients)
|
||||
{
|
||||
if (client != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
client.Reconnect();
|
||||
reconnectedCount++;
|
||||
Log($"클라이언트 재접속 성공: {client.gameObject.name}");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
LogError($"클라이언트 재접속 실패 ({client.gameObject.name}): {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reconnectedCount > 0)
|
||||
{
|
||||
Log($"=== Facial Motion 재접속 완료 ({reconnectedCount}/{facialMotionClients.Count}개) ===");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("재접속에 성공한 클라이언트가 없습니다!");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 스크린샷 기능
|
||||
|
||||
/// <summary>
|
||||
/// 일반 스크린샷 촬영
|
||||
/// </summary>
|
||||
public void CaptureScreenshot()
|
||||
{
|
||||
if (screenshotCamera == null)
|
||||
{
|
||||
LogError("촬영할 카메라가 설정되지 않았습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
string fileName = GenerateFileName("png");
|
||||
string fullPath = Path.Combine(screenshotSavePath, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
// 렌더 텍스처 생성
|
||||
RenderTexture rt = new RenderTexture(screenshotWidth, screenshotHeight, 24);
|
||||
RenderTexture currentRT = screenshotCamera.targetTexture;
|
||||
|
||||
// 카메라로 렌더링
|
||||
screenshotCamera.targetTexture = rt;
|
||||
screenshotCamera.Render();
|
||||
|
||||
// 텍스처를 Texture2D로 변환
|
||||
RenderTexture.active = rt;
|
||||
Texture2D screenshot = new Texture2D(screenshotWidth, screenshotHeight, TextureFormat.RGB24, false);
|
||||
screenshot.ReadPixels(new Rect(0, 0, screenshotWidth, screenshotHeight), 0, 0);
|
||||
screenshot.Apply();
|
||||
|
||||
// PNG로 저장
|
||||
byte[] bytes = screenshot.EncodeToPNG();
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
|
||||
// 정리
|
||||
screenshotCamera.targetTexture = currentRT;
|
||||
RenderTexture.active = null;
|
||||
Destroy(rt);
|
||||
Destroy(screenshot);
|
||||
|
||||
Log($"스크린샷 저장 완료: {fullPath}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"스크린샷 촬영 실패: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 알파 채널 포함 스크린샷 촬영
|
||||
/// NiloToon Prepass 버퍼의 G 채널을 알파로 사용
|
||||
/// </summary>
|
||||
public void CaptureAlphaScreenshot()
|
||||
{
|
||||
if (screenshotCamera == null)
|
||||
{
|
||||
LogError("촬영할 카메라가 설정되지 않았습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (alphaShader == null)
|
||||
{
|
||||
LogError("알파 셰이더가 설정되지 않았습니다!");
|
||||
return;
|
||||
}
|
||||
|
||||
string fileName = GenerateFileName("png", "_Alpha");
|
||||
string fullPath = Path.Combine(screenshotSavePath, fileName);
|
||||
|
||||
try
|
||||
{
|
||||
// 렌더 텍스처 생성
|
||||
RenderTexture rt = new RenderTexture(screenshotWidth, screenshotHeight, 24);
|
||||
RenderTexture currentRT = screenshotCamera.targetTexture;
|
||||
|
||||
// 카메라로 렌더링
|
||||
screenshotCamera.targetTexture = rt;
|
||||
screenshotCamera.Render();
|
||||
|
||||
// NiloToon Prepass 버퍼 가져오기
|
||||
Texture niloToonPrepassBuffer = Shader.GetGlobalTexture(niloToonPrepassBufferName);
|
||||
|
||||
if (niloToonPrepassBuffer == null)
|
||||
{
|
||||
LogError($"NiloToon Prepass 버퍼를 찾을 수 없습니다: {niloToonPrepassBufferName}");
|
||||
screenshotCamera.targetTexture = currentRT;
|
||||
Destroy(rt);
|
||||
return;
|
||||
}
|
||||
|
||||
// 알파 합성용 머티리얼 생성
|
||||
if (alphaMaterial == null)
|
||||
{
|
||||
alphaMaterial = new Material(alphaShader);
|
||||
}
|
||||
|
||||
// 알파 채널 합성
|
||||
RenderTexture alphaRT = new RenderTexture(screenshotWidth, screenshotHeight, 0, RenderTextureFormat.ARGB32);
|
||||
alphaMaterial.SetTexture("_MainTex", rt);
|
||||
alphaMaterial.SetTexture("_AlphaTex", niloToonPrepassBuffer);
|
||||
alphaMaterial.SetFloat("_BlurRadius", alphaBlurRadius);
|
||||
|
||||
// Blit으로 알파 합성
|
||||
Graphics.Blit(rt, alphaRT, alphaMaterial);
|
||||
|
||||
// 텍스처를 Texture2D로 변환
|
||||
RenderTexture.active = alphaRT;
|
||||
Texture2D screenshot = new Texture2D(screenshotWidth, screenshotHeight, TextureFormat.RGBA32, false);
|
||||
screenshot.ReadPixels(new Rect(0, 0, screenshotWidth, screenshotHeight), 0, 0);
|
||||
screenshot.Apply();
|
||||
|
||||
// PNG로 저장
|
||||
byte[] bytes = screenshot.EncodeToPNG();
|
||||
File.WriteAllBytes(fullPath, bytes);
|
||||
|
||||
// 정리
|
||||
screenshotCamera.targetTexture = currentRT;
|
||||
RenderTexture.active = null;
|
||||
Destroy(rt);
|
||||
Destroy(alphaRT);
|
||||
Destroy(screenshot);
|
||||
|
||||
Log($"알파 스크린샷 저장 완료: {fullPath}");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogError($"알파 스크린샷 촬영 실패: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 스크린샷 저장 폴더 열기
|
||||
/// </summary>
|
||||
public void OpenScreenshotFolder()
|
||||
{
|
||||
if (Directory.Exists(screenshotSavePath))
|
||||
{
|
||||
System.Diagnostics.Process.Start(screenshotSavePath);
|
||||
Log($"저장 폴더 열기: {screenshotSavePath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError($"저장 폴더가 존재하지 않습니다: {screenshotSavePath}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 파일명 생성
|
||||
/// </summary>
|
||||
private string GenerateFileName(string extension, string suffix = "")
|
||||
{
|
||||
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
|
||||
return $"{screenshotFilePrefix}{suffix}_{timestamp}.{extension}";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 모션 녹화 기능 (EasyMotion Recorder + OptiTrack Motive)
|
||||
|
||||
/// <summary>
|
||||
/// 모션 녹화 시작 (EasyMotion Recorder + OptiTrack Motive)
|
||||
/// </summary>
|
||||
public void StartMotionRecording()
|
||||
{
|
||||
// OptiTrack 녹화 시작 (옵션이 켜져 있을 때만)
|
||||
bool optitrackStarted = false;
|
||||
if (recordOptiTrackWithMotion)
|
||||
{
|
||||
if (optitrackClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
optitrackStarted = optitrackClient.StartRecording();
|
||||
if (optitrackStarted)
|
||||
{
|
||||
Log("OptiTrack Motive 녹화 시작 성공");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("OptiTrack Motive 녹화 시작 실패");
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
LogError($"OptiTrack 녹화 시작 오류: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("OptiTrack 클라이언트 없음 - OptiTrack 녹화 건너뜀");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("OptiTrack 녹화 옵션 꺼짐 - OptiTrack 녹화 건너뜀");
|
||||
}
|
||||
|
||||
// EasyMotion Recorder 녹화 시작
|
||||
int startedCount = 0;
|
||||
if (motionRecorders != null && motionRecorders.Count > 0)
|
||||
{
|
||||
foreach (var recorder in motionRecorders)
|
||||
{
|
||||
if (recorder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// RecordStart 메서드 호출
|
||||
var method = recorder.GetType().GetMethod("RecordStart");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(recorder, null);
|
||||
startedCount++;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
LogError($"레코더 시작 실패 ({recorder.name}): {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (startedCount > 0)
|
||||
{
|
||||
Log($"EasyMotion 녹화 시작 ({startedCount}/{motionRecorders.Count}개 레코더)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("녹화를 시작한 EasyMotion 레코더가 없습니다!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("EasyMotion Recorder 없음 - EasyMotion 녹화 건너뜀");
|
||||
}
|
||||
|
||||
// 하나라도 성공하면 녹화 중 상태로 설정
|
||||
if (optitrackStarted || startedCount > 0)
|
||||
{
|
||||
isRecording = true;
|
||||
Log($"=== 녹화 시작 완료 ===");
|
||||
if (recordOptiTrackWithMotion)
|
||||
{
|
||||
Log($"OptiTrack: {(optitrackStarted ? "시작됨" : "시작 안됨")}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"OptiTrack: 옵션 꺼짐");
|
||||
}
|
||||
Log($"EasyMotion: {startedCount}개 레코더 시작됨");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("녹화를 시작할 수 있는 시스템이 없습니다!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모션 녹화 중지 (EasyMotion Recorder + OptiTrack Motive)
|
||||
/// </summary>
|
||||
public void StopMotionRecording()
|
||||
{
|
||||
// OptiTrack 녹화 중지 (옵션이 켜져 있을 때만)
|
||||
bool optitrackStopped = false;
|
||||
if (recordOptiTrackWithMotion)
|
||||
{
|
||||
if (optitrackClient != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
optitrackStopped = optitrackClient.StopRecording();
|
||||
if (optitrackStopped)
|
||||
{
|
||||
Log("OptiTrack Motive 녹화 중지 성공");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("OptiTrack Motive 녹화 중지 실패");
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
LogError($"OptiTrack 녹화 중지 오류: {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("OptiTrack 클라이언트 없음 - OptiTrack 녹화 중지 건너뜀");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("OptiTrack 녹화 옵션 꺼짐 - OptiTrack 녹화 중지 건너뜀");
|
||||
}
|
||||
|
||||
// EasyMotion Recorder 녹화 중지
|
||||
int stoppedCount = 0;
|
||||
if (motionRecorders != null && motionRecorders.Count > 0)
|
||||
{
|
||||
foreach (var recorder in motionRecorders)
|
||||
{
|
||||
if (recorder != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// RecordEnd 메서드 호출
|
||||
var method = recorder.GetType().GetMethod("RecordEnd");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(recorder, null);
|
||||
stoppedCount++;
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
LogError($"레코더 중지 실패 ({recorder.name}): {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stoppedCount > 0)
|
||||
{
|
||||
Log($"EasyMotion 녹화 중지 ({stoppedCount}/{motionRecorders.Count}개 레코더)");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("녹화를 중지한 EasyMotion 레코더가 없습니다!");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("EasyMotion Recorder 없음 - EasyMotion 녹화 중지 건너뜀");
|
||||
}
|
||||
|
||||
// 하나라도 성공하면 녹화 중지 상태로 설정
|
||||
if (optitrackStopped || stoppedCount > 0)
|
||||
{
|
||||
isRecording = false;
|
||||
Log($"=== 녹화 중지 완료 ===");
|
||||
Log($"OptiTrack: {(optitrackStopped ? "중지됨" : "중지 안됨")}");
|
||||
Log($"EasyMotion: {stoppedCount}개 레코더 중지됨");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("녹화를 중지할 수 있는 시스템이 없습니다!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 모션 녹화 토글 (시작/중지)
|
||||
/// </summary>
|
||||
public void ToggleMotionRecording()
|
||||
{
|
||||
if (isRecording)
|
||||
{
|
||||
StopMotionRecording();
|
||||
}
|
||||
else
|
||||
{
|
||||
StartMotionRecording();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 녹화 중인지 여부 반환
|
||||
/// </summary>
|
||||
public bool IsRecording()
|
||||
{
|
||||
return isRecording;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 명령어 실행 - WebSocket에서 받은 명령을 처리
|
||||
/// </summary>
|
||||
public void ExecuteCommand(string command, Dictionary<string, object> parameters)
|
||||
{
|
||||
Log($"명령어 실행: {command}");
|
||||
|
||||
switch (command)
|
||||
{
|
||||
// OptiTrack 마커
|
||||
case "toggle_optitrack_markers":
|
||||
ToggleOptitrackMarkers();
|
||||
break;
|
||||
|
||||
case "show_optitrack_markers":
|
||||
ShowOptitrackMarkers();
|
||||
break;
|
||||
|
||||
case "hide_optitrack_markers":
|
||||
HideOptitrackMarkers();
|
||||
break;
|
||||
|
||||
// OptiTrack 재접속
|
||||
case "reconnect_optitrack":
|
||||
ReconnectOptitrack();
|
||||
break;
|
||||
|
||||
// Facial Motion 재접속
|
||||
case "reconnect_facial_motion":
|
||||
ReconnectFacialMotion();
|
||||
break;
|
||||
|
||||
case "refresh_facial_motion_clients":
|
||||
RefreshFacialMotionClients();
|
||||
break;
|
||||
|
||||
// EasyMotion Recorder
|
||||
case "start_motion_recording":
|
||||
StartMotionRecording();
|
||||
break;
|
||||
|
||||
case "stop_motion_recording":
|
||||
StopMotionRecording();
|
||||
break;
|
||||
|
||||
case "toggle_motion_recording":
|
||||
ToggleMotionRecording();
|
||||
break;
|
||||
|
||||
case "refresh_motion_recorders":
|
||||
RefreshMotionRecorders();
|
||||
break;
|
||||
|
||||
// 스크린샷
|
||||
case "capture_screenshot":
|
||||
CaptureScreenshot();
|
||||
break;
|
||||
|
||||
case "capture_alpha_screenshot":
|
||||
CaptureAlphaScreenshot();
|
||||
break;
|
||||
|
||||
case "open_screenshot_folder":
|
||||
OpenScreenshotFolder();
|
||||
break;
|
||||
|
||||
default:
|
||||
LogError($"알 수 없는 명령어: {command}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (alphaMaterial != null)
|
||||
{
|
||||
Destroy(alphaMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
if (enableDebugLog)
|
||||
{
|
||||
Debug.Log($"[SystemController] {message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void LogError(string message)
|
||||
{
|
||||
Debug.LogError($"[SystemController] {message}");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f8e9a1b2c3d4e5f6a7b8c9d0e1f2a3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Streamdeck/DEBUG_MARKER_BUTTON.md
(Stored with Git LFS)
Normal file
BIN
Streamdeck/DEBUG_MARKER_BUTTON.md
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/DEPLOY_README.md
(Stored with Git LFS)
Normal file
BIN
Streamdeck/DEPLOY_README.md
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/HOW_TO_DEBUG_STREAMDOCK.md
(Stored with Git LFS)
Normal file
BIN
Streamdeck/HOW_TO_DEBUG_STREAMDOCK.md
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/STREAMDOCK_SDK_REFERENCE.md
(Stored with Git LFS)
Normal file
BIN
Streamdeck/STREAMDOCK_SDK_REFERENCE.md
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/SYSTEM_CONTROLLER_GUIDE.md
(Stored with Git LFS)
Normal file
BIN
Streamdeck/SYSTEM_CONTROLLER_GUIDE.md
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/facial_motion_reconnect_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/facial_motion_reconnect_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/motion_record_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/motion_record_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/motion_record_icon_off.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/motion_record_icon_off.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_group_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_group_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_marker_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_marker_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_marker_icon_off.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_marker_icon_off.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_reconnect_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/optitrack_reconnect_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/screenshot_alpha_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/screenshot_alpha_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/screenshot_icon.png
(Stored with Git LFS)
Normal file
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/images/screenshot_icon.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/manifest.json
(Stored with Git LFS)
BIN
Streamdeck/com.mirabox.streamingle.sdPlugin/manifest.json
(Stored with Git LFS)
Binary file not shown.
@ -69,36 +69,199 @@ function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inAction
|
||||
case 'didReceiveSettings':
|
||||
if (jsonObj.payload && jsonObj.context) {
|
||||
const newSettings = jsonObj.payload.settings || {};
|
||||
|
||||
// actionType이 없으면 action UUID로 판단해서 강제 설정
|
||||
if (!newSettings.actionType && jsonObj.action) {
|
||||
console.log('⚠️ actionType 없음! action UUID로 재설정:', jsonObj.action);
|
||||
if (jsonObj.action === 'com.mirabox.streamingle.optitrack_marker_toggle') {
|
||||
newSettings.actionType = 'optitrack_marker_toggle';
|
||||
console.log('✅ 마커 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.motion_recording_toggle') {
|
||||
newSettings.actionType = 'motion_recording_toggle';
|
||||
console.log('🎬 모션 녹화 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_reconnect') {
|
||||
newSettings.actionType = 'optitrack_reconnect';
|
||||
console.log('🔄 OptiTrack 재접속 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.facial_motion_reconnect') {
|
||||
newSettings.actionType = 'facial_motion_reconnect';
|
||||
console.log('😊 Facial Motion 재접속 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot') {
|
||||
newSettings.actionType = 'screenshot';
|
||||
console.log('📸 스크린샷 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot_alpha') {
|
||||
newSettings.actionType = 'screenshot_alpha';
|
||||
console.log('📷 알파 스크린샷 버튼으로 강제 설정됨');
|
||||
|
||||
// StreamDock SDK에 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: newSettings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 강제 설정 저장:', newSettings);
|
||||
}
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.item') {
|
||||
newSettings.actionType = 'item';
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.event') {
|
||||
newSettings.actionType = 'event';
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.avatar_outfit') {
|
||||
newSettings.actionType = 'avatar_outfit';
|
||||
} else {
|
||||
newSettings.actionType = 'camera';
|
||||
}
|
||||
}
|
||||
|
||||
buttonContexts.set(jsonObj.context, newSettings);
|
||||
console.log('⚙️ 설정 업데이트:', newSettings);
|
||||
updateButtonTitle(jsonObj.context);
|
||||
}
|
||||
break;
|
||||
case 'willAppear':
|
||||
console.log('👀 버튼 나타남:', jsonObj.context);
|
||||
console.log('============================================');
|
||||
console.log('👀 버튼 나타남 이벤트');
|
||||
console.log('🔍 Context:', jsonObj.context);
|
||||
console.log('🔍 Action UUID:', jsonObj.action);
|
||||
|
||||
|
||||
let settings = jsonObj.payload?.settings || {};
|
||||
console.log('⚙️ 초기 설정:', settings);
|
||||
|
||||
console.log('⚙️ 초기 설정:', JSON.stringify(settings));
|
||||
|
||||
// action UUID로 actionType 결정
|
||||
if (jsonObj.action === 'com.mirabox.streamingle.item') {
|
||||
settings.actionType = 'item';
|
||||
console.log('🎯 아이템 컨트롤러 등록:', jsonObj.context);
|
||||
console.log('🎯 아이템 컨트롤러 등록');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.event') {
|
||||
settings.actionType = 'event';
|
||||
console.log('🎯 이벤트 컨트롤러 등록:', jsonObj.context);
|
||||
console.log('🎯 이벤트 컨트롤러 등록');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.avatar_outfit') {
|
||||
settings.actionType = 'avatar_outfit';
|
||||
console.log('👗 아바타 의상 컨트롤러 등록:', jsonObj.context);
|
||||
console.log('👗 아바타 의상 컨트롤러 등록');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_marker_toggle') {
|
||||
settings.actionType = 'optitrack_marker_toggle';
|
||||
console.log('✅✅✅ MARKER BUTTON DETECTED ✅✅✅');
|
||||
console.log('✅ This is the OptiTrack Marker Toggle button!');
|
||||
console.log('✅ When clicked, it should send: toggle_optitrack_markers');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '마커\nON');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.motion_recording_toggle') {
|
||||
settings.actionType = 'motion_recording_toggle';
|
||||
console.log('🎬🎬🎬 MOTION RECORDING BUTTON DETECTED 🎬🎬🎬');
|
||||
console.log('🎬 This is the Motion Recording Toggle button!');
|
||||
console.log('🎬 When clicked, it should send: toggle_motion_recording');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '녹화\nOFF');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.optitrack_reconnect') {
|
||||
settings.actionType = 'optitrack_reconnect';
|
||||
console.log('🔄🔄🔄 OPTITRACK RECONNECT BUTTON DETECTED 🔄🔄🔄');
|
||||
console.log('🔄 This is the OptiTrack Reconnect button!');
|
||||
console.log('🔄 When clicked, it should send: reconnect_optitrack');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '옵티트랙\n재접속');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.facial_motion_reconnect') {
|
||||
settings.actionType = 'facial_motion_reconnect';
|
||||
console.log('😊😊😊 FACIAL MOTION RECONNECT BUTTON DETECTED 😊😊😊');
|
||||
console.log('😊 This is the Facial Motion Reconnect button!');
|
||||
console.log('😊 When clicked, it should send: reconnect_facial_motion');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '페이셜\n재접속');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot') {
|
||||
settings.actionType = 'screenshot';
|
||||
console.log('📸📸📸 SCREENSHOT BUTTON DETECTED 📸📸📸');
|
||||
console.log('📸 This is the Screenshot button!');
|
||||
console.log('📸 When clicked, it should send: capture_screenshot');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '스크린샷');
|
||||
} else if (jsonObj.action === 'com.mirabox.streamingle.screenshot_alpha') {
|
||||
settings.actionType = 'screenshot_alpha';
|
||||
console.log('📷📷📷 ALPHA SCREENSHOT BUTTON DETECTED 📷📷📷');
|
||||
console.log('📷 This is the Alpha Screenshot button!');
|
||||
console.log('📷 When clicked, it should send: capture_alpha_screenshot');
|
||||
|
||||
// 기본 제목 설정
|
||||
setButtonTitle(jsonObj.context, '알파\n스크린샷');
|
||||
} else {
|
||||
settings.actionType = 'camera';
|
||||
console.log('📹 카메라 컨트롤러 등록:', jsonObj.context);
|
||||
console.log('📹 카메라 컨트롤러 등록 (기본값)');
|
||||
console.log('📹 This button will send camera switch messages');
|
||||
}
|
||||
|
||||
|
||||
console.log('🎯 최종 actionType:', settings.actionType);
|
||||
buttonContexts.set(jsonObj.context, settings);
|
||||
console.log('💾 설정 저장됨:', settings);
|
||||
|
||||
// StreamDock SDK에도 설정 저장
|
||||
if (websocket) {
|
||||
const setSettingsMessage = {
|
||||
event: 'setSettings',
|
||||
context: jsonObj.context,
|
||||
payload: settings
|
||||
};
|
||||
websocket.send(JSON.stringify(setSettingsMessage));
|
||||
console.log('💾 StreamDock SDK에 설정 저장:', settings);
|
||||
}
|
||||
|
||||
console.log('💾 설정 저장 완료');
|
||||
console.log('============================================');
|
||||
updateButtonTitle(jsonObj.context);
|
||||
|
||||
// Unity가 이미 연결되어 있다면 Property Inspector에 상태 전송
|
||||
@ -140,7 +303,8 @@ function connectElgatoStreamDeckSocket(inPort, inUUID, inEvent, inInfo, inAction
|
||||
|
||||
case 'keyUp':
|
||||
console.log('🔘 버튼 클릭됨!');
|
||||
handleButtonClick(jsonObj.context);
|
||||
console.log('🔍 Action UUID:', jsonObj.action);
|
||||
handleButtonClick(jsonObj.context, jsonObj.action);
|
||||
break;
|
||||
|
||||
case 'sendToPlugin':
|
||||
@ -277,47 +441,130 @@ function connectToUnity() {
|
||||
}
|
||||
|
||||
// 버튼 클릭 처리
|
||||
function handleButtonClick(context) {
|
||||
function handleButtonClick(context, actionUUID) {
|
||||
console.log('🎯 버튼 클릭 처리 시작');
|
||||
console.log('📍 컨텍스트:', context);
|
||||
console.log('📍 Action UUID:', actionUUID);
|
||||
console.log('🔌 Unity 연결 상태:', isUnityConnected);
|
||||
|
||||
|
||||
if (!isUnityConnected || !unitySocket) {
|
||||
console.log('🔄 Unity 연결되지 않음, 재연결 시도...');
|
||||
connectToUnity();
|
||||
// 연결 후 잠시 대기한 후 다시 시도
|
||||
setTimeout(() => {
|
||||
if (isUnityConnected && unitySocket) {
|
||||
handleButtonClick(context);
|
||||
handleButtonClick(context, actionUUID);
|
||||
} else {
|
||||
console.error('❌ Unity 재연결 실패');
|
||||
}
|
||||
}, 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// context별 settings 사용
|
||||
const settings = getCurrentSettings(context);
|
||||
const actionType = settings.actionType || 'camera'; // 기본값은 camera
|
||||
|
||||
console.log('🎯 액션 타입:', actionType);
|
||||
|
||||
let actionType = settings.actionType;
|
||||
|
||||
// actionType이 없으면 action UUID로 판단
|
||||
if (!actionType && actionUUID) {
|
||||
console.log('⚠️ actionType 없음! Action UUID로 결정:', actionUUID);
|
||||
if (actionUUID === 'com.mirabox.streamingle.optitrack_marker_toggle') {
|
||||
actionType = 'optitrack_marker_toggle';
|
||||
console.log('✅ 마커 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.motion_recording_toggle') {
|
||||
actionType = 'motion_recording_toggle';
|
||||
console.log('🎬 모션 녹화 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.optitrack_reconnect') {
|
||||
actionType = 'optitrack_reconnect';
|
||||
console.log('🔄 OptiTrack 재접속 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.facial_motion_reconnect') {
|
||||
actionType = 'facial_motion_reconnect';
|
||||
console.log('😊 Facial Motion 재접속 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.screenshot') {
|
||||
actionType = 'screenshot';
|
||||
console.log('📸 스크린샷 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.screenshot_alpha') {
|
||||
actionType = 'screenshot_alpha';
|
||||
console.log('📷 알파 스크린샷 버튼으로 인식');
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.item') {
|
||||
actionType = 'item';
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.event') {
|
||||
actionType = 'event';
|
||||
} else if (actionUUID === 'com.mirabox.streamingle.avatar_outfit') {
|
||||
actionType = 'avatar_outfit';
|
||||
} else {
|
||||
actionType = 'camera';
|
||||
}
|
||||
|
||||
// 설정에 저장
|
||||
settings.actionType = actionType;
|
||||
setCurrentSettings(context, settings);
|
||||
}
|
||||
|
||||
if (!actionType) {
|
||||
actionType = 'camera'; // 최종 폴백
|
||||
}
|
||||
|
||||
console.log('╔════════════════════════════════════════╗');
|
||||
console.log('║ BUTTON CLICKED - DEBUG INFO ║');
|
||||
console.log('╠════════════════════════════════════════╣');
|
||||
console.log(' Context:', context);
|
||||
console.log(' Action UUID:', actionUUID);
|
||||
console.log(' ActionType:', actionType);
|
||||
console.log(' All Settings:', JSON.stringify(settings));
|
||||
console.log('╚════════════════════════════════════════╝');
|
||||
|
||||
switch (actionType) {
|
||||
case 'camera':
|
||||
console.log('➡️ Routing to: CAMERA handler');
|
||||
console.log(' Will send: {"type":"switch_camera","data":{...}}');
|
||||
handleCameraAction(settings);
|
||||
break;
|
||||
case 'item':
|
||||
console.log('➡️ Routing to: ITEM handler');
|
||||
handleItemAction(settings);
|
||||
break;
|
||||
case 'event':
|
||||
console.log('➡️ Routing to: EVENT handler');
|
||||
handleEventAction(settings);
|
||||
break;
|
||||
case 'avatar_outfit':
|
||||
console.log('➡️ Routing to: AVATAR OUTFIT handler');
|
||||
handleAvatarOutfitAction(settings);
|
||||
break;
|
||||
case 'optitrack_marker_toggle':
|
||||
console.log('➡️ Routing to: MARKER TOGGLE handler');
|
||||
console.log(' Will send: {"type":"toggle_optitrack_markers"}');
|
||||
handleOptitrackMarkerToggle(context);
|
||||
break;
|
||||
case 'motion_recording_toggle':
|
||||
console.log('➡️ Routing to: MOTION RECORDING handler');
|
||||
console.log(' Will send: {"type":"toggle_motion_recording"}');
|
||||
handleMotionRecordingToggle(context);
|
||||
break;
|
||||
case 'optitrack_reconnect':
|
||||
console.log('➡️ Routing to: OPTITRACK RECONNECT handler');
|
||||
console.log(' Will send: {"type":"reconnect_optitrack"}');
|
||||
handleOptitrackReconnect(context);
|
||||
break;
|
||||
case 'facial_motion_reconnect':
|
||||
console.log('➡️ Routing to: FACIAL MOTION RECONNECT handler');
|
||||
console.log(' Will send: {"type":"reconnect_facial_motion"}');
|
||||
handleFacialMotionReconnect(context);
|
||||
break;
|
||||
case 'screenshot':
|
||||
console.log('➡️ Routing to: SCREENSHOT handler');
|
||||
console.log(' Will send: {"type":"capture_screenshot"}');
|
||||
handleScreenshot(context);
|
||||
break;
|
||||
case 'screenshot_alpha':
|
||||
console.log('➡️ Routing to: ALPHA SCREENSHOT handler');
|
||||
console.log(' Will send: {"type":"capture_alpha_screenshot"}');
|
||||
handleAlphaScreenshot(context);
|
||||
break;
|
||||
default:
|
||||
console.log('⚠️ 알 수 없는 액션 타입:', actionType);
|
||||
// 기본적으로 카메라 액션으로 처리
|
||||
console.log('⚠️ WARNING: Unknown actionType:', actionType);
|
||||
console.log(' Defaulting to CAMERA handler');
|
||||
handleCameraAction(settings);
|
||||
}
|
||||
}
|
||||
@ -477,6 +724,244 @@ function handleAvatarOutfitAction(settings) {
|
||||
}
|
||||
}
|
||||
|
||||
// OptiTrack 마커 토글 액션 처리
|
||||
function handleOptitrackMarkerToggle(context) {
|
||||
console.log('🎯 OptiTrack 마커 토글 실행');
|
||||
|
||||
// Unity에 마커 토글 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'toggle_optitrack_markers'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 OptiTrack 마커 토글 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 버튼 상태 토글 (0 <-> 1)
|
||||
toggleButtonState(context);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// 버튼 상태 토글 함수
|
||||
function toggleButtonState(context) {
|
||||
const settings = getCurrentSettings(context);
|
||||
const currentState = settings.currentState || 0;
|
||||
const newState = currentState === 0 ? 1 : 0;
|
||||
|
||||
// 설정 업데이트
|
||||
settings.currentState = newState;
|
||||
setCurrentSettings(context, settings);
|
||||
|
||||
// 버튼 상태 변경
|
||||
const stateMessage = {
|
||||
event: 'setState',
|
||||
context: context,
|
||||
payload: {
|
||||
state: newState,
|
||||
target: 0 // hardware and software
|
||||
}
|
||||
};
|
||||
|
||||
if (websocket) {
|
||||
websocket.send(JSON.stringify(stateMessage));
|
||||
console.log('🎨 버튼 상태 업데이트:', newState === 0 ? 'ON' : 'OFF');
|
||||
}
|
||||
|
||||
// 제목도 함께 변경
|
||||
const title = newState === 0 ? '마커\nON' : '마커\nOFF';
|
||||
setButtonTitle(context, title);
|
||||
}
|
||||
|
||||
// 모션 녹화 토글 액션 처리
|
||||
function handleMotionRecordingToggle(context) {
|
||||
console.log('🎬 모션 녹화 토글 실행');
|
||||
|
||||
// Unity에 모션 녹화 토글 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'toggle_motion_recording'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 모션 녹화 토글 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 버튼 상태 토글 (0 <-> 1)
|
||||
toggleMotionRecordingButtonState(context);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// 모션 녹화 버튼 상태 토글 함수
|
||||
function toggleMotionRecordingButtonState(context) {
|
||||
const settings = getCurrentSettings(context);
|
||||
const currentState = settings.currentState || 0;
|
||||
const newState = currentState === 0 ? 1 : 0;
|
||||
|
||||
// 설정 업데이트
|
||||
settings.currentState = newState;
|
||||
setCurrentSettings(context, settings);
|
||||
|
||||
// 버튼 상태 변경
|
||||
const stateMessage = {
|
||||
event: 'setState',
|
||||
context: context,
|
||||
payload: {
|
||||
state: newState,
|
||||
target: 0 // hardware and software
|
||||
}
|
||||
};
|
||||
|
||||
if (websocket) {
|
||||
websocket.send(JSON.stringify(stateMessage));
|
||||
console.log('🎨 녹화 버튼 상태 업데이트:', newState === 0 ? 'OFF' : 'REC');
|
||||
}
|
||||
|
||||
// 제목도 함께 변경
|
||||
const title = newState === 0 ? '녹화\nOFF' : '녹화\nREC';
|
||||
setButtonTitle(context, title);
|
||||
}
|
||||
|
||||
// OptiTrack 재접속 액션 처리
|
||||
function handleOptitrackReconnect(context) {
|
||||
console.log('🔄 OptiTrack 재접속 실행');
|
||||
|
||||
// Unity에 OptiTrack 재접속 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'reconnect_optitrack'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 OptiTrack 재접속 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 재접속은 상태가 없는 단순 액션이므로 버튼 상태 변경 없음
|
||||
// 피드백을 위해 제목을 잠시 변경할 수 있음
|
||||
setButtonTitle(context, '재접속\n중...');
|
||||
|
||||
// 1초 후 원래 제목으로 복구
|
||||
setTimeout(() => {
|
||||
setButtonTitle(context, '옵티트랙\n재접속');
|
||||
}, 1000);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// Facial Motion 재접속 액션 처리
|
||||
function handleFacialMotionReconnect(context) {
|
||||
console.log('😊 Facial Motion 재접속 실행');
|
||||
|
||||
// Unity에 Facial Motion 재접속 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'reconnect_facial_motion'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 Facial Motion 재접속 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 재접속은 상태가 없는 단순 액션이므로 버튼 상태 변경 없음
|
||||
// 피드백을 위해 제목을 잠시 변경할 수 있음
|
||||
setButtonTitle(context, '재접속\n중...');
|
||||
|
||||
// 1초 후 원래 제목으로 복구
|
||||
setTimeout(() => {
|
||||
setButtonTitle(context, '페이셜\n재접속');
|
||||
}, 1000);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// 스크린샷 액션 처리
|
||||
function handleScreenshot(context) {
|
||||
console.log('📸 스크린샷 촬영 실행');
|
||||
|
||||
// Unity에 스크린샷 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'capture_screenshot'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 스크린샷 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 피드백을 위해 제목을 잠시 변경
|
||||
setButtonTitle(context, '촬영\n중...');
|
||||
|
||||
// 1초 후 원래 제목으로 복구
|
||||
setTimeout(() => {
|
||||
setButtonTitle(context, '스크린샷');
|
||||
}, 1000);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// 알파 스크린샷 액션 처리
|
||||
function handleAlphaScreenshot(context) {
|
||||
console.log('📷 알파 스크린샷 촬영 실행');
|
||||
|
||||
// Unity에 알파 스크린샷 요청
|
||||
const message = JSON.stringify({
|
||||
type: 'capture_alpha_screenshot'
|
||||
});
|
||||
|
||||
console.log('📤 Unity에 알파 스크린샷 요청 전송:', message);
|
||||
console.log('🔍 Unity 연결 상태:', isUnityConnected);
|
||||
console.log('🔍 Unity 소켓 상태:', !!unitySocket);
|
||||
|
||||
if (unitySocket && unitySocket.readyState === WebSocket.OPEN) {
|
||||
unitySocket.send(message);
|
||||
console.log('✅ 메시지 전송 완료');
|
||||
|
||||
// 피드백을 위해 제목을 잠시 변경
|
||||
setButtonTitle(context, '촬영\n중...');
|
||||
|
||||
// 1초 후 원래 제목으로 복구
|
||||
setTimeout(() => {
|
||||
setButtonTitle(context, '알파\n스크린샷');
|
||||
}, 1000);
|
||||
} else {
|
||||
console.error('❌ Unity 소켓이 연결되지 않음');
|
||||
console.log('🔄 Unity 재연결 시도...');
|
||||
connectToUnity();
|
||||
}
|
||||
}
|
||||
|
||||
// Property Inspector 메시지 처리
|
||||
function handlePropertyInspectorMessage(payload, context, actionUUID) {
|
||||
const command = payload.command;
|
||||
@ -1243,6 +1728,28 @@ function handleUnityMessage(data) {
|
||||
}
|
||||
}
|
||||
|
||||
// 버튼 제목을 직접 설정하는 함수
|
||||
function setButtonTitle(context, title) {
|
||||
if (!websocket || !context) {
|
||||
console.log('🚫 WebSocket 또는 context 없음 - 제목 설정 건너뜀');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🏷️ 버튼 제목 설정: "${title}" (Context: ${context})`);
|
||||
|
||||
const message = {
|
||||
event: 'setTitle',
|
||||
context: context,
|
||||
payload: {
|
||||
title: title,
|
||||
target: 0 // hardware and software
|
||||
}
|
||||
};
|
||||
|
||||
websocket.send(JSON.stringify(message));
|
||||
console.log('✅ setTitle 메시지 전송 완료');
|
||||
}
|
||||
|
||||
// 모든 버튼의 제목 업데이트
|
||||
function updateAllButtonTitles() {
|
||||
for (const context of buttonContexts.keys()) {
|
||||
@ -1408,8 +1915,40 @@ function updateButtonTitle(context) {
|
||||
console.log('👗 아바타 목록이 없거나 인덱스가 범위를 벗어남');
|
||||
console.log('👗 목록 길이:', avatarOutfitList ? avatarOutfitList.length : 'null');
|
||||
}
|
||||
} else if (actionType === 'optitrack_marker_toggle') {
|
||||
// OptiTrack 마커 토글 버튼
|
||||
const currentState = settings.currentState || 0;
|
||||
title = currentState === 0 ? '마커\nON' : '마커\nOFF';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('🎯 OptiTrack 마커 버튼 제목:', title, '(State:', currentState, ')');
|
||||
} else if (actionType === 'motion_recording_toggle') {
|
||||
// 모션 녹화 토글 버튼
|
||||
const currentState = settings.currentState || 0;
|
||||
title = currentState === 0 ? '녹화\nOFF' : '녹화\nREC';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('🎬 모션 녹화 버튼 제목:', title, '(State:', currentState, ')');
|
||||
} else if (actionType === 'optitrack_reconnect') {
|
||||
// OptiTrack 재접속 버튼
|
||||
title = '옵티트랙\n재접속';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('🔄 OptiTrack 재접속 버튼 제목:', title);
|
||||
} else if (actionType === 'facial_motion_reconnect') {
|
||||
// Facial Motion 재접속 버튼
|
||||
title = '페이셜\n재접속';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('😊 Facial Motion 재접속 버튼 제목:', title);
|
||||
} else if (actionType === 'screenshot') {
|
||||
// 스크린샷 버튼
|
||||
title = '스크린샷';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('📸 스크린샷 버튼 제목:', title);
|
||||
} else if (actionType === 'screenshot_alpha') {
|
||||
// 알파 스크린샷 버튼
|
||||
title = '알파\n스크린샷';
|
||||
isActive = true; // 항상 활성 상태
|
||||
console.log('📷 알파 스크린샷 버튼 제목:', title);
|
||||
}
|
||||
|
||||
|
||||
// StreamDock에 제목 업데이트 요청
|
||||
const message = {
|
||||
event: 'setTitle',
|
||||
|
||||
39
Streamdeck/deploy-plugin.bat
Normal file
39
Streamdeck/deploy-plugin.bat
Normal file
@ -0,0 +1,39 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
|
||||
:: Check for admin rights
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% == 0 (
|
||||
goto :run_script
|
||||
) else (
|
||||
echo Requesting administrator privileges...
|
||||
echo.
|
||||
|
||||
:: Restart as administrator
|
||||
powershell -Command "Start-Process '%~f0' -Verb RunAs"
|
||||
exit /b
|
||||
)
|
||||
|
||||
:run_script
|
||||
echo.
|
||||
echo ========================================
|
||||
echo StreamDeck Plugin Auto Deploy
|
||||
echo ========================================
|
||||
echo.
|
||||
echo Running PowerShell deployment script...
|
||||
echo.
|
||||
|
||||
REM Run PowerShell script with admin rights
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -Command "& '%~dp0deploy-plugin.ps1'"
|
||||
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo.
|
||||
echo ERROR: Deployment failed!
|
||||
echo.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Complete! Window will close in 2 seconds...
|
||||
timeout /t 2 /nobreak >nul
|
||||
166
Streamdeck/deploy-plugin.ps1
Normal file
166
Streamdeck/deploy-plugin.ps1
Normal file
@ -0,0 +1,166 @@
|
||||
# StreamDeck Plugin Auto Deployment Script
|
||||
# Usage: Run .\deploy-plugin.ps1 in PowerShell
|
||||
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " StreamDeck Plugin Auto Deploy" -ForegroundColor Cyan
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Path settings
|
||||
$PluginSourcePath = "$PSScriptRoot\com.mirabox.streamingle.sdPlugin"
|
||||
$PluginDestPath = "$env:APPDATA\Hotspot\StreamDock\plugins\com.mirabox.streamingle.sdPlugin"
|
||||
$StreamDeckExe = "C:\Program Files\Hotspot\StreamDock\StreamDock.exe"
|
||||
|
||||
# Check StreamDock executable path
|
||||
$StreamDeckExeFound = $false
|
||||
if (-not (Test-Path $StreamDeckExe)) {
|
||||
Write-Host "WARNING: StreamDock executable not found: $StreamDeckExe" -ForegroundColor Yellow
|
||||
Write-Host "Checking alternative paths..." -ForegroundColor Yellow
|
||||
|
||||
# Check alternative paths
|
||||
$AlternativePaths = @(
|
||||
"C:\Program Files (x86)\Hotspot\StreamDock\StreamDock.exe",
|
||||
"C:\Program Files\Elgato\StreamDeck\StreamDeck.exe",
|
||||
"$env:ProgramFiles\Hotspot\StreamDock\StreamDock.exe",
|
||||
"${env:ProgramFiles(x86)}\Hotspot\StreamDock\StreamDock.exe",
|
||||
"$env:LOCALAPPDATA\Hotspot\StreamDock\StreamDock.exe",
|
||||
"$env:APPDATA\Hotspot\StreamDock\StreamDock.exe"
|
||||
)
|
||||
|
||||
foreach ($path in $AlternativePaths) {
|
||||
if (Test-Path $path) {
|
||||
$StreamDeckExe = $path
|
||||
$StreamDeckExeFound = $true
|
||||
Write-Host "SUCCESS: StreamDock executable found: $StreamDeckExe" -ForegroundColor Green
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $StreamDeckExeFound) {
|
||||
Write-Host "WARNING: StreamDock executable not found!" -ForegroundColor Yellow
|
||||
Write-Host "Plugin files will be copied. Please restart StreamDock manually." -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
$StreamDeckExeFound = $true
|
||||
}
|
||||
|
||||
# Step 1: Stop StreamDock process
|
||||
Write-Host "Step 1: Stopping StreamDock process..." -ForegroundColor Yellow
|
||||
$StreamDeckProcess = Get-Process -Name "StreamDock" -ErrorAction SilentlyContinue
|
||||
|
||||
if ($StreamDeckProcess) {
|
||||
Write-Host " StreamDock process found (PID: $($StreamDeckProcess.Id))" -ForegroundColor Gray
|
||||
|
||||
try {
|
||||
Stop-Process -Name "StreamDock" -Force -ErrorAction Stop
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Host " SUCCESS: StreamDock process stopped" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " WARNING: Failed to stop process (permission issue)" -ForegroundColor Yellow
|
||||
Write-Host " Continuing... Please close StreamDock manually." -ForegroundColor Yellow
|
||||
Write-Host "" -ForegroundColor Yellow
|
||||
Write-Host " => Right-click StreamDock icon in taskbar -> Exit" -ForegroundColor Cyan
|
||||
Write-Host " => Press any key to continue after closing..." -ForegroundColor Cyan
|
||||
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
|
||||
Write-Host ""
|
||||
}
|
||||
} else {
|
||||
Write-Host " INFO: StreamDock process is not running" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 2: Backup and remove existing plugin folder
|
||||
Write-Host "Step 2: Cleaning up existing plugin folder..." -ForegroundColor Yellow
|
||||
|
||||
if (Test-Path $PluginDestPath) {
|
||||
# Create backup folder
|
||||
$BackupPath = "$PluginDestPath.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
|
||||
Write-Host " Backing up existing plugin: $BackupPath" -ForegroundColor Gray
|
||||
|
||||
try {
|
||||
Move-Item -Path $PluginDestPath -Destination $BackupPath -Force
|
||||
Write-Host " SUCCESS: Existing plugin backed up" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host " WARNING: Backup failed, attempting to delete..." -ForegroundColor Yellow
|
||||
Remove-Item -Path $PluginDestPath -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
} else {
|
||||
Write-Host " INFO: No existing plugin found" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Ensure parent directory exists
|
||||
$PluginDestParent = Split-Path -Parent $PluginDestPath
|
||||
if (-not (Test-Path $PluginDestParent)) {
|
||||
Write-Host " Creating plugin directory: $PluginDestParent" -ForegroundColor Gray
|
||||
New-Item -ItemType Directory -Path $PluginDestParent -Force | Out-Null
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 3: Copy new plugin
|
||||
Write-Host "Step 3: Copying new plugin..." -ForegroundColor Yellow
|
||||
|
||||
if (-not (Test-Path $PluginSourcePath)) {
|
||||
Write-Host " ERROR: Source plugin folder not found: $PluginSourcePath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Host " Copying from: $PluginSourcePath" -ForegroundColor Gray
|
||||
Write-Host " Copying to: $PluginDestPath" -ForegroundColor Gray
|
||||
|
||||
Copy-Item -Path $PluginSourcePath -Destination $PluginDestPath -Recurse -Force
|
||||
|
||||
# Count copied files
|
||||
$CopiedFiles = Get-ChildItem -Path $PluginDestPath -Recurse -File
|
||||
Write-Host " SUCCESS: Plugin copied ($($CopiedFiles.Count) files)" -ForegroundColor Green
|
||||
|
||||
} catch {
|
||||
Write-Host " ERROR: Plugin copy failed: $_" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
|
||||
# Step 4: Restart StreamDock
|
||||
Write-Host "Step 4: Restarting StreamDock..." -ForegroundColor Yellow
|
||||
|
||||
if ($StreamDeckExeFound) {
|
||||
try {
|
||||
Start-Process -FilePath $StreamDeckExe
|
||||
Write-Host " StreamDock started: $StreamDeckExe" -ForegroundColor Gray
|
||||
|
||||
# Wait for process to start
|
||||
Start-Sleep -Seconds 3
|
||||
|
||||
$NewProcess = Get-Process -Name "StreamDock" -ErrorAction SilentlyContinue
|
||||
if ($NewProcess) {
|
||||
Write-Host " SUCCESS: StreamDock restarted (PID: $($NewProcess.Id))" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " WARNING: Cannot verify StreamDock process (may be running in background)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host " ERROR: StreamDock restart failed: $_" -ForegroundColor Red
|
||||
Write-Host " Please start StreamDock manually." -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host " WARNING: StreamDock executable not found, skipping auto-restart." -ForegroundColor Yellow
|
||||
Write-Host " Please restart StreamDock manually:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Right-click StreamDock icon in taskbar -> Exit" -ForegroundColor Gray
|
||||
Write-Host " 2. Search 'StreamDock' in Start Menu and run" -ForegroundColor Gray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host " Deployment Complete!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
Write-Host "Plugin has been updated." -ForegroundColor Green
|
||||
Write-Host "Check new actions in StreamDeck!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Auto-close after 5 seconds
|
||||
Write-Host "Auto-closing in 5 seconds..." -ForegroundColor Gray
|
||||
Start-Sleep -Seconds 5
|
||||
45
Streamdeck/view-logs.bat
Normal file
45
Streamdeck/view-logs.bat
Normal file
@ -0,0 +1,45 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
|
||||
echo ========================================
|
||||
echo StreamDock Plugin Log Viewer
|
||||
echo ========================================
|
||||
echo.
|
||||
|
||||
echo Opening StreamDock log directory...
|
||||
echo.
|
||||
|
||||
set LOG_DIR=%APPDATA%\Hotspot\StreamDock\logs
|
||||
|
||||
if exist "%LOG_DIR%" (
|
||||
echo Log directory found: %LOG_DIR%
|
||||
echo.
|
||||
echo Recent log files:
|
||||
dir /B /O-D "%LOG_DIR%\*.log" 2>nul | findstr /N "^"
|
||||
echo.
|
||||
echo Opening log directory in Explorer...
|
||||
explorer "%LOG_DIR%"
|
||||
echo.
|
||||
echo Opening latest log file in Notepad...
|
||||
for /f "delims=" %%i in ('dir /B /O-D "%LOG_DIR%\*.log" 2^>nul') do (
|
||||
start notepad "%LOG_DIR%\%%i"
|
||||
goto :done
|
||||
)
|
||||
) else (
|
||||
echo WARNING: Log directory not found!
|
||||
echo Checking alternative locations...
|
||||
echo.
|
||||
|
||||
set ALT_LOG=%LOCALAPPDATA%\Hotspot\StreamDock\logs
|
||||
if exist "!ALT_LOG!" (
|
||||
echo Found at: !ALT_LOG!
|
||||
explorer "!ALT_LOG!"
|
||||
) else (
|
||||
echo No logs found. StreamDock may not have been launched yet.
|
||||
)
|
||||
)
|
||||
|
||||
:done
|
||||
echo.
|
||||
echo Press any key to exit...
|
||||
pause >nul
|
||||
Loading…
x
Reference in New Issue
Block a user