Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions com.unity.cinemachine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 blend cancellation not taking into account updates to "cancelled" camera state

### Changed
- Added support for fast enter play mode.

Expand Down
49 changes: 33 additions & 16 deletions com.unity.cinemachine/Runtime/Core/CameraBlendStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ public void UpdateRootFrame(
// to cancel out the progress made in the opposite direction
if (backingOutOfBlend)
{
snapshot = true; // always use a snapshot for this to prevent pops
duration = duration * normalizedBlendPosition; // skip first part of blend
normalizedBlendPosition = 1 - normalizedBlendPosition;
}
Expand All @@ -306,7 +305,11 @@ public void UpdateRootFrame(
{
var blendCopy = new CinemachineBlend();
blendCopy.CopyFrom(frame.Blend);
camA = new NestedBlendSource(blendCopy);

if (backingOutOfBlend)
camA = new FrozenBlendSource(blendCopy);
else
camA = new NestedBlendSource(blendCopy);
}
}
// For the event, we use the raw outgoing camera, not the actual blend source
Expand All @@ -328,27 +331,31 @@ public void UpdateRootFrame(
}

// Advance the working blend
if (AdvanceBlend(frame.Blend, deltaTime))
if (AdvanceBlend(frame, deltaTime))
frame.Source.ClearBlend();
frame.UpdateCameraState(up, deltaTime);

// local function
static bool AdvanceBlend(CinemachineBlend blend, float deltaTime)
static bool AdvanceBlend(NestedBlendSource source, float deltaTime)
{
bool blendCompleted = false;
if (blend.CamA != null)
{
var blend = source.Blend;
if (blend.CamA == null)
return false;

if (source is not FrozenBlendSource)
blend.TimeInBlend += (deltaTime >= 0) ? deltaTime : blend.Duration;
if (blend.IsComplete)
{
// No more blend
blend.ClearBlend();
blendCompleted = true;
}
else if (blend.CamA is NestedBlendSource bs)
AdvanceBlend(bs.Blend, deltaTime);

if (blend.IsComplete)
{
// No more blend
blend.ClearBlend();
return true;
}
return blendCompleted;

if (blend.CamA is NestedBlendSource bs)
AdvanceBlend(bs, deltaTime);

return false;
}
}

Expand Down Expand Up @@ -471,5 +478,15 @@ public void TakeSnapshot(ICinemachineCamera source)
m_Name ??= source == null ? "(null)" : "*" + source.Name;
}
}

/// <summary>
/// Source for a blend where the time is frozen. Used for cancelling blends where we want
/// the source of the cancelling blend to be at the point in time when the blend was cancelled,
/// but still update the undelying sources in case they move.
/// </summary>
class FrozenBlendSource : NestedBlendSource
{
public FrozenBlendSource(CinemachineBlend blend) : base(blend) {}
}
}
}
29 changes: 29 additions & 0 deletions com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,5 +279,34 @@ public void TestBlendReversal()
Assert.That(m_BlendManager.IsBlending, Is.False);
Assert.AreEqual(1.0f, m_BlendManager.CameraState.RawPosition.x, 0.001f);
}

[Test]
public void TestBlendCancellationKeepsOutgoingCameraUpdating()
{
void MoveCameras() {
m_Cam1.Position += Vector3.forward;
m_Cam2.Position += Vector3.forward;
}

Reset(1); // constant blend time of 1
m_Cam1.Position = new Vector3(0, 0, 0);
m_Cam2.Position = new Vector3(1, 0, 0);

// Start with cam1, then blend towards cam2.
ProcessFrame(m_Cam1, 0.1f);
MoveCameras();
ProcessFrame(m_Cam2, 0.5f);
MoveCameras();
Assume.That(m_BlendManager.IsBlending, Is.True);

// Cancel/reverse the blend back to cam1 and advance a couple of frames.
ProcessFrame(m_Cam1, 0.1f);
MoveCameras();
ProcessFrame(m_Cam1, 0.1f);
Assume.That(m_BlendManager.IsBlending, Is.True);

// Z coordinate should be the same for both cameras and blend result
Assert.AreEqual(m_Cam1.Position.z, m_BlendManager.CameraState.RawPosition.z, 0.001f);
}
}
}