diff --git a/com.unity.cinemachine/CHANGELOG.md b/com.unity.cinemachine/CHANGELOG.md index 1e3a55d21..a891ed7fa 100644 --- a/com.unity.cinemachine/CHANGELOG.md +++ b/com.unity.cinemachine/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [3.1.7-pre.1] - 2026-02-24 +### Bugfixes +- Fixed stack frame snapshot not resetting when CameraBlendStack completed blend. + ### Changed - Added support for fast enter play mode. diff --git a/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs b/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs index a6431f861..9fa7c3b53 100644 --- a/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs +++ b/com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs @@ -83,10 +83,8 @@ public ICinemachineCamera GetSnapshotIfAppropriate(ICinemachineCamera cam, float { if (cam == null || (cam.State.BlendHint & CameraState.BlendHints.FreezeWhenBlendingOut) == 0) { - // No snapshot required - reset it - m_Snapshot.TakeSnapshot(null); - m_SnapshotSource = null; - m_SnapshotBlendWeight = 0; + // No snapshot required - clear it + ClearSnapshot(); return cam; } // A snapshot is needed @@ -101,6 +99,13 @@ public ICinemachineCamera GetSnapshotIfAppropriate(ICinemachineCamera cam, float // Use the most recent snapshot return m_Snapshot; } + + public void ClearSnapshot() + { + m_Snapshot.TakeSnapshot(null); + m_SnapshotSource = null; + m_SnapshotBlendWeight = 0; + } } // Current game state is always frame 0, overrides are subsequent frames @@ -213,6 +218,7 @@ public void ResetRootFrame() frame.Blend.CamB = null; frame.Source.ClearBlend(); frame.Source.CamB = null; + frame.ClearSnapshot(); } } @@ -329,7 +335,10 @@ public void UpdateRootFrame( // Advance the working blend if (AdvanceBlend(frame.Blend, deltaTime)) + { frame.Source.ClearBlend(); + frame.ClearSnapshot(); + } frame.UpdateCameraState(up, deltaTime); // local function diff --git a/com.unity.cinemachine/Tests/Runtime/CamerasBlendingTests.cs b/com.unity.cinemachine/Tests/Runtime/CamerasBlendingTests.cs index 666c36882..9b2834972 100644 --- a/com.unity.cinemachine/Tests/Runtime/CamerasBlendingTests.cs +++ b/com.unity.cinemachine/Tests/Runtime/CamerasBlendingTests.cs @@ -210,5 +210,48 @@ public IEnumerator SetActiveBlend() yield return UpdateCinemachine(); Assert.That(m_Brain.ActiveBlend == null); } + + [UnityTest] + [Description("Regression test for UUM-131151 tests that blend snapshots are reset correctly when blending is interrupted")] + public IEnumerator BlendClearsSnapshotWhenBlendingIsInterrupted() + { + // We test cancelling a blend, changing the source camera position and then restarting the blend + // The camera must be in FreezeWhenBlendingOut mode + // We expect the blend to start from the new position + + ((CinemachineCamera)m_Source).BlendHint |= CinemachineCore.BlendHints.FreezeWhenBlendingOut; + + yield return UpdateCinemachine(); + Assume.That(ReferenceEquals(m_Brain.ActiveVirtualCamera, m_Source)); + Assume.That(m_Brain.transform.position, Is.EqualTo(m_Source.transform.position)); + + // Start blend to target + m_Target.enabled = true; + yield return UpdateCinemachine(); + Assume.That(m_Brain.IsBlending, Is.True); + + yield return UpdateCinemachine(); + + // Cancel, blend back to source + m_Target.enabled = false; + yield return UpdateCinemachine(); + Assume.That(m_Brain.IsBlending, Is.True); + + while (m_Brain.IsBlending) + yield return UpdateCinemachine(); + + Assume.That(ReferenceEquals(m_Brain.ActiveVirtualCamera, m_Source)); + Assume.That(m_Brain.transform.position, Is.EqualTo(m_Source.transform.position)); + + var newPos = new Vector3(0, 5, 0); + m_Source.transform.position = newPos; + + // Restart blend to target + m_Target.enabled = true; + yield return UpdateCinemachine(); + + // Make sure we started from 0, 5, 0 and not 10, 0, 0 + Assert.That(m_Brain.transform.position.y, Is.GreaterThan(4.0f)); + } } } \ No newline at end of file