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