From 27ac11e2b4d7ec728d20ec08bd4845cc21406955 Mon Sep 17 00:00:00 2001 From: "qsxft258@gmail.com" Date: Wed, 3 Jun 2026 01:57:50 +0900 Subject: [PATCH] =?UTF-8?q?Fix=20:=20=EB=A6=AC=ED=83=80=EA=B2=9F=ED=8C=85?= =?UTF-8?q?=20=EB=B0=8F=20=EC=98=B5=ED=8B=B0=20=EC=97=B0=EA=B2=B0=EB=B6=80?= =?UTF-8?q?=20=EC=95=88=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Scripts/OptitrackStreamingClient.cs | 92 +++++--- .../StreamingleDashboard/dashboard_script.txt | 4 +- .../CustomRetargetingScript.cs | 220 +++--------------- .../Editor/CustomRetargetingScriptEditor.cs | 105 +-------- .../Editor/RetargetingControlWindow.cs | 154 +----------- Assets/Scripts/KindRetargeting/README.md | 4 +- .../Remote/RetargetingRemoteController.cs | 85 +------ .../Editor/StreamDeckServerManagerEditor.cs | 123 +++++++--- .../UXML/StreamDeckServerManagerEditor.uxml | 1 + .../Streamdeck/NetworkInterfaceUtil.cs | 74 ++++++ .../Streamdeck/NetworkInterfaceUtil.cs.meta | 2 + .../Streamdeck/StreamDeckServerManager.cs | 6 +- .../Streamdeck/StreamingleDashboardServer.cs | 27 +-- 13 files changed, 289 insertions(+), 608 deletions(-) create mode 100644 Assets/Scripts/Streamdeck/NetworkInterfaceUtil.cs create mode 100644 Assets/Scripts/Streamdeck/NetworkInterfaceUtil.cs.meta diff --git a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs index d0bd2be5f..e72d4ec6c 100644 --- a/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs +++ b/Assets/External/OptiTrack Unity Plugin/OptiTrack/Scripts/OptitrackStreamingClient.cs @@ -1768,10 +1768,11 @@ public class OptitrackStreamingClient : MonoBehaviour private System.Collections.IEnumerator ConnectCoroutine() { m_receivedFrameSinceConnect = false; + + // --- 메인 스레드: 주소/모드 파싱 (Unity API 미사용, 즉시 완료) --- IPAddress serverAddr; IPAddress localAddr; NatNetConnectionType connType; - try { serverAddr = IPAddress.Parse( ServerAddress ); @@ -1780,42 +1781,75 @@ public class OptitrackStreamingClient : MonoBehaviour connType = ConnectionType == ClientConnectionType.Unicast ? NatNetConnectionType.NatNetConnectionType_Unicast : NatNetConnectionType.NatNetConnectionType_Multicast; - - m_client = new NatNetClient(); - m_client.Connect( connType, localAddr, serverAddr ); - - // SetProperty는 최초 연결에서만 전송 — 재연결 시 Motive 글로벌 설정 반복 변경 방지 - if ( !m_hasAppliedServerSettings ) - { - // Remotely change the Skeleton Coordinate property to Global/Local - if (SkeletonCoordinates == StreamingCoordinatesValues.Global) - m_client.RequestCommand("SetProperty,,Skeleton Coordinates,false"); - else - m_client.RequestCommand("SetProperty,,Skeleton Coordinates,true"); - - // Remotely change the Bone Naming Convention to Motive/FBX/BVH - if (BoneNamingConvention == OptitrackBoneNameConvention.Motive) - m_client.RequestCommand("SetProperty,,Bone Naming Convention,0"); - else if (BoneNamingConvention == OptitrackBoneNameConvention.FBX) - m_client.RequestCommand("SetProperty,,Bone Naming Convention,1"); - else if (BoneNamingConvention == OptitrackBoneNameConvention.BVH) - m_client.RequestCommand("SetProperty,,Bone Naming Convention,2"); - - m_hasAppliedServerSettings = true; - } - else - { - Debug.Log(GetType().FullName + ": 재연결 — SetProperty 명령 스킵 (최초 연결에서 이미 적용됨).", this); - } } catch ( Exception ex ) { Debug.LogException( ex, this ); Debug.LogError( GetType().FullName + ": Error connecting to server; check your configuration, and make sure the server is currently streaming.", this ); - if (m_client != null) { m_client.Dispose(); m_client = null; } yield break; } + // --- 백그라운드 스레드: 블로킹 네이티브 연결 + SetProperty --- + // NatNet_Client_Connect 는 서버가 즉시 응답하지 않으면 내부 타임아웃까지 블로킹된다. + // 코루틴은 메인 스레드에서 실행되므로 여기서 직접 호출하면 매 재접속마다 화면이 멈춘다. + // Task 로 분리하고 메인 스레드는 매 프레임 양보(yield)하며 완료를 폴링한다. + bool applyServerSettings = !m_hasAppliedServerSettings; + NatNetClient connectedClient = null; + Exception connectError = null; + + System.Threading.Tasks.Task connectTask = System.Threading.Tasks.Task.Run( () => + { + NatNetClient c = null; + try + { + c = new NatNetClient(); + c.Connect( connType, localAddr, serverAddr ); + + // SetProperty는 최초 연결에서만 전송 — 재연결 시 Motive 글로벌 설정 반복 변경 방지 + if ( applyServerSettings ) + { + // Remotely change the Skeleton Coordinate property to Global/Local + if (SkeletonCoordinates == StreamingCoordinatesValues.Global) + c.RequestCommand("SetProperty,,Skeleton Coordinates,false"); + else + c.RequestCommand("SetProperty,,Skeleton Coordinates,true"); + + // Remotely change the Bone Naming Convention to Motive/FBX/BVH + if (BoneNamingConvention == OptitrackBoneNameConvention.Motive) + c.RequestCommand("SetProperty,,Bone Naming Convention,0"); + else if (BoneNamingConvention == OptitrackBoneNameConvention.FBX) + c.RequestCommand("SetProperty,,Bone Naming Convention,1"); + else if (BoneNamingConvention == OptitrackBoneNameConvention.BVH) + c.RequestCommand("SetProperty,,Bone Naming Convention,2"); + } + + connectedClient = c; + } + catch ( Exception ex ) + { + connectError = ex; + if ( c != null ) { try { c.Dispose(); } catch { } } + } + } ); + + // 메인 스레드는 매 프레임 양보하며 연결 완료를 대기 → 렌더링/입력이 멈추지 않음 + while ( !connectTask.IsCompleted ) + yield return null; + + if ( connectError != null || connectedClient == null ) + { + if ( connectError != null ) + Debug.LogException( connectError, this ); + Debug.LogError( GetType().FullName + ": Error connecting to server; check your configuration, and make sure the server is currently streaming.", this ); + yield break; + } + + m_client = connectedClient; + if ( applyServerSettings ) + m_hasAppliedServerSettings = true; + else + Debug.Log(GetType().FullName + ": 재연결 — SetProperty 명령 스킵 (최초 연결에서 이미 적용됨).", this); + // SetProperty 명령이 서버에 적용될 때까지 대기 (재연결 시 SetProperty 스킵했으면 대기 불필요) if (!m_isReconnecting) yield return new UnityEngine.WaitForSeconds( 0.1f ); diff --git a/Assets/Resources/StreamingleDashboard/dashboard_script.txt b/Assets/Resources/StreamingleDashboard/dashboard_script.txt index 1444f84d7..bf45e6d10 100644 --- a/Assets/Resources/StreamingleDashboard/dashboard_script.txt +++ b/Assets/Resources/StreamingleDashboard/dashboard_script.txt @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2d0e24ccf0b33c83b1643a0b69655a9b758cebfe772af2677a06b41e9b90b34 -size 81111 +oid sha256:bd383f4f7eacbdbca0810f2f4f811d8008bcefc9dd2afdd3a2fe292a30d49508 +size 79809 diff --git a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs index 0aa17a9f1..06aa51848 100644 --- a/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs +++ b/Assets/Scripts/KindRetargeting/CustomRetargetingScript.cs @@ -22,28 +22,9 @@ namespace KindRetargeting // IK 컴포넌트 참조 [SerializeField] public TwoBoneIKSolver ikSolver = new TwoBoneIKSolver(); - [Header("힙 위치 보정 (로컬 좌표계 기반)")] - [SerializeField, Range(-1, 1)] - private float hipsOffsetX = 0f; // 캐릭터 기준 좌우 (항상 Right 방향) - [SerializeField, Range(-1, 1)] - private float hipsOffsetY = 0f; // 캐릭터 기준 상하 (항상 Up 방향) - [SerializeField, Range(-1, 1)] - private float hipsOffsetZ = 0f; // 캐릭터 기준 앞뒤 (항상 Forward 방향) - [HideInInspector] public float HipsWeightOffset = 1f; [HideInInspector] public float ChairSeatHeightOffset = 0f; // 의자 좌석 높이 오프셋 (월드 Y 기준) - // 축 매핑: 월드 방향(Right/Up/Forward)을 담당하는 로컬 축을 저장 - // 예: localAxisForWorldRight = (0, 0, 1) 이면 로컬 Z축이 월드 Right 방향을 담당 - // 부호는 방향을 나타냄: (0, 0, -1)이면 로컬 -Z가 월드 Right를 담당 - private Vector3 localAxisForWorldRight = Vector3.right; // 월드 좌우(Right)를 담당하는 로컬 축 - private Vector3 localAxisForWorldUp = Vector3.up; // 월드 상하(Up)를 담당하는 로컬 축 - private Vector3 localAxisForWorldForward = Vector3.forward; // 월드 앞뒤(Forward)를 담당하는 로컬 축 - - [Header("축 정규화 정보 (읽기 전용)")] - [SerializeField] - public Vector3 debugAxisNormalizer = Vector3.one; - /// /// 소스 본 Transform 접근 래퍼 (OptiTrack 매핑 사용) /// @@ -76,13 +57,6 @@ namespace KindRetargeting // HumanBodyBones.LastBone을 이용한 본 순회 범위 private int lastBoneIndex = 55; // 0~54: 몸체 + 손가락 전부 - [Header("무릎 안/밖 조정")] - [SerializeField, Range(-1f, 1f)] - private float kneeInOutWeight = 0f; // 무릎 안/밖 위치 조정 가중치 - [Header("무릎 앞/뒤 조정")] - [SerializeField, Range(-1f, 1f)] - private float kneeFrontBackWeight = 0.4f; // 무릎 앞/뒤 위치 조정 가중치 - [Header("발 IK 위치 조정")] [SerializeField, Range(-1f, 1f)] private float footFrontBackOffset = 0f; // 발 앞뒤 오프셋 (+: 앞, -: 뒤) @@ -153,11 +127,6 @@ namespace KindRetargeting [System.Serializable] private class RetargetingSettings { - public float hipsOffsetX; // 변경: hipsWeight → hipsOffsetX/Y/Z - public float hipsOffsetY; - public float hipsOffsetZ; - public float kneeInOutWeight; - public float kneeFrontBackWeight; public float footFrontBackOffset; // 발 앞뒤 오프셋 public float footInOutOffset; // 발 안쪽/바깥쪽 오프셋 public float floorHeight; @@ -203,9 +172,6 @@ namespace KindRetargeting // IK 타겟 생성 (무릎 시각화 오브젝트 포함) CreateIKTargets(); - // T-포즈 전에 축 정규화 계수 계산 - CalculateAxisNormalizer(); - // 원본 및 대상 아바타를 T-포즈로 복원 // OptiTrack 소스는 Humanoid가 아니므로 현재 포즈를 기준으로 캐싱 if (optitrackSource != null) @@ -275,105 +241,6 @@ namespace KindRetargeting fingerShaped.Initialize(targetAnimator); } - /// - /// 타겟 아바타의 로컬 축과 월드 축의 관계를 분석하여 축 매핑을 계산합니다. - /// T-포즈 상태에서 힙의 각 로컬 축이 월드의 어느 방향을 가리키는지 분석합니다. - /// - /// 예시: - /// - 아바타 A: 로컬 Y가 월드 Up을 가리킴 → localAxisForWorldUp = (0, 1, 0) - /// - 아바타 B: 로컬 Z가 월드 Up을 가리킴 → localAxisForWorldUp = (0, 0, 1) - /// - 아바타 C: 로컬 -Z가 월드 Up을 가리킴 → localAxisForWorldUp = (0, 0, -1) - /// - /// 이를 통해 hipsOffsetY는 항상 "위/아래" 방향으로 작동합니다. - /// - private void CalculateAxisNormalizer() - { - if (targetAnimator == null) return; - - Transform hips = targetAnimator.GetBoneTransform(HumanBodyBones.Hips); - if (hips == null) return; - - // 힙의 각 로컬 축을 월드 공간으로 변환 - Vector3 localXInWorld = hips.TransformDirection(Vector3.right).normalized; - Vector3 localYInWorld = hips.TransformDirection(Vector3.up).normalized; - Vector3 localZInWorld = hips.TransformDirection(Vector3.forward).normalized; - - // 월드 Right(X)에 가장 가까운 로컬 축 찾기 - localAxisForWorldRight = FindBestLocalAxisForWorld(localXInWorld, localYInWorld, localZInWorld, Vector3.right, out string rightAxisName); - - // 월드 Up(Y)에 가장 가까운 로컬 축 찾기 - localAxisForWorldUp = FindBestLocalAxisForWorld(localXInWorld, localYInWorld, localZInWorld, Vector3.up, out string upAxisName); - - // 월드 Forward(Z)에 가장 가까운 로컬 축 찾기 - localAxisForWorldForward = FindBestLocalAxisForWorld(localXInWorld, localYInWorld, localZInWorld, Vector3.forward, out string forwardAxisName); - - // 디버그용: 각 오프셋이 어느 로컬 축에 매핑되는지 표시 - // X: 좌우 오프셋이 사용하는 로컬 축 (1=X, 2=Y, 3=Z, 부호는 방향) - // Y: 상하 오프셋이 사용하는 로컬 축 - // Z: 앞뒤 오프셋이 사용하는 로컬 축 - debugAxisNormalizer = new Vector3( - GetAxisIndex(localAxisForWorldRight), - GetAxisIndex(localAxisForWorldUp), - GetAxisIndex(localAxisForWorldForward) - ); - - Debug.Log($"[{gameObject.name}] 축 매핑 분석 완료:\n" + - $" 월드 Right(좌우) ← 로컬 {rightAxisName} → 매핑: {localAxisForWorldRight}\n" + - $" 월드 Up(상하) ← 로컬 {upAxisName} → 매핑: {localAxisForWorldUp}\n" + - $" 월드 Forward(앞뒤) ← 로컬 {forwardAxisName} → 매핑: {localAxisForWorldForward}"); - } - - /// - /// 축 벡터를 인덱스로 변환합니다 (디버그용). - /// X축=1, Y축=2, Z축=3, 부호는 방향을 나타냄 - /// - private float GetAxisIndex(Vector3 axisVector) - { - if (Mathf.Abs(axisVector.x) > 0.5f) - return 1f * Mathf.Sign(axisVector.x); // X축 - else if (Mathf.Abs(axisVector.y) > 0.5f) - return 2f * Mathf.Sign(axisVector.y); // Y축 - else - return 3f * Mathf.Sign(axisVector.z); // Z축 - } - - /// - /// 세 로컬 축 중에서 목표 월드 방향과 가장 일치하는 축을 찾아 로컬 축 벡터를 반환합니다. - /// - /// 로컬 X축의 월드 방향 - /// 로컬 Y축의 월드 방향 - /// 로컬 Z축의 월드 방향 - /// 비교할 월드 방향 - /// 매칭된 축 이름 (출력용) - /// 해당 월드 방향을 담당하는 로컬 축 벡터 (부호 포함) - private Vector3 FindBestLocalAxisForWorld(Vector3 localXInWorld, Vector3 localYInWorld, Vector3 localZInWorld, Vector3 worldDirection, out string matchedAxisName) - { - float dotX = Vector3.Dot(localXInWorld, worldDirection); - float dotY = Vector3.Dot(localYInWorld, worldDirection); - float dotZ = Vector3.Dot(localZInWorld, worldDirection); - - float absDotX = Mathf.Abs(dotX); - float absDotY = Mathf.Abs(dotY); - float absDotZ = Mathf.Abs(dotZ); - - // 가장 큰 내적값을 가진 축이 해당 월드 방향과 가장 일치하는 축 - if (absDotX >= absDotY && absDotX >= absDotZ) - { - matchedAxisName = dotX > 0 ? "+X (Right)" : "-X (Left)"; - return Vector3.right * Mathf.Sign(dotX); // 로컬 X축 (부호 포함) - } - else if (absDotY >= absDotX && absDotY >= absDotZ) - { - matchedAxisName = dotY > 0 ? "+Y (Up)" : "-Y (Down)"; - return Vector3.up * Mathf.Sign(dotY); // 로컬 Y축 (부호 포함) - } - else - { - matchedAxisName = dotZ > 0 ? "+Z (Forward)" : "-Z (Back)"; - return Vector3.forward * Mathf.Sign(dotZ); // 로컬 Z축 (부호 포함) - } - } - /// /// HumanPoseHandler를 초기화합니다. /// @@ -457,11 +324,6 @@ namespace KindRetargeting var settings = new RetargetingSettings { - hipsOffsetX = hipsOffsetX, - hipsOffsetY = hipsOffsetY, - hipsOffsetZ = hipsOffsetZ, - kneeInOutWeight = kneeInOutWeight, - kneeFrontBackWeight = kneeFrontBackWeight, footFrontBackOffset = footFrontBackOffset, footInOutOffset = footInOutOffset, floorHeight = floorHeight, @@ -515,11 +377,6 @@ namespace KindRetargeting var settings = JsonUtility.FromJson(json); // 설정 적용 - hipsOffsetX = settings.hipsOffsetX; - hipsOffsetY = settings.hipsOffsetY; - hipsOffsetZ = settings.hipsOffsetZ; - kneeInOutWeight = settings.kneeInOutWeight; - kneeFrontBackWeight = settings.kneeFrontBackWeight; footFrontBackOffset = settings.footFrontBackOffset; footInOutOffset = settings.footInOutOffset; floorHeight = settings.floorHeight; @@ -928,44 +785,25 @@ namespace KindRetargeting if (sourceHips != null && targetHips != null) { - // 1. 힙 회전 먼저 동기화 (회전 오프셋 적용) - Quaternion finalHipsRotation = sourceHips.rotation; + // 1. 힙 회전 동기화 (회전 오프셋 적용) if (rotationOffsets.TryGetValue(HumanBodyBones.Hips, out Quaternion hipsOffset)) { - finalHipsRotation = sourceHips.rotation * hipsOffset; - targetHips.rotation = finalHipsRotation; + targetHips.rotation = sourceHips.rotation * hipsOffset; } - // 2. 캐릭터 기준 로컬 오프셋 계산 (축 정규화 적용) - // - // 문제: 아바타마다 힙의 로컬 축 방향이 다름 - // - 아바타 A: 로컬 Y가 "위", 로컬 Z가 "앞" - // - 아바타 B: 로컬 Z가 "위", 로컬 X가 "앞" - // - // 해결: T-포즈에서 계산한 축 매핑을 사용 - // - localAxisForWorldRight: 실제로 "오른쪽"을 가리키는 로컬 축 - // - localAxisForWorldUp: 실제로 "위"를 가리키는 로컬 축 - // - localAxisForWorldForward: 실제로 "앞"을 가리키는 로컬 축 - // - // 이렇게 하면 모든 아바타에서 동일하게 작동합니다. + // 2. 힙 위치 동기화 (수동 힙 위치 보정 제거됨 — 소스 위치 직접 사용) + Vector3 adjustedPosition = sourceHips.position; - // 힙의 현재 회전을 기준으로, 정규화된 방향 벡터 계산 - Vector3 characterRight = finalHipsRotation * localAxisForWorldRight; - Vector3 characterUp = finalHipsRotation * localAxisForWorldUp; - Vector3 characterForward = finalHipsRotation * localAxisForWorldForward; + // 3. 다리 높이 자동 보정 (월드 Y축, 앉기 가중치 HipsWeightOffset 반영) + // 타겟 다리가 소스보다 길면 힙을 올려 발 접지를 맞춘다. + // 매 프레임 계산되어 avatarScale 변경에도 자동 대응한다 + // (다리 분절 길이는 포즈와 무관하게 일정하므로 매 프레임 계산해도 안전). + adjustedPosition.y += ComputeLegHeightOffset() * HipsWeightOffset; - Vector3 characterOffset = - characterRight * (hipsOffsetX * HipsWeightOffset) + // 캐릭터 기준 좌우 - characterUp * (hipsOffsetY * HipsWeightOffset) + // 캐릭터 기준 상하 - characterForward * (hipsOffsetZ * HipsWeightOffset); // 캐릭터 기준 앞뒤 - - // 3. 힙 위치 동기화 + 캐릭터 기준 오프셋 적용 - Vector3 adjustedPosition = sourceHips.position + characterOffset; - - // 4. 바닥 높이 추가 (월드 Y축 - 바닥은 항상 월드 기준) + // 4. 바닥 높이 추가 (월드 Y축) adjustedPosition.y += floorHeight; - // 5. 의자 좌석 높이 오프셋 추가 (월드 Y축 - 로컬 보정과 별개) + // 5. 의자 좌석 높이 오프셋 추가 (월드 Y축) adjustedPosition.y += ChairSeatHeightOffset; targetHips.position = adjustedPosition; @@ -978,6 +816,28 @@ namespace KindRetargeting SyncBoneRotations(skipBone: HumanBodyBones.Hips); } + /// + /// 타겟/소스 왼다리 길이 차이를 반환합니다 (힙 월드 Y 보정용). + /// 타겟 다리가 소스보다 길면 양수 → 힙을 올려 발 접지를 맞춘다. + /// IK 솔버에 캐싱된 다리 본을 재사용하며, 분절 길이는 포즈와 무관하게 일정하므로 + /// 매 프레임 호출해도 안전하고 avatarScale 변화에 자동 대응한다. + /// + private float ComputeLegHeightOffset() + { + var leg = ikSolver?.leftLeg; + if (leg == null) return 0f; + if (leg.upper == null || leg.lower == null || leg.end == null) return 0f; + if (leg.sourceUpper == null || leg.sourceLower == null || leg.sourceEnd == null) return 0f; + + float targetLeg = Vector3.Distance(leg.upper.position, leg.lower.position) + + Vector3.Distance(leg.lower.position, leg.end.position); + float sourceLeg = Vector3.Distance(leg.sourceUpper.position, leg.sourceLower.position) + + Vector3.Distance(leg.sourceLower.position, leg.sourceEnd.position); + + if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; + return targetLeg - sourceLeg; + } + /// /// 힙을 제외한 모든 본의 회전을 오프셋을 적용하여 동기화합니다. /// @@ -1319,9 +1179,7 @@ namespace KindRetargeting sourceIKpoint = bone == HumanBodyBones.LeftLowerLeg ? sourceIKJoints.leftLowerLeg.position : sourceIKJoints.rightLowerLeg.position; - zOffset = kneeFrontBackWeight; // 무릎 앞/뒤 조정 yOffset = floorHeight; - xOffset = kneeInOutWeight * (bone == HumanBodyBones.LeftLowerLeg ? -1f : 1f); // 무릎 안/밖 조정 break; case HumanBodyBones.LeftLowerArm: case HumanBodyBones.RightLowerArm: @@ -1430,18 +1288,6 @@ namespace KindRetargeting return File.Exists(filePath); } - // 무릎 앞/뒤뒤 위치 조정을 위한 public 메서드 추가 - public void SetKneeFrontBackOffset(float offset) - { - kneeFrontBackWeight = offset; - } - - // 무릎 조정을 위한 public 메서드들 - public void SetKneeInOutOffset(float offset) - { - kneeInOutWeight = offset; - } - public void ResetPoseAndCache() { // 캐시 파일 삭제 diff --git a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs index 249550aa4..e9374add3 100644 --- a/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs +++ b/Assets/Scripts/KindRetargeting/Editor/CustomRetargetingScriptEditor.cs @@ -31,19 +31,6 @@ namespace KindRetargeting scaleFoldout.Add(new PropertyField(serializedObject.FindProperty("headScale"), "머리 크기")); root.Add(scaleFoldout); - // ── 힙 위치 보정 ── - root.Add(BuildHipsSection()); - - // ── 무릎 위치 조정 ── - var kneeFoldout = new Foldout { text = "무릎 위치 조정", value = false }; - var kneeFB = new Slider("무릎 앞/뒤", -1f, 1f) { showInputField = true }; - kneeFB.BindProperty(serializedObject.FindProperty("kneeFrontBackWeight")); - kneeFoldout.Add(kneeFB); - var kneeIO = new Slider("무릎 안/밖", -1f, 1f) { showInputField = true }; - kneeIO.BindProperty(serializedObject.FindProperty("kneeInOutWeight")); - kneeFoldout.Add(kneeIO); - root.Add(kneeFoldout); - // ── 발 IK 위치 조정 ── var footFoldout = new Foldout { text = "발 IK 위치 조정", value = false }; var footFB = new Slider("발 앞/뒤", -1f, 1f) { showInputField = true }; @@ -83,58 +70,6 @@ namespace KindRetargeting return root; } - // ========== 힙 위치 보정 ========== - - private VisualElement BuildHipsSection() - { - var foldout = new Foldout { text = "힙 위치 보정 (로컬 좌표계)", value = true }; - - var axisInfo = new HelpBox("플레이 모드에서 축 매핑 정보가 표시됩니다.", HelpBoxMessageType.Info); - foldout.Add(axisInfo); - - foldout.schedule.Execute(() => - { - if (target == null) return; - serializedObject.Update(); - var axisProp = serializedObject.FindProperty("debugAxisNormalizer"); - if (axisProp == null || !Application.isPlaying) { axisInfo.text = "플레이 모드에서 축 매핑 정보가 표시됩니다."; return; } - Vector3 m = axisProp.vector3Value; - if (m == Vector3.one) { axisInfo.text = "플레이 모드에서 축 매핑 정보가 표시됩니다."; return; } - string A(float v) => Mathf.RoundToInt(Mathf.Abs(v)) switch { 1 => (v > 0 ? "+X" : "-X"), 2 => (v > 0 ? "+Y" : "-Y"), 3 => (v > 0 ? "+Z" : "-Z"), _ => "?" }; - axisInfo.text = $"축 매핑: 좌우→{A(m.x)} 상하→{A(m.y)} 앞뒤→{A(m.z)}"; - }).Every(500); - - var hx = new Slider("← 좌우 →", -1f, 1f) { showInputField = true }; - hx.BindProperty(serializedObject.FindProperty("hipsOffsetX")); - foldout.Add(hx); - var hy = new Slider("↓ 상하 ↑", -1f, 1f) { showInputField = true }; - hy.BindProperty(serializedObject.FindProperty("hipsOffsetY")); - foldout.Add(hy); - var hz = new Slider("← 앞뒤 →", -1f, 1f) { showInputField = true }; - hz.BindProperty(serializedObject.FindProperty("hipsOffsetZ")); - foldout.Add(hz); - - // 의자 앉기 높이 - var chairSlider = new Slider("의자 앉기 높이", -1f, 1f) { showInputField = true, tooltip = "의자에 앉을 때 엉덩이 높이 조정" }; - chairSlider.BindProperty(serializedObject.FindProperty("limbWeight.chairSeatHeightOffset")); - foldout.Add(chairSlider); - - // 다리 길이 자동 보정 버튼 - var autoHipsBtn = new Button(() => - { - if (!Application.isPlaying) { Debug.LogWarning("플레이 모드에서만 사용 가능합니다."); return; } - var script = (CustomRetargetingScript)target; - float offset = CalculateHipsOffsetFromLegDifference(script); - serializedObject.FindProperty("hipsOffsetY").floatValue = offset; - serializedObject.ApplyModifiedProperties(); - script.SaveSettings(); - }) { text = "다리 길이 자동 보정", tooltip = "소스/타겟 다리 길이 차이로 힙 상하 오프셋을 자동 계산합니다." }; - autoHipsBtn.style.marginTop = 4; autoHipsBtn.style.height = 25; - foldout.Add(autoHipsBtn); - - return foldout; - } - // ========== 머리 회전 오프셋 ========== private VisualElement BuildHeadRotationSection() @@ -216,6 +151,11 @@ namespace KindRetargeting serializedObject.FindProperty("limbWeight.footHeightMaxThreshold"), 0.1f, 1f)); + // 의자 앉기 높이 + var chairSlider = new Slider("의자 앉기 높이", -1f, 1f) { showInputField = true, tooltip = "의자에 앉을 때 엉덩이 높이 조정 (월드 Y 기준)" }; + chairSlider.BindProperty(serializedObject.FindProperty("limbWeight.chairSeatHeightOffset")); + foldout.Add(chairSlider); + return foldout; } @@ -345,7 +285,7 @@ namespace KindRetargeting { if (!Application.isPlaying) { Debug.LogWarning("플레이 모드에서만 사용 가능합니다."); return; } AutoCalibrateAll((CustomRetargetingScript)target, serializedObject); - }) { text = "전체 자동 보정 (크기 + 힙 높이 + 머리 정면)", tooltip = "아바타 크기, 힙 높이, 머리 정면을 자동 보정합니다." }; + }) { text = "전체 자동 보정 (크기 + 머리 정면)", tooltip = "아바타 크기와 머리 정면을 자동 보정합니다. 힙 높이는 매 프레임 다리 길이로 자동 유지됩니다." }; autoBtn.style.marginTop = 4; autoBtn.style.height = 28; box.Add(autoBtn); @@ -419,7 +359,6 @@ namespace KindRetargeting script.ResetScale(); so.FindProperty("avatarScale").floatValue = 1f; - so.FindProperty("hipsOffsetY").floatValue = CalculateHipsOffsetFromLegDifference(script); so.ApplyModifiedProperties(); EditorApplication.delayCall += () => @@ -440,7 +379,6 @@ namespace KindRetargeting { if (script == null) return; var so3 = new SerializedObject(script); - so3.FindProperty("hipsOffsetY").floatValue = CalculateHipsOffsetFromLegDifference(script); var xP = so3.FindProperty("headRotationOffsetX"); var yP = so3.FindProperty("headRotationOffsetY"); @@ -457,37 +395,6 @@ namespace KindRetargeting // ========== 유틸리티 ========== - private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) - { - var source = script.optitrackSource; - Animator targetAnim = script.targetAnimator; - if (source == null || targetAnim == null) return 0f; - - float sourceLeg = GetSourceLegLength(source); - float targetLeg = GetLegLength(targetAnim); - if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; - - return targetLeg - sourceLeg; - } - - private float GetLegLength(Animator animator) - { - Transform upper = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lower = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); - if (upper == null || lower == null || foot == null) return 0f; - return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); - } - - private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) - { - Transform upper = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lower = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); - if (upper == null || lower == null || foot == null) return 0f; - return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); - } - private void CalibrateHeadToForward(SerializedObject so, SerializedProperty xProp, SerializedProperty yProp, SerializedProperty zProp) { CustomRetargetingScript script = so.targetObject as CustomRetargetingScript; diff --git a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs index 70f61aaca..35f5b9b0d 100644 --- a/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs +++ b/Assets/Scripts/KindRetargeting/Editor/RetargetingControlWindow.cs @@ -195,22 +195,6 @@ public class RetargetingControlWindow : EditorWindow // 가중치 설정 panel.Add(BuildWeightSection(script, so)); - // 힙 위치 보정 - panel.Add(BuildHipsSection(script, so)); - - // 무릎 위치 조정 - var kneeFoldout = new Foldout { text = "무릎 위치 조정", value = false }; - var kneeContainer = new VisualElement(); - var kneeFB = new Slider("무릎 앞/뒤 가중치", -1f, 1f) { showInputField = true }; - kneeFB.BindProperty(so.FindProperty("kneeFrontBackWeight")); - kneeContainer.Add(kneeFB); - var kneeIO = new Slider("무릎 안/밖 가중치", -1f, 1f) { showInputField = true }; - kneeIO.BindProperty(so.FindProperty("kneeInOutWeight")); - kneeContainer.Add(kneeIO); - kneeFoldout.Add(kneeContainer); - kneeContainer.Bind(so); - panel.Add(kneeFoldout); - // 발 IK 위치 조정 var footFoldout = new Foldout { text = "발 IK 위치 조정", value = false }; var footContainer = new VisualElement(); @@ -245,16 +229,6 @@ public class RetargetingControlWindow : EditorWindow if (headScaleProp != null) scaleContainer.Add(new PropertyField(headScaleProp, "머리 크기")); - // 아바타 크기 변경 시 다리 길이 자동 보정 (실시간) - scaleContainer.TrackPropertyValue(so.FindProperty("avatarScale"), _ => - { - if (!Application.isPlaying || script == null) return; - var sox = new SerializedObject(script); - sox.FindProperty("hipsOffsetY").floatValue = CalculateHipsOffsetFromLegDifference(script); - sox.ApplyModifiedProperties(); - sox.Dispose(); - }); - scaleContainer.Bind(so); scaleFoldout.Add(scaleContainer); panel.Add(scaleFoldout); @@ -331,71 +305,11 @@ public class RetargetingControlWindow : EditorWindow var smoothField = new PropertyField(so.FindProperty("limbWeight.weightSmoothSpeed"), "가중치 변화 속도"); container.Add(smoothField); - foldout.Add(container); - return foldout; - } - - // ========== Hips Settings ========== - - private VisualElement BuildHipsSection(CustomRetargetingScript script, SerializedObject so) - { - var foldout = new Foldout { text = "힙 위치 보정 (로컬)", value = false }; - var container = new VisualElement(); - - // 축 매핑 정보 - var axisLabel = new Label { style = { fontSize = 10, color = new Color(0.6f, 0.6f, 0.6f) } }; - container.Add(axisLabel); - - container.schedule.Execute(() => - { - try { if (so == null || so.targetObject == null) return; } - catch (System.Exception) { return; } - so.Update(); - var normProp = so.FindProperty("debugAxisNormalizer"); - if (normProp == null || !Application.isPlaying) { axisLabel.text = ""; return; } - Vector3 m = normProp.vector3Value; - if (m == Vector3.one) { axisLabel.text = ""; return; } - string A(float v) => Mathf.RoundToInt(Mathf.Abs(v)) switch { 1 => (v > 0 ? "+X" : "-X"), 2 => (v > 0 ? "+Y" : "-Y"), 3 => (v > 0 ? "+Z" : "-Z"), _ => "?" }; - axisLabel.text = $"축 매핑: 좌우→{A(m.x)} 상하→{A(m.y)} 앞뒤→{A(m.z)}"; - }).Every(500); - - var hx = new Slider("← 좌우 →", -1f, 1f) { showInputField = true }; - hx.BindProperty(so.FindProperty("hipsOffsetX")); - container.Add(hx); - - var hy = new Slider("↓ 상하 ↑", -1f, 1f) { showInputField = true }; - hy.BindProperty(so.FindProperty("hipsOffsetY")); - container.Add(hy); - - var hz = new Slider("← 앞뒤 →", -1f, 1f) { showInputField = true }; - hz.BindProperty(so.FindProperty("hipsOffsetZ")); - container.Add(hz); - // 의자 앉기 높이 var chairSlider = new Slider("의자 앉기 높이", -1f, 1f) { showInputField = true, tooltip = "의자에 앉을 때 엉덩이 높이 조정 (월드 Y 기준)" }; chairSlider.BindProperty(so.FindProperty("limbWeight.chairSeatHeightOffset")); container.Add(chairSlider); - // 다리 길이 자동 보정 버튼 - var autoHipsBtn = new Button(() => - { - if (!Application.isPlaying) - { - Debug.LogWarning("다리 길이 자동 보정은 플레이 모드에서만 사용 가능합니다."); - return; - } - float offset = CalculateHipsOffsetFromLegDifference(script); - var hipsProp = so.FindProperty("hipsOffsetY"); - hipsProp.floatValue = offset; - so.ApplyModifiedProperties(); - script.SaveSettings(); - Debug.Log($"자동 보정 완료: hipsOffsetY = {offset:F4}"); - }) { text = "다리 길이 자동 보정", tooltip = "소스/타겟 다리 길이 차이로 힙 상하 오프셋을 자동 계산합니다. (플레이 모드 전용)" }; - autoHipsBtn.style.marginTop = 4; - autoHipsBtn.style.height = 25; - container.Add(autoHipsBtn); - - container.Bind(so); foldout.Add(container); return foldout; } @@ -836,7 +750,8 @@ public class RetargetingControlWindow : EditorWindow // ========== Auto Full Calibration ========== /// - /// 소스/타겟 목 높이 비율로 avatarScale을 맞추고, 다리 길이 차이로 hipsOffsetY를 보정합니다. + /// 소스/타겟 목 높이 비율로 avatarScale을 맞추고 머리 정면을 보정합니다. + /// 힙 높이는 매 프레임 다리 길이 자동 보정이 처리하므로 여기서 건드리지 않습니다. /// private void AutoCalibrateAll(CustomRetargetingScript script, SerializedObject so) { @@ -849,11 +764,10 @@ public class RetargetingControlWindow : EditorWindow return; } - // ── 프레임 1: 스케일 리셋 + 다리 보정 ── + // ── 프레임 1: 스케일 리셋 ── script.ResetScale(); var scaleProp = so.FindProperty("avatarScale"); scaleProp.floatValue = 1f; - so.FindProperty("hipsOffsetY").floatValue = CalculateHipsOffsetFromLegDifference(script); so.ApplyModifiedProperties(); // ── 프레임 2: 리타게팅 반영 후 목 높이 측정 → avatarScale 설정 ── @@ -885,8 +799,6 @@ public class RetargetingControlWindow : EditorWindow if (script == null) return; var so3 = new SerializedObject(script); - float finalHipsOffset = CalculateHipsOffsetFromLegDifference(script); - so3.FindProperty("hipsOffsetY").floatValue = finalHipsOffset; var xProp = so3.FindProperty("headRotationOffsetX"); var yProp = so3.FindProperty("headRotationOffsetY"); @@ -898,69 +810,11 @@ public class RetargetingControlWindow : EditorWindow so3.Dispose(); script.SaveSettings(); - Debug.Log($"전체 자동 보정 완료: avatarScale = {scaleRatio:F3}, hipsOffsetY = {finalHipsOffset:F4}m"); + Debug.Log($"전체 자동 보정 완료: avatarScale = {scaleRatio:F3} (힙 높이는 자동 유지)"); }; }; } - // ========== Auto Hips Offset ========== - - /// - /// 소스/타겟 다리 길이 차이로 힙 상하 오프셋을 계산합니다. - /// 타겟 다리가 소스보다 짧으면 → 양수 (힙을 올려서 다리를 펴줌) - /// 타겟 다리가 소스보다 길면 → 음수 (힙을 내려서 다리를 펴줌) - /// - private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) - { - var source = script.optitrackSource; - Animator target = script.targetAnimator; - - if (source == null || target == null || !target.isHuman) - { - Debug.LogWarning("소스 OptiTrack 또는 타겟 Animator가 설정되지 않았습니다."); - return 0f; - } - - float sourceLeg = GetSourceLegLength(source); - float targetLeg = GetLegLength(target); - - if (sourceLeg < 0.01f || targetLeg < 0.01f) - { - Debug.LogWarning("다리 길이를 계산할 수 없습니다. 본이 올바르게 설정되어 있는지 확인해주세요."); - return 0f; - } - - // 소스 다리가 더 길면 타겟이 뜨므로 힙을 내려야 함 (음수) - // 소스 다리가 더 짧으면 타겟 다리가 구부러지므로 힙을 올려야 함 (양수) - float diff = targetLeg - sourceLeg; - Debug.Log($"소스 다리 길이: {sourceLeg:F4}, 타겟 다리 길이: {targetLeg:F4}, 힙 오프셋: {diff:F4}m"); - return diff; - } - - private float GetLegLength(Animator animator) - { - Transform upperLeg = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lowerLeg = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); - - if (upperLeg == null || lowerLeg == null || foot == null) return 0f; - - return Vector3.Distance(upperLeg.position, lowerLeg.position) - + Vector3.Distance(lowerLeg.position, foot.position); - } - - private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) - { - Transform upperLeg = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lowerLeg = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); - - if (upperLeg == null || lowerLeg == null || foot == null) return 0f; - - return Vector3.Distance(upperLeg.position, lowerLeg.position) - + Vector3.Distance(lowerLeg.position, foot.position); - } - private void CalibrateHeadToForward(SerializedObject so, SerializedProperty xProp, SerializedProperty yProp, SerializedProperty zProp) { CustomRetargetingScript script = so.targetObject as CustomRetargetingScript; diff --git a/Assets/Scripts/KindRetargeting/README.md b/Assets/Scripts/KindRetargeting/README.md index 131cb3bd5..b14b8515d 100644 --- a/Assets/Scripts/KindRetargeting/README.md +++ b/Assets/Scripts/KindRetargeting/README.md @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:567b19df1dd4fa2f6eea49fcc8e5c6287318a3caf51cdeb8a692a4e2c2733019 -size 13245 +oid sha256:63ca6b78d97b25a22e218f9f22cfa654b816c1ee05e536bcb3ad6d307df3f0b6 +size 18375 diff --git a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs index fef4d3414..1395b77a8 100644 --- a/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs +++ b/Assets/Scripts/KindRetargeting/Remote/RetargetingRemoteController.cs @@ -166,13 +166,6 @@ namespace KindRetargeting.Remote } break; - case "autoHipsOffset": - { - string charId = json["characterId"]?.ToString(); - AutoHipsOffset(charId); - } - break; - case "autoCalibrateAll": { string charId = json["characterId"]?.ToString(); @@ -233,15 +226,6 @@ namespace KindRetargeting.Remote var data = new Dictionary { - // 힙 위치 보정 (로컬) - { "hipsVertical", GetPrivateField(script, "hipsOffsetY") }, - { "hipsForward", GetPrivateField(script, "hipsOffsetZ") }, - { "hipsHorizontal", GetPrivateField(script, "hipsOffsetX") }, - - // 무릎 위치 조정 - { "kneeFrontBackWeight", GetPrivateField(script, "kneeFrontBackWeight") }, - { "kneeInOutWeight", GetPrivateField(script, "kneeInOutWeight") }, - // 발 IK 위치 조정 { "feetForwardBackward", GetPrivateField(script, "footFrontBackOffset") }, { "feetNarrow", GetPrivateField(script, "footInOutOffset") }, @@ -316,25 +300,6 @@ namespace KindRetargeting.Remote switch (property) { - // 힙 위치 보정 - case "hipsVertical": - SetPrivateField(script, "hipsOffsetY", value); - break; - case "hipsForward": - SetPrivateField(script, "hipsOffsetZ", value); - break; - case "hipsHorizontal": - SetPrivateField(script, "hipsOffsetX", value); - break; - - // 무릎 위치 조정 - case "kneeFrontBackWeight": - SetPrivateField(script, "kneeFrontBackWeight", value); - break; - case "kneeInOutWeight": - SetPrivateField(script, "kneeInOutWeight", value); - break; - // 발 IK 위치 조정 case "feetForwardBackward": SetPrivateField(script, "footFrontBackOffset", value); @@ -570,19 +535,6 @@ namespace KindRetargeting.Remote SendStatus(true, "정면 캘리브레이션 완료"); } - private void AutoHipsOffset(string characterId) - { - var script = FindCharacter(characterId); - if (script == null) return; - - float offset = CalculateHipsOffsetFromLegDifference(script); - SetPrivateField(script, "hipsOffsetY", offset); - script.SaveSettings(); - - SendCharacterData(characterId); - SendStatus(true, $"다리 길이 자동 보정 완료: hipsOffsetY={offset:F4}"); - } - private void AutoCalibrateAll(string characterId) { var script = FindCharacter(characterId); @@ -596,10 +548,9 @@ namespace KindRetargeting.Remote return; } - // Step 1: 크기 초기화 + 힙 오프셋 계산 + // Step 1: 크기 초기화 (힙 높이는 매 프레임 다리 길이 자동 보정이 처리) script.ResetScale(); SetPrivateField(script, "avatarScale", 1f); - SetPrivateField(script, "hipsOffsetY", CalculateHipsOffsetFromLegDifference(script)); // Step 2: 1프레임 후 목 높이 비율로 크기 조정 StartCoroutine(AutoCalibrateCoroutine(script, characterId)); @@ -626,8 +577,7 @@ namespace KindRetargeting.Remote yield return null; // 1프레임 대기 - // Step 3: 힙 오프셋 재계산 + 머리 정면 캘리브레이션 - SetPrivateField(script, "hipsOffsetY", CalculateHipsOffsetFromLegDifference(script)); + // Step 3: 머리 정면 캘리브레이션 (힙 높이는 매 프레임 다리 길이 자동 보정이 처리) script.CalibrateHeadToForward(); script.SaveSettings(); @@ -635,37 +585,6 @@ namespace KindRetargeting.Remote SendStatus(true, $"전체 자동 보정 완료: avatarScale={scaleRatio:F3}"); } - private float CalculateHipsOffsetFromLegDifference(CustomRetargetingScript script) - { - var source = script.optitrackSource; - Animator targetAnim = script.targetAnimator; - if (source == null || targetAnim == null) return 0f; - - float sourceLeg = GetSourceLegLength(source); - float targetLeg = GetLegLength(targetAnim); - if (sourceLeg < 0.01f || targetLeg < 0.01f) return 0f; - - return targetLeg - sourceLeg; - } - - private float GetSourceLegLength(OptitrackSkeletonAnimator_Mingle source) - { - Transform upper = source.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lower = source.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = source.GetBoneTransform(HumanBodyBones.LeftFoot); - if (upper == null || lower == null || foot == null) return 0f; - return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); - } - - private float GetLegLength(Animator animator) - { - Transform upper = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg); - Transform lower = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg); - Transform foot = animator.GetBoneTransform(HumanBodyBones.LeftFoot); - if (upper == null || lower == null || foot == null) return 0f; - return Vector3.Distance(upper.position, lower.position) + Vector3.Distance(lower.position, foot.position); - } - private CustomRetargetingScript FindCharacter(string characterId) { foreach (var script in registeredCharacters) diff --git a/Assets/Scripts/Streamdeck/Editor/StreamDeckServerManagerEditor.cs b/Assets/Scripts/Streamdeck/Editor/StreamDeckServerManagerEditor.cs index 1745ad3b3..e87985226 100644 --- a/Assets/Scripts/Streamdeck/Editor/StreamDeckServerManagerEditor.cs +++ b/Assets/Scripts/Streamdeck/Editor/StreamDeckServerManagerEditor.cs @@ -2,8 +2,8 @@ using UnityEngine; using UnityEditor; using UnityEngine.UIElements; using UnityEditor.UIElements; -using System.Net; -using System.Net.Sockets; +using System.Collections.Generic; +using System.Linq; [CustomEditor(typeof(StreamDeckServerManager))] public class StreamDeckServerManagerEditor : Editor @@ -12,12 +12,15 @@ public class StreamDeckServerManagerEditor : Editor private const string UssPath = "Assets/Scripts/Streamdeck/Editor/UXML/StreamDeckServerManagerEditor.uss"; private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss"; + private const string AutoOption = "자동 (auto)"; + private StreamDeckServerManager manager; private VisualElement playStatusContainer; private Label playStatusLabel; private Label lanIPLabel; private Label dashboardUrlLabel; private VisualElement dashboardPortField; + private VisualElement networkAdapterField; public override VisualElement CreateInspectorGUI() { @@ -41,23 +44,24 @@ public class StreamDeckServerManagerEditor : Editor lanIPLabel = root.Q