release(camera-ai): publish 0.4.14 importer hotfix

This commit is contained in:
KINDNICK
2026-08-18 00:00:12 +09:00
parent 9cfdf3d947
commit 150ef2d30c
7 changed files with 1279 additions and 25 deletions
@@ -440,7 +440,7 @@ namespace Streamingle.Editor
originalDirector.gameObject.activeSelf;
try
{
previewDirectorObject = UnityEngine.Object.Instantiate(
previewDirectorObject = InstantiateInactivePreviewCloneForEditor(
originalDirector.gameObject,
originalDirector.transform.parent);
Undo.RegisterCreatedObjectUndo(
@@ -456,6 +456,7 @@ namespace Streamingle.Editor
originalTimeline,
previewDirector,
copiedTimeline);
SuppressAuthoredCinemachineOutputsForEditor(previewDirector);
MuteCopiedCameraTracks(previewDirector, copiedTimeline);
MuteCopiedRecorderTracks(copiedTimeline);
@@ -595,6 +596,7 @@ namespace Streamingle.Editor
$"Position Dense C2 Fallbacks: {positionDenseFallbackCount:N0}",
$"Preview Director: {GetHierarchyPath(previewDirector.transform)}",
$"Preview Camera Root: {GetHierarchyPath(previewCameraObject.transform)}",
GetPreviewOutputStatusForEditor(brain),
$"Timeline Asset: {copiedTimelinePath}",
$"Animation Assets: {generatedClipPaths.Count:N0} " +
$"under {shotAnimationFolder}",
@@ -1449,6 +1451,8 @@ namespace Streamingle.Editor
TimelineAsset finalTimeline = null;
GameObject cameraPrefab = null;
GameObject finalCameraRoot = null;
AuthoredCinemachineStateSnapshot destinationCinemachineStateBefore =
null;
try
{
var sourceTimelinePath = AssetDatabase.GetAssetPath(sourceTimeline);
@@ -1511,6 +1515,11 @@ namespace Streamingle.Editor
destinationTimelineBefore,
finalTimeline,
finalCameraRoot);
destinationCinemachineStateBefore =
CaptureAuthoredCinemachineState(destinationDirector);
SuppressAuthoredCinemachineOutputsForEditor(
destinationDirector,
false);
destinationDirector.time = destinationTimeBefore;
destinationDirector.RebuildGraph();
destinationDirector.time = destinationTimeBefore;
@@ -1545,6 +1554,8 @@ namespace Streamingle.Editor
}
catch
{
RestoreAuthoredCinemachineState(
destinationCinemachineStateBefore);
RestoreDestinationDirectorAfterFinalizationFailure(
destinationStateBefore,
finalTimeline);
@@ -1943,6 +1954,7 @@ namespace Streamingle.Editor
destinationDirector.SetReferenceValue(exposedName, camera);
EditorUtility.SetDirty(shot);
}
}
internal static void ClearTimelineBindingsForFinalization(
@@ -5575,6 +5587,46 @@ namespace Streamingle.Editor
return clonedComponents[index];
}
internal static GameObject InstantiateInactivePreviewCloneForEditor(
GameObject sourceObject,
Transform destinationParent)
{
if (sourceObject == null)
{
throw new ArgumentNullException(nameof(sourceObject));
}
var sourceScene = sourceObject.scene;
if (!sourceScene.IsValid() || !sourceScene.isLoaded)
{
throw new InvalidOperationException(
"The source Timeline object must belong to a loaded scene.");
}
var stagingObject = new GameObject(
"__CWAI_InactivePreviewCloneStaging");
stagingObject.SetActive(false);
try
{
if (stagingObject.scene != sourceScene)
{
SceneManager.MoveGameObjectToScene(stagingObject, sourceScene);
}
var clone = UnityEngine.Object.Instantiate(
sourceObject,
stagingObject.transform,
true);
clone.SetActive(false);
clone.transform.SetParent(destinationParent, true);
return clone;
}
finally
{
UnityEngine.Object.DestroyImmediate(stagingObject);
}
}
private static void EnsureNoExistingPreview(
Scene scene,
string previewDirectorName,
@@ -5645,30 +5697,49 @@ namespace Streamingle.Editor
}
var scene = director.gameObject.scene;
var boundBrains = timeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Select(track => director.GetGenericBinding(track) as CinemachineBrain)
.Where(brain => brain != null)
.Distinct()
.ToArray();
if (boundBrains.Length > 1)
var directlyBoundBrains = GetBoundCinemachineBrains(
director,
timeline);
if (directlyBoundBrains.Length > 1)
{
throw new InvalidOperationException(
"The Timeline has Cinemachine Tracks bound to multiple " +
"CinemachineBrain components. Bind every camera track to one Brain.");
}
if (boundBrains.Length == 1)
if (directlyBoundBrains.Length == 1)
{
var boundBrain = boundBrains[0];
if (!IsUsableCinemachineBrain(boundBrain, scene))
{
throw new InvalidOperationException(
"The CinemachineBrain bound to the Timeline must be enabled, " +
"attached to an enabled Camera, and belong to the Timeline scene.");
}
return ValidateExplicitCinemachineBrain(
directlyBoundBrains[0],
scene);
}
return boundBrain;
var descendantBoundBrains = director.gameObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != director &&
descendant.gameObject.scene == scene &&
descendant.playableAsset is TimelineAsset)
.SelectMany(descendant => GetBoundCinemachineBrains(
descendant,
(TimelineAsset)descendant.playableAsset))
.Distinct()
.ToArray();
if (descendantBoundBrains.Length > 1)
{
throw new InvalidOperationException(
"Descendant PlayableDirectors under the selected Timeline object " +
"have Cinemachine Tracks bound to multiple CinemachineBrain " +
"components. Bind them to one Brain or bind the selected Timeline " +
"directly.");
}
if (descendantBoundBrains.Length == 1)
{
return ValidateExplicitCinemachineBrain(
descendantBoundBrains[0],
scene);
}
var usableBrains = sceneBrains
@@ -5678,7 +5749,7 @@ namespace Streamingle.Editor
var mainCameraBrains = usableBrains
.Where(brain =>
{
var camera = brain.GetComponent<Camera>();
var camera = brain.OutputCamera;
return camera != null && camera.CompareTag("MainCamera");
})
.ToArray();
@@ -5714,6 +5785,81 @@ namespace Streamingle.Editor
"one Camera as MainCamera or bind a Cinemachine Track explicitly.");
}
private static CinemachineBrain[] GetBoundCinemachineBrains(
PlayableDirector director,
TimelineAsset timeline)
{
return timeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Select(track =>
director.GetGenericBinding(track) as CinemachineBrain)
.Where(brain => brain != null)
.Distinct()
.ToArray();
}
private static CinemachineBrain ValidateExplicitCinemachineBrain(
CinemachineBrain brain,
Scene scene)
{
if (brain.gameObject.scene != scene)
{
throw new InvalidOperationException(
"The CinemachineBrain explicitly bound to a Timeline must belong " +
"to the same scene as the selected Timeline director.");
}
var camera = brain.OutputCamera;
if (camera == null)
{
throw new InvalidOperationException(
"The CinemachineBrain explicitly bound to a Timeline must have an " +
"output Camera component on its controlled GameObject.");
}
if (camera.gameObject.scene != scene)
{
throw new InvalidOperationException(
"The CinemachineBrain output Camera explicitly bound to a Timeline " +
"must belong to the same scene as the selected Timeline director.");
}
if (!brain.isActiveAndEnabled || !camera.isActiveAndEnabled)
{
Debug.LogWarning(
"The CinemachineBrain explicitly bound to a Timeline or its " +
"Camera is disabled. Camera AI will use the configured Brain " +
"without enabling either component automatically.",
brain);
}
return brain;
}
internal static string GetPreviewOutputStatusForEditor(
CinemachineBrain brain)
{
if (brain == null)
{
throw new ArgumentNullException(nameof(brain));
}
var camera = brain.OutputCamera;
var cameraName = camera != null
? camera.gameObject.name
: brain.gameObject.name;
if (camera != null &&
brain.isActiveAndEnabled &&
camera.isActiveAndEnabled)
{
return $"Preview Output: configured '{cameraName}' CinemachineBrain " +
"and Camera are active.";
}
return $"Preview Output: configured '{cameraName}' CinemachineBrain or " +
"Camera is disabled; enable it to see AI camera playback.";
}
private static bool IsUsableCinemachineBrain(
CinemachineBrain brain,
Scene scene)
@@ -5725,8 +5871,193 @@ namespace Streamingle.Editor
return false;
}
var camera = brain.GetComponent<Camera>();
return camera != null && camera.isActiveAndEnabled;
var camera = brain.OutputCamera;
return camera != null &&
camera.gameObject.scene == scene &&
camera.isActiveAndEnabled;
}
internal static void SuppressAuthoredCinemachineOutputsForEditor(
PlayableDirector selectedDirector,
bool recordUndo = true)
{
if (selectedDirector == null)
{
throw new ArgumentNullException(nameof(selectedDirector));
}
var directorObject = selectedDirector.gameObject;
var descendantDirectors = directorObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != selectedDirector &&
descendant.playableAsset is TimelineAsset)
.ToArray();
foreach (var descendantDirector in descendantDirectors)
{
var descendantTimeline =
(TimelineAsset)descendantDirector.playableAsset;
var cinemachineTracks = descendantTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.ToArray();
if (cinemachineTracks.Length == 0)
{
continue;
}
if (recordUndo)
{
Undo.RecordObject(
descendantDirector,
"Disconnect Authored Cameras From AI Preview");
}
foreach (var cinemachineTrack in cinemachineTracks)
{
descendantDirector.ClearGenericBinding(cinemachineTrack);
}
RebuildDirectorGraphPreservingPlayback(descendantDirector);
EditorUtility.SetDirty(descendantDirector);
}
foreach (var authoredCamera in directorObject
.GetComponentsInChildren<CinemachineVirtualCameraBase>(true))
{
if (authoredCamera == null || !authoredCamera.enabled)
{
continue;
}
if (recordUndo)
{
Undo.RecordObject(
authoredCamera,
"Disable Authored Cameras In AI Output");
}
authoredCamera.enabled = false;
EditorUtility.SetDirty(authoredCamera);
}
}
internal static AuthoredCinemachineStateSnapshot
CaptureAuthoredCinemachineState(PlayableDirector selectedDirector)
{
if (selectedDirector == null)
{
throw new ArgumentNullException(nameof(selectedDirector));
}
var directorObject = selectedDirector.gameObject;
var directorStates = directorObject
.GetComponentsInChildren<PlayableDirector>(true)
.Where(descendant =>
descendant != null &&
descendant != selectedDirector &&
descendant.playableAsset is TimelineAsset)
.Select(descendant =>
{
var timeline = (TimelineAsset)descendant.playableAsset;
var tracks = timeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.ToArray();
return new AuthoredCinemachineDirectorState(
descendant,
tracks,
tracks.Select(descendant.GetGenericBinding).ToArray());
})
.Where(state => state.Tracks.Length > 0)
.ToArray();
var cameraStates = directorObject
.GetComponentsInChildren<CinemachineVirtualCameraBase>(true)
.Where(camera => camera != null)
.Select(camera => new AuthoredVirtualCameraState(
camera,
camera.enabled))
.ToArray();
return new AuthoredCinemachineStateSnapshot(
directorStates,
cameraStates);
}
internal static void RestoreAuthoredCinemachineState(
AuthoredCinemachineStateSnapshot snapshot)
{
if (snapshot == null)
{
return;
}
foreach (var cameraState in snapshot.CameraStates)
{
if (cameraState.Camera == null)
{
continue;
}
cameraState.Camera.enabled = cameraState.Enabled;
EditorUtility.SetDirty(cameraState.Camera);
}
foreach (var directorState in snapshot.DirectorStates)
{
var director = directorState.Director;
if (director == null)
{
continue;
}
for (var index = 0; index < directorState.Tracks.Length; index++)
{
var track = directorState.Tracks[index];
if (track == null)
{
continue;
}
var binding = directorState.Bindings[index];
if (binding != null)
{
director.SetGenericBinding(track, binding);
}
else
{
director.ClearGenericBinding(track);
}
}
RebuildDirectorGraphPreservingPlayback(director);
EditorUtility.SetDirty(director);
}
}
private static void RebuildDirectorGraphPreservingPlayback(
PlayableDirector director)
{
if (!director.playableGraph.IsValid())
{
return;
}
var time = director.time;
var wasPlaying = director.state == PlayState.Playing;
director.Stop();
director.RebuildGraph();
director.time = time;
if (wasPlaying)
{
director.Play();
director.time = time;
}
else
{
director.Evaluate();
director.time = time;
}
}
private static void CopyTrackBindings(
@@ -6944,6 +7275,51 @@ namespace Streamingle.Editor
public GameObject CameraRoot { get; }
}
internal sealed class AuthoredCinemachineStateSnapshot
{
public AuthoredCinemachineStateSnapshot(
AuthoredCinemachineDirectorState[] directorStates,
AuthoredVirtualCameraState[] cameraStates)
{
DirectorStates = directorStates;
CameraStates = cameraStates;
}
public AuthoredCinemachineDirectorState[] DirectorStates { get; }
public AuthoredVirtualCameraState[] CameraStates { get; }
}
internal sealed class AuthoredCinemachineDirectorState
{
public AuthoredCinemachineDirectorState(
PlayableDirector director,
CinemachineTrack[] tracks,
UnityEngine.Object[] bindings)
{
Director = director;
Tracks = tracks;
Bindings = bindings;
}
public PlayableDirector Director { get; }
public CinemachineTrack[] Tracks { get; }
public UnityEngine.Object[] Bindings { get; }
}
internal sealed class AuthoredVirtualCameraState
{
public AuthoredVirtualCameraState(
CinemachineVirtualCameraBase camera,
bool enabled)
{
Camera = camera;
Enabled = enabled;
}
public CinemachineVirtualCameraBase Camera { get; }
public bool Enabled { get; }
}
private sealed class DirectorStateSnapshot
{
public DirectorStateSnapshot(