Valentine Notes Dlivery game flow changes and improvements.

This commit is contained in:
Michal Pikulski
2025-12-17 00:55:47 +01:00
parent 6133caec53
commit c6d2ca4e5c
11 changed files with 1095 additions and 412 deletions

View File

@@ -49,6 +49,10 @@ namespace Common.Camera
[Tooltip("Priority for the active camera")]
[SerializeField] protected int activePriority = 20;
[Header("Cinemachine Brain")]
[Tooltip("CinemachineBrain for blend detection (auto-finds if null)")]
[SerializeField] protected CinemachineBrain cinemachineBrain;
[Header("Debug")]
[SerializeField] protected bool showDebugLogs = false;
@@ -60,6 +64,11 @@ namespace Common.Camera
private TState _currentState;
private bool _isInitialized = false;
// Event-driven blend tracking
private CinemachineCamera _pendingBlendTarget;
private bool _isBlendComplete;
private Action _pendingBlendCallback;
public TState CurrentState => _currentState;
#endregion
@@ -71,6 +80,11 @@ namespace Common.Camera
/// </summary>
public event Action<TState, TState> OnStateChanged;
/// <summary>
/// Fired when camera blend completes after state switch
/// </summary>
public event Action OnBlendComplete;
#endregion
#region Lifecycle
@@ -84,6 +98,17 @@ namespace Common.Camera
{
base.OnManagedAwake();
// Auto-find CinemachineBrain if not assigned
if (cinemachineBrain == null)
{
cinemachineBrain = UnityEngine.Camera.main?.GetComponent<CinemachineBrain>();
if (cinemachineBrain == null && showDebugLogs)
{
Logging.Warning($"[{GetType().Name}] CinemachineBrain not found. Blend tracking will be unavailable.");
}
}
// Initialize cameras from Inspector mappings
InitializeCameraMap();
@@ -91,6 +116,35 @@ namespace Common.Camera
ValidateCameras();
}
/// <summary>
/// Subscribe to Cinemachine events on enable
/// </summary>
private void OnEnable()
{
// Subscribe to Cinemachine global events
CinemachineCore.BlendFinishedEvent.AddListener(OnBlendFinished);
CinemachineCore.CameraActivatedEvent.AddListener(OnCameraActivated);
if (showDebugLogs)
Logging.Debug($"[{GetType().Name}] Subscribed to Cinemachine events");
}
/// <summary>
/// Unsubscribe from Cinemachine events on disable
/// </summary>
private void OnDisable()
{
// Unsubscribe from Cinemachine events to prevent memory leaks
CinemachineCore.BlendFinishedEvent.RemoveListener(OnBlendFinished);
CinemachineCore.CameraActivatedEvent.RemoveListener(OnCameraActivated);
// Clear any pending callbacks
_pendingBlendCallback = null;
if (showDebugLogs)
Logging.Debug($"[{GetType().Name}] Unsubscribed from Cinemachine events");
}
#endregion
#region Initialization
@@ -229,6 +283,115 @@ namespace Common.Camera
return _cameraMap.ContainsKey(state);
}
/// <summary>
/// Blend to a state and wait asynchronously (coroutine).
/// Yields until Cinemachine blend event fires. Event-driven, no polling.
/// Use this when you need to wait for the blend to complete before continuing.
/// </summary>
public System.Collections.IEnumerator BlendToStateAsync(TState newState)
{
// Reset completion flag
_isBlendComplete = false;
// Set pending target camera
if (!_cameraMap.TryGetValue(newState, out _pendingBlendTarget))
{
Logging.Error($"[{GetType().Name}] No camera for state {newState}");
yield break;
}
// Switch camera state (triggers blend)
SwitchToState(newState);
// Fallback: if no brain, complete immediately
if (cinemachineBrain == null)
{
if (showDebugLogs)
Logging.Warning($"[{GetType().Name}] No brain, completing blend immediately");
yield break;
}
// Wait for event to fire (event handlers set _isBlendComplete = true)
yield return new WaitUntil(() => _isBlendComplete);
if (showDebugLogs)
Logging.Debug($"[{GetType().Name}] Blend to {newState} completed (event-driven)");
}
/// <summary>
/// Blend to a state with callback invoked on completion.
/// Callback fires when Cinemachine blend event fires. Event-driven, no polling.
/// Use this when you want to perform an action after the blend completes.
/// </summary>
public void BlendToState(TState newState, Action onComplete)
{
// Set pending target camera
if (!_cameraMap.TryGetValue(newState, out _pendingBlendTarget))
{
Logging.Error($"[{GetType().Name}] No camera for state {newState}");
onComplete?.Invoke(); // Still invoke to prevent hanging
return;
}
// Store callback
_pendingBlendCallback = onComplete;
// Switch camera state (triggers blend)
SwitchToState(newState);
// Fallback: if no brain, invoke callback immediately
if (cinemachineBrain == null)
{
if (showDebugLogs)
Logging.Warning($"[{GetType().Name}] No brain, invoking callback immediately");
CompleteBlend();
}
// Event handlers will invoke callback when blend finishes
}
#endregion
#region Event Handlers
/// <summary>
/// Called when Cinemachine finishes a blend (non-zero length blends)
/// </summary>
private void OnBlendFinished(ICinemachineMixer mixer, ICinemachineCamera cam)
{
// Filter: only respond to blends from our brain to our expected camera
if (mixer == cinemachineBrain && cam == _pendingBlendTarget)
{
CompleteBlend();
}
}
/// <summary>
/// Called when Cinemachine activates a camera (handles instant cuts)
/// </summary>
private void OnCameraActivated(ICinemachineCamera.ActivationEventParams evt)
{
// Filter: only respond to cuts from our brain to our expected camera
if (evt.Origin == cinemachineBrain && evt.IncomingCamera == _pendingBlendTarget && evt.IsCut)
{
CompleteBlend();
}
}
/// <summary>
/// Mark blend as complete and fire callbacks
/// </summary>
private void CompleteBlend()
{
_isBlendComplete = true;
_pendingBlendCallback?.Invoke();
_pendingBlendCallback = null;
OnBlendComplete?.Invoke();
if (showDebugLogs)
Logging.Debug($"[{GetType().Name}] Blend completed, callbacks invoked");
}
#endregion
#region Validation