Fix : 여러 디테일 요소 버그 제거
This commit is contained in:
parent
b4e680334c
commit
860be78b34
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
BIN
Assets/Resources/StreamingleDashboard/dashboard_script.txt
(Stored with Git LFS)
Binary file not shown.
BIN
Assets/Resources/StreamingleDashboard/dashboard_style.txt
(Stored with Git LFS)
BIN
Assets/Resources/StreamingleDashboard/dashboard_style.txt
(Stored with Git LFS)
Binary file not shown.
@ -244,6 +244,17 @@ public class RetargetingControlWindow : EditorWindow
|
||||
var headScaleProp = so.FindProperty("headScale");
|
||||
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);
|
||||
|
||||
@ -6,11 +6,13 @@ using KindRetargeting;
|
||||
public class SimplePoseTransfer : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public struct TargetEntry
|
||||
public class TargetEntry
|
||||
{
|
||||
public Animator animator;
|
||||
[Tooltip("월드 공간 힙 오프셋")]
|
||||
public Vector3 hipOffset;
|
||||
[Tooltip("소스의 루트 스케일 변화량을 타겟에도 적용")]
|
||||
public bool syncRootScale = true;
|
||||
}
|
||||
|
||||
[Header("Pose Transfer Settings")]
|
||||
@ -32,6 +34,10 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
private CustomRetargetingScript sourceRetargetingScript;
|
||||
private Vector3[] originalTargetHeadScales;
|
||||
|
||||
// 루트 스케일 관련
|
||||
private Vector3 originalSourceRootScale;
|
||||
private Vector3[] originalTargetRootScales;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
Init();
|
||||
@ -83,13 +89,19 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
// 소스에서 CustomRetargetingScript 찾기
|
||||
sourceRetargetingScript = sourceBone.GetComponent<CustomRetargetingScript>();
|
||||
|
||||
// 타겟들의 원본 머리 스케일 저장
|
||||
// 소스 루트 스케일 저장
|
||||
originalSourceRootScale = sourceBone.transform.localScale;
|
||||
|
||||
// 타겟들의 원본 머리/루트 스케일 저장
|
||||
originalTargetHeadScales = new Vector3[targets.Count];
|
||||
originalTargetRootScales = new Vector3[targets.Count];
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
Animator animator = targets[i].animator;
|
||||
if (animator != null)
|
||||
{
|
||||
originalTargetRootScales[i] = animator.transform.localScale;
|
||||
|
||||
Transform headBone = animator.GetBoneTransform(HumanBodyBones.Head);
|
||||
if (headBone != null)
|
||||
{
|
||||
@ -100,6 +112,10 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
originalTargetHeadScales[i] = Vector3.one;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
originalTargetRootScales[i] = Vector3.one;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,6 +198,12 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
// 루트 회전 동기화
|
||||
targetAnimator.transform.rotation = sourceBone.transform.rotation;
|
||||
|
||||
// 루트 스케일 동기화 (타겟별 on/off)
|
||||
if (entry.syncRootScale)
|
||||
{
|
||||
ApplyRootScale(targetIndex, targetAnimator.transform);
|
||||
}
|
||||
|
||||
// 모든 본에 대해 포즈 전송
|
||||
for (int i = 0; i < 55; i++)
|
||||
{
|
||||
@ -208,6 +230,19 @@ public class SimplePoseTransfer : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyRootScale(int targetIndex, Transform targetRoot)
|
||||
{
|
||||
// 소스의 초기 루트 스케일 대비 현재 스케일 비율을 타겟 원본 스케일에 곱해 적용
|
||||
Vector3 sourceCurrent = sourceBone.transform.localScale;
|
||||
Vector3 sourceOriginal = originalSourceRootScale;
|
||||
Vector3 ratio = new Vector3(
|
||||
sourceOriginal.x != 0f ? sourceCurrent.x / sourceOriginal.x : 1f,
|
||||
sourceOriginal.y != 0f ? sourceCurrent.y / sourceOriginal.y : 1f,
|
||||
sourceOriginal.z != 0f ? sourceCurrent.z / sourceOriginal.z : 1f
|
||||
);
|
||||
targetRoot.localScale = Vector3.Scale(originalTargetRootScales[targetIndex], ratio);
|
||||
}
|
||||
|
||||
private void ApplyHeadScale(int targetIndex, Transform targetHeadBone)
|
||||
{
|
||||
if (sourceRetargetingScript != null)
|
||||
|
||||
@ -7,6 +7,9 @@ using Streamingle;
|
||||
using Newtonsoft.Json;
|
||||
using NiloToon.NiloToonURP;
|
||||
using Streamingle.Effects;
|
||||
#if MAGICACLOTH2
|
||||
using MagicaCloth2;
|
||||
#endif
|
||||
|
||||
public class AvatarOutfitController : MonoBehaviour, IController
|
||||
{
|
||||
@ -89,22 +92,50 @@ public class AvatarOutfitController : MonoBehaviour, IController
|
||||
[Header("Transform Effect (이 의상으로 변경할 때 재생)")]
|
||||
public TransformEffectSettings transformEffect = new TransformEffectSettings();
|
||||
|
||||
#if MAGICACLOTH2
|
||||
// NiloToon renderCharacter 토글 시 GameObject 가 Active 유지되어 MagicaCloth 가 계속 시뮬되는 문제 방지.
|
||||
// BuildSimulationCache 로 1회 수집 후 Apply/Remove 에서 cloth.enabled 토글.
|
||||
[NonSerialized] private MagicaCloth[][] _clothingSimCache;
|
||||
[NonSerialized] private MagicaCloth[][] _hideSimCache;
|
||||
[NonSerialized] private bool _simCacheBuilt;
|
||||
#endif
|
||||
|
||||
public void BuildSimulationCache()
|
||||
{
|
||||
#if MAGICACLOTH2
|
||||
if (_simCacheBuilt) return;
|
||||
_clothingSimCache = CollectCloths(clothingObjects);
|
||||
_hideSimCache = CollectCloths(hideObjects);
|
||||
_simCacheBuilt = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void ApplyOutfit()
|
||||
{
|
||||
LogToggle("Apply", clothingObjects, true, hideObjects, false);
|
||||
foreach (var obj in clothingObjects)
|
||||
SetRenderOrActive(obj, true);
|
||||
foreach (var obj in hideObjects)
|
||||
SetRenderOrActive(obj, false);
|
||||
ApplyArrayState(clothingObjects, true, clothingSide: true);
|
||||
ApplyArrayState(hideObjects, false, clothingSide: false);
|
||||
}
|
||||
|
||||
public void RemoveOutfit()
|
||||
{
|
||||
LogToggle("Remove", clothingObjects, false, hideObjects, true);
|
||||
foreach (var obj in clothingObjects)
|
||||
SetRenderOrActive(obj, false);
|
||||
foreach (var obj in hideObjects)
|
||||
SetRenderOrActive(obj, true);
|
||||
ApplyArrayState(clothingObjects, false, clothingSide: true);
|
||||
ApplyArrayState(hideObjects, true, clothingSide: false);
|
||||
}
|
||||
|
||||
void ApplyArrayState(GameObject[] objs, bool value, bool clothingSide)
|
||||
{
|
||||
if (objs == null) return;
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
{
|
||||
SetRenderOrActive(objs[i], value);
|
||||
#if MAGICACLOTH2
|
||||
var cache = clothingSide ? _clothingSimCache : _hideSimCache;
|
||||
if (cache != null && i < cache.Length)
|
||||
SetClothsEnabled(cache[i], value);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void LogToggle(string op, GameObject[] a, bool av, GameObject[] b, bool bv)
|
||||
@ -136,6 +167,29 @@ public class AvatarOutfitController : MonoBehaviour, IController
|
||||
else
|
||||
obj.SetActive(value);
|
||||
}
|
||||
|
||||
#if MAGICACLOTH2
|
||||
static MagicaCloth[][] CollectCloths(GameObject[] objs)
|
||||
{
|
||||
if (objs == null) return Array.Empty<MagicaCloth[]>();
|
||||
var arr = new MagicaCloth[objs.Length][];
|
||||
for (int i = 0; i < objs.Length; i++)
|
||||
arr[i] = objs[i] != null
|
||||
? objs[i].GetComponentsInChildren<MagicaCloth>(true)
|
||||
: Array.Empty<MagicaCloth>();
|
||||
return arr;
|
||||
}
|
||||
|
||||
static void SetClothsEnabled(MagicaCloth[] cloths, bool value)
|
||||
{
|
||||
if (cloths == null) return;
|
||||
foreach (var cloth in cloths)
|
||||
{
|
||||
if (cloth != null && cloth.enabled != value)
|
||||
cloth.enabled = value;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -143,6 +197,9 @@ public class AvatarOutfitController : MonoBehaviour, IController
|
||||
#region Events
|
||||
public delegate void AvatarOutfitChangedEventHandler(AvatarData avatar, OutfitData oldOutfit, OutfitData newOutfit);
|
||||
public event AvatarOutfitChangedEventHandler OnAvatarOutfitChanged;
|
||||
|
||||
/// <summary>UI 등 상태 구독자용 통합 이벤트. 의상 스왑이 실제로 완료된 시점에 발생 (변신 연출 중에는 아직 발생하지 않음).</summary>
|
||||
public event System.Action OnStateChanged;
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
@ -199,12 +256,24 @@ public class AvatarOutfitController : MonoBehaviour, IController
|
||||
if (avatars.Count > 0 && currentAvatar == null)
|
||||
currentAvatar = avatars[0];
|
||||
|
||||
BuildAllSimulationCaches();
|
||||
|
||||
if (syncOutfitStateOnAwake)
|
||||
SyncInitialOutfitState();
|
||||
|
||||
Debug.Log($"[AvatarOutfitController] 총 {avatars.Count}개의 아바타가 등록되었습니다.");
|
||||
}
|
||||
|
||||
private void BuildAllSimulationCaches()
|
||||
{
|
||||
foreach (var avatar in avatars)
|
||||
{
|
||||
if (avatar?.outfits == null) continue;
|
||||
foreach (var outfit in avatar.outfits)
|
||||
outfit?.BuildSimulationCache();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>모든 아바타의 모든 의상을 먼저 숨긴 뒤, 각 아바타의 currentOutfit 만 표시. 초기 씬 상태 정리용.</summary>
|
||||
public void SyncInitialOutfitState()
|
||||
{
|
||||
@ -401,6 +470,7 @@ public class AvatarOutfitController : MonoBehaviour, IController
|
||||
#region StreamDeck Integration
|
||||
private void NotifyAvatarOutfitChanged(AvatarData avatar)
|
||||
{
|
||||
OnStateChanged?.Invoke();
|
||||
if (streamDeckManager != null)
|
||||
streamDeckManager.NotifyAvatarOutfitChanged();
|
||||
}
|
||||
|
||||
@ -127,6 +127,9 @@ public class CameraManager : MonoBehaviour, IController
|
||||
#region Events
|
||||
public delegate void CameraChangedEventHandler(CameraPreset oldPreset, CameraPreset newPreset);
|
||||
public event CameraChangedEventHandler OnCameraChanged;
|
||||
|
||||
/// <summary>UI 등 상태 구독자용 통합 이벤트. 프리셋 전환/블렌드 토글/드론 모드 등 모든 상태 변경 후 발생.</summary>
|
||||
public event System.Action OnStateChanged;
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
@ -360,6 +363,7 @@ public class CameraManager : MonoBehaviour, IController
|
||||
|
||||
private void NotifyStreamDeckCameraStateChanged()
|
||||
{
|
||||
OnStateChanged?.Invoke();
|
||||
if (streamDeckManager != null)
|
||||
{
|
||||
streamDeckManager.NotifyCameraChanged();
|
||||
@ -887,12 +891,8 @@ public class CameraManager : MonoBehaviour, IController
|
||||
}
|
||||
}
|
||||
|
||||
// 스트림덱에 카메라 변경 알림 전송
|
||||
if (streamDeckManager != null)
|
||||
{
|
||||
streamDeckManager.NotifyCameraChanged();
|
||||
}
|
||||
|
||||
// 스트림덱 / UI 등 모든 구독자에게 상태 변경 알림
|
||||
NotifyStreamDeckCameraStateChanged();
|
||||
}
|
||||
|
||||
private bool ValidateCameraIndex(int index)
|
||||
|
||||
@ -46,6 +46,9 @@ public class ItemController : MonoBehaviour, IController
|
||||
#region Events
|
||||
public delegate void ItemGroupChangedEventHandler(ItemGroup oldGroup, ItemGroup newGroup);
|
||||
public event ItemGroupChangedEventHandler OnItemGroupChanged;
|
||||
|
||||
/// <summary>UI 등 상태 구독자용 통합 이벤트. 모든 그룹 활성/비활성/토글/추가/제거 후 발생.</summary>
|
||||
public event System.Action OnStateChanged;
|
||||
#endregion
|
||||
|
||||
#region Fields
|
||||
@ -225,6 +228,7 @@ public class ItemController : MonoBehaviour, IController
|
||||
#region StreamDeck Integration
|
||||
private void NotifyItemChanged()
|
||||
{
|
||||
OnStateChanged?.Invoke();
|
||||
if (streamDeckManager != null)
|
||||
{
|
||||
var updateMessage = new
|
||||
|
||||
@ -42,6 +42,12 @@ public class RuntimeControlPanelManager
|
||||
|
||||
private bool initStarted;
|
||||
|
||||
// 컨트롤러 상태 변경 이벤트 구독 여부 (최초 manager 바인딩 시 한 번만 연결)
|
||||
private bool controllersSubscribed;
|
||||
private Action cameraStateHandler;
|
||||
private Action itemStateHandler;
|
||||
private Action avatarStateHandler;
|
||||
|
||||
public void Initialize(Transform parent, Action<string> log, Action<string> logError)
|
||||
{
|
||||
this.log = log;
|
||||
@ -149,6 +155,7 @@ public class RuntimeControlPanelManager
|
||||
{
|
||||
manager = StreamDeckServerManager.Instance;
|
||||
if (manager == null) return;
|
||||
SubscribeControllerEvents();
|
||||
SwitchCategory(currentCategory);
|
||||
}
|
||||
|
||||
@ -169,6 +176,45 @@ public class RuntimeControlPanelManager
|
||||
SwitchCategory(currentCategory);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Controller State Subscription
|
||||
// ================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 컨트롤러의 상태 변경 이벤트를 구독. 클릭 후 즉시 UI를 다시 그리는 대신
|
||||
/// 실제 상태가 갱신된 시점에만 현재 카테고리를 다시 빌드한다.
|
||||
/// (예: 아바타 변신 연출 코루틴 완료 시점, Cinemachine 프리셋 전환 후 등)
|
||||
/// </summary>
|
||||
private void SubscribeControllerEvents()
|
||||
{
|
||||
if (controllersSubscribed || manager == null) return;
|
||||
|
||||
if (manager.cameraManager != null)
|
||||
{
|
||||
cameraStateHandler = () => OnControllerStateChanged("camera");
|
||||
manager.cameraManager.OnStateChanged += cameraStateHandler;
|
||||
}
|
||||
if (manager.itemController != null)
|
||||
{
|
||||
itemStateHandler = () => OnControllerStateChanged("item");
|
||||
manager.itemController.OnStateChanged += itemStateHandler;
|
||||
}
|
||||
if (manager.avatarOutfitController != null)
|
||||
{
|
||||
avatarStateHandler = () => OnControllerStateChanged("avatar");
|
||||
manager.avatarOutfitController.OnStateChanged += avatarStateHandler;
|
||||
}
|
||||
|
||||
controllersSubscribed = true;
|
||||
}
|
||||
|
||||
private void OnControllerStateChanged(string sourceCategory)
|
||||
{
|
||||
if (!isInitialized || !isVisible) return;
|
||||
if (currentCategory != sourceCategory) return;
|
||||
SwitchCategory(currentCategory);
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Category Switching
|
||||
// ================================================================
|
||||
@ -222,11 +268,7 @@ public class RuntimeControlPanelManager
|
||||
var blendBtn = MakeButton(
|
||||
$"Blend: {styleName} ▶ {(isCut ? "EaseInOut" : "Cut")}로 전환",
|
||||
isCut ? "action-btn--secondary" : "action-btn--success");
|
||||
blendBtn.clicked += () =>
|
||||
{
|
||||
cam.ToggleDefaultBlend();
|
||||
SwitchCategory("camera");
|
||||
};
|
||||
blendBtn.clicked += () => cam.ToggleDefaultBlend();
|
||||
blendRow.Add(blendBtn);
|
||||
actionList.Add(blendRow);
|
||||
|
||||
@ -239,11 +281,7 @@ public class RuntimeControlPanelManager
|
||||
|
||||
var btn = MakeButton("Switch", preset.isActive ? "action-btn--success" : null);
|
||||
int idx = preset.index;
|
||||
btn.clicked += () =>
|
||||
{
|
||||
cam.Set(idx);
|
||||
SwitchCategory("camera");
|
||||
};
|
||||
btn.clicked += () => cam.Set(idx);
|
||||
item.Add(btn);
|
||||
actionList.Add(item);
|
||||
}
|
||||
@ -270,11 +308,11 @@ public class RuntimeControlPanelManager
|
||||
topRow.AddToClassList("action-row");
|
||||
|
||||
var allOnBtn = MakeButton("All On", "action-btn--success");
|
||||
allOnBtn.clicked += () => { ctrl.ActivateAllGroups(); SwitchCategory("item"); };
|
||||
allOnBtn.clicked += () => ctrl.ActivateAllGroups();
|
||||
topRow.Add(allOnBtn);
|
||||
|
||||
var allOffBtn = MakeButton("All Off", "action-btn--secondary");
|
||||
allOffBtn.clicked += () => { ctrl.DeactivateAllGroups(); SwitchCategory("item"); };
|
||||
allOffBtn.clicked += () => ctrl.DeactivateAllGroups();
|
||||
topRow.Add(allOffBtn);
|
||||
|
||||
actionList.Add(topRow);
|
||||
@ -295,11 +333,7 @@ public class RuntimeControlPanelManager
|
||||
|
||||
var btn = MakeButton("Toggle");
|
||||
int idx = itemData.index;
|
||||
btn.clicked += () =>
|
||||
{
|
||||
ctrl.ToggleGroup(idx);
|
||||
SwitchCategory("item");
|
||||
};
|
||||
btn.clicked += () => ctrl.ToggleGroup(idx);
|
||||
item.Add(btn);
|
||||
actionList.Add(item);
|
||||
}
|
||||
@ -378,11 +412,7 @@ public class RuntimeControlPanelManager
|
||||
|
||||
int avatarIdx = avatar.index;
|
||||
int outfitIdx = outfit.index;
|
||||
btn.clicked += () =>
|
||||
{
|
||||
ctrl.SetAvatarOutfit(avatarIdx, outfitIdx);
|
||||
SwitchCategory("avatar");
|
||||
};
|
||||
btn.clicked += () => ctrl.SetAvatarOutfit(avatarIdx, outfitIdx);
|
||||
outfitRow.Add(btn);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user