Streamingle_URP/Assets/Scripts/Editor/TimelineBindingTransferWindow.cs

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}>";
}
}
}