Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CMCL-0000: CameraDeactivated events were not sent when a blend interrupted another #1028

Merged
merged 6 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions com.unity.cinemachine/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 8 additions & 6 deletions com.unity.cinemachine/Runtime/Core/BlendManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ public bool IsLiveInBlend(ICinemachineCamera cam)

/// <summary>
/// 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.
/// </summary>
public void ComputeCurrentBlend()
{
Expand All @@ -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]);
Expand All @@ -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);
}
Expand Down Expand Up @@ -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<ICinemachineCamera> cams)
Expand All @@ -191,6 +191,8 @@ static void CollectLiveCameras(CinemachineBlend blend, ref List<ICinemachineCame
else if (blend.CamB != null)
cams.Add(blend.CamB);
}

return incomingCamera;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt)

void OnBlendCreated(CinemachineCore.BlendEventParams evt)
{
if (evt.Blend.CamA == (ICinemachineCamera)EventTarget || evt.Blend.CamB == (ICinemachineCamera)EventTarget)
if (evt.Blend.CamB == (ICinemachineCamera)EventTarget)
BlendCreatedEvent.Invoke(evt);
}

Expand Down
80 changes: 73 additions & 7 deletions com.unity.cinemachine/Tests/Editor/BlendManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,21 +58,26 @@ [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);
}

[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);
Expand Down Expand Up @@ -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 time has elapsed, 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);
}
}
}
2 changes: 1 addition & 1 deletion com.unity.cinemachine/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
Loading