release(camera-ai): publish 0.4.14 importer hotfix
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user