【剧情】后处理场景增加剧情cinemachine兼容
parent
9821d4159e
commit
7e76c2cab3
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
|
|
@ -1093,87 +1094,297 @@ public abstract class PostProcessor
|
|||
/// <summary>
|
||||
/// 设置剧情timeline
|
||||
/// </summary>
|
||||
private async UniTask SetTimeline()
|
||||
private UniTask SetTimeline()
|
||||
{
|
||||
GameObject goOld = PostProcessInstructor.timeline;
|
||||
if (null == goOld)
|
||||
return;
|
||||
return UniTask.CompletedTask;
|
||||
|
||||
PlayableDirector oldDirector = goOld.GetComponent<PlayableDirector>();
|
||||
if (null == oldDirector)
|
||||
{
|
||||
Debug.LogError("获取PlayableDirector失败");
|
||||
return;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
if (null == oldDirector.playableAsset)
|
||||
{
|
||||
Debug.LogError("获取PlayableAsset失败");
|
||||
return;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
string oldPath = AssetDatabase.GetAssetPath(oldDirector.playableAsset);
|
||||
string newPath = $"{ProcessedResourceDir}/{oldDirector.playableAsset.name}.playable";
|
||||
string cmPath = $"{ProcessedResourceDir}/{oldDirector.playableAsset.name}_cinemachine.playable";
|
||||
var oldTimelineAsset = oldDirector.playableAsset as TimelineAsset;
|
||||
var hasCinemachineTrack = oldTimelineAsset != null && _EnumerateTracks(oldTimelineAsset).Any(_IsCinemachineTrack);
|
||||
if (!AssetDatabase.CopyAsset(oldPath, newPath))
|
||||
{
|
||||
Debug.LogError("Timeline拷贝失败");
|
||||
return;
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
if (hasCinemachineTrack && !AssetDatabase.CopyAsset(oldPath, cmPath))
|
||||
{
|
||||
Debug.LogError("Cinemachine Timeline拷贝失败");
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
var newTimelineAsset = AssetDatabase.LoadAssetAtPath<TimelineAsset>(newPath);
|
||||
var cmTimelineAsset = hasCinemachineTrack ? AssetDatabase.LoadAssetAtPath<TimelineAsset>(cmPath) : null;
|
||||
if (newTimelineAsset == null || (hasCinemachineTrack && cmTimelineAsset == null))
|
||||
{
|
||||
Debug.LogError("加载后处理Timeline资源失败");
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
_FilterTimelineTracks(newTimelineAsset, keepCinemachine: false);
|
||||
if (hasCinemachineTrack)
|
||||
{
|
||||
_FilterTimelineTracks(cmTimelineAsset, keepCinemachine: true);
|
||||
}
|
||||
EditorUtility.SetDirty(newTimelineAsset);
|
||||
AssetDatabase.SaveAssetIfDirty(newTimelineAsset);
|
||||
if (hasCinemachineTrack)
|
||||
{
|
||||
EditorUtility.SetDirty(cmTimelineAsset);
|
||||
AssetDatabase.SaveAssetIfDirty(cmTimelineAsset);
|
||||
}
|
||||
|
||||
GameObject goNew = new(goOld.name);
|
||||
goNew.transform.parent = goOld.transform.parent;
|
||||
goNew.transform.position = Vector3.zero;
|
||||
goNew.transform.rotation = Quaternion.identity;
|
||||
PlayableDirector newDirector = goNew.AddComponent<PlayableDirector>();
|
||||
_CopyDirectorSettings(oldDirector, newDirector);
|
||||
newDirector.playableAsset = newTimelineAsset;
|
||||
|
||||
PlayableDirector cmDirector = null;
|
||||
if (hasCinemachineTrack)
|
||||
{
|
||||
GameObject goCm = new("timeline_cinemachine");
|
||||
goCm.transform.parent = goOld.transform.parent;
|
||||
goCm.transform.position = Vector3.zero;
|
||||
goCm.transform.rotation = Quaternion.identity;
|
||||
cmDirector = goCm.AddComponent<PlayableDirector>();
|
||||
_CopyDirectorSettings(oldDirector, cmDirector);
|
||||
cmDirector.playableAsset = cmTimelineAsset;
|
||||
}
|
||||
|
||||
_CopyDirectorBindings(oldDirector, newDirector, track => !_IsCinemachineTrack(track));
|
||||
if (cmDirector != null)
|
||||
{
|
||||
_CopyDirectorBindings(oldDirector, cmDirector, _IsCinemachineTrack);
|
||||
_SyncExposedReferences(oldDirector, cmDirector);
|
||||
}
|
||||
|
||||
GameObject.DestroyImmediate(goOld);
|
||||
|
||||
PostProcessInstructor.timeline = goNew;
|
||||
EditorUtility.SetDirty(PostProcessInstructor);
|
||||
EditorUtility.SetDirty(newDirector);
|
||||
if (cmDirector != null)
|
||||
{
|
||||
EditorUtility.SetDirty(cmDirector);
|
||||
}
|
||||
EditorSceneManager.MarkSceneDirty(goNew.scene);
|
||||
|
||||
AssetDatabase.SaveAssetIfDirty(newDirector);
|
||||
if (cmDirector != null)
|
||||
{
|
||||
AssetDatabase.SaveAssetIfDirty(cmDirector);
|
||||
}
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
private void _CopyDirectorSettings(PlayableDirector oldDirector, PlayableDirector newDirector)
|
||||
{
|
||||
newDirector.timeUpdateMode = oldDirector.timeUpdateMode;
|
||||
newDirector.playOnAwake = oldDirector.playOnAwake;
|
||||
newDirector.extrapolationMode = oldDirector.extrapolationMode;
|
||||
newDirector.initialTime = oldDirector.initialTime;
|
||||
}
|
||||
|
||||
// 新预制体绑定新Playable资源
|
||||
newDirector.playableAsset = AssetDatabase.LoadAssetAtPath<TimelineAsset>(newPath);
|
||||
newDirector.playableAsset.name = System.IO.Path.GetFileNameWithoutExtension(newPath);
|
||||
SerializedObject directorSO = new(oldDirector);
|
||||
SerializedProperty sceneBindings = directorSO.FindProperty("m_SceneBindings");
|
||||
|
||||
newDirector.Play();
|
||||
await UniTask.NextFrame();
|
||||
|
||||
// 删除PlayableDirector.Bindgins的空Key项,并把原Timeline文件的轨道Key替换到当前Timeline文件的轨道对象
|
||||
Dictionary<Object, Object> bindingsKV = new();
|
||||
for (int i = 0; i < sceneBindings.arraySize; ++i)
|
||||
private void _CopyDirectorBindings(PlayableDirector oldDirector, PlayableDirector newDirector, Func<TrackAsset, bool> trackFilter)
|
||||
{
|
||||
var oldTimelineAsset = oldDirector.playableAsset as TimelineAsset;
|
||||
var newTimelineAsset = newDirector.playableAsset as TimelineAsset;
|
||||
if (oldTimelineAsset == null || newTimelineAsset == null)
|
||||
{
|
||||
SerializedProperty bindingElement = sceneBindings.GetArrayElementAtIndex(i);
|
||||
// key是源文件的轨道对象,value是当前文件的绑定value
|
||||
Object key = bindingElement.FindPropertyRelative("key").objectReferenceValue;
|
||||
Object value = bindingElement.FindPropertyRelative("value").objectReferenceValue;
|
||||
return;
|
||||
}
|
||||
|
||||
if (null == key)
|
||||
var oldTracks = oldTimelineAsset.GetOutputTracks()
|
||||
.Where(track => track != null && trackFilter(track))
|
||||
.ToList();
|
||||
var newTracks = newTimelineAsset.GetOutputTracks()
|
||||
.Where(track => track != null)
|
||||
.ToList();
|
||||
|
||||
for (int i = 0; i < oldTracks.Count && i < newTracks.Count; ++i)
|
||||
{
|
||||
var value = oldDirector.GetGenericBinding(oldTracks[i]);
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bindingsKV.Add(key, value);
|
||||
newDirector.SetGenericBinding(newTracks[i], value);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除无用绑定
|
||||
while (sceneBindings.arraySize > 0)
|
||||
private void _FilterTimelineTracks(TimelineAsset timelineAsset, bool keepCinemachine)
|
||||
{
|
||||
var allTracks = _EnumerateTracks(timelineAsset).ToList();
|
||||
var cmTrackAncestors = new HashSet<TrackAsset>();
|
||||
foreach (var track in allTracks.Where(_IsCinemachineTrack))
|
||||
{
|
||||
sceneBindings.DeleteArrayElementAtIndex(0);
|
||||
for (var current = track; current != null; current = current.parent as TrackAsset)
|
||||
{
|
||||
cmTrackAncestors.Add(current);
|
||||
}
|
||||
}
|
||||
directorSO.ApplyModifiedProperties();
|
||||
|
||||
// 设置有效绑定(新老timeline资源outputs顺序完全一致)
|
||||
List<PlayableBinding> listOldBinding = oldDirector.playableAsset.outputs.ToList();
|
||||
List<PlayableBinding> listNewBinding = newDirector.playableAsset.outputs.ToList();
|
||||
for (int i = 0; i < listOldBinding.Count; ++i)
|
||||
var removeTracks = allTracks
|
||||
.Where(track => keepCinemachine ? !cmTrackAncestors.Contains(track) : _IsCinemachineTrack(track))
|
||||
.OrderByDescending(_GetTrackDepth)
|
||||
.ToList();
|
||||
|
||||
foreach (var track in removeTracks)
|
||||
{
|
||||
TrackAsset oldTrackAsset = listOldBinding[i].sourceObject as TrackAsset;
|
||||
TrackAsset newTrackAsset = listNewBinding[i].sourceObject as TrackAsset;
|
||||
timelineAsset.DeleteTrack(track);
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingsKV.TryGetValue(oldTrackAsset, out Object value))
|
||||
newDirector.SetGenericBinding(newTrackAsset, value);
|
||||
private IEnumerable<TrackAsset> _EnumerateTracks(TimelineAsset timelineAsset)
|
||||
{
|
||||
foreach (var rootTrack in timelineAsset.GetRootTracks())
|
||||
{
|
||||
if (rootTrack == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var track in _EnumerateTrack(rootTrack))
|
||||
{
|
||||
yield return track;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<TrackAsset> _EnumerateTrack(TrackAsset track)
|
||||
{
|
||||
yield return track;
|
||||
foreach (var childTrack in track.GetChildTracks())
|
||||
{
|
||||
if (childTrack == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var nestedTrack in _EnumerateTrack(childTrack))
|
||||
{
|
||||
yield return nestedTrack;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int _GetTrackDepth(TrackAsset track)
|
||||
{
|
||||
var depth = 0;
|
||||
for (var parent = track.parent as TrackAsset; parent != null; parent = parent.parent as TrackAsset)
|
||||
{
|
||||
depth++;
|
||||
}
|
||||
|
||||
GameObject.DestroyImmediate(PostProcessInstructor.timeline);
|
||||
AssetDatabase.SaveAssetIfDirty(newDirector);
|
||||
return depth;
|
||||
}
|
||||
|
||||
private bool _IsCinemachineTrack(TrackAsset track)
|
||||
{
|
||||
return track != null && track.GetType().FullName.Contains("Cinemachine");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步 Timeline 的 ExposedReferences 到新场景
|
||||
/// 主要解决 CinemachineShot 引用的 VirtualCamera 在场景复制后 fileID 变化的问题
|
||||
/// </summary>
|
||||
private void _SyncExposedReferences(PlayableDirector oldDirector, PlayableDirector newDirector)
|
||||
{
|
||||
SerializedObject oldDirectorSO = new(oldDirector);
|
||||
SerializedProperty oldExposedRefs = oldDirectorSO.FindProperty("m_ExposedReferences.m_References");
|
||||
|
||||
if (oldExposedRefs == null || oldExposedRefs.arraySize == 0)
|
||||
{
|
||||
Debug.LogWarning("旧 PlayableDirector 没有 ExposedReferences");
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历旧的暴露引用,在新场景中找到对应的对象
|
||||
for (int i = 0; i < oldExposedRefs.arraySize; ++i)
|
||||
{
|
||||
SerializedProperty refElement = oldExposedRefs.GetArrayElementAtIndex(i);
|
||||
SerializedProperty guidProp = refElement.FindPropertyRelative("first");
|
||||
SerializedProperty objProp = refElement.FindPropertyRelative("second");
|
||||
|
||||
if (guidProp == null || objProp == null) continue;
|
||||
|
||||
string exposedName = guidProp.stringValue;
|
||||
Object oldReferencedObj = objProp.objectReferenceValue;
|
||||
|
||||
if (string.IsNullOrEmpty(exposedName) || oldReferencedObj == null) continue;
|
||||
|
||||
if (oldReferencedObj is not Component oldComponent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 先只处理 Cinemachine 相关轨道,避免误改其他 ExposedReference。
|
||||
if (!oldComponent.GetType().FullName.Contains("Cinemachine"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 在新场景中找到对应的 GameObject(通过完整路径匹配)
|
||||
string oldObjPath = GetGameObjectPath(oldComponent.gameObject);
|
||||
GameObject newReferencedObjGo = GameObject.Find(oldObjPath);
|
||||
|
||||
if (newReferencedObjGo == null)
|
||||
{
|
||||
Debug.LogWarning($"在新场景中找不到对应的 GameObject: {oldObjPath} (exposedName: {exposedName})");
|
||||
continue;
|
||||
}
|
||||
|
||||
var newReferencedObj = newReferencedObjGo.GetComponent(oldComponent.GetType());
|
||||
if (newReferencedObj == null)
|
||||
{
|
||||
Debug.LogWarning($"在新场景中找不到对应组件: {oldComponent.GetType().FullName}, path: {oldObjPath}, exposedName: {exposedName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 设置新的引用
|
||||
newDirector.SetReferenceValue(new PropertyName(exposedName), newReferencedObj);
|
||||
Debug.Log($"同步 Cinemachine ExposedReference: {exposedName} -> {oldObjPath} ({oldComponent.GetType().Name})");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取 GameObject 相对于场景根的完整路径
|
||||
/// </summary>
|
||||
private string GetGameObjectPath(GameObject go)
|
||||
{
|
||||
if (go == null) return "";
|
||||
|
||||
var path = go.name;
|
||||
Transform parent = go.transform.parent;
|
||||
|
||||
while (parent != null)
|
||||
{
|
||||
path = parent.name + "/" + path;
|
||||
parent = parent.parent;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue