- BackgroundSceneLoaderWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - PropBrowserWindow: OnGUI → CreateGUI (Toolbar + ToolbarSearchField) - StreamingleCommon.uss: 브라우저 공통 스타일 추가 (그리드/리스트/뷰토글/액션바/상태바) - excludeFromWeb 상태 새로고침 시 보존 수정 - 삭제된 배경 리소스 정리 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
150 lines
5.2 KiB
C#
150 lines
5.2 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using UnityEngine.UIElements;
|
|
using UnityEditor.UIElements;
|
|
using System.IO;
|
|
using UniHumanoid;
|
|
using UniGLTF;
|
|
|
|
[System.Serializable]
|
|
public class HumanPoseData
|
|
{
|
|
public Vector3 bodyPosition;
|
|
public Quaternion bodyRotation;
|
|
public float[] muscles;
|
|
}
|
|
|
|
public class HumanPoseClipApplier : EditorWindow
|
|
{
|
|
private const string CommonUssPath = "Assets/Scripts/Streamingle/StreamingleControl/Editor/UXML/StreamingleCommon.uss";
|
|
|
|
private ObjectField animatorField;
|
|
private ObjectField poseClipField;
|
|
private TextField jsonPathField;
|
|
private Toggle useJsonToggle;
|
|
private VisualElement jsonContainer;
|
|
private VisualElement clipContainer;
|
|
|
|
[MenuItem("Tools/Animation Tools/포즈 클립 적용기")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<HumanPoseClipApplier>("포즈 클립 적용기");
|
|
}
|
|
|
|
public void CreateGUI()
|
|
{
|
|
var root = rootVisualElement;
|
|
root.AddToClassList("tool-root");
|
|
|
|
var commonUss = AssetDatabase.LoadAssetAtPath<StyleSheet>(CommonUssPath);
|
|
if (commonUss != null) root.styleSheets.Add(commonUss);
|
|
|
|
root.Add(new Label("휴먼 포즈 클립 적용") { name = "title" });
|
|
root.Q<Label>("title").AddToClassList("tool-title");
|
|
|
|
animatorField = new ObjectField("타겟 Animator") { objectType = typeof(Animator), allowSceneObjects = true };
|
|
root.Add(animatorField);
|
|
|
|
useJsonToggle = new Toggle("JSON 파일 사용") { value = false };
|
|
useJsonToggle.RegisterValueChangedCallback(evt =>
|
|
{
|
|
jsonContainer.style.display = evt.newValue ? DisplayStyle.Flex : DisplayStyle.None;
|
|
clipContainer.style.display = evt.newValue ? DisplayStyle.None : DisplayStyle.Flex;
|
|
});
|
|
root.Add(useJsonToggle);
|
|
|
|
// JSON mode
|
|
jsonContainer = new VisualElement { style = { display = DisplayStyle.None } };
|
|
jsonPathField = new TextField("JSON 파일 경로");
|
|
jsonContainer.Add(jsonPathField);
|
|
var browseBtn = new Button(() =>
|
|
{
|
|
string path = EditorUtility.OpenFilePanel("JSON 파일 선택", "", "json");
|
|
if (!string.IsNullOrEmpty(path)) jsonPathField.value = path;
|
|
}) { text = "파일 선택" };
|
|
jsonContainer.Add(browseBtn);
|
|
root.Add(jsonContainer);
|
|
|
|
// Clip mode
|
|
clipContainer = new VisualElement();
|
|
poseClipField = new ObjectField("포즈 클립") { objectType = typeof(HumanPoseClip), allowSceneObjects = false };
|
|
clipContainer.Add(poseClipField);
|
|
root.Add(clipContainer);
|
|
|
|
var applyBtn = new Button(ApplyPoseClip) { text = "포즈 적용" };
|
|
applyBtn.style.height = 30;
|
|
applyBtn.style.marginTop = 12;
|
|
applyBtn.AddToClassList("btn-primary");
|
|
root.Add(applyBtn);
|
|
|
|
root.schedule.Execute(() =>
|
|
{
|
|
bool canApply = animatorField.value != null &&
|
|
((useJsonToggle.value && !string.IsNullOrEmpty(jsonPathField.value)) ||
|
|
(!useJsonToggle.value && poseClipField.value != null));
|
|
applyBtn.SetEnabled(canApply);
|
|
}).Every(200);
|
|
}
|
|
|
|
private void ApplyPoseClip()
|
|
{
|
|
var targetAnimator = animatorField.value as Animator;
|
|
if (targetAnimator == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "Animator를 선택해주세요.", "확인");
|
|
return;
|
|
}
|
|
|
|
HumanPose pose;
|
|
if (useJsonToggle.value)
|
|
{
|
|
string jsonPath = jsonPathField.value;
|
|
if (string.IsNullOrEmpty(jsonPath) || !File.Exists(jsonPath))
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "유효한 JSON 파일을 선택해주세요.", "확인");
|
|
return;
|
|
}
|
|
string jsonContent = File.ReadAllText(jsonPath);
|
|
HumanPoseData poseData = JsonUtility.FromJson<HumanPoseData>(jsonContent);
|
|
pose = new HumanPose
|
|
{
|
|
bodyPosition = poseData.bodyPosition,
|
|
bodyRotation = poseData.bodyRotation,
|
|
muscles = poseData.muscles
|
|
};
|
|
}
|
|
else
|
|
{
|
|
var poseClip = poseClipField.value as HumanPoseClip;
|
|
if (poseClip == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "포즈 클립을 선택해주세요.", "확인");
|
|
return;
|
|
}
|
|
pose = poseClip.GetPose();
|
|
}
|
|
|
|
var avatar = targetAnimator.avatar;
|
|
if (avatar == null || !avatar.isHuman)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "Humanoid Avatar가 필요합니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
var handler = new HumanPoseHandler(avatar, targetAnimator.transform);
|
|
handler.SetHumanPose(ref pose);
|
|
EditorUtility.SetDirty(targetAnimator);
|
|
|
|
Debug.Log("포즈 클립이 적용되었습니다.");
|
|
EditorUtility.DisplayDialog("성공", "포즈 클립이 적용되었습니다.", "확인");
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
Debug.LogError($"포즈 적용 오류: {e.Message}");
|
|
EditorUtility.DisplayDialog("오류", $"포즈 적용 실패: {e.Message}", "확인");
|
|
}
|
|
}
|
|
}
|