Fix : 모션 레코더 추가 패치
This commit is contained in:
parent
42358b15a6
commit
80fd70c464
261
Assets/External/EasyMotionRecorder/Scripts/Editor/FaceAnimationRecorderEditor.cs
vendored
Normal file
261
Assets/External/EasyMotionRecorder/Scripts/Editor/FaceAnimationRecorderEditor.cs
vendored
Normal file
@ -0,0 +1,261 @@
|
||||
#if UNITY_EDITOR
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
|
||||
namespace Entum
|
||||
{
|
||||
[CustomEditor(typeof(FaceAnimationRecorder))]
|
||||
[CanEditMultipleObjects]
|
||||
public class FaceAnimationRecorderEditor : Editor
|
||||
{
|
||||
private FaceAnimationRecorder faceRecorder;
|
||||
private bool showAdvancedSettings = false;
|
||||
private bool showExclusiveSettings = false;
|
||||
|
||||
// SerializedProperty 참조
|
||||
private SerializedProperty recordFaceBlendshapesProp;
|
||||
private SerializedProperty exclusiveBlendshapeNamesProp;
|
||||
private SerializedProperty targetFPSProp;
|
||||
private SerializedProperty instanceIDProp;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
faceRecorder = (FaceAnimationRecorder)target;
|
||||
|
||||
// SerializedProperty 초기화
|
||||
recordFaceBlendshapesProp = serializedObject.FindProperty("_recordFaceBlendshapes");
|
||||
exclusiveBlendshapeNamesProp = serializedObject.FindProperty("_exclusiveBlendshapeNames");
|
||||
targetFPSProp = serializedObject.FindProperty("TargetFPS");
|
||||
instanceIDProp = serializedObject.FindProperty("instanceID");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
DrawMultiObjectGUI();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("표정 애니메이션 레코더", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 기본 설정
|
||||
DrawBasicSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 제외 설정
|
||||
DrawExclusiveSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 고급 설정
|
||||
DrawAdvancedSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 상태 정보
|
||||
DrawStatusInfo();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawMultiObjectGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField($"표정 애니메이션 레코더 ({targets.Length}개 선택됨)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectBasicSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectExclusiveSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectAdvancedSettings();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawBasicSettings()
|
||||
{
|
||||
EditorGUILayout.LabelField("기본 설정", EditorStyles.boldLabel);
|
||||
|
||||
// 표정 기록 활성화 체크박스
|
||||
EditorGUILayout.PropertyField(recordFaceBlendshapesProp,
|
||||
new GUIContent("표정 기록 활성화", "체크하면 모션 기록과 함께 표정 애니메이션도 기록됩니다."));
|
||||
|
||||
if (!recordFaceBlendshapesProp.boolValue)
|
||||
{
|
||||
EditorGUILayout.HelpBox("표정 기록이 비활성화되어 있습니다. 표정 애니메이션을 기록하려면 위 옵션을 체크하세요.", MessageType.Warning);
|
||||
}
|
||||
|
||||
// FPS 설정
|
||||
EditorGUILayout.PropertyField(targetFPSProp,
|
||||
new GUIContent("기록 FPS", "표정 애니메이션을 기록할 FPS입니다. 0으로 설정하면 제한하지 않습니다."));
|
||||
}
|
||||
|
||||
private void DrawExclusiveSettings()
|
||||
{
|
||||
showExclusiveSettings = EditorGUILayout.Foldout(showExclusiveSettings, "제외할 블렌드셰이프 설정");
|
||||
|
||||
if (showExclusiveSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.HelpBox("립싱크나 특정 블렌드셰이프를 기록에서 제외하고 싶을 때 사용합니다.", MessageType.Info);
|
||||
|
||||
EditorGUILayout.PropertyField(exclusiveBlendshapeNamesProp,
|
||||
new GUIContent("제외할 블렌드셰이프 이름", "이 목록의 문자열을 포함하는 블렌드셰이프는 기록되지 않습니다."), true);
|
||||
|
||||
if (exclusiveBlendshapeNamesProp.arraySize > 0)
|
||||
{
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.LabelField("현재 제외 목록:", EditorStyles.miniLabel);
|
||||
for (int i = 0; i < exclusiveBlendshapeNamesProp.arraySize; i++)
|
||||
{
|
||||
var element = exclusiveBlendshapeNamesProp.GetArrayElementAtIndex(i);
|
||||
if (!string.IsNullOrEmpty(element.stringValue))
|
||||
{
|
||||
EditorGUILayout.LabelField($"• {element.stringValue}", EditorStyles.miniLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAdvancedSettings()
|
||||
{
|
||||
showAdvancedSettings = EditorGUILayout.Foldout(showAdvancedSettings, "고급 설정");
|
||||
|
||||
if (showAdvancedSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// 인스턴스 ID (읽기 전용)
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.PropertyField(instanceIDProp, new GUIContent("인스턴스 ID (자동 생성)"));
|
||||
GUI.enabled = true;
|
||||
|
||||
EditorGUILayout.HelpBox("인스턴스 ID는 자동으로 생성되며 각 FaceAnimationRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStatusInfo()
|
||||
{
|
||||
EditorGUILayout.LabelField("상태 정보", EditorStyles.boldLabel);
|
||||
|
||||
// MotionDataRecorder 연결 상태 확인
|
||||
var motionRecorder = faceRecorder.GetComponent<MotionDataRecorder>();
|
||||
if (motionRecorder != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("✓ MotionDataRecorder가 연결되어 있습니다.", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("⚠ MotionDataRecorder가 필요합니다. 같은 GameObject에 추가해주세요.", MessageType.Warning);
|
||||
}
|
||||
|
||||
// SavePathManager 연결 상태 확인
|
||||
var savePathManager = faceRecorder.GetComponent<EasyMotionRecorder.SavePathManager>();
|
||||
if (savePathManager != null)
|
||||
{
|
||||
EditorGUILayout.HelpBox("✓ SavePathManager가 연결되어 있습니다.", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("ℹ SavePathManager가 없습니다. 자동으로 생성됩니다.", MessageType.Info);
|
||||
}
|
||||
|
||||
// 블렌드셰이프 발견 상태
|
||||
if (motionRecorder?.CharacterAnimator != null)
|
||||
{
|
||||
var skinnedMeshRenderers = motionRecorder.CharacterAnimator.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
int totalBlendShapes = 0;
|
||||
foreach (var smr in skinnedMeshRenderers)
|
||||
{
|
||||
if (smr.sharedMesh != null)
|
||||
{
|
||||
totalBlendShapes += smr.sharedMesh.blendShapeCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (totalBlendShapes > 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"✓ {totalBlendShapes}개의 블렌드셰이프를 발견했습니다.", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.HelpBox("⚠ 블렌드셰이프를 찾을 수 없습니다. 캐릭터에 블렌드셰이프가 있는지 확인하세요.", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMultiObjectBasicSettings()
|
||||
{
|
||||
EditorGUILayout.LabelField("기본 설정 (모든 선택된 오브젝트에 적용)", EditorStyles.boldLabel);
|
||||
|
||||
// 표정 기록 활성화
|
||||
EditorGUI.showMixedValue = recordFaceBlendshapesProp.hasMultipleDifferentValues;
|
||||
EditorGUILayout.PropertyField(recordFaceBlendshapesProp,
|
||||
new GUIContent("표정 기록 활성화"));
|
||||
|
||||
// FPS 설정
|
||||
EditorGUI.showMixedValue = targetFPSProp.hasMultipleDifferentValues;
|
||||
EditorGUILayout.PropertyField(targetFPSProp,
|
||||
new GUIContent("기록 FPS"));
|
||||
|
||||
EditorGUI.showMixedValue = false;
|
||||
}
|
||||
|
||||
private void DrawMultiObjectExclusiveSettings()
|
||||
{
|
||||
showExclusiveSettings = EditorGUILayout.Foldout(showExclusiveSettings, "제외할 블렌드셰이프 설정");
|
||||
|
||||
if (showExclusiveSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
EditorGUILayout.HelpBox("립싱크나 특정 블렌드셰이프를 기록에서 제외하고 싶을 때 사용합니다.", MessageType.Info);
|
||||
|
||||
EditorGUI.showMixedValue = exclusiveBlendshapeNamesProp.hasMultipleDifferentValues;
|
||||
EditorGUILayout.PropertyField(exclusiveBlendshapeNamesProp,
|
||||
new GUIContent("제외할 블렌드셰이프 이름"), true);
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMultiObjectAdvancedSettings()
|
||||
{
|
||||
showAdvancedSettings = EditorGUILayout.Foldout(showAdvancedSettings, "고급 설정");
|
||||
|
||||
if (showAdvancedSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
|
||||
// 인스턴스 ID 표시 (읽기 전용)
|
||||
EditorGUILayout.LabelField("인스턴스 ID", "각 오브젝트마다 자동 생성됨");
|
||||
|
||||
EditorGUILayout.HelpBox("인스턴스 ID는 자동으로 생성되며 각 FaceAnimationRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
2
Assets/External/EasyMotionRecorder/Scripts/Editor/FaceAnimationRecorderEditor.cs.meta
vendored
Normal file
2
Assets/External/EasyMotionRecorder/Scripts/Editor/FaceAnimationRecorderEditor.cs.meta
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 793f8f25531a10a45bbaf0ec019c20a3
|
||||
@ -2,6 +2,7 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace EasyMotionRecorder
|
||||
{
|
||||
@ -11,6 +12,14 @@ namespace EasyMotionRecorder
|
||||
{
|
||||
private SavePathManager savePathManager;
|
||||
private bool showAdvancedSettings = false;
|
||||
private bool showExportSettings = true;
|
||||
private bool showComponentInfo = false;
|
||||
|
||||
// 스타일 상수
|
||||
private GUIStyle headerStyle;
|
||||
private GUIStyle subHeaderStyle;
|
||||
private GUIStyle buttonStyle;
|
||||
private GUIStyle iconButtonStyle;
|
||||
|
||||
// SerializedProperty 참조
|
||||
private SerializedProperty motionSavePathProp;
|
||||
@ -37,8 +46,40 @@ namespace EasyMotionRecorder
|
||||
useDontDestroyOnLoadProp = serializedObject.FindProperty("useDontDestroyOnLoad");
|
||||
}
|
||||
|
||||
private void InitializeStyles()
|
||||
{
|
||||
if (headerStyle == null)
|
||||
{
|
||||
headerStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 14,
|
||||
normal = { textColor = EditorGUIUtility.isProSkin ? Color.white : Color.black }
|
||||
};
|
||||
|
||||
subHeaderStyle = new GUIStyle(EditorStyles.boldLabel)
|
||||
{
|
||||
fontSize = 12,
|
||||
normal = { textColor = EditorGUIUtility.isProSkin ? new Color(0.7f, 0.7f, 0.7f) : new Color(0.3f, 0.3f, 0.3f) }
|
||||
};
|
||||
|
||||
buttonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fixedHeight = 28,
|
||||
fontSize = 11
|
||||
};
|
||||
|
||||
iconButtonStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
fixedHeight = 24,
|
||||
fixedWidth = 24
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
InitializeStyles();
|
||||
|
||||
if (targets.Length > 1)
|
||||
{
|
||||
DrawMultiObjectGUI();
|
||||
@ -47,70 +88,57 @@ namespace EasyMotionRecorder
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField("저장 경로 관리", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 기본 설정
|
||||
DrawHeader();
|
||||
DrawBasicSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 고급 설정
|
||||
DrawExportSettings();
|
||||
DrawAdvancedSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 자동 출력 옵션
|
||||
DrawAutoExportSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 버튼들
|
||||
DrawComponentInfo();
|
||||
DrawActionButtons();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawMultiObjectGUI()
|
||||
private void DrawHeader()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField($"저장 경로 관리 ({targets.Length}개 선택됨)", EditorStyles.boldLabel);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 멀티 오브젝트 기본 설정
|
||||
DrawMultiObjectBasicSettings();
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.LabelField("📁 저장 경로 관리자", headerStyle);
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// 멀티 오브젝트 고급 설정
|
||||
DrawMultiObjectAdvancedSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 멀티 오브젝트 자동 출력 옵션
|
||||
DrawMultiObjectAutoExportSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
// 멀티 오브젝트 액션
|
||||
DrawMultiObjectActions();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorGUILayout.Space(5);
|
||||
DrawSeparator();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
|
||||
private void DrawSeparator()
|
||||
{
|
||||
var rect = EditorGUILayout.GetControlRect(false, 1);
|
||||
rect.height = 1;
|
||||
EditorGUI.DrawRect(rect, EditorGUIUtility.isProSkin ? new Color(0.2f, 0.2f, 0.2f) : new Color(0.7f, 0.7f, 0.7f));
|
||||
}
|
||||
|
||||
private void DrawBasicSettings()
|
||||
{
|
||||
EditorGUILayout.LabelField("기본 설정", EditorStyles.boldLabel);
|
||||
DrawSectionHeader("💾 기본 설정", true);
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 통합 저장 경로
|
||||
EditorGUILayout.LabelField("저장 경로", subHeaderStyle);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.PropertyField(motionSavePathProp, new GUIContent("저장 경로"));
|
||||
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
||||
|
||||
EditorGUILayout.PropertyField(motionSavePathProp, GUIContent.none);
|
||||
|
||||
// 📂 폴더 선택 버튼
|
||||
if (GUILayout.Button(new GUIContent("📂", "폴더 선택 다이얼로그 열기"), iconButtonStyle))
|
||||
{
|
||||
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", "Assets", "");
|
||||
string currentPath = motionSavePathProp.stringValue;
|
||||
string startPath = !string.IsNullOrEmpty(currentPath) ? currentPath : "Assets";
|
||||
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", startPath, "");
|
||||
if (!string.IsNullOrEmpty(newPath))
|
||||
{
|
||||
// Assets 폴더 기준으로 상대 경로로 변환
|
||||
@ -123,57 +151,212 @@ namespace EasyMotionRecorder
|
||||
savePathManager.SetMotionSavePath(newPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 폴더 열기 버튼
|
||||
if (GUILayout.Button(new GUIContent("🔍", "파일 탐색기에서 폴더 열기"), iconButtonStyle))
|
||||
{
|
||||
string path = savePathManager.GetMotionSavePath();
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
EditorUtility.RevealInFinder(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("알림", "폴더가 아직 생성되지 않았습니다.", "확인");
|
||||
}
|
||||
}
|
||||
|
||||
// ➕ 폴더 생성 버튼
|
||||
if (GUILayout.Button(new GUIContent("➕", "폴더가 없으면 생성"), iconButtonStyle))
|
||||
{
|
||||
string path = savePathManager.GetMotionSavePath();
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.DisplayDialog("성공", $"폴더가 생성되었습니다:\n{path}", "확인");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", $"폴더 생성 실패:\n{e.Message}", "확인");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("알림", "폴더가 이미 존재합니다.", "확인");
|
||||
}
|
||||
}
|
||||
|
||||
// 🏠 기본 경로 버튼
|
||||
if (GUILayout.Button(new GUIContent("🏠", "기본 경로(Assets/Motion)로 설정"), iconButtonStyle))
|
||||
{
|
||||
string defaultPath = "Assets/Motion";
|
||||
motionSavePathProp.stringValue = defaultPath;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
savePathManager.SetMotionSavePath(defaultPath);
|
||||
}
|
||||
|
||||
// 📋 경로 복사 버튼
|
||||
if (GUILayout.Button(new GUIContent("📋", "경로를 클립보드에 복사"), iconButtonStyle))
|
||||
{
|
||||
string path = savePathManager.GetMotionSavePath();
|
||||
EditorGUIUtility.systemCopyBuffer = path;
|
||||
EditorUtility.DisplayDialog("복사 완료", $"경로가 클립보드에 복사되었습니다:\n{path}", "확인");
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.HelpBox("모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.HelpBox("📝 모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
|
||||
private void DrawExportSettings()
|
||||
{
|
||||
showExportSettings = DrawSectionHeader("🚀 자동 출력 옵션", showExportSettings);
|
||||
|
||||
if (showExportSettings)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
EditorGUILayout.HelpBox("💾 저장 시 자동으로 출력할 파일 형식을 선택하세요.", MessageType.Info);
|
||||
EditorGUILayout.Space(3);
|
||||
|
||||
// 그리드 레이아웃으로 옵션 나열
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
DrawToggleOption("🤖 휴머노이드", exportHumanoidOnSaveProp);
|
||||
DrawToggleOption("🔧 제네릭", exportGenericOnSaveProp);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
DrawToggleOption("📄 FBX ASCII", exportFBXAsciiOnSaveProp);
|
||||
DrawToggleOption("📊 FBX Binary", exportFBXBinaryOnSaveProp);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawToggleOption(string label, SerializedProperty prop)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
prop.boolValue = EditorGUILayout.Toggle(prop.boolValue, GUILayout.Width(15));
|
||||
EditorGUILayout.LabelField(label, GUILayout.ExpandWidth(true));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void DrawAdvancedSettings()
|
||||
{
|
||||
showAdvancedSettings = EditorGUILayout.Foldout(showAdvancedSettings, "고급 설정");
|
||||
showAdvancedSettings = DrawSectionHeader("⚙️ 고급 설정", showAdvancedSettings);
|
||||
|
||||
if (showAdvancedSettings)
|
||||
{
|
||||
EditorGUI.indentLevel++;
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 서브디렉토리 생성 여부
|
||||
EditorGUILayout.PropertyField(createSubdirectoriesProp, new GUIContent("서브디렉토리 자동 생성"));
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("📁 서브디렉토리 자동 생성", GUILayout.Width(200));
|
||||
createSubdirectoriesProp.boolValue = EditorGUILayout.Toggle(createSubdirectoriesProp.boolValue);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
// 인스턴스 ID (읽기 전용)
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.PropertyField(instanceIDProp, new GUIContent("인스턴스 ID (자동 생성)"));
|
||||
GUI.enabled = true;
|
||||
EditorGUILayout.Space(3);
|
||||
|
||||
// DontDestroyOnLoad 설정
|
||||
EditorGUILayout.PropertyField(useDontDestroyOnLoadProp, new GUIContent("씬 전환 시 유지"));
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("🔒 씬 전환 시 유지", GUILayout.Width(200));
|
||||
useDontDestroyOnLoadProp.boolValue = EditorGUILayout.Toggle(useDontDestroyOnLoadProp.boolValue);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.HelpBox("인스턴스 ID는 자동으로 생성되며 각 EasyMotionRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
EditorGUI.indentLevel--;
|
||||
// 인스턴스 ID (읽기 전용)
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("🆔 인스턴스 ID", GUILayout.Width(120));
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.TextField(instanceIDProp.stringValue);
|
||||
GUI.enabled = true;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(3);
|
||||
EditorGUILayout.HelpBox("📝 인스턴스 ID는 자동으로 생성되며 각 EasyMotionRecorder 인스턴스를 구분합니다.", MessageType.Info);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAutoExportSettings()
|
||||
private void DrawComponentInfo()
|
||||
{
|
||||
EditorGUILayout.LabelField("자동 출력 옵션", EditorStyles.boldLabel);
|
||||
EditorGUILayout.HelpBox("저장 시 자동으로 출력할 파일 형식을 선택하세요.", MessageType.Info);
|
||||
showComponentInfo = DrawSectionHeader("🔍 컴포넌트 상태", showComponentInfo);
|
||||
|
||||
EditorGUILayout.PropertyField(exportHumanoidOnSaveProp, new GUIContent("휴머노이드 애니메이션 자동 출력"));
|
||||
EditorGUILayout.PropertyField(exportGenericOnSaveProp, new GUIContent("제네릭 애니메이션 자동 출력"));
|
||||
EditorGUILayout.PropertyField(exportFBXAsciiOnSaveProp, new GUIContent("FBX ASCII 자동 출력"));
|
||||
EditorGUILayout.PropertyField(exportFBXBinaryOnSaveProp, new GUIContent("FBX Binary 자동 출력"));
|
||||
if (showComponentInfo)
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
// 연결된 컴포넌트들 확인
|
||||
var motionRecorder = savePathManager.GetComponent<Entum.MotionDataRecorder>();
|
||||
var faceRecorder = savePathManager.GetComponent<Entum.FaceAnimationRecorder>();
|
||||
var objectRecorder = savePathManager.GetComponent<Entum.ObjectMotionRecorder>();
|
||||
|
||||
EditorGUILayout.LabelField("연결된 컴포넌트:", subHeaderStyle);
|
||||
EditorGUILayout.Space(3);
|
||||
|
||||
DrawComponentStatus("🎥 MotionDataRecorder", motionRecorder != null);
|
||||
DrawComponentStatus("😀 FaceAnimationRecorder", faceRecorder != null);
|
||||
DrawComponentStatus("📦 ObjectMotionRecorder", objectRecorder != null);
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawComponentStatus(string componentName, bool isConnected)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
string status = isConnected ? "✅" : "❌";
|
||||
string statusText = isConnected ? "연결됨" : "비연결";
|
||||
EditorGUILayout.LabelField($"{status} {componentName}", GUILayout.Width(200));
|
||||
EditorGUILayout.LabelField(statusText, isConnected ? EditorStyles.miniLabel : EditorStyles.centeredGreyMiniLabel);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private bool DrawSectionHeader(string title, bool expanded)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
expanded = EditorGUILayout.Foldout(expanded, title, true, subHeaderStyle);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private void DrawActionButtons()
|
||||
{
|
||||
EditorGUILayout.LabelField("작업", EditorStyles.boldLabel);
|
||||
DrawSectionHeader("🚀 작업", true);
|
||||
|
||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
// 기본값으로 리셋 버튼
|
||||
if (GUILayout.Button("기본값으로 리셋", GUILayout.Height(30)))
|
||||
if (GUILayout.Button("🔄 기본값으로 리셋", buttonStyle))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("기본값으로 리셋",
|
||||
if (EditorUtility.DisplayDialog("설정 리셋",
|
||||
"모든 설정을 기본값으로 되돌리시겠습니까?", "확인", "취소"))
|
||||
{
|
||||
savePathManager.ResetToDefaults();
|
||||
@ -183,7 +366,7 @@ namespace EasyMotionRecorder
|
||||
}
|
||||
|
||||
// 폴더 열기 버튼
|
||||
if (GUILayout.Button("저장 폴더 열기", GUILayout.Height(30)))
|
||||
if (GUILayout.Button("📂 저장 폴더 열기", buttonStyle))
|
||||
{
|
||||
string path = savePathManager.GetMotionSavePath();
|
||||
if (Directory.Exists(path))
|
||||
@ -192,11 +375,92 @@ namespace EasyMotionRecorder
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", "저장 폴더가 존재하지 않습니다.", "확인");
|
||||
if (EditorUtility.DisplayDialog("폴더 생성",
|
||||
"저장 폴더가 아직 생성되지 않았습니다.\n지금 생성하시겠습니까?", "생성", "취소"))
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
EditorUtility.RevealInFinder(path);
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(3);
|
||||
|
||||
// 추가 유용한 버튼들
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("📊 인스턴스 상태 확인", buttonStyle))
|
||||
{
|
||||
ShowInstanceInfo();
|
||||
}
|
||||
|
||||
if (GUILayout.Button("🛠️ 설정 내보내기", buttonStyle))
|
||||
{
|
||||
ExportSettings();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space(10);
|
||||
}
|
||||
|
||||
private void ShowInstanceInfo()
|
||||
{
|
||||
var motionRecorder = savePathManager.GetComponent<Entum.MotionDataRecorder>();
|
||||
var faceRecorder = savePathManager.GetComponent<Entum.FaceAnimationRecorder>();
|
||||
var objectRecorder = savePathManager.GetComponent<Entum.ObjectMotionRecorder>();
|
||||
|
||||
string info = $"인스턴스 ID: {savePathManager.InstanceID}\n";
|
||||
info += $"저장 경로: {savePathManager.GetMotionSavePath()}\n\n";
|
||||
info += "연결된 컴포넌트:\n";
|
||||
info += $"- MotionDataRecorder: {(motionRecorder != null ? "✅" : "❌")}\n";
|
||||
info += $"- FaceAnimationRecorder: {(faceRecorder != null ? "✅" : "❌")}\n";
|
||||
info += $"- ObjectMotionRecorder: {(objectRecorder != null ? "✅" : "❌")}";
|
||||
|
||||
EditorUtility.DisplayDialog("인스턴스 정보", info, "확인");
|
||||
}
|
||||
|
||||
private void ExportSettings()
|
||||
{
|
||||
string settings = $"SavePathManager 설정:\n";
|
||||
settings += $"저장 경로: {savePathManager.GetMotionSavePath()}\n";
|
||||
settings += $"휴머노이드 자동 출력: {savePathManager.ExportHumanoidOnSave}\n";
|
||||
settings += $"제네릭 자동 출력: {savePathManager.ExportGenericOnSave}\n";
|
||||
settings += $"FBX ASCII 자동 출력: {savePathManager.ExportFBXAsciiOnSave}\n";
|
||||
settings += $"FBX Binary 자동 출력: {savePathManager.ExportFBXBinaryOnSave}";
|
||||
|
||||
GUIUtility.systemCopyBuffer = settings;
|
||||
EditorUtility.DisplayDialog("설정 내보내기", "설정이 클립보드에 복사되었습니다.", "확인");
|
||||
}
|
||||
|
||||
private void DrawMultiObjectGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField($"📁 저장 경로 관리자 ({targets.Length}개 선택됨)", headerStyle);
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectBasicSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectAdvancedSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectAutoExportSettings();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
DrawMultiObjectActions();
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void DrawMultiObjectBasicSettings()
|
||||
@ -218,9 +482,12 @@ namespace EasyMotionRecorder
|
||||
}
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
||||
// 📂 폴더 선택 버튼
|
||||
if (GUILayout.Button(new GUIContent("📂", "폴더 선택 다이얼로그 열기"), iconButtonStyle))
|
||||
{
|
||||
string selectedPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", "Assets", "");
|
||||
string currentPath = motionSavePathProp.stringValue;
|
||||
string startPath = !string.IsNullOrEmpty(currentPath) ? currentPath : "Assets";
|
||||
string selectedPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", startPath, "");
|
||||
if (!string.IsNullOrEmpty(selectedPath))
|
||||
{
|
||||
// Assets 폴더 기준으로 상대 경로로 변환
|
||||
@ -237,9 +504,54 @@ namespace EasyMotionRecorder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🏠 기본 경로 버튼
|
||||
if (GUILayout.Button(new GUIContent("🏠", "기본 경로(Assets/Motion)로 설정"), iconButtonStyle))
|
||||
{
|
||||
string defaultPath = "Assets/Motion";
|
||||
motionSavePathProp.stringValue = defaultPath;
|
||||
foreach (SavePathManager manager in targets)
|
||||
{
|
||||
manager.SetMotionSavePath(defaultPath);
|
||||
EditorUtility.SetDirty(manager);
|
||||
}
|
||||
}
|
||||
|
||||
// ➕ 폴더 생성 버튼
|
||||
if (GUILayout.Button(new GUIContent("➕", "선택된 모든 경로의 폴더 생성"), iconButtonStyle))
|
||||
{
|
||||
int createdCount = 0;
|
||||
int existCount = 0;
|
||||
|
||||
foreach (SavePathManager manager in targets)
|
||||
{
|
||||
string path = manager.GetMotionSavePath();
|
||||
if (!Directory.Exists(path))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(path);
|
||||
createdCount++;
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError($"폴더 생성 실패: {path} - {e.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
existCount++;
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
EditorUtility.DisplayDialog("폴더 생성 완료",
|
||||
$"생성됨: {createdCount}개\n이미 존재: {existCount}개\n총 선택: {targets.Length}개", "확인");
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.HelpBox("모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("📝 모션, 표정, 오브젝트 파일이 모두 이 경로에 저장됩니다.", MessageType.Info);
|
||||
}
|
||||
|
||||
private void DrawMultiObjectAdvancedSettings()
|
||||
@ -328,4 +640,4 @@ namespace EasyMotionRecorder
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@ -20,25 +20,26 @@ http://opensource.org/licenses/mit-license.php
|
||||
#if UNITY_EDITOR
|
||||
namespace Entum {
|
||||
/// <summary>
|
||||
/// Blendshapeの動きを記録するクラス
|
||||
/// リップシンクは後入れでTimeline上にAudioClipをつけて、みたいな可能性が高いので
|
||||
/// Blendshape의 동작을 기록하는 클래스
|
||||
/// 립싱크는 나중에 Timeline에 AudioClip을 추가할 가능성이 높으므로
|
||||
/// Exclusive(除外)するBlendshape名を登録できるようにしています。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(MotionDataRecorder))]
|
||||
public class FaceAnimationRecorder:MonoBehaviour {
|
||||
[Header("表情記録を同時に行う場合はtrueにします")]
|
||||
[SerializeField]
|
||||
[Header("표정 애니메이션 기록 설정")]
|
||||
[SerializeField, Tooltip("모션 데이터와 함께 표정 애니메이션도 기록합니다")]
|
||||
private bool _recordFaceBlendshapes = false;
|
||||
|
||||
[Header("リップシンクを記録したくない場合はここにモーフ名を入れていく 例:face_mouse_eなど")]
|
||||
[SerializeField]
|
||||
private List<string> _exclusiveBlendshapeNames;
|
||||
[Header("제외할 블렌드셈이프 설정")]
|
||||
[SerializeField, Tooltip("립싱크나 특정 블렌드셈이프를 기록에서 제외하고 싶은 경우 사용합니다. 예: face_mouse_e")]
|
||||
private List<string> _exclusiveBlendshapeNames = new List<string>();
|
||||
|
||||
[Tooltip("記録するFPS。0で制限しない。UpdateのFPSは超えられません。")]
|
||||
[Header("고급 옵션")]
|
||||
[SerializeField, Tooltip("기록할 FPS. 0으로 설정하면 제한하지 않습니다.")]
|
||||
public float TargetFPS = 60.0f;
|
||||
|
||||
[HideInInspector, SerializeField] private string instanceID = "";
|
||||
[HideInInspector, SerializeField] private SavePathManager _savePathManager;
|
||||
[SerializeField, HideInInspector] private string instanceID = "";
|
||||
[SerializeField, HideInInspector] private SavePathManager _savePathManager;
|
||||
|
||||
private MotionDataRecorder _animRecorder;
|
||||
|
||||
@ -62,9 +63,17 @@ namespace Entum {
|
||||
// Use this for initialization
|
||||
private void OnEnable() {
|
||||
_animRecorder = GetComponent<MotionDataRecorder>();
|
||||
|
||||
// 이벤트 중복 연결 방지
|
||||
_animRecorder.OnRecordStart -= RecordStart;
|
||||
_animRecorder.OnRecordEnd -= RecordEnd;
|
||||
|
||||
// 이벤트 연결
|
||||
_animRecorder.OnRecordStart += RecordStart;
|
||||
_animRecorder.OnRecordEnd += RecordEnd;
|
||||
|
||||
Debug.Log($"FaceAnimationRecorder 이벤트 연결됨 - 인스턴스: {instanceID}");
|
||||
|
||||
// SavePathManager 자동 찾기
|
||||
if (_savePathManager == null)
|
||||
{
|
||||
@ -79,6 +88,22 @@ namespace Entum {
|
||||
|
||||
if(_animRecorder.CharacterAnimator != null) {
|
||||
_smeshs = GetSkinnedMeshRenderers(_animRecorder.CharacterAnimator);
|
||||
Debug.Log($"SkinnedMeshRenderer 초기화 - 인스턴스: {instanceID}, 발견된 개수: {_smeshs?.Length ?? 0}");
|
||||
|
||||
// 각 SkinnedMeshRenderer의 블렌드셰이프 개수 로그
|
||||
if (_smeshs != null)
|
||||
{
|
||||
for (int i = 0; i < _smeshs.Length; i++)
|
||||
{
|
||||
var mesh = _smeshs[i];
|
||||
var blendShapeCount = mesh.sharedMesh?.blendShapeCount ?? 0;
|
||||
Debug.Log($" - {mesh.name}: {blendShapeCount}개 블렌드셰이프");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"CharacterAnimator가 null - 인스턴스: {instanceID}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,21 +129,65 @@ namespace Entum {
|
||||
}
|
||||
|
||||
private void OnDisable() {
|
||||
Debug.Log($"FaceAnimationRecorder OnDisable 호출 - 인스턴스: {instanceID}, 녹화 중: {_recording}");
|
||||
|
||||
if(_recording) {
|
||||
RecordEnd();
|
||||
_recording = false;
|
||||
}
|
||||
|
||||
if(_animRecorder == null) return;
|
||||
if(_animRecorder == null)
|
||||
{
|
||||
Debug.LogWarning($"FaceAnimationRecorder OnDisable: _animRecorder가 null - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
|
||||
_animRecorder.OnRecordStart -= RecordStart;
|
||||
_animRecorder.OnRecordEnd -= RecordEnd;
|
||||
Debug.Log($"FaceAnimationRecorder 이벤트 해제됨 - 인스턴스: {instanceID}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 記録開始
|
||||
/// </summary>
|
||||
private void RecordStart() {
|
||||
Debug.Log($"FaceAnimationRecorder.RecordStart 호출됨 - 인스턴스: {instanceID}, 활성화: {_recordFaceBlendshapes}, 이미 녹화 중: {_recording}");
|
||||
|
||||
if(_recordFaceBlendshapes == false) {
|
||||
Debug.Log($"표정 기록이 비활성화되어 있음 - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_recording) {
|
||||
Debug.Log($"이미 표정 녹화가 진행 중임 - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
|
||||
// _animRecorder가 null이면 다시 찾기
|
||||
if (_animRecorder == null) {
|
||||
_animRecorder = GetComponent<MotionDataRecorder>();
|
||||
if (_animRecorder != null) {
|
||||
Debug.Log($"RecordStart에서 MotionDataRecorder 재연결 - 인스턴스: {instanceID}");
|
||||
|
||||
// 이벤트도 다시 연결
|
||||
_animRecorder.OnRecordStart -= RecordStart;
|
||||
_animRecorder.OnRecordEnd -= RecordEnd;
|
||||
_animRecorder.OnRecordStart += RecordStart;
|
||||
_animRecorder.OnRecordEnd += RecordEnd;
|
||||
Debug.Log($"이벤트 재연결 완료 - 인스턴스: {instanceID}");
|
||||
} else {
|
||||
Debug.LogError($"MotionDataRecorder를 찾을 수 없음 - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// SkinnedMeshRenderer 재초기화 (두 번째 녹화 시 문제 방지)
|
||||
if(_animRecorder.CharacterAnimator != null) {
|
||||
_smeshs = GetSkinnedMeshRenderers(_animRecorder.CharacterAnimator);
|
||||
Debug.Log($"RecordStart에서 SkinnedMeshRenderer 재초기화 - 인스턴스: {instanceID}, 발견된 개수: {_smeshs?.Length ?? 0}");
|
||||
}
|
||||
else {
|
||||
Debug.LogError($"CharacterAnimator가 null이어서 표정 녹화를 시작할 수 없음 - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -132,11 +201,14 @@ namespace Entum {
|
||||
_recordedTime = 0f;
|
||||
_startTime = Time.time;
|
||||
|
||||
Debug.Log($"표정 애니메이션 녹화 시작 - 인스턴스: {instanceID}");
|
||||
Debug.Log($"표정 애니메이션 녹화 시작 - 인스턴스: {instanceID}, SessionID: {_animRecorder.SessionID}");
|
||||
}
|
||||
|
||||
private void RecordEnd() {
|
||||
Debug.Log($"FaceAnimationRecorder.RecordEnd 호출됨 - 인스턴스: {instanceID}, 녹화 중: {_recording}");
|
||||
|
||||
if(_recording == false) {
|
||||
Debug.Log($"이미 녹화가 중단된 상태 - 인스턴스: {instanceID}");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -144,10 +216,32 @@ namespace Entum {
|
||||
Debug.Log($"표정 애니메이션 녹화 종료 - 인스턴스: {instanceID}, 총 프레임: {_frameCount}");
|
||||
|
||||
// 바로 애니메이션 클립으로 출력
|
||||
ExportFacialAnimationClip(_animRecorder.CharacterAnimator, _facialData);
|
||||
if (_facialData != null && _facialData.Faces.Count > 0)
|
||||
{
|
||||
ExportFacialAnimationClip(_animRecorder.CharacterAnimator, _facialData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"표정 데이터가 없어서 출력할 수 없음 - 인스턴스: {instanceID}");
|
||||
}
|
||||
|
||||
// 상태 완전 리셋 (다음 녹화를 위해)
|
||||
_frameCount = 0;
|
||||
_recordedTime = 0f;
|
||||
_facialData = null;
|
||||
_past = new CharacterFacialData.SerializeHumanoidFace() {
|
||||
BlendShapeNames = new List<string>(),
|
||||
BlendShapeValues = new List<float>(),
|
||||
SkinnedMeshRendererNames = new List<string>()
|
||||
};
|
||||
|
||||
Debug.Log($"표정 녹화 상태 리셋 완료 - 인스턴스: {instanceID}");
|
||||
}
|
||||
|
||||
private void ExportFacialAnimationClip(Animator root, CharacterFacialData facial) {
|
||||
Debug.Log($"ExportFacialAnimationClip 시작 - 인스턴스: {instanceID}");
|
||||
Debug.Log($"facial null 여부: {facial == null}, Faces 개수: {facial?.Faces?.Count ?? 0}");
|
||||
|
||||
if(facial == null || facial.Faces.Count == 0) {
|
||||
Debug.LogError("저장할 표정 데이터가 없습니다.");
|
||||
return;
|
||||
@ -389,6 +483,20 @@ namespace Entum {
|
||||
return;
|
||||
}
|
||||
|
||||
// SkinnedMeshRenderer 배열이 null이거나 비어있으면 중단
|
||||
if (_smeshs == null || _smeshs.Length == 0) {
|
||||
if (_frameCount == 0) {
|
||||
Debug.LogError($"LateUpdate: SkinnedMeshRenderer 배열이 null이거나 비어있음 - 인스턴스: {instanceID}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 첫 번째 프레임에서만 로그 출력
|
||||
if (_frameCount == 0)
|
||||
{
|
||||
Debug.Log($"LateUpdate 표정 데이터 수집 시작 - 인스턴스: {instanceID}, SkinnedMesh 개수: {_smeshs?.Length ?? 0}");
|
||||
}
|
||||
|
||||
_recordedTime = Time.time - _startTime;
|
||||
|
||||
if (TargetFPS != 0.0f)
|
||||
@ -437,6 +545,19 @@ namespace Entum {
|
||||
current.Time = _recordedTime;
|
||||
_facialData.Faces.Add(current);
|
||||
_past = current;
|
||||
|
||||
// 10프레임마다 진행상황 로그 출력
|
||||
if (_frameCount % 10 == 0)
|
||||
{
|
||||
Debug.Log($"표정 데이터 수집 중 - 프레임: {_frameCount}, 총 데이터: {_facialData.Faces.Count}, 블렌드셰이프: {current.BlendShapeNames.Count}개");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 변화 없어서 스킵된 경우 (100프레임마다 로그)
|
||||
if (_frameCount % 100 == 0 && _frameCount > 0)
|
||||
{
|
||||
Debug.Log($"표정 변화 없음 - 프레임: {_frameCount}, 스킵됨");
|
||||
}
|
||||
}
|
||||
|
||||
_frameCount++;
|
||||
|
||||
@ -13,17 +13,17 @@ using System.IO;
|
||||
namespace Entum
|
||||
{
|
||||
/// <summary>
|
||||
/// CSVに吐かれたモーションデータを再生する
|
||||
/// CSV에 출력된 모션 데이터를 재생하는 클래스
|
||||
/// </summary>
|
||||
public class MotionDataPlayerCSV : MotionDataPlayer
|
||||
{
|
||||
[SerializeField, Tooltip("スラッシュで終わる形で")]
|
||||
[SerializeField, Tooltip("슬래시로 끝나는 형태로")]
|
||||
private string _recordedDirectory;
|
||||
|
||||
[SerializeField, Tooltip("拡張子も")]
|
||||
[SerializeField, Tooltip("확장자도 포함하여")]
|
||||
private string _recordedFileName;
|
||||
|
||||
// Use this for initialization
|
||||
// 초기화용
|
||||
private void Start()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_recordedDirectory))
|
||||
@ -35,10 +35,10 @@ namespace Entum
|
||||
LoadCSVData(motionCSVPath);
|
||||
}
|
||||
|
||||
//CSVから_recordedMotionDataを作る
|
||||
// CSV에서 _recordedMotionData를 생성
|
||||
private void LoadCSVData(string motionDataPath)
|
||||
{
|
||||
//ファイルが存在しなければ終了
|
||||
// 파일이 존재하지 않으면 종료
|
||||
if (!File.Exists(motionDataPath))
|
||||
{
|
||||
return;
|
||||
@ -50,7 +50,7 @@ namespace Entum
|
||||
FileStream fs = null;
|
||||
StreamReader sr = null;
|
||||
|
||||
//ファイル読み込み
|
||||
// 파일 읽기
|
||||
try
|
||||
{
|
||||
fs = new FileStream(motionDataPath, FileMode.Open);
|
||||
@ -73,7 +73,7 @@ namespace Entum
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogError("ファイル読み込み失敗!" + e.Message + e.StackTrace);
|
||||
Debug.LogError("파일 읽기 실패!" + e.Message + e.StackTrace);
|
||||
}
|
||||
|
||||
if (sr != null)
|
||||
|
||||
@ -14,25 +14,25 @@ using System.IO;
|
||||
namespace Entum
|
||||
{
|
||||
/// <summary>
|
||||
/// モーションデータをCSVに記録するクラス
|
||||
/// ランタイムでも記録できる
|
||||
/// 모션 데이터를 CSV에 기록하는 클래스
|
||||
/// 런타임에도 기록 가능
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(31000)]
|
||||
public class MotionDataRecorderCSV : MotionDataRecorder
|
||||
{
|
||||
[SerializeField, Tooltip("スラッシュで終わる形で")]
|
||||
[SerializeField, Tooltip("슬래시로 끝나는 형태로")]
|
||||
private string _outputDirectory;
|
||||
|
||||
[SerializeField, Tooltip("拡張子も")]
|
||||
[SerializeField, Tooltip("확장자도 포함하여")]
|
||||
private string _outputFileName;
|
||||
|
||||
protected override void WriteAnimationFile()
|
||||
{
|
||||
//ファイルオープン
|
||||
// 파일 열기
|
||||
string directoryStr = _outputDirectory;
|
||||
if (directoryStr == "")
|
||||
{
|
||||
//自動設定ディレクトリ
|
||||
// 자동 설정 디렉토리
|
||||
directoryStr = Application.streamingAssetsPath + "/";
|
||||
|
||||
if (!Directory.Exists(directoryStr))
|
||||
@ -44,7 +44,7 @@ namespace Entum
|
||||
string fileNameStr = _outputFileName;
|
||||
if (fileNameStr == "")
|
||||
{
|
||||
//自動設定ファイル名
|
||||
// 자동 설정 파일명
|
||||
fileNameStr = string.Format("motion_{0:yyyy_MM_dd_HH_mm_ss}.csv", DateTime.Now);
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ namespace Entum
|
||||
sw.WriteLine(seriStr);
|
||||
}
|
||||
|
||||
//ファイルクローズ
|
||||
// 파일 닫기
|
||||
try
|
||||
{
|
||||
sw.Close();
|
||||
@ -67,7 +67,7 @@ namespace Entum
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError("ファイル書き出し失敗!" + e.Message + e.StackTrace);
|
||||
Debug.LogError("파일 쓰기 실패!" + e.Message + e.StackTrace);
|
||||
}
|
||||
|
||||
if (sw != null)
|
||||
|
||||
@ -2108,5 +2108,178 @@ namespace Entum
|
||||
|
||||
[SerializeField]
|
||||
public SummaryInfo Summary = new SummaryInfo();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 선택된 여러 HumanoidPoses 에셋에서 휴머노이드 애니메이션 일괄 출력
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export Humanoid Animations", false, 1)]
|
||||
public static void BatchExportHumanoidAnimations()
|
||||
{
|
||||
var selectedHumanoidPoses = GetSelectedHumanoidPoses();
|
||||
if (selectedHumanoidPoses.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", "HumanoidPoses 에셋을 하나 이상 선택해주세요.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
BatchExportProcess(selectedHumanoidPoses, "휴머노이드 애니메이션", (pose) => pose.ExportHumanoidAnim());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 여러 HumanoidPoses 에셋에서 제네릭 애니메이션 일괄 출력
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export Generic Animations", false, 2)]
|
||||
public static void BatchExportGenericAnimations()
|
||||
{
|
||||
var selectedHumanoidPoses = GetSelectedHumanoidPoses();
|
||||
if (selectedHumanoidPoses.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", "HumanoidPoses 에셋을 하나 이상 선택해주세요.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
BatchExportProcess(selectedHumanoidPoses, "제네릭 애니메이션", (pose) => pose.ExportGenericAnim());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 여러 HumanoidPoses 에셋에서 FBX ASCII 일괄 출력
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export FBX ASCII", false, 3)]
|
||||
public static void BatchExportFBXAscii()
|
||||
{
|
||||
var selectedHumanoidPoses = GetSelectedHumanoidPoses();
|
||||
if (selectedHumanoidPoses.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", "HumanoidPoses 에셋을 하나 이상 선택해주세요.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
BatchExportProcess(selectedHumanoidPoses, "FBX ASCII", (pose) => pose.ExportFBXAscii());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 여러 HumanoidPoses 에셋에서 FBX Binary 일괄 출력
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export FBX Binary", false, 4)]
|
||||
public static void BatchExportFBXBinary()
|
||||
{
|
||||
var selectedHumanoidPoses = GetSelectedHumanoidPoses();
|
||||
if (selectedHumanoidPoses.Length == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("오류", "HumanoidPoses 에셋을 하나 이상 선택해주세요.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
BatchExportProcess(selectedHumanoidPoses, "FBX Binary", (pose) => pose.ExportFBXBinary());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 선택된 오브젝트에서 HumanoidPoses 에셋들을 찾아 반환
|
||||
/// </summary>
|
||||
private static HumanoidPoses[] GetSelectedHumanoidPoses()
|
||||
{
|
||||
var selectedObjects = Selection.objects;
|
||||
var humanoidPosesList = new List<HumanoidPoses>();
|
||||
|
||||
foreach (var obj in selectedObjects)
|
||||
{
|
||||
if (obj is HumanoidPoses humanoidPoses)
|
||||
{
|
||||
humanoidPosesList.Add(humanoidPoses);
|
||||
}
|
||||
}
|
||||
|
||||
return humanoidPosesList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 일괄 출력 프로세스 실행
|
||||
/// </summary>
|
||||
private static void BatchExportProcess(HumanoidPoses[] poses, string exportType, System.Action<HumanoidPoses> exportAction)
|
||||
{
|
||||
if (poses.Length == 0) return;
|
||||
|
||||
bool confirmed = EditorUtility.DisplayDialog(
|
||||
"일괄 출력 확인",
|
||||
$"{poses.Length}개의 HumanoidPoses 에셋에서 {exportType} 파일을 출력하시겠습니까?",
|
||||
"출력 시작",
|
||||
"취소");
|
||||
|
||||
if (!confirmed) return;
|
||||
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
float progress = 0f;
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < poses.Length; i++)
|
||||
{
|
||||
var pose = poses[i];
|
||||
progress = (float)i / poses.Length;
|
||||
|
||||
string assetName = pose.name;
|
||||
bool cancelled = EditorUtility.DisplayCancelableProgressBar(
|
||||
$"{exportType} 일괄 출력 중...",
|
||||
$"처리 중: {assetName} ({i + 1}/{poses.Length})",
|
||||
progress);
|
||||
|
||||
if (cancelled)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.DisplayDialog("취소됨", "일괄 출력이 사용자에 의해 취소되었습니다.", "확인");
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Debug.Log($"[일괄 출력] {exportType} 처리 시작: {assetName}");
|
||||
exportAction(pose);
|
||||
successCount++;
|
||||
Debug.Log($"[일괄 출력] {exportType} 처리 완료: {assetName}");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
failCount++;
|
||||
Debug.LogError($"[일괄 출력] {exportType} 처리 실패: {assetName} - {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
// 결과 다이얼로그 표시
|
||||
string message = $"{exportType} 일괄 출력이 완료되었습니다.\n\n" +
|
||||
$"성공: {successCount}개\n" +
|
||||
$"실패: {failCount}개\n" +
|
||||
$"총 처리: {poses.Length}개";
|
||||
|
||||
if (failCount > 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("일괄 출력 완료 (일부 실패)", message, "확인");
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.DisplayDialog("일괄 출력 완료", message, "확인");
|
||||
}
|
||||
|
||||
Debug.Log($"[일괄 출력 완료] {exportType}: 성공 {successCount}개, 실패 {failCount}개");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 메뉴 아이템 검증: HumanoidPoses 에셋이 선택되었을 때만 메뉴 활성화
|
||||
/// </summary>
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export Humanoid Animations", true)]
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export Generic Animations", true)]
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export FBX ASCII", true)]
|
||||
[MenuItem("Assets/Easy Motion Recorder/Batch Export FBX Binary", true)]
|
||||
public static bool ValidateBatchExport()
|
||||
{
|
||||
return GetSelectedHumanoidPoses().Length > 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,10 +13,10 @@ using System;
|
||||
namespace Entum
|
||||
{
|
||||
/// <summary>
|
||||
/// モーションデータ再生クラス
|
||||
/// SpringBone, DynamicBone, BulletPhysicsImplなどの揺れ物アセットのScript Execution Orderを20000など
|
||||
/// 大きな値にしてください。
|
||||
/// DefaultExecutionOrder(11000) はVRIK系より処理順を遅くする、という意図です
|
||||
/// 모션 데이터 재생 클래스
|
||||
/// SpringBone, DynamicBone, BulletPhysicsImpl 등의 흔들리는 에셋의 Script Execution Order를 20000 등
|
||||
/// 큰 값으로 설정해주세요.
|
||||
/// DefaultExecutionOrder(11000)은 VRIK계보다 처리 순서를 느리게 한다는 의도입니다
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(11000)]
|
||||
public class MotionDataPlayer : MonoBehaviour
|
||||
@ -31,7 +31,7 @@ namespace Entum
|
||||
[SerializeField]
|
||||
private Animator _animator;
|
||||
|
||||
[SerializeField, Tooltip("再生開始フレームを指定します。0だとファイル先頭から開始です")]
|
||||
[SerializeField, Tooltip("재생 시작 프레임을 지정합니다. 0이면 파일 시작부터 시작합니다")]
|
||||
private int _startFrame;
|
||||
[SerializeField]
|
||||
private bool _playing;
|
||||
@ -75,7 +75,7 @@ namespace Entum
|
||||
_onPlayFinish += StopMotion;
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
// 매 프레임마다 호출
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(_playStartKey))
|
||||
@ -101,7 +101,7 @@ namespace Entum
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// モーションデータ再生開始
|
||||
/// 모션 데이터 재생 시작
|
||||
/// </summary>
|
||||
private void PlayMotion()
|
||||
{
|
||||
|
||||
@ -22,9 +22,9 @@ using UniHumanoid;
|
||||
namespace Entum
|
||||
{
|
||||
/// <summary>
|
||||
/// モーションデータ記録クラス
|
||||
/// スクリプト実行順はVRIKの処理が終わった後の姿勢を取得したいので
|
||||
/// 最大値=32000を指定しています
|
||||
/// 모션 데이터 기록 클래스
|
||||
/// 스크립트 실행 순서는 VRIK 처리가 끝난 후의 자세를 취득하기 위해
|
||||
/// 최대값 32000을 지정합니다
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(32000)]
|
||||
public class MotionDataRecorder : MonoBehaviour
|
||||
@ -68,10 +68,10 @@ namespace Entum
|
||||
public Action OnRecordStart;
|
||||
public Action OnRecordEnd;
|
||||
|
||||
[Tooltip("記録するFPS。0で制限しない。UpdateのFPSは超えられません。")]
|
||||
[Tooltip("기록할 FPS. 0으로 설정하면 제한하지 않습니다. Update FPS를 초과할 수 없습니다.")]
|
||||
public float TargetFPS = 60.0f;
|
||||
|
||||
// Use this for initialization
|
||||
// 초기화용
|
||||
private void Awake()
|
||||
{
|
||||
if (_animator == null)
|
||||
@ -148,7 +148,7 @@ namespace Entum
|
||||
}
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
// 매 프레임마다 호출
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_recording)
|
||||
@ -179,7 +179,7 @@ namespace Entum
|
||||
}
|
||||
}
|
||||
|
||||
//現在のフレームのHumanoidの姿勢を取得
|
||||
// 현재 프레임의 Humanoid 자세를 취득
|
||||
if (_poseHandler == null)
|
||||
{
|
||||
Debug.LogError("PoseHandler가 초기화되지 않았습니다. 녹화를 중단합니다.");
|
||||
@ -188,7 +188,7 @@ namespace Entum
|
||||
}
|
||||
|
||||
_poseHandler.GetHumanPose(ref _currentPose);
|
||||
//posesに取得한姿勢를書き込む
|
||||
// poses에 취득한 자세를 기록
|
||||
var serializedPose = new HumanoidPoses.SerializeHumanoidPose();
|
||||
|
||||
switch (_rootBoneSystem)
|
||||
@ -312,6 +312,10 @@ namespace Entum
|
||||
|
||||
Debug.Log($"모션 녹화 시작 - 인스턴스: {instanceID}, 세션: {SessionID}, T포즈 옵션: {_recordTPoseAtStart}");
|
||||
|
||||
// 이벤트 구독자 확인
|
||||
int subscriberCount = OnRecordStart?.GetInvocationList()?.Length ?? 0;
|
||||
Debug.Log($"OnRecordStart 이벤트 구독자 수: {subscriberCount}");
|
||||
|
||||
OnRecordStart?.Invoke();
|
||||
}
|
||||
|
||||
@ -659,18 +663,15 @@ namespace Entum
|
||||
{
|
||||
try
|
||||
{
|
||||
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Humanoid.anim");
|
||||
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||
|
||||
// 직접 휴머노이드 애니메이션 클립 생성
|
||||
var clip = CreateHumanoidAnimationClip();
|
||||
if (clip != null)
|
||||
// Poses 에셋에서 직접 휴머노이드 애니메이션 출력
|
||||
if (Poses != null)
|
||||
{
|
||||
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(animPath));
|
||||
AssetDatabase.CreateAsset(clip, animPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log($"휴머노이드 애니메이션 출력 완료: {animPath}");
|
||||
Poses.ExportHumanoidAnim();
|
||||
Debug.Log($"휴머노이드 애니메이션 출력 완료 (HumanoidPoses.ExportHumanoidAnim 사용)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Poses가 null이어서 휴머노이드 애니메이션을 출력할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
@ -683,18 +684,15 @@ namespace Entum
|
||||
{
|
||||
try
|
||||
{
|
||||
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Generic.anim");
|
||||
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||
|
||||
// 직접 제네릭 애니메이션 클립 생성
|
||||
var clip = CreateGenericAnimationClip();
|
||||
if (clip != null)
|
||||
// Poses 에셋에서 직접 제네릭 애니메이션 출력
|
||||
if (Poses != null)
|
||||
{
|
||||
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(animPath));
|
||||
AssetDatabase.CreateAsset(clip, animPath);
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log($"제네릭 애니메이션 출력 완료: {animPath}");
|
||||
Poses.ExportGenericAnim();
|
||||
Debug.Log($"제네릭 애니메이션 출력 완료 (HumanoidPoses.ExportGenericAnim 사용)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Poses가 null이어서 제네릭 애니메이션을 출력할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
@ -727,29 +725,29 @@ namespace Entum
|
||||
}
|
||||
}
|
||||
|
||||
private AnimationClip CreateHumanoidAnimationClip()
|
||||
// 더 이상 사용하지 않음 - HumanoidPoses.ExportHumanoidAnim() 직접 사용
|
||||
/*private AnimationClip CreateHumanoidAnimationClip()
|
||||
{
|
||||
if (Poses == null || Poses.Poses.Count == 0) return null;
|
||||
|
||||
var clip = new AnimationClip { frameRate = 30 };
|
||||
var clip = new AnimationClip();
|
||||
clip.frameRate = 30;
|
||||
|
||||
// Humanoid 애니메이션 설정
|
||||
// 중요: 휴머노이드 애니메이션으로 명시적 설정
|
||||
clip.legacy = false;
|
||||
|
||||
// HumanoidPoses와 동일한 애니메이션 클립 설정
|
||||
var settings = new AnimationClipSettings
|
||||
{
|
||||
loopTime = false,
|
||||
cycleOffset = 0,
|
||||
loopBlend = false,
|
||||
loopBlendOrientation = true,
|
||||
loopBlendPositionY = true,
|
||||
loopBlendPositionXZ = true,
|
||||
keepOriginalOrientation = true,
|
||||
keepOriginalPositionY = true,
|
||||
keepOriginalPositionXZ = true,
|
||||
heightFromFeet = false,
|
||||
mirror = false,
|
||||
hasAdditiveReferencePose = false,
|
||||
additiveReferencePoseTime = 0
|
||||
loopTime = false, // Loop Time: false
|
||||
cycleOffset = 0, // Cycle Offset: 0
|
||||
startTime = 0, // Start Time: 0
|
||||
stopTime = Poses.Poses.Count > 0 ? Poses.Poses.Last().Time : 1f, // Stop Time
|
||||
orientationOffsetY = 0, // Orientation Offset Y: 0
|
||||
level = 0, // Level: 0
|
||||
mirror = false // Mirror: false
|
||||
};
|
||||
|
||||
AnimationUtility.SetAnimationClipSettings(clip, settings);
|
||||
|
||||
// Muscles 데이터를 커브로 변환
|
||||
@ -776,15 +774,228 @@ namespace Entum
|
||||
string muscleName = HumanTrait.MuscleName[i];
|
||||
clip.SetCurve("", typeof(Animator), muscleName, muscleCurves[i]);
|
||||
}
|
||||
|
||||
// HumanoidPoses와 동일한 방식으로 Root Motion 설정
|
||||
// 1. Body Position (RootT.x/y/z)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyPosition.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyPosition.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyPosition.z);
|
||||
}
|
||||
|
||||
// 실제 녹화된 포즈들 추가
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.BodyPosition.x);
|
||||
curveY.AddKey(pose.Time, pose.BodyPosition.y);
|
||||
curveZ.AddKey(pose.Time, pose.BodyPosition.z);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "RootT.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "RootT.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "RootT.z", curveZ);
|
||||
}
|
||||
|
||||
// 2. Body Rotation (RootQ.x/y/z/w)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
var curveW = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyRotation.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyRotation.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyRotation.z);
|
||||
curveW.AddKey(Poses.TPoseData.Time, Poses.TPoseData.BodyRotation.w);
|
||||
}
|
||||
|
||||
// 실제 녹화된 포즈들 추가
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.BodyRotation.x);
|
||||
curveY.AddKey(pose.Time, pose.BodyRotation.y);
|
||||
curveZ.AddKey(pose.Time, pose.BodyRotation.z);
|
||||
curveW.AddKey(pose.Time, pose.BodyRotation.w);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "RootQ.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "RootQ.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "RootQ.z", curveZ);
|
||||
clip.SetCurve("", typeof(Animator), "RootQ.w", curveW);
|
||||
}
|
||||
|
||||
// 3. Left Foot IK Position (LeftFootT.x/y/z)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Pos.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Pos.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Pos.z);
|
||||
}
|
||||
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.LeftfootIK_Pos.x);
|
||||
curveY.AddKey(pose.Time, pose.LeftfootIK_Pos.y);
|
||||
curveZ.AddKey(pose.Time, pose.LeftfootIK_Pos.z);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootT.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootT.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootT.z", curveZ);
|
||||
}
|
||||
|
||||
// 4. Right Foot IK Position (RightFootT.x/y/z)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Pos.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Pos.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Pos.z);
|
||||
}
|
||||
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.RightfootIK_Pos.x);
|
||||
curveY.AddKey(pose.Time, pose.RightfootIK_Pos.y);
|
||||
curveZ.AddKey(pose.Time, pose.RightfootIK_Pos.z);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "RightFootT.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "RightFootT.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "RightFootT.z", curveZ);
|
||||
}
|
||||
|
||||
// 5. Left Foot IK Rotation (LeftFootQ.x/y/z/w)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
var curveW = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Rot.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Rot.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Rot.z);
|
||||
curveW.AddKey(Poses.TPoseData.Time, Poses.TPoseData.LeftfootIK_Rot.w);
|
||||
}
|
||||
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.LeftfootIK_Rot.x);
|
||||
curveY.AddKey(pose.Time, pose.LeftfootIK_Rot.y);
|
||||
curveZ.AddKey(pose.Time, pose.LeftfootIK_Rot.z);
|
||||
curveW.AddKey(pose.Time, pose.LeftfootIK_Rot.w);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootQ.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootQ.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootQ.z", curveZ);
|
||||
clip.SetCurve("", typeof(Animator), "LeftFootQ.w", curveW);
|
||||
}
|
||||
|
||||
// 6. Right Foot IK Rotation (RightFootQ.x/y/z/w)
|
||||
{
|
||||
var curveX = new AnimationCurve();
|
||||
var curveY = new AnimationCurve();
|
||||
var curveZ = new AnimationCurve();
|
||||
var curveW = new AnimationCurve();
|
||||
|
||||
// T-포즈가 있으면 0프레임에 먼저 추가
|
||||
if (Poses.HasTPoseData && Poses.TPoseData != null)
|
||||
{
|
||||
curveX.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Rot.x);
|
||||
curveY.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Rot.y);
|
||||
curveZ.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Rot.z);
|
||||
curveW.AddKey(Poses.TPoseData.Time, Poses.TPoseData.RightfootIK_Rot.w);
|
||||
}
|
||||
|
||||
foreach (var pose in Poses.Poses)
|
||||
{
|
||||
curveX.AddKey(pose.Time, pose.RightfootIK_Rot.x);
|
||||
curveY.AddKey(pose.Time, pose.RightfootIK_Rot.y);
|
||||
curveZ.AddKey(pose.Time, pose.RightfootIK_Rot.z);
|
||||
curveW.AddKey(pose.Time, pose.RightfootIK_Rot.w);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "RightFootQ.x", curveX);
|
||||
clip.SetCurve("", typeof(Animator), "RightFootQ.y", curveY);
|
||||
clip.SetCurve("", typeof(Animator), "RightFootQ.z", curveZ);
|
||||
clip.SetCurve("", typeof(Animator), "RightFootQ.w", curveW);
|
||||
}
|
||||
|
||||
// HumanoidPoses와 동일하게 쿼터니언 연속성 보장
|
||||
clip.EnsureQuaternionContinuity();
|
||||
|
||||
// 2. 애니메이션 클립 타입 명시적 설정 (여러번 시도)
|
||||
try
|
||||
{
|
||||
AnimationUtility.SetAnimationType(clip, ModelImporterAnimationType.Human);
|
||||
Debug.Log("휴머노이드 애니메이션 타입 설정 성공");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"휴머노이드 애니메이션 타입 설정 실패: {e.Message}");
|
||||
}
|
||||
|
||||
// 3. 애니메이션 커브 최적화
|
||||
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
|
||||
{
|
||||
var curve = AnimationUtility.GetEditorCurve(clip, binding);
|
||||
if (curve != null && curve.keys.Length > 1)
|
||||
{
|
||||
// 키프레임 최적화
|
||||
for (int i = 0; i < curve.keys.Length; i++)
|
||||
{
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
|
||||
}
|
||||
AnimationUtility.SetEditorCurve(clip, binding, curve);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 휴머노이드 확인을 위한 이벤트 추가
|
||||
AnimationEvent humanoidEvent = new AnimationEvent();
|
||||
humanoidEvent.time = 0f;
|
||||
humanoidEvent.functionName = "HumanoidMotionStart";
|
||||
humanoidEvent.stringParameter = "Humanoid";
|
||||
clip.events = new AnimationEvent[] { humanoidEvent };
|
||||
|
||||
return clip;
|
||||
}
|
||||
}*/
|
||||
|
||||
private AnimationClip CreateGenericAnimationClip()
|
||||
// 더 이상 사용하지 않음 - HumanoidPoses.ExportGenericAnim() 직접 사용
|
||||
/*private AnimationClip CreateGenericAnimationClip()
|
||||
{
|
||||
if (Poses == null || Poses.Poses.Count == 0) return null;
|
||||
|
||||
var clip = new AnimationClip { frameRate = 30 };
|
||||
var clip = new AnimationClip();
|
||||
clip.frameRate = 30;
|
||||
|
||||
// 중요: 제네릭 애니메이션으로 명시적 설정
|
||||
clip.legacy = false;
|
||||
|
||||
// 본별 커브 생성
|
||||
var boneCurves = new Dictionary<string, Dictionary<string, AnimationCurve>>();
|
||||
@ -833,6 +1044,36 @@ namespace Entum
|
||||
}
|
||||
}
|
||||
|
||||
// 애니메이션 클립 타입 명시적 설정 (제네릭)
|
||||
try
|
||||
{
|
||||
AnimationUtility.SetAnimationType(clip, ModelImporterAnimationType.Generic);
|
||||
Debug.Log("제네릭 애니메이션 타입 설정 성공");
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Debug.LogWarning($"제네릭 애니메이션 타입 설정 실패: {e.Message}");
|
||||
}
|
||||
|
||||
// 애니메이션 커브 최적화 (제네릭)
|
||||
foreach (var bonePair in boneCurves)
|
||||
{
|
||||
var curves = bonePair.Value;
|
||||
foreach (var curvePair in curves)
|
||||
{
|
||||
var curve = curvePair.Value;
|
||||
if (curve != null && curve.keys.Length > 1)
|
||||
{
|
||||
// 키프레임 최적화
|
||||
for (int i = 0; i < curve.keys.Length; i++)
|
||||
{
|
||||
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
|
||||
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.ClampedAuto);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 손가락 본들이 포함되었는지 로깅
|
||||
var fingerBoneCount = boneCurves.Keys.Count(name =>
|
||||
name.Contains("Thumb") || name.Contains("Index") || name.Contains("Middle") ||
|
||||
@ -840,7 +1081,7 @@ namespace Entum
|
||||
Debug.Log($"제네릭 애니메이션 클립 생성 완료 - 총 {boneCurves.Count}개 본, 손가락 본 {fingerBoneCount}개 포함");
|
||||
|
||||
return clip;
|
||||
}
|
||||
}*/
|
||||
#endif
|
||||
|
||||
private void UpdateSummaryInfo()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user