227 lines
6.5 KiB
C#
227 lines
6.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.Playables;
|
|
using UnityEngine.Timeline;
|
|
|
|
namespace Streamingle.Editor
|
|
{
|
|
public class TimelineBindingTransferWindow : EditorWindow
|
|
{
|
|
private PlayableDirector sourceDirector;
|
|
private PlayableDirector targetDirector;
|
|
private bool matchByHierarchyPath = true;
|
|
private bool requireSameTrackType = true;
|
|
private bool showPreview = true;
|
|
private Vector2 scroll;
|
|
|
|
private struct TrackMap
|
|
{
|
|
public TrackAsset sourceTrack;
|
|
public TrackAsset targetTrack;
|
|
public Object boundObject;
|
|
}
|
|
|
|
private List<TrackMap> previewMappings = new List<TrackMap>();
|
|
|
|
[MenuItem("Tools/Timeline/Transfer Bindings")]
|
|
public static void ShowWindow()
|
|
{
|
|
GetWindow<TimelineBindingTransferWindow>("Timeline Binding Transfer");
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
EditorGUILayout.LabelField("타임라인 바인딩 복사", EditorStyles.boldLabel);
|
|
EditorGUILayout.Space();
|
|
|
|
sourceDirector = (PlayableDirector)EditorGUILayout.ObjectField("소스 디렉터", sourceDirector, typeof(PlayableDirector), true);
|
|
targetDirector = (PlayableDirector)EditorGUILayout.ObjectField("타겟 디렉터", targetDirector, typeof(PlayableDirector), true);
|
|
|
|
EditorGUILayout.Space();
|
|
matchByHierarchyPath = EditorGUILayout.ToggleLeft("트랙 계층 경로로 매칭 (권장)", matchByHierarchyPath);
|
|
requireSameTrackType = EditorGUILayout.ToggleLeft("트랙 타입이 동일할 때만 매칭", requireSameTrackType);
|
|
showPreview = EditorGUILayout.ToggleLeft("미리보기 표시", showPreview);
|
|
|
|
EditorGUILayout.Space();
|
|
EditorGUI.BeginDisabledGroup(!CanBuildMapping());
|
|
if (GUILayout.Button("매핑 미리보기 갱신"))
|
|
{
|
|
BuildPreviewMapping();
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
EditorGUI.BeginDisabledGroup(previewMappings.Count == 0);
|
|
if (GUILayout.Button("바인딩 복사 실행"))
|
|
{
|
|
ApplyMappings();
|
|
}
|
|
EditorGUI.EndDisabledGroup();
|
|
|
|
if (showPreview)
|
|
{
|
|
DrawPreview();
|
|
}
|
|
}
|
|
|
|
private bool CanBuildMapping()
|
|
{
|
|
if (sourceDirector == null || targetDirector == null) return false;
|
|
var srcAsset = sourceDirector.playableAsset as TimelineAsset;
|
|
var dstAsset = targetDirector.playableAsset as TimelineAsset;
|
|
return srcAsset != null && dstAsset != null;
|
|
}
|
|
|
|
private void BuildPreviewMapping()
|
|
{
|
|
previewMappings.Clear();
|
|
|
|
var srcAsset = sourceDirector.playableAsset as TimelineAsset;
|
|
var dstAsset = targetDirector.playableAsset as TimelineAsset;
|
|
if (srcAsset == null || dstAsset == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "소스/타겟 디렉터에 유효한 TimelineAsset이 필요합니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
var sourceTracks = GetAllOutputTracks(srcAsset).ToList();
|
|
var targetTracks = GetAllOutputTracks(dstAsset).ToList();
|
|
|
|
// 타겟 트랙 인덱스 구성
|
|
Dictionary<string, List<TrackAsset>> nameToTargetTracks = new Dictionary<string, List<TrackAsset>>();
|
|
Dictionary<string, TrackAsset> pathToTargetTrack = new Dictionary<string, TrackAsset>();
|
|
|
|
foreach (var t in targetTracks)
|
|
{
|
|
string nameKey = t.name;
|
|
if (!nameToTargetTracks.TryGetValue(nameKey, out var list))
|
|
{
|
|
list = new List<TrackAsset>();
|
|
nameToTargetTracks[nameKey] = list;
|
|
}
|
|
list.Add(t);
|
|
|
|
string pathKey = GetTrackPath(t);
|
|
if (!pathToTargetTrack.ContainsKey(pathKey)) pathToTargetTrack.Add(pathKey, t);
|
|
}
|
|
|
|
foreach (var s in sourceTracks)
|
|
{
|
|
var bound = sourceDirector.GetGenericBinding(s);
|
|
if (bound == null) continue;
|
|
|
|
TrackAsset matched = null;
|
|
if (matchByHierarchyPath)
|
|
{
|
|
pathToTargetTrack.TryGetValue(GetTrackPath(s), out matched);
|
|
}
|
|
else
|
|
{
|
|
if (nameToTargetTracks.TryGetValue(s.name, out var candidates))
|
|
{
|
|
if (requireSameTrackType)
|
|
{
|
|
matched = candidates.FirstOrDefault(c => c.GetType() == s.GetType());
|
|
}
|
|
else
|
|
{
|
|
matched = candidates.FirstOrDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (matched == null) continue;
|
|
if (requireSameTrackType && matched.GetType() != s.GetType()) continue;
|
|
|
|
previewMappings.Add(new TrackMap
|
|
{
|
|
sourceTrack = s,
|
|
targetTrack = matched,
|
|
boundObject = bound as Object
|
|
});
|
|
}
|
|
}
|
|
|
|
private void ApplyMappings()
|
|
{
|
|
if (targetDirector == null)
|
|
{
|
|
EditorUtility.DisplayDialog("오류", "타겟 디렉터가 필요합니다.", "확인");
|
|
return;
|
|
}
|
|
|
|
int applied = 0;
|
|
Undo.RecordObject(targetDirector, "Timeline Binding Transfer");
|
|
foreach (var map in previewMappings)
|
|
{
|
|
if (map.targetTrack == null || map.boundObject == null) continue;
|
|
targetDirector.SetGenericBinding(map.targetTrack, map.boundObject);
|
|
applied++;
|
|
}
|
|
|
|
EditorUtility.SetDirty(targetDirector);
|
|
var go = targetDirector.gameObject;
|
|
if (go != null && go.scene.IsValid())
|
|
{
|
|
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(go.scene);
|
|
}
|
|
|
|
EditorUtility.DisplayDialog("완료", $"바인딩 복사 완료: {applied}개 적용", "확인");
|
|
}
|
|
|
|
private void DrawPreview()
|
|
{
|
|
EditorGUILayout.Space();
|
|
EditorGUILayout.LabelField("매핑 미리보기", EditorStyles.boldLabel);
|
|
scroll = EditorGUILayout.BeginScrollView(scroll, GUILayout.MinHeight(150));
|
|
|
|
if (previewMappings.Count == 0)
|
|
{
|
|
EditorGUILayout.HelpBox("미리보기가 비어 있습니다. '매핑 미리보기 갱신'을 눌러 생성하세요.", MessageType.Info);
|
|
}
|
|
else
|
|
{
|
|
foreach (var m in previewMappings)
|
|
{
|
|
EditorGUILayout.BeginVertical("box");
|
|
EditorGUILayout.LabelField($"소스: {GetTrackDisplay(m.sourceTrack)}");
|
|
EditorGUILayout.LabelField($"타겟: {GetTrackDisplay(m.targetTrack)}");
|
|
EditorGUILayout.ObjectField("바인딩 객체", m.boundObject, typeof(Object), true);
|
|
EditorGUILayout.EndVertical();
|
|
}
|
|
}
|
|
|
|
EditorGUILayout.EndScrollView();
|
|
}
|
|
|
|
private static IEnumerable<TrackAsset> GetAllOutputTracks(TimelineAsset asset)
|
|
{
|
|
// GetOutputTracks는 서브트랙을 평탄화하여 반환하므로 그대로 사용
|
|
return asset.GetOutputTracks();
|
|
}
|
|
|
|
private static string GetTrackPath(TrackAsset track)
|
|
{
|
|
// 상위 트랙 이름을 포함한 경로 문자열 생성
|
|
List<string> names = new List<string>();
|
|
var current = track;
|
|
while (current != null)
|
|
{
|
|
names.Add(current.name);
|
|
current = current.parent as TrackAsset;
|
|
}
|
|
names.Reverse();
|
|
return string.Join("/", names);
|
|
}
|
|
|
|
private static string GetTrackDisplay(TrackAsset track)
|
|
{
|
|
if (track == null) return "(없음)";
|
|
return $"{GetTrackPath(track)} <{track.GetType().Name}>";
|
|
}
|
|
}
|
|
}
|
|
|
|
|