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
@@ -6,13 +6,26 @@ using System.Text;
using NUnit.Framework;
using Unity.Cinemachine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.Timeline;
using Object = UnityEngine.Object;
namespace Streamingle.Editor.Tests
{
public sealed class PreviewCloneActivationProbe : MonoBehaviour
{
internal static int EnableCount { get; set; }
private void OnEnable()
{
EnableCount++;
}
}
public sealed class AICameraTimelinePreviewImporterTests
{
[Test]
@@ -34,6 +47,10 @@ namespace Streamingle.Editor.Tests
timeline,
new[] { brain }),
Is.SameAs(brain));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("are active"));
}
finally
{
@@ -111,6 +128,843 @@ namespace Streamingle.Editor.Tests
}
}
[Test]
public void BrainResolutionUsesDisabledUniqueDescendantBinding()
{
var directorObject = new GameObject("Director");
var childDirectorObject = new GameObject("Camera Director");
var cameraObject = new GameObject("Configured Camera");
childDirectorObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset childTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var childDirector =
childDirectorObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
childTimeline = CreateTimelineBoundToBrain(
childDirector,
brain,
"Child Camera Track");
childDirectorObject.SetActive(false);
brain.enabled = false;
LogAssert.Expect(
LogType.Warning,
"The CinemachineBrain explicitly bound to a Timeline or its " +
"Camera is disabled. Camera AI will use the configured Brain " +
"without enabling either component automatically.");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Is.SameAs(brain));
Assert.That(brain.enabled, Is.False);
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("enable it to see AI camera playback"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(childTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionDeduplicatesSameBrainAcrossDescendants()
{
var directorObject = new GameObject("Director");
var firstChildObject = new GameObject("First Camera Director");
var secondChildObject = new GameObject("Second Camera Director");
var cameraObject = new GameObject("Configured Camera");
firstChildObject.transform.SetParent(directorObject.transform, false);
secondChildObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset firstChildTimeline = null;
TimelineAsset secondChildTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var firstChildDirector =
firstChildObject.AddComponent<PlayableDirector>();
var secondChildDirector =
secondChildObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
firstChildTimeline = CreateTimelineBoundToBrain(
firstChildDirector,
brain,
"First Child Camera Track");
secondChildTimeline = CreateTimelineBoundToBrain(
secondChildDirector,
brain,
"Second Child Camera Track");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
Array.Empty<CinemachineBrain>()),
Is.SameAs(brain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(firstChildTimeline);
Object.DestroyImmediate(secondChildTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsAmbiguousDescendantBindings()
{
var directorObject = new GameObject("Director");
var firstChildObject = new GameObject("First Camera Director");
var secondChildObject = new GameObject("Second Camera Director");
var firstCameraObject = new GameObject("First Configured Camera");
var secondCameraObject = new GameObject("Second Configured Camera");
firstChildObject.transform.SetParent(directorObject.transform, false);
secondChildObject.transform.SetParent(directorObject.transform, false);
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset firstChildTimeline = null;
TimelineAsset secondChildTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var firstChildDirector =
firstChildObject.AddComponent<PlayableDirector>();
var secondChildDirector =
secondChildObject.AddComponent<PlayableDirector>();
firstCameraObject.AddComponent<Camera>();
var firstBrain =
firstCameraObject.AddComponent<CinemachineBrain>();
secondCameraObject.AddComponent<Camera>();
var secondBrain =
secondCameraObject.AddComponent<CinemachineBrain>();
firstChildTimeline = CreateTimelineBoundToBrain(
firstChildDirector,
firstBrain,
"First Child Camera Track");
secondChildTimeline = CreateTimelineBoundToBrain(
secondChildDirector,
secondBrain,
"Second Child Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { firstBrain, secondBrain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("Descendant PlayableDirectors"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(firstCameraObject);
Object.DestroyImmediate(secondCameraObject);
Object.DestroyImmediate(firstChildTimeline);
Object.DestroyImmediate(secondChildTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionPrefersDirectBindingOverDescendants()
{
var directorObject = new GameObject("Director");
var childDirectorObject = new GameObject("Camera Director");
var directCameraObject = new GameObject("Direct Camera");
var childCameraObject = new GameObject("Child Camera");
childDirectorObject.transform.SetParent(directorObject.transform, false);
TimelineAsset timeline = null;
TimelineAsset childTimeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var childDirector =
childDirectorObject.AddComponent<PlayableDirector>();
directCameraObject.AddComponent<Camera>();
var directBrain =
directCameraObject.AddComponent<CinemachineBrain>();
childCameraObject.AddComponent<Camera>();
var childBrain =
childCameraObject.AddComponent<CinemachineBrain>();
timeline = CreateTimelineBoundToBrain(
director,
directBrain,
"Direct Camera Track");
childTimeline = CreateTimelineBoundToBrain(
childDirector,
childBrain,
"Child Camera Track");
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { childBrain, directBrain }),
Is.SameAs(directBrain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(directCameraObject);
Object.DestroyImmediate(childCameraObject);
Object.DestroyImmediate(childTimeline);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsExplicitBrainFromAnotherScene()
{
var directorObject = new GameObject("Director");
var cameraObject = new GameObject("Foreign Camera");
var foreignScene = EditorSceneManager.NewPreviewScene();
SceneManager.MoveGameObjectToScene(cameraObject, foreignScene);
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Foreign Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("same scene"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(timeline);
EditorSceneManager.ClosePreviewScene(foreignScene);
}
}
[Test]
public void BrainResolutionRejectsExplicitBrainWithoutCamera()
{
var directorObject = new GameObject("Director");
var cameraObject = new GameObject("Camera-less Brain");
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var camera = cameraObject.AddComponent<Camera>();
var brain = cameraObject.AddComponent<CinemachineBrain>();
Object.DestroyImmediate(camera);
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Invalid Camera Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("Camera component"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionAcceptsSeparateControlledOutputCamera()
{
var directorObject = new GameObject("Director");
var brainObject = new GameObject("Configured Brain");
var outputCameraObject = new GameObject("Controlled Output Camera");
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var brain = brainObject.AddComponent<CinemachineBrain>();
var outputCamera = outputCameraObject.AddComponent<Camera>();
brain.ControlledObject = outputCameraObject;
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Controlled Output Camera Track");
Assert.That(brain.OutputCamera, Is.SameAs(outputCamera));
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Is.SameAs(brain));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain(outputCameraObject.name));
Assert.That(
AICameraTimelinePreviewImporter
.GetPreviewOutputStatusForEditor(brain),
Does.Contain("are active"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(brainObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionUsesControlledOutputMainCameraForFallback()
{
var directorObject = new GameObject("Director");
var mainBrainObject = new GameObject("Main Output Brain");
var mainCameraObject = new GameObject("Main Output Camera");
var secondaryCameraObject = new GameObject("Secondary Camera");
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
try
{
var director = directorObject.AddComponent<PlayableDirector>();
director.playableAsset = timeline;
var mainBrain = mainBrainObject.AddComponent<CinemachineBrain>();
mainCameraObject.tag = "MainCamera";
mainCameraObject.AddComponent<Camera>();
mainBrain.ControlledObject = mainCameraObject;
secondaryCameraObject.AddComponent<Camera>();
var secondaryBrain =
secondaryCameraObject.AddComponent<CinemachineBrain>();
Assert.That(
AICameraTimelinePreviewImporter.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { secondaryBrain, mainBrain }),
Is.SameAs(mainBrain));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(mainBrainObject);
Object.DestroyImmediate(mainCameraObject);
Object.DestroyImmediate(secondaryCameraObject);
Object.DestroyImmediate(timeline);
}
}
[Test]
public void BrainResolutionRejectsControlledOutputCameraFromAnotherScene()
{
var directorObject = new GameObject("Director");
var brainObject = new GameObject("Configured Brain");
var outputCameraObject = new GameObject("Foreign Output Camera");
var foreignScene = EditorSceneManager.NewPreviewScene();
SceneManager.MoveGameObjectToScene(outputCameraObject, foreignScene);
TimelineAsset timeline = null;
try
{
var director = directorObject.AddComponent<PlayableDirector>();
var brain = brainObject.AddComponent<CinemachineBrain>();
outputCameraObject.AddComponent<Camera>();
brain.ControlledObject = outputCameraObject;
timeline = CreateTimelineBoundToBrain(
director,
brain,
"Foreign Controlled Output Track");
Assert.That(
() => AICameraTimelinePreviewImporter
.ResolveCinemachineBrainForEditor(
director,
timeline,
new[] { brain }),
Throws.TypeOf<InvalidOperationException>()
.With.Message.Contains("output Camera")
.And.Message.Contains("same scene"));
}
finally
{
Object.DestroyImmediate(directorObject);
Object.DestroyImmediate(brainObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(timeline);
EditorSceneManager.ClosePreviewScene(foreignScene);
}
}
[Test]
public void InactiveStagingCloneNeverActivatesAndPreservesSourcePlayback()
{
var parentObject = new GameObject("Preview Parent");
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredCameraObject = new GameObject("Authored Virtual Camera");
sourceObject.transform.SetParent(parentObject.transform, false);
motionObject.transform.SetParent(sourceObject.transform, false);
authoredCameraObject.transform.SetParent(
motionObject.transform,
false);
parentObject.transform.position = new Vector3(4f, -2f, 7f);
sourceObject.transform.localPosition = new Vector3(1f, 2f, 3f);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
var motionTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
GameObject cloneObject = null;
PlayableDirector motionDirector = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
motionDirector = motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = true;
motionDirector.timeUpdateMode = DirectorUpdateMode.Manual;
motionDirector.playableAsset = motionTimeline;
var authoredCamera = authoredCameraObject
.AddComponent<CinemachineCamera>();
sourceObject.AddComponent<PreviewCloneActivationProbe>();
motionDirector.Play();
motionDirector.time = 0.4d;
motionDirector.Evaluate();
var sourceWorldPosition = sourceObject.transform.position;
var sourcePlaybackState = motionDirector.state;
PreviewCloneActivationProbe.EnableCount = 0;
cloneObject = AICameraTimelinePreviewImporter
.InstantiateInactivePreviewCloneForEditor(
sourceObject,
parentObject.transform);
var cloneMotionDirector = cloneObject.transform
.Find(motionObject.name)
.GetComponent<PlayableDirector>();
var cloneAuthoredCamera = cloneObject.transform
.Find($"{motionObject.name}/{authoredCameraObject.name}")
.GetComponent<CinemachineCamera>();
Assert.That(cloneObject.transform.parent, Is.SameAs(parentObject.transform));
Assert.That(cloneObject.transform.position, Is.EqualTo(sourceWorldPosition));
Assert.That(cloneObject.activeSelf, Is.False);
Assert.That(cloneObject.activeInHierarchy, Is.False);
Assert.That(PreviewCloneActivationProbe.EnableCount, Is.Zero);
Assert.That(cloneMotionDirector.playOnAwake, Is.True);
Assert.That(cloneMotionDirector.playableGraph.IsValid(), Is.False);
Assert.That(cloneAuthoredCamera.enabled, Is.True);
Assert.That(cloneAuthoredCamera.isActiveAndEnabled, Is.False);
Assert.That(sourceObject.activeSelf, Is.True);
Assert.That(sourceObject.activeInHierarchy, Is.True);
Assert.That(motionDirector.state, Is.EqualTo(sourcePlaybackState));
Assert.That(motionDirector.time, Is.EqualTo(0.4d).Within(0.000001d));
Assert.That(authoredCamera.enabled, Is.True);
Assert.That(authoredCamera.isActiveAndEnabled, Is.True);
}
finally
{
PreviewCloneActivationProbe.EnableCount = 0;
if (motionDirector != null)
{
motionDirector.Stop();
}
Object.DestroyImmediate(cloneObject);
Object.DestroyImmediate(parentObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void PreviewCloneSuppressesOnlyAuthoredCinemachineOutputs()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredVirtualCameraObject =
new GameObject("Authored Virtual Camera");
var cameraObject = new GameObject("Authored Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
authoredVirtualCameraObject.transform.SetParent(
motionObject.transform,
false);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset motionTimeline = null;
GameObject previewObject = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
var motionAnimator = motionObject.AddComponent<Animator>();
var authoredVirtualCamera = authoredVirtualCameraObject
.AddComponent<CinemachineCamera>();
cameraObject.AddComponent<Camera>();
var authoredBrain =
cameraObject.AddComponent<CinemachineBrain>();
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
authoredBrain,
"Authored Camera Track");
var motionTrack = motionTimeline.CreateTrack<AnimationTrack>(
null,
"Motion Track");
motionDirector.SetGenericBinding(motionTrack, motionAnimator);
previewObject = Object.Instantiate(sourceObject);
var previewMotionObject =
previewObject.transform.Find(motionObject.name).gameObject;
var previewMotionDirector =
previewMotionObject.GetComponent<PlayableDirector>();
var previewMotionAnimator =
previewMotionObject.GetComponent<Animator>();
var previewAuthoredVirtualCamera = previewMotionObject.transform
.Find(authoredVirtualCameraObject.name)
.GetComponent<CinemachineCamera>();
var authoredCameraTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
var motionTimelineWasDirty = EditorUtility.IsDirty(motionTimeline);
var suppressionState = AICameraTimelinePreviewImporter
.CaptureAuthoredCinemachineState(
previewObject.GetComponent<PlayableDirector>());
Assert.That(previewObject.activeSelf, Is.True);
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.True);
Assert.That(authoredVirtualCamera.enabled, Is.True);
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
previewObject.GetComponent<PlayableDirector>());
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.Null);
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.False);
Assert.That(authoredVirtualCamera.enabled, Is.True);
Assert.That(
previewMotionDirector.playableAsset,
Is.SameAs(motionTimeline));
Assert.That(
motionTimeline.GetOutputTracks(),
Does.Contain(authoredCameraTrack));
Assert.That(
motionTimeline.GetOutputTracks(),
Does.Contain(motionTrack));
Assert.That(
motionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
motionDirector.GetGenericBinding(motionTrack),
Is.SameAs(motionAnimator));
Assert.That(
motionDirector.playableAsset,
Is.SameAs(motionTimeline));
Assert.That(
EditorUtility.IsDirty(motionTimeline),
Is.EqualTo(motionTimelineWasDirty));
AICameraTimelinePreviewImporter.RestoreAuthoredCinemachineState(
suppressionState);
Assert.That(
previewMotionDirector.GetGenericBinding(authoredCameraTrack),
Is.SameAs(authoredBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(motionTrack),
Is.SameAs(previewMotionAnimator));
Assert.That(previewAuthoredVirtualCamera.enabled, Is.True);
Assert.That(authoredVirtualCamera.enabled, Is.True);
}
finally
{
Object.DestroyImmediate(previewObject);
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(cameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void PreviewCloneKeepsParentGeneratedCinemachineBinding()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var generatedCameraObject = new GameObject("Generated Camera");
var authoredCameraObject = new GameObject("Authored Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
TimelineAsset sourceTimeline = null;
TimelineAsset motionTimeline = null;
GameObject previewObject = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
generatedCameraObject.AddComponent<Camera>();
var generatedBrain =
generatedCameraObject.AddComponent<CinemachineBrain>();
authoredCameraObject.AddComponent<Camera>();
var authoredBrain =
authoredCameraObject.AddComponent<CinemachineBrain>();
sourceTimeline = CreateTimelineBoundToBrain(
sourceDirector,
generatedBrain,
AICameraTimelinePreviewImporter.GeneratedCinemachineTrackName);
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
authoredBrain,
"Authored Camera Track");
var generatedTrack = sourceTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
var authoredTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
previewObject = Object.Instantiate(sourceObject);
var previewDirector =
previewObject.GetComponent<PlayableDirector>();
var previewMotionDirector = previewObject.transform
.Find(motionObject.name)
.GetComponent<PlayableDirector>();
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
previewDirector);
Assert.That(
previewDirector.GetGenericBinding(generatedTrack),
Is.SameAs(generatedBrain));
Assert.That(
previewMotionDirector.GetGenericBinding(authoredTrack),
Is.Null);
Assert.That(
sourceDirector.GetGenericBinding(generatedTrack),
Is.SameAs(generatedBrain));
Assert.That(
motionDirector.GetGenericBinding(authoredTrack),
Is.SameAs(authoredBrain));
}
finally
{
Object.DestroyImmediate(previewObject);
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(generatedCameraObject);
Object.DestroyImmediate(authoredCameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[Test]
public void FinalSuppressionDoesNotCreatePartialUndoEntry()
{
var destinationObject = new GameObject("Destination Director");
var motionObject = new GameObject("Motion Director");
var authoredVirtualCameraObject =
new GameObject("Authored Virtual Camera");
var outputCameraObject = new GameObject("Output Camera");
var undoSentinelObject = new GameObject("Undo Sentinel");
motionObject.transform.SetParent(destinationObject.transform, false);
authoredVirtualCameraObject.transform.SetParent(
motionObject.transform,
false);
var destinationTimeline =
ScriptableObject.CreateInstance<TimelineAsset>();
TimelineAsset motionTimeline = null;
try
{
var destinationDirector =
destinationObject.AddComponent<PlayableDirector>();
destinationDirector.playableAsset = destinationTimeline;
var motionDirector =
motionObject.AddComponent<PlayableDirector>();
var authoredVirtualCamera = authoredVirtualCameraObject
.AddComponent<CinemachineCamera>();
outputCameraObject.AddComponent<Camera>();
var brain = outputCameraObject.AddComponent<CinemachineBrain>();
motionTimeline = CreateTimelineBoundToBrain(
motionDirector,
brain,
"Authored Camera Track");
var cameraTrack = motionTimeline.GetOutputTracks()
.OfType<CinemachineTrack>()
.Single();
Undo.ClearAll();
Undo.IncrementCurrentGroup();
var sentinelGroup = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(
undoSentinelObject.transform,
"Final Suppression Undo Sentinel");
undoSentinelObject.transform.position = Vector3.one;
Undo.CollapseUndoOperations(sentinelGroup);
Undo.FlushUndoRecordObjects();
Undo.IncrementCurrentGroup();
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(
destinationDirector,
false);
Undo.FlushUndoRecordObjects();
Assert.That(
motionDirector.GetGenericBinding(cameraTrack),
Is.Null);
Assert.That(authoredVirtualCamera.enabled, Is.False);
Undo.PerformUndo();
Assert.That(undoSentinelObject.transform.position, Is.EqualTo(Vector3.zero));
Assert.That(
motionDirector.GetGenericBinding(cameraTrack),
Is.Null);
Assert.That(authoredVirtualCamera.enabled, Is.False);
}
finally
{
Undo.ClearAll();
Object.DestroyImmediate(destinationObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(undoSentinelObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(destinationTimeline);
}
}
[Test]
public void SuppressionRebuildsPlayingDirectorAndReleasesBrainOverride()
{
var sourceObject = new GameObject("Source Director");
var motionObject = new GameObject("Motion Director");
var authoredCameraObject = new GameObject("Authored Virtual Camera");
var outputCameraObject = new GameObject("Output Camera");
motionObject.transform.SetParent(sourceObject.transform, false);
authoredCameraObject.transform.SetParent(
motionObject.transform,
false);
var sourceTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
var motionTimeline = ScriptableObject.CreateInstance<TimelineAsset>();
PlayableDirector motionDirector = null;
try
{
var sourceDirector =
sourceObject.AddComponent<PlayableDirector>();
sourceDirector.playOnAwake = false;
sourceDirector.playableAsset = sourceTimeline;
motionDirector = motionObject.AddComponent<PlayableDirector>();
motionDirector.playOnAwake = false;
motionDirector.timeUpdateMode = DirectorUpdateMode.Manual;
motionDirector.playableAsset = motionTimeline;
var authoredCamera =
authoredCameraObject.AddComponent<CinemachineCamera>();
outputCameraObject.AddComponent<Camera>();
var brain = outputCameraObject.AddComponent<CinemachineBrain>();
brain.UpdateMethod = CinemachineBrain.UpdateMethods.ManualUpdate;
var track = motionTimeline.CreateTrack<CinemachineTrack>(
null,
"Authored Camera Track");
var clip = track.CreateDefaultClip();
clip.start = 0d;
clip.duration = 1d;
var shot = clip.asset as CinemachineShot;
Assert.That(shot, Is.Not.Null);
var exposedName = new PropertyName("AuthoredCameraForSuppressionTest");
shot.VirtualCamera.exposedName = exposedName;
shot.VirtualCamera.defaultValue = authoredCamera;
motionDirector.SetReferenceValue(exposedName, authoredCamera);
motionDirector.SetGenericBinding(track, brain);
motionDirector.Play();
motionDirector.time = 0.25d;
motionDirector.Evaluate();
brain.ManualUpdate(100, 1f / 60f);
Assert.That(motionDirector.playableGraph.IsValid(), Is.True);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(brain.ActiveVirtualCamera, Is.SameAs(authoredCamera));
var suppressionState = AICameraTimelinePreviewImporter
.CaptureAuthoredCinemachineState(sourceDirector);
AICameraTimelinePreviewImporter
.SuppressAuthoredCinemachineOutputsForEditor(sourceDirector);
brain.ManualUpdate(101, 1f / 60f);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(motionDirector.time, Is.EqualTo(0.25d).Within(0.000001d));
Assert.That(motionDirector.GetGenericBinding(track), Is.Null);
Assert.That(authoredCamera.enabled, Is.False);
Assert.That(brain.ActiveVirtualCamera, Is.Not.SameAs(authoredCamera));
AICameraTimelinePreviewImporter.RestoreAuthoredCinemachineState(
suppressionState);
brain.ManualUpdate(102, 1f / 60f);
Assert.That(motionDirector.state, Is.EqualTo(PlayState.Playing));
Assert.That(motionDirector.time, Is.EqualTo(0.25d).Within(0.000001d));
Assert.That(
motionDirector.GetGenericBinding(track),
Is.SameAs(brain));
Assert.That(authoredCamera.enabled, Is.True);
Assert.That(brain.ActiveVirtualCamera, Is.SameAs(authoredCamera));
}
finally
{
if (motionDirector != null)
{
motionDirector.Stop();
}
Object.DestroyImmediate(sourceObject);
Object.DestroyImmediate(outputCameraObject);
Object.DestroyImmediate(motionTimeline);
Object.DestroyImmediate(sourceTimeline);
}
}
[TestCase(false)]
[TestCase(true)]
public void GeneratedDirectorySupportsLegacyAndDeclaredHashes(
@@ -778,6 +1632,18 @@ namespace Streamingle.Editor.Tests
}
}
private static TimelineAsset CreateTimelineBoundToBrain(
PlayableDirector director,
CinemachineBrain brain,
string trackName)
{
var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
var track = timeline.CreateTrack<CinemachineTrack>(null, trackName);
director.playableAsset = timeline;
director.SetGenericBinding(track, brain);
return timeline;
}
private static string CreateGeneratedDirectory(bool includeHashes)
{
var directory = Path.Combine(