373 lines
14 KiB
C#
373 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System;
|
|
|
|
namespace Streamingle.Editor
|
|
{
|
|
public class PoseRecorderWindow : EditorWindow
|
|
{
|
|
private GameObject selectedAvatar;
|
|
private Animator avatarAnimator;
|
|
private string poseName = "NewPose";
|
|
private string savePath = "Assets/Recordings/";
|
|
|
|
// 휴먼본 본들 (Animator HumanBodyBones 사용)
|
|
private readonly HumanBodyBones[] humanBodyBones = {
|
|
HumanBodyBones.Hips,
|
|
HumanBodyBones.Spine,
|
|
HumanBodyBones.Chest,
|
|
HumanBodyBones.UpperChest,
|
|
HumanBodyBones.Neck,
|
|
HumanBodyBones.Head,
|
|
HumanBodyBones.LeftShoulder,
|
|
HumanBodyBones.LeftUpperArm,
|
|
HumanBodyBones.LeftLowerArm,
|
|
HumanBodyBones.LeftHand,
|
|
HumanBodyBones.RightShoulder,
|
|
HumanBodyBones.RightUpperArm,
|
|
HumanBodyBones.RightLowerArm,
|
|
HumanBodyBones.RightHand,
|
|
HumanBodyBones.LeftUpperLeg,
|
|
HumanBodyBones.LeftLowerLeg,
|
|
HumanBodyBones.LeftFoot,
|
|
HumanBodyBones.LeftToes,
|
|
HumanBodyBones.RightUpperLeg,
|
|
HumanBodyBones.RightLowerLeg,
|
|
HumanBodyBones.RightFoot,
|
|
HumanBodyBones.RightToes
|
|
};
|
|
|
|
// 손가락 본들
|
|
private readonly HumanBodyBones[] fingerBones = {
|
|
// 왼손 손가락
|
|
HumanBodyBones.LeftThumbProximal,
|
|
HumanBodyBones.LeftThumbIntermediate,
|
|
HumanBodyBones.LeftThumbDistal,
|
|
HumanBodyBones.LeftIndexProximal,
|
|
HumanBodyBones.LeftIndexIntermediate,
|
|
HumanBodyBones.LeftIndexDistal,
|
|
HumanBodyBones.LeftMiddleProximal,
|
|
HumanBodyBones.LeftMiddleIntermediate,
|
|
HumanBodyBones.LeftMiddleDistal,
|
|
HumanBodyBones.LeftRingProximal,
|
|
HumanBodyBones.LeftRingIntermediate,
|
|
HumanBodyBones.LeftRingDistal,
|
|
HumanBodyBones.LeftLittleProximal,
|
|
HumanBodyBones.LeftLittleIntermediate,
|
|
HumanBodyBones.LeftLittleDistal,
|
|
// 오른손 손가락
|
|
HumanBodyBones.RightThumbProximal,
|
|
HumanBodyBones.RightThumbIntermediate,
|
|
HumanBodyBones.RightThumbDistal,
|
|
HumanBodyBones.RightIndexProximal,
|
|
HumanBodyBones.RightIndexIntermediate,
|
|
HumanBodyBones.RightIndexDistal,
|
|
HumanBodyBones.RightMiddleProximal,
|
|
HumanBodyBones.RightMiddleIntermediate,
|
|
HumanBodyBones.RightMiddleDistal,
|
|
HumanBodyBones.RightRingProximal,
|
|
HumanBodyBones.RightRingIntermediate,
|
|
HumanBodyBones.RightRingDistal,
|
|
HumanBodyBones.RightLittleProximal,
|
|
HumanBodyBones.RightLittleIntermediate,
|
|
HumanBodyBones.RightLittleDistal
|
|
};
|
|
|
|
[MenuItem("Tools/Pose Recorder")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<PoseRecorderWindow>("포즈 기록기");
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
// 선택된 GameObject가 아바타인지 확인
|
|
if (Selection.activeGameObject != null)
|
|
{
|
|
selectedAvatar = Selection.activeGameObject;
|
|
avatarAnimator = selectedAvatar.GetComponent<Animator>();
|
|
if (avatarAnimator == null)
|
|
{
|
|
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
|
|
}
|
|
}
|
|
}
|
|
|
|
private Vector2 scrollPosition;
|
|
|
|
private void OnGUI()
|
|
{
|
|
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
|
|
|
GUILayout.Label("아바타 포즈 기록기", EditorStyles.boldLabel);
|
|
EditorGUILayout.Space();
|
|
|
|
// 아바타 선택
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Label("아바타:", GUILayout.Width(60));
|
|
selectedAvatar = (GameObject)EditorGUILayout.ObjectField(selectedAvatar, typeof(GameObject), true);
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (selectedAvatar != null)
|
|
{
|
|
avatarAnimator = selectedAvatar.GetComponent<Animator>();
|
|
if (avatarAnimator == null)
|
|
{
|
|
avatarAnimator = selectedAvatar.GetComponentInChildren<Animator>();
|
|
}
|
|
|
|
if (avatarAnimator == null)
|
|
{
|
|
EditorGUILayout.HelpBox("선택된 오브젝트에 Animator가 없습니다!", MessageType.Error);
|
|
EditorGUILayout.EndScrollView();
|
|
return;
|
|
}
|
|
|
|
EditorGUILayout.HelpBox($"아바타: {selectedAvatar.name}", MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
EditorGUILayout.HelpBox("아바타를 선택해주세요.", MessageType.Warning);
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 포즈 이름 입력
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Label("포즈 이름:", GUILayout.Width(60));
|
|
poseName = EditorGUILayout.TextField(poseName);
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 저장 경로 설정
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.Label("저장 경로:", GUILayout.Width(60));
|
|
savePath = EditorGUILayout.TextField(savePath);
|
|
if (GUILayout.Button("폴더 선택", GUILayout.Width(80)))
|
|
{
|
|
string newPath = EditorUtility.OpenFolderPanel("저장 폴더 선택", savePath, "");
|
|
if (!string.IsNullOrEmpty(newPath))
|
|
{
|
|
// Assets 폴더 내부로 경로 변환
|
|
if (newPath.StartsWith(Application.dataPath))
|
|
{
|
|
savePath = "Assets" + newPath.Substring(Application.dataPath.Length) + "/";
|
|
}
|
|
else
|
|
{
|
|
savePath = "Assets/Recordings/";
|
|
}
|
|
}
|
|
}
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 버튼들
|
|
EditorGUI.BeginDisabledGroup(selectedAvatar == null || avatarAnimator == null);
|
|
|
|
if (GUILayout.Button("현재 포즈 기록 (전체)", GUILayout.Height(30)))
|
|
{
|
|
RecordCurrentPose(true);
|
|
}
|
|
|
|
if (GUILayout.Button("현재 포즈 기록 (본체만)", GUILayout.Height(30)))
|
|
{
|
|
RecordCurrentPose(false);
|
|
}
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
if (GUILayout.Button("T-Pose 기록", GUILayout.Height(30)))
|
|
{
|
|
RecordTPose();
|
|
}
|
|
|
|
if (GUILayout.Button("A-Pose 기록", GUILayout.Height(30)))
|
|
{
|
|
RecordAPose();
|
|
}
|
|
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 정보 표시
|
|
if (selectedAvatar != null && avatarAnimator != null)
|
|
{
|
|
EditorGUILayout.LabelField("기록 가능한 본들:", EditorStyles.boldLabel);
|
|
|
|
// 본체 본들
|
|
EditorGUILayout.LabelField("본체 본들:", EditorStyles.boldLabel);
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
int foundBodyBones = 0;
|
|
foreach (HumanBodyBones humanBone in humanBodyBones)
|
|
{
|
|
Transform bone = avatarAnimator.GetBoneTransform(humanBone);
|
|
if (bone != null)
|
|
{
|
|
EditorGUILayout.LabelField($"✓ {humanBone}");
|
|
foundBodyBones++;
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.LabelField($"본체 본: {foundBodyBones}개");
|
|
|
|
EditorGUILayout.Space();
|
|
|
|
// 손가락 본들
|
|
EditorGUILayout.LabelField("손가락 본들:", EditorStyles.boldLabel);
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
int foundFingerBones = 0;
|
|
foreach (HumanBodyBones fingerBone in fingerBones)
|
|
{
|
|
Transform bone = avatarAnimator.GetBoneTransform(fingerBone);
|
|
if (bone != null)
|
|
{
|
|
EditorGUILayout.LabelField($"✓ {fingerBone}");
|
|
foundFingerBones++;
|
|
}
|
|
}
|
|
EditorGUILayout.EndVertical();
|
|
EditorGUILayout.LabelField($"손가락 본: {foundFingerBones}개");
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField($"총 {foundBodyBones + foundFingerBones}개 본 발견", EditorStyles.boldLabel);
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private void RecordCurrentPose(bool includeFingers = true)
|
|
{
|
|
if (selectedAvatar == null || avatarAnimator == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "아바타를 선택해주세요.", "확인");
|
|
return;
|
|
}
|
|
|
|
PoseData poseData = new PoseData();
|
|
poseData.avatarName = selectedAvatar.name;
|
|
poseData.poseName = poseName;
|
|
poseData.recordedTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
|
|
|
int recordedBones = 0;
|
|
|
|
// 본체 본들 기록
|
|
foreach (HumanBodyBones humanBone in humanBodyBones)
|
|
{
|
|
Transform bone = avatarAnimator.GetBoneTransform(humanBone);
|
|
if (bone != null)
|
|
{
|
|
BonePoseData boneData = new BonePoseData();
|
|
boneData.boneName = humanBone.ToString();
|
|
boneData.localPosition = bone.localPosition;
|
|
boneData.localRotation = bone.localEulerAngles;
|
|
boneData.localScale = bone.localScale;
|
|
|
|
poseData.bonePoses.Add(boneData);
|
|
recordedBones++;
|
|
}
|
|
}
|
|
|
|
// 손가락 본들 기록 (옵션)
|
|
if (includeFingers)
|
|
{
|
|
int fingerBonesCount = 0;
|
|
foreach (HumanBodyBones fingerBone in fingerBones)
|
|
{
|
|
Transform bone = avatarAnimator.GetBoneTransform(fingerBone);
|
|
if (bone != null)
|
|
{
|
|
BonePoseData boneData = new BonePoseData();
|
|
boneData.boneName = fingerBone.ToString();
|
|
boneData.localPosition = bone.localPosition;
|
|
boneData.localRotation = bone.localEulerAngles;
|
|
boneData.localScale = bone.localScale;
|
|
|
|
poseData.bonePoses.Add(boneData);
|
|
recordedBones++;
|
|
fingerBonesCount++;
|
|
}
|
|
}
|
|
|
|
if (fingerBonesCount > 0)
|
|
{
|
|
UnityEngine.Debug.Log($"손가락 본 {fingerBonesCount}개 기록됨");
|
|
}
|
|
}
|
|
|
|
if (recordedBones > 0)
|
|
{
|
|
SavePoseData(poseData);
|
|
string message = includeFingers ?
|
|
$"포즈가 기록되었습니다!\n본체 본: {recordedBones - fingerBones.Length}개\n손가락 본: {fingerBones.Length}개\n총 {recordedBones}개 본" :
|
|
$"포즈가 기록되었습니다!\n본체 본: {recordedBones}개";
|
|
EditorUtility.DisplayDialog("성공", message, "확인");
|
|
}
|
|
else
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "기록할 본을 찾을 수 없습니다.", "확인");
|
|
}
|
|
}
|
|
|
|
private void RecordTPose()
|
|
{
|
|
poseName = "T-Pose";
|
|
RecordCurrentPose(true); // 손가락 포함
|
|
}
|
|
|
|
private void RecordAPose()
|
|
{
|
|
poseName = "A-Pose";
|
|
RecordCurrentPose(true); // 손가락 포함
|
|
}
|
|
|
|
|
|
|
|
private void SavePoseData(PoseData poseData)
|
|
{
|
|
// 저장 폴더 생성
|
|
if (!Directory.Exists(savePath))
|
|
{
|
|
Directory.CreateDirectory(savePath);
|
|
}
|
|
|
|
// 파일명 생성
|
|
string fileName = $"{poseData.poseName}_{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
|
string filePath = Path.Combine(savePath, fileName);
|
|
|
|
// JSON으로 저장
|
|
string json = JsonUtility.ToJson(poseData, true);
|
|
File.WriteAllText(filePath, json);
|
|
|
|
// Unity 에디터에서 파일 새로고침
|
|
AssetDatabase.Refresh();
|
|
|
|
UnityEngine.Debug.Log($"포즈가 저장되었습니다: {filePath}");
|
|
}
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class BonePoseData
|
|
{
|
|
public string boneName;
|
|
public Vector3 localPosition;
|
|
public Vector3 localRotation;
|
|
public Vector3 localScale;
|
|
}
|
|
|
|
[System.Serializable]
|
|
public class PoseData
|
|
{
|
|
public string avatarName;
|
|
public string poseName;
|
|
public string recordedTime;
|
|
public List<BonePoseData> bonePoses = new List<BonePoseData>();
|
|
}
|
|
} |