Add : 모션 레코더 버그 패치 싱글톤에서 멀티로 변경
This commit is contained in:
parent
9dc2d4d64f
commit
98d207583a
@ -1,11 +1,13 @@
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using System.IO;
|
||||||
using Entum;
|
using Entum;
|
||||||
|
|
||||||
namespace EasyMotionRecorder
|
namespace EasyMotionRecorder
|
||||||
{
|
{
|
||||||
[CustomEditor(typeof(ObjectMotionRecorder))]
|
[CustomEditor(typeof(ObjectMotionRecorder))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
public class ObjectMotionRecorderEditor : Editor
|
public class ObjectMotionRecorderEditor : Editor
|
||||||
{
|
{
|
||||||
private ObjectMotionRecorder recorder;
|
private ObjectMotionRecorder recorder;
|
||||||
@ -19,6 +21,12 @@ namespace EasyMotionRecorder
|
|||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
|
if (targets.Length > 1)
|
||||||
|
{
|
||||||
|
DrawMultiObjectGUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
@ -46,6 +54,143 @@ namespace EasyMotionRecorder
|
|||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectGUI()
|
||||||
|
{
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField($"오브젝트 모션 레코더 ({targets.Length}개 선택됨)", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 상태 표시
|
||||||
|
DrawMultiObjectStatus();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 설정
|
||||||
|
DrawMultiObjectSettings();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 액션
|
||||||
|
DrawMultiObjectActions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectStatus()
|
||||||
|
{
|
||||||
|
int recordingCount = 0;
|
||||||
|
foreach (ObjectMotionRecorder recorder in targets)
|
||||||
|
{
|
||||||
|
if (recorder.IsRecording) recordingCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField("레코딩 상태:", GUILayout.Width(100));
|
||||||
|
|
||||||
|
if (recordingCount == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("○ 모두 대기 중", EditorStyles.boldLabel);
|
||||||
|
}
|
||||||
|
else if (recordingCount == targets.Length)
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("● 모두 녹화 중", EditorStyles.boldLabel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField($"◐ 일부 녹화 중 ({recordingCount}/{targets.Length})", EditorStyles.boldLabel);
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectSettings()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
showRecordingSettings = EditorGUILayout.Foldout(showRecordingSettings, "레코딩 설정 (모든 선택된 오브젝트에 적용)");
|
||||||
|
|
||||||
|
if (showRecordingSettings)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
// 키 설정
|
||||||
|
var startKeyProp = serializedObject.FindProperty("recordStartKey");
|
||||||
|
var stopKeyProp = serializedObject.FindProperty("recordStopKey");
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = startKeyProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
int startKeyIndex = EditorGUILayout.Popup("시작 키", startKeyProp.enumValueIndex, startKeyProp.enumDisplayNames);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
startKeyProp.enumValueIndex = startKeyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = stopKeyProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
int stopKeyIndex = EditorGUILayout.Popup("정지 키", stopKeyProp.enumValueIndex, stopKeyProp.enumDisplayNames);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
stopKeyProp.enumValueIndex = stopKeyIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FPS 설정
|
||||||
|
var fpsProp = serializedObject.FindProperty("targetFPS");
|
||||||
|
EditorGUI.showMixedValue = fpsProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
float fps = EditorGUILayout.FloatField("타겟 FPS", fpsProp.floatValue);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
fpsProp.floatValue = fps;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = false;
|
||||||
|
|
||||||
|
if (fpsProp.floatValue <= 0 && !fpsProp.hasMultipleDifferentValues)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("FPS가 0 이하면 제한 없이 녹화됩니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectActions()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("멀티 오브젝트 액션", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
// 모든 레코더 시작
|
||||||
|
if (GUILayout.Button("모든 레코더 시작", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
foreach (ObjectMotionRecorder recorder in targets)
|
||||||
|
{
|
||||||
|
if (!recorder.IsRecording && recorder.TargetObjects.Length > 0)
|
||||||
|
{
|
||||||
|
recorder.StartRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 모든 레코더 정지
|
||||||
|
if (GUILayout.Button("모든 레코더 정지", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
foreach (ObjectMotionRecorder recorder in targets)
|
||||||
|
{
|
||||||
|
if (recorder.IsRecording)
|
||||||
|
{
|
||||||
|
recorder.StopRecording();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// SavePathManager 안내
|
||||||
|
EditorGUILayout.HelpBox("저장 경로 및 자동 출력 설정은 각 오브젝트의 SavePathManager 컴포넌트에서 관리됩니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawRecordingStatus()
|
private void DrawRecordingStatus()
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
@ -188,6 +333,19 @@ namespace EasyMotionRecorder
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// SavePathManager 안내
|
||||||
|
var savePathManager = recorder.GetComponent<SavePathManager>();
|
||||||
|
if (savePathManager != null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("저장 경로 및 자동 출력 설정은 SavePathManager 컴포넌트에서 관리됩니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("SavePathManager 컴포넌트가 없습니다. SavePathManager를 추가해주세요.", MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSelectedObject()
|
private void AddSelectedObject()
|
||||||
@ -199,35 +357,27 @@ namespace EasyMotionRecorder
|
|||||||
targetsProp.arraySize++;
|
targetsProp.arraySize++;
|
||||||
var newElement = targetsProp.GetArrayElementAtIndex(targetsProp.arraySize - 1);
|
var newElement = targetsProp.GetArrayElementAtIndex(targetsProp.arraySize - 1);
|
||||||
newElement.objectReferenceValue = selected.transform;
|
newElement.objectReferenceValue = selected.transform;
|
||||||
|
EditorUtility.SetDirty(recorder);
|
||||||
Debug.Log($"오브젝트 추가: {selected.name}");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EditorUtility.DisplayDialog("오류", "선택된 오브젝트가 없습니다.", "확인");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddSelectedObjects()
|
private void AddSelectedObjects()
|
||||||
{
|
{
|
||||||
var selectedObjects = Selection.gameObjects;
|
var selectedObjects = Selection.gameObjects;
|
||||||
if (selectedObjects.Length == 0)
|
if (selectedObjects.Length > 0)
|
||||||
{
|
{
|
||||||
EditorUtility.DisplayDialog("오류", "선택된 오브젝트가 없습니다.", "확인");
|
var targetsProp = serializedObject.FindProperty("targetObjects");
|
||||||
return;
|
int startIndex = targetsProp.arraySize;
|
||||||
|
targetsProp.arraySize += selectedObjects.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedObjects.Length; i++)
|
||||||
|
{
|
||||||
|
var element = targetsProp.GetArrayElementAtIndex(startIndex + i);
|
||||||
|
element.objectReferenceValue = selectedObjects[i].transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(recorder);
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetsProp = serializedObject.FindProperty("targetObjects");
|
|
||||||
int startIndex = targetsProp.arraySize;
|
|
||||||
targetsProp.arraySize += selectedObjects.Length;
|
|
||||||
|
|
||||||
for (int i = 0; i < selectedObjects.Length; i++)
|
|
||||||
{
|
|
||||||
var element = targetsProp.GetArrayElementAtIndex(startIndex + i);
|
|
||||||
element.objectReferenceValue = selectedObjects[i].transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Log($"{selectedObjects.Length}개 오브젝트 추가됨");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,18 +6,45 @@ using System.IO;
|
|||||||
namespace EasyMotionRecorder
|
namespace EasyMotionRecorder
|
||||||
{
|
{
|
||||||
[CustomEditor(typeof(SavePathManager))]
|
[CustomEditor(typeof(SavePathManager))]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
public class SavePathManagerEditor : Editor
|
public class SavePathManagerEditor : Editor
|
||||||
{
|
{
|
||||||
private SavePathManager savePathManager;
|
private SavePathManager savePathManager;
|
||||||
private bool showAdvancedSettings = false;
|
private bool showAdvancedSettings = false;
|
||||||
|
|
||||||
|
// SerializedProperty 참조
|
||||||
|
private SerializedProperty motionSavePathProp;
|
||||||
|
private SerializedProperty createSubdirectoriesProp;
|
||||||
|
private SerializedProperty exportHumanoidOnSaveProp;
|
||||||
|
private SerializedProperty exportGenericOnSaveProp;
|
||||||
|
private SerializedProperty exportFBXAsciiOnSaveProp;
|
||||||
|
private SerializedProperty exportFBXBinaryOnSaveProp;
|
||||||
|
private SerializedProperty instanceIDProp;
|
||||||
|
private SerializedProperty useDontDestroyOnLoadProp;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
savePathManager = (SavePathManager)target;
|
savePathManager = (SavePathManager)target;
|
||||||
|
|
||||||
|
// SerializedProperty 초기화
|
||||||
|
motionSavePathProp = serializedObject.FindProperty("motionSavePath");
|
||||||
|
createSubdirectoriesProp = serializedObject.FindProperty("createSubdirectories");
|
||||||
|
exportHumanoidOnSaveProp = serializedObject.FindProperty("exportHumanoidOnSave");
|
||||||
|
exportGenericOnSaveProp = serializedObject.FindProperty("exportGenericOnSave");
|
||||||
|
exportFBXAsciiOnSaveProp = serializedObject.FindProperty("exportFBXAsciiOnSave");
|
||||||
|
exportFBXBinaryOnSaveProp = serializedObject.FindProperty("exportFBXBinaryOnSave");
|
||||||
|
instanceIDProp = serializedObject.FindProperty("instanceID");
|
||||||
|
useDontDestroyOnLoadProp = serializedObject.FindProperty("useDontDestroyOnLoad");
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
|
if (targets.Length > 1)
|
||||||
|
{
|
||||||
|
DrawMultiObjectGUI();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
@ -34,19 +61,53 @@ namespace EasyMotionRecorder
|
|||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 자동 출력 옵션
|
||||||
|
DrawAutoExportSettings();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
// 버튼들
|
// 버튼들
|
||||||
DrawActionButtons();
|
DrawActionButtons();
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.LabelField($"저장 경로 관리 ({targets.Length}개 선택됨)", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 기본 설정
|
||||||
|
DrawMultiObjectBasicSettings();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 고급 설정
|
||||||
|
DrawMultiObjectAdvancedSettings();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 자동 출력 옵션
|
||||||
|
DrawMultiObjectAutoExportSettings();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
|
// 멀티 오브젝트 액션
|
||||||
|
DrawMultiObjectActions();
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawBasicSettings()
|
private void DrawBasicSettings()
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("기본 설정", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("기본 설정", EditorStyles.boldLabel);
|
||||||
|
|
||||||
// 통합 저장 경로 (모든 파일이 같은 위치에 저장됨)
|
// 통합 저장 경로
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
string motionPath = EditorGUILayout.TextField("저장 경로", savePathManager.GetMotionSavePath());
|
EditorGUILayout.PropertyField(motionSavePathProp, new GUIContent("저장 경로"));
|
||||||
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
||||||
{
|
{
|
||||||
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", "Assets", "");
|
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", "Assets", "");
|
||||||
@ -57,12 +118,14 @@ namespace EasyMotionRecorder
|
|||||||
{
|
{
|
||||||
newPath = "Assets" + newPath.Substring(Application.dataPath.Length);
|
newPath = "Assets" + newPath.Substring(Application.dataPath.Length);
|
||||||
}
|
}
|
||||||
|
motionSavePathProp.stringValue = newPath;
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
savePathManager.SetMotionSavePath(newPath);
|
savePathManager.SetMotionSavePath(newPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.HelpBox("모션, 페이스, 제네릭 애니메이션 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
EditorGUILayout.HelpBox("모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawAdvancedSettings()
|
private void DrawAdvancedSettings()
|
||||||
@ -74,16 +137,33 @@ namespace EasyMotionRecorder
|
|||||||
EditorGUI.indentLevel++;
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
// 서브디렉토리 생성 여부
|
// 서브디렉토리 생성 여부
|
||||||
bool createSubdirectories = EditorGUILayout.Toggle("서브디렉토리 자동 생성",
|
EditorGUILayout.PropertyField(createSubdirectoriesProp, new GUIContent("서브디렉토리 자동 생성"));
|
||||||
serializedObject.FindProperty("createSubdirectories").boolValue);
|
|
||||||
serializedObject.FindProperty("createSubdirectories").boolValue = createSubdirectories;
|
|
||||||
|
|
||||||
EditorGUILayout.HelpBox("현재 모든 파일이 동일한 경로에 저장됩니다.", MessageType.Info);
|
// 인스턴스 ID (읽기 전용)
|
||||||
|
GUI.enabled = false;
|
||||||
|
EditorGUILayout.PropertyField(instanceIDProp, new GUIContent("인스턴스 ID (자동 생성)"));
|
||||||
|
GUI.enabled = true;
|
||||||
|
|
||||||
|
// DontDestroyOnLoad 설정
|
||||||
|
EditorGUILayout.PropertyField(useDontDestroyOnLoadProp, new GUIContent("씬 전환 시 유지"));
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("인스턴스 ID는 자동으로 생성되며 각 EasyMotionRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||||
|
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.indentLevel--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawAutoExportSettings()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("자동 출력 옵션", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.HelpBox("저장 시 자동으로 출력할 파일 형식을 선택하세요.", MessageType.Info);
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(exportHumanoidOnSaveProp, new GUIContent("휴머노이드 애니메이션 자동 출력"));
|
||||||
|
EditorGUILayout.PropertyField(exportGenericOnSaveProp, new GUIContent("제네릭 애니메이션 자동 출력"));
|
||||||
|
EditorGUILayout.PropertyField(exportFBXAsciiOnSaveProp, new GUIContent("FBX ASCII 자동 출력"));
|
||||||
|
EditorGUILayout.PropertyField(exportFBXBinaryOnSaveProp, new GUIContent("FBX Binary 자동 출력"));
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawActionButtons()
|
private void DrawActionButtons()
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField("작업", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("작업", EditorStyles.boldLabel);
|
||||||
@ -97,6 +177,7 @@ namespace EasyMotionRecorder
|
|||||||
"모든 설정을 기본값으로 되돌리시겠습니까?", "확인", "취소"))
|
"모든 설정을 기본값으로 되돌리시겠습니까?", "확인", "취소"))
|
||||||
{
|
{
|
||||||
savePathManager.ResetToDefaults();
|
savePathManager.ResetToDefaults();
|
||||||
|
serializedObject.Update();
|
||||||
EditorUtility.SetDirty(savePathManager);
|
EditorUtility.SetDirty(savePathManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,13 +197,134 @@ namespace EasyMotionRecorder
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
private void DrawMultiObjectBasicSettings()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("기본 설정 (모든 선택된 오브젝트에 적용)", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
// 통합 저장 경로
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUI.showMixedValue = motionSavePathProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
string newPath = EditorGUILayout.TextField("저장 경로", motionSavePathProp.stringValue);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
motionSavePathProp.stringValue = newPath;
|
||||||
|
foreach (SavePathManager manager in targets)
|
||||||
|
{
|
||||||
|
manager.SetMotionSavePath(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUI.showMixedValue = false;
|
||||||
|
|
||||||
|
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
||||||
|
{
|
||||||
|
string selectedPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", "Assets", "");
|
||||||
|
if (!string.IsNullOrEmpty(selectedPath))
|
||||||
|
{
|
||||||
|
// Assets 폴더 기준으로 상대 경로로 변환
|
||||||
|
if (selectedPath.StartsWith(Application.dataPath))
|
||||||
|
{
|
||||||
|
selectedPath = "Assets" + selectedPath.Substring(Application.dataPath.Length);
|
||||||
|
}
|
||||||
|
motionSavePathProp.stringValue = selectedPath;
|
||||||
|
|
||||||
|
foreach (SavePathManager manager in targets)
|
||||||
|
{
|
||||||
|
manager.SetMotionSavePath(selectedPath);
|
||||||
|
EditorUtility.SetDirty(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectAdvancedSettings()
|
||||||
|
{
|
||||||
|
showAdvancedSettings = EditorGUILayout.Foldout(showAdvancedSettings, "고급 설정");
|
||||||
|
|
||||||
|
if (showAdvancedSettings)
|
||||||
|
{
|
||||||
|
EditorGUI.indentLevel++;
|
||||||
|
|
||||||
|
// 서브디렉토리 생성 여부
|
||||||
|
EditorGUI.showMixedValue = createSubdirectoriesProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUILayout.PropertyField(createSubdirectoriesProp, new GUIContent("서브디렉토리 자동 생성"));
|
||||||
|
|
||||||
|
// DontDestroyOnLoad 설정
|
||||||
|
EditorGUI.showMixedValue = useDontDestroyOnLoadProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUILayout.PropertyField(useDontDestroyOnLoadProp, new GUIContent("씬 전환 시 유지"));
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = false;
|
||||||
|
|
||||||
|
// 인스턴스 ID 표시 (읽기 전용)
|
||||||
|
EditorGUILayout.LabelField("인스턴스 ID", "각 오브젝트마다 자동 생성됨");
|
||||||
|
|
||||||
|
EditorGUILayout.HelpBox("인스턴스 ID는 자동으로 생성되며 각 EasyMotionRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||||
|
|
||||||
|
EditorGUI.indentLevel--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectAutoExportSettings()
|
||||||
|
{
|
||||||
EditorGUILayout.LabelField("자동 출력 옵션", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("자동 출력 옵션", EditorStyles.boldLabel);
|
||||||
var humanoidProp = serializedObject.FindProperty("exportHumanoidOnSave");
|
EditorGUILayout.HelpBox("저장 시 자동으로 출력할 파일 형식을 선택하세요.", MessageType.Info);
|
||||||
var genericProp = serializedObject.FindProperty("exportGenericOnSave");
|
|
||||||
humanoidProp.boolValue = EditorGUILayout.ToggleLeft("휴머노이드 애니메이션 자동 출력", humanoidProp.boolValue);
|
EditorGUI.showMixedValue = exportHumanoidOnSaveProp.hasMultipleDifferentValues;
|
||||||
genericProp.boolValue = EditorGUILayout.ToggleLeft("제네릭 애니메이션 자동 출력", genericProp.boolValue);
|
EditorGUILayout.PropertyField(exportHumanoidOnSaveProp, new GUIContent("휴머노이드 애니메이션 자동 출력"));
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = exportGenericOnSaveProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUILayout.PropertyField(exportGenericOnSaveProp, new GUIContent("제네릭 애니메이션 자동 출력"));
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = exportFBXAsciiOnSaveProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUILayout.PropertyField(exportFBXAsciiOnSaveProp, new GUIContent("FBX ASCII 자동 출력"));
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = exportFBXBinaryOnSaveProp.hasMultipleDifferentValues;
|
||||||
|
EditorGUILayout.PropertyField(exportFBXBinaryOnSaveProp, new GUIContent("FBX Binary 자동 출력"));
|
||||||
|
|
||||||
|
EditorGUI.showMixedValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawMultiObjectActions()
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField("멀티 오브젝트 액션", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
|
||||||
|
// 모든 객체를 기본값으로 리셋
|
||||||
|
if (GUILayout.Button("모든 객체 기본값으로 리셋", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
if (EditorUtility.DisplayDialog("기본값으로 리셋",
|
||||||
|
$"선택된 {targets.Length}개 객체의 모든 설정을 기본값으로 되돌리시겠습니까?", "확인", "취소"))
|
||||||
|
{
|
||||||
|
foreach (SavePathManager manager in targets)
|
||||||
|
{
|
||||||
|
manager.ResetToDefaults();
|
||||||
|
EditorUtility.SetDirty(manager);
|
||||||
|
}
|
||||||
|
serializedObject.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 저장 폴더 열기
|
||||||
|
if (GUILayout.Button("저장 폴더 열기", GUILayout.Height(30)))
|
||||||
|
{
|
||||||
|
string path = savePathManager.GetMotionSavePath();
|
||||||
|
if (Directory.Exists(path))
|
||||||
|
{
|
||||||
|
EditorUtility.RevealInFinder(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorUtility.DisplayDialog("오류", "저장 폴더가 존재하지 않습니다.", "확인");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,9 +37,8 @@ namespace Entum {
|
|||||||
[Tooltip("記録するFPS。0で制限しない。UpdateのFPSは超えられません。")]
|
[Tooltip("記録するFPS。0で制限しない。UpdateのFPSは超えられません。")]
|
||||||
public float TargetFPS = 60.0f;
|
public float TargetFPS = 60.0f;
|
||||||
|
|
||||||
[Header("인스턴스 설정")]
|
[HideInInspector, SerializeField] private string instanceID = "";
|
||||||
[SerializeField] private string instanceID = "";
|
[HideInInspector, SerializeField] private SavePathManager _savePathManager;
|
||||||
[SerializeField] private SavePathManager _savePathManager;
|
|
||||||
|
|
||||||
private MotionDataRecorder _animRecorder;
|
private MotionDataRecorder _animRecorder;
|
||||||
|
|
||||||
@ -62,28 +61,29 @@ namespace Entum {
|
|||||||
_animRecorder.OnRecordStart += RecordStart;
|
_animRecorder.OnRecordStart += RecordStart;
|
||||||
_animRecorder.OnRecordEnd += RecordEnd;
|
_animRecorder.OnRecordEnd += RecordEnd;
|
||||||
|
|
||||||
|
// SavePathManager 자동 찾기
|
||||||
|
if (_savePathManager == null)
|
||||||
|
{
|
||||||
|
_savePathManager = GetComponent<SavePathManager>();
|
||||||
|
}
|
||||||
|
|
||||||
// 인스턴스 ID가 비어있으면 자동 생성
|
// 인스턴스 ID가 비어있으면 자동 생성
|
||||||
if (string.IsNullOrEmpty(instanceID))
|
if (string.IsNullOrEmpty(instanceID))
|
||||||
{
|
{
|
||||||
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePathManager가 없으면 같은 GameObject에서 찾기
|
|
||||||
if (_savePathManager == null)
|
|
||||||
{
|
|
||||||
_savePathManager = GetComponent<SavePathManager>();
|
|
||||||
if (_savePathManager == null)
|
|
||||||
{
|
|
||||||
_savePathManager = gameObject.AddComponent<SavePathManager>();
|
|
||||||
_savePathManager.SetInstanceID(instanceID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(_animRecorder.CharacterAnimator != null) {
|
if(_animRecorder.CharacterAnimator != null) {
|
||||||
_smeshs = GetSkinnedMeshRenderers(_animRecorder.CharacterAnimator);
|
_smeshs = GetSkinnedMeshRenderers(_animRecorder.CharacterAnimator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 내부 메서드 (SavePathManager에서 호출)
|
||||||
|
internal void SetSavePathManager(SavePathManager manager)
|
||||||
|
{
|
||||||
|
_savePathManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
SkinnedMeshRenderer[] GetSkinnedMeshRenderers(Animator root) {
|
SkinnedMeshRenderer[] GetSkinnedMeshRenderers(Animator root) {
|
||||||
var helper = root;
|
var helper = root;
|
||||||
var renderers = helper.GetComponentsInChildren<SkinnedMeshRenderer>();
|
var renderers = helper.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||||
@ -139,18 +139,46 @@ namespace Entum {
|
|||||||
_recording = false;
|
_recording = false;
|
||||||
Debug.Log($"표정 애니메이션 녹화 종료 - 인스턴스: {instanceID}, 총 프레임: {_frameCount}");
|
Debug.Log($"표정 애니메이션 녹화 종료 - 인스턴스: {instanceID}, 총 프레임: {_frameCount}");
|
||||||
|
|
||||||
WriteAnimationFileToScriptableObject();
|
// 바로 애니메이션 클립으로 출력
|
||||||
|
ExportFacialAnimationClip(_animRecorder.CharacterAnimator, _facialData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteAnimationFileToScriptableObject() {
|
private void ExportFacialAnimationClip(Animator root, CharacterFacialData facial) {
|
||||||
if (_facialData == null || _facialData.Faces.Count == 0)
|
if(facial == null || facial.Faces.Count == 0) {
|
||||||
{
|
|
||||||
Debug.LogError("저장할 표정 데이터가 없습니다.");
|
Debug.LogError("저장할 표정 데이터가 없습니다.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
string fileName = $"{_animRecorder.SessionID}_Facial";
|
var clip = new AnimationClip();
|
||||||
string filePath = Path.Combine(_savePathManager.GetFacialSavePath(), fileName + ".asset");
|
clip.frameRate = 30;
|
||||||
|
|
||||||
|
var blendShapeNames = facial.Faces[0].BlendShapeNames;
|
||||||
|
var curves = new Dictionary<string, AnimationCurve>();
|
||||||
|
|
||||||
|
for(int i = 0; i < blendShapeNames.Count; i++) {
|
||||||
|
curves[blendShapeNames[i]] = new AnimationCurve();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i = 0; i < facial.Faces.Count; i++) {
|
||||||
|
var face = facial.Faces[i];
|
||||||
|
var time = face.Time;
|
||||||
|
|
||||||
|
for(int j = 0; j < face.BlendShapeNames.Count; j++) {
|
||||||
|
var blendShapeName = face.BlendShapeNames[j];
|
||||||
|
var value = face.BlendShapeValues[j];
|
||||||
|
|
||||||
|
if(curves.ContainsKey(blendShapeName)) {
|
||||||
|
curves[blendShapeName].AddKey(time, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach(var curve in curves) {
|
||||||
|
clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape." + curve.Key, curve.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
string fileName = $"{facial.SessionID}_Facial";
|
||||||
|
string filePath = Path.Combine(_savePathManager.GetFacialSavePath(), fileName + ".anim");
|
||||||
|
|
||||||
// 인스턴스별 고유 경로 생성
|
// 인스턴스별 고유 경로 생성
|
||||||
filePath = _savePathManager.GetInstanceSpecificPath(filePath);
|
filePath = _savePathManager.GetInstanceSpecificPath(filePath);
|
||||||
@ -158,10 +186,10 @@ namespace Entum {
|
|||||||
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(filePath));
|
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(filePath));
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
AssetDatabase.CreateAsset(_facialData, filePath);
|
AssetDatabase.CreateAsset(clip, filePath);
|
||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
AssetDatabase.Refresh();
|
AssetDatabase.Refresh();
|
||||||
Debug.Log($"표정 데이터 저장 완료: {filePath}");
|
Debug.Log($"표정 애니메이션 클립 저장 완료: {filePath}");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,61 +263,6 @@ namespace Entum {
|
|||||||
_frameCount++;
|
_frameCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExportFacialAnimationClip(Animator root, CharacterFacialData facial) {
|
|
||||||
if(facial.Faces.Count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var clip = new AnimationClip();
|
|
||||||
clip.frameRate = 30;
|
|
||||||
|
|
||||||
var blendShapeNames = facial.Faces[0].BlendShapeNames;
|
|
||||||
var curves = new Dictionary<string, AnimationCurve>();
|
|
||||||
|
|
||||||
for(int i = 0; i < blendShapeNames.Count; i++) {
|
|
||||||
curves[blendShapeNames[i]] = new AnimationCurve();
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int i = 0; i < facial.Faces.Count; i++) {
|
|
||||||
var face = facial.Faces[i];
|
|
||||||
var time = face.Time;
|
|
||||||
|
|
||||||
for(int j = 0; j < face.BlendShapeNames.Count; j++) {
|
|
||||||
var blendShapeName = face.BlendShapeNames[j];
|
|
||||||
var value = face.BlendShapeValues[j];
|
|
||||||
|
|
||||||
if(curves.ContainsKey(blendShapeName)) {
|
|
||||||
curves[blendShapeName].AddKey(time, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach(var curve in curves) {
|
|
||||||
clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape." + curve.Key, curve.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
string fileName = $"{facial.SessionID}_Facial";
|
|
||||||
string filePath = Path.Combine(_savePathManager.GetFacialSavePath(), fileName + ".anim");
|
|
||||||
|
|
||||||
// 인스턴스별 고유 경로 생성
|
|
||||||
filePath = _savePathManager.GetInstanceSpecificPath(filePath);
|
|
||||||
|
|
||||||
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(filePath));
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
|
||||||
AssetDatabase.CreateAsset(clip, filePath);
|
|
||||||
AssetDatabase.SaveAssets();
|
|
||||||
AssetDatabase.Refresh();
|
|
||||||
Debug.Log($"표정 애니메이션 클립 저장 완료: {filePath}");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void ExportFacialAnimationClipTest() {
|
|
||||||
if(_facialData != null) {
|
|
||||||
ExportFacialAnimationClip(_animRecorder.CharacterAnimator, _facialData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetInstanceID(string id)
|
public void SetInstanceID(string id)
|
||||||
{
|
{
|
||||||
instanceID = id;
|
instanceID = id;
|
||||||
|
|||||||
@ -43,9 +43,8 @@ namespace Entum
|
|||||||
[SerializeField, Tooltip("rootBoneSystemがOBJECTROOTの時は使われないパラメータです。")]
|
[SerializeField, Tooltip("rootBoneSystemがOBJECTROOTの時は使われないパラメータです。")]
|
||||||
private HumanBodyBones _targetRootBone = HumanBodyBones.Hips;
|
private HumanBodyBones _targetRootBone = HumanBodyBones.Hips;
|
||||||
|
|
||||||
[Header("인스턴스 설정")]
|
[HideInInspector, SerializeField] private string instanceID = "";
|
||||||
[SerializeField] private string instanceID = "";
|
[HideInInspector, SerializeField] private bool useDontDestroyOnLoad = false;
|
||||||
[SerializeField] private bool useDontDestroyOnLoad = false;
|
|
||||||
|
|
||||||
private HumanPoseHandler _poseHandler;
|
private HumanPoseHandler _poseHandler;
|
||||||
private Action _onPlayFinish;
|
private Action _onPlayFinish;
|
||||||
|
|||||||
@ -54,14 +54,14 @@ namespace Entum
|
|||||||
[SerializeField, Tooltip("녹화 시작 시 T-포즈를 별도로 저장할지 여부 (출력 시 0프레임에 포함)")]
|
[SerializeField, Tooltip("녹화 시작 시 T-포즈를 별도로 저장할지 여부 (출력 시 0프레임에 포함)")]
|
||||||
private bool _recordTPoseAtStart = true;
|
private bool _recordTPoseAtStart = true;
|
||||||
|
|
||||||
[Header("인스턴스 설정")]
|
[HideInInspector, SerializeField] private string instanceID = "";
|
||||||
[SerializeField] private string instanceID = "";
|
[HideInInspector, SerializeField] private SavePathManager _savePathManager;
|
||||||
[SerializeField] private SavePathManager _savePathManager;
|
|
||||||
|
|
||||||
protected HumanoidPoses Poses;
|
protected HumanoidPoses Poses;
|
||||||
|
|
||||||
|
protected float StartTime { get; set; }
|
||||||
protected float RecordedTime;
|
protected float RecordedTime;
|
||||||
protected float StartTime;
|
public string SessionID { get; set; }
|
||||||
public string SessionID; // 세션 ID 추가
|
|
||||||
|
|
||||||
private HumanPose _currentPose;
|
private HumanPose _currentPose;
|
||||||
private HumanPoseHandler _poseHandler;
|
private HumanPoseHandler _poseHandler;
|
||||||
@ -81,26 +81,41 @@ namespace Entum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 인스턴스 ID가 비어있으면 자동 생성
|
// SavePathManager 자동 찾기 또는 추가
|
||||||
if (string.IsNullOrEmpty(instanceID))
|
|
||||||
{
|
|
||||||
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// SavePathManager가 없으면 같은 GameObject에서 찾기
|
|
||||||
if (_savePathManager == null)
|
if (_savePathManager == null)
|
||||||
{
|
{
|
||||||
_savePathManager = GetComponent<SavePathManager>();
|
_savePathManager = GetComponent<SavePathManager>();
|
||||||
if (_savePathManager == null)
|
if (_savePathManager == null)
|
||||||
{
|
{
|
||||||
_savePathManager = gameObject.AddComponent<SavePathManager>();
|
_savePathManager = gameObject.AddComponent<SavePathManager>();
|
||||||
_savePathManager.SetInstanceID(instanceID);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 인스턴스 ID가 비어있으면 자동 생성
|
||||||
|
if (string.IsNullOrEmpty(instanceID))
|
||||||
|
{
|
||||||
|
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionID 생성 (인스턴스 ID 제외)
|
||||||
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
|
|
||||||
_poseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform);
|
_poseHandler = new HumanPoseHandler(_animator.avatar, _animator.transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 내부 메서드들 (SavePathManager에서 호출)
|
||||||
|
internal void SetInstanceID(string id)
|
||||||
|
{
|
||||||
|
instanceID = id;
|
||||||
|
// SessionID는 인스턴스 ID 없이 유지
|
||||||
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SetSavePathManager(SavePathManager manager)
|
||||||
|
{
|
||||||
|
_savePathManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (Input.GetKeyDown(_recordStartKey))
|
if (Input.GetKeyDown(_recordStartKey))
|
||||||
@ -365,9 +380,90 @@ namespace Entum
|
|||||||
AssetDatabase.SaveAssets();
|
AssetDatabase.SaveAssets();
|
||||||
AssetDatabase.Refresh();
|
AssetDatabase.Refresh();
|
||||||
Debug.Log($"모션 데이터 저장 완료: {filePath}");
|
Debug.Log($"모션 데이터 저장 완료: {filePath}");
|
||||||
|
|
||||||
|
// 자동 출력 옵션 처리
|
||||||
|
if (_savePathManager != null)
|
||||||
|
{
|
||||||
|
if (_savePathManager.ExportHumanoidOnSave)
|
||||||
|
{
|
||||||
|
ExportHumanoidAnimation(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_savePathManager.ExportGenericOnSave)
|
||||||
|
{
|
||||||
|
ExportGenericAnimation(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_savePathManager.ExportFBXAsciiOnSave)
|
||||||
|
{
|
||||||
|
ExportFBXAnimation(fileName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_savePathManager.ExportFBXBinaryOnSave)
|
||||||
|
{
|
||||||
|
ExportFBXAnimation(fileName, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private void ExportHumanoidAnimation(string baseFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Humanoid.anim");
|
||||||
|
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||||
|
|
||||||
|
// HumanoidPoses의 기존 내보내기 메서드 사용
|
||||||
|
Poses.ExportHumanoidAnim();
|
||||||
|
Debug.Log($"휴머노이드 애니메이션 출력 완료: {baseFileName}_Humanoid");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"휴머노이드 애니메이션 출력 실패: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportGenericAnimation(string baseFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Generic.anim");
|
||||||
|
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||||
|
|
||||||
|
// HumanoidPoses의 기존 내보내기 메서드 사용
|
||||||
|
Poses.ExportGenericAnim();
|
||||||
|
Debug.Log($"제네릭 애니메이션 출력 완료: {baseFileName}_Generic");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"제네릭 애니메이션 출력 실패: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportFBXAnimation(string baseFileName, bool ascii)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// HumanoidPoses의 기존 FBX 내보내기 메서드 사용
|
||||||
|
if (ascii)
|
||||||
|
{
|
||||||
|
Poses.ExportFBXAscii();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Poses.ExportFBXBinary();
|
||||||
|
}
|
||||||
|
Debug.Log($"FBX 애니메이션 출력 완료: {baseFileName}_{(ascii ? "ASCII" : "Binary")}");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"FBX 애니메이션 출력 실패: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private void UpdateSummaryInfo()
|
private void UpdateSummaryInfo()
|
||||||
{
|
{
|
||||||
if (Poses == null) return;
|
if (Poses == null) return;
|
||||||
@ -425,3 +521,4 @@ namespace Entum
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -30,9 +30,13 @@ namespace Entum
|
|||||||
[Header("파일명 설정")]
|
[Header("파일명 설정")]
|
||||||
[SerializeField] private string objectNamePrefix = "Object";
|
[SerializeField] private string objectNamePrefix = "Object";
|
||||||
|
|
||||||
[Header("인스턴스 설정")]
|
[HideInInspector, SerializeField] private string instanceID = "";
|
||||||
[SerializeField] private string instanceID = "";
|
[HideInInspector, SerializeField] private SavePathManager _savePathManager;
|
||||||
[SerializeField] private SavePathManager _savePathManager;
|
|
||||||
|
// Properties
|
||||||
|
public Transform[] TargetObjects => targetObjects;
|
||||||
|
public bool IsRecording => isRecording;
|
||||||
|
public float RecordedTime => recordedTime;
|
||||||
|
|
||||||
private bool isRecording = false;
|
private bool isRecording = false;
|
||||||
private float startTime;
|
private float startTime;
|
||||||
@ -52,22 +56,20 @@ namespace Entum
|
|||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
|
// SavePathManager 자동 찾기
|
||||||
|
if (_savePathManager == null)
|
||||||
|
{
|
||||||
|
_savePathManager = GetComponent<SavePathManager>();
|
||||||
|
}
|
||||||
|
|
||||||
// 인스턴스 ID가 비어있으면 자동 생성
|
// 인스턴스 ID가 비어있으면 자동 생성
|
||||||
if (string.IsNullOrEmpty(instanceID))
|
if (string.IsNullOrEmpty(instanceID))
|
||||||
{
|
{
|
||||||
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
instanceID = System.Guid.NewGuid().ToString().Substring(0, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePathManager가 없으면 같은 GameObject에서 찾기
|
// SessionID 생성 (인스턴스 ID 제외)
|
||||||
if (_savePathManager == null)
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
{
|
|
||||||
_savePathManager = GetComponent<SavePathManager>();
|
|
||||||
if (_savePathManager == null)
|
|
||||||
{
|
|
||||||
_savePathManager = gameObject.AddComponent<SavePathManager>();
|
|
||||||
_savePathManager.SetInstanceID(instanceID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
@ -243,9 +245,52 @@ namespace Entum
|
|||||||
AssetDatabase.Refresh();
|
AssetDatabase.Refresh();
|
||||||
|
|
||||||
Debug.Log($"오브젝트 애니메이션 파일 저장: {filePath}");
|
Debug.Log($"오브젝트 애니메이션 파일 저장: {filePath}");
|
||||||
|
|
||||||
|
// 자동 출력 옵션 처리
|
||||||
|
if (_savePathManager != null)
|
||||||
|
{
|
||||||
|
if (_savePathManager.ExportHumanoidOnSave)
|
||||||
|
{
|
||||||
|
ExportObjectAnimationAsHumanoid(target, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_savePathManager.ExportGenericOnSave)
|
||||||
|
{
|
||||||
|
ExportObjectAnimationAsGeneric(target, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
private void ExportObjectAnimationAsHumanoid(Transform target, string baseFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 오브젝트 애니메이션은 휴머노이드로 변환할 수 없으므로 제네릭으로 처리
|
||||||
|
ExportObjectAnimationAsGeneric(target, baseFileName);
|
||||||
|
Debug.Log($"오브젝트 휴머노이드 애니메이션 출력 완료: {baseFileName}_Humanoid");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"오브젝트 휴머노이드 애니메이션 출력 실패: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExportObjectAnimationAsGeneric(Transform target, string baseFileName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 이미 제네릭 애니메이션으로 저장되었으므로 추가 작업 불필요
|
||||||
|
Debug.Log($"오브젝트 제네릭 애니메이션 출력 완료: {baseFileName}_Generic");
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError($"오브젝트 제네릭 애니메이션 출력 실패: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// 인스펙터에서 타겟 오브젝트 추가/제거를 위한 헬퍼 메서드
|
// 인스펙터에서 타겟 오브젝트 추가/제거를 위한 헬퍼 메서드
|
||||||
[ContextMenu("Add Current Selection")]
|
[ContextMenu("Add Current Selection")]
|
||||||
public void AddCurrentSelection()
|
public void AddCurrentSelection()
|
||||||
@ -270,19 +315,17 @@ namespace Entum
|
|||||||
Debug.Log("모든 타겟 오브젝트 제거");
|
Debug.Log("모든 타겟 오브젝트 제거");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetInstanceID(string id)
|
// 내부 메서드들 (SavePathManager에서 호출)
|
||||||
|
internal void SetInstanceID(string id)
|
||||||
{
|
{
|
||||||
instanceID = id;
|
instanceID = id;
|
||||||
|
// SessionID는 인스턴스 ID 없이 유지
|
||||||
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetInstanceID()
|
internal void SetSavePathManager(SavePathManager manager)
|
||||||
{
|
{
|
||||||
return instanceID;
|
_savePathManager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 타겟 오브젝트 배열 접근자
|
|
||||||
public Transform[] TargetObjects => targetObjects;
|
|
||||||
public bool IsRecording => isRecording;
|
|
||||||
public float RecordedTime => recordedTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Entum;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
#endif
|
#endif
|
||||||
@ -10,8 +11,6 @@ namespace EasyMotionRecorder
|
|||||||
{
|
{
|
||||||
[Header("저장 경로 설정")]
|
[Header("저장 경로 설정")]
|
||||||
[SerializeField] private string motionSavePath = "Assets/Resources/Motion";
|
[SerializeField] private string motionSavePath = "Assets/Resources/Motion";
|
||||||
[SerializeField] private string facialSavePath = "Assets/Resources/Motion";
|
|
||||||
[SerializeField] private string objectSavePath = "Assets/Resources/Motion";
|
|
||||||
|
|
||||||
[Header("설정")]
|
[Header("설정")]
|
||||||
[SerializeField] private bool createSubdirectories = true;
|
[SerializeField] private bool createSubdirectories = true;
|
||||||
@ -26,6 +25,11 @@ namespace EasyMotionRecorder
|
|||||||
[SerializeField] private string instanceID = "";
|
[SerializeField] private string instanceID = "";
|
||||||
[SerializeField] private bool useDontDestroyOnLoad = false;
|
[SerializeField] private bool useDontDestroyOnLoad = false;
|
||||||
|
|
||||||
|
// 같은 오브젝트의 컴포넌트 참조
|
||||||
|
private MotionDataRecorder motionRecorder;
|
||||||
|
private FaceAnimationRecorder faceRecorder;
|
||||||
|
private ObjectMotionRecorder objectRecorder;
|
||||||
|
|
||||||
public bool ExportHumanoidOnSave => exportHumanoidOnSave;
|
public bool ExportHumanoidOnSave => exportHumanoidOnSave;
|
||||||
public bool ExportGenericOnSave => exportGenericOnSave;
|
public bool ExportGenericOnSave => exportGenericOnSave;
|
||||||
public bool ExportFBXAsciiOnSave => exportFBXAsciiOnSave;
|
public bool ExportFBXAsciiOnSave => exportFBXAsciiOnSave;
|
||||||
@ -46,15 +50,44 @@ namespace EasyMotionRecorder
|
|||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 같은 오브젝트의 컴포넌트들 찾기
|
||||||
|
FindAndSetupComponents();
|
||||||
|
|
||||||
InitializePaths();
|
InitializePaths();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void FindAndSetupComponents()
|
||||||
|
{
|
||||||
|
// 같은 오브젝트에서 컴포넌트들 찾기
|
||||||
|
motionRecorder = GetComponent<MotionDataRecorder>();
|
||||||
|
faceRecorder = GetComponent<FaceAnimationRecorder>();
|
||||||
|
objectRecorder = GetComponent<ObjectMotionRecorder>();
|
||||||
|
|
||||||
|
// 각 컴포넌트에 인스턴스 ID 설정
|
||||||
|
if (motionRecorder != null)
|
||||||
|
{
|
||||||
|
motionRecorder.SetInstanceID(instanceID);
|
||||||
|
motionRecorder.SetSavePathManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (faceRecorder != null)
|
||||||
|
{
|
||||||
|
faceRecorder.SetInstanceID(instanceID);
|
||||||
|
faceRecorder.SetSavePathManager(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objectRecorder != null)
|
||||||
|
{
|
||||||
|
objectRecorder.SetInstanceID(instanceID);
|
||||||
|
objectRecorder.SetSavePathManager(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void InitializePaths()
|
private void InitializePaths()
|
||||||
{
|
{
|
||||||
if (createSubdirectories)
|
if (createSubdirectories)
|
||||||
{
|
{
|
||||||
CreateDirectoryIfNotExists(motionSavePath);
|
CreateDirectoryIfNotExists(motionSavePath);
|
||||||
CreateDirectoryIfNotExists(facialSavePath);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,12 +118,12 @@ namespace EasyMotionRecorder
|
|||||||
|
|
||||||
public string GetFacialSavePath()
|
public string GetFacialSavePath()
|
||||||
{
|
{
|
||||||
return facialSavePath;
|
return motionSavePath; // 통합된 경로 사용
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetObjectSavePath()
|
public string GetObjectSavePath()
|
||||||
{
|
{
|
||||||
return objectSavePath;
|
return motionSavePath; // 통합된 경로 사용
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMotionSavePath(string path)
|
public void SetMotionSavePath(string path)
|
||||||
@ -98,46 +131,60 @@ namespace EasyMotionRecorder
|
|||||||
motionSavePath = path;
|
motionSavePath = path;
|
||||||
if (createSubdirectories)
|
if (createSubdirectories)
|
||||||
CreateDirectoryIfNotExists(path);
|
CreateDirectoryIfNotExists(path);
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetFacialSavePath(string path)
|
public void SetFacialSavePath(string path)
|
||||||
{
|
{
|
||||||
facialSavePath = path;
|
// 통합된 경로이므로 모션 저장 경로와 동일하게 설정
|
||||||
if (createSubdirectories)
|
SetMotionSavePath(path);
|
||||||
CreateDirectoryIfNotExists(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetObjectSavePath(string path)
|
public void SetObjectSavePath(string path)
|
||||||
{
|
{
|
||||||
objectSavePath = path;
|
// 통합된 경로이므로 모션 저장 경로와 동일하게 설정
|
||||||
if (createSubdirectories)
|
SetMotionSavePath(path);
|
||||||
CreateDirectoryIfNotExists(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCreateSubdirectories(bool create)
|
private void SetCreateSubdirectories(bool create)
|
||||||
{
|
{
|
||||||
createSubdirectories = create;
|
createSubdirectories = create;
|
||||||
if (create)
|
if (create)
|
||||||
{
|
{
|
||||||
InitializePaths();
|
InitializePaths();
|
||||||
}
|
}
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetInstanceID(string id)
|
private void SetInstanceID(string id)
|
||||||
{
|
{
|
||||||
instanceID = id;
|
instanceID = id;
|
||||||
|
|
||||||
|
// 모든 컴포넌트에 새 인스턴스 ID 적용
|
||||||
|
if (motionRecorder != null) motionRecorder.SetInstanceID(id);
|
||||||
|
if (faceRecorder != null) faceRecorder.SetInstanceID(id);
|
||||||
|
if (objectRecorder != null) objectRecorder.SetInstanceID(id);
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetUseDontDestroyOnLoad(bool use)
|
private void SetUseDontDestroyOnLoad(bool use)
|
||||||
{
|
{
|
||||||
useDontDestroyOnLoad = use;
|
useDontDestroyOnLoad = use;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ResetToDefaults()
|
public void ResetToDefaults()
|
||||||
{
|
{
|
||||||
motionSavePath = "Assets/Resources/Motion";
|
motionSavePath = "Assets/Resources/Motion";
|
||||||
facialSavePath = "Assets/Resources/Motion";
|
|
||||||
objectSavePath = "Assets/Resources/Motion";
|
|
||||||
createSubdirectories = true;
|
createSubdirectories = true;
|
||||||
|
|
||||||
// 자동 출력 옵션 초기화
|
// 자동 출력 옵션 초기화
|
||||||
@ -147,17 +194,9 @@ namespace EasyMotionRecorder
|
|||||||
exportFBXBinaryOnSave = false;
|
exportFBXBinaryOnSave = false;
|
||||||
|
|
||||||
InitializePaths();
|
InitializePaths();
|
||||||
}
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
public void SynchronizePaths()
|
#endif
|
||||||
{
|
|
||||||
// 모든 경로를 모션 경로와 동일하게 설정
|
|
||||||
facialSavePath = motionSavePath;
|
|
||||||
objectSavePath = motionSavePath;
|
|
||||||
if (createSubdirectories)
|
|
||||||
{
|
|
||||||
InitializePaths();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 인스턴스별 고유 경로 생성
|
// 인스턴스별 고유 경로 생성
|
||||||
@ -172,5 +211,38 @@ namespace EasyMotionRecorder
|
|||||||
|
|
||||||
return Path.Combine(directory, $"{fileName}_{instanceID}{extension}");
|
return Path.Combine(directory, $"{fileName}_{instanceID}{extension}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 자동 출력 옵션 설정 (private으로 변경)
|
||||||
|
private void SetExportHumanoidOnSave(bool value)
|
||||||
|
{
|
||||||
|
exportHumanoidOnSave = value;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetExportGenericOnSave(bool value)
|
||||||
|
{
|
||||||
|
exportGenericOnSave = value;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetExportFBXAsciiOnSave(bool value)
|
||||||
|
{
|
||||||
|
exportFBXAsciiOnSave = value;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetExportFBXBinaryOnSave(bool value)
|
||||||
|
{
|
||||||
|
exportFBXBinaryOnSave = value;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
UnityEditor.EditorUtility.SetDirty(this);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user