Fix : 인스턴스 출력 기능 패치
This commit is contained in:
parent
98d207583a
commit
0abf6970d4
@ -177,7 +177,12 @@ namespace Entum {
|
|||||||
clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape." + curve.Key, curve.Value);
|
clip.SetCurve("", typeof(SkinnedMeshRenderer), "blendShape." + curve.Key, curve.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
string fileName = $"{facial.SessionID}_Facial";
|
// 캐릭터 이름 가져오기
|
||||||
|
string characterName = GetCharacterName();
|
||||||
|
string fileName = string.IsNullOrEmpty(characterName)
|
||||||
|
? $"{facial.SessionID}_Facial"
|
||||||
|
: $"{facial.SessionID}_{characterName}_Facial";
|
||||||
|
|
||||||
string filePath = Path.Combine(_savePathManager.GetFacialSavePath(), fileName + ".anim");
|
string filePath = Path.Combine(_savePathManager.GetFacialSavePath(), fileName + ".anim");
|
||||||
|
|
||||||
// 인스턴스별 고유 경로 생성
|
// 인스턴스별 고유 경로 생성
|
||||||
@ -193,6 +198,75 @@ namespace Entum {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCharacterName()
|
||||||
|
{
|
||||||
|
if (_animRecorder?.CharacterAnimator == null) return "";
|
||||||
|
|
||||||
|
var animator = _animRecorder.CharacterAnimator;
|
||||||
|
|
||||||
|
// 1. GameObject 이름 사용
|
||||||
|
string objectName = animator.gameObject.name;
|
||||||
|
|
||||||
|
// 2. Avatar 이름이 있으면 우선 사용
|
||||||
|
if (animator.avatar != null && !string.IsNullOrEmpty(animator.avatar.name))
|
||||||
|
{
|
||||||
|
string avatarName = animator.avatar.name;
|
||||||
|
// "Avatar" 접미사 제거
|
||||||
|
if (avatarName.EndsWith("Avatar"))
|
||||||
|
{
|
||||||
|
avatarName = avatarName.Substring(0, avatarName.Length - 6);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(avatarName))
|
||||||
|
{
|
||||||
|
return SanitizeFileName(avatarName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 부모 오브젝트에서 캐릭터 루트 찾기
|
||||||
|
Transform current = animator.transform.parent;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
// VRM, humanoid, character 등의 키워드가 있는 경우
|
||||||
|
string parentName = current.name.ToLower();
|
||||||
|
if (parentName.Contains("character") || parentName.Contains("humanoid") ||
|
||||||
|
parentName.Contains("avatar") || parentName.Contains("vrm"))
|
||||||
|
{
|
||||||
|
return SanitizeFileName(current.name);
|
||||||
|
}
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. GameObject 이름에서 불필요한 부분 제거
|
||||||
|
objectName = objectName.Replace("(Clone)", "").Trim();
|
||||||
|
return SanitizeFileName(objectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SanitizeFileName(string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName)) return "";
|
||||||
|
|
||||||
|
// 파일명에 사용할 수 없는 문자 제거
|
||||||
|
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
foreach (char c in invalidChars)
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace(c, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공백을 언더스코어로 변경
|
||||||
|
fileName = fileName.Replace(' ', '_');
|
||||||
|
|
||||||
|
// 연속된 언더스코어 제거
|
||||||
|
while (fileName.Contains("__"))
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace("__", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 앞뒤 언더스코어 제거
|
||||||
|
fileName = fileName.Trim('_');
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
private bool IsSame(CharacterFacialData.SerializeHumanoidFace a, CharacterFacialData.SerializeHumanoidFace b) {
|
private bool IsSame(CharacterFacialData.SerializeHumanoidFace a, CharacterFacialData.SerializeHumanoidFace b) {
|
||||||
if(a.BlendShapeNames.Count != b.BlendShapeNames.Count) {
|
if(a.BlendShapeNames.Count != b.BlendShapeNames.Count) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -112,18 +112,14 @@ namespace Entum
|
|||||||
// 2. 스크립터블 오브젝트의 이름에서 세션 ID 추출 시도
|
// 2. 스크립터블 오브젝트의 이름에서 세션 ID 추출 시도
|
||||||
if (!string.IsNullOrEmpty(this.name))
|
if (!string.IsNullOrEmpty(this.name))
|
||||||
{
|
{
|
||||||
// 파일명에서 세션 ID 패턴 찾기 (예: 250717_192404_abc12345_Motion)
|
// 파일명에서 세션 ID 패턴 찾기 (예: 250717_192404_Motion_abc12345)
|
||||||
var nameParts = this.name.Split('_');
|
var nameParts = this.name.Split('_');
|
||||||
if (nameParts.Length >= 3)
|
if (nameParts.Length >= 2)
|
||||||
{
|
{
|
||||||
// 첫 번째 두 부분이 날짜와 시간인지 확인
|
// 첫 번째 두 부분이 날짜와 시간인지 확인
|
||||||
if (nameParts[0].Length == 6 && nameParts[1].Length == 6) // yyMMdd_HHmmss 형식
|
if (nameParts[0].Length == 6 && nameParts[1].Length == 6) // yyMMdd_HHmmss 형식
|
||||||
{
|
{
|
||||||
string sessionID = $"{nameParts[0]}_{nameParts[1]}";
|
string sessionID = $"{nameParts[0]}_{nameParts[1]}";
|
||||||
if (nameParts.Length > 2)
|
|
||||||
{
|
|
||||||
sessionID += "_" + nameParts[2]; // 인스턴스 ID 포함
|
|
||||||
}
|
|
||||||
Debug.Log($"스크립터블 오브젝트 이름에서 세션 ID 추출: {sessionID}");
|
Debug.Log($"스크립터블 오브젝트 이름에서 세션 ID 추출: {sessionID}");
|
||||||
return sessionID;
|
return sessionID;
|
||||||
}
|
}
|
||||||
@ -136,15 +132,11 @@ namespace Entum
|
|||||||
{
|
{
|
||||||
string fileName = Path.GetFileNameWithoutExtension(assetPath);
|
string fileName = Path.GetFileNameWithoutExtension(assetPath);
|
||||||
var nameParts = fileName.Split('_');
|
var nameParts = fileName.Split('_');
|
||||||
if (nameParts.Length >= 3)
|
if (nameParts.Length >= 2)
|
||||||
{
|
{
|
||||||
if (nameParts[0].Length == 6 && nameParts[1].Length == 6) // yyMMdd_HHmmss 형식
|
if (nameParts[0].Length == 6 && nameParts[1].Length == 6) // yyMMdd_HHmmss 형식
|
||||||
{
|
{
|
||||||
string sessionID = $"{nameParts[0]}_{nameParts[1]}";
|
string sessionID = $"{nameParts[0]}_{nameParts[1]}";
|
||||||
if (nameParts.Length > 2)
|
|
||||||
{
|
|
||||||
sessionID += "_" + nameParts[2]; // 인스턴스 ID 포함
|
|
||||||
}
|
|
||||||
Debug.Log($"에셋 파일명에서 세션 ID 추출: {sessionID}");
|
Debug.Log($"에셋 파일명에서 세션 ID 추출: {sessionID}");
|
||||||
return sessionID;
|
return sessionID;
|
||||||
}
|
}
|
||||||
@ -309,9 +301,32 @@ namespace Entum
|
|||||||
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
||||||
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
||||||
|
|
||||||
|
// 인스턴스 ID 디버깅 로그
|
||||||
|
Debug.Log($"출력 시 인스턴스 ID 상태: '{InstanceID}' (비어있음: {string.IsNullOrEmpty(InstanceID)})");
|
||||||
|
|
||||||
|
// 인스턴스 ID가 비어있으면 현재 씬의 SavePathManager에서 가져오기
|
||||||
|
string currentInstanceID = InstanceID;
|
||||||
|
if (string.IsNullOrEmpty(currentInstanceID))
|
||||||
|
{
|
||||||
|
var savePathManager = FindObjectOfType<EasyMotionRecorder.SavePathManager>();
|
||||||
|
if (savePathManager != null)
|
||||||
|
{
|
||||||
|
currentInstanceID = savePathManager.InstanceID;
|
||||||
|
Debug.Log($"SavePathManager에서 인스턴스 ID 가져옴: '{currentInstanceID}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning("SavePathManager를 찾을 수 없습니다. 인스턴스 ID 없이 파일 생성됩니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인스턴스 ID 포함 파일명 생성
|
||||||
|
string fileName = !string.IsNullOrEmpty(currentInstanceID)
|
||||||
|
? $"{sessionID}_{avatarName}_Generic_{currentInstanceID}.anim"
|
||||||
|
: $"{sessionID}_{avatarName}_Generic.anim";
|
||||||
|
|
||||||
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
||||||
string savePath = "Assets/Resources"; // 기본값
|
string savePath = "Assets/Resources"; // 기본값
|
||||||
string fileName = $"{sessionID}_{avatarName}_Generic.anim";
|
|
||||||
|
|
||||||
// 현재 에셋 파일의 경로 가져오기
|
// 현재 에셋 파일의 경로 가져오기
|
||||||
string assetPath = AssetDatabase.GetAssetPath(this);
|
string assetPath = AssetDatabase.GetAssetPath(this);
|
||||||
@ -657,9 +672,32 @@ namespace Entum
|
|||||||
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
// 아바타 이름이 있으면 포함, 없으면 기본값 사용
|
||||||
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
||||||
|
|
||||||
|
// 인스턴스 ID 디버깅 로그
|
||||||
|
Debug.Log($"Humanoid 출력 시 인스턴스 ID 상태: '{InstanceID}' (비어있음: {string.IsNullOrEmpty(InstanceID)})");
|
||||||
|
|
||||||
|
// 인스턴스 ID가 비어있으면 현재 씬의 SavePathManager에서 가져오기
|
||||||
|
string currentInstanceID = InstanceID;
|
||||||
|
if (string.IsNullOrEmpty(currentInstanceID))
|
||||||
|
{
|
||||||
|
var savePathManager = FindObjectOfType<EasyMotionRecorder.SavePathManager>();
|
||||||
|
if (savePathManager != null)
|
||||||
|
{
|
||||||
|
currentInstanceID = savePathManager.InstanceID;
|
||||||
|
Debug.Log($"SavePathManager에서 인스턴스 ID 가져옴: '{currentInstanceID}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning("SavePathManager를 찾을 수 없습니다. 인스턴스 ID 없이 파일 생성됩니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 인스턴스 ID 포함 파일명 생성
|
||||||
|
string fileName = !string.IsNullOrEmpty(currentInstanceID)
|
||||||
|
? $"{sessionID}_{avatarName}_Humanoid_{currentInstanceID}.anim"
|
||||||
|
: $"{sessionID}_{avatarName}_Humanoid.anim";
|
||||||
|
|
||||||
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
// 에셋 파일의 경로를 기반으로 저장 경로 결정
|
||||||
string savePath = "Assets/Resources"; // 기본값
|
string savePath = "Assets/Resources"; // 기본값
|
||||||
string fileName = $"{sessionID}_{avatarName}_Humanoid.anim";
|
|
||||||
|
|
||||||
// 현재 에셋 파일의 경로 가져오기
|
// 현재 에셋 파일의 경로 가져오기
|
||||||
string assetPath = AssetDatabase.GetAssetPath(this);
|
string assetPath = AssetDatabase.GetAssetPath(this);
|
||||||
@ -721,9 +759,29 @@ namespace Entum
|
|||||||
string sessionID = GetSessionID();
|
string sessionID = GetSessionID();
|
||||||
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
string avatarName = !string.IsNullOrEmpty(AvatarName) ? AvatarName : "Unknown";
|
||||||
|
|
||||||
|
// 인스턴스 ID가 비어있으면 현재 씬의 SavePathManager에서 가져오기
|
||||||
|
string currentInstanceID = InstanceID;
|
||||||
|
if (string.IsNullOrEmpty(currentInstanceID))
|
||||||
|
{
|
||||||
|
var savePathManager = FindObjectOfType<EasyMotionRecorder.SavePathManager>();
|
||||||
|
if (savePathManager != null)
|
||||||
|
{
|
||||||
|
currentInstanceID = savePathManager.InstanceID;
|
||||||
|
Debug.Log($"FBX 출력 시 SavePathManager에서 인스턴스 ID 가져옴: '{currentInstanceID}'");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Debug.LogWarning("SavePathManager를 찾을 수 없습니다. 인스턴스 ID 없이 FBX 파일 생성됩니다.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 저장 경로 결정
|
// 저장 경로 결정
|
||||||
string savePath = "Assets/Resources";
|
string savePath = "Assets/Resources";
|
||||||
string fileName = $"{sessionID}_{avatarName}_Motion.fbx";
|
|
||||||
|
// 인스턴스 ID 포함 파일명 생성
|
||||||
|
string fileName = !string.IsNullOrEmpty(currentInstanceID)
|
||||||
|
? $"{sessionID}_{avatarName}_Motion_{(useAscii ? "ASCII" : "Binary")}_{currentInstanceID}.fbx"
|
||||||
|
: $"{sessionID}_{avatarName}_Motion_{(useAscii ? "ASCII" : "Binary")}.fbx";
|
||||||
|
|
||||||
// 현재 에셋 파일의 경로 가져오기
|
// 현재 에셋 파일의 경로 가져오기
|
||||||
string assetPath = AssetDatabase.GetAssetPath(this);
|
string assetPath = AssetDatabase.GetAssetPath(this);
|
||||||
|
|||||||
@ -214,13 +214,14 @@ namespace Entum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 세션 ID 생성 (인스턴스별 고유)
|
// 세션 ID 생성 (인스턴스 ID 제외)
|
||||||
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss") + "_" + instanceID;
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
|
|
||||||
Poses = ScriptableObject.CreateInstance<HumanoidPoses>();
|
Poses = ScriptableObject.CreateInstance<HumanoidPoses>();
|
||||||
Poses.AvatarName = _animator.name;
|
Poses.AvatarName = _animator.name;
|
||||||
Poses.Poses = new List<HumanoidPoses.SerializeHumanoidPose>();
|
Poses.Poses = new List<HumanoidPoses.SerializeHumanoidPose>();
|
||||||
Poses.SessionID = SessionID;
|
Poses.SessionID = SessionID;
|
||||||
|
Poses.InstanceID = instanceID; // 인스턴스 ID 설정
|
||||||
|
|
||||||
RecordedTime = 0f;
|
RecordedTime = 0f;
|
||||||
StartTime = Time.time;
|
StartTime = Time.time;
|
||||||
@ -229,11 +230,6 @@ namespace Entum
|
|||||||
|
|
||||||
Debug.Log($"모션 녹화 시작 - 인스턴스: {instanceID}, 세션: {SessionID}");
|
Debug.Log($"모션 녹화 시작 - 인스턴스: {instanceID}, 세션: {SessionID}");
|
||||||
|
|
||||||
if (_recordTPoseAtStart)
|
|
||||||
{
|
|
||||||
RecordTPoseAsFirstFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
OnRecordStart?.Invoke();
|
OnRecordStart?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +363,12 @@ namespace Entum
|
|||||||
|
|
||||||
UpdateSummaryInfo();
|
UpdateSummaryInfo();
|
||||||
|
|
||||||
string fileName = $"{SessionID}_Motion";
|
// 캐릭터 이름 가져오기
|
||||||
|
string characterName = GetCharacterName();
|
||||||
|
string fileName = string.IsNullOrEmpty(characterName)
|
||||||
|
? $"{SessionID}_Motion"
|
||||||
|
: $"{SessionID}_{characterName}_Motion";
|
||||||
|
|
||||||
string filePath = Path.Combine(_savePathManager.GetMotionSavePath(), fileName + ".asset");
|
string filePath = Path.Combine(_savePathManager.GetMotionSavePath(), fileName + ".asset");
|
||||||
|
|
||||||
// 인스턴스별 고유 경로 생성
|
// 인스턴스별 고유 경로 생성
|
||||||
@ -407,6 +408,73 @@ namespace Entum
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetCharacterName()
|
||||||
|
{
|
||||||
|
if (_animator == null) return "";
|
||||||
|
|
||||||
|
// 1. GameObject 이름 사용
|
||||||
|
string objectName = _animator.gameObject.name;
|
||||||
|
|
||||||
|
// 2. Avatar 이름이 있으면 우선 사용
|
||||||
|
if (_animator.avatar != null && !string.IsNullOrEmpty(_animator.avatar.name))
|
||||||
|
{
|
||||||
|
string avatarName = _animator.avatar.name;
|
||||||
|
// "Avatar" 접미사 제거
|
||||||
|
if (avatarName.EndsWith("Avatar"))
|
||||||
|
{
|
||||||
|
avatarName = avatarName.Substring(0, avatarName.Length - 6);
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(avatarName))
|
||||||
|
{
|
||||||
|
return SanitizeFileName(avatarName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 부모 오브젝트에서 캐릭터 루트 찾기
|
||||||
|
Transform current = _animator.transform.parent;
|
||||||
|
while (current != null)
|
||||||
|
{
|
||||||
|
// VRM, humanoid, character 등의 키워드가 있는 경우
|
||||||
|
string parentName = current.name.ToLower();
|
||||||
|
if (parentName.Contains("character") || parentName.Contains("humanoid") ||
|
||||||
|
parentName.Contains("avatar") || parentName.Contains("vrm"))
|
||||||
|
{
|
||||||
|
return SanitizeFileName(current.name);
|
||||||
|
}
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. GameObject 이름에서 불필요한 부분 제거
|
||||||
|
objectName = objectName.Replace("(Clone)", "").Trim();
|
||||||
|
return SanitizeFileName(objectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string SanitizeFileName(string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName)) return "";
|
||||||
|
|
||||||
|
// 파일명에 사용할 수 없는 문자 제거
|
||||||
|
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
foreach (char c in invalidChars)
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace(c, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공백을 언더스코어로 변경
|
||||||
|
fileName = fileName.Replace(' ', '_');
|
||||||
|
|
||||||
|
// 연속된 언더스코어 제거
|
||||||
|
while (fileName.Contains("__"))
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace("__", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 앞뒤 언더스코어 제거
|
||||||
|
fileName = fileName.Trim('_');
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void ExportHumanoidAnimation(string baseFileName)
|
private void ExportHumanoidAnimation(string baseFileName)
|
||||||
{
|
{
|
||||||
@ -415,9 +483,16 @@ namespace Entum
|
|||||||
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Humanoid.anim");
|
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Humanoid.anim");
|
||||||
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||||
|
|
||||||
// HumanoidPoses의 기존 내보내기 메서드 사용
|
// 직접 휴머노이드 애니메이션 클립 생성
|
||||||
Poses.ExportHumanoidAnim();
|
var clip = CreateHumanoidAnimationClip();
|
||||||
Debug.Log($"휴머노이드 애니메이션 출력 완료: {baseFileName}_Humanoid");
|
if (clip != null)
|
||||||
|
{
|
||||||
|
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(animPath));
|
||||||
|
AssetDatabase.CreateAsset(clip, animPath);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Debug.Log($"휴머노이드 애니메이션 출력 완료: {animPath}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception e)
|
||||||
{
|
{
|
||||||
@ -432,9 +507,16 @@ namespace Entum
|
|||||||
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Generic.anim");
|
string animPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_Generic.anim");
|
||||||
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
animPath = _savePathManager.GetInstanceSpecificPath(animPath);
|
||||||
|
|
||||||
// HumanoidPoses의 기존 내보내기 메서드 사용
|
// 직접 제네릭 애니메이션 클립 생성
|
||||||
Poses.ExportGenericAnim();
|
var clip = CreateGenericAnimationClip();
|
||||||
Debug.Log($"제네릭 애니메이션 출력 완료: {baseFileName}_Generic");
|
if (clip != null)
|
||||||
|
{
|
||||||
|
SavePathManager.SafeCreateDirectory(Path.GetDirectoryName(animPath));
|
||||||
|
AssetDatabase.CreateAsset(clip, animPath);
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
AssetDatabase.Refresh();
|
||||||
|
Debug.Log($"제네릭 애니메이션 출력 완료: {animPath}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (System.Exception e)
|
catch (System.Exception e)
|
||||||
{
|
{
|
||||||
@ -446,7 +528,10 @@ namespace Entum
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// HumanoidPoses의 기존 FBX 내보내기 메서드 사용
|
string fbxPath = Path.Combine(_savePathManager.GetMotionSavePath(), $"{baseFileName}_{(ascii ? "ASCII" : "Binary")}.fbx");
|
||||||
|
fbxPath = _savePathManager.GetInstanceSpecificPath(fbxPath);
|
||||||
|
|
||||||
|
// FBX 출력은 HumanoidPoses의 기존 메서드 사용 (경로 지정 불가)
|
||||||
if (ascii)
|
if (ascii)
|
||||||
{
|
{
|
||||||
Poses.ExportFBXAscii();
|
Poses.ExportFBXAscii();
|
||||||
@ -462,6 +547,115 @@ namespace Entum
|
|||||||
Debug.LogError($"FBX 애니메이션 출력 실패: {e.Message}");
|
Debug.LogError($"FBX 애니메이션 출력 실패: {e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AnimationClip CreateHumanoidAnimationClip()
|
||||||
|
{
|
||||||
|
if (Poses == null || Poses.Poses.Count == 0) return null;
|
||||||
|
|
||||||
|
var clip = new AnimationClip { frameRate = 30 };
|
||||||
|
|
||||||
|
// Humanoid 애니메이션 설정
|
||||||
|
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
|
||||||
|
};
|
||||||
|
AnimationUtility.SetAnimationClipSettings(clip, settings);
|
||||||
|
|
||||||
|
// Muscles 데이터를 커브로 변환
|
||||||
|
var muscleCurves = new AnimationCurve[HumanTrait.MuscleCount];
|
||||||
|
for (int i = 0; i < HumanTrait.MuscleCount; i++)
|
||||||
|
{
|
||||||
|
muscleCurves[i] = new AnimationCurve();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pose in Poses.Poses)
|
||||||
|
{
|
||||||
|
if (pose.Muscles != null && pose.Muscles.Length == HumanTrait.MuscleCount)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < HumanTrait.MuscleCount; i++)
|
||||||
|
{
|
||||||
|
muscleCurves[i].AddKey(pose.Time, pose.Muscles[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커브를 애니메이션 클립에 적용
|
||||||
|
for (int i = 0; i < HumanTrait.MuscleCount; i++)
|
||||||
|
{
|
||||||
|
string muscleName = HumanTrait.MuscleName[i];
|
||||||
|
clip.SetCurve("", typeof(Animator), muscleName, muscleCurves[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AnimationClip CreateGenericAnimationClip()
|
||||||
|
{
|
||||||
|
if (Poses == null || Poses.Poses.Count == 0) return null;
|
||||||
|
|
||||||
|
var clip = new AnimationClip { frameRate = 30 };
|
||||||
|
|
||||||
|
// 본별 커브 생성
|
||||||
|
var boneCurves = new Dictionary<string, Dictionary<string, AnimationCurve>>();
|
||||||
|
|
||||||
|
foreach (var pose in Poses.Poses)
|
||||||
|
{
|
||||||
|
if (pose.HumanoidBones != null)
|
||||||
|
{
|
||||||
|
foreach (var bone in pose.HumanoidBones)
|
||||||
|
{
|
||||||
|
if (!boneCurves.ContainsKey(bone.Name))
|
||||||
|
{
|
||||||
|
boneCurves[bone.Name] = new Dictionary<string, AnimationCurve>
|
||||||
|
{
|
||||||
|
["localPosition.x"] = new AnimationCurve(),
|
||||||
|
["localPosition.y"] = new AnimationCurve(),
|
||||||
|
["localPosition.z"] = new AnimationCurve(),
|
||||||
|
["localRotation.x"] = new AnimationCurve(),
|
||||||
|
["localRotation.y"] = new AnimationCurve(),
|
||||||
|
["localRotation.z"] = new AnimationCurve(),
|
||||||
|
["localRotation.w"] = new AnimationCurve()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var curves = boneCurves[bone.Name];
|
||||||
|
curves["localPosition.x"].AddKey(pose.Time, bone.LocalPosition.x);
|
||||||
|
curves["localPosition.y"].AddKey(pose.Time, bone.LocalPosition.y);
|
||||||
|
curves["localPosition.z"].AddKey(pose.Time, bone.LocalPosition.z);
|
||||||
|
curves["localRotation.x"].AddKey(pose.Time, bone.LocalRotation.x);
|
||||||
|
curves["localRotation.y"].AddKey(pose.Time, bone.LocalRotation.y);
|
||||||
|
curves["localRotation.z"].AddKey(pose.Time, bone.LocalRotation.z);
|
||||||
|
curves["localRotation.w"].AddKey(pose.Time, bone.LocalRotation.w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 커브를 애니메이션 클립에 적용
|
||||||
|
foreach (var bonePair in boneCurves)
|
||||||
|
{
|
||||||
|
string boneName = bonePair.Key;
|
||||||
|
var curves = bonePair.Value;
|
||||||
|
|
||||||
|
foreach (var curvePair in curves)
|
||||||
|
{
|
||||||
|
clip.SetCurve(boneName, typeof(Transform), curvePair.Key, curvePair.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clip;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
private void UpdateSummaryInfo()
|
private void UpdateSummaryInfo()
|
||||||
|
|||||||
@ -144,8 +144,8 @@ namespace Entum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 세션 ID 생성 (인스턴스별 고유)
|
// 세션 ID 생성 (인스턴스 ID 제외)
|
||||||
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss") + "_" + instanceID;
|
SessionID = DateTime.Now.ToString("yyMMdd_HHmmss");
|
||||||
|
|
||||||
// 데이터 초기화
|
// 데이터 초기화
|
||||||
objectClips = new Dictionary<Transform, AnimationClip>();
|
objectClips = new Dictionary<Transform, AnimationClip>();
|
||||||
@ -227,8 +227,8 @@ namespace Entum
|
|||||||
// Quaternion 연속성 보장
|
// Quaternion 연속성 보장
|
||||||
clip.EnsureQuaternionContinuity();
|
clip.EnsureQuaternionContinuity();
|
||||||
|
|
||||||
// 파일명 생성
|
// 파일명 생성 (오브젝트 이름 정리)
|
||||||
string objectName = target.name;
|
string objectName = SanitizeFileName(target.name);
|
||||||
string fileName = $"{SessionID}_{objectName}_Object.anim";
|
string fileName = $"{SessionID}_{objectName}_Object.anim";
|
||||||
|
|
||||||
// SavePathManager 사용
|
// SavePathManager 사용
|
||||||
@ -262,6 +262,35 @@ namespace Entum
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string SanitizeFileName(string fileName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(fileName)) return "";
|
||||||
|
|
||||||
|
// 불필요한 부분 제거
|
||||||
|
fileName = fileName.Replace("(Clone)", "").Trim();
|
||||||
|
|
||||||
|
// 파일명에 사용할 수 없는 문자 제거
|
||||||
|
char[] invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
foreach (char c in invalidChars)
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace(c, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 공백을 언더스코어로 변경
|
||||||
|
fileName = fileName.Replace(' ', '_');
|
||||||
|
|
||||||
|
// 연속된 언더스코어 제거
|
||||||
|
while (fileName.Contains("__"))
|
||||||
|
{
|
||||||
|
fileName = fileName.Replace("__", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 앞뒤 언더스코어 제거
|
||||||
|
fileName = fileName.Trim('_');
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void ExportObjectAnimationAsHumanoid(Transform target, string baseFileName)
|
private void ExportObjectAnimationAsHumanoid(Transform target, string baseFileName)
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user