Play intro audio only once
This commit is contained in:
@@ -453761,6 +453761,10 @@ PrefabInstance:
|
|||||||
propertyPath: m_PlayOnAwake
|
propertyPath: m_PlayOnAwake
|
||||||
value: 0
|
value: 0
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
|
- target: {fileID: 8545106365577783398, guid: ead4e790fa3a1924ebd1586c93cd5479, type: 3}
|
||||||
|
propertyPath: isOneTime
|
||||||
|
value: 1
|
||||||
|
objectReference: {fileID: 0}
|
||||||
m_RemovedComponents: []
|
m_RemovedComponents: []
|
||||||
m_RemovedGameObjects: []
|
m_RemovedGameObjects: []
|
||||||
m_AddedGameObjects: []
|
m_AddedGameObjects: []
|
||||||
|
|||||||
@@ -469,6 +469,34 @@ namespace Core.Lifecycle
|
|||||||
LogDebug($"Restored scene data to {restoredCount} components");
|
LogDebug($"Restored scene data to {restoredCount} components");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Broadcasts scene restore completed event to all registered components.
|
||||||
|
/// Called AFTER all OnSceneRestoreRequested calls complete.
|
||||||
|
/// </summary>
|
||||||
|
public void BroadcastSceneRestoreCompleted()
|
||||||
|
{
|
||||||
|
LogDebug("Broadcasting SceneRestoreCompleted");
|
||||||
|
|
||||||
|
// Create a copy to avoid collection modification during iteration
|
||||||
|
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
|
||||||
|
|
||||||
|
foreach (var component in componentsCopy)
|
||||||
|
{
|
||||||
|
if (component == null) continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
component.InvokeSceneRestoreCompleted();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[LifecycleManager] Exception during scene restore completed for {component.SaveId}: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LogDebug("SceneRestoreCompleted broadcast complete");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Broadcasts global restore request to all registered components that opt-in.
|
/// Broadcasts global restore request to all registered components that opt-in.
|
||||||
/// Distributes serialized data to matching components by SaveId.
|
/// Distributes serialized data to matching components by SaveId.
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ namespace Core.Lifecycle
|
|||||||
public void InvokeSceneReady() => OnSceneReady();
|
public void InvokeSceneReady() => OnSceneReady();
|
||||||
public string InvokeSceneSaveRequested() => OnSceneSaveRequested();
|
public string InvokeSceneSaveRequested() => OnSceneSaveRequested();
|
||||||
public void InvokeSceneRestoreRequested(string data) => OnSceneRestoreRequested(data);
|
public void InvokeSceneRestoreRequested(string data) => OnSceneRestoreRequested(data);
|
||||||
|
public void InvokeSceneRestoreCompleted() => OnSceneRestoreCompleted();
|
||||||
public string InvokeGlobalSaveRequested() => OnGlobalSaveRequested();
|
public string InvokeGlobalSaveRequested() => OnGlobalSaveRequested();
|
||||||
public void InvokeGlobalRestoreRequested(string data) => OnGlobalRestoreRequested(data);
|
public void InvokeGlobalRestoreRequested(string data) => OnGlobalRestoreRequested(data);
|
||||||
public void InvokeManagedDestroy() => OnManagedDestroy();
|
public void InvokeManagedDestroy() => OnManagedDestroy();
|
||||||
@@ -202,6 +203,10 @@ namespace Core.Lifecycle
|
|||||||
/// Called during scene transitions to restore scene-specific state.
|
/// Called during scene transitions to restore scene-specific state.
|
||||||
/// Receives previously serialized data (from OnSceneSaveRequested).
|
/// Receives previously serialized data (from OnSceneSaveRequested).
|
||||||
///
|
///
|
||||||
|
/// IMPORTANT: This method MUST be synchronous. Do not use coroutines or async/await.
|
||||||
|
/// OnSceneRestoreCompleted is called immediately after all restore calls complete,
|
||||||
|
/// so any async operations would still be running when it fires.
|
||||||
|
///
|
||||||
/// TIMING:
|
/// TIMING:
|
||||||
/// - Called AFTER scene load, during OnSceneReady phase
|
/// - Called AFTER scene load, during OnSceneReady phase
|
||||||
/// - Frequency: Every scene transition
|
/// - Frequency: Every scene transition
|
||||||
@@ -212,6 +217,29 @@ namespace Core.Lifecycle
|
|||||||
// Default: no-op
|
// Default: no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called after all scene restore operations complete.
|
||||||
|
/// Does NOT receive data - use OnSceneRestoreRequested for that.
|
||||||
|
///
|
||||||
|
/// GUARANTEE:
|
||||||
|
/// - ALWAYS called after scene load, whether there's save data or not
|
||||||
|
/// - All OnSceneRestoreRequested() calls have RETURNED (but async operations may still be running)
|
||||||
|
/// - Safe for synchronous restore operations (JSON deserialization, setting fields, etc.)
|
||||||
|
///
|
||||||
|
/// TIMING:
|
||||||
|
/// - Called AFTER all OnSceneRestoreRequested calls complete (or immediately if no save data exists)
|
||||||
|
/// - Frequency: Every scene transition
|
||||||
|
/// - Use for: Post-restore initialization, first-time initialization, triggering events after state is restored
|
||||||
|
///
|
||||||
|
/// COMMON PATTERN:
|
||||||
|
/// Use this to perform actions that depend on whether data was restored or not.
|
||||||
|
/// Example: Play one-time audio only if it hasn't been played before (_hasPlayed == false).
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void OnSceneRestoreCompleted()
|
||||||
|
{
|
||||||
|
// Default: no-op
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Called once on game boot to restore global persistent state.
|
/// Called once on game boot to restore global persistent state.
|
||||||
/// Receives data that was saved via OnGlobalSaveRequested.
|
/// Receives data that was saved via OnGlobalSaveRequested.
|
||||||
|
|||||||
@@ -504,9 +504,19 @@ namespace Core.SaveLoad
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void RestoreSceneData()
|
public void RestoreSceneData()
|
||||||
{
|
{
|
||||||
|
if (Lifecycle.LifecycleManager.Instance == null)
|
||||||
|
{
|
||||||
|
Logging.Warning("[SaveLoadManager] LifecycleManager not available for scene restore");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (currentSaveData == null || currentSaveData.participantStates == null)
|
if (currentSaveData == null || currentSaveData.participantStates == null)
|
||||||
{
|
{
|
||||||
Logging.Debug("[SaveLoadManager] No scene data to restore");
|
Logging.Debug("[SaveLoadManager] No scene data to restore (first visit or no save data)");
|
||||||
|
|
||||||
|
// Still broadcast restore completed so components can initialize properly
|
||||||
|
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreCompleted();
|
||||||
|
Logging.Debug($"[SaveLoadManager] Scene restore completed (no data)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,11 +530,12 @@ namespace Core.SaveLoad
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Restore scene data via LifecycleManager
|
// Restore scene data via LifecycleManager
|
||||||
if (Lifecycle.LifecycleManager.Instance != null)
|
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreRequested(saveDataDict);
|
||||||
{
|
Logging.Debug($"[SaveLoadManager] Broadcast scene restore to LifecycleManager");
|
||||||
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreRequested(saveDataDict);
|
|
||||||
Logging.Debug($"[SaveLoadManager] Broadcast scene restore to LifecycleManager");
|
// Broadcast scene restore completed - ALWAYS called, whether there's data or not
|
||||||
}
|
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreCompleted();
|
||||||
|
Logging.Debug($"[SaveLoadManager] Scene restore completed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,22 +1,80 @@
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Audio;
|
using UnityEngine.Audio;
|
||||||
using System;
|
using System;
|
||||||
|
using Core.Lifecycle;
|
||||||
|
|
||||||
public class LevelAudioObject : MonoBehaviour
|
[Serializable]
|
||||||
|
public class LevelAudioObjectSaveData
|
||||||
{
|
{
|
||||||
|
public bool hasPlayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LevelAudioObject : ManagedBehaviour
|
||||||
|
{
|
||||||
|
[Header("Audio Settings")]
|
||||||
public AppleAudioSource narratorAudioSource;
|
public AppleAudioSource narratorAudioSource;
|
||||||
public AudioResource firstNarration;
|
public AudioResource firstNarration;
|
||||||
|
|
||||||
|
[Header("Playback Settings")]
|
||||||
|
[Tooltip("If true, the audio will only play once and never again after being played")]
|
||||||
|
public bool isOneTime;
|
||||||
|
|
||||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
private bool _hasPlayed;
|
||||||
void Start()
|
|
||||||
|
#region ManagedBehaviour Overrides
|
||||||
|
|
||||||
|
public override bool AutoRegisterForSave => isOneTime; // Only save if one-time audio
|
||||||
|
|
||||||
|
protected override string OnSceneSaveRequested()
|
||||||
{
|
{
|
||||||
PlayNarrationAudio();
|
if (!isOneTime)
|
||||||
|
return null; // No need to save if not one-time
|
||||||
|
|
||||||
|
LevelAudioObjectSaveData saveData = new LevelAudioObjectSaveData
|
||||||
|
{
|
||||||
|
hasPlayed = _hasPlayed
|
||||||
|
};
|
||||||
|
|
||||||
|
return JsonUtility.ToJson(saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayNarrationAudio()
|
protected override void OnSceneRestoreRequested(string serializedData)
|
||||||
{
|
{
|
||||||
|
if (!isOneTime || string.IsNullOrEmpty(serializedData))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LevelAudioObjectSaveData saveData = JsonUtility.FromJson<LevelAudioObjectSaveData>(serializedData);
|
||||||
|
_hasPlayed = saveData.hasPlayed;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[LevelAudioObject] Failed to restore audio state: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnSceneRestoreCompleted()
|
||||||
|
{
|
||||||
|
if (isOneTime && !_hasPlayed)
|
||||||
|
{
|
||||||
|
PlayNarrationAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
private void PlayNarrationAudio()
|
||||||
|
{
|
||||||
|
if (narratorAudioSource == null || firstNarration == null)
|
||||||
|
{
|
||||||
|
Debug.LogWarning($"[LevelAudioObject] Missing audio source or narration resource on {gameObject.name}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
narratorAudioSource.audioSource.resource = firstNarration;
|
narratorAudioSource.audioSource.resource = firstNarration;
|
||||||
narratorAudioSource.Play(0);
|
narratorAudioSource.Play(0);
|
||||||
|
|
||||||
|
_hasPlayed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user