This commit is contained in:
2025-11-10 12:19:25 +01:00
17 changed files with 10947 additions and 10085 deletions

View File

@@ -1185,6 +1185,7 @@ MonoBehaviour:
CinematicBackground: {fileID: 1256355336041814197} CinematicBackground: {fileID: 1256355336041814197}
eagleEye: {fileID: 8093509920149135307} eagleEye: {fileID: 8093509920149135307}
ramaSjangButton: {fileID: 4599222264323240281} ramaSjangButton: {fileID: 4599222264323240281}
scrabBookButton: {fileID: 2880351836456325619}
cinematicSprites: {fileID: 0} cinematicSprites: {fileID: 0}
cinematicBackgroundSprites: {fileID: 0} cinematicBackgroundSprites: {fileID: 0}
currentCinematicPlayer: {fileID: 0} currentCinematicPlayer: {fileID: 0}

View File

@@ -614,6 +614,7 @@ GameObject:
- component: {fileID: 768265498311662326} - component: {fileID: 768265498311662326}
- component: {fileID: 6338084184716153992} - component: {fileID: 6338084184716153992}
- component: {fileID: 253472492358066383} - component: {fileID: 253472492358066383}
- component: {fileID: 7628818949793551399}
m_Layer: 10 m_Layer: 10
m_Name: FakeChoco m_Name: FakeChoco
m_TagString: Untagged m_TagString: Untagged
@@ -633,7 +634,8 @@ Transform:
m_LocalPosition: {x: -6.784, y: -2.901, z: 0} m_LocalPosition: {x: -6.784, y: -2.901, z: 0}
m_LocalScale: {x: 0.7, y: 0.7, z: 0.7} m_LocalScale: {x: 0.7, y: 0.7, z: 0.7}
m_ConstrainProportionsScale: 1 m_ConstrainProportionsScale: 1
m_Children: [] m_Children:
- {fileID: 2205893234949555645}
m_Father: {fileID: 1509867968154593713} m_Father: {fileID: 1509867968154593713}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &6338084184716153992 --- !u!212 &6338084184716153992
@@ -740,6 +742,45 @@ BoxCollider2D:
m_AutoTiling: 0 m_AutoTiling: 0
m_Size: {x: 6, y: 6} m_Size: {x: 6, y: 6}
m_EdgeRadius: 0 m_EdgeRadius: 0
--- !u!114 &7628818949793551399
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2391935521422290070}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 833a4ccef651449e973e623d9107bef5, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Interactions.OneClickInteraction
isOneTime: 0
cooldown: -1
characterToInteract: 2
interactionStarted:
m_PersistentCalls:
m_Calls: []
interactionInterrupted:
m_PersistentCalls:
m_Calls: []
characterArrived:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 5762733430166618195}
m_TargetAssemblyTypeName: PicnicBehaviour, AppleHillsScripts
m_MethodName: triedToStealChocolate
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
interactionComplete:
m_PersistentCalls:
m_Calls: []
--- !u!1 &2728537141134591410 --- !u!1 &2728537141134591410
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1322,6 +1363,104 @@ GameObject:
m_CorrespondingSourceObject: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} m_CorrespondingSourceObject: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3}
m_PrefabInstance: {fileID: 3750141998400252915} m_PrefabInstance: {fileID: 3750141998400252915}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
--- !u!1001 &7365721869475958115
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 768265498311662326}
m_Modifications:
- target: {fileID: 2991221189157356317, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.x
value: 0.3
objectReference: {fileID: 0}
- target: {fileID: 2991221189157356317, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.y
value: 0.3
objectReference: {fileID: 0}
- target: {fileID: 2991221189157356317, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.z
value: 0.3
objectReference: {fileID: 0}
- target: {fileID: 8506461915049351794, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_Name
value: HighlightEffect
objectReference: {fileID: 0}
- target: {fileID: 8506461915049351794, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_IsActive
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalScale.z
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalRotation.x
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalRotation.y
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalRotation.z
value: -0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_ConstrainProportionsScale
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8998003315986923927, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
propertyPath: m_SortingOrder
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
--- !u!4 &2205893234949555645 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 8693254833721559262, guid: f21581740b83b624cac5e6a8fa4d0f47, type: 3}
m_PrefabInstance: {fileID: 7365721869475958115}
m_PrefabAsset: {fileID: 0}
--- !u!1001 &7995402114015427944 --- !u!1001 &7995402114015427944
PrefabInstance: PrefabInstance:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@@ -1362,6 +1501,34 @@ PrefabInstance:
propertyPath: m_SortingOrder propertyPath: m_SortingOrder
value: 1 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.size
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_Mode
value: 1
objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_Target
value:
objectReference: {fileID: 5762733430166618195}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_CallState
value: 2
objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_MethodName
value: destroyFakeChocolate
objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName
value: PicnicBehaviour, AppleHillsScripts
objectReference: {fileID: 0}
- target: {fileID: 2846246689251721816, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: interactionComplete.m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName
value: UnityEngine.Object, UnityEngine
objectReference: {fileID: 0}
- target: {fileID: 3984039030829909690, guid: b3fc964bec385174f85a143f2fcff121, type: 3} - target: {fileID: 3984039030829909690, guid: b3fc964bec385174f85a143f2fcff121, type: 3}
propertyPath: m_LocalScale.x propertyPath: m_LocalScale.x
value: 0.7 value: 0.7

File diff suppressed because it is too large Load Diff

View File

@@ -285,8 +285,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1} m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: 173} m_AnchoredPosition: {x: 0, y: 125}
m_SizeDelta: {x: 600, y: 150} m_SizeDelta: {x: 600, y: 120}
m_Pivot: {x: 0.5, y: 1} m_Pivot: {x: 0.5, y: 1}
--- !u!222 &5545241165728741220 --- !u!222 &5545241165728741220
CanvasRenderer: CanvasRenderer:
@@ -343,8 +343,8 @@ MonoBehaviour:
m_faceColor: m_faceColor:
serializedVersion: 2 serializedVersion: 2
rgba: 4294967295 rgba: 4294967295
m_fontSize: 150 m_fontSize: 125
m_fontSizeBase: 150 m_fontSizeBase: 125
m_fontWeight: 400 m_fontWeight: 400
m_enableAutoSizing: 0 m_enableAutoSizing: 0
m_fontSizeMin: 18 m_fontSizeMin: 18
@@ -1053,6 +1053,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.x propertyPath: m_SizeDelta.x
value: 0 value: 0
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 4925415087786595420, guid: 1d8cc8d9238eec34b8e600e7050e2979, type: 3}
propertyPath: m_fontSize
value: 54.45
objectReference: {fileID: 0}
- target: {fileID: 5378230129755544441, guid: 1d8cc8d9238eec34b8e600e7050e2979, type: 3} - target: {fileID: 5378230129755544441, guid: 1d8cc8d9238eec34b8e600e7050e2979, type: 3}
propertyPath: m_AnchorMax.x propertyPath: m_AnchorMax.x
value: 0 value: 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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: []

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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.

View File

@@ -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); Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreRequested(saveDataDict);
Logging.Debug($"[SaveLoadManager] Broadcast scene restore to LifecycleManager"); 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>

View File

@@ -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;
// Start is called once before the first execution of Update after the MonoBehaviour is created [Header("Playback Settings")]
void Start() [Tooltip("If true, the audio will only play once and never again after being played")]
public bool isOneTime;
private bool _hasPlayed;
#region ManagedBehaviour Overrides
public override bool AutoRegisterForSave => isOneTime; // Only save if one-time audio
protected override string OnSceneSaveRequested()
{
if (!isOneTime)
return null; // No need to save if not one-time
LevelAudioObjectSaveData saveData = new LevelAudioObjectSaveData
{
hasPlayed = _hasPlayed
};
return JsonUtility.ToJson(saveData);
}
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(); PlayNarrationAudio();
} }
void PlayNarrationAudio()
{
narratorAudioSource.audioSource.resource = firstNarration;
narratorAudioSource.Play(0);
} }
#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.Play(0);
_hasPlayed = true;
}
} }

View File

@@ -231,6 +231,9 @@ namespace UI.CardSystem
CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded; CardSystemManager.Instance.OnPendingCardAdded -= OnPendingCardAdded;
} }
// Clean up active pending cards to prevent duplicates on next opening
CleanupActiveCards();
// Don't restore input mode here - only restore when actually exiting (in OnExitButtonClicked) // Don't restore input mode here - only restore when actually exiting (in OnExitButtonClicked)
base.TransitionOut(); base.TransitionOut();
} }

View File

@@ -118,6 +118,13 @@ namespace UI.CardSystem
public override void TransitionIn() public override void TransitionIn()
{ {
base.TransitionIn(); base.TransitionIn();
// Ensure album icon is visible when page opens
if (albumIcon != null)
{
albumIcon.SetActive(true);
}
InitializeBoosterDisplay(); InitializeBoosterDisplay();
} }
@@ -363,6 +370,13 @@ namespace UI.CardSystem
// Remove from active slots list // Remove from active slots list
_activeBoostersInSlots.Remove(booster); _activeBoostersInSlots.Remove(booster);
// Hide album icon when booster is placed in center
if (albumIcon != null)
{
albumIcon.SetActive(false);
Debug.Log($"[BoosterOpeningPage] Album icon hidden");
}
// Lock the slot so it can't be dragged out // Lock the slot so it can't be dragged out
Debug.Log($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}"); Debug.Log($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}");
centerOpeningSlot.SetLocked(true); centerOpeningSlot.SetLocked(true);
@@ -800,6 +814,13 @@ namespace UI.CardSystem
// All cards revealed and interacted with, wait a moment // All cards revealed and interacted with, wait a moment
yield return new WaitForSeconds(0.5f); yield return new WaitForSeconds(0.5f);
// Show album icon before cards start tweening to it
if (albumIcon != null)
{
albumIcon.SetActive(true);
Debug.Log($"[BoosterOpeningPage] Album icon shown for card tween target");
}
// Animate cards to album icon (or center if no icon assigned) with staggered delays // Animate cards to album icon (or center if no icon assigned) with staggered delays
Vector3 targetPosition = albumIcon != null ? albumIcon.transform.position : Vector3.zero; Vector3 targetPosition = albumIcon != null ? albumIcon.transform.position : Vector3.zero;
@@ -828,6 +849,8 @@ namespace UI.CardSystem
_currentRevealedCards.Clear(); _currentRevealedCards.Clear();
yield return new WaitForSeconds(totalAnimationTime); yield return new WaitForSeconds(totalAnimationTime);
// Album icon stays visible for next booster (will be hidden when next booster is placed)
} }
/// <summary> /// <summary>

View File

@@ -10,7 +10,8 @@ namespace UI.CardSystem
/// <summary> /// <summary>
/// Singleton UI component for granting booster packs from minigames. /// Singleton UI component for granting booster packs from minigames.
/// Displays a booster pack with glow effect, waits for user to click continue, /// Displays a booster pack with glow effect, waits for user to click continue,
/// then animates the pack flying to bottom-left corner before granting the reward. /// then shows the scrapbook button and animates the pack flying to it before granting the reward.
/// The scrapbook button is automatically hidden after the animation completes.
/// </summary> /// </summary>
public class MinigameBoosterGiver : MonoBehaviour public class MinigameBoosterGiver : MonoBehaviour
{ {
@@ -187,23 +188,61 @@ namespace UI.CardSystem
yield break; yield break;
} }
// Calculate bottom-left corner position in local space // Show scrapbook button temporarily using HUD visibility context
RectTransform canvasRect = GetComponentInParent<Canvas>()?.GetComponent<RectTransform>(); PlayerHudManager.HudVisibilityContext hudContext = null;
Vector3 targetPosition; GameObject scrapbookButton = null;
if (canvasRect != null) scrapbookButton = PlayerHudManager.Instance.GetScrabookButton();
if (scrapbookButton != null)
{ {
// Convert bottom-left corner with offset to local position hudContext = PlayerHudManager.Instance.ShowElementTemporarily(scrapbookButton);
Vector2 bottomLeft = new Vector2(-canvasRect.rect.width / 2f, -canvasRect.rect.height / 2f);
targetPosition = bottomLeft + targetBottomLeftOffset;
} }
else else
{ {
// Fallback if no canvas found Debug.LogWarning("[MinigameBoosterGiver] Scrapbook button not found in PlayerHudManager.");
targetPosition = _boosterInitialPosition + new Vector3(-500f, -500f, 0f);
} }
// Tween to bottom-left corner // Calculate target position - use scrapbook button position if available
Vector3 targetPosition;
if (scrapbookButton != null)
{
// Get the scrapbook button's position in the same coordinate space as boosterImage
RectTransform scrapbookRect = scrapbookButton.GetComponent<RectTransform>();
if (scrapbookRect != null)
{
// Convert scrapbook button's world position to local position relative to boosterImage's parent
Canvas canvas = GetComponentInParent<Canvas>();
if (canvas != null && canvas.renderMode == RenderMode.ScreenSpaceOverlay)
{
// For overlay canvas, convert screen position to local position
Vector2 screenPos = RectTransformUtility.WorldToScreenPoint(null, scrapbookRect.position);
RectTransformUtility.ScreenPointToLocalPointInRectangle(
boosterImage.parent as RectTransform,
screenPos,
null,
out Vector2 localPoint);
targetPosition = localPoint;
}
else
{
// For world space or camera canvas
targetPosition = boosterImage.parent.InverseTransformPoint(scrapbookRect.position);
}
}
else
{
Debug.LogWarning("[MinigameBoosterGiver] Scrapbook button has no RectTransform, using fallback position.");
targetPosition = GetFallbackPosition();
}
}
else
{
// Fallback to bottom-left corner
targetPosition = GetFallbackPosition();
}
// Tween to scrapbook button position
Tween.LocalPosition(boosterImage, targetPosition, disappearDuration, 0f, Tween.EaseInBack); Tween.LocalPosition(boosterImage, targetPosition, disappearDuration, 0f, Tween.EaseInBack);
// Scale down // Scale down
@@ -224,6 +263,9 @@ namespace UI.CardSystem
Debug.LogWarning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster pack."); Debug.LogWarning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster pack.");
} }
// Hide scrapbook button by disposing the context
hudContext?.Dispose();
// Hide the visual // Hide the visual
if (visualContainer != null) if (visualContainer != null)
{ {
@@ -237,6 +279,22 @@ namespace UI.CardSystem
// Clear sequence reference // Clear sequence reference
_currentSequence = null; _currentSequence = null;
} }
private Vector3 GetFallbackPosition()
{
RectTransform canvasRect = GetComponentInParent<Canvas>()?.GetComponent<RectTransform>();
if (canvasRect != null)
{
// Convert bottom-left corner with offset to local position
Vector2 bottomLeft = new Vector2(-canvasRect.rect.width / 2f, -canvasRect.rect.height / 2f);
return bottomLeft + targetBottomLeftOffset;
}
else
{
// Ultimate fallback if no canvas found
return _boosterInitialPosition + new Vector3(-500f, -500f, 0f);
}
}
} }
} }

View File

@@ -7,6 +7,7 @@ using UnityEngine.Playables;
using UnityEngine.UI; using UnityEngine.UI;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System;
namespace UI namespace UI
{ {
@@ -19,6 +20,73 @@ namespace UI
{ {
public enum UIMode { Overworld, Puzzle, Minigame, HideAll }; public enum UIMode { Overworld, Puzzle, Minigame, HideAll };
/// <summary>
/// Context object for managing temporary HUD element visibility.
/// Automatically restores previous visibility state when disposed.
/// Usage: using (var ctx = PlayerHudManager.Instance.ShowElementTemporarily(myButton)) { ... }
/// </summary>
public class HudVisibilityContext : IDisposable
{
private readonly GameObject _element;
private readonly bool _previousState;
private bool _disposed;
internal HudVisibilityContext(GameObject element, bool show)
{
_element = element;
_previousState = element.activeSelf;
_element.SetActive(show);
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
if (_element != null)
{
_element.SetActive(_previousState);
}
}
}
/// <summary>
/// Multi-element visibility context for managing multiple HUD elements at once.
/// Automatically restores previous visibility states when disposed.
/// </summary>
public class MultiHudVisibilityContext : IDisposable
{
private readonly List<(GameObject element, bool previousState)> _elements;
private bool _disposed;
internal MultiHudVisibilityContext(IEnumerable<GameObject> elements, bool show)
{
_elements = new List<(GameObject, bool)>();
foreach (var element in elements)
{
if (element != null)
{
_elements.Add((element, element.activeSelf));
element.SetActive(show);
}
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
foreach (var (element, previousState) in _elements)
{
if (element != null)
{
element.SetActive(previousState);
}
}
}
}
public UIMode currentUIMode; public UIMode currentUIMode;
[Header("HUD Management")] [Header("HUD Management")]
@@ -33,6 +101,7 @@ namespace UI
[Header("HUD Elements")] [Header("HUD Elements")]
public GameObject eagleEye; public GameObject eagleEye;
public GameObject ramaSjangButton; public GameObject ramaSjangButton;
public GameObject scrabBookButton;
[HideInInspector] public Image cinematicSprites; [HideInInspector] public Image cinematicSprites;
[HideInInspector] public Image cinematicBackgroundSprites; [HideInInspector] public Image cinematicBackgroundSprites;
@@ -254,6 +323,9 @@ namespace UI
if (visible) if (visible)
{ {
eagleEye.SetActive(false); eagleEye.SetActive(false);
ramaSjangButton.SetActive(false);
scrabBookButton.SetActive(false);
} }
break; break;
case UIMode.HideAll: case UIMode.HideAll:
@@ -318,6 +390,64 @@ namespace UI
UnityEngine.Debug.LogError("[PlayerHudManager] Cannot push page - UIPageController not found!"); UnityEngine.Debug.LogError("[PlayerHudManager] Cannot push page - UIPageController not found!");
} }
} }
#region HUD Element Getters
public GameObject GetEagleEye() => eagleEye;
public GameObject GetRamaSjangButton() => ramaSjangButton;
public GameObject GetScrabookButton() => scrabBookButton;
#endregion
#region Context-Based Visibility Management
/// <summary>
/// Temporarily shows a HUD element. Returns a context object that restores the previous state when disposed.
/// Usage: using (var ctx = PlayerHudManager.Instance.ShowElementTemporarily(myButton)) { /* element is visible */ }
/// </summary>
public HudVisibilityContext ShowElementTemporarily(GameObject element)
{
if (element == null)
{
Logging.Warning("[PlayerHudManager] Attempted to show null element");
return null;
}
return new HudVisibilityContext(element, true);
}
/// <summary>
/// Temporarily hides a HUD element. Returns a context object that restores the previous state when disposed.
/// Usage: using (var ctx = PlayerHudManager.Instance.HideElementTemporarily(myButton)) { /* element is hidden */ }
/// </summary>
public HudVisibilityContext HideElementTemporarily(GameObject element)
{
if (element == null)
{
Logging.Warning("[PlayerHudManager] Attempted to hide null element");
return null;
}
return new HudVisibilityContext(element, false);
}
/// <summary>
/// Temporarily shows multiple HUD elements. Returns a context object that restores all previous states when disposed.
/// Usage: using (var ctx = PlayerHudManager.Instance.ShowElementsTemporarily(button1, button2, button3)) { /* elements are visible */ }
/// </summary>
public MultiHudVisibilityContext ShowElementsTemporarily(params GameObject[] elements)
{
return new MultiHudVisibilityContext(elements, true);
}
/// <summary>
/// Temporarily hides multiple HUD elements. Returns a context object that restores all previous states when disposed.
/// Usage: using (var ctx = PlayerHudManager.Instance.HideElementsTemporarily(button1, button2, button3)) { /* elements are hidden */ }
/// </summary>
public MultiHudVisibilityContext HideElementsTemporarily(params GameObject[] elements)
{
return new MultiHudVisibilityContext(elements, false);
}
#endregion
} }
} }