From fd12c89ca68e0a772691a3e205472a3064ce17ec Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Wed, 20 Nov 2024 18:05:25 -0500 Subject: [PATCH 1/5] CameraDeactivated events were not sent when a blend interrupted another blend --- com.unity.cinemachine/CHANGELOG.md | 6 + .../Runtime/Core/BlendManager.cs | 14 ++- .../Helpers/CinemachineCameraEvents.cs | 2 +- .../Tests/Runtime/EventsTests.cs | 106 ++++++++++++++++++ .../Tests/Runtime/EventsTests.cs.meta | 11 ++ com.unity.cinemachine/package.json | 2 +- 6 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 com.unity.cinemachine/Tests/Runtime/EventsTests.cs create mode 100644 com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index 7f4690691..b50061a2c 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [3.1.3] - 2025-12-31 + +### Bugfixes +- CameraDeactivated events were not sent consistently when a blend interrupted another blend before completion. + + ## [3.1.2] - 2024-10-01 ### Added diff --git a/com.unity.cinemachine/Runtime/Core/BlendManager.cs b/com.unity.cinemachine/Runtime/Core/BlendManager.cs index 2f2c0210d..3bdc58cc5 100644 --- a/com.unity.cinemachine/Runtime/Core/BlendManager.cs +++ b/com.unity.cinemachine/Runtime/Core/BlendManager.cs @@ -102,8 +102,7 @@ public bool IsLiveInBlend(ICinemachineCamera cam) /// /// Compute the current blend, taking into account - /// the in-game camera and all the active overrides. Caller may optionally - /// exclude n topmost overrides. + /// the in-game camera and all the active overrides. /// public void ComputeCurrentBlend() { @@ -129,8 +128,6 @@ public void RefreshCurrentCameraState(Vector3 up, float deltaTime) public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 up, float deltaTime) { // Send deactivation events - m_CameraCache.Clear(); - CollectLiveCameras(m_PreviousLiveCameras, ref m_CameraCache); for (int i = 0; i < m_CameraCache.Count; ++i) if (!IsLive(m_CameraCache[i])) CinemachineCore.CameraDeactivatedEvent.Invoke(mixer, m_CameraCache[i]); @@ -146,7 +143,7 @@ public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 u if (incomingCamera == outgoingCamera) { - // Send a blend completeed event if appropriate + // Send a blend completed event if appropriate if (m_PreviousLiveCameras.CamA != null && m_CurrentLiveCameras.CamA == null) CinemachineCore.BlendFinishedEvent.Invoke(mixer, incomingCamera); } @@ -176,7 +173,10 @@ public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 u incomingCamera.UpdateCameraState(up, deltaTime); } } - return incomingCamera; + + // Collect cameras that are live this frame, for processing next frame + m_CameraCache.Clear(); + CollectLiveCameras(m_CurrentLiveCameras, ref m_CameraCache); // local method - find all the live cameras in a blend static void CollectLiveCameras(CinemachineBlend blend, ref List cams) @@ -191,6 +191,8 @@ static void CollectLiveCameras(CinemachineBlend blend, ref List m_ActivatedCount = new (); + Dictionary m_DeactivatedCount = new (); + Dictionary m_BlendCreatedCount = new (); + Dictionary m_BlendFinishedCount = new (); + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_Brain.DefaultBlend = new CinemachineBlendDefinition(CinemachineBlendDefinition.Styles.Linear, k_BlendingTime); + + m_ActivatedCount.Clear(); + m_DeactivatedCount.Clear(); + m_BlendCreatedCount.Clear(); + m_BlendFinishedCount.Clear(); + + A = SetupCamera("A", 30); + B = SetupCamera("B", 20); + C = SetupCamera("C", 10); + + CinemachineCamera SetupCamera(string name, int priority) + { + var cam = CreateGameObject(name, typeof(CinemachineCamera)).GetComponent(); + cam.Priority = priority; + + var events = cam.gameObject.AddComponent(); + m_ActivatedCount.Add(name, 0); + events.CameraActivatedEvent.AddListener((m, c) => CountEvent(m_ActivatedCount, c.Name, name)); + + m_DeactivatedCount.Add(name, 0); + events.CameraDeactivatedEvent.AddListener((m, c) => CountEvent(m_DeactivatedCount, c.Name, name)); + + m_BlendCreatedCount.Add(name, 0); + events.BlendCreatedEvent.AddListener((b) => CountEvent(m_BlendCreatedCount, b.Blend.CamB.Name, name)); + + m_BlendFinishedCount.Add(name, 0); + events.BlendFinishedEvent.AddListener((m, c) => CountEvent(m_BlendFinishedCount, c.Name, name)); + + return cam; + } + + void CountEvent(Dictionary dic, string name, string expectedName) + { + dic[name] += 1; + Assert.AreEqual(expectedName, name); // make sure we get events only for the relevant camera + } + } + + [UnityTest] + public IEnumerator GetBlendEvents() + { + // Check that A is active + yield return UpdateCinemachine(); + Assert.That(ReferenceEquals(m_Brain.ActiveVirtualCamera, A)); + Assert.AreEqual(m_ActivatedCount["A"], 1); + Assert.AreEqual(m_ActivatedCount["B"], 0); + Assert.AreEqual(m_ActivatedCount["C"], 0); + + // Activate B + yield return WaitForSeconds(k_BlendingTime / 4); + B.Priority = 100; + yield return WaitForSeconds(k_BlendingTime / 4); + Assert.AreEqual(m_ActivatedCount["B"], 1); + Assert.AreEqual(m_BlendCreatedCount["B"], 1); + Assert.AreEqual(m_DeactivatedCount["A"], 0); // still blending + + // Activate C before blend is finished - interrupting the first blend + C.Priority = 200; + yield return WaitForSeconds(k_BlendingTime / 4); + Assert.AreEqual(m_ActivatedCount["C"], 1); + Assert.AreEqual(m_BlendCreatedCount["C"], 1); + Assert.AreEqual(m_DeactivatedCount["A"], 0); + Assert.AreEqual(m_DeactivatedCount["B"], 0); + + // Wait until first blend is finished, but not the second + yield return WaitForSeconds(k_BlendingTime / 2); + Assert.AreEqual(m_DeactivatedCount["A"], 1); + Assert.AreEqual(m_DeactivatedCount["B"], 0); + Assert.AreEqual(m_BlendFinishedCount["B"], 0); // blend was interrupted, so never finishes + + // Wait until second blend is finished + yield return WaitForSeconds(k_BlendingTime); + Assert.AreEqual(m_DeactivatedCount["A"], 1); + Assert.AreEqual(m_DeactivatedCount["B"], 1); + Assert.AreEqual(m_DeactivatedCount["C"], 0); + Assert.AreEqual(m_BlendFinishedCount["A"], 0); + Assert.AreEqual(m_BlendFinishedCount["B"], 0); + Assert.AreEqual(m_BlendFinishedCount["C"], 1); // this blend is allowed to finish + } + } +} diff --git a/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta b/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta new file mode 100644 index 000000000..f720e1495 --- /dev/null +++ b/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f7fa8b7aa8be7643902d389a8c31ecb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.cinemachine/package.json b/com.unity.cinemachine/package.json index 3e65a92dd..75719c36a 100644 --- a/com.unity.cinemachine/package.json +++ b/com.unity.cinemachine/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.cinemachine", "displayName": "Cinemachine", - "version": "3.1.2", + "version": "3.1.3", "unity": "2022.3", "description": "Smart camera tools for passionate creators. \n\nCinemachine 3 is a newer and better version of Cinemachine, but upgrading an existing project from 2.X will likely require some effort. If you're considering upgrading an older project, please see our upgrade guide in the user manual.", "keywords": [ From e4246bccfd0b788a51c16bc786bd6174e001c40f Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Thu, 21 Nov 2024 08:10:38 -0500 Subject: [PATCH 2/5] fix test --- .../Tests/Editor/BlendManagerTests.cs | 80 +++++++++++-- .../Tests/Runtime/EventsTests.cs | 106 ------------------ .../Tests/Runtime/EventsTests.cs.meta | 11 -- 3 files changed, 73 insertions(+), 124 deletions(-) delete mode 100644 com.unity.cinemachine/Tests/Runtime/EventsTests.cs delete mode 100644 com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta diff --git a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs index d0ea39e58..f7856e9e2 100644 --- a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs +++ b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs @@ -29,6 +29,7 @@ public FakeMixer(string name) : base(name) {} FakeMixer m_Mixer = new ("Mixer"); FakeCamera m_Cam1 = new ("Cam1"); FakeCamera m_Cam2 = new ("Cam2"); + FakeCamera m_Cam3 = new ("Cam3"); int m_ActivatedEventCount; int m_DeactivatedEventCount; @@ -57,9 +58,18 @@ [TearDown] public void TearDown() void ResetCounters() => m_ActivatedEventCount = m_DeactivatedEventCount = m_BlendCreatedCount = m_BlendFinishedCount = 0; - void ProcessFrame(ICinemachineCamera cam, float deltaTime) + void Reset(float blendTime) { - m_BlendManager.UpdateRootFrame(m_Mixer, cam, Vector3.up, deltaTime); + m_BlendManager.LookupBlendDelegate = (outgoing, incoming) + => new (CinemachineBlendDefinition.Styles.EaseInOut, blendTime); // constant blend time + m_BlendManager.ResetRootFrame(); + ProcessFrame(null, 0.1f); + ResetCounters(); + } + + void ProcessFrame(ICinemachineCamera activeCam, float deltaTime) + { + m_BlendManager.UpdateRootFrame(m_Mixer, activeCam, Vector3.up, deltaTime); m_BlendManager.ComputeCurrentBlend(); m_BlendManager.ProcessActiveCamera(m_Mixer, Vector3.up, deltaTime); } @@ -67,11 +77,7 @@ void ProcessFrame(ICinemachineCamera cam, float deltaTime) [Test] public void TestEvents() { - m_BlendManager.LookupBlendDelegate = (outgoing, incoming) - => new (CinemachineBlendDefinition.Styles.EaseInOut, 1); // constant blend time of 1 - - ResetCounters(); - m_BlendManager.ResetRootFrame(); + Reset(1); // constant blend time of 1 // We should get an initial activation event, no blend ProcessFrame(m_Cam1, 0.1f); @@ -110,5 +116,65 @@ public void TestEvents() Assert.AreEqual(1, m_BlendFinishedCount); Assert.That(m_BlendManager.IsBlending, Is.False); } + + [Test] + public void TestEventsNestedBlend() + { + Reset(1); // constant blend time of 1 + + // We should get an initial activation event, no blend + ProcessFrame(m_Cam1, 0.1f); + Assert.AreEqual(1, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.AreEqual(0, m_BlendCreatedCount); + Assert.That(m_BlendManager.IsBlending, Is.False); + + ProcessFrame(m_Cam1, 0.1f); + Assert.AreEqual(1, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(0, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.False); + + // Activate new camera, blend will take 1 sec + ProcessFrame(m_Cam2, 0.1f); + Assert.AreEqual(2, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(1, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.True); + + ProcessFrame(m_Cam2, 0.5f); + Assert.AreEqual(2, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(1, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.True); + + // Acivate new cam before old blend is finished + ProcessFrame(m_Cam3, 0.1f); + Assert.AreEqual(3, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(2, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.True); + + // After first blend is finished, check the counters + ProcessFrame(m_Cam3, 0.5f); + Assert.AreEqual(3, m_ActivatedEventCount); + Assert.AreEqual(1, m_DeactivatedEventCount); + Assert.AreEqual(2, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); // blend was interrupted, never finished + Assert.That(m_BlendManager.IsBlending, Is.True); + + // After second blend is finished, check the counters + ProcessFrame(m_Cam3, 0.5f); + Assert.AreEqual(3, m_ActivatedEventCount); + Assert.AreEqual(2, m_DeactivatedEventCount); + Assert.AreEqual(2, m_BlendCreatedCount); + Assert.AreEqual(1, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.False); + } } } \ No newline at end of file diff --git a/com.unity.cinemachine/Tests/Runtime/EventsTests.cs b/com.unity.cinemachine/Tests/Runtime/EventsTests.cs deleted file mode 100644 index 2261ff64d..000000000 --- a/com.unity.cinemachine/Tests/Runtime/EventsTests.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using NUnit.Framework; -using UnityEngine.TestTools; - -namespace Unity.Cinemachine.Tests -{ - [TestFixture] - public class EventsTests : CinemachineRuntimeTimeInvariantFixtureBase - { - const float k_BlendingTime = 2; - - CinemachineCamera A, B, C; - - Dictionary m_ActivatedCount = new (); - Dictionary m_DeactivatedCount = new (); - Dictionary m_BlendCreatedCount = new (); - Dictionary m_BlendFinishedCount = new (); - - [SetUp] - public override void SetUp() - { - base.SetUp(); - - m_Brain.DefaultBlend = new CinemachineBlendDefinition(CinemachineBlendDefinition.Styles.Linear, k_BlendingTime); - - m_ActivatedCount.Clear(); - m_DeactivatedCount.Clear(); - m_BlendCreatedCount.Clear(); - m_BlendFinishedCount.Clear(); - - A = SetupCamera("A", 30); - B = SetupCamera("B", 20); - C = SetupCamera("C", 10); - - CinemachineCamera SetupCamera(string name, int priority) - { - var cam = CreateGameObject(name, typeof(CinemachineCamera)).GetComponent(); - cam.Priority = priority; - - var events = cam.gameObject.AddComponent(); - m_ActivatedCount.Add(name, 0); - events.CameraActivatedEvent.AddListener((m, c) => CountEvent(m_ActivatedCount, c.Name, name)); - - m_DeactivatedCount.Add(name, 0); - events.CameraDeactivatedEvent.AddListener((m, c) => CountEvent(m_DeactivatedCount, c.Name, name)); - - m_BlendCreatedCount.Add(name, 0); - events.BlendCreatedEvent.AddListener((b) => CountEvent(m_BlendCreatedCount, b.Blend.CamB.Name, name)); - - m_BlendFinishedCount.Add(name, 0); - events.BlendFinishedEvent.AddListener((m, c) => CountEvent(m_BlendFinishedCount, c.Name, name)); - - return cam; - } - - void CountEvent(Dictionary dic, string name, string expectedName) - { - dic[name] += 1; - Assert.AreEqual(expectedName, name); // make sure we get events only for the relevant camera - } - } - - [UnityTest] - public IEnumerator GetBlendEvents() - { - // Check that A is active - yield return UpdateCinemachine(); - Assert.That(ReferenceEquals(m_Brain.ActiveVirtualCamera, A)); - Assert.AreEqual(m_ActivatedCount["A"], 1); - Assert.AreEqual(m_ActivatedCount["B"], 0); - Assert.AreEqual(m_ActivatedCount["C"], 0); - - // Activate B - yield return WaitForSeconds(k_BlendingTime / 4); - B.Priority = 100; - yield return WaitForSeconds(k_BlendingTime / 4); - Assert.AreEqual(m_ActivatedCount["B"], 1); - Assert.AreEqual(m_BlendCreatedCount["B"], 1); - Assert.AreEqual(m_DeactivatedCount["A"], 0); // still blending - - // Activate C before blend is finished - interrupting the first blend - C.Priority = 200; - yield return WaitForSeconds(k_BlendingTime / 4); - Assert.AreEqual(m_ActivatedCount["C"], 1); - Assert.AreEqual(m_BlendCreatedCount["C"], 1); - Assert.AreEqual(m_DeactivatedCount["A"], 0); - Assert.AreEqual(m_DeactivatedCount["B"], 0); - - // Wait until first blend is finished, but not the second - yield return WaitForSeconds(k_BlendingTime / 2); - Assert.AreEqual(m_DeactivatedCount["A"], 1); - Assert.AreEqual(m_DeactivatedCount["B"], 0); - Assert.AreEqual(m_BlendFinishedCount["B"], 0); // blend was interrupted, so never finishes - - // Wait until second blend is finished - yield return WaitForSeconds(k_BlendingTime); - Assert.AreEqual(m_DeactivatedCount["A"], 1); - Assert.AreEqual(m_DeactivatedCount["B"], 1); - Assert.AreEqual(m_DeactivatedCount["C"], 0); - Assert.AreEqual(m_BlendFinishedCount["A"], 0); - Assert.AreEqual(m_BlendFinishedCount["B"], 0); - Assert.AreEqual(m_BlendFinishedCount["C"], 1); // this blend is allowed to finish - } - } -} diff --git a/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta b/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta deleted file mode 100644 index f720e1495..000000000 --- a/com.unity.cinemachine/Tests/Runtime/EventsTests.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5f7fa8b7aa8be7643902d389a8c31ecb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 648fca23c4fb0fb850ecb778f5fe1a510d47d125 Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Thu, 21 Nov 2024 08:15:21 -0500 Subject: [PATCH 3/5] Update BlendManagerTests.cs --- com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs index f7856e9e2..f1fc66c14 100644 --- a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs +++ b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs @@ -160,7 +160,7 @@ public void TestEventsNestedBlend() Assert.AreEqual(0, m_BlendFinishedCount); Assert.That(m_BlendManager.IsBlending, Is.True); - // After first blend is finished, check the counters + // After first blend time has elapsed, check the counters ProcessFrame(m_Cam3, 0.5f); Assert.AreEqual(3, m_ActivatedEventCount); Assert.AreEqual(1, m_DeactivatedEventCount); From 78e709729cc6a93fa982ce097d79bd2eba021cad Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Thu, 21 Nov 2024 15:35:21 -0500 Subject: [PATCH 4/5] Revert "Use splines 2.7.1" This reverts commit 867c0a095715ad0e3a5917b3a08a6b7637ea23ac. --- com.unity.cinemachine/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/com.unity.cinemachine/package.json b/com.unity.cinemachine/package.json index 20ddaffd6..75719c36a 100644 --- a/com.unity.cinemachine/package.json +++ b/com.unity.cinemachine/package.json @@ -30,7 +30,7 @@ ], "category": "cinematography", "dependencies": { - "com.unity.splines": "2.7.1" + "com.unity.splines": "2.0.0" }, "samples": [ { From 2290aa41e35d0d95a1551636eec122c0ea36e0fc Mon Sep 17 00:00:00 2001 From: Gregory Labute Date: Wed, 11 Dec 2024 14:21:06 -0500 Subject: [PATCH 5/5] Fix additional missing activation events --- com.unity.cinemachine/CHANGELOG.md | 1 + .../Runtime/Core/BlendManager.cs | 41 ++++++++++----- .../Runtime/Core/CameraBlendStack.cs | 4 +- .../Tests/Editor/BlendManagerTests.cs | 50 ++++++++++++++++++- 4 files changed, 80 insertions(+), 16 deletions(-) diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index b50061a2c..26a8e43b6 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Bugfixes - CameraDeactivated events were not sent consistently when a blend interrupted another blend before completion. +- CameraActivated events were not sent consistently when activation was due to timeline blends. ## [3.1.2] - 2024-10-01 diff --git a/com.unity.cinemachine/Runtime/Core/BlendManager.cs b/com.unity.cinemachine/Runtime/Core/BlendManager.cs index 3bdc58cc5..f40541ae8 100644 --- a/com.unity.cinemachine/Runtime/Core/BlendManager.cs +++ b/com.unity.cinemachine/Runtime/Core/BlendManager.cs @@ -13,12 +13,20 @@ class BlendManager : CameraBlendStack // Current blend State - result of all frames. Blend camB is "current" camera always CinemachineBlend m_CurrentLiveCameras = new (); - // Blend state last frame, used for computing deltas - CinemachineBlend m_PreviousLiveCameras = new (); - // This is to control GC allocs when generating camera deactivated events - List m_CameraCache = new(); + HashSet m_PreviousLiveCameras = new(); + ICinemachineCamera m_PreviousActiveCamera; + bool m_WasBlending; + /// + public override void OnEnable() + { + base.OnEnable(); + m_PreviousLiveCameras.Clear(); + m_PreviousActiveCamera = null; + m_WasBlending = false; + } + /// Get the current active virtual camera. public ICinemachineCamera ActiveVirtualCamera => DeepCamBFromBlend(m_CurrentLiveCameras); @@ -106,7 +114,6 @@ public bool IsLiveInBlend(ICinemachineCamera cam) /// public void ComputeCurrentBlend() { - m_PreviousLiveCameras.CopyFrom(m_CurrentLiveCameras); ProcessOverrideFrames(ref m_CurrentLiveCameras, 0); } @@ -128,23 +135,29 @@ public void RefreshCurrentCameraState(Vector3 up, float deltaTime) public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 up, float deltaTime) { // Send deactivation events - for (int i = 0; i < m_CameraCache.Count; ++i) - if (!IsLive(m_CameraCache[i])) - CinemachineCore.CameraDeactivatedEvent.Invoke(mixer, m_CameraCache[i]); + using (var enumerator = m_PreviousLiveCameras.GetEnumerator()) + { + while (enumerator.MoveNext()) + { + var item = enumerator.Current; + if (!IsLive(item)) + CinemachineCore.CameraDeactivatedEvent.Invoke(mixer, item); + } + } // Process newly activated cameras var incomingCamera = ActiveVirtualCamera; if (incomingCamera != null && incomingCamera.IsValid) { // Has the current camera changed this frame? - var outgoingCamera = DeepCamBFromBlend(m_PreviousLiveCameras); + var outgoingCamera = m_PreviousActiveCamera; if (outgoingCamera != null && !outgoingCamera.IsValid) outgoingCamera = null; // object was deleted if (incomingCamera == outgoingCamera) { // Send a blend completed event if appropriate - if (m_PreviousLiveCameras.CamA != null && m_CurrentLiveCameras.CamA == null) + if (m_WasBlending && m_CurrentLiveCameras.CamA == null) CinemachineCore.BlendFinishedEvent.Invoke(mixer, incomingCamera); } else @@ -175,11 +188,13 @@ public ICinemachineCamera ProcessActiveCamera(ICinemachineMixer mixer, Vector3 u } // Collect cameras that are live this frame, for processing next frame - m_CameraCache.Clear(); - CollectLiveCameras(m_CurrentLiveCameras, ref m_CameraCache); + m_PreviousLiveCameras.Clear(); + CollectLiveCameras(m_CurrentLiveCameras, ref m_PreviousLiveCameras); + m_PreviousActiveCamera = DeepCamBFromBlend(m_CurrentLiveCameras); + m_WasBlending = m_CurrentLiveCameras.CamA != null; // local method - find all the live cameras in a blend - static void CollectLiveCameras(CinemachineBlend blend, ref List cams) + static void CollectLiveCameras(CinemachineBlend blend, ref HashSet cams) { if (blend.CamA is NestedBlendSource a && a.Blend != null) CollectLiveCameras(a.Blend, ref cams); diff --git a/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs b/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs index 39e3441f5..73a2ec59e 100644 --- a/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs +++ b/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs @@ -176,7 +176,7 @@ public void ReleaseCameraOverride(int overrideId) } /// Call this when object is enabled - public void OnEnable() + public virtual void OnEnable() { // Make sure there is a first stack frame m_FrameStack.Clear(); @@ -184,7 +184,7 @@ public void OnEnable() } /// Call this when object is disabled - public void OnDisable() + public virtual void OnDisable() { m_FrameStack.Clear(); m_NextFrameId = 0; diff --git a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs index f1fc66c14..22ef675cc 100644 --- a/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs +++ b/com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs @@ -62,7 +62,7 @@ void Reset(float blendTime) { m_BlendManager.LookupBlendDelegate = (outgoing, incoming) => new (CinemachineBlendDefinition.Styles.EaseInOut, blendTime); // constant blend time - m_BlendManager.ResetRootFrame(); + m_BlendManager.OnEnable(); ProcessFrame(null, 0.1f); ResetCounters(); } @@ -176,5 +176,53 @@ public void TestEventsNestedBlend() Assert.AreEqual(1, m_BlendFinishedCount); Assert.That(m_BlendManager.IsBlending, Is.False); } + + [Test] + public void TestEventsBlendToNestedBlend() + { + var customBlend = new NestedBlendSource(new CinemachineBlend() + { + CamA = m_Cam1, + CamB = m_Cam2, + BlendCurve = AnimationCurve.Linear(0, 0, 1, 1), + Duration = 1, + TimeInBlend = 0.1f + }); + + Reset(1); // constant blend time of 1 + + // We should get an initial activation event, no blend + ProcessFrame(m_Cam1, 0.1f); + Assert.AreEqual(1, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.AreEqual(0, m_BlendCreatedCount); + Assert.That(m_BlendManager.IsBlending, Is.False); + + // Activate nested blend camera, blend will take 1 sec + ProcessFrame(customBlend, 0.1f); + Assert.AreEqual(2, m_ActivatedEventCount); + Assert.AreEqual(0, m_DeactivatedEventCount); + Assert.AreEqual(1, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.True); + + // change camera in the custom blend - we expect activation and deactivation events + customBlend.Blend.CamB = m_Cam3; + ProcessFrame(customBlend, 0.1f); + Assert.AreEqual(3, m_ActivatedEventCount); + Assert.AreEqual(1, m_DeactivatedEventCount); + Assert.AreEqual(1, m_BlendCreatedCount); + Assert.AreEqual(0, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.True); + + customBlend.Blend.CamA = null; + ProcessFrame(customBlend, 1); + Assert.AreEqual(3, m_ActivatedEventCount); + Assert.AreEqual(2, m_DeactivatedEventCount); + Assert.AreEqual(1, m_BlendCreatedCount); + Assert.AreEqual(1, m_BlendFinishedCount); + Assert.That(m_BlendManager.IsBlending, Is.False); + } } } \ No newline at end of file