Compare commits

...

24 Commits

Author SHA1 Message Date
Michal Pikulski
dcf8c8bb87 Add documentation 2025-11-11 13:32:02 +01:00
Michal Pikulski
9a6914b9bd Final touchups to the lifecycle management 2025-11-11 10:53:09 +01:00
0aa2270e1a Lifecycle System Refactor & Logging Centralization (#56)
## ManagedBehaviour System Refactor

- **Sealed `Awake()`** to prevent override mistakes that break singleton registration
- **Added `OnManagedAwake()`** for early initialization (fires during registration)
- **Renamed lifecycle hook:** `OnManagedAwake()` → `OnManagedStart()` (fires after boot, mirrors Unity's Awake→Start)
- **40 files migrated** to new pattern (2 core, 38 components)
- Eliminated all fragile `private new void Awake()` patterns
- Zero breaking changes - backward compatible

## Centralized Logging System

- **Automatic tagging** via `CallerMemberName` and `CallerFilePath` - logs auto-tagged as `[ClassName][MethodName] message`
- **Unified API:** Single `Logging.Debug/Info/Warning/Error()` replaces custom `LogDebugMessage()` implementations
- **~90 logging call sites** migrated across 10 files
- **10 redundant helper methods** removed
- All logs broadcast via `Logging.OnLogEntryAdded` event for real-time monitoring

## Custom Log Console (Editor Window)

- **Persistent filter popups** for multi-selection (classes, methods, log levels) - windows stay open during selection
- **Search** across class names, methods, and message content
- **Time range filter** with MinMaxSlider
- **Export** filtered logs to timestamped `.txt` files
- **Right-click context menu** for quick filtering and copy actions
- **Visual improvements:** White text, alternating row backgrounds, color-coded log levels
- **Multiple instances** supported for simultaneous system monitoring
- Open via `AppleHills > Custom Log Console`

Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com>
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #56
2025-11-11 08:48:29 +00:00
Michal Pikulski
c4d356886f Dumb fix for data clearing 2025-11-10 14:27:14 +01:00
Michal Pikulski
64d7f19b83 ADd clearing saves 2025-11-10 14:11:02 +01:00
ce21e8b02e Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 13:13:36 +01:00
f44f8c5171 Made intro VO play in diving minigame 2025-11-10 13:13:32 +01:00
9772206362 Merge pull request 'Clean up logging' (#55) from clean_up_logging into main
Reviewed-on: #55
2025-11-10 12:06:05 +00:00
Michal Pikulski
3ebbecc277 Clean up logging 2025-11-10 13:03:36 +01:00
Michal Pikulski
c99aad49f3 Save bird states 2025-11-10 12:55:27 +01:00
3fe4c6afd9 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 12:53:07 +01:00
9c61065947 Fixed nullref in TakePhotoState 2025-11-10 12:53:03 +01:00
Michal Pikulski
7c09db641a Only show cards when in actual album 2025-11-10 12:41:28 +01:00
Michal Pikulski
e369660a8f Fix tab navigation on card select less wonky 2025-11-10 12:29:25 +01:00
a6e3413499 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 12:19:25 +01:00
7bb992acb8 Made apple audio sources use OnManagedAwake 2025-11-10 12:19:20 +01:00
Michal Pikulski
cefa488a92 Update issues with card appearing twice after reopening page 2025-11-10 12:17:17 +01:00
9344f06886 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 11:37:53 +01:00
af7e081c9a Fixed Chocolate Puzzle, but does not work well on load (the destroyed chocolate appears again) 2025-11-10 11:37:45 +01:00
Michal Pikulski
4a6ac7281f Udate the booster page. New card text smaller, moved down. Album icon disappears when opening boosters. 2025-11-10 11:28:23 +01:00
861797ba41 Merge pull request 'DamianBranch' (#54) from DamianBranch into main
Reviewed-on: #54
2025-11-10 10:10:40 +00:00
98883bd382 Merge branch 'main' into DamianBranch 2025-11-10 11:10:14 +01:00
Michal Pikulski
75cd70a18a Play intro audio only once 2025-11-10 11:09:06 +01:00
Michal Pikulski
252cb99884 Update icons in minigame 2025-11-10 10:41:38 +01:00
103 changed files with 3591 additions and 2388 deletions

3
.gitignore vendored
View File

@@ -104,3 +104,6 @@ InitTestScene*.unity*
.vscode/launch.json
.vscode/settings.json
.idea/.idea.AppleHillsProduction/.idea/indexLayout.xml
# WIP docs
/docs/wip/

View File

@@ -71,7 +71,6 @@ namespace AppleHills.Editor
{
CardSystemManager.Instance.OnBoosterOpened += OnBoosterOpened;
CardSystemManager.Instance.OnCardCollected += OnCardCollected;
CardSystemManager.Instance.OnCardRarityUpgraded += OnCardRarityUpgraded;
CardSystemManager.Instance.OnBoosterCountChanged += OnBoosterCountChanged;
isSubscribed = true;
@@ -87,7 +86,6 @@ namespace AppleHills.Editor
{
CardSystemManager.Instance.OnBoosterOpened -= OnBoosterOpened;
CardSystemManager.Instance.OnCardCollected -= OnCardCollected;
CardSystemManager.Instance.OnCardRarityUpgraded -= OnCardRarityUpgraded;
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
}
@@ -109,13 +107,6 @@ namespace AppleHills.Editor
Repaint();
}
private void OnCardRarityUpgraded(CardData card)
{
lastEventMessage = $"Card upgraded: {card.Name} → {card.Rarity}!";
RefreshData();
Repaint();
}
private void OnBoosterCountChanged(int count)
{
boosterCount = count;

View File

@@ -1,4 +1,4 @@
using UnityEngine;
using UnityEngine;
using UnityEditor;
namespace Interactions

View File

@@ -0,0 +1,829 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
using Core;
namespace Editor
{
/// <summary>
/// Custom dockable log window with advanced filtering capabilities.
/// Supports filtering by class, method, log level, time range, and text search.
/// Multiple instances can be opened with independent filter states.
///
/// PERFORMANCE NOTE: For large log counts (10,000+), consider implementing virtualized scrolling.
/// Only render visible entries within the scroll view to avoid GUI overhead.
/// See DrawLogEntries() method for potential optimization location.
/// </summary>
public class CustomLogWindow : EditorWindow
{
#region Window State
private Vector2 scrollPosition;
private readonly List<LogEntry> allLogs = new List<LogEntry>();
#endregion
#region Filter State
private HashSet<string> activeClassTags = new HashSet<string>();
private HashSet<string> activeMethodTags = new HashSet<string>();
private HashSet<string> selectedClassFilters = new HashSet<string>();
private HashSet<string> selectedMethodFilters = new HashSet<string>();
private HashSet<LogLevel> selectedLevelFilters = new HashSet<LogLevel>
{
LogLevel.Debug, LogLevel.Info, LogLevel.Warning, LogLevel.Error
};
private string searchText = "";
private bool autoScroll = true;
// Time range filtering
private bool enableTimeFilter = false;
private float minTimestamp = 0;
private float maxTimestamp = 0;
private float currentMaxTimestamp = 0;
#endregion
#region UI State
// Colors for log levels
private readonly Color debugColor = Color.white;
private readonly Color infoColor = Color.white;
private readonly Color warningColor = Color.yellow;
private readonly Color errorColor = new Color(1f, 0.3f, 0.3f);
// Alternating row background colors
private readonly Color evenRowColor = new Color(0.22f, 0.22f, 0.22f);
private readonly Color oddRowColor = new Color(0.26f, 0.26f, 0.26f);
#endregion
#region Menu Items
[MenuItem("AppleHills/Custom Log Console")]
public static void ShowWindow()
{
var window = GetWindow<CustomLogWindow>("Custom Log");
window.Show();
}
#endregion
#region Unity Lifecycle
private void OnEnable()
{
// Subscribe to new log events
Logging.OnLogEntryAdded += OnLogAdded;
// Load existing logs
allLogs.AddRange(Logging.GetRecentLogs());
// Build initial tag lists
RebuildTagLists();
// Initialize time range
UpdateTimeRange();
}
private void OnDisable()
{
Logging.OnLogEntryAdded -= OnLogAdded;
}
private void OnLogAdded(LogEntry entry)
{
allLogs.Add(entry);
// Update active tags
activeClassTags.Add(entry.ClassName);
activeMethodTags.Add(entry.MethodName);
// Update time range
UpdateTimeRange();
if (autoScroll)
scrollPosition.y = float.MaxValue;
Repaint(); // Redraw window
}
#endregion
#region GUI Drawing
private void OnGUI()
{
DrawToolbar();
DrawLogEntries();
}
private void DrawToolbar()
{
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
// Clear button
if (GUILayout.Button("Clear", EditorStyles.toolbarButton, GUILayout.Width(50)))
{
allLogs.Clear();
activeClassTags.Clear();
activeMethodTags.Clear();
Logging.ClearLogs();
Repaint();
}
// Auto-scroll toggle
autoScroll = GUILayout.Toggle(autoScroll, "Auto-scroll", EditorStyles.toolbarButton, GUILayout.Width(80));
GUILayout.Space(10);
// Class Filter Button
string classLabel = selectedClassFilters.Count == 0 ? "Classes: All" :
selectedClassFilters.Count == activeClassTags.Count ? "Classes: All" :
$"Classes: {selectedClassFilters.Count}";
if (GUILayout.Button(new GUIContent(classLabel, "Filter by class"), EditorStyles.toolbarDropDown, GUILayout.Width(100)))
{
ShowClassFilterMenu();
}
// Method Filter Button
string methodLabel = selectedMethodFilters.Count == 0 ? "Methods: All" :
selectedMethodFilters.Count == activeMethodTags.Count ? "Methods: All" :
$"Methods: {selectedMethodFilters.Count}";
if (GUILayout.Button(new GUIContent(methodLabel, "Filter by method"), EditorStyles.toolbarDropDown, GUILayout.Width(110)))
{
ShowMethodFilterMenu();
}
// Log Level Filter Button
string levelLabel = selectedLevelFilters.Count == 4 ? "Levels: All" :
selectedLevelFilters.Count == 0 ? "Levels: None" :
$"Levels: {selectedLevelFilters.Count}";
if (GUILayout.Button(new GUIContent(levelLabel, "Filter by log level"), EditorStyles.toolbarDropDown, GUILayout.Width(90)))
{
ShowLevelFilterMenu();
}
// Time Range Filter Button
if (GUILayout.Button(new GUIContent("⏱", "Time range filter"), EditorStyles.toolbarButton, GUILayout.Width(25)))
{
ShowTimeRangeWindow();
}
GUILayout.FlexibleSpace();
// Search box
GUILayout.Label("Search:", GUILayout.Width(50));
searchText = GUILayout.TextField(searchText, EditorStyles.toolbarSearchField, GUILayout.Width(150));
GUILayout.Space(10);
// Export button
if (GUILayout.Button("Export", EditorStyles.toolbarButton, GUILayout.Width(60)))
{
ExportLogs();
}
GUILayout.Space(5);
// Log count
var filteredCount = GetFilteredLogs().Count();
GUILayout.Label($"{filteredCount}/{allLogs.Count}", GUILayout.Width(80));
EditorGUILayout.EndHorizontal();
}
private void ShowClassFilterMenu()
{
ClassFilterWindow.ShowWindow(this, activeClassTags, selectedClassFilters,
(newSelection) =>
{
selectedClassFilters = newSelection;
Repaint();
});
}
private void ShowMethodFilterMenu()
{
MethodFilterWindow.ShowWindow(this, activeMethodTags, selectedMethodFilters,
(newSelection) =>
{
selectedMethodFilters = newSelection;
Repaint();
});
}
private void ShowLevelFilterMenu()
{
LevelFilterWindow.ShowWindow(this, selectedLevelFilters,
(newSelection) =>
{
selectedLevelFilters = newSelection;
Repaint();
});
}
private void ShowTimeRangeWindow()
{
TimeRangeFilterWindow.ShowWindow(this, enableTimeFilter, minTimestamp, maxTimestamp, currentMaxTimestamp,
(enabled, min, max) =>
{
enableTimeFilter = enabled;
minTimestamp = min;
maxTimestamp = max;
Repaint();
});
}
private void DrawLogEntries()
{
// PERFORMANCE NOTE: For 10,000+ logs, consider virtualized scrolling here.
// Only render entries visible within the scroll view rect.
// Current implementation renders all filtered logs which may cause GUI slowdown.
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
var filteredLogs = GetFilteredLogs().ToList();
for (int i = 0; i < filteredLogs.Count; i++)
{
DrawLogEntry(filteredLogs[i], i);
}
EditorGUILayout.EndScrollView();
}
private void DrawLogEntry(LogEntry entry, int index)
{
// Draw alternating row background
Color bgColor = (index % 2 == 0) ? evenRowColor : oddRowColor;
Rect rowRect = EditorGUILayout.BeginHorizontal();
EditorGUI.DrawRect(rowRect, bgColor);
// Color-code by log level
Color color = GetColorForLevel(entry.Level);
var originalColor = GUI.contentColor;
GUI.contentColor = color;
// Timestamp
GUILayout.Label($"[{entry.Timestamp:F2}s]", GUILayout.Width(80));
// Level
GUILayout.Label($"[{entry.Level}]", GUILayout.Width(70));
// Class
GUILayout.Label($"[{entry.ClassName}]", GUILayout.Width(150));
// Method
GUILayout.Label($"[{entry.MethodName}]", GUILayout.Width(150));
// Message
GUILayout.Label(entry.Message, GUILayout.ExpandWidth(true));
EditorGUILayout.EndHorizontal();
GUI.contentColor = originalColor;
// Right-click context menu
if (Event.current.type == EventType.ContextClick && rowRect.Contains(Event.current.mousePosition))
{
GenericMenu menu = new GenericMenu();
menu.AddItem(new GUIContent("Copy Full Message"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = entry.FullFormattedMessage;
});
menu.AddItem(new GUIContent("Copy Message Only"), false, () =>
{
EditorGUIUtility.systemCopyBuffer = entry.Message;
});
menu.AddSeparator("");
menu.AddItem(new GUIContent("Filter to this Class"), false, () =>
{
selectedClassFilters.Clear();
selectedClassFilters.Add(entry.ClassName);
Repaint();
});
menu.AddItem(new GUIContent("Filter to this Method"), false, () =>
{
selectedMethodFilters.Clear();
selectedMethodFilters.Add(entry.MethodName);
Repaint();
});
menu.AddItem(new GUIContent("Filter to this Class + Method"), false, () =>
{
selectedClassFilters.Clear();
selectedClassFilters.Add(entry.ClassName);
selectedMethodFilters.Clear();
selectedMethodFilters.Add(entry.MethodName);
Repaint();
});
menu.AddSeparator("");
menu.AddItem(new GUIContent("Filter to this Log Level"), false, () =>
{
selectedLevelFilters.Clear();
selectedLevelFilters.Add(entry.Level);
Repaint();
});
menu.ShowAsContext();
Event.current.Use();
}
}
#endregion
#region Filtering Logic
private IEnumerable<LogEntry> GetFilteredLogs()
{
return allLogs.Where(entry =>
{
// Filter by class (if any selected)
if (selectedClassFilters.Count > 0 && !selectedClassFilters.Contains(entry.ClassName))
return false;
// Filter by method (if any selected)
if (selectedMethodFilters.Count > 0 && !selectedMethodFilters.Contains(entry.MethodName))
return false;
// Filter by log level
if (!selectedLevelFilters.Contains(entry.Level))
return false;
// Filter by time range
if (enableTimeFilter)
{
if (entry.Timestamp < minTimestamp || entry.Timestamp > maxTimestamp)
return false;
}
// Filter by search text
if (!string.IsNullOrEmpty(searchText))
{
bool matchesSearch =
entry.ClassName.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
entry.MethodName.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
entry.Message.Contains(searchText, StringComparison.OrdinalIgnoreCase);
if (!matchesSearch)
return false;
}
return true;
});
}
#endregion
#region Helper Methods
private Color GetColorForLevel(LogLevel level)
{
return level switch
{
LogLevel.Debug => debugColor,
LogLevel.Info => infoColor,
LogLevel.Warning => warningColor,
LogLevel.Error => errorColor,
_ => infoColor
};
}
private void RebuildTagLists()
{
activeClassTags.Clear();
activeMethodTags.Clear();
foreach (var log in allLogs)
{
activeClassTags.Add(log.ClassName);
activeMethodTags.Add(log.MethodName);
}
}
private void UpdateTimeRange()
{
if (allLogs.Count > 0)
{
currentMaxTimestamp = allLogs.Max(l => l.Timestamp);
// Initialize time range on first log
if (maxTimestamp == 0)
{
maxTimestamp = currentMaxTimestamp;
}
else
{
// Auto-expand max range as new logs come in
maxTimestamp = currentMaxTimestamp;
}
}
}
private void ExportLogs()
{
string defaultFileName = $"logs_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.txt";
string path = EditorUtility.SaveFilePanel("Export Logs", "", defaultFileName, "txt");
if (!string.IsNullOrEmpty(path))
{
try
{
var filteredLogs = GetFilteredLogs().ToList();
var lines = filteredLogs.Select(e => e.FullFormattedMessage);
File.WriteAllLines(path, lines);
EditorUtility.DisplayDialog("Export Successful",
$"Exported {filteredLogs.Count} log entries to:\n{path}", "OK");
}
catch (Exception ex)
{
EditorUtility.DisplayDialog("Export Failed",
$"Failed to export logs:\n{ex.Message}", "OK");
}
}
}
#endregion
}
/// <summary>
/// Persistent popup window for class filtering with multi-selection support
/// </summary>
public class ClassFilterWindow : EditorWindow
{
private HashSet<string> availableTags;
private HashSet<string> selectedTags;
private System.Action<HashSet<string>> onApply;
private Vector2 scrollPosition;
private string searchFilter = "";
public static void ShowWindow(CustomLogWindow parent, HashSet<string> available, HashSet<string> selected,
System.Action<HashSet<string>> applyCallback)
{
var window = CreateInstance<ClassFilterWindow>();
window.titleContent = new GUIContent("Class Filter");
window.availableTags = available;
window.selectedTags = new HashSet<string>(selected);
window.onApply = applyCallback;
var parentRect = parent.position;
window.position = new Rect(parentRect.x + 50, parentRect.y + 50, 300, 400);
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(5);
// Control buttons
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("All", GUILayout.Width(60)))
{
if (availableTags != null)
selectedTags = new HashSet<string>(availableTags);
}
if (GUILayout.Button("None", GUILayout.Width(60)))
{
selectedTags.Clear();
}
GUILayout.FlexibleSpace();
searchFilter = EditorGUILayout.TextField(searchFilter, EditorStyles.toolbarSearchField, GUILayout.Width(150));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// Scrollable list of toggles
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
if (availableTags != null && availableTags.Count > 0)
{
var filteredTags = string.IsNullOrEmpty(searchFilter)
? availableTags.OrderBy(t => t)
: availableTags.Where(t => t.Contains(searchFilter, StringComparison.OrdinalIgnoreCase)).OrderBy(t => t);
foreach (var tag in filteredTags)
{
bool isSelected = selectedTags.Contains(tag);
bool newSelection = EditorGUILayout.ToggleLeft(tag, isSelected);
if (newSelection != isSelected)
{
if (newSelection)
selectedTags.Add(tag);
else
selectedTags.Remove(tag);
}
}
}
else
{
EditorGUILayout.HelpBox("No classes available", MessageType.Info);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(5);
// Apply/Close buttons
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply", GUILayout.Width(80)))
{
onApply?.Invoke(selectedTags);
Close();
}
if (GUILayout.Button("Close", GUILayout.Width(80)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
}
}
/// <summary>
/// Persistent popup window for method filtering with multi-selection support
/// </summary>
public class MethodFilterWindow : EditorWindow
{
private HashSet<string> availableTags;
private HashSet<string> selectedTags;
private System.Action<HashSet<string>> onApply;
private Vector2 scrollPosition;
private string searchFilter = "";
public static void ShowWindow(CustomLogWindow parent, HashSet<string> available, HashSet<string> selected,
System.Action<HashSet<string>> applyCallback)
{
var window = CreateInstance<MethodFilterWindow>();
window.titleContent = new GUIContent("Method Filter");
window.availableTags = available;
window.selectedTags = new HashSet<string>(selected);
window.onApply = applyCallback;
var parentRect = parent.position;
window.position = new Rect(parentRect.x + 50, parentRect.y + 50, 300, 400);
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(5);
// Control buttons
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("All", GUILayout.Width(60)))
{
if (availableTags != null)
selectedTags = new HashSet<string>(availableTags);
}
if (GUILayout.Button("None", GUILayout.Width(60)))
{
selectedTags.Clear();
}
GUILayout.FlexibleSpace();
searchFilter = EditorGUILayout.TextField(searchFilter, EditorStyles.toolbarSearchField, GUILayout.Width(150));
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
// Scrollable list of toggles
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
if (availableTags != null && availableTags.Count > 0)
{
var filteredTags = string.IsNullOrEmpty(searchFilter)
? availableTags.OrderBy(t => t)
: availableTags.Where(t => t.Contains(searchFilter, StringComparison.OrdinalIgnoreCase)).OrderBy(t => t);
foreach (var tag in filteredTags)
{
bool isSelected = selectedTags.Contains(tag);
bool newSelection = EditorGUILayout.ToggleLeft(tag, isSelected);
if (newSelection != isSelected)
{
if (newSelection)
selectedTags.Add(tag);
else
selectedTags.Remove(tag);
}
}
}
else
{
EditorGUILayout.HelpBox("No methods available", MessageType.Info);
}
EditorGUILayout.EndScrollView();
EditorGUILayout.Space(5);
// Apply/Close buttons
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply", GUILayout.Width(80)))
{
onApply?.Invoke(selectedTags);
Close();
}
if (GUILayout.Button("Close", GUILayout.Width(80)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
}
}
/// <summary>
/// Persistent popup window for log level filtering with multi-selection support
/// </summary>
public class LevelFilterWindow : EditorWindow
{
private HashSet<LogLevel> selectedLevels;
private System.Action<HashSet<LogLevel>> onApply;
public static void ShowWindow(CustomLogWindow parent, HashSet<LogLevel> selected,
System.Action<HashSet<LogLevel>> applyCallback)
{
var window = CreateInstance<LevelFilterWindow>();
window.titleContent = new GUIContent("Log Level Filter");
window.selectedLevels = new HashSet<LogLevel>(selected);
window.onApply = applyCallback;
var parentRect = parent.position;
window.position = new Rect(parentRect.x + 50, parentRect.y + 50, 250, 180);
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(5);
// Control buttons
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("All", GUILayout.Width(60)))
{
selectedLevels = new HashSet<LogLevel>
{
LogLevel.Debug, LogLevel.Info, LogLevel.Warning, LogLevel.Error
};
}
if (GUILayout.Button("None", GUILayout.Width(60)))
{
selectedLevels.Clear();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
// Log level toggles
foreach (LogLevel level in Enum.GetValues(typeof(LogLevel)))
{
bool isSelected = selectedLevels.Contains(level);
bool newSelection = EditorGUILayout.ToggleLeft(level.ToString(), isSelected);
if (newSelection != isSelected)
{
if (newSelection)
selectedLevels.Add(level);
else
selectedLevels.Remove(level);
}
}
EditorGUILayout.Space(10);
// Apply/Close buttons
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply", GUILayout.Width(80)))
{
onApply?.Invoke(selectedLevels);
Close();
}
if (GUILayout.Button("Close", GUILayout.Width(80)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(5);
}
}
/// <summary>
/// Popup window for configuring time range filters
/// </summary>
public class TimeRangeFilterWindow : EditorWindow
{
private bool enableTimeFilter;
private float minTimestamp;
private float maxTimestamp;
private float currentMaxTimestamp;
private System.Action<bool, float, float> onApply;
public static void ShowWindow(CustomLogWindow parent, bool enabled, float min, float max, float currentMax,
System.Action<bool, float, float> applyCallback)
{
var window = CreateInstance<TimeRangeFilterWindow>();
window.titleContent = new GUIContent("Time Range Filter");
window.enableTimeFilter = enabled;
window.minTimestamp = min;
window.maxTimestamp = max;
window.currentMaxTimestamp = currentMax;
window.onApply = applyCallback;
// Position near the parent window
var parentRect = parent.position;
window.position = new Rect(parentRect.x + 100, parentRect.y + 100, 350, 120);
window.ShowUtility();
}
private void OnGUI()
{
EditorGUILayout.Space(10);
enableTimeFilter = EditorGUILayout.Toggle("Enable Time Filter", enableTimeFilter);
EditorGUILayout.Space(5);
if (enableTimeFilter && currentMaxTimestamp > 0)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"Min: {minTimestamp:F2}s", GUILayout.Width(100));
EditorGUILayout.LabelField($"Max: {maxTimestamp:F2}s", GUILayout.Width(100));
EditorGUILayout.EndHorizontal();
EditorGUILayout.MinMaxSlider(
ref minTimestamp,
ref maxTimestamp,
0,
currentMaxTimestamp);
EditorGUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Reset Range"))
{
minTimestamp = 0;
maxTimestamp = currentMaxTimestamp;
}
EditorGUILayout.EndHorizontal();
}
else if (enableTimeFilter)
{
EditorGUILayout.HelpBox("No logs available for time filtering", MessageType.Info);
}
EditorGUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
if (GUILayout.Button("Apply", GUILayout.Width(80)))
{
onApply?.Invoke(enableTimeFilter, minTimestamp, maxTimestamp);
Close();
}
if (GUILayout.Button("Cancel", GUILayout.Width(80)))
{
Close();
}
GUILayout.FlexibleSpace();
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(10);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 60a300585fef4ac990c963c8b37c3887
timeCreated: 1762814971

View File

@@ -181,7 +181,7 @@ namespace Editor
string scenePath = AssetDatabase.GUIDToAssetPath(guid);
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
var allComponents = GameObject.FindObjectsOfType<Component>(true);
var allComponents = GameObject.FindObjectsByType<Component>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var component in allComponents)
{
if (component == null || component.gameObject.scene != scene) continue;

View File

@@ -12,7 +12,7 @@ namespace Editor.Lifecycle
/// Editor-only bootstrap that ensures OnSceneReady is triggered when playing directly from a scene in Unity Editor.
///
/// PROBLEM: When you press Play in the editor without going through the scene manager:
/// - CustomBoot runs and triggers OnBootCompletionTriggered (which broadcasts OnManagedAwake)
/// - CustomBoot runs and triggers OnBootCompletionTriggered (which broadcasts OnManagedStart)
/// - But BroadcastSceneReady is NEVER called for the initial scene
/// - Components in the scene never receive their OnSceneReady() callback
///

View File

@@ -1,8 +1,6 @@
using UnityEditor;
using AppleHills.Core.Settings;
using Core;
using UnityEngine;
using UnityEngine.Rendering.VirtualTexturing;
namespace AppleHills.Editor
{
@@ -65,7 +63,7 @@ namespace AppleHills.Editor
GetPuzzlePromptRange
);
LogDebugMessage("Editor settings loaded for Scene View use");
Logging.Debug("Editor settings loaded for Scene View use");
}
public static void RefreshSceneViews()
@@ -102,14 +100,5 @@ namespace AppleHills.Editor
return null;
}
private static void LogDebugMessage(string message)
{
if (Application.isPlaying &&
DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().settingsLogVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[EditorSettingsProvider] {message}");
}
}
}
}

View File

@@ -1,153 +0,0 @@
using Interactions;
using PuzzleS;
using UnityEditor;
using UnityEngine;
namespace Editor
{
public class ItemPrefabEditorWindow : EditorWindow
{
private GameObject _selectedGameObject;
private InteractableBase _interactable;
private PickupItemData _pickupData;
private PuzzleStepSO _objectiveData;
private UnityEditor.Editor _soEditor;
private string _pickupSoFolderPath = "Assets/Data/Items";
private string _puzzleSoFolderPath = "Assets/Data/Puzzles";
private enum ItemType { None, Pickup, ItemSlot }
private ItemType _itemType = ItemType.None;
[MenuItem("AppleHills/Item Prefab Editor")]
public static void ShowWindow()
{
var window = GetWindow<ItemPrefabEditorWindow>("Item Prefab Editor");
window.minSize = new Vector2(400, 400);
}
private void OnEnable()
{
Selection.selectionChanged += Repaint;
}
private void OnDisable()
{
Selection.selectionChanged -= Repaint;
}
private void OnGUI()
{
_selectedGameObject = null;
_interactable = null;
if (Selection.activeGameObject != null)
{
_selectedGameObject = Selection.activeGameObject;
_interactable = _selectedGameObject.GetComponent<InteractableBase>();
}
else if (Selection.activeObject is GameObject go)
{
_selectedGameObject = go;
_interactable = go.GetComponent<InteractableBase>();
}
if (_selectedGameObject == null || _interactable == null)
{
EditorGUILayout.HelpBox("Select a GameObject or prefab with an InteractableBase component to edit.", MessageType.Info);
return;
}
EditorGUILayout.LabelField("Editing: ", _selectedGameObject.name, EditorStyles.boldLabel);
EditorGUILayout.Space();
// Determine current type
bool hasPickup = _selectedGameObject.GetComponent<Pickup>() != null;
bool hasSlot = _selectedGameObject.GetComponent<ItemSlot>() != null;
if (hasSlot) _itemType = ItemType.ItemSlot;
else if (hasPickup) _itemType = ItemType.Pickup;
else _itemType = ItemType.None;
// Item type selection
var newType = (ItemType)EditorGUILayout.EnumPopup("Item Type", _itemType);
if (newType != _itemType)
{
// Remove both, then add selected
PrefabEditorUtility.RemoveComponent<Pickup>(_selectedGameObject);
PrefabEditorUtility.RemoveComponent<ItemSlot>(_selectedGameObject);
if (newType == ItemType.Pickup)
PrefabEditorUtility.AddOrGetComponent<Pickup>(_selectedGameObject);
else if (newType == ItemType.ItemSlot)
PrefabEditorUtility.AddOrGetComponent<ItemSlot>(_selectedGameObject);
_itemType = newType;
}
// ObjectiveStepBehaviour
bool hasObjective = _selectedGameObject.GetComponent<ObjectiveStepBehaviour>() != null;
bool addObjective = EditorGUILayout.Toggle("ObjectiveStepBehaviour", hasObjective);
if (addObjective && !hasObjective)
{
PrefabEditorUtility.AddOrGetComponent<ObjectiveStepBehaviour>(_selectedGameObject);
}
else if (!addObjective && hasObjective)
{
PrefabEditorUtility.RemoveComponent<ObjectiveStepBehaviour>(_selectedGameObject);
}
// Pickup Data (for Pickup or ItemSlot)
if (_itemType == ItemType.Pickup || _itemType == ItemType.ItemSlot)
{
var pickup = _selectedGameObject.GetComponent<Pickup>();
_pickupData = pickup.itemData;
EditorGUILayout.LabelField("Pickup Data:", EditorStyles.boldLabel);
_pickupData = (PickupItemData)EditorGUILayout.ObjectField("PickupItemData", _pickupData, typeof(PickupItemData), false);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Save To");
EditorGUILayout.SelectableLabel(_pickupSoFolderPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
if (GUILayout.Button("Select...", GUILayout.Width(80)))
{
_pickupSoFolderPath = PrefabEditorUtility.SelectFolder(_pickupSoFolderPath, "Data/Items");
}
EditorGUILayout.EndHorizontal();
if (_pickupData == null && GUILayout.Button("Create New PickupItemData"))
{
_pickupData = PrefabEditorUtility.CreateScriptableAsset<PickupItemData>(_selectedGameObject.name + "_pickup", _pickupSoFolderPath);
}
if (_pickupData != null)
{
PrefabEditorUtility.DrawScriptableObjectEditor(ref _soEditor, _pickupData);
pickup.itemData = _pickupData;
}
}
// Objective Data
if (addObjective)
{
var obj = _selectedGameObject.GetComponent<ObjectiveStepBehaviour>();
_objectiveData = obj.stepData;
EditorGUILayout.LabelField("Objective Data:", EditorStyles.boldLabel);
_objectiveData = (PuzzleStepSO)EditorGUILayout.ObjectField("PuzzleStepSO", _objectiveData, typeof(PuzzleStepSO), false);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Save To");
EditorGUILayout.SelectableLabel(_puzzleSoFolderPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
if (GUILayout.Button("Select...", GUILayout.Width(80)))
{
_puzzleSoFolderPath = PrefabEditorUtility.SelectFolder(_puzzleSoFolderPath, "Data/Puzzles");
}
EditorGUILayout.EndHorizontal();
if (_objectiveData == null && GUILayout.Button("Create New PuzzleStepSO"))
{
_objectiveData = PrefabEditorUtility.CreateScriptableAsset<PuzzleStepSO>(_selectedGameObject.name + "_puzzle", _puzzleSoFolderPath);
}
if (_objectiveData != null)
{
PrefabEditorUtility.DrawScriptableObjectEditor(ref _soEditor, _objectiveData);
obj.stepData = _objectiveData;
}
}
if (GUI.changed)
{
EditorUtility.SetDirty(_selectedGameObject);
PrefabUtility.RecordPrefabInstancePropertyModifications(_selectedGameObject);
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 943b203cde5343c68a6278c111fce2ed
timeCreated: 1757508162

View File

@@ -1,168 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.IO;
using Interactions;
using PuzzleS;
namespace Editor
{
public class PrefabCreatorWindow : EditorWindow
{
private string _prefabName = "NewPrefab";
private string _saveFolderPath = "Assets/Prefabs/Items";
private string _pickupSoFolderPath = "Assets/Data/Items";
private string _puzzleSoFolderPath = "Assets/Data/Puzzles";
private bool _addObjective;
private PickupItemData _pickupData;
private PuzzleStepSO _objectiveData;
private UnityEditor.Editor _soEditor;
private enum ItemType { None, Pickup, ItemSlot }
private ItemType _itemType = ItemType.None;
private bool _createNext = false;
[MenuItem("AppleHills/Item Prefab Creator")]
public static void ShowWindow()
{
var window = GetWindow<PrefabCreatorWindow>("Prefab Creator");
window.minSize = new Vector2(400, 400);
// Set default paths if not already set
if (string.IsNullOrEmpty(window._saveFolderPath))
window._saveFolderPath = "Assets/Prefabs/Items";
if (string.IsNullOrEmpty(window._pickupSoFolderPath))
window._pickupSoFolderPath = "Assets/Data/Items";
if (string.IsNullOrEmpty(window._puzzleSoFolderPath))
window._puzzleSoFolderPath = "Assets/Data/Puzzles";
}
private void OnGUI()
{
EditorGUILayout.LabelField("Prefab Creator", EditorStyles.boldLabel);
_prefabName = EditorGUILayout.TextField("Prefab Name", _prefabName);
// Prefab save folder
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Save Folder");
EditorGUILayout.SelectableLabel(_saveFolderPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
if (GUILayout.Button("Select...", GUILayout.Width(80)))
{
_saveFolderPath = PrefabEditorUtility.SelectFolder(_saveFolderPath, "Prefabs/Items");
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Add Components:", EditorStyles.boldLabel);
// Item type selection
var newType = (ItemType)EditorGUILayout.EnumPopup("Item Type", _itemType);
if (newType != _itemType)
{
_itemType = newType;
}
bool addObjective = EditorGUILayout.Toggle("ObjectiveStepBehaviour", _addObjective);
_addObjective = addObjective;
EditorGUILayout.Space();
// Pickup Data (for Pickup or ItemSlot)
if (_itemType == ItemType.Pickup || _itemType == ItemType.ItemSlot)
{
EditorGUILayout.LabelField("Pickup Data:", EditorStyles.boldLabel);
_pickupData = (PickupItemData)EditorGUILayout.ObjectField("PickupItemData", _pickupData, typeof(PickupItemData), false);
// Pickup SO save folder
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Save To");
EditorGUILayout.SelectableLabel(_pickupSoFolderPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
if (GUILayout.Button("Select...", GUILayout.Width(80)))
{
_pickupSoFolderPath = PrefabEditorUtility.SelectFolder(_pickupSoFolderPath, "Data/Items");
}
EditorGUILayout.EndHorizontal();
if (_pickupData == null && GUILayout.Button("Create New PickupItemData"))
{
_pickupData = PrefabEditorUtility.CreateScriptableAsset<PickupItemData>(_prefabName + "_pickup", _pickupSoFolderPath);
}
if (_pickupData != null)
{
PrefabEditorUtility.DrawScriptableObjectEditor(ref _soEditor, _pickupData);
}
}
// Objective Data
if (_addObjective)
{
EditorGUILayout.LabelField("Objective Data:", EditorStyles.boldLabel);
_objectiveData = (PuzzleStepSO)EditorGUILayout.ObjectField("PuzzleStepSO", _objectiveData, typeof(PuzzleStepSO), false);
// Puzzle SO save folder
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel("Save To");
EditorGUILayout.SelectableLabel(_puzzleSoFolderPath, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
if (GUILayout.Button("Select...", GUILayout.Width(80)))
{
_puzzleSoFolderPath = PrefabEditorUtility.SelectFolder(_puzzleSoFolderPath, "Data/Puzzles");
}
EditorGUILayout.EndHorizontal();
if (_objectiveData == null && GUILayout.Button("Create New PuzzleStepSO"))
{
_objectiveData = PrefabEditorUtility.CreateScriptableAsset<PuzzleStepSO>(_prefabName + "_puzzle", _puzzleSoFolderPath);
}
if (_objectiveData != null)
{
PrefabEditorUtility.DrawScriptableObjectEditor(ref _soEditor, _objectiveData);
}
}
GUILayout.FlexibleSpace();
EditorGUILayout.BeginHorizontal();
GUI.enabled = !string.IsNullOrEmpty(_prefabName) && !string.IsNullOrEmpty(_saveFolderPath);
if (GUILayout.Button("Create Prefab", GUILayout.Height(28)))
{
CreatePrefab();
}
_createNext = GUILayout.Toggle(_createNext, "Create Next", GUILayout.Width(100), GUILayout.Height(28));
GUI.enabled = true;
EditorGUILayout.EndHorizontal();
}
private void CreatePrefab()
{
var go = new GameObject(_prefabName);
// Note: No need to add InteractableBase separately - Pickup and ItemSlot inherit from it
go.AddComponent<BoxCollider>();
int interactableLayer = LayerMask.NameToLayer("Interactable");
if (interactableLayer != -1)
go.layer = interactableLayer;
go.AddComponent<SpriteRenderer>();
if (_itemType == ItemType.Pickup)
{
var pickup = go.AddComponent<Pickup>();
pickup.itemData = _pickupData;
}
else if (_itemType == ItemType.ItemSlot)
{
var slot = go.AddComponent<ItemSlot>();
slot.itemData = _pickupData;
}
if (_addObjective)
{
var obj = go.AddComponent<ObjectiveStepBehaviour>();
obj.stepData = _objectiveData;
}
string prefabPath = Path.Combine(_saveFolderPath, _prefabName + ".prefab").Replace("\\", "/");
var prefab = PrefabUtility.SaveAsPrefabAsset(go, prefabPath);
DestroyImmediate(go);
AssetDatabase.Refresh();
Selection.activeObject = prefab;
EditorGUIUtility.PingObject(prefab);
EditorUtility.DisplayDialog("Prefab Created", $"Prefab saved to {prefabPath}", "OK");
if (_createNext)
{
_prefabName = "NewPrefab";
_pickupData = null;
_objectiveData = null;
_itemType = ItemType.None;
_addObjective = false;
_soEditor = null;
GUI.FocusControl(null);
Repaint();
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: f67e06e997f642509ba61ea12b0f793e
timeCreated: 1757503955

View File

@@ -248,7 +248,9 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 2bd397a60643eed45b586961ae6e3453, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::PulverAudioController
audioSource: {fileID: 887004370483616855}
combineAudio: {fileID: 8300000, guid: 768a16f348fe1d94c9cc267dc7ecf3b5, type: 3}
itemManager: {fileID: 0}
--- !u!82 &4467608046243604209
AudioSource:
m_ObjectHideFlags: 0

View File

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

View File

@@ -614,6 +614,7 @@ GameObject:
- component: {fileID: 768265498311662326}
- component: {fileID: 6338084184716153992}
- component: {fileID: 253472492358066383}
- component: {fileID: 7628818949793551399}
m_Layer: 10
m_Name: FakeChoco
m_TagString: Untagged
@@ -633,7 +634,8 @@ Transform:
m_LocalPosition: {x: -6.784, y: -2.901, z: 0}
m_LocalScale: {x: 0.7, y: 0.7, z: 0.7}
m_ConstrainProportionsScale: 1
m_Children: []
m_Children:
- {fileID: 2205893234949555645}
m_Father: {fileID: 1509867968154593713}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!212 &6338084184716153992
@@ -740,6 +742,45 @@ BoxCollider2D:
m_AutoTiling: 0
m_Size: {x: 6, y: 6}
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
GameObject:
m_ObjectHideFlags: 0
@@ -1322,6 +1363,104 @@ GameObject:
m_CorrespondingSourceObject: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3}
m_PrefabInstance: {fileID: 3750141998400252915}
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
PrefabInstance:
m_ObjectHideFlags: 0
@@ -1362,6 +1501,34 @@ PrefabInstance:
propertyPath: m_SortingOrder
value: 1
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}
propertyPath: m_LocalScale.x
value: 0.7

View File

@@ -285,8 +285,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: 173}
m_SizeDelta: {x: 600, y: 150}
m_AnchoredPosition: {x: 0, y: 125}
m_SizeDelta: {x: 600, y: 120}
m_Pivot: {x: 0.5, y: 1}
--- !u!222 &5545241165728741220
CanvasRenderer:
@@ -343,8 +343,8 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 150
m_fontSizeBase: 150
m_fontSize: 125
m_fontSizeBase: 125
m_fontWeight: 400
m_enableAutoSizing: 0
m_fontSizeMin: 18
@@ -1053,6 +1053,10 @@ PrefabInstance:
propertyPath: m_SizeDelta.x
value: 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}
propertyPath: m_AnchorMax.x
value: 0

View File

@@ -728,10 +728,18 @@ PrefabInstance:
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 2162084082982493373, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: tabContainer
value:
objectReference: {fileID: 7104076737882304566}
- target: {fileID: 2162084082982493373, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: boosterOpeningPage
value:
objectReference: {fileID: 304904072851265091}
- target: {fileID: 2162084082982493373, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: zoneTabs.Array.size
value: 6
objectReference: {fileID: 0}
- target: {fileID: 2162084082982493373, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: cardEnlargedBackdrop
value:
@@ -740,6 +748,10 @@ PrefabInstance:
propertyPath: cardEnlargedContainer
value:
objectReference: {fileID: 8702856743180718180}
- target: {fileID: 2162084082982493373, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: 'zoneTabs.Array.data[5]'
value:
objectReference: {fileID: 5733094729750047914}
- target: {fileID: 2222053324174596529, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
propertyPath: m_AnchorMax.x
value: 0
@@ -1705,8 +1717,24 @@ PrefabInstance:
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
--- !u!114 &5733094729750047914 stripped
MonoBehaviour:
m_CorrespondingSourceObject: {fileID: 185814890104990467, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
m_PrefabInstance: {fileID: 5549612182461073321}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ff50caabb55742bc8d24a6ddffeda815, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::UI.CardSystem.BookTabButton
--- !u!224 &5906828909529466605 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 2088943967680250180, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
m_PrefabInstance: {fileID: 5549612182461073321}
m_PrefabAsset: {fileID: 0}
--- !u!224 &7104076737882304566 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 3428064805021480863, guid: 88a05fdd940194543ade1cc2bcdada5f, type: 3}
m_PrefabInstance: {fileID: 5549612182461073321}
m_PrefabAsset: {fileID: 0}

File diff suppressed because one or more lines are too long

View File

@@ -453761,6 +453761,10 @@ PrefabInstance:
propertyPath: m_PlayOnAwake
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8545106365577783398, guid: ead4e790fa3a1924ebd1586c93cd5479, type: 3}
propertyPath: isOneTime
value: 1
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []

View File

@@ -1562,7 +1562,7 @@ AudioSource:
OutputAudioMixerGroup: {fileID: 3533147658878909314, guid: 727a7e4b6df4b0d47897f7d8ee7fa323, type: 2}
m_audioClip: {fileID: 0}
m_Resource: {fileID: 0}
m_PlayOnAwake: 1
m_PlayOnAwake: 0
m_Volume: 1
m_Pitch: 1
Loop: 0
@@ -3633,6 +3633,18 @@ PrefabInstance:
propertyPath: m_Name
value: Tutorial
objectReference: {fileID: 0}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: introVO
value:
objectReference: {fileID: 8300000, guid: fca641cdc8dcd074483fad3db1cbe24c, type: 3}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: playTutorial
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: bottleAudioPlayer
value:
objectReference: {fileID: 747976408}
- target: {fileID: 8452897808363562605, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: m_Sprite
value:

View File

@@ -56,7 +56,7 @@ public class BirdEyesBehavior : ManagedBehaviour
_statemachine.ChangeState("BirdSpawned");
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
base.OnSceneRestoreRequested(serializedData);
@@ -75,7 +75,7 @@ public class BirdEyesBehavior : ManagedBehaviour
}
}
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
return _wolterisoutTriggered.ToString();
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using AppleHills.Core.Settings;
using UnityEngine;
using Core;
@@ -30,14 +30,10 @@ namespace Bootstrap
private float _sceneLoadingProgress = 0f;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
// Run very early - need to set up loading screen before other systems initialize
public override int ManagedAwakePriority => 5;
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // Register with LifecycleManager
LogDebugMessage("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
Logging.Debug("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
// Validate loading screen exists
if (initialLoadingScreen == null)
@@ -71,11 +67,11 @@ namespace Bootstrap
}
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
LogDebugMessage("BootSceneController.OnManagedAwake() - Boot is GUARANTEED complete, starting scene loading");
Logging.Debug("BootSceneController.OnManagedStart() - Boot is GUARANTEED complete, starting scene loading");
// Boot is GUARANTEED complete at this point - that's the whole point of OnManagedAwake!
// Boot is GUARANTEED complete at this point - that's the whole point of OnManagedStart!
// No need to subscribe to OnBootCompleted or check CustomBoot.Initialised
_bootComplete = true;
_currentPhase = LoadingPhase.SceneLoading;
@@ -85,10 +81,8 @@ namespace Bootstrap
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Manual cleanup for events
if (initialLoadingScreen != null)
{
@@ -102,12 +96,12 @@ namespace Bootstrap
/// </summary>
private void OnInitialLoadingComplete()
{
LogDebugMessage("Initial loading screen fully hidden, boot sequence completed");
Logging.Debug("Initial loading screen fully hidden, boot sequence completed");
// Play the intro cinematic if available
if (CinematicsManager.Instance != null)
{
LogDebugMessage("Attempting to play intro cinematic");
Logging.Debug("Attempting to play intro cinematic");
// Use LoadAndPlayCinematic to play the intro sequence
CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false);
@@ -149,13 +143,13 @@ namespace Bootstrap
{
if (debugMode)
{
LogDebugMessage($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
Logging.Debug($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
}
}
private void LogDebugInfo()
{
LogDebugMessage($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
Logging.Debug($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
$"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
}
@@ -172,7 +166,7 @@ namespace Bootstrap
private async void LoadMainScene()
{
LogDebugMessage($"Loading main menu scene: {mainSceneName}");
Logging.Debug($"Loading main menu scene: {mainSceneName}");
try
{
@@ -186,7 +180,7 @@ namespace Bootstrap
if (debugMode)
{
LogDebugMessage($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
Logging.Debug($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
}
});
@@ -210,13 +204,13 @@ namespace Bootstrap
_sceneLoadingProgress = 1f;
// CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks
LogDebugMessage($"Broadcasting OnSceneReady for: {mainSceneName}");
Logging.Debug($"Broadcasting OnSceneReady for: {mainSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName);
// Restore scene data for the main menu
if (SaveLoadManager.Instance != null)
{
LogDebugMessage($"Restoring scene data for: {mainSceneName}");
Logging.Debug($"Restoring scene data for: {mainSceneName}");
SaveLoadManager.Instance.RestoreSceneData();
}
@@ -246,7 +240,7 @@ namespace Bootstrap
Scene currentScene = SceneManager.GetActiveScene();
string startingSceneName = currentScene.name;
LogDebugMessage($"Unloading StartingScene: {startingSceneName}");
Logging.Debug($"Unloading StartingScene: {startingSceneName}");
// Unload the StartingScene
await SceneManager.UnloadSceneAsync(startingSceneName);
@@ -255,14 +249,14 @@ namespace Bootstrap
Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName);
SceneManager.SetActiveScene(mainMenuScene);
LogDebugMessage($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active");
Logging.Debug($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active");
// Destroy the boot scene controller since its job is done
Destroy(gameObject);
}
catch (Exception e)
{
Logging.Warning($"[BootSceneController] Error unloading StartingScene: {e.Message}");
Logging.Warning($"Error unloading StartingScene: {e.Message}");
}
}
@@ -283,13 +277,5 @@ namespace Bootstrap
_progressAction?.Invoke(value);
}
}
private void LogDebugMessage(string message)
{
if ( _logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[BootSceneController] {message}");
}
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using UnityEngine;
@@ -105,7 +104,7 @@ namespace Bootstrap
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()");
Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
@@ -127,7 +126,7 @@ namespace Bootstrap
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()");
Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
@@ -238,16 +237,7 @@ namespace Bootstrap
{
CurrentProgress = Mathf.Clamp01(progress);
OnBootProgressChanged?.Invoke(CurrentProgress);
LogDebugMessage($"Progress: {CurrentProgress:P0}");
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[CustomBoot] {message}");
}
Logging.Debug($"Progress: {CurrentProgress:P0}");
}
}
}

View File

@@ -138,7 +138,7 @@ namespace Bootstrap
float displayProgress = Mathf.Min(steadyProgress, actualProgress);
// Log the progress values for debugging
LogDebugMessage($"Progress - Default: {steadyProgress:F2}, Actual: {actualProgress:F2}, Display: {displayProgress:F2}");
Logging.Debug($"Progress - Default: {steadyProgress:F2}, Actual: {actualProgress:F2}, Display: {displayProgress:F2}");
// Directly set the progress bar fill amount without smoothing
if (progressBarImage != null)
@@ -151,7 +151,7 @@ namespace Bootstrap
if (steadyProgress >= 1.0f && displayProgress >= 1.0f)
{
_animationComplete = true;
LogDebugMessage("Animation complete");
Logging.Debug("Animation complete");
break;
}
@@ -163,7 +163,7 @@ namespace Bootstrap
if (progressBarImage != null)
{
progressBarImage.fillAmount = 1.0f;
LogDebugMessage("Final progress set to 1.0");
Logging.Debug("Final progress set to 1.0");
}
// Hide the screen if loading is also complete
@@ -172,7 +172,7 @@ namespace Bootstrap
if (loadingScreenContainer != null)
{
loadingScreenContainer.SetActive(false);
LogDebugMessage("Animation AND loading complete, hiding screen");
Logging.Debug("Animation AND loading complete, hiding screen");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
@@ -189,7 +189,7 @@ namespace Bootstrap
/// </summary>
public void HideLoadingScreen()
{
LogDebugMessage("Loading complete, marking loading as finished");
Logging.Debug("Loading complete, marking loading as finished");
// Mark that loading is complete
_loadingComplete = true;
@@ -200,7 +200,7 @@ namespace Bootstrap
if (loadingScreenContainer != null)
{
loadingScreenContainer.SetActive(false);
LogDebugMessage("Animation already complete, hiding screen immediately");
Logging.Debug("Animation already complete, hiding screen immediately");
// Invoke the callback when fully hidden
_onLoadingScreenFullyHidden?.Invoke();
@@ -210,7 +210,7 @@ namespace Bootstrap
}
else
{
LogDebugMessage("Animation still in progress, waiting for it to complete");
Logging.Debug("Animation still in progress, waiting for it to complete");
// The coroutine will handle hiding when animation completes
}
}
@@ -244,13 +244,5 @@ namespace Bootstrap
return tcs.Task;
}
private void LogDebugMessage(string message)
{
if ( _logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[InitialLoadingScreen] {message}");
}
}
}
}

View File

@@ -37,17 +37,13 @@ namespace Cinematics
public PlayableDirector playableDirector;
public override int ManagedAwakePriority => 170; // Cinematic systems
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[CinematicsManager] Initialized");
}
@@ -86,7 +82,7 @@ namespace Cinematics
// If still null, try to add the component
if (playableDirector == null)
{
Debug.Log("[CinematicsManager] Could not find Playable Director on the PlayerHudManager");
Logging.Debug("[CinematicsManager] Could not find Playable Director on the PlayerHudManager");
}
}
@@ -99,7 +95,7 @@ namespace Cinematics
// If still null, return error
if (_cinematicSprites == null)
{
Debug.LogWarning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly.");
Logging.Warning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly.");
}
}

View File

@@ -15,11 +15,8 @@ namespace Cinematics
private float _holdStartTime;
private bool _isHolding;
private bool _skipPerformed;
private bool _initialized = false;
public override int ManagedAwakePriority => 180; // Cinematic UI
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Reset the progress bar
if (radialProgressBar != null)
@@ -33,10 +30,8 @@ namespace Cinematics
Logging.Debug("[SkipCinematic] Initialized");
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Clean up subscriptions
UnsubscribeFromCinematicsEvents();
}

View File

@@ -34,17 +34,13 @@ namespace Core
public event Action OnGamePaused;
public event Action OnGameResumed;
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 10; // Core infrastructure - runs early
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Create settings providers - must happen in Awake so other managers can access settings in their ManagedAwake
// Create settings providers - must happen in OnManagedAwake so other managers can access settings in their ManagedStart
SettingsProvider.Instance.gameObject.name = "Settings Provider";
DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider";
@@ -57,9 +53,9 @@ namespace Core
_managerLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().gameManagerLogVerbosity;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Settings are already initialized in Awake()
// Settings are already initialized in OnManagedAwake()
// This is available for future initialization that depends on other managers
}
@@ -79,7 +75,7 @@ namespace Core
component.Pause();
}
LogDebugMessage($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
Logging.Debug($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
}
}
@@ -92,7 +88,7 @@ namespace Core
if (component != null && _pausableComponents.Contains(component))
{
_pausableComponents.Remove(component);
LogDebugMessage($"Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
Logging.Debug($"Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
}
}
@@ -108,7 +104,7 @@ namespace Core
ApplyPause(true);
}
LogDebugMessage($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
Logging.Debug($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
}
/// <summary>
@@ -123,7 +119,7 @@ namespace Core
ApplyPause(false);
}
LogDebugMessage($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
Logging.Debug($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
}
/// <summary>
@@ -162,12 +158,12 @@ namespace Core
OnGameResumed?.Invoke();
}
LogDebugMessage($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components.");
Logging.Debug($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components.");
}
private void InitializeSettings()
{
LogDebugMessage("Starting settings initialization...", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("Starting settings initialization...");
// Load settings synchronously
var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous<PlayerFollowerSettings>();
@@ -178,7 +174,7 @@ namespace Core
if (playerSettings != null)
{
ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings);
LogDebugMessage("PlayerFollowerSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("PlayerFollowerSettings registered successfully");
}
else
{
@@ -188,7 +184,7 @@ namespace Core
if (interactionSettings != null)
{
ServiceLocator.Register<IInteractionSettings>(interactionSettings);
LogDebugMessage("InteractionSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("InteractionSettings registered successfully");
}
else
{
@@ -198,7 +194,7 @@ namespace Core
if (minigameSettings != null)
{
ServiceLocator.Register<IDivingMinigameSettings>(minigameSettings);
LogDebugMessage("MinigameSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("MinigameSettings registered successfully");
}
else
{
@@ -209,7 +205,7 @@ namespace Core
_settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null;
if (_settingsLoaded)
{
LogDebugMessage("All settings loaded and registered with ServiceLocator", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("All settings loaded and registered with ServiceLocator");
}
else
{
@@ -222,7 +218,7 @@ namespace Core
/// </summary>
private void InitializeDeveloperSettings()
{
LogDebugMessage("Starting developer settings initialization...", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("Starting developer settings initialization...");
// Load developer settings
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
@@ -232,7 +228,7 @@ namespace Core
if (_developerSettingsLoaded)
{
LogDebugMessage("All developer settings loaded successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("All developer settings loaded successfully");
}
else
{
@@ -266,19 +262,6 @@ namespace Core
{
return DeveloperSettingsProvider.Instance?.GetSettings<T>();
}
private void LogDebugMessage(string message, string prefix = "GameManager", LogVerbosity verbosity = LogVerbosity.None)
{
if (verbosity == LogVerbosity.None)
{
verbosity = _managerLogVerbosity;
}
if ( verbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[{prefix}] {message}");
}
}
// LEFTOVER LEGACY SETTINGS
public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f;

View File

@@ -47,32 +47,26 @@ namespace Core
// Broadcasts when any two items are successfully combined
// Args: first item data, second item data, result item data
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
public override int ManagedAwakePriority => 75; // Item registry
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[ItemManager] Initialized");
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
// Replaces SceneLoadStarted subscription for clearing registrations
ClearAllRegistrations();
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
ClearAllRegistrations();
}
@@ -144,7 +138,6 @@ namespace Core
{
s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
s.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
}
}
@@ -191,7 +184,6 @@ namespace Core
// Subscribe to all slot events
slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted += ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved;
}
}
@@ -204,7 +196,6 @@ namespace Core
// Unsubscribe from all slot events
slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
}
}

View File

@@ -6,12 +6,19 @@
/// </summary>
public enum LifecyclePhase
{
/// <summary>
/// Called immediately during registration (during Awake).
/// Use for early initialization such as setting singleton instances.
/// NOT ordered - fires whenever Unity calls this component's Awake().
/// </summary>
ManagedAwake,
/// <summary>
/// Called once per component after bootstrap completes.
/// Guaranteed to be called after all bootstrap resources are loaded.
/// For late-registered components, called immediately upon registration.
/// </summary>
ManagedAwake,
ManagedStart,
/// <summary>
/// Called before a scene is unloaded.

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -27,7 +27,7 @@ namespace Core.Lifecycle
{
if (_instance != null)
{
Debug.LogWarning("[LifecycleManager] Instance already exists");
Logging.Warning("[LifecycleManager] Instance already exists");
return;
}
@@ -35,7 +35,7 @@ namespace Core.Lifecycle
_instance = go.AddComponent<LifecycleManager>();
DontDestroyOnLoad(go);
Debug.Log("[LifecycleManager] Instance created");
Logging.Debug("[LifecycleManager] Instance created");
}
#endregion
@@ -59,11 +59,11 @@ namespace Core.Lifecycle
#region State Flags
private bool isBootComplete = false;
private bool isBootComplete;
private string currentSceneReady = "";
// Scene loading state tracking
private bool isLoadingScene = false;
private bool isLoadingScene;
private string sceneBeingLoaded = "";
private List<ManagedBehaviour> pendingSceneComponents = new List<ManagedBehaviour>();
@@ -85,7 +85,7 @@ namespace Core.Lifecycle
}
else if (_instance != this)
{
Debug.LogWarning("[LifecycleManager] Duplicate instance detected. Destroying.");
Logging.Warning("[LifecycleManager] Duplicate instance detected. Destroying.");
Destroy(gameObject);
}
}
@@ -111,7 +111,7 @@ namespace Core.Lifecycle
{
if (component == null)
{
Debug.LogWarning("[LifecycleManager] Attempted to register null component");
Logging.Warning("[LifecycleManager] Attempted to register null component");
return;
}
@@ -120,43 +120,49 @@ namespace Core.Lifecycle
// Track which scene this component belongs to
componentScenes[component] = sceneName;
// ALWAYS add to managedAwakeList - this is the master list used for save/load
InsertSorted(managedAwakeList, component, component.ManagedAwakePriority);
// Add to all lifecycle lists (order of registration determines execution order)
managedAwakeList.Add(component);
sceneUnloadingList.Add(component);
sceneReadyList.Add(component);
saveRequestedList.Add(component);
restoreRequestedList.Add(component);
destroyList.Add(component);
try
{
component.OnManagedAwake();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
}
// Handle ManagedAwake timing based on boot state
// Handle OnManagedStart timing based on boot state
if (isBootComplete)
{
// Check if we're currently loading a scene
if (isLoadingScene && sceneName == sceneBeingLoaded)
{
// Batch this component - will be processed in priority order when scene load completes
// Batch this component - will be processed when scene load completes
pendingSceneComponents.Add(component);
LogDebug($"Batched component for scene load: {component.gameObject.name} (Scene: {sceneName})");
}
else
{
// Truly late registration (component enabled after scene is ready)
// Call OnManagedAwake immediately since boot already completed
LogDebug($"Late registration: Calling OnManagedAwake immediately for {component.gameObject.name}");
// Call OnManagedStart immediately since boot already completed
LogDebug($"Late registration: Calling OnManagedStart immediately for {component.gameObject.name}");
try
{
component.InvokeManagedAwake();
component.OnManagedStart();
HandleAutoRegistrations(component);
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for {component.gameObject.name}: {ex}");
}
}
}
// If boot not complete, component stays in list and will be processed by BroadcastManagedAwake()
// Register for all scene lifecycle hooks
InsertSorted(sceneUnloadingList, component, component.SceneUnloadingPriority);
InsertSorted(sceneReadyList, component, component.SceneReadyPriority);
InsertSorted(saveRequestedList, component, component.SavePriority);
InsertSorted(restoreRequestedList, component, component.RestorePriority);
InsertSorted(destroyList, component, component.DestroyPriority);
// If boot not complete, component stays in list and will be processed by BroadcastManagedStart()
// If this scene is already ready (and we're not in loading mode), call OnSceneReady immediately
if (!isLoadingScene && currentSceneReady == sceneName)
@@ -164,7 +170,7 @@ namespace Core.Lifecycle
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
try
{
component.InvokeSceneReady();
component.OnSceneReady();
}
catch (Exception ex)
{
@@ -202,7 +208,7 @@ namespace Core.Lifecycle
/// <summary>
/// Called by CustomBoot when boot completes.
/// Broadcasts ManagedAwake to all registered components.
/// Broadcasts ManagedStart to all registered components.
/// </summary>
public void OnBootCompletionTriggered()
{
@@ -210,16 +216,16 @@ namespace Core.Lifecycle
return;
LogDebug("=== Boot Completion Triggered ===");
BroadcastManagedAwake();
BroadcastManagedStart();
isBootComplete = true;
}
/// <summary>
/// Broadcast OnManagedAwake to all registered components (priority ordered).
/// Broadcast OnManagedStart to all registered components (priority ordered).
/// </summary>
private void BroadcastManagedAwake()
private void BroadcastManagedStart()
{
LogDebug($"Broadcasting ManagedAwake to {managedAwakeList.Count} components");
LogDebug($"Broadcasting ManagedStart to {managedAwakeList.Count} components");
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
@@ -230,12 +236,12 @@ namespace Core.Lifecycle
try
{
component.InvokeManagedAwake();
component.OnManagedStart();
HandleAutoRegistrations(component);
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for {component.gameObject.name}: {ex}");
}
}
@@ -272,23 +278,20 @@ namespace Core.Lifecycle
LogDebug($"Processing {pendingSceneComponents.Count} batched components for scene: {sceneBeingLoaded}");
// Sort by ManagedAwake priority (lower values first)
pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority));
// Call OnManagedAwake in priority order
// Call OnManagedStart in registration order
foreach (var component in pendingSceneComponents)
{
if (component == null) continue;
try
{
component.InvokeManagedAwake();
component.OnManagedStart();
HandleAutoRegistrations(component);
LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})");
LogDebug($"Processed batched component: {component.gameObject.name}");
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for batched component {component.gameObject.name}: {ex}");
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for batched component {component.gameObject.name}: {ex}");
}
}
@@ -299,7 +302,7 @@ namespace Core.Lifecycle
}
/// <summary>
/// Broadcast OnSceneUnloading to components in the specified scene (reverse priority order).
/// Broadcast OnSceneUnloading to components in the specified scene.
/// </summary>
public void BroadcastSceneUnloading(string sceneName)
{
@@ -315,7 +318,7 @@ namespace Core.Lifecycle
{
try
{
component.InvokeSceneUnloading();
component.OnSceneUnloading();
}
catch (Exception ex)
{
@@ -326,8 +329,8 @@ namespace Core.Lifecycle
}
/// <summary>
/// Broadcast OnSceneReady to components in the specified scene (priority order).
/// If scene loading mode is active, processes batched components first.
/// Broadcast OnSceneReady to components in the specified scene.
/// Processes batched components first, then calls OnSceneReady on all components in that scene.
/// </summary>
public void BroadcastSceneReady(string sceneName)
{
@@ -351,7 +354,7 @@ namespace Core.Lifecycle
{
try
{
component.InvokeSceneReady();
component.OnSceneReady();
}
catch (Exception ex)
{
@@ -379,7 +382,7 @@ namespace Core.Lifecycle
try
{
string serializedData = component.InvokeSceneSaveRequested();
string serializedData = component.OnSceneSaveRequested();
if (!string.IsNullOrEmpty(serializedData))
{
string saveId = component.SaveId;
@@ -415,7 +418,7 @@ namespace Core.Lifecycle
try
{
string serializedData = component.InvokeGlobalSaveRequested();
string serializedData = component.OnGlobalSaveRequested();
if (!string.IsNullOrEmpty(serializedData))
{
saveData[component.SaveId] = serializedData;
@@ -455,7 +458,7 @@ namespace Core.Lifecycle
{
try
{
component.InvokeSceneRestoreRequested(serializedData);
component.OnSceneRestoreRequested(serializedData);
restoredCount++;
LogDebug($"Restored scene data to: {component.SaveId}");
}
@@ -469,6 +472,34 @@ namespace Core.Lifecycle
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.OnSceneRestoreCompleted();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during scene restore completed for {component.SaveId}: {ex}");
}
}
LogDebug("SceneRestoreCompleted broadcast complete");
}
/// <summary>
/// Broadcasts global restore request to all registered components that opt-in.
/// Distributes serialized data to matching components by SaveId.
@@ -491,7 +522,7 @@ namespace Core.Lifecycle
{
try
{
component.InvokeGlobalRestoreRequested(serializedData);
component.OnGlobalRestoreRequested(serializedData);
restoredCount++;
LogDebug($"Restored global data to: {component.SaveId}");
}
@@ -523,7 +554,7 @@ namespace Core.Lifecycle
try
{
component.InvokeGlobalLoadCompleted();
component.OnGlobalLoadCompleted();
}
catch (Exception ex)
{
@@ -550,7 +581,7 @@ namespace Core.Lifecycle
try
{
component.InvokeGlobalSaveStarted();
component.OnGlobalSaveStarted();
}
catch (Exception ex)
{
@@ -583,42 +614,6 @@ namespace Core.Lifecycle
#endregion
#region Helper Methods
/// <summary>
/// Insert component into list maintaining sorted order by priority.
/// Uses binary search for efficient insertion.
/// </summary>
private void InsertSorted(List<ManagedBehaviour> list, ManagedBehaviour component, int priority)
{
// Simple linear insertion for now (can optimize with binary search later if needed)
int index = 0;
for (int i = 0; i < list.Count; i++)
{
int existingPriority = GetPriorityForList(list[i], list);
if (priority < existingPriority)
{
index = i;
break;
}
index = i + 1;
}
list.Insert(index, component);
}
/// <summary>
/// Get the priority value for a component based on which list it's in.
/// </summary>
private int GetPriorityForList(ManagedBehaviour component, List<ManagedBehaviour> list)
{
if (list == managedAwakeList) return component.ManagedAwakePriority;
if (list == sceneUnloadingList) return component.SceneUnloadingPriority;
if (list == sceneReadyList) return component.SceneReadyPriority;
if (list == saveRequestedList) return component.SavePriority;
if (list == restoreRequestedList) return component.RestorePriority;
if (list == destroyList) return component.DestroyPriority;
return 100;
}
/// <summary>
/// Log debug message if debug logging is enabled.
@@ -627,7 +622,7 @@ namespace Core.Lifecycle
{
if (enableDebugLogging)
{
Debug.Log($"[LifecycleManager] {message}");
Logging.Debug($"[LifecycleManager] {message}");
}
}

View File

@@ -1,5 +1,4 @@
using System;
using UnityEngine;
using UnityEngine;
namespace Core.Lifecycle
{
@@ -9,46 +8,6 @@ namespace Core.Lifecycle
/// </summary>
public abstract class ManagedBehaviour : MonoBehaviour
{
#region Priority Properties
/// <summary>
/// Priority for OnManagedAwake (lower values execute first).
/// Default: 100
/// </summary>
public virtual int ManagedAwakePriority => 100;
/// <summary>
/// Priority for OnSceneUnloading (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int SceneUnloadingPriority => 100;
/// <summary>
/// Priority for OnSceneReady (lower values execute first).
/// Default: 100
/// </summary>
public virtual int SceneReadyPriority => 100;
/// <summary>
/// Priority for OnSaveRequested (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int SavePriority => 100;
/// <summary>
/// Priority for OnRestoreRequested (lower values execute first).
/// Default: 100
/// </summary>
public virtual int RestorePriority => 100;
/// <summary>
/// Priority for OnManagedDestroy (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int DestroyPriority => 100;
#endregion
#region Configuration Properties
/// <summary>
@@ -68,38 +27,28 @@ namespace Core.Lifecycle
/// Unique identifier for this component in the save system.
/// Default: "SceneName/GameObjectName/ComponentType"
/// Override ONLY for special cases (e.g., singletons like "PlayerController", or custom IDs).
/// Cached on first access to avoid runtime allocation.
/// </summary>
public virtual string SaveId
{
get
{
string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene";
string componentType = GetType().Name;
return $"{sceneName}/{gameObject.name}/{componentType}";
if (_cachedSaveId == null)
{
string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene";
string componentType = GetType().Name;
_cachedSaveId = $"{sceneName}/{gameObject.name}/{componentType}";
}
return _cachedSaveId;
}
}
#endregion
#region Public Accessors (for LifecycleManager)
// Public wrappers to invoke protected lifecycle methods
public void InvokeManagedAwake() => OnManagedAwake();
public void InvokeSceneUnloading() => OnSceneUnloading();
public void InvokeSceneReady() => OnSceneReady();
public string InvokeSceneSaveRequested() => OnSceneSaveRequested();
public void InvokeSceneRestoreRequested(string data) => OnSceneRestoreRequested(data);
public string InvokeGlobalSaveRequested() => OnGlobalSaveRequested();
public void InvokeGlobalRestoreRequested(string data) => OnGlobalRestoreRequested(data);
public void InvokeManagedDestroy() => OnManagedDestroy();
public void InvokeGlobalLoadCompleted() => OnGlobalLoadCompleted();
public void InvokeGlobalSaveStarted() => OnGlobalSaveStarted();
#endregion
#region Private Fields
private bool _isRegistered;
private string _cachedSaveId;
#endregion
@@ -107,9 +56,9 @@ namespace Core.Lifecycle
/// <summary>
/// Unity Awake - automatically registers with LifecycleManager.
/// IMPORTANT: Derived classes that override Awake MUST call base.Awake()
/// SEALED: Cannot be overridden. Use OnManagedAwake() for early initialization or OnManagedStart() for late initialization.
/// </summary>
protected virtual void Awake()
private void Awake()
{
if (LifecycleManager.Instance != null)
{
@@ -118,19 +67,22 @@ namespace Core.Lifecycle
}
else
{
Debug.LogWarning($"[ManagedBehaviour] LifecycleManager not found for {gameObject.name}. Component will not receive lifecycle callbacks.");
Logging.Warning($"[ManagedBehaviour] LifecycleManager not found for {gameObject.name}. Component will not receive lifecycle callbacks.");
}
}
/// <summary>
/// Unity OnDestroy - automatically unregisters and cleans up.
/// IMPORTANT: Derived classes that override OnDestroy MUST call base.OnDestroy()
/// SEALED: Cannot be overridden. Use OnManagedDestroy() for custom cleanup logic.
/// </summary>
protected virtual void OnDestroy()
private void OnDestroy()
{
if (!_isRegistered)
return;
// Call managed destroy hook
OnManagedDestroy();
// Unregister from LifecycleManager
if (LifecycleManager.Instance != null)
{
@@ -151,34 +103,47 @@ namespace Core.Lifecycle
#region Managed Lifecycle Hooks
/// <summary>
/// Called immediately during registration (during Awake).
/// Use for early initialization such as setting singleton instances.
/// TIMING: Fires during component's Awake(), no execution order guarantees between components.
/// NOT priority-ordered - fires whenever Unity calls this component's Awake().
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnManagedAwake()
{
// Override in derived classes
}
/// <summary>
/// Called once per component after bootstrap completes.
/// GUARANTEE: Bootstrap resources are available, all managers are initialized.
/// For boot-time components: Called during LifecycleManager.BroadcastManagedAwake (priority ordered).
/// For boot-time components: Called during LifecycleManager.BroadcastManagedStart (registration order).
/// For late-registered components: Called immediately upon registration (bootstrap already complete).
/// Replaces the old Awake + InitializePostBoot pattern.
/// Use for initialization that depends on other systems.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnManagedAwake()
internal virtual void OnManagedStart()
{
// Override in derived classes
}
/// <summary>
/// Called before the scene this component belongs to is unloaded.
/// Called in REVERSE priority order (higher values execute first).
/// Use for scene-specific cleanup.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnSceneUnloading()
internal virtual void OnSceneUnloading()
{
// Override in derived classes
}
/// <summary>
/// Called after the scene this component belongs to has finished loading.
/// Called in priority order (lower values execute first).
/// Use for scene-specific initialization.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnSceneReady()
internal virtual void OnSceneReady()
{
// Override in derived classes
}
@@ -192,8 +157,10 @@ namespace Core.Lifecycle
/// - Called BEFORE scene unload during scene transitions
/// - Frequency: Every scene transition
/// - Use for: Level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual string OnSceneSaveRequested()
internal virtual string OnSceneSaveRequested()
{
return null; // Default: no data to save
}
@@ -202,12 +169,43 @@ namespace Core.Lifecycle
/// Called during scene transitions to restore scene-specific state.
/// 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:
/// - Called AFTER scene load, during OnSceneReady phase
/// - Frequency: Every scene transition
/// - Use for: Restoring level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnSceneRestoreRequested(string serializedData)
internal virtual void OnSceneRestoreRequested(string serializedData)
{
// 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).
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnSceneRestoreCompleted()
{
// Default: no-op
}
@@ -220,8 +218,10 @@ namespace Core.Lifecycle
/// - Called ONCE on game boot after save file is read
/// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnGlobalRestoreRequested(string serializedData)
internal virtual void OnGlobalRestoreRequested(string serializedData)
{
// Default: no-op
}
@@ -235,8 +235,10 @@ namespace Core.Lifecycle
/// - Called ONCE before save file is written (on quit, manual save, etc.)
/// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual string OnGlobalSaveRequested()
internal virtual string OnGlobalSaveRequested()
{
return null; // Default: no data to save
}
@@ -250,8 +252,10 @@ namespace Core.Lifecycle
/// - Called ONCE on game boot after all restore operations complete
/// - NOT called during scene transitions
/// - Use for: Triggering UI updates, broadcasting load events
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnGlobalLoadCompleted()
internal virtual void OnGlobalLoadCompleted()
{
// Default: no-op
}
@@ -265,19 +269,21 @@ namespace Core.Lifecycle
/// - Called ONCE before save file is written
/// - NOT called during scene transitions
/// - Use for: Final validation, cleanup operations
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnGlobalSaveStarted()
internal virtual void OnGlobalSaveStarted()
{
// Default: no-op
}
/// <summary>
/// Called during OnDestroy before component is destroyed.
/// Called in REVERSE priority order (higher values execute first).
/// NOTE: Most cleanup is automatic (managed events, auto-registrations).
/// Only override if you need custom cleanup logic.
/// Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
protected virtual void OnManagedDestroy()
internal virtual void OnManagedDestroy()
{
// Override in derived classes
}

View File

@@ -1,17 +1,144 @@
namespace Core
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Core
{
/// <summary>
/// Centralized logging system with automatic class/method tagging and editor integration.
/// Broadcasts log entries to custom editor windows for filtering and analysis.
/// </summary>
public static class Logging
{
[System.Diagnostics.Conditional("ENABLE_LOG")]
public static void Debug(object message)
/// <summary>
/// Event fired when a new log entry is added. Subscribe to this in editor windows.
/// </summary>
public static event Action<LogEntry> OnLogEntryAdded;
// Store recent logs for late-subscriber editor windows (e.g., windows opened after play mode started)
private static readonly List<LogEntry> RecentLogs = new List<LogEntry>();
private const int MaxStoredLogs = 5000; // Prevent memory bloat
/// <summary>
/// Get all recent logs. Used by editor windows when they first open.
/// </summary>
public static IReadOnlyList<LogEntry> GetRecentLogs() => RecentLogs;
/// <summary>
/// Clear all stored logs. Useful for editor windows "Clear" button.
/// </summary>
public static void ClearLogs()
{
UnityEngine.Debug.Log(message);
RecentLogs.Clear();
}
[System.Diagnostics.Conditional("ENABLE_LOG")]
public static void Warning(object message)
public static void Debug(string message,
[CallerFilePath] string filePath = "",
[CallerMemberName] string memberName = "")
{
UnityEngine.Debug.LogWarning(message);
LogInternal(LogLevel.Debug, message, filePath, memberName);
}
[System.Diagnostics.Conditional("ENABLE_LOG")]
public static void Info(string message,
[CallerFilePath] string filePath = "",
[CallerMemberName] string memberName = "")
{
LogInternal(LogLevel.Info, message, filePath, memberName);
}
[System.Diagnostics.Conditional("ENABLE_LOG")]
public static void Warning(string message,
[CallerFilePath] string filePath = "",
[CallerMemberName] string memberName = "")
{
LogInternal(LogLevel.Warning, message, filePath, memberName);
}
public static void Error(string message,
[CallerFilePath] string filePath = "",
[CallerMemberName] string memberName = "")
{
LogInternal(LogLevel.Error, message, filePath, memberName);
}
private static void LogInternal(LogLevel level, string message, string filePath, string memberName)
{
string className = Path.GetFileNameWithoutExtension(filePath);
string formattedMessage = $"[{className}][{memberName}] {message}";
// Create log entry
var entry = new LogEntry(className, memberName, message, level, Time.realtimeSinceStartup);
// Store for late subscribers
RecentLogs.Add(entry);
if (RecentLogs.Count > MaxStoredLogs)
RecentLogs.RemoveAt(0);
// Broadcast to editor windows (editor-only, won't fire in builds)
OnLogEntryAdded?.Invoke(entry);
// Also log to Unity console
switch (level)
{
case LogLevel.Debug:
case LogLevel.Info:
UnityEngine.Debug.Log(formattedMessage);
break;
case LogLevel.Warning:
UnityEngine.Debug.LogWarning(formattedMessage);
break;
case LogLevel.Error:
UnityEngine.Debug.LogError(formattedMessage);
break;
}
}
}
/// <summary>
/// Represents a single log entry with class, method, message, level, and timestamp.
/// </summary>
public class LogEntry
{
public string ClassName { get; }
public string MethodName { get; }
public string Message { get; }
public LogLevel Level { get; }
public float Timestamp { get; }
public LogEntry(string className, string methodName, string message, LogLevel level, float timestamp)
{
ClassName = className;
MethodName = methodName;
Message = message;
Level = level;
Timestamp = timestamp;
}
/// <summary>
/// Formatted message with class and method tags.
/// Format: [ClassName][MethodName] Message
/// </summary>
public string FormattedMessage => $"[{ClassName}][{MethodName}] {Message}";
/// <summary>
/// Full formatted message with timestamp and level.
/// Format: [12.34s][Debug][ClassName][MethodName] Message
/// </summary>
public string FullFormattedMessage => $"[{Timestamp:F2}s][{Level}]{FormattedMessage}";
}
/// <summary>
/// Log severity levels.
/// </summary>
public enum LogLevel
{
Debug,
Info,
Warning,
Error
}
}

View File

@@ -24,9 +24,6 @@ namespace AppleHills.Core
#endregion Singleton Setup
// Very early initialization - QuickAccess should be available immediately
public override int ManagedAwakePriority => 5;
#region Manager Instances
// Core Managers
@@ -129,20 +126,18 @@ namespace AppleHills.Core
#region Lifecycle Methods
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// QuickAccess has minimal initialization
}
protected override void OnSceneUnloading()
internal override void OnSceneUnloading()
{
// Clear references BEFORE scene unloads for better cleanup timing
ClearReferences();

View File

@@ -88,7 +88,7 @@ namespace Core.SaveLoad
}
else
{
Debug.LogWarning($"[AppleMachine] SaveLoadManager not available for '{name}'", this);
Logging.Warning($"[AppleMachine] SaveLoadManager not available for '{name}'");
}
}
@@ -98,7 +98,7 @@ namespace Core.SaveLoad
// Optional: Log the auto-generated ID in verbose mode
if (verbose && string.IsNullOrEmpty(customSaveId))
{
Debug.Log($"[SaveableStateMachine] '{name}' will use auto-generated Save ID: {GetSaveId()}", this);
Logging.Debug($"[SaveableStateMachine] '{name}' will use auto-generated Save ID: {GetSaveId()}");
}
}
#endif
@@ -158,7 +158,7 @@ namespace Core.SaveLoad
{
if (verbose)
{
Debug.LogWarning($"[SaveableStateMachine] No data to restore for '{name}'", this);
Logging.Warning($"[SaveableStateMachine] No data to restore for '{name}'");
}
return;
}
@@ -171,7 +171,7 @@ namespace Core.SaveLoad
{
if (verbose)
{
Debug.LogWarning($"[SaveableStateMachine] No state name in save data for '{name}'", this);
Logging.Warning($"[SaveableStateMachine] No state name in save data for '{name}'");
}
return;
}
@@ -197,7 +197,7 @@ namespace Core.SaveLoad
if (verbose)
{
Debug.Log($"[SaveableStateMachine] Restored '{name}' to state: {saveData.stateName}", this);
Logging.Debug($"[SaveableStateMachine] Restored '{name}' to state: {saveData.stateName}");
}
}
catch (System.Exception ex)
@@ -215,14 +215,14 @@ namespace Core.SaveLoad
[ContextMenu("Log Save ID")]
private void LogSaveId()
{
Debug.Log($"Save ID: {GetSaveId()}", this);
Logging.Debug($"Save ID: {GetSaveId()}");
}
[ContextMenu("Test Serialize")]
private void TestSerialize()
{
string serialized = SerializeState();
Debug.Log($"Serialized state: {serialized}", this);
Logging.Debug($"Serialized state: {serialized}");
}
#endif

View File

@@ -43,14 +43,10 @@ namespace Core.SaveLoad
public event Action<string> OnLoadCompleted;
public event Action OnParticipantStatesRestored;
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 20; // After GameManager and SceneManagerService
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Initialize critical state immediately
@@ -58,7 +54,7 @@ namespace Core.SaveLoad
IsRestoringState = false;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[SaveLoadManager] Initialized");
@@ -69,19 +65,19 @@ namespace Core.SaveLoad
}
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
// SaveableInteractables now auto-register via ManagedBehaviour lifecycle
// No need to discover and register them manually
}
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
// SaveLoadManager orchestrates saves, doesn't participate in them
return null;
}
protected override string OnGlobalSaveRequested()
internal override string OnGlobalSaveRequested()
{
// SaveLoadManager orchestrates saves, doesn't participate in them
return null;
@@ -97,10 +93,8 @@ namespace Core.SaveLoad
// ...existing code...
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy(); // Important: call base to unregister from LifecycleManager
if (_instance == this)
{
_instance = null;
@@ -504,9 +498,19 @@ namespace Core.SaveLoad
/// </summary>
public void RestoreSceneData()
{
if (Lifecycle.LifecycleManager.Instance == null)
{
Logging.Warning("[SaveLoadManager] LifecycleManager not available for scene restore");
return;
}
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;
}
@@ -520,11 +524,12 @@ namespace Core.SaveLoad
}
// 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>
@@ -706,7 +711,7 @@ namespace Core.SaveLoad
{
IsSaving = false;
OnSaveCompleted?.Invoke(slot);
Debug.Log($"[SaveLoadManager] Save completed for slot '{slot}'");
Logging.Debug($"[SaveLoadManager] Save completed for slot '{slot}'");
}
}

View File

@@ -30,9 +30,9 @@ namespace Core
// Enable save/load participation
public override bool AutoRegisterForSave => true;
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake();
base.OnManagedAwake();
_director = GetComponent<PlayableDirector>();
if (_director != null)
@@ -42,10 +42,8 @@ namespace Core
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
if (_director != null)
{
_director.stopped -= OnDirectorStopped;
@@ -65,7 +63,7 @@ namespace Core
#region Save/Load Implementation
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
var saveData = new PlayableDirectorSaveData
{
@@ -77,11 +75,11 @@ namespace Core
return JsonUtility.ToJson(saveData);
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Debug.LogWarning($"[SaveablePlayableDirector] No save data to restore for {gameObject.name}");
Logging.Warning($"[SaveablePlayableDirector] No save data to restore for {gameObject.name}");
return;
}
@@ -99,7 +97,7 @@ namespace Core
_director.time = _director.duration;
_director.Evaluate(); // Force evaluation to apply the state
Debug.Log($"[SaveablePlayableDirector] Restored completed timeline '{gameObject.name}' - seeked to end");
Logging.Debug($"[SaveablePlayableDirector] Restored completed timeline '{gameObject.name}' - seeked to end");
}
else if (_hasPlayed && saveData.playbackTime > 0)
{
@@ -107,7 +105,7 @@ namespace Core
_director.time = saveData.playbackTime;
_director.Evaluate();
Debug.Log($"[SaveablePlayableDirector] Restored timeline '{gameObject.name}' at time {saveData.playbackTime}");
Logging.Debug($"[SaveablePlayableDirector] Restored timeline '{gameObject.name}' at time {saveData.playbackTime}");
}
else
{
@@ -115,7 +113,7 @@ namespace Core
_director.time = 0;
_director.Evaluate();
Debug.Log($"[SaveablePlayableDirector] Timeline '{gameObject.name}' not yet played - at start");
Logging.Debug($"[SaveablePlayableDirector] Timeline '{gameObject.name}' not yet played - at start");
}
}

View File

@@ -44,14 +44,10 @@ namespace Core
private LogVerbosity _logVerbosity = LogVerbosity.Debug;
private const string BootstrapSceneName = "BootstrapScene";
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 15; // Core infrastructure, after GameManager
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Initialize current scene tracking - critical for scene management
@@ -65,17 +61,17 @@ namespace Core
}
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Set up loading screen reference and events
// This must happen in ManagedAwake because LoadingScreenController instance needs to be set first
// This must happen in ManagedStart because LoadingScreenController instance needs to be set first
_loadingScreen = LoadingScreenController.Instance;
SetupLoadingScreenEvents();
// Load verbosity settings
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
LogDebugMessage($"SceneManagerService initialized, current scene is: {CurrentGameplayScene}");
Logging.Debug($"SceneManagerService initialized, current scene is: {CurrentGameplayScene}");
}
/// <summary>
@@ -93,19 +89,19 @@ namespace Core
if (activeScene.name != BootstrapSceneName)
{
CurrentGameplayScene = activeScene.name;
LogDebugMessage($"Initialized with current scene: {CurrentGameplayScene}");
Logging.Debug($"Initialized with current scene: {CurrentGameplayScene}");
}
// Otherwise default to MainMenu
else
{
CurrentGameplayScene = "AppleHillsOverworld";
LogDebugMessage($"Initialized with default scene: {CurrentGameplayScene}");
Logging.Debug($"Initialized with default scene: {CurrentGameplayScene}");
}
}
else
{
CurrentGameplayScene = "AppleHillsOverworld";
LogDebugMessage($"No valid active scene, defaulting to: {CurrentGameplayScene}");
Logging.Debug($"No valid active scene, defaulting to: {CurrentGameplayScene}");
}
}
@@ -307,7 +303,7 @@ namespace Core
}
// PHASE 2: Broadcast scene unloading - notify components to cleanup
LogDebugMessage($"Broadcasting OnSceneUnloading for: {oldSceneName}");
Logging.Debug($"Broadcasting OnSceneUnloading for: {oldSceneName}");
LifecycleManager.Instance?.BroadcastSceneUnloading(oldSceneName);
// PHASE 3: Save scene-specific data via SaveLoadManager (unless skipSave is true)
@@ -316,19 +312,19 @@ namespace Core
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
if (debugSettings.useSaveLoadSystem)
{
LogDebugMessage($"Saving scene data for: {oldSceneName}");
Logging.Debug($"Saving scene data for: {oldSceneName}");
SaveLoadManager.Instance.SaveSceneData();
}
}
else if (skipSave)
{
LogDebugMessage($"Skipping save for: {oldSceneName} (skipSave=true)");
Logging.Debug($"Skipping save for: {oldSceneName} (skipSave=true)");
}
// PHASE 4: Clear PuzzleManager state before scene transition
if (PuzzleS.PuzzleManager.Instance != null)
{
LogDebugMessage($"Clearing puzzle state before scene transition");
Logging.Debug($"Clearing puzzle state before scene transition");
PuzzleS.PuzzleManager.Instance.ClearPuzzleState();
}
@@ -352,7 +348,7 @@ namespace Core
}
else
{
Logging.Warning($"[SceneManagerService] Previous scene '{oldSceneName}' is not loaded, skipping unload.");
Logging.Warning($"Previous scene '{oldSceneName}' is not loaded, skipping unload.");
}
}
@@ -364,15 +360,15 @@ namespace Core
}
// PHASE 8: Begin scene loading mode - enables priority-ordered component initialization
LogDebugMessage($"Beginning scene load for: {newSceneName}");
Logging.Debug($"Beginning scene load for: {newSceneName}");
LifecycleManager.Instance?.BeginSceneLoad(newSceneName);
// PHASE 9: Load new gameplay scene
await LoadSceneAsync(newSceneName, progress);
CurrentGameplayScene = newSceneName;
// PHASE 10: Broadcast scene ready - processes batched components in priority order, then calls OnSceneReady
LogDebugMessage($"Broadcasting OnSceneReady for: {newSceneName}");
// PHASE 10: Broadcast scene ready - processes batched components, then calls OnSceneReady
Logging.Debug($"Broadcasting OnSceneReady for: {newSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
// PHASE 11: Restore scene-specific data via SaveLoadManager
@@ -381,13 +377,14 @@ namespace Core
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
if (debugSettings.useSaveLoadSystem)
{
LogDebugMessage($"Restoring scene data for: {newSceneName}");
Logging.Debug($"Restoring scene data for: {newSceneName}");
SaveLoadManager.Instance.RestoreSceneData();
}
}
else if (skipSave)
{
LogDebugMessage($"Skipping restore for: {newSceneName} (skipSave=true)");
SaveLoadManager.Instance.RestoreSceneData();
Logging.Debug($"Skipping restore for: {newSceneName} (skipSave=true)");
}
// PHASE 12: Only hide the loading screen if autoHideLoadingScreen is true
@@ -396,13 +393,5 @@ namespace Core
_loadingScreen.HideLoadingScreen();
}
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[SceneManagerService] {message}");
}
}
}
}

View File

@@ -18,23 +18,18 @@ namespace Core
public GameObject orientationPromptPrefab;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 70; // Platform-specific utility
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Load verbosity settings early (GameManager sets up settings in its Awake)
// Load verbosity settings early (GameManager sets up settings in its OnManagedAwake)
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
LogDebugMessage("Initialized");
Logging.Debug("Initialized");
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Subscribe to SceneManagerService to enforce orientation on every scene load
if (SceneManagerService.Instance != null)
@@ -51,7 +46,7 @@ namespace Core
#endif
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
// Handle orientation when scene is ready (initial scene)
// Note: This fires for the scene that just loaded, LifecycleManager tracks which scene
@@ -73,7 +68,7 @@ namespace Core
if (sceneName.ToLower().Contains("bootstrap"))
{
// Bootstrap being loaded additively, don't do anything
LogDebugMessage($"Detected bootstrapped scene: '{sceneName}'. Skipping orientation enforcement.");
Logging.Debug($"Detected bootstrapped scene: '{sceneName}'. Skipping orientation enforcement.");
return;
}
@@ -83,37 +78,35 @@ namespace Core
}
else
{
LogDebugMessage($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'");
Logging.Debug($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'");
}
switch (requirement)
{
case ScreenOrientationRequirement.Portrait:
LogDebugMessage($"Forcing Portrait for scene '{sceneName}'");
Logging.Debug($"Forcing Portrait for scene '{sceneName}'");
StartCoroutine(ForcePortrait());
break;
case ScreenOrientationRequirement.Landscape:
LogDebugMessage($"Forcing Landscape for scene '{sceneName}'");
Logging.Debug($"Forcing Landscape for scene '{sceneName}'");
StartCoroutine(ForceLandscape());
break;
case ScreenOrientationRequirement.NotApplicable:
default:
// Default to landscape when no specific requirement is found
LogDebugMessage($"No specific orientation for scene '{sceneName}'. Defaulting to Landscape");
Logging.Debug($"No specific orientation for scene '{sceneName}'. Defaulting to Landscape");
StartCoroutine(ForceLandscape());
break;
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
// Unsubscribe from events to prevent memory leaks
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
base.OnDestroy(); // Important: call base
}
/// <summary>
@@ -127,12 +120,12 @@ namespace Core
if (!currentlyLandscape)
{
// Lock it to portrait and allow the device to orient itself
LogDebugMessage($"Actually forcing Portrait from previous: {Screen.orientation}");
Logging.Debug($"Actually forcing Portrait from previous: {Screen.orientation}");
Screen.orientation = ScreenOrientation.LandscapeRight;
}
else
{
LogDebugMessage($"Skipping Landscape enforcement, device already in: {Screen.orientation}");
Logging.Debug($"Skipping Landscape enforcement, device already in: {Screen.orientation}");
}
yield return null;
@@ -160,12 +153,12 @@ namespace Core
if (!currentlyPortrait)
{
// Lock it to portrait and allow the device to orient itself
LogDebugMessage($"Actually forcing Portrait from previous: {Screen.orientation}");
Logging.Debug($"Actually forcing Portrait from previous: {Screen.orientation}");
Screen.orientation = ScreenOrientation.PortraitUpsideDown;
}
else
{
LogDebugMessage($"Skipping Portrait enforcement, device already in: {Screen.orientation}");
Logging.Debug($"Skipping Portrait enforcement, device already in: {Screen.orientation}");
}
yield return null;
@@ -181,13 +174,5 @@ namespace Core
// Allow device to auto-rotate to correct portrait orientation
Screen.orientation = ScreenOrientation.AutoRotation;
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[SceneOrientationEnforcer] {message}");
}
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using AppleHills.Core.Settings;
using UnityEngine;
namespace Core.Settings
{
@@ -21,7 +19,7 @@ namespace Core.Settings
public static void Register<T>(T service) where T : class
{
Services[typeof(T)] = service;
LogDebugMessage($"Service registered: {typeof(T).Name}");
Logging.Debug($"Service registered: {typeof(T).Name}");
}
/// <summary>
@@ -36,7 +34,7 @@ namespace Core.Settings
return service as T;
}
Logging.Warning($"[ServiceLocator] Service of type {typeof(T).Name} not found!");
Logging.Warning($"Service of type {typeof(T).Name} not found!");
return null;
}
@@ -46,16 +44,7 @@ namespace Core.Settings
public static void Clear()
{
Services.Clear();
LogDebugMessage("All services cleared");
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().settingsLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[ServiceLocator] {message}");
}
Logging.Debug("All services cleared");
}
}
}

View File

@@ -1,11 +1,12 @@
using UnityEngine;
using Pixelplacement;
using System;
using System.Collections;
using Core;
using Core.Lifecycle;
using Core.SaveLoad;
using UnityEngine.Audio;
public class PicnicBehaviour : MonoBehaviour
public class PicnicBehaviour : ManagedBehaviour
{
[Header("Random Call Settings")]
public float getDistractedMin = 2f;
@@ -17,7 +18,8 @@ public class PicnicBehaviour : MonoBehaviour
private Animator animator;
[Header("The FakeChocolate to destroy!")]
[SerializeField] private GameObject fakeChocolate; // Assign in Inspector
[SerializeField] private GameObject fakeChocolate;
[SerializeField] private GameObject realChocolate;
private AppleAudioSource _audioSource;
public AudioResource distractedAudioClips;
@@ -25,32 +27,44 @@ public class PicnicBehaviour : MonoBehaviour
public AudioResource feederClips;
public AudioResource moanerClips;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
StartCoroutine(StateCycleRoutine());
}
// Save system configuration
public override bool AutoRegisterForSave => true;
void Awake()
// Runtime state tracking
private bool _fakeChocolateDestroyed;
internal override void OnManagedAwake()
{
stateMachine = GetComponent<AppleMachine>();
animator = GetComponent<Animator>();
_audioSource = GetComponent<AppleAudioSource>();
}
internal override void OnSceneRestoreCompleted()
{
if (_fakeChocolateDestroyed)
{
DestroyChocolateObjects();
}
else
{
StartCoroutine(StateCycleRoutine());
}
}
private IEnumerator StateCycleRoutine()
{
while (true)
{
// Distracted state
float distractedWait = Random.Range(getDistractedMin, getDistractedMax);
float distractedWait = UnityEngine.Random.Range(getDistractedMin, getDistractedMax);
stateMachine.ChangeState("Picnic PPL Distracted");
animator.SetBool("theyDistracted", true);
_audioSource.Stop();
yield return new WaitForSeconds(distractedWait);
// Chilling state
float chillingWait = Random.Range(getFlirtyMin, getFlirtyMax);
float chillingWait = UnityEngine.Random.Range(getFlirtyMin, getFlirtyMax);
stateMachine.ChangeState("Picnic PPL Chilling");
animator.SetBool("theyDistracted", false);
_audioSource.Stop();
@@ -58,27 +72,33 @@ public class PicnicBehaviour : MonoBehaviour
}
}
void StopAudio()
{
_audioSource.Stop();
}
public void triedToStealChocolate()
{
_audioSource.Stop();
animator.SetTrigger("theyAngry");
//stateMachine.ChangeState("Picnic PPL Angry");
Logging.Debug("Hey! Don't steal my chocolate!");
_audioSource.audioSource.resource = angryAudioClips;
_audioSource.Play(0);
}
public void destroyFakeChocolate()
{
_fakeChocolateDestroyed = true;
Destroy(fakeChocolate);
Destroy(realChocolate);
}
private void DestroyChocolateObjects()
{
if (fakeChocolate != null)
{
Destroy(fakeChocolate);
fakeChocolate = null; // Optional: clear reference
fakeChocolate = null;
}
if (realChocolate != null)
{
realChocolate.SetActive(true);
}
}
@@ -100,5 +120,33 @@ public class PicnicBehaviour : MonoBehaviour
_audioSource.Play(0);
}
internal override string OnSceneSaveRequested()
{
var state = new PicnicBehaviourState { fakeChocolateDestroyed = _fakeChocolateDestroyed };
return JsonUtility.ToJson(state);
}
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData)) return;
try
{
var state = JsonUtility.FromJson<PicnicBehaviourState>(serializedData);
if (state != null)
{
_fakeChocolateDestroyed = state.fakeChocolateDestroyed;
}
}
catch (Exception ex)
{
Debug.LogWarning($"[PicnicBehaviour] Failed to restore state: {ex.Message}");
}
}
}
[Serializable]
public class PicnicBehaviourState
{
public bool fakeChocolateDestroyed;
}

View File

@@ -1,3 +1,4 @@
using Core;
using UnityEngine;
using Core.Lifecycle;
@@ -21,7 +22,7 @@ public class soundBird_CanFly : ManagedBehaviour
#region Save/Load Implementation
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
var saveData = new SoundBirdSaveData
{
@@ -31,11 +32,11 @@ public class soundBird_CanFly : ManagedBehaviour
return JsonUtility.ToJson(saveData);
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Debug.LogWarning($"[soundBird_CanFly] No save data to restore for {gameObject.name}");
Logging.Warning($"[soundBird_CanFly] No save data to restore for {gameObject.name}");
return;
}
@@ -43,7 +44,7 @@ public class soundBird_CanFly : ManagedBehaviour
if (saveData != null)
{
canFly = saveData.canFly;
Debug.Log($"[soundBird_CanFly] Restored canFly state: {canFly}");
Logging.Debug($"[soundBird_CanFly] Restored canFly state: {canFly}");
}
}

View File

@@ -95,6 +95,7 @@ namespace AppleHills.Data.CardSystem
public enum CardZone
{
NotApplicable,
AppleHills,
Quarry,
CementFactory,

View File

@@ -40,25 +40,20 @@ namespace Data.CardSystem
// Event callbacks using System.Action
public event Action<List<CardData>> OnBoosterOpened;
public event Action<CardData> OnCardCollected;
public event Action<CardData> OnCardRarityUpgraded;
public event Action<int> OnBoosterCountChanged;
public event Action<CardData> OnPendingCardAdded;
public event Action<CardData> OnCardPlacedInAlbum;
public override int ManagedAwakePriority => 60; // Data systems
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Load card definitions from Addressables, then register with save system
LoadCardDefinitionsFromAddressables();
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[CardSystemManager] Initialized");
}
@@ -720,13 +715,13 @@ namespace Data.CardSystem
#region Save/Load Lifecycle Hooks
protected override string OnGlobalSaveRequested()
internal override string OnGlobalSaveRequested()
{
var state = ExportCardCollectionState();
return JsonUtility.ToJson(state);
}
protected override void OnGlobalRestoreRequested(string serializedData)
internal override void OnGlobalRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using Interactions;
@@ -34,10 +32,7 @@ namespace Dialogue
public bool IsCompleted { get; private set; }
public string CurrentSpeakerName => dialogueGraph?.speakerName;
public override int ManagedAwakePriority => 150; // Dialogue systems
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Get required components
appleAudioSource = GetComponent<AppleAudioSource>();
@@ -186,7 +181,7 @@ namespace Dialogue
return null;
}
private void OnDestroy()
internal override void OnManagedDestroy()
{
// Unregister from events
if (PuzzleManager.Instance != null)

View File

@@ -49,14 +49,10 @@ namespace Input
private ITouchInputConsumer defaultConsumer;
private bool isHoldActive;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
public override int ManagedAwakePriority => 25; // Input infrastructure
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Load verbosity settings early
@@ -89,10 +85,10 @@ namespace Input
SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name);
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Subscribe to scene load events from SceneManagerService
// This must happen in ManagedAwake because SceneManagerService instance needs to be set first
// This must happen in ManagedStart because SceneManagerService instance needs to be set first
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
@@ -104,11 +100,11 @@ namespace Input
/// </summary>
private void OnSceneLoadCompleted(string sceneName)
{
LogDebugMessage($"Scene loaded: {sceneName}, restoring input mode");
Logging.Debug($"Scene loaded: {sceneName}, restoring input mode");
SwitchInputOnSceneLoaded(sceneName);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
// Unsubscribe from SceneManagerService events
if (SceneManagerService.Instance != null)
@@ -116,7 +112,6 @@ namespace Input
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
base.OnDestroy();
// Input action cleanup happens automatically
}
@@ -182,24 +177,24 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"TapMove performed at {worldPos2D}");
Logging.Debug($"TapMove performed at {worldPos2D}");
// First try to delegate to an override consumer if available
if (TryDelegateToOverrideConsumer(screenPos, worldPos2D))
{
LogDebugMessage("Tap delegated to override consumer");
Logging.Debug("Tap delegated to override consumer");
return;
}
// Then try to delegate to any ITouchInputConsumer (UI or world interactable)
if (!TryDelegateToAnyInputConsumer(screenPos, worldPos2D))
{
LogDebugMessage("No input consumer found, forwarding tap to default consumer");
Logging.Debug("No input consumer found, forwarding tap to default consumer");
defaultConsumer?.OnTap(worldPos2D);
}
else
{
LogDebugMessage("Tap delegated to input consumer");
Logging.Debug("Tap delegated to input consumer");
}
}
@@ -212,13 +207,13 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"HoldMove started at {worldPos2D}");
Logging.Debug($"HoldMove started at {worldPos2D}");
// First check for override consumers
if (_overrideConsumers.Count > 0)
{
_activeHoldConsumer = _overrideConsumers[_overrideConsumers.Count - 1];
LogDebugMessage($"Hold delegated to override consumer: {_activeHoldConsumer}");
Logging.Debug($"Hold delegated to override consumer: {_activeHoldConsumer}");
_activeHoldConsumer.OnHoldStart(worldPos2D);
return;
}
@@ -238,7 +233,7 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"HoldMove canceled at {worldPos2D}");
Logging.Debug($"HoldMove canceled at {worldPos2D}");
// Notify the active hold consumer that the hold has ended
_activeHoldConsumer?.OnHoldEnd(worldPos2D);
@@ -302,7 +297,7 @@ namespace Input
}
if (consumer != null)
{
LogDebugMessage($"Delegating tap to UI consumer at {screenPos} (GameObject: {result.gameObject.name})");
Logging.Debug($"Delegating tap to UI consumer at {screenPos} (GameObject: {result.gameObject.name})");
consumer.OnTap(screenPos);
return true;
}
@@ -331,7 +326,7 @@ namespace Input
}
if (consumer != null)
{
LogDebugMessage($"Delegating tap to consumer at {worldPos} (GameObject: {hitWithMask.gameObject.name})");
Logging.Debug($"Delegating tap to consumer at {worldPos} (GameObject: {hitWithMask.gameObject.name})");
consumer.OnTap(worldPos);
return true;
}
@@ -345,15 +340,15 @@ namespace Input
var consumer = hit.GetComponent<ITouchInputConsumer>();
if (consumer != null)
{
LogDebugMessage($"Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})");
Logging.Debug($"Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})");
consumer.OnTap(worldPos);
return true;
}
LogDebugMessage($"Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found.");
Logging.Debug($"Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found.");
}
else
{
LogDebugMessage($"No Collider2D found at {worldPos} for interactable delegation.");
Logging.Debug($"No Collider2D found at {worldPos} for interactable delegation.");
}
return false;
}
@@ -368,7 +363,7 @@ namespace Input
return;
_overrideConsumers.Add(consumer);
LogDebugMessage($"Override consumer registered: {consumer}");
Logging.Debug($"Override consumer registered: {consumer}");
}
/// <summary>
@@ -386,7 +381,7 @@ namespace Input
}
_overrideConsumers.Remove(consumer);
LogDebugMessage($"Override consumer unregistered: {consumer}");
Logging.Debug($"Override consumer unregistered: {consumer}");
}
/// <summary>
@@ -396,7 +391,7 @@ namespace Input
{
_activeHoldConsumer = null;
_overrideConsumers.Clear();
LogDebugMessage("All override consumers cleared.");
Logging.Debug("All override consumers cleared.");
}
/// <summary>
@@ -409,17 +404,9 @@ namespace Input
// Get the topmost override consumer (last registered)
var consumer = _overrideConsumers[_overrideConsumers.Count - 1];
LogDebugMessage($"Delegating tap to override consumer at {worldPos} (GameObject: {consumer})");
Logging.Debug($"Delegating tap to override consumer at {worldPos} (GameObject: {consumer})");
consumer.OnTap(worldPos);
return true;
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[InputManager] {message}");
}
}
}
}

View File

@@ -3,7 +3,6 @@ using Pathfinding;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using Core.SaveLoad;
namespace Input
{
@@ -71,9 +70,8 @@ namespace Input
public override bool AutoRegisterForSave => true;
// Scene-specific SaveId - each level has its own player state
public override string SaveId => $"{gameObject.scene.name}/PlayerController";
public override int ManagedAwakePriority => 100; // Player controller
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
aiPath = GetComponent<AIPath>();
artTransform = transform.Find("CharacterArt");
@@ -103,7 +101,7 @@ namespace Input
public void OnTap(Vector2 worldPosition)
{
InterruptMoveTo();
LogDebugMessage($"OnTap at {worldPosition}");
Logging.Debug($"OnTap at {worldPosition}");
if (aiPath != null)
{
aiPath.enabled = true;
@@ -122,7 +120,7 @@ namespace Input
public void OnHoldStart(Vector2 worldPosition)
{
InterruptMoveTo();
LogDebugMessage($"OnHoldStart at {worldPosition}");
Logging.Debug($"OnHoldStart at {worldPosition}");
lastHoldPosition = worldPosition;
isHolding = true;
if (_settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding &&
@@ -159,7 +157,7 @@ namespace Input
/// </summary>
public void OnHoldEnd(Vector2 worldPosition)
{
LogDebugMessage($"OnHoldEnd at {worldPosition}");
Logging.Debug($"OnHoldEnd at {worldPosition}");
isHolding = false;
directMoveVelocity = Vector3.zero;
if (aiPath != null && _settings.DefaultHoldMovementMode ==
@@ -335,13 +333,13 @@ namespace Input
{
_isMoving = true;
OnMovementStarted?.Invoke();
LogDebugMessage("Movement started");
Logging.Debug("Movement started");
}
else if (!isCurrentlyMoving && _isMoving)
{
_isMoving = false;
OnMovementStopped?.Invoke();
LogDebugMessage("Movement stopped");
Logging.Debug("Movement stopped");
}
}
@@ -424,18 +422,10 @@ namespace Input
OnArrivedAtTarget?.Invoke();
}
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[PlayerTouchController] {message}");
}
}
#region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
var saveData = new PlayerSaveData
{
@@ -445,7 +435,7 @@ namespace Input
return JsonUtility.ToJson(saveData);
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{

View File

@@ -41,9 +41,6 @@ namespace Interactions
// Action component system
private List<InteractionActionBase> _registeredActions = new List<InteractionActionBase>();
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 100; // Gameplay base classes
/// <summary>
/// Register an action component with this interactable
@@ -190,7 +187,7 @@ namespace Interactions
/// <returns>True if interaction succeeded, false otherwise</returns>
protected virtual bool DoInteraction()
{
Debug.LogWarning($"[Interactable] DoInteraction not implemented for {GetType().Name}");
Logging.Warning($"[Interactable] DoInteraction not implemented for {GetType().Name}");
return false;
}

View File

@@ -68,8 +68,6 @@ namespace Interactions
public event Action<PickupItemData, PickupItemData> OnIncorrectItemSlotted;
public UnityEvent onForbiddenItemSlotted;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData, PickupItemData> OnForbiddenItemSlotted;
public GameObject GetSlottedObject()
{
@@ -85,9 +83,9 @@ namespace Interactions
}
}
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // SaveableInteractable registration
base.OnManagedAwake(); // SaveableInteractable registration
// Setup visuals
if (iconRenderer == null)
@@ -289,10 +287,8 @@ namespace Interactions
ItemManager.Instance?.RegisterItemSlot(this);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Unregister from slot manager
ItemManager.Instance?.UnregisterItemSlot(this);
}
@@ -333,7 +329,7 @@ namespace Interactions
ItemSlotSaveData data = JsonUtility.FromJson<ItemSlotSaveData>(serializedData);
if (data == null)
{
Debug.LogWarning($"[ItemSlot] Failed to deserialize save data for {gameObject.name}");
Logging.Warning($"[ItemSlot] Failed to deserialize save data for {gameObject.name}");
return;
}
@@ -343,7 +339,7 @@ namespace Interactions
// Restore slotted item if there was one
if (!string.IsNullOrEmpty(data.slottedItemSaveId))
{
Debug.Log($"[ItemSlot] Restoring slotted item: {data.slottedItemSaveId} (itemId: {data.slottedItemDataId})");
Logging.Debug($"[ItemSlot] Restoring slotted item: {data.slottedItemSaveId} (itemId: {data.slottedItemDataId})");
RestoreSlottedItem(data.slottedItemSaveId, data.slottedItemDataId);
}
}
@@ -361,7 +357,7 @@ namespace Interactions
{
// Item not found in scene - it might be a dynamically spawned combined item
// Try to spawn it from the itemDataId
Debug.Log($"[ItemSlot] Slotted item not found in scene: {slottedItemSaveId}, attempting to spawn from itemId: {expectedItemDataId}");
Logging.Debug($"[ItemSlot] Slotted item not found in scene: {slottedItemSaveId}, attempting to spawn from itemId: {expectedItemDataId}");
GameObject prefab = interactionSettings?.FindPickupPrefabByItemId(expectedItemDataId);
if (prefab != null)
@@ -369,17 +365,17 @@ namespace Interactions
// Spawn the item (inactive, since it will be slotted)
slottedObject = Instantiate(prefab, transform.position, Quaternion.identity);
slottedObject.SetActive(false);
Debug.Log($"[ItemSlot] Successfully spawned combined item for slot: {expectedItemDataId}");
Logging.Debug($"[ItemSlot] Successfully spawned combined item for slot: {expectedItemDataId}");
}
else
{
Debug.LogWarning($"[ItemSlot] Could not find prefab for itemId: {expectedItemDataId}");
Logging.Warning($"[ItemSlot] Could not find prefab for itemId: {expectedItemDataId}");
return;
}
}
else if (slottedObject == null)
{
Debug.LogWarning($"[ItemSlot] Could not find slotted item with save ID: {slottedItemSaveId}");
Logging.Warning($"[ItemSlot] Could not find slotted item with save ID: {slottedItemSaveId}");
return;
}
@@ -395,13 +391,13 @@ namespace Interactions
{
if (slottedData.itemId != expectedItemDataId)
{
Debug.LogWarning($"[ItemSlot] ItemId mismatch! Pickup has '{slottedData.itemId}' but expected '{expectedItemDataId}'");
Logging.Warning($"[ItemSlot] ItemId mismatch! Pickup has '{slottedData.itemId}' but expected '{expectedItemDataId}'");
}
}
if (slottedData == null)
{
Debug.LogWarning($"[ItemSlot] Pickup {pickup.gameObject.name} has null itemData! Expected itemId: {expectedItemDataId}");
Logging.Warning($"[ItemSlot] Pickup {pickup.gameObject.name} has null itemData! Expected itemId: {expectedItemDataId}");
if (slottedObject != null)
Destroy(slottedObject);
return;
@@ -409,7 +405,7 @@ namespace Interactions
}
else
{
Debug.LogWarning($"[ItemSlot] Slotted object has no Pickup component: {slottedObject.name}");
Logging.Warning($"[ItemSlot] Slotted object has no Pickup component: {slottedObject.name}");
if (slottedObject != null)
Destroy(slottedObject);
return;
@@ -419,7 +415,7 @@ namespace Interactions
// Follower state is managed separately during save/load restoration
ApplySlottedItemState(slottedObject, slottedData, triggerEvents: false);
Debug.Log($"[ItemSlot] Successfully restored slotted item: {slottedData.itemName} (itemId: {slottedData.itemId})");
Logging.Debug($"[ItemSlot] Successfully restored slotted item: {slottedData.itemName} (itemId: {slottedData.itemId})");
}
/// <summary>
@@ -526,7 +522,7 @@ namespace Interactions
// If slot already has an item, reject the claim
if (currentlySlottedItemObject != null)
{
Debug.LogWarning($"[ItemSlot] Already has a slotted item, rejecting claim from {pickup.gameObject.name}");
Logging.Warning($"[ItemSlot] Already has a slotted item, rejecting claim from {pickup.gameObject.name}");
return false;
}
@@ -537,7 +533,7 @@ namespace Interactions
// Claim the pickup
ApplySlottedItemState(pickup.gameObject, pickup.itemData, triggerEvents: false);
Debug.Log($"[ItemSlot] Successfully claimed slotted item: {pickup.itemData?.itemName}");
Logging.Debug($"[ItemSlot] Successfully claimed slotted item: {pickup.itemData?.itemName}");
return true;
}

View File

@@ -32,9 +32,9 @@ namespace Interactions
public event Action<PickupItemData> OnItemPickedUp;
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // Register with save system
base.OnManagedAwake(); // Register with save system
if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>();
@@ -44,15 +44,14 @@ namespace Interactions
// Always register with ItemManager, even if picked up
// This allows the save/load system to find held items when restoring state
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
base.OnManagedStart();
ItemManager.Instance?.RegisterPickup(this);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Unregister from ItemManager
ItemManager.Instance?.UnregisterPickup(this);
}
@@ -166,7 +165,7 @@ namespace Interactions
PickupSaveData data = JsonUtility.FromJson<PickupSaveData>(serializedData);
if (data == null)
{
Debug.LogWarning($"[Pickup] Failed to deserialize save data for {gameObject.name}");
Logging.Warning($"[Pickup] Failed to deserialize save data for {gameObject.name}");
return;
}
@@ -200,7 +199,7 @@ namespace Interactions
}
else
{
Debug.LogWarning($"[Pickup] Could not find slot with SaveId: {data.slotSaveId}");
Logging.Warning($"[Pickup] Could not find slot with SaveId: {data.slotSaveId}");
}
}
}

View File

@@ -1,4 +1,5 @@
using UnityEngine;
using Core;
using UnityEngine;
namespace Interactions
{
@@ -23,7 +24,7 @@ namespace Interactions
#region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
object stateData = GetSerializableState();
if (stateData == null)
@@ -34,11 +35,11 @@ namespace Interactions
return JsonUtility.ToJson(stateData);
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Debug.LogWarning($"[SaveableInteractable] Empty save data for {SaveId}");
Logging.Warning($"[SaveableInteractable] Empty save data for {SaveId}");
return;
}
@@ -85,17 +86,17 @@ namespace Interactions
[ContextMenu("Log Save ID")]
private void LogSaveId()
{
Debug.Log($"Save ID: {SaveId}");
Logging.Debug($"Save ID: {SaveId}");
}
[ContextMenu("Test Serialize/Deserialize")]
private void TestSerializeDeserialize()
{
string serialized = OnSceneSaveRequested();
Debug.Log($"Serialized state: {serialized}");
Logging.Debug($"Serialized state: {serialized}");
OnSceneRestoreRequested(serialized);
Debug.Log("Deserialization test complete");
Logging.Debug("Deserialization test complete");
}
#endif

View File

@@ -21,11 +21,11 @@ namespace Levels
/// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary>
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake();
base.OnManagedAwake();
Debug.Log($"[LevelSwitch] Awake called for {gameObject.name} in scene {gameObject.scene.name}");
Logging.Debug($"[LevelSwitch] Awake called for {gameObject.name} in scene {gameObject.scene.name}");
if (_iconRenderer == null)
_iconRenderer = GetComponent<SpriteRenderer>();
@@ -36,14 +36,14 @@ namespace Levels
ApplySwitchData();
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Debug.Log($"[LevelSwitch] OnManagedAwake called for {gameObject.name}");
Logging.Debug($"[LevelSwitch] OnManagedStart called for {gameObject.name}");
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
Debug.Log($"[LevelSwitch] OnSceneReady called for {gameObject.name}");
Logging.Debug($"[LevelSwitch] OnSceneReady called for {gameObject.name}");
}
#if UNITY_EDITOR
@@ -79,7 +79,7 @@ namespace Levels
{
if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName))
{
Debug.LogWarning("LevelSwitch has no valid switchData!");
Logging.Warning("LevelSwitch has no valid switchData!");
return false;
}

View File

@@ -35,21 +35,16 @@ namespace Levels
[SerializeField] private bool startUnlocked = false;
private SpriteRenderer iconRenderer;
// Settings reference
private IInteractionSettings interactionSettings;
private bool switchActive = true;
private bool isUnlocked;
/// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary>
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake();
switchActive = true;
base.OnManagedAwake();
if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>();
@@ -64,10 +59,9 @@ namespace Levels
ApplySwitchData();
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
base.OnManagedAwake();
base.OnManagedStart();
// If startUnlocked is true, always start active
if (startUnlocked)
{
@@ -86,10 +80,8 @@ namespace Levels
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
if (PuzzleManager.Instance != null)
{
PuzzleManager.Instance.OnAllPuzzlesComplete -= HandleAllPuzzlesComplete;
@@ -144,15 +136,7 @@ namespace Levels
{
return base.CanBeClicked() && isUnlocked;
}
/// <summary>
/// Setup: Prevent re-entry while interaction is in progress.
/// </summary>
protected override void OnInteractionStarted()
{
switchActive = false;
}
/// <summary>
/// Main interaction logic: Spawn menu and switch input mode.
/// </summary>
@@ -160,7 +144,7 @@ namespace Levels
{
if (switchData == null || string.IsNullOrEmpty(switchData.targetLevelSceneName))
{
Debug.LogWarning("MinigameSwitch has no valid switchData!");
Logging.Warning("MinigameSwitch has no valid switchData!");
return false;
}
@@ -203,7 +187,7 @@ namespace Levels
private void OnMenuCancel()
{
switchActive = true; // Allow interaction again if cancelled
InputManager.Instance.SetInputMode(InputMode.GameAndUI);
}
@@ -231,7 +215,7 @@ namespace Levels
MinigameSwitchSaveData data = JsonUtility.FromJson<MinigameSwitchSaveData>(serializedData);
if (data == null)
{
Debug.LogWarning($"[MinigameSwitch] Failed to deserialize save data for {gameObject.name}");
Logging.Warning($"[MinigameSwitch] Failed to deserialize save data for {gameObject.name}");
return;
}

View File

@@ -104,13 +104,10 @@ namespace Minigames.DivingForPictures
public static DivingGameManager Instance => _instance;
public override int ManagedAwakePriority => 190;
public override bool AutoRegisterPausable => true; // Automatic GameManager registration
protected override void Awake()
internal override void OnManagedAwake()
{
base.Awake();
if (_instance == null)
{
_instance = this;
@@ -121,7 +118,7 @@ namespace Minigames.DivingForPictures
}
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
_currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f;
@@ -132,7 +129,7 @@ namespace Minigames.DivingForPictures
Logging.Debug("[DivingGameManager] Initialized");
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
InitializeGame();
@@ -164,10 +161,8 @@ namespace Minigames.DivingForPictures
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy(); // Handles auto-unregister from GameManager
// Unsubscribe from events when the manager is destroyed
PlayerCollisionBehavior.OnDamageTaken -= OnPlayerDamageTaken;
OnMonsterSpawned -= DoMonsterSpawned;

View File

@@ -106,9 +106,7 @@ public class FollowerController : ManagedBehaviour
private bool _hasRestoredHeldItem; // Track if held item restoration completed
private string _expectedHeldItemSaveId; // Expected saveId during restoration
public override int ManagedAwakePriority => 110; // Follower after player
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
_aiPath = GetComponent<AIPath>();
// Find art prefab and animator
@@ -129,7 +127,7 @@ public class FollowerController : ManagedBehaviour
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
// Find player reference when scene is ready (called for every scene load)
FindPlayerReference();
@@ -727,7 +725,7 @@ public class FollowerController : ManagedBehaviour
#region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
var saveData = new FollowerSaveData
{
@@ -754,7 +752,7 @@ public class FollowerController : ManagedBehaviour
return JsonUtility.ToJson(saveData);
}
protected override void OnSceneRestoreRequested(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
@@ -914,7 +912,7 @@ public class FollowerController : ManagedBehaviour
/// </summary>
public static FollowerController FindInstance()
{
return FindObjectOfType<FollowerController>();
return FindFirstObjectByType<FollowerController>();
}
#endregion Save/Load Lifecycle Hooks

View File

@@ -1,19 +1,74 @@
using System;
using Core;
using Core.Lifecycle;
using UnityEngine;
public class BirdGameStats : MonoBehaviour
namespace PuzzleS
{
public int birdsFoundInLevel;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
/// <summary>
/// Tracks bird discovery progress in the bird finding minigame.
/// Saves scene-specific progress using the ManagedBehaviour lifecycle system.
/// </summary>
public class BirdGameStats : ManagedBehaviour
{
public int birdsFoundInLevel;
// Save system configuration
public override bool AutoRegisterForSave => true;
internal override void OnManagedStart()
{
// Initialize after all managers are ready
}
public void BirdFound()
{
birdsFoundInLevel += 1;
}
#region Save/Load Lifecycle Hooks
internal override string OnSceneSaveRequested()
{
// Save scene-specific progress
var state = new BirdGameState
{
birdsFoundInLevel = this.birdsFoundInLevel
};
return JsonUtility.ToJson(state);
}
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
// No saved data, keep default values
return;
}
try
{
var state = JsonUtility.FromJson<BirdGameState>(serializedData);
if (state != null)
{
birdsFoundInLevel = state.birdsFoundInLevel;
}
}
catch (Exception ex)
{
Logging.Warning($"[BirdGameStats] Failed to restore state: {ex.Message}");
}
}
#endregion
}
public void BirdFound()
/// <summary>
/// Serializable state for bird game progress
/// </summary>
[Serializable]
public class BirdGameState
{
birdsFoundInLevel += 1;
public int birdsFoundInLevel;
}
}

View File

@@ -32,7 +32,7 @@ namespace PuzzleS
// Enum for tracking proximity state (simplified to just Close and Far)
public enum ProximityState { Close, Far }
protected override void Awake()
internal override void OnManagedAwake()
{
_interactable = GetComponent<InteractableBase>();
@@ -56,14 +56,10 @@ namespace PuzzleS
Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt");
}
}
base.Awake();
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
base.OnManagedAwake();
// Register with PuzzleManager - safe to access .Instance here
if (stepData != null && PuzzleManager.Instance != null)
{
@@ -87,10 +83,8 @@ namespace PuzzleS
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
if (PuzzleManager.Instance != null && stepData != null)
{
PuzzleManager.Instance.UnregisterStepBehaviour(this);

View File

@@ -93,18 +93,14 @@ namespace PuzzleS
// Track pending unlocks for steps that were unlocked before their behavior registered
private HashSet<string> _pendingUnlocks = new HashSet<string>();
public override int ManagedAwakePriority => 80; // Puzzle systems
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
@@ -140,10 +136,8 @@ namespace PuzzleS
LoadPuzzlesForScene(sceneName);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Unsubscribe from SceneManagerService events
if (SceneManagerService.Instance != null)
{
@@ -591,7 +585,7 @@ namespace PuzzleS
#region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested()
internal override string OnSceneSaveRequested()
{
if (_currentLevelData == null)
{
@@ -611,9 +605,9 @@ namespace PuzzleS
return json;
}
protected override void OnSceneRestoreRequested(string data)
internal override void OnSceneRestoreRequested(string data)
{
Debug.Log("[XAXA] PuzzleManager loading with data: " + data);
Logging.Debug("[XAXA] PuzzleManager loading with data: " + data);
if (string.IsNullOrEmpty(data) || data == "{}")
{

View File

@@ -3,12 +3,14 @@ using System;
using System.Diagnostics.Tracing;
using UnityEngine;
using UnityEngine.Audio;
using Core;
using Core.Lifecycle;
/// <summary>
/// We automatically add the AudioSource component here so we can control it. Do not add it manually!
/// </summary>
[RequireComponent(typeof(AudioSource))]
public class AppleAudioSource : MonoBehaviour
public class AppleAudioSource : ManagedBehaviour
{
public enum AudioSourceType{CriticalVO,VO,Ambience,SFX,Music}
public AudioSourceType audioSourceType;
@@ -17,10 +19,14 @@ public class AppleAudioSource : MonoBehaviour
[HideInInspector ] public int clipPriority;
public int sourcePriority;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void OnEnable()
internal override void OnManagedAwake()
{
audioSource = GetComponent<AudioSource>();
}
internal override void OnManagedStart()
{
AudioManager.Instance.RegisterNewAudioSource(this);
_audioMixer = AudioManager.Instance.audioMixer;
InitializeAudioSource();
@@ -54,6 +60,10 @@ public class AppleAudioSource : MonoBehaviour
public void Play(int requestedClipPriority)
{
if (audioSource == null)
{
audioSource = GetComponent<AudioSource>();
}
clipPriority = requestedClipPriority;
if (audioSourceType == AudioSourceType.CriticalVO || audioSourceType == AudioSourceType.VO)
{
@@ -63,7 +73,7 @@ public class AppleAudioSource : MonoBehaviour
}
else
{
Debug.Log("[AUDIOMANAGER] AppleAudioSource " + name + " was suppressed because something more important is playing");
Logging.Debug("[AUDIOMANAGER] AppleAudioSource " + name + " was suppressed because something more important is playing");
}
}
else

View File

@@ -5,6 +5,7 @@ using AppleHills.Core.Interfaces;
using System.Collections.Generic;
using AudioSourceEvents;
using System;
using Core;
using Core.Lifecycle;
public class AudioManager : ManagedBehaviour, IPausable
@@ -39,19 +40,15 @@ public class AudioManager : ManagedBehaviour, IPausable
/// </summary>
public static AudioManager Instance => _instance;
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 30; // Audio infrastructure
public override bool AutoRegisterPausable => true; // Auto-register as IPausable
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Initialize lists if they were not set in inspector
criticalVOSources = criticalVOSources ?? new List<AppleAudioSource>();
@@ -71,16 +68,14 @@ public class AudioManager : ManagedBehaviour, IPausable
}
else
{
Debug.LogWarning("[AudioManager] QuickAccess.Instance is null during OnManagedAwake. Some audio references may remain unset.");
Logging.Warning("[AudioManager] QuickAccess.Instance is null during OnManagedStart. Some audio references may remain unset.");
}
// Diagnostic
foreach (AppleAudioSource _audioSource in criticalVOSources)
{
Debug.Log("Found source: " + _audioSource.name);
Logging.Debug("Found source: " + _audioSource.name);
}
Debug.Log("[AudioManager] OnManagedAwake completed");
}
public void SetAudioPauseBehavior(PauseBehavior newPauseBehavior)
@@ -104,15 +99,15 @@ public class AudioManager : ManagedBehaviour, IPausable
public LevelAudioObject GetCurrentLevelAudioObject()
{
Debug.Log("Audio objects: " + FindObjectsByType<LevelAudioObject>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length);
Logging.Debug("Audio objects: " + FindObjectsByType<LevelAudioObject>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length);
if (FindObjectsByType<LevelAudioObject>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length > 1)
{
Debug.LogWarning("Warning! More than one LevelAudioObject in the level! Using the first one found");
Logging.Warning("Warning! More than one LevelAudioObject in the level! Using the first one found");
return FindObjectsByType<LevelAudioObject>(FindObjectsInactive.Include, FindObjectsSortMode.None)[0];
}
if (FindObjectsByType<LevelAudioObject>(FindObjectsInactive.Include, FindObjectsSortMode.None).Length == 0)
{
Debug.LogWarning("Error! No LevelAudioObject found, AudioManager might not function properly!");
Logging.Warning("Error! No LevelAudioObject found, AudioManager might not function properly!");
return null;
}
else
@@ -158,12 +153,10 @@ public class AudioManager : ManagedBehaviour, IPausable
/// </summary>
public bool RequestPlayVO(AppleAudioSource requestedAudioSource)
{
//Debug.Log($"[AUDIOMANAGER] CurrentVO source prio: {currentlyPlayingVO.sourcePriority}, clip prio: {currentlyPlayingVO.clipPriority} requested VO prio: {requestedAudioSource.sourcePriority}, clip prio: {clipPriority}");
// If nothing is playing, let the requested audio source play
if (currentlyPlayingVO == null)
{
SetupNewAudioSource(requestedAudioSource);
Debug.Log($"[AUDIOMANAGER] Playing {currentlyPlayingVO.name} as nothing is currently playing.");
return true;
}
@@ -177,7 +170,6 @@ public class AudioManager : ManagedBehaviour, IPausable
{
InterruptAudioSource(requestedAudioSource);
SetupNewAudioSource(requestedAudioSource);
Debug.Log($"[AUDIOMANAGER] {currentlyPlayingVO.name} is the same as {requestedAudioSource.name}. Triggering it again.");
return true;
}
@@ -187,7 +179,6 @@ public class AudioManager : ManagedBehaviour, IPausable
InterruptAudioSource(requestedAudioSource);
SetupNewAudioSource(requestedAudioSource);
Debug.Log($"[AUDIOMANAGER] {currentlyPlayingVO.name} is not critical. Playing {requestedAudioSource.name} instead because it is critical.");
return true;
}
// If the requested audio source has the same priority as currently playing source, check the priority of the requested clip
@@ -197,8 +188,6 @@ public class AudioManager : ManagedBehaviour, IPausable
{
InterruptAudioSource(requestedAudioSource);
SetupNewAudioSource(requestedAudioSource);
Debug.Log($"[AUDIOMANAGER] Interrupted {currentlyPlayingVO.name} because it has same priority as {requestedAudioSource.name} but the requested clip has higher priority");
return true;
}
else
@@ -210,7 +199,6 @@ public class AudioManager : ManagedBehaviour, IPausable
if (currentlyPlayingVO.audioSourceType == AppleAudioSource.AudioSourceType.CriticalVO && currentlyPlayingVO.sourcePriority > requestedAudioSource.sourcePriority)
{
currentlyPlayingVO.InterruptAudio(requestedAudioSource.name);
Debug.Log($"[AUDIOMANAGER] Interrupted {currentlyPlayingVO.name} because {requestedAudioSource.name} has higher priority");
InterruptAudioSource(requestedAudioSource);
SetupNewAudioSource(requestedAudioSource);
return true;
@@ -218,7 +206,6 @@ public class AudioManager : ManagedBehaviour, IPausable
// If the requested audio source didn't clear any of the above cases, tell it to get rekt.
else
{
Debug.Log($"[AUDIOMANAGER] {currentlyPlayingVO.name} is still playing. {requestedAudioSource.name} has lower priority");
return false;
}
}
@@ -232,7 +219,6 @@ public class AudioManager : ManagedBehaviour, IPausable
{
if (audioSource.audioSource.resource == null)
{
Debug.Log($"[AUDIOMANAGER] Faled to setup {audioSource.name}. Invalid resource");
}
else
{
@@ -281,7 +267,15 @@ public class AudioManager : ManagedBehaviour, IPausable
{
foreach (AppleAudioSource source in criticalVOSources)
{
source.InterruptAudio("GlobalInterrupt");
if (source == null)
{
return;
}
else
{
source.InterruptAudio("GlobalInterrupt");
}
}
foreach (AppleAudioSource source in VOSources)

View File

@@ -4,8 +4,11 @@ using System;
using System.Diagnostics.Tracing;
using UnityEngine;
using UnityEngine.Audio;
using Core;
using Core.Lifecycle;
using PuzzleS;
public class BushAudioController : MonoBehaviour
public class BushAudioController : ManagedBehaviour
{
private IAudioEventSource _eventSource;
public AppleAudioSource VOPlayer;
@@ -20,7 +23,7 @@ public class BushAudioController : MonoBehaviour
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
internal override void OnManagedStart()
{
_eventSource = VOPlayer.audioSource.RequestEventHandlers();
_eventSource.AudioStopped += PlayBirdCounter;
@@ -48,9 +51,9 @@ public class BushAudioController : MonoBehaviour
}
public void OnDisable()
{
// Unsubscribe from events when disabled
_eventSource.AudioStopped -= PlayBirdCounter;
}
//public void OnDisable()
//{
// // Unsubscribe from events when disabled
// _eventSource.AudioStopped -= PlayBirdCounter;
//}
}

View File

@@ -1,22 +1,81 @@
using UnityEngine;
using UnityEngine.Audio;
using System;
using Core;
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 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
void Start()
private bool _hasPlayed;
#region ManagedBehaviour Overrides
public override bool AutoRegisterForSave => isOneTime; // Only save if one-time audio
internal 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()
internal override void OnSceneRestoreRequested(string serializedData)
{
if (!isOneTime || string.IsNullOrEmpty(serializedData))
return;
try
{
LevelAudioObjectSaveData saveData = JsonUtility.FromJson<LevelAudioObjectSaveData>(serializedData);
_hasPlayed = saveData.hasPlayed;
}
catch (Exception e)
{
Logging.Warning($"[LevelAudioObject] Failed to restore audio state: {e.Message}");
}
}
internal override void OnSceneRestoreCompleted()
{
if (isOneTime && !_hasPlayed)
{
PlayNarrationAudio();
}
}
#endregion
private void PlayNarrationAudio()
{
if (narratorAudioSource == null || firstNarration == null)
{
Logging.Warning($"[LevelAudioObject] Missing audio source or narration resource on {gameObject.name}");
return;
}
narratorAudioSource.audioSource.resource = firstNarration;
narratorAudioSource.Play(0);
_hasPlayed = true;
}
}

View File

@@ -1,3 +1,4 @@
using Core;
using UnityEngine;
public class ProximitySoundReaction : MonoBehaviour
@@ -12,13 +13,13 @@ public class ProximitySoundReaction : MonoBehaviour
private void OnCollisionEnter2D(Collision2D collision)
{
Debug.Log("overlap!");
Logging.Debug("overlap!");
}
private void OnTriggerEnter2D(Collider2D collision)
{
Debug.Log("Wolter triggered!");
Logging.Debug("Wolter triggered!");
}

View File

@@ -1,18 +1,17 @@
using Core;
using Core.Lifecycle;
using UnityEngine;
using UnityEngine.Audio;
public class PulverAudioController : MonoBehaviour
public class PulverAudioController : ManagedBehaviour
{
private AppleAudioSource audioSource;
public AppleAudioSource audioSource;
public AudioResource combineAudio;
private FollowerController followerController;
public ItemManager itemManager;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
internal override void OnManagedStart()
{
audioSource = GetComponent<AppleAudioSource>();
followerController = GetComponent<FollowerController>();
followerController.PulverIsCombining.AddListener(PulverIsCombining);

View File

@@ -25,10 +25,10 @@ public class AppSwitcher : UIPage
private TweenBase slideInTween;
private TweenBase slideOutTween;
protected override void OnManagedAwake()
internal override void OnManagedAwake()
{
base.OnManagedAwake();
PageName = "AppSwitcher";
rainbowInPlayer = rainbowIn.GetComponent<SkottiePlayerV2>();
rainbowOutPlayer = rainbowOut.GetComponent<SkottiePlayerV2>();
@@ -110,10 +110,8 @@ public class AppSwitcher : UIPage
);
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Clean up tweens
slideInTween?.Stop();
slideOutTween?.Stop();

View File

@@ -1,5 +1,6 @@
using System;
using AppleHills.Data.CardSystem;
using Core;
using Pixelplacement;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -82,37 +83,37 @@ namespace UI.CardSystem
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log($"[CLICK-TRACE-ALBUMCARD] OnPointerClick on {name}, _parentSlot={((_parentSlot != null) ? _parentSlot.name : "NULL")}, _isEnlarged={_isEnlarged}, position={eventData.position}");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] OnPointerClick on {name}, _parentSlot={((_parentSlot != null) ? _parentSlot.name : "NULL")}, _isEnlarged={_isEnlarged}, position={eventData.position}");
// During reveal flow (before placed in slot), forward clicks to parent FlippableCard
if (_parentSlot == null)
{
Debug.Log($"[CLICK-TRACE-ALBUMCARD] {name} - No parent slot, forwarding click to parent FlippableCard");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] {name} - No parent slot, forwarding click to parent FlippableCard");
// Find parent FlippableCard and forward the click
FlippableCard parentFlippable = GetComponentInParent<FlippableCard>();
if (parentFlippable != null)
{
Debug.Log($"[CLICK-TRACE-ALBUMCARD] {name} - Found parent FlippableCard, calling OnPointerClick");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] {name} - Found parent FlippableCard, calling OnPointerClick");
parentFlippable.OnPointerClick(eventData);
}
else
{
Debug.LogWarning($"[CLICK-TRACE-ALBUMCARD] {name} - No parent FlippableCard found!");
Logging.Warning($"[CLICK-TRACE-ALBUMCARD] {name} - No parent FlippableCard found!");
}
return;
}
Debug.Log($"[CLICK-TRACE-ALBUMCARD] {name} - Has parent slot, processing click");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] {name} - Has parent slot, processing click");
if (_isEnlarged)
{
Debug.Log($"[CLICK-TRACE-ALBUMCARD] {name} - Is enlarged, requesting shrink");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] {name} - Is enlarged, requesting shrink");
OnShrinkRequested?.Invoke(this);
}
else
{
Debug.Log($"[CLICK-TRACE-ALBUMCARD] {name} - Is normal size, requesting enlarge");
Logging.Debug($"[CLICK-TRACE-ALBUMCARD] {name} - Is normal size, requesting enlarge");
OnEnlargeRequested?.Invoke(this);
}
}

View File

@@ -24,7 +24,9 @@ namespace UI.CardSystem
[SerializeField] private BookCurlPro.BookPro book;
[Header("Zone Navigation")]
[SerializeField] private BookTabButton[] zoneTabs; // All zone tab buttons
[SerializeField] private Transform tabContainer; // Container holding all BookTabButton children
private BookTabButton[] zoneTabs; // Discovered zone tab buttons
[Header("Album Card Reveal")]
[SerializeField] private SlotContainer bottomRightSlots;
@@ -42,9 +44,10 @@ namespace UI.CardSystem
private List<AlbumCardPlacementDraggable> _activeCards = new List<AlbumCardPlacementDraggable>();
private const int MAX_VISIBLE_CARDS = 3;
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
base.OnManagedAwake();
// Discover zone tabs from container
DiscoverZoneTabs();
// Make sure we have a CanvasGroup for transitions
if (canvasGroup == null)
@@ -67,6 +70,17 @@ namespace UI.CardSystem
// Set up booster pack button listeners
SetupBoosterButtonListeners();
// Subscribe to book page flip events
if (book != null)
{
book.OnFlip.AddListener(OnPageFlipped);
Logging.Debug("[AlbumViewPage] Subscribed to book.OnFlip event");
}
else
{
Logging.Warning("[AlbumViewPage] Book reference is null, cannot subscribe to OnFlip event!");
}
// Subscribe to CardSystemManager events (managers are guaranteed to be initialized)
if (CardSystemManager.Instance != null)
{
@@ -83,6 +97,36 @@ namespace UI.CardSystem
gameObject.SetActive(false);
}
/// <summary>
/// Discover all BookTabButton components from the tab container
/// </summary>
private void DiscoverZoneTabs()
{
if (tabContainer == null)
{
Debug.LogError("[AlbumViewPage] Tab container is not assigned! Cannot discover zone tabs.");
zoneTabs = new BookTabButton[0];
return;
}
// Get all BookTabButton components from children
zoneTabs = tabContainer.GetComponentsInChildren<BookTabButton>(includeInactive: false);
if (zoneTabs == null || zoneTabs.Length == 0)
{
Logging.Warning($"[AlbumViewPage] No BookTabButton components found in tab container '{tabContainer.name}'!");
zoneTabs = new BookTabButton[0];
}
else
{
Logging.Debug($"[AlbumViewPage] Discovered {zoneTabs.Length} zone tabs from container '{tabContainer.name}'");
foreach (var tab in zoneTabs)
{
Logging.Debug($" - Tab: {tab.name}, Zone: {tab.Zone}, TargetPage: {tab.TargetPage}");
}
}
}
private void SetupBoosterButtonListeners()
{
if (boosterPackButtons == null) return;
@@ -90,6 +134,12 @@ namespace UI.CardSystem
for (int i = 0; i < boosterPackButtons.Length; i++)
{
if (boosterPackButtons[i] == null) continue;
// Unsubscribe from book events
if (book != null)
{
book.OnFlip.RemoveListener(OnPageFlipped);
}
Button button = boosterPackButtons[i].GetComponent<Button>();
if (button != null)
@@ -99,7 +149,7 @@ namespace UI.CardSystem
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
// Unsubscribe from CardSystemManager
if (CardSystemManager.Instance != null)
@@ -131,9 +181,6 @@ namespace UI.CardSystem
// Clean up active cards
CleanupActiveCards();
// Call base implementation
base.OnDestroy();
}
private void OnExitButtonClicked()
@@ -157,7 +204,7 @@ namespace UI.CardSystem
if (Input.InputManager.Instance != null)
{
Input.InputManager.Instance.SetInputMode(_previousInputMode);
Debug.Log($"[AlbumViewPage] Restored input mode to {_previousInputMode} on exit");
Logging.Debug($"[AlbumViewPage] Restored input mode to {_previousInputMode} on exit");
}
if (UIPageController.Instance != null)
@@ -208,7 +255,7 @@ namespace UI.CardSystem
// Store the current input mode before switching
_previousInputMode = Input.InputMode.GameAndUI;
Input.InputManager.Instance.SetInputMode(Input.InputMode.UI);
Debug.Log("[AlbumViewPage] Switched to UI-only input mode on first entry");
Logging.Debug("[AlbumViewPage] Switched to UI-only input mode on first entry");
}
// Subscribe to pending card events while page is active
@@ -217,8 +264,16 @@ namespace UI.CardSystem
CardSystemManager.Instance.OnPendingCardAdded += OnPendingCardAdded;
}
// Spawn pending cards when opening album
SpawnPendingCards();
// Only spawn pending cards if we're already on an album page (not the menu)
if (IsInAlbumProper())
{
Logging.Debug("[AlbumViewPage] Opening directly to album page - spawning cards immediately");
SpawnPendingCards();
}
else
{
Logging.Debug("[AlbumViewPage] Opening to menu page - cards will spawn when entering album");
}
base.TransitionIn();
}
@@ -231,6 +286,9 @@ namespace UI.CardSystem
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)
base.TransitionOut();
}
@@ -304,6 +362,46 @@ namespace UI.CardSystem
}
}
/// <summary>
/// Check if we're currently viewing the album proper (not the menu page)
/// </summary>
private bool IsInAlbumProper()
{
if (book == null)
{
Logging.Warning("[AlbumViewPage] Book reference is null in IsInAlbumProper check");
return false;
}
// Page 1 is the menu/cover, page 2+ are album pages with card slots
bool inAlbum = book.CurrentPaper > 1;
return inAlbum;
}
/// <summary>
/// Called when book page flips - show/hide pending cards based on whether we're in the album proper
/// </summary>
private void OnPageFlipped()
{
bool isInAlbum = IsInAlbumProper();
if (isInAlbum && _activeCards.Count == 0)
{
// Entering album proper and no cards spawned yet - spawn them with animation
Logging.Debug("[AlbumViewPage] Entering album proper - spawning pending cards with animation");
SpawnPendingCards();
}
else if (!isInAlbum && _activeCards.Count > 0)
{
// Returning to menu page - cleanup cards
Logging.Debug("[AlbumViewPage] Returning to menu page - cleaning up pending cards");
CleanupActiveCards();
}
else
{
Logging.Debug($"[AlbumViewPage] Page flipped but no card state change needed (already in correct state)");
}
}
#region Album Card Reveal System
/// <summary>
@@ -327,7 +425,7 @@ namespace UI.CardSystem
int spawnCount = Mathf.Min(uniquePending.Count, MAX_VISIBLE_CARDS);
Debug.Log($"[AlbumViewPage] Spawning {spawnCount} unique pending cards (total pending: {pending.Count})");
Logging.Debug($"[AlbumViewPage] Spawning {spawnCount} unique pending cards (total pending: {pending.Count})");
for (int i = 0; i < spawnCount; i++)
{
@@ -343,14 +441,14 @@ namespace UI.CardSystem
// Guard: Don't spawn cards with zero copies
if (cardData.CopiesOwned <= 0)
{
Debug.LogWarning($"[AlbumViewPage] Skipping spawn of card '{cardData.Name}' with {cardData.CopiesOwned} copies");
Logging.Warning($"[AlbumViewPage] Skipping spawn of card '{cardData.Name}' with {cardData.CopiesOwned} copies");
return;
}
DraggableSlot slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Debug.LogWarning($"[AlbumViewPage] Could not find slot with SlotIndex {slotIndex}");
Logging.Warning($"[AlbumViewPage] Could not find slot with SlotIndex {slotIndex}");
return;
}
@@ -377,11 +475,11 @@ namespace UI.CardSystem
// Track it
_activeCards.Add(cardPlacement);
Debug.Log($"[AlbumViewPage] Spawned card '{cardData.Name}' (CopiesOwned: {cardData.CopiesOwned}) in slot {slotIndex}");
Logging.Debug($"[AlbumViewPage] Spawned card '{cardData.Name}' (CopiesOwned: {cardData.CopiesOwned}) in slot {slotIndex}");
}
else
{
Debug.LogWarning($"[AlbumViewPage] Spawned card has no AlbumCardDraggable component!");
Logging.Warning($"[AlbumViewPage] Spawned card has no AlbumCardDraggable component!");
Destroy(cardObj);
}
}
@@ -395,7 +493,7 @@ namespace UI.CardSystem
// Guard: Don't spawn cards with zero copies
if (card.CopiesOwned <= 0)
{
Debug.LogWarning($"[AlbumViewPage] Ignoring pending card '{card.Name}' with {card.CopiesOwned} copies");
Logging.Warning($"[AlbumViewPage] Ignoring pending card '{card.Name}' with {card.CopiesOwned} copies");
return;
}
@@ -406,7 +504,7 @@ namespace UI.CardSystem
if (alreadySpawned)
{
Debug.Log($"[AlbumViewPage] Card '{card.Name}' already spawned, skipping duplicate spawn");
Logging.Debug($"[AlbumViewPage] Card '{card.Name}' already spawned, skipping duplicate spawn");
return; // Don't spawn duplicates
}
@@ -423,13 +521,13 @@ namespace UI.CardSystem
/// </summary>
private void OnCardRevealed(AlbumCardPlacementDraggable cardPlacement, CardData cardData)
{
Debug.Log($"[AlbumViewPage] Card revealed: {cardData.Name} (Zone: {cardData.Zone}, CopiesOwned: {cardData.CopiesOwned})");
Logging.Debug($"[AlbumViewPage] Card revealed: {cardData.Name} (Zone: {cardData.Zone}, CopiesOwned: {cardData.CopiesOwned})");
// IMMEDIATELY move card from pending to inventory upon reveal
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.MarkCardAsPlaced(cardData);
Debug.Log($"[AlbumViewPage] Moved card '{cardData.Name}' from pending to inventory on reveal");
Logging.Debug($"[AlbumViewPage] Moved card '{cardData.Name}' from pending to inventory on reveal");
}
// Remove this card from active cards list
@@ -441,12 +539,12 @@ namespace UI.CardSystem
if (currentZone != cardData.Zone)
{
// Card is from a different zone - navigate to its zone
Debug.Log($"[AlbumViewPage] Card zone ({cardData.Zone}) doesn't match current zone ({currentZone}). Navigating to card's zone...");
Logging.Debug($"[AlbumViewPage] Card zone ({cardData.Zone}) doesn't match current zone ({currentZone}). Navigating to card's zone...");
NavigateToZone(cardData.Zone);
}
else
{
Debug.Log($"[AlbumViewPage] Card zone ({cardData.Zone}) matches current zone - no navigation needed.");
Logging.Debug($"[AlbumViewPage] Card zone ({cardData.Zone}) matches current zone - no navigation needed.");
}
// Shuffle remaining cards to front and spawn next unique card
@@ -461,7 +559,7 @@ namespace UI.CardSystem
/// </summary>
private void OnCardPlacedInAlbum(AlbumCardPlacementDraggable cardPlacement, CardData cardData)
{
Debug.Log($"[AlbumViewPage] Card placed in album slot: {cardData.Name}");
Logging.Debug($"[AlbumViewPage] Card placed in album slot: {cardData.Name}");
// Unsubscribe from events (card is now static in album)
cardPlacement.OnCardRevealed -= OnCardRevealed;
@@ -545,7 +643,9 @@ namespace UI.CardSystem
public CardZone GetCurrentZone()
{
if (book == null || zoneTabs == null || zoneTabs.Length == 0)
{
return CardZone.AppleHills; // Default
}
int currentPage = book.CurrentPaper;
@@ -557,9 +657,8 @@ namespace UI.CardSystem
return tab.Zone;
}
}
// Fallback to first zone
return zoneTabs[0].Zone;
return CardZone.NotApplicable;
}
/// <summary>
@@ -567,16 +666,20 @@ namespace UI.CardSystem
/// </summary>
public BookTabButton GetTabForZone(CardZone zone)
{
if (zoneTabs == null)
{
return null;
}
foreach (var tab in zoneTabs)
{
{
if (tab.Zone == zone)
{
return tab;
}
}
return null;
}
@@ -643,7 +746,7 @@ namespace UI.CardSystem
{
if (card == null) return;
Debug.Log($"[AlbumViewPage] OnCardEnlargeRequested called for card: {card.name}, current parent: {card.transform.parent.name}");
Logging.Debug($"[AlbumViewPage] OnCardEnlargeRequested called for card: {card.name}, current parent: {card.transform.parent.name}");
// IMPORTANT: Call EnlargeCard FIRST to store original parent (the slot)
// BEFORE reparenting to the enlarged container
@@ -653,7 +756,7 @@ namespace UI.CardSystem
if (cardEnlargedBackdrop != null)
{
cardEnlargedBackdrop.SetActive(true);
Debug.Log($"[AlbumViewPage] Backdrop shown");
Logging.Debug($"[AlbumViewPage] Backdrop shown");
}
// NOW reparent card to enlarged container (above backdrop)
@@ -661,10 +764,10 @@ namespace UI.CardSystem
{
card.transform.SetParent(cardEnlargedContainer, true);
card.transform.SetAsLastSibling(); // Ensure on top
Debug.Log($"[AlbumViewPage] Card reparented to enlarged container");
Logging.Debug($"[AlbumViewPage] Card reparented to enlarged container");
}
Debug.Log($"[AlbumViewPage] Card enlarged: {card.GetCardData()?.Name}");
Logging.Debug($"[AlbumViewPage] Card enlarged: {card.GetCardData()?.Name}");
}
/// <summary>
@@ -692,7 +795,7 @@ namespace UI.CardSystem
card.transform.localRotation = card.GetOriginalLocalRotation();
}
Debug.Log($"[AlbumViewPage] Card shrunk: {card.GetCardData()?.Name}");
Logging.Debug($"[AlbumViewPage] Card shrunk: {card.GetCardData()?.Name}");
}
/// <summary>
@@ -703,7 +806,7 @@ namespace UI.CardSystem
if (previewCardTransform == null)
return;
Debug.Log($"[AlbumViewPage] ShowSlotPreview called for slot: {slot.name}");
Logging.Debug($"[AlbumViewPage] ShowSlotPreview called for slot: {slot.name}");
// Show backdrop
if (cardEnlargedBackdrop != null)
@@ -727,7 +830,7 @@ namespace UI.CardSystem
if (previewCardTransform == null)
return;
Debug.Log($"[AlbumViewPage] HideSlotPreview called for slot: {slot.name}");
Logging.Debug($"[AlbumViewPage] HideSlotPreview called for slot: {slot.name}");
// Hide backdrop
if (cardEnlargedBackdrop != null)

View File

@@ -1,6 +1,7 @@
using System;
using AppleHills.Data.CardSystem;
using BookCurlPro;
using Core;
using UnityEngine;
using UnityEngine.UI;
using Tween = Pixelplacement.Tween;
@@ -69,7 +70,7 @@ namespace UI.CardSystem
{
if (book == null)
{
Debug.LogWarning($"[BookTabButton] No BookPro reference assigned on {gameObject.name}");
Logging.Warning($"[BookTabButton] No BookPro reference assigned on {gameObject.name}");
return;
}

View File

@@ -40,10 +40,8 @@ namespace UI.CardSystem
private TweenBase _activeTween;
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
base.OnManagedAwake();
// Store original scale for pulse animation
if (dotBackground != null)
{
@@ -72,16 +70,13 @@ namespace UI.CardSystem
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
// Unsubscribe from CardSystemManager events to prevent memory leaks
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.OnBoosterCountChanged -= OnBoosterCountChanged;
}
// Call base implementation
base.OnDestroy();
}
/// <summary>

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Core;
using Data.CardSystem;
using Pixelplacement;
using UI.Core;
@@ -33,7 +34,6 @@ namespace UI.CardSystem
[SerializeField] private float cardSpacing = 150f;
[Header("Settings")]
[SerializeField] private float cardRevealDelay = 0.5f;
[SerializeField] private float boosterDisappearDuration = 0.5f;
[SerializeField] private CinemachineImpulseSource impulseSource;
[SerializeField] private ParticleSystem openingParticleSystem;
@@ -68,7 +68,7 @@ namespace UI.CardSystem
}
else
{
Debug.LogWarning("[BoosterOpeningPage] albumIcon does not have a Button component!");
Logging.Warning("[BoosterOpeningPage] albumIcon does not have a Button component!");
}
}
@@ -76,7 +76,7 @@ namespace UI.CardSystem
gameObject.SetActive(false);
}
private void OnDestroy()
internal override void OnManagedDestroy()
{
// Unsubscribe from dismiss button
if (_dismissButton != null)
@@ -111,13 +111,20 @@ namespace UI.CardSystem
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
Debug.Log("[BoosterOpeningPage] Dismiss button clicked, popping page from stack");
Logging.Debug("[BoosterOpeningPage] Dismiss button clicked, popping page from stack");
}
}
public override void TransitionIn()
{
base.TransitionIn();
// Ensure album icon is visible when page opens
if (albumIcon != null)
{
albumIcon.SetActive(true);
}
InitializeBoosterDisplay();
}
@@ -132,17 +139,17 @@ namespace UI.CardSystem
/// </summary>
private void InitializeBoosterDisplay()
{
Debug.Log($"[BoosterOpeningPage] InitializeBoosterDisplay called with {_availableBoosterCount} boosters available");
Logging.Debug($"[BoosterOpeningPage] InitializeBoosterDisplay called with {_availableBoosterCount} boosters available");
if (boosterPackPrefab == null)
{
Debug.LogWarning("BoosterOpeningPage: No booster pack prefab assigned!");
Logging.Warning("BoosterOpeningPage: No booster pack prefab assigned!");
return;
}
if (bottomRightSlots == null || bottomRightSlots.SlotCount == 0)
{
Debug.LogWarning("BoosterOpeningPage: No slots available!");
Logging.Warning("BoosterOpeningPage: No slots available!");
return;
}
@@ -152,7 +159,7 @@ namespace UI.CardSystem
// Calculate how many boosters to show (max 3, or available count, whichever is lower)
int visibleCount = Mathf.Min(_availableBoosterCount, MAX_VISIBLE_BOOSTERS);
Debug.Log($"[BoosterOpeningPage] Will spawn {visibleCount} boosters");
Logging.Debug($"[BoosterOpeningPage] Will spawn {visibleCount} boosters");
// Spawn boosters and assign to slots
for (int i = 0; i < visibleCount; i++)
@@ -165,11 +172,11 @@ namespace UI.CardSystem
{
centerOpeningSlot.OnOccupied += OnBoosterPlacedInCenter;
centerOpeningSlot.OnVacated += OnBoosterRemovedFromCenter;
Debug.Log($"[BoosterOpeningPage] Subscribed to center slot events");
Logging.Debug($"[BoosterOpeningPage] Subscribed to center slot events");
}
else
{
Debug.LogWarning("[BoosterOpeningPage] centerOpeningSlot is null!");
Logging.Warning("[BoosterOpeningPage] centerOpeningSlot is null!");
}
}
@@ -181,7 +188,7 @@ namespace UI.CardSystem
DraggableSlot slot = FindSlotByIndex(slotIndex);
if (slot == null)
{
Debug.LogWarning($"[BoosterOpeningPage] Could not find slot with SlotIndex {slotIndex}!");
Logging.Warning($"[BoosterOpeningPage] Could not find slot with SlotIndex {slotIndex}!");
return;
}
@@ -204,11 +211,11 @@ namespace UI.CardSystem
// Track it
_activeBoostersInSlots.Add(booster);
Debug.Log($"[BoosterOpeningPage] Spawned booster in slot with SlotIndex {slotIndex}");
Logging.Debug($"[BoosterOpeningPage] Spawned booster in slot with SlotIndex {slotIndex}");
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] Spawned booster has no BoosterPackDraggable component!");
Logging.Warning($"[BoosterOpeningPage] Spawned booster has no BoosterPackDraggable component!");
Destroy(boosterObj);
}
}
@@ -244,7 +251,7 @@ namespace UI.CardSystem
booster.CurrentSlot.Vacate();
}
Debug.Log($"[BoosterOpeningPage] Removed booster from slot {slotIndex}");
Logging.Debug($"[BoosterOpeningPage] Removed booster from slot {slotIndex}");
}
_activeBoostersInSlots.RemoveAt(slotIndex);
@@ -264,7 +271,7 @@ namespace UI.CardSystem
RemoveBoosterFromSlot(lastIndex);
}
Debug.Log($"[BoosterOpeningPage] Updated visible boosters: {_activeBoostersInSlots.Count}/{targetCount}");
Logging.Debug($"[BoosterOpeningPage] Updated visible boosters: {_activeBoostersInSlots.Count}/{targetCount}");
}
/// <summary>
@@ -274,7 +281,7 @@ namespace UI.CardSystem
{
if (_activeBoostersInSlots.Count == 0) return;
Debug.Log($"[BoosterOpeningPage] Shuffling {_activeBoostersInSlots.Count} boosters to front slots");
Logging.Debug($"[BoosterOpeningPage] Shuffling {_activeBoostersInSlots.Count} boosters to front slots");
// Unassign all boosters from their current slots
foreach (var booster in _activeBoostersInSlots)
@@ -294,12 +301,12 @@ namespace UI.CardSystem
if (targetSlot != null)
{
Debug.Log($"[BoosterOpeningPage] Assigning booster to slot with SlotIndex {i} {targetSlot.name}");
Logging.Debug($"[BoosterOpeningPage] Assigning booster to slot with SlotIndex {i} {targetSlot.name}");
booster.AssignToSlot(targetSlot, true); // Animate the move
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] Could not find slot with SlotIndex {i} {targetSlot.name}");
Logging.Warning($"[BoosterOpeningPage] Could not find slot with SlotIndex {i} {targetSlot.name}");
}
}
}
@@ -344,7 +351,7 @@ namespace UI.CardSystem
if (slot != null && !slot.IsOccupied)
{
SpawnBoosterInSlot(i);
Debug.Log($"[BoosterOpeningPage] Spawned new booster in slot with SlotIndex {i}");
Logging.Debug($"[BoosterOpeningPage] Spawned new booster in slot with SlotIndex {i}");
break;
}
}
@@ -363,13 +370,20 @@ namespace UI.CardSystem
// Remove from active slots list
_activeBoostersInSlots.Remove(booster);
// Hide album icon when booster is placed in center
if (albumIcon != null)
{
albumIcon.SetActive(false);
Logging.Debug($"[BoosterOpeningPage] Album icon hidden");
}
// Lock the slot so it can't be dragged out
Debug.Log($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}");
Logging.Debug($"[BoosterOpeningPage] Locking center slot. IsLocked before: {centerOpeningSlot.IsLocked}");
centerOpeningSlot.SetLocked(true);
Debug.Log($"[BoosterOpeningPage] IsLocked after: {centerOpeningSlot.IsLocked}");
Logging.Debug($"[BoosterOpeningPage] IsLocked after: {centerOpeningSlot.IsLocked}");
// Configure booster for opening (disables drag, enables tapping, resets tap count)
Debug.Log($"[BoosterOpeningPage] Calling SetInOpeningSlot(true) on booster");
Logging.Debug($"[BoosterOpeningPage] Calling SetInOpeningSlot(true) on booster");
booster.SetInOpeningSlot(true);
// Subscribe to tap events for visual feedback
@@ -384,7 +398,7 @@ namespace UI.CardSystem
// Shuffle remaining boosters to occupy the first slots
ShuffleBoostersToFront();
Debug.Log($"[BoosterOpeningPage] Booster placed in center, ready for taps. Active boosters in slots: {_activeBoostersInSlots.Count}");
Logging.Debug($"[BoosterOpeningPage] Booster placed in center, ready for taps. Active boosters in slots: {_activeBoostersInSlots.Count}");
}
/// <summary>
@@ -403,12 +417,12 @@ namespace UI.CardSystem
}
_currentBoosterInCenter = null;
Debug.Log($"[BoosterOpeningPage] Booster removed from center");
Logging.Debug($"[BoosterOpeningPage] Booster removed from center");
}
private void OnBoosterTapped(BoosterPackDraggable booster, int currentTaps, int maxTaps)
{
Debug.Log($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
Logging.Debug($"[BoosterOpeningPage] Booster tapped: {currentTaps}/{maxTaps}");
// Fire Cinemachine impulse with random velocity (excluding Z)
if (impulseSource != null)
@@ -433,7 +447,7 @@ namespace UI.CardSystem
/// </summary>
private void OnBoosterOpened(BoosterPackDraggable booster)
{
Debug.Log($"[BoosterOpeningPage] Booster opened, playing particle effect");
Logging.Debug($"[BoosterOpeningPage] Booster opened, playing particle effect");
// Reset and play particle system
if (openingParticleSystem != null)
@@ -471,7 +485,7 @@ namespace UI.CardSystem
{
if (_isProcessingOpening) return;
Debug.Log($"[BoosterOpeningPage] Booster ready to open!");
Logging.Debug($"[BoosterOpeningPage] Booster ready to open!");
// Trigger the actual opening sequence
booster.TriggerOpen();
@@ -514,7 +528,7 @@ namespace UI.CardSystem
// WaitForCardReveals already includes: 0.5s wait + (cardCount * 0.5s stagger) + 0.5s animation + 0.5s final
// Total is: 1.5s + (cardCount * 0.5s)
// For 5 cards that's 4 seconds total, which should be enough
Debug.Log("[BoosterOpeningPage] Last booster opened, auto-transitioning to album main page");
Logging.Debug("[BoosterOpeningPage] Last booster opened, auto-transitioning to album main page");
if (UIPageController.Instance != null)
{
UIPageController.Instance.PopPage();
@@ -561,7 +575,7 @@ namespace UI.CardSystem
{
if (flippableCardPrefab == null || cardDisplayContainer == null)
{
Debug.LogWarning("BoosterOpeningPage: Missing card prefab or container!");
Logging.Warning("BoosterOpeningPage: Missing card prefab or container!");
return;
}
@@ -605,7 +619,7 @@ namespace UI.CardSystem
}
else
{
Debug.LogWarning($"[BoosterOpeningPage] FlippableCard component not found on card {i}!");
Logging.Warning($"[BoosterOpeningPage] FlippableCard component not found on card {i}!");
}
_currentRevealedCards.Add(cardObj);
@@ -621,7 +635,7 @@ namespace UI.CardSystem
/// </summary>
private void OnCardFlipStarted(FlippableCard flippingCard)
{
Debug.Log($"[BoosterOpeningPage] Card flip started, disabling all other cards.");
Logging.Debug($"[BoosterOpeningPage] Card flip started, disabling all other cards.");
// Disable ALL cards immediately to prevent multi-flip
foreach (GameObject cardObj in _currentRevealedCards)
@@ -639,14 +653,14 @@ namespace UI.CardSystem
/// </summary>
private void OnCardRevealed(int cardIndex)
{
Debug.Log($"[BoosterOpeningPage] Card {cardIndex} revealed!");
Logging.Debug($"[BoosterOpeningPage] Card {cardIndex} revealed!");
_revealedCardCount++;
// Get the flippable card and card data
FlippableCard flippableCard = _currentRevealedCards[cardIndex].GetComponent<FlippableCard>();
if (flippableCard == null)
{
Debug.LogWarning($"[BoosterOpeningPage] FlippableCard not found for card {cardIndex}!");
Logging.Warning($"[BoosterOpeningPage] FlippableCard not found for card {cardIndex}!");
return;
}
@@ -657,7 +671,7 @@ namespace UI.CardSystem
if (isNew)
{
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is NEW!");
Logging.Debug($"[BoosterOpeningPage] Card '{cardData.Name}' is NEW!");
flippableCard.ShowAsNew();
}
else
@@ -665,7 +679,7 @@ namespace UI.CardSystem
// Check if card is already Legendary - if so, skip progress bar and auto-progress
if (existingCard.Rarity == AppleHills.Data.CardSystem.CardRarity.Legendary)
{
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is LEGENDARY - auto-progressing!");
Logging.Debug($"[BoosterOpeningPage] Card '{cardData.Name}' is LEGENDARY - auto-progressing!");
// Add to inventory immediately and move to next card
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(cardData);
_cardsCompletedInteraction++;
@@ -675,14 +689,14 @@ namespace UI.CardSystem
}
int ownedCount = existingCard.CopiesOwned;
Debug.Log($"[BoosterOpeningPage] Card '{cardData.Name}' is a REPEAT! Owned: {ownedCount}");
Logging.Debug($"[BoosterOpeningPage] Card '{cardData.Name}' is a REPEAT! Owned: {ownedCount}");
// Check if this card will trigger an upgrade (ownedCount + 1 >= threshold)
bool willUpgrade = (ownedCount + 1) >= flippableCard.CardsToUpgrade && existingCard.Rarity < AppleHills.Data.CardSystem.CardRarity.Legendary;
if (willUpgrade)
{
Debug.Log($"[BoosterOpeningPage] This card will trigger upgrade! ({ownedCount + 1}/{flippableCard.CardsToUpgrade})");
Logging.Debug($"[BoosterOpeningPage] This card will trigger upgrade! ({ownedCount + 1}/{flippableCard.CardsToUpgrade})");
// Show as repeat - progress bar will fill and auto-trigger upgrade
flippableCard.ShowAsRepeatWithUpgrade(ownedCount, existingCard);
}
@@ -705,7 +719,7 @@ namespace UI.CardSystem
/// </summary>
private void OnCardCompletedInteraction(FlippableCard card, int cardIndex)
{
Debug.Log($"[BoosterOpeningPage] Card {cardIndex} interaction complete!");
Logging.Debug($"[BoosterOpeningPage] Card {cardIndex} interaction complete!");
// Add card to inventory NOW (after player saw it)
Data.CardSystem.CardSystemManager.Instance.AddCardToInventoryDelayed(card.CardData);
@@ -722,7 +736,7 @@ namespace UI.CardSystem
// Re-enable all unrevealed cards (they can be flipped now)
EnableUnrevealedCards();
Debug.Log($"[BoosterOpeningPage] Cards completed interaction: {_cardsCompletedInteraction}/{_currentCardData.Length}");
Logging.Debug($"[BoosterOpeningPage] Cards completed interaction: {_cardsCompletedInteraction}/{_currentCardData.Length}");
}
/// <summary>
@@ -743,7 +757,7 @@ namespace UI.CardSystem
}
}
Debug.Log($"[BoosterOpeningPage] Set active card. Only one card is now clickable.");
Logging.Debug($"[BoosterOpeningPage] Set active card. Only one card is now clickable.");
}
/// <summary>
@@ -760,7 +774,7 @@ namespace UI.CardSystem
}
}
Debug.Log($"[BoosterOpeningPage] Re-enabled unrevealed cards for flipping.");
Logging.Debug($"[BoosterOpeningPage] Re-enabled unrevealed cards for flipping.");
}
/// <summary>
@@ -768,7 +782,7 @@ namespace UI.CardSystem
/// </summary>
private void OnCardClickedWhileInactive(FlippableCard inactiveCard)
{
Debug.Log($"[BoosterOpeningPage] Inactive card clicked, jiggling active card.");
Logging.Debug($"[BoosterOpeningPage] Inactive card clicked, jiggling active card.");
if (_currentActiveCard != null)
{
@@ -787,7 +801,7 @@ namespace UI.CardSystem
yield return null;
}
Debug.Log($"[BoosterOpeningPage] All cards revealed! Waiting for interactions...");
Logging.Debug($"[BoosterOpeningPage] All cards revealed! Waiting for interactions...");
// Wait until all cards have completed their new/repeat interaction
while (_cardsCompletedInteraction < _currentCardData.Length)
@@ -795,11 +809,18 @@ namespace UI.CardSystem
yield return null;
}
Debug.Log($"[BoosterOpeningPage] All interactions complete! Animating cards to album...");
Logging.Debug($"[BoosterOpeningPage] All interactions complete! Animating cards to album...");
// All cards revealed and interacted with, wait a moment
yield return new WaitForSeconds(0.5f);
// Show album icon before cards start tweening to it
if (albumIcon != null)
{
albumIcon.SetActive(true);
Logging.Debug($"[BoosterOpeningPage] Album icon shown for card tween target");
}
// Animate cards to album icon (or center if no icon assigned) with staggered delays
Vector3 targetPosition = albumIcon != null ? albumIcon.transform.position : Vector3.zero;
@@ -828,6 +849,8 @@ namespace UI.CardSystem
_currentRevealedCards.Clear();
yield return new WaitForSeconds(totalAnimationTime);
// Album icon stays visible for next booster (will be hidden when next booster is placed)
}
/// <summary>

View File

@@ -1,4 +1,5 @@
using System.Collections;
using Core;
using Data.CardSystem;
using UnityEngine;
using UnityEngine.Events;
@@ -58,7 +59,7 @@ namespace UI.CardSystem
{
if (Instance != null && Instance != this)
{
Debug.LogWarning("[BoosterPackGiver] Duplicate instance detected. Destroying this component.");
Logging.Warning("[BoosterPackGiver] Duplicate instance detected. Destroying this component.");
Destroy(this);
yield break;
}

View File

@@ -1,4 +1,5 @@
using UI.Core;
using Core;
using UI.Core;
using UnityEngine;
using UnityEngine.UI;
@@ -46,7 +47,7 @@ namespace UI.CardSystem
openAlbumButton.onClick.RemoveListener(OnOpenAlbumClicked);
}
Debug.Log("ALBUM: CardAlbumDestroyed");
Logging.Debug("ALBUM: CardAlbumDestroyed");
}
private void OnOpenAlbumClicked()

View File

@@ -335,7 +335,7 @@ namespace UI.CardSystem
{
if (editorCardDefinition == null)
{
UnityEngine.Debug.LogWarning("[CardDisplay] No Card Definition assigned in Editor Tools.");
Debug.LogWarning("[CardDisplay] No Card Definition assigned in Editor Tools.");
return;
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections;
using AppleHills.Data.CardSystem;
using Core;
using Data.CardSystem;
using Pixelplacement;
using UI.DragAndDrop.Core;
@@ -63,15 +64,15 @@ namespace UI.CardSystem
/// </summary>
public void RevealCard()
{
if (_isRevealed) return;
if (_isRevealed)
{
return;
}
_isRevealed = true;
if (flippableCard != null)
{
flippableCard.FlipToReveal();
}
OnCardRevealed?.Invoke(this, _cardData);
}
@@ -82,7 +83,7 @@ namespace UI.CardSystem
{
if (_cardData == null)
{
Debug.LogWarning("[AlbumCardPlacementDraggable] Cannot snap to slot - no card data assigned.");
Logging.Warning("[AlbumCardPlacementDraggable] Cannot snap to slot - no card data assigned.");
return;
}
@@ -117,7 +118,7 @@ namespace UI.CardSystem
TweenExtractedCardToSlot(extractedCard, () =>
{
// After animation completes
Debug.Log($"[AlbumCardPlacementDraggable] Card placement animation complete for {_cardData.Name}");
Logging.Debug($"[AlbumCardPlacementDraggable] Card placement animation complete for {_cardData.Name}");
// Notify that card was placed
OnCardPlacedInAlbum?.Invoke(this, _cardData);
@@ -128,13 +129,13 @@ namespace UI.CardSystem
}
else
{
Debug.LogWarning("[AlbumCardPlacementDraggable] Failed to extract AlbumCard from wrapper!");
Logging.Warning("[AlbumCardPlacementDraggable] Failed to extract AlbumCard from wrapper!");
}
}
}
else
{
Debug.LogWarning($"[AlbumCardPlacementDraggable] Could not find matching slot for card '{_cardData.Name}' (Zone: {_cardData.Zone}, Index: {_cardData.CollectionIndex})");
Logging.Warning($"[AlbumCardPlacementDraggable] Could not find matching slot for card '{_cardData.Name}' (Zone: {_cardData.Zone}, Index: {_cardData.CollectionIndex})");
}
}
@@ -162,7 +163,7 @@ namespace UI.CardSystem
Tween.LocalRotation(cardTransform, Quaternion.identity, snapDuration, 0f, Tween.EaseOutBack,
completeCallback: () =>
{
Debug.Log($"[AlbumCardPlacementDraggable] Tween complete for extracted card {card.name}, final height: {cardRect.sizeDelta.y}");
Logging.Debug($"[AlbumCardPlacementDraggable] Tween complete for extracted card {card.name}, final height: {cardRect.sizeDelta.y}");
onComplete?.Invoke();
});
}
@@ -191,9 +192,6 @@ namespace UI.CardSystem
protected override void OnPointerUpHook(bool longPress)
{
base.OnPointerUpHook(longPress);
Debug.Log($"[CLICK-TRACE-PLACEMENT] OnPointerUpHook on {name}, _wasDragged={_wasDragged}, _isRevealed={_isRevealed}, _waitingForPlacementTap={_waitingForPlacementTap}, longPress={longPress}");
_isHolding = false;
// Cancel hold timer if running
@@ -202,40 +200,35 @@ namespace UI.CardSystem
StopCoroutine(_holdRevealCoroutine);
_holdRevealCoroutine = null;
}
else
{
}
// Handle tap (not dragged)
if (!_wasDragged)
{
if (!_isRevealed)
{
Debug.Log($"[CLICK-TRACE-PLACEMENT] {name} - First tap, revealing card");
// First tap: reveal the card
RevealCard();
_waitingForPlacementTap = true;
}
else if (_waitingForPlacementTap)
{
Debug.Log($"[CLICK-TRACE-PLACEMENT] {name} - Second tap, snapping to slot");
// Second tap: snap to slot
_waitingForPlacementTap = false;
SnapToAlbumSlot();
}
else
{
Debug.Log($"[CLICK-TRACE-PLACEMENT] {name} - Tap after reveal but not waiting for placement tap");
}
}
else if (_isDragRevealing)
{
Debug.Log($"[CLICK-TRACE-PLACEMENT] {name} - Was drag-revealed, auto-snapping");
// Was drag-revealed, auto-snap on release
_isDragRevealing = false;
SnapToAlbumSlot();
}
else
{
Debug.Log($"[CLICK-TRACE-PLACEMENT] {name} - Was dragged but no special handling");
}
}
/// <summary>
@@ -250,7 +243,6 @@ namespace UI.CardSystem
{
RevealCard();
_isDragRevealing = true;
Debug.Log("[AlbumCardDraggable] Card revealed via hold");
}
_holdRevealCoroutine = null;

View File

@@ -1,4 +1,5 @@
using AppleHills.Data.CardSystem;
using Core;
using Data.CardSystem;
using UI.DragAndDrop.Core;
using UnityEngine;
@@ -72,7 +73,7 @@ namespace UI.CardSystem
albumCard.SetParentSlot(this);
// Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.RegisterAlbumCard(albumCard);
@@ -133,7 +134,7 @@ namespace UI.CardSystem
// Keep preview hidden - it'll show when user taps to enlarge
previewCardDisplay.gameObject.SetActive(false);
Debug.Log($"[AlbumCardSlot] Setup preview card for {targetCardDefinition.Name} (hidden until tap)");
Logging.Debug($"[AlbumCardSlot] Setup preview card for {targetCardDefinition.Name} (hidden until tap)");
}
/// <summary>
@@ -152,7 +153,7 @@ namespace UI.CardSystem
// Guard: need prefab to spawn
if (albumCardPrefab == null)
{
Debug.LogWarning($"[AlbumCardSlot] No albumCardPrefab assigned for slot targeting {targetCardDefinition.name}");
Logging.Warning($"[AlbumCardSlot] No albumCardPrefab assigned for slot targeting {targetCardDefinition.name}");
return;
}
@@ -207,17 +208,17 @@ namespace UI.CardSystem
}
// Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.RegisterAlbumCard(albumCard);
}
Debug.Log($"[AlbumCardSlot] Spawned owned card '{cardData.Name}' ({cardData.Rarity}) in slot");
Logging.Debug($"[AlbumCardSlot] Spawned owned card '{cardData.Name}' ({cardData.Rarity}) in slot");
}
else
{
Debug.LogWarning($"[AlbumCardSlot] Spawned prefab has no AlbumCard component!");
Logging.Warning($"[AlbumCardSlot] Spawned prefab has no AlbumCard component!");
Destroy(cardObj);
}
}
@@ -235,30 +236,30 @@ namespace UI.CardSystem
/// </summary>
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log($"[CLICK-TRACE-SLOT] OnPointerClick on {name}, _isOccupiedPermanently={_isOccupiedPermanently}, _placedCard={((_placedCard != null) ? _placedCard.name : "NULL")}, _isPreviewShowing={_isPreviewShowing}, position={eventData.position}");
Logging.Debug($"[CLICK-TRACE-SLOT] OnPointerClick on {name}, _isOccupiedPermanently={_isOccupiedPermanently}, _placedCard={((_placedCard != null) ? _placedCard.name : "NULL")}, _isPreviewShowing={_isPreviewShowing}, position={eventData.position}");
// Only handle clicks if slot is empty
if (_isOccupiedPermanently || _placedCard != null)
{
Debug.Log($"[CLICK-TRACE-SLOT] {name} - Slot is occupied, ignoring");
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Slot is occupied, ignoring");
return;
}
// Only handle if we have a preview card setup
if (previewCardDisplay == null || targetCardDefinition == null)
{
Debug.Log($"[CLICK-TRACE-SLOT] {name} - No preview setup, ignoring");
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - No preview setup, ignoring");
return;
}
if (_isPreviewShowing)
{
Debug.Log($"[CLICK-TRACE-SLOT] {name} - Preview is showing, hiding it");
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Preview is showing, hiding it");
HidePreview();
}
else
{
Debug.Log($"[CLICK-TRACE-SLOT] {name} - Preview is hidden, showing it");
Logging.Debug($"[CLICK-TRACE-SLOT] {name} - Preview is hidden, showing it");
ShowPreview();
}
}
@@ -283,7 +284,7 @@ namespace UI.CardSystem
previewCardDisplay.transform.localScale = _previewOriginalScale;
// Get AlbumViewPage to show backdrop and reparent
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.ShowSlotPreview(this, previewCardDisplay.transform);
@@ -293,7 +294,7 @@ namespace UI.CardSystem
Pixelplacement.Tween.LocalScale(previewCardDisplay.transform, _previewOriginalScale * previewEnlargedScale,
previewScaleDuration, 0f, Pixelplacement.Tween.EaseOutBack);
Debug.Log($"[AlbumCardSlot] Showing preview for {targetCardDefinition.Name}");
Logging.Debug($"[AlbumCardSlot] Showing preview for {targetCardDefinition.Name}");
}
/// <summary>
@@ -310,7 +311,7 @@ namespace UI.CardSystem
previewCardDisplay.SetPreviewMode(false, null);
// Get AlbumViewPage to hide backdrop
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>();
AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null)
{
albumPage.HideSlotPreview(this, previewCardDisplay.transform, () =>
@@ -340,11 +341,11 @@ namespace UI.CardSystem
// Hide the preview card after returning to slot
previewCardDisplay.gameObject.SetActive(false);
Debug.Log($"[AlbumCardSlot] Preview hidden and reset for {targetCardDefinition.Name}");
Logging.Debug($"[AlbumCardSlot] Preview hidden and reset for {targetCardDefinition.Name}");
});
}
Debug.Log($"[AlbumCardSlot] Hiding preview for {targetCardDefinition.Name}");
Logging.Debug($"[AlbumCardSlot] Hiding preview for {targetCardDefinition.Name}");
}
/// <summary>
@@ -352,7 +353,7 @@ namespace UI.CardSystem
/// </summary>
public void DismissPreview()
{
Debug.Log($"[CLICK-TRACE-SLOT] DismissPreview called on {name}");
Logging.Debug($"[CLICK-TRACE-SLOT] DismissPreview called on {name}");
HidePreview();
}

View File

@@ -1,4 +1,5 @@
using UI.DragAndDrop.Core;
using Core;
using UI.DragAndDrop.Core;
using UnityEngine;
namespace UI.CardSystem.DragDrop
@@ -127,10 +128,10 @@ namespace UI.CardSystem.DragDrop
/// </summary>
public void SetInOpeningSlot(bool inSlot)
{
Debug.Log($"[BoosterPackDraggable] SetInOpeningSlot({inSlot}) called on {name}");
Logging.Debug($"[BoosterPackDraggable] SetInOpeningSlot({inSlot}) called on {name}");
SetDraggingEnabled(!inSlot); // Disable dragging when in opening slot
Debug.Log($"[BoosterPackDraggable] SetDraggingEnabled({!inSlot}) called");
Logging.Debug($"[BoosterPackDraggable] SetDraggingEnabled({!inSlot}) called");
canTapToOpen = inSlot; // Enable tap-to-open when in opening slot

View File

@@ -307,7 +307,7 @@ namespace UI.CardSystem.DragDrop
}
#endregion
protected override void OnDestroy()
{
base.OnDestroy();

View File

@@ -106,7 +106,7 @@ namespace UI.CardSystem.DragDrop
base.OnDragEndedVisual();
// Card-specific visual effects when dragging ends
}
protected override void OnDestroy()
{
base.OnDestroy();

View File

@@ -1,5 +1,6 @@
using System;
using AppleHills.Data.CardSystem;
using Core;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -40,7 +41,6 @@ namespace UI.CardSystem
// State
private bool _isFlipped = false;
private bool _isFlipping = false;
private bool _isHovering = false;
private TweenBase _idleHoverTween;
private CardData _cardData;
private Vector2 _originalPosition; // Track original spawn position
@@ -241,8 +241,6 @@ namespace UI.CardSystem
if (_isFlipped || _isFlipping)
return;
_isHovering = true;
// Scale up slightly on hover
Tween.LocalScale(transform, Vector3.one * hoverScaleMultiplier, 0.2f, 0f, Tween.EaseOutBack);
}
@@ -252,20 +250,18 @@ namespace UI.CardSystem
if (_isFlipped || _isFlipping)
return;
_isHovering = false;
// Scale back to normal
Tween.LocalScale(transform, Vector3.one, 0.2f, 0f, Tween.EaseOutBack);
}
public void OnPointerClick(PointerEventData eventData)
{
Debug.Log($"[CLICK-TRACE-FLIPPABLE] OnPointerClick on {name}, _isClickable={_isClickable}, _isWaitingForTap={_isWaitingForTap}, _isFlipped={_isFlipped}, position={eventData.position}");
Logging.Debug($"[CLICK-TRACE-FLIPPABLE] OnPointerClick on {name}, _isClickable={_isClickable}, _isWaitingForTap={_isWaitingForTap}, _isFlipped={_isFlipped}, position={eventData.position}");
// If not clickable, notify and return
if (!_isClickable)
{
Debug.Log($"[CLICK-TRACE-FLIPPABLE] {name} - Not clickable, firing OnClickedWhileInactive");
Logging.Debug($"[CLICK-TRACE-FLIPPABLE] {name} - Not clickable, firing OnClickedWhileInactive");
OnClickedWhileInactive?.Invoke(this);
return;
}
@@ -273,7 +269,7 @@ namespace UI.CardSystem
// If waiting for tap after reveal, handle that
if (_isWaitingForTap)
{
Debug.Log($"[CLICK-TRACE-FLIPPABLE] {name} - Waiting for tap, dismissing enlarged state");
Logging.Debug($"[CLICK-TRACE-FLIPPABLE] {name} - Waiting for tap, dismissing enlarged state");
OnCardTappedAfterReveal?.Invoke(this);
_isWaitingForTap = false;
return;
@@ -281,11 +277,11 @@ namespace UI.CardSystem
if (_isFlipped || _isFlipping)
{
Debug.Log($"[CLICK-TRACE-FLIPPABLE] {name} - Ignoring click (flipped={_isFlipped}, flipping={_isFlipping})");
Logging.Debug($"[CLICK-TRACE-FLIPPABLE] {name} - Ignoring click (flipped={_isFlipped}, flipping={_isFlipping})");
return;
}
Debug.Log($"[CLICK-TRACE-FLIPPABLE] {name} - Processing click, starting flip");
Logging.Debug($"[CLICK-TRACE-FLIPPABLE] {name} - Processing click, starting flip");
// Flip on click
FlipToReveal();
}
@@ -341,7 +337,7 @@ namespace UI.CardSystem
/// </summary>
private void TriggerUpgradeTransition(AppleHills.Data.CardSystem.CardData lowerRarityCard)
{
Debug.Log($"[FlippableCard] Triggering upgrade transition from {lowerRarityCard.Rarity}!");
Logging.Debug($"[FlippableCard] Triggering upgrade transition from {lowerRarityCard.Rarity}!");
AppleHills.Data.CardSystem.CardRarity oldRarity = lowerRarityCard.Rarity;
AppleHills.Data.CardSystem.CardRarity newRarity = oldRarity + 1;
@@ -429,7 +425,7 @@ namespace UI.CardSystem
if (newCardText != null)
newCardText.SetActive(true);
Debug.Log($"[FlippableCard] Card upgraded from {oldRarity} to {newRarity}! Showing as NEW.");
Logging.Debug($"[FlippableCard] Card upgraded from {oldRarity} to {newRarity}! Showing as NEW.");
// Card is already enlarged from the repeat display, so no need to enlarge again
}
@@ -457,7 +453,7 @@ namespace UI.CardSystem
TransitionToNewCardView(newRarity);
});
Debug.Log($"[FlippableCard] Card upgraded from {oldRarity} to {newRarity}! Showing progress {ownedAtNewRarity}/5");
Logging.Debug($"[FlippableCard] Card upgraded from {oldRarity} to {newRarity}! Showing progress {ownedAtNewRarity}/5");
}
/// <summary>
@@ -465,7 +461,7 @@ namespace UI.CardSystem
/// </summary>
private void TransitionToNewCardView(AppleHills.Data.CardSystem.CardRarity newRarity)
{
Debug.Log($"[FlippableCard] Transitioning to NEW CARD view at {newRarity} rarity");
Logging.Debug($"[FlippableCard] Transitioning to NEW CARD view at {newRarity} rarity");
// Update the CardDisplay to show new rarity
if (cardDisplay != null && _cardData != null)
@@ -488,7 +484,7 @@ namespace UI.CardSystem
_isNew = true;
_isWaitingForTap = true;
Debug.Log($"[FlippableCard] Now showing as NEW CARD at {newRarity}, waiting for tap");
Logging.Debug($"[FlippableCard] Now showing as NEW CARD at {newRarity}, waiting for tap");
}
/// <summary>
@@ -538,7 +534,7 @@ namespace UI.CardSystem
// Check if we have the required number of elements (should match cardsToUpgrade)
if (progressElements.Length < cardsToUpgrade)
{
Debug.LogWarning($"[FlippableCard] Not enough Image components in progress bar! Expected {cardsToUpgrade}, found {progressElements.Length}");
Logging.Warning($"[FlippableCard] Not enough Image components in progress bar! Expected {cardsToUpgrade}, found {progressElements.Length}");
onComplete?.Invoke();
return;
}
@@ -647,7 +643,7 @@ namespace UI.CardSystem
{
if (albumCard == null)
{
Debug.LogWarning("[FlippableCard] Cannot extract AlbumCard - none found!");
Logging.Warning("[FlippableCard] Cannot extract AlbumCard - none found!");
return null;
}
@@ -661,7 +657,7 @@ namespace UI.CardSystem
albumCard.SetupCard(_cardData);
}
Debug.Log($"[FlippableCard] Extracted AlbumCard '{_cardData?.Name}' to {newParent.name} - ready for tween");
Logging.Debug($"[FlippableCard] Extracted AlbumCard '{_cardData?.Name}' to {newParent.name} - ready for tween");
return albumCard;
}

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using Core;
using Data.CardSystem;
using Pixelplacement;
using UnityEngine;
@@ -10,7 +11,8 @@ namespace UI.CardSystem
/// <summary>
/// Singleton UI component for granting booster packs from minigames.
/// 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>
public class MinigameBoosterGiver : MonoBehaviour
{
@@ -25,7 +27,6 @@ namespace UI.CardSystem
[Header("Animation Settings")]
[SerializeField] private float hoverAmount = 20f;
[SerializeField] private float hoverDuration = 1.5f;
[SerializeField] private float glowPulseMin = 0.9f;
[SerializeField] private float glowPulseMax = 1.1f;
[SerializeField] private float glowPulseDuration = 1.2f;
@@ -45,7 +46,7 @@ namespace UI.CardSystem
// Singleton pattern
if (Instance != null && Instance != this)
{
Debug.LogWarning("[MinigameBoosterGiver] Duplicate instance found. Destroying.");
Logging.Warning("[MinigameBoosterGiver] Duplicate instance found. Destroying.");
Destroy(gameObject);
return;
}
@@ -98,7 +99,7 @@ namespace UI.CardSystem
{
if (_currentSequence != null)
{
Debug.LogWarning("[MinigameBoosterGiver] Already running a sequence. Ignoring new request.");
Logging.Warning("[MinigameBoosterGiver] Already running a sequence. Ignoring new request.");
return;
}
@@ -187,23 +188,61 @@ namespace UI.CardSystem
yield break;
}
// Calculate bottom-left corner position in local space
RectTransform canvasRect = GetComponentInParent<Canvas>()?.GetComponent<RectTransform>();
Vector3 targetPosition;
if (canvasRect != null)
// Show scrapbook button temporarily using HUD visibility context
PlayerHudManager.HudVisibilityContext hudContext = null;
GameObject scrapbookButton = null;
scrapbookButton = PlayerHudManager.Instance.GetScrabookButton();
if (scrapbookButton != null)
{
// Convert bottom-left corner with offset to local position
Vector2 bottomLeft = new Vector2(-canvasRect.rect.width / 2f, -canvasRect.rect.height / 2f);
targetPosition = bottomLeft + targetBottomLeftOffset;
hudContext = PlayerHudManager.Instance.ShowElementTemporarily(scrapbookButton);
}
else
{
// Fallback if no canvas found
targetPosition = _boosterInitialPosition + new Vector3(-500f, -500f, 0f);
Logging.Warning("[MinigameBoosterGiver] Scrapbook button not found in PlayerHudManager.");
}
// 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
{
Logging.Warning("[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);
// Scale down
@@ -217,13 +256,16 @@ namespace UI.CardSystem
if (CardSystemManager.Instance != null)
{
CardSystemManager.Instance.AddBoosterPack(1);
Debug.Log("[MinigameBoosterGiver] Booster pack granted!");
Logging.Debug("[MinigameBoosterGiver] Booster pack granted!");
}
else
{
Debug.LogWarning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster pack.");
Logging.Warning("[MinigameBoosterGiver] CardSystemManager not found, cannot grant booster pack.");
}
// Hide scrapbook button by disposing the context
hudContext?.Dispose();
// Hide the visual
if (visualContainer != null)
{
@@ -237,6 +279,22 @@ namespace UI.CardSystem
// Clear sequence reference
_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

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
using UI.DragAndDrop.Core;
using Core;
using UI.DragAndDrop.Core;
using UnityEngine;
namespace UI.CardSystem
@@ -22,7 +23,7 @@ namespace UI.CardSystem
if (container == null || objects == null || objects.Count == 0)
return;
Debug.Log($"[SlotContainerHelper] Shuffling {objects.Count} objects to front slots");
Logging.Debug($"[SlotContainerHelper] Shuffling {objects.Count} objects to front slots");
// Unassign all objects from their current slots
foreach (var obj in objects)
@@ -41,12 +42,12 @@ namespace UI.CardSystem
if (targetSlot != null)
{
Debug.Log($"[SlotContainerHelper] Assigning object to slot with SlotIndex {i}");
Logging.Debug($"[SlotContainerHelper] Assigning object to slot with SlotIndex {i}");
obj.AssignToSlot(targetSlot, animate);
}
else
{
Debug.LogWarning($"[SlotContainerHelper] Could not find slot with SlotIndex {i}");
Logging.Warning($"[SlotContainerHelper] Could not find slot with SlotIndex {i}");
}
}
}

View File

@@ -15,9 +15,6 @@ namespace UI.Core
[Header("Page Settings")]
public string PageName;
// UI pages load after UI infrastructure (UIPageController is priority 50)
public override int ManagedAwakePriority => 200;
// Events using System.Action instead of UnityEvents
public event Action OnTransitionInStarted;
public event Action OnTransitionInCompleted;

View File

@@ -37,25 +37,19 @@ namespace UI.Core
private PlayerInput _playerInput;
private InputAction _cancelAction;
public override int ManagedAwakePriority => 50; // UI infrastructure
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[UIPageController] Initialized");
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Clean up cached instances
foreach (var cachedPage in _prefabInstanceCache.Values)
{

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using Core;
using Pixelplacement;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -79,10 +80,10 @@ namespace UI.DragAndDrop.Core
protected virtual void Initialize()
{
Debug.Log($"[DraggableObject] Initializing {name} at world pos {transform.position}, local pos {transform.localPosition}, parent: {(transform.parent != null ? transform.parent.name : "NULL")}");
Logging.Debug($"[DraggableObject] Initializing {name} at world pos {transform.position}, local pos {transform.localPosition}, parent: {(transform.parent != null ? transform.parent.name : "NULL")}");
_canvas = GetComponentInParent<Canvas>();
Debug.Log($"[DraggableObject] {name} found canvas: {(_canvas != null ? _canvas.name : "NULL")}, canvas pos: {(_canvas != null ? _canvas.transform.position.ToString() : "N/A")}");
Logging.Debug($"[DraggableObject] {name} found canvas: {(_canvas != null ? _canvas.name : "NULL")}, canvas pos: {(_canvas != null ? _canvas.transform.position.ToString() : "N/A")}");
_imageComponent = GetComponent<Image>();
_canvasGroup = GetComponent<CanvasGroup>();
@@ -95,7 +96,7 @@ namespace UI.DragAndDrop.Core
_imageComponent = gameObject.AddComponent<Image>();
_imageComponent.color = new Color(1, 1, 1, 0.01f); // Nearly transparent (0 doesn't work)
_imageComponent.raycastTarget = true;
Debug.Log($"[DraggableObject] Added invisible Image to {name} for raycast detection");
Logging.Debug($"[DraggableObject] Added invisible Image to {name} for raycast detection");
}
// Use assigned visual, or find in children recursively if not assigned
@@ -190,19 +191,19 @@ namespace UI.DragAndDrop.Core
if (eventData.button != PointerEventData.InputButton.Left)
return;
Debug.Log($"[DraggableObject] OnBeginDrag called on {name}. _isDraggingEnabled={_isDraggingEnabled}");
Logging.Debug($"[DraggableObject] OnBeginDrag called on {name}. _isDraggingEnabled={_isDraggingEnabled}");
// Check if dragging is enabled BEFORE setting any state
if (!_isDraggingEnabled)
{
Debug.Log($"[DraggableObject] OnBeginDrag blocked - dragging disabled on {name}");
Logging.Debug($"[DraggableObject] OnBeginDrag blocked - dragging disabled on {name}");
return;
}
_isDragging = true;
_wasDragged = true;
Debug.Log($"[DraggableObject] Drag started on {name}");
Logging.Debug($"[DraggableObject] Drag started on {name}");
// ...existing code...
if (_canvas != null && _canvas.renderMode == RenderMode.ScreenSpaceOverlay && RectTransform != null)
@@ -345,7 +346,7 @@ namespace UI.DragAndDrop.Core
protected virtual void FindAndSnapToSlot()
{
SlotContainer[] containers = FindObjectsOfType<SlotContainer>();
SlotContainer[] containers = FindObjectsByType<SlotContainer>(FindObjectsSortMode.None);
DraggableSlot closestSlot = null;
float closestDistance = float.MaxValue;
@@ -413,7 +414,7 @@ namespace UI.DragAndDrop.Core
if (slot == null)
return;
Debug.Log($"[DraggableObject] Assigning {name} to slot {slot.name}, animate={animate}, current pos={transform.position}, slot pos={slot.transform.position}");
Logging.Debug($"[DraggableObject] Assigning {name} to slot {slot.name}, animate={animate}, current pos={transform.position}, slot pos={slot.transform.position}");
DraggableSlot previousSlot = _currentSlot;
_currentSlot = slot;
@@ -429,7 +430,7 @@ namespace UI.DragAndDrop.Core
transform.SetParent(slot.transform);
transform.localPosition = _isSelected ? new Vector3(0, selectionOffset, 0) : Vector3.zero;
transform.localRotation = Quaternion.identity;
Debug.Log($"[DraggableObject] {name} assigned to slot {slot.name}, new world pos={transform.position}, local pos={transform.localPosition}");
Logging.Debug($"[DraggableObject] {name} assigned to slot {slot.name}, new world pos={transform.position}, local pos={transform.localPosition}");
}
OnSlotChanged?.Invoke(this, slot);
@@ -510,14 +511,14 @@ namespace UI.DragAndDrop.Core
/// </summary>
public virtual void SetDraggingEnabled(bool enabled)
{
Debug.Log($"[DraggableObject] SetDraggingEnabled({enabled}) called on {name}. _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}");
Logging.Debug($"[DraggableObject] SetDraggingEnabled({enabled}) called on {name}. _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}");
_isDraggingEnabled = enabled;
// If disabling dragging while actively dragging, stop the drag
if (!enabled && _isDragging)
{
Debug.Log($"[DraggableObject] Stopping active drag on {name}");
Logging.Debug($"[DraggableObject] Stopping active drag on {name}");
_isDragging = false;
// Re-enable raycasting
@@ -529,7 +530,7 @@ namespace UI.DragAndDrop.Core
_canvasGroup.blocksRaycasts = true;
}
Debug.Log($"[DraggableObject] After SetDraggingEnabled: _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}");
Logging.Debug($"[DraggableObject] After SetDraggingEnabled: _isDragging={_isDragging}, _isDraggingEnabled={_isDraggingEnabled}");
}
#endregion

View File

@@ -1,4 +1,5 @@
using Pixelplacement;
using Core;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
using UnityEngine.InputSystem; // Added for new Input System
@@ -67,14 +68,14 @@ namespace UI.DragAndDrop.Core
_parentDraggable = parent;
Canvas parentCanvas = parent.GetComponentInParent<Canvas>();
Debug.Log($"[DraggableVisual] Initializing visual for {parent.name} at world pos {parent.transform.position}, parent canvas: {(parentCanvas != null ? parentCanvas.name + " (renderMode: " + parentCanvas.renderMode + ")" : "NULL")}");
Logging.Debug($"[DraggableVisual] Initializing visual for {parent.name} at world pos {parent.transform.position}, parent canvas: {(parentCanvas != null ? parentCanvas.name + " (renderMode: " + parentCanvas.renderMode + ")" : "NULL")}");
// CRITICAL: Reparent visual to canvas (not base) so it can move independently
// This enables the delayed follow effect
if (parentCanvas != null)
{
transform.SetParent(parentCanvas.transform, true); // worldPositionStays = true
Debug.Log($"[DraggableVisual] Reparented visual {name} to canvas {parentCanvas.name} for independent movement");
Logging.Debug($"[DraggableVisual] Reparented visual {name} to canvas {parentCanvas.name} for independent movement");
}
// Get components if assigned (don't auto-create Canvas to avoid Unity's auto-reparenting)
@@ -106,7 +107,7 @@ namespace UI.DragAndDrop.Core
tiltParent.localRotation = Quaternion.identity;
}
Debug.Log($"[DraggableVisual] Visual {name} initialized at world pos {transform.position}, local pos {transform.localPosition}, local rotation {transform.localRotation.eulerAngles}, parent at world pos {parent.transform.position}, local pos {parent.transform.localPosition}, rotation {parent.transform.rotation.eulerAngles}");
Logging.Debug($"[DraggableVisual] Visual {name} initialized at world pos {transform.position}, local pos {transform.localPosition}, local rotation {transform.localRotation.eulerAngles}, parent at world pos {parent.transform.position}, local pos {parent.transform.localPosition}, rotation {parent.transform.rotation.eulerAngles}");
_isInitialized = true;
@@ -202,7 +203,7 @@ namespace UI.DragAndDrop.Core
float distance = Vector3.Distance(currentPosition, targetPosition);
if (distance > 500f)
{
Debug.LogWarning($"[DraggableVisual] Large position delta detected! Visual {name} at {currentPosition}, target {targetPosition}, parent {_parentDraggable.name}, distance: {distance}");
Logging.Warning($"[DraggableVisual] Large position delta detected! Visual {name} at {currentPosition}, target {targetPosition}, parent {_parentDraggable.name}, distance: {distance}");
}
// Apply follow logic with snappy easing

View File

@@ -1,4 +1,5 @@
using UI.Core;
using Core;
using UI.Core;
using UnityEngine;
using UnityEngine.UI;
@@ -53,7 +54,7 @@ namespace UI
return;
}
Debug.Log($"[HudMenuButton] {buttonName} opening page from prefab: {pagePrefab.name}");
Logging.Debug($"[HudMenuButton] {buttonName} opening page from prefab: {pagePrefab.name}");
UIPageController.Instance.PushPageFromPrefab(pagePrefab);
}

View File

@@ -52,15 +52,10 @@ namespace UI
/// Singleton instance of the LoadingScreenController. No longer creates an instance if one doesn't exist.
/// </summary>
public static LoadingScreenController Instance => _instance;
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 45; // UI infrastructure, before UIPageController
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Set up container reference early
@@ -74,7 +69,7 @@ namespace UI
}
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
Logging.Debug("[LoadingScreenController] Initialized");
}

View File

@@ -27,15 +27,10 @@ namespace UI
[SerializeField] private UnityEngine.UI.Button devOptionsButton;
[SerializeField] private GameObject mainOptionsContainer;
[SerializeField] private GameObject devOptionsContainer;
// After UIPageController (50)
public override int ManagedAwakePriority => 55;
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake(); // CRITICAL: Register with LifecycleManager!
// Set instance immediately so it's available before OnManagedAwake() is called
// Set instance immediately (early initialization)
_instance = this;
// Ensure we have a CanvasGroup for transitions
@@ -51,9 +46,9 @@ namespace UI
gameObject.SetActive(false);
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Subscribe to scene-dependent events - must be in OnManagedAwake, not OnSceneReady
// Subscribe to scene-dependent events - must be in OnManagedStart, not OnSceneReady
// because PauseMenu is in DontDestroyOnLoad and OnSceneReady only fires once
if (SceneManagerService.Instance != null)
{
@@ -73,15 +68,13 @@ namespace UI
Logging.Debug("[PauseMenu] Subscribed to SceneManagerService events");
}
protected override void OnSceneReady()
internal override void OnSceneReady()
{
// This only fires once for DontDestroyOnLoad objects, so we handle scene loads in OnManagedAwake
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Unsubscribe when destroyed
if (SceneManagerService.Instance != null)
{
@@ -421,6 +414,7 @@ namespace UI
{
SaveLoadManager.Instance.currentSaveData.participantStates.Clear();
SaveLoadManager.Instance.currentSaveData.unlockedMinigames.Clear();
SaveLoadManager.Instance.currentSaveData.playedDivingTutorial = false;
Logging.Debug("[PauseMenu] Cleared all save data from memory");
}

View File

@@ -7,6 +7,7 @@ using UnityEngine.Playables;
using UnityEngine.UI;
using System.Linq;
using System.Collections.Generic;
using System;
namespace UI
{
@@ -19,6 +20,73 @@ namespace UI
{
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;
[Header("HUD Management")]
@@ -33,6 +101,7 @@ namespace UI
[Header("HUD Elements")]
public GameObject eagleEye;
public GameObject ramaSjangButton;
public GameObject scrabBookButton;
[HideInInspector] public Image cinematicSprites;
[HideInInspector] public Image cinematicBackgroundSprites;
@@ -46,14 +115,14 @@ namespace UI
private UIPageController _uiPageController;
private AppSwitcher _appSwitcherComponent;
private new void Awake()
internal override void OnManagedAwake()
{
base.Awake();
if (Instance != null)
{
Destroy(this);
return;
}
// Set instance immediately (early initialization)
_instance = this;
// Get UIPageController on same GameObject
@@ -66,7 +135,7 @@ namespace UI
InitializeReferences();
}
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Subscribe to UIPageController page changes for auto HUD management
if (_uiPageController != null)
@@ -103,10 +172,8 @@ namespace UI
}
}
protected override void OnDestroy()
internal override void OnManagedDestroy()
{
base.OnDestroy();
// Unsubscribe from events
if (_uiPageController != null)
{
@@ -254,6 +321,9 @@ namespace UI
if (visible)
{
eagleEye.SetActive(false);
ramaSjangButton.SetActive(false);
scrabBookButton.SetActive(false);
}
break;
case UIMode.HideAll:
@@ -318,6 +388,64 @@ namespace UI
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
}
}

View File

@@ -15,6 +15,6 @@ public class ScrapbookController : MonoBehaviour
public void DebugClick()
{
// Debug.Log("Yey I was clicked!");
// Logging.Debug("Yey I was clicked!");
}
}

View File

@@ -6,6 +6,7 @@ using Input;
using Pixelplacement;
using UI.Core;
using UnityEngine;
using UnityEngine.Audio;
namespace UI.Tutorial
{
@@ -19,6 +20,8 @@ namespace UI.Tutorial
private StateMachine _stateMachine;
public bool playTutorial;
public AudioSource bottleAudioPlayer;
public AudioResource introVO;
[SerializeField] private ProgressType progressType = ProgressType.Auto;
// gating for input until current state's animation finishes first loop
@@ -27,9 +30,8 @@ namespace UI.Tutorial
private bool _canAcceptInput;
private Coroutine _waitLoopCoroutine;
public override int ManagedAwakePriority => 200; // Tutorial runs late, after other systems
protected override void OnManagedAwake()
internal override void OnManagedStart()
{
// Ensure prompt is hidden initially (even before tutorial initialization)
if (tapPrompt != null)
@@ -59,7 +61,7 @@ namespace UI.Tutorial
void RemoveTutorial()
{
Debug.Log("Remove me!");
Logging.Debug("Remove me!");
if (_waitLoopCoroutine != null)
{
StopCoroutine(_waitLoopCoroutine);
@@ -76,6 +78,8 @@ namespace UI.Tutorial
tapPrompt.SetActive(false);
Destroy(gameObject);
bottleAudioPlayer.resource = introVO;
bottleAudioPlayer.Play();
}
public void OnTap(Vector2 position)

View File

@@ -101,7 +101,7 @@ namespace Utils
}
catch (System.Exception ex)
{
Debug.LogWarning($"[AppleHillsUtils] Error checking addressable key existence: {ex.Message}");
Logging.Warning($"[AppleHillsUtils] Error checking addressable key existence: {ex.Message}");
return false;
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 53ec386bba82c1748886a5beb8468ecf
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,48 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0}
m_Name: iOS
m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.Build.Profile.BuildProfile
m_AssetVersion: 1
m_BuildTarget: 9
m_Subtarget: 0
m_PlatformId: ad48d16a66894befa4d8181998c3cb09
m_PlatformBuildProfile:
rid: 3475452038477774988
m_OverrideGlobalSceneList: 0
m_Scenes: []
m_ScriptingDefines: []
m_PlayerSettingsYaml:
m_Settings: []
references:
version: 2
RefIds:
- rid: 3475452038477774988
type: {class: iOSPlatformSettings, ns: UnityEditor.iOS, asm: UnityEditor.iOS.Extensions}
data:
m_Development: 0
m_ConnectProfiler: 0
m_BuildWithDeepProfilingSupport: 0
m_AllowDebugging: 0
m_WaitForManagedDebugger: 0
m_ManagedDebuggerFixedPort: 0
m_ExplicitNullChecks: 0
m_ExplicitDivideByZeroChecks: 0
m_ExplicitArrayBoundsChecks: 0
m_CompressionType: -1
m_InstallInBuildFolder: 0
m_InsightsSettingsContainer:
m_BuildProfileEngineDiagnosticsState: 2
m_iOSXcodeBuildConfig: 1
m_SymlinkSources: 0
m_PreferredXcode:
m_SymlinkTrampoline: 0

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 110a4eabb37dbaa428e55c751696cd1e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

120
docs/custom_log_console.md Normal file
View File

@@ -0,0 +1,120 @@
# Custom Log Console
## Overview
A centralized logging system with an advanced filtering console that automatically tags log entries with class and method names. Provides powerful filtering capabilities beyond Unity's default console, including multi-select filters, text search, time-range filtering, and log export.
## Using the Logging System in Code
All logging automatically captures the calling class and method name using `CallerMemberName` and `CallerFilePath` attributes. Simply call the static logging methods:
```csharp
using Core;
public class MyClass : ManagedBehaviour
{
internal override void OnManagedStart()
{
Logging.Debug("Initialization complete");
Logging.Info("Player spawned at position");
Logging.Warning("Missing configuration, using defaults");
Logging.Error("Failed to load required asset");
}
}
```
**Output format:**
```
[ClassName][MethodName] Your message
```
**Available methods:**
- `Logging.Debug(string message)` - Detailed diagnostic information
- `Logging.Info(string message)` - General informational messages
- `Logging.Warning(string message)` - Non-critical issues
- `Logging.Error(string message)` - Critical errors
**Note:** All logs are broadcast via the `Logging.OnLogEntryAdded` event and stored in a central buffer accessible via `Logging.GetRecentLogs()`.
---
## Opening the Console Window
**Menu:** `AppleHills > Custom Log Console`
You can open multiple independent console instances with different filter configurations to monitor separate systems simultaneously
---
## Console Interface
![Custom Log Console Interface](media/custom_console.png)
### Toolbar Controls
#### 🔵 Basic Controls (Blue outline)
- **Clear** - Clears all log entries and resets tag lists
- **Auto-scroll** - Automatically scrolls to newest entries when enabled
#### Filter Buttons (Persistent Popups)
All filter buttons open persistent popup windows that remain open during multi-selection. Changes apply when you click "Apply" or dismiss with "Close".
- **🔴 Classes Filter (Red outline)**
- Multi-select which classes to display
- Includes search box for quick filtering
- All/None quick actions
- **🟢 Methods Filter (Green outline)**
- Multi-select which methods to display
- Includes search box for quick filtering
- All/None quick actions
- **🟡 Levels Filter (Yellow outline)**
- Toggle Debug, Info, Warning, Error levels
- All/None quick actions
- **⏱ Time Filter**
- Opens utility window with MinMaxSlider
- Filter logs by timestamp range
- Enable/disable toggle with reset option
#### Search & Export
- **Search** - Full-text search across class names, method names, and message content
- **Export** - Save filtered logs to .txt file with timestamp
- **Count** - Shows `filtered/total` log count
---
## Visual Indicators
**Color Coding:**
- White: Debug/Info (normal operation)
- Yellow: Warning (non-critical issues)
- Red: Error (critical failures)
**Alternating Rows:** Light/dark grey backgrounds improve readability for dense log output.
---
## Technical Details
**Event Broadcasting:**
```csharp
Logging.OnLogEntryAdded += (LogEntry entry) => { /* handle */ };
```
**Manual Log Retrieval:**
```csharp
List<LogEntry> recentLogs = Logging.GetRecentLogs();
```
**LogEntry Structure:**
- `ClassName` - Captured from calling file path
- `MethodName` - Captured from `CallerMemberName`
- `Message` - User-provided message text
- `Level` - Debug/Info/Warning/Error enum
- `Timestamp` - Time.realtimeSinceStartup
- `FullFormattedMessage` - Complete formatted string
---

View File

@@ -0,0 +1,93 @@
# ManagedBehaviour System - Architecture Overview
**Version:** 2.0 <br>
**Updated:** 11.11.2025
---
## What is the ManagedBehaviour System?
Lifecycle orchestration framework that provides guaranteed execution order and deterministic lifecycle management for Unity components.
### Problems Solved
We've had quite a few things shoe-stringed together in various ways and dependant on references to each other.
Due to undefined initialization order - null references during access to yet uninitialized resources was becoming a problem.
### What You Get
- **Guaranteed Initialization Order** - Managers ready before components that depend on them
- **Deterministic Lifecycle Hooks** - Predictable callbacks at key moments
- **Automatic Registration** - No boilerplate for wiring up systems
- **Scene Lifecycle Events** - Built-in hooks for scene load/unload
- **Save/Load Coordination** - Centralized collection of save data
- **Bootstrap Integration** - Components know when bootstrap completes
---
## Architecture Principles
### 1. Centralized Orchestration
Single `LifecycleManager` singleton coordinates all lifecycle events. Components auto-register and receive callbacks at appropriate times.
### 2. Sealed Framework Methods
`Awake()` and `OnDestroy()` are sealed. Use `OnManagedAwake()` and `OnManagedDestroy()` instead. Prevents forgetting to call `base.Awake()`.
### 3. Two-Phase Initialization
- **Early (`OnManagedAwake()`)**: During Unity's Awake, before bootstrap. Use for singleton setup.
- **Late (`OnManagedStart()`)**: After bootstrap completes. All managers guaranteed ready.
### 4. Registration Order Execution
Execution follows Unity's natural Awake order. No priority numbers to manage.
### 5. Automatic Cleanup
Framework handles unregistration automatically. Override `OnManagedDestroy()` only if you need custom cleanup.
---
## Lifecycle Flow Diagrams
### Boot Sequence
![Boot Sequence](../media/boot_sequence.png)
### Scene Transition Flow
![Scene Transition Flow](../media/Scene_transition_flow.png)
### Component Lifecycle (Individual Component)
![Component Lifecycle](../media/component_lifecycle.png)
---
## Class Diagram
![Class Diagram](../media/class_diagram.png)
---
## Key Guarantees
### Guaranteed
1. **Bootstrap Completion** - `OnManagedStart()` always fires after bootstrap completes
2. **Manager Availability** - All manager singletons exist when `OnManagedStart()` is called
3. **Scene Lifecycle** - `OnSceneReady()` fires after scene load, `OnSceneUnloading()` before unload
4. **Automatic Registration** - Can't forget to register (Awake is sealed)
5. **Automatic Cleanup** - Can't forget to unregister (OnDestroy is sealed)
6. **Save/Load Coordination** - All save participants called in one pass
### Not Guaranteed
1. **Initialization Order Between Components** - `OnManagedAwake()` follows Unity's unpredictable Awake order
2. **Thread Safety** - All methods must run on main thread
3. **Performance** - Broadcasting to 1000+ components may have overhead
4. **SaveId Uniqueness** - Developer responsible for unique SaveIds

View File

@@ -0,0 +1,377 @@
# ManagedBehaviour System - Technical Reference
**Version:** 2.0 <br>
**Updated:** 11.11.2025
---
## Overview
The ManagedBehaviour system provides a deterministic, ordered lifecycle management framework for Unity MonoBehaviours. This document provides complete technical documentation of all classes, methods, and APIs.
---
## Core Classes
### ManagedBehaviour (Abstract Base Class)
**Namespace:** `Core.Lifecycle`
**Inherits:** `MonoBehaviour`
**Location:** `Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/ManagedBehaviour.cs)
Abstract base class that all managed components must inherit from. Provides automatic registration with LifecycleManager and ordered lifecycle callbacks.
#### Lifecycle Hook Methods
Override these `internal virtual` methods to customize component behavior at different lifecycle stages. Called automatically by `LifecycleManager`.
<details>
<summary>Click to see more details</summary>
##### `OnManagedAwake()`
```csharp
internal virtual void OnManagedAwake()
```
**Called:** During registration (within Unity's Awake phase)
**Execution Order:** Natural Unity script execution order (not guaranteed between components)
**Use For:**
- Setting singleton instances (`_instance = this`)
- Early GetComponent calls
- One-time initialization that doesn't depend on other systems
**Timing Guarantees:**
- ✅ GameObject and component exist
- ✅ Scene is loaded
- ❌ Other components may not be initialized yet
- ❌ Bootstrap may not be complete
##### `OnManagedStart()`
```csharp
internal virtual void OnManagedStart()
```
**Called:** After bootstrap completes (for boot components) or immediately after registration (for late-registered components)
**Execution Order:** Registration order
**Use For:**
- Initialization that depends on other managers
- Accessing singleton instances safely
- Setting up cross-system dependencies
**Timing Guarantees:**
- ✅ All managers are initialized
- ✅ Bootstrap resources are available
- ✅ Safe to access `GameManager.Instance`, `AudioManager.Instance`, etc.
##### `OnSceneUnloading()`
```csharp
internal virtual void OnSceneUnloading()
```
**Called:** Before scene unload
**Execution Order:** Registration order
**Use For:**
- Scene-specific cleanup
- Saving temporary scene state
**Timing Guarantees:**
- ✅ Scene is still loaded
- ✅ Other components in scene still exist
##### `OnSceneReady()`
```csharp
internal virtual void OnSceneReady()
```
**Called:** After scene load completes
**Execution Order:** Registration order
**Use For:**
- Scene-specific initialization
- Finding scene objects
- Setting up scene-specific state
**Timing Guarantees:**
- ✅ All scene GameObjects are loaded
- ✅ Batched components have received `OnManagedStart()`
##### `OnSceneSaveRequested()`
```csharp
internal virtual string OnSceneSaveRequested()
```
**Called:** Before scene unload during scene transitions
**Returns:** Serialized scene-specific data (JSON string), or `null` if nothing to save
**Use For:**
- Level progress
- Object positions
- Puzzle states
- Temporary scene data
**⚠️ Important:** Must return synchronously.
##### `OnSceneRestoreRequested(string serializedData)`
```csharp
internal virtual void OnSceneRestoreRequested(string serializedData)
```
**Called:** After scene load, during `OnSceneReady` phase
**Parameters:**
- `serializedData` - Previously saved data from `OnSceneSaveRequested()`
**Use For:**
- Restoring level progress
- Setting object positions
- Restoring puzzle states
**⚠️ Important:** Must execute synchronously. Do not use coroutines or async/await.
##### `OnSceneRestoreCompleted()`
```csharp
internal virtual void OnSceneRestoreCompleted()
```
**Called:** After all `OnSceneRestoreRequested()` calls complete
**Timing:** Always called after scene load, whether save data exists or not
**Use For:**
- Post-restore initialization
- First-time initialization (when no save data exists)
- Triggering events after state is restored
**Common Pattern:**
```csharp
internal override void OnSceneRestoreCompleted()
{
if (!_hasPlayed) // Check if this is first time
{
PlayIntroAudio();
_hasPlayed = true;
}
}
```
##### `OnGlobalSaveRequested()`
```csharp
internal virtual string OnGlobalSaveRequested()
```
**Called:** Once before save file is written to disk
**Returns:** Serialized global persistent data (JSON string), or `null`
**Use For:**
- Player inventory
- Unlocked features
- Card collections
- Persistent progression
**Timing:** Called once per game save (not per scene transition)
##### `OnGlobalRestoreRequested(string serializedData)`
```csharp
internal virtual void OnGlobalRestoreRequested(string serializedData)
```
**Called:** Once on game boot after save file is read
**Parameters:**
- `serializedData` - Previously saved data from `OnGlobalSaveRequested()`
**Use For:**
- Restoring player inventory
- Restoring unlocked features
- Restoring persistent progression
**Timing:** Called once on boot, not during scene transitions
##### `OnGlobalLoadCompleted()`
```csharp
internal virtual void OnGlobalLoadCompleted()
```
**Called:** Once on game boot after all global restore operations complete
**Use For:**
- Triggering UI updates after load
- Broadcasting load events
- Post-load initialization
##### `OnGlobalSaveStarted()`
```csharp
internal virtual void OnGlobalSaveStarted()
```
**Called:** Once before save file is written
**Use For:**
- Final validation before save
- Cleanup operations before save
##### `OnManagedDestroy()`
```csharp
internal virtual void OnManagedDestroy()
```
**Called:** During `OnDestroy`, before unregistration
**Execution Order:** Registration order
**Use For:**
- Unsubscribing from events
- Releasing resources
- Custom cleanup logic
**Note:** Most cleanup is automatic (auto-registrations are handled by framework).
</details>
#### Configuration Properties
Virtual properties that control automatic behaviors like pause registration and save system participation.
<details>
<summary>Click to see more details</summary>
##### `AutoRegisterPausable`
```csharp
public virtual bool AutoRegisterPausable => false;
```
**Type:** `bool`
**Default:** `false`
**Description:** If true and component implements `IPausable`, automatically registers with `GameManager.Instance` during initialization. Automatic unregistration occurs on destruction.
##### `AutoRegisterForSave`
```csharp
public virtual bool AutoRegisterForSave => false;
```
**Type:** `bool`
**Default:** `false`
**Description:** If true, component participates in the save/load system. Should override `OnSceneSaveRequested()` and `OnSceneRestoreRequested()` or global equivalents.
##### `SaveId`
```csharp
public virtual string SaveId { get; }
```
**Type:** `string`
**Default:** `"{SceneName}/{GameObjectName}/{ComponentType}"`
**Description:** Unique identifier for this component in the save system. Cached on first access for performance. Override for singletons (e.g., `"PlayerController"`) or custom IDs.
**⚠️ Warning:** GameObject name changes at runtime will NOT update the cached SaveId.
</details>
### Private Lifecycle Methods
<details>
<summary>Click to see more details</summary>
##### `Awake()`
```csharp
private void Awake()
```
**Visibility:** `private` (sealed, cannot be overridden)
**Called By:** Unity
**Description:** Automatically registers component with `LifecycleManager`. Calls `OnManagedAwake()` during registration.
**⚠️ Important:** This method is sealed. Use `OnManagedAwake()` for early initialization.
##### `OnDestroy()`
```csharp
private void OnDestroy()
```
**Visibility:** `private` (sealed, cannot be overridden)
**Called By:** Unity
**Description:** Calls `OnManagedDestroy()`, unregisters from `LifecycleManager`, and handles auto-unregistrations.
**⚠️ Important:** This method is sealed. Use `OnManagedDestroy()` for custom cleanup.
</details>
---
## LifecycleManager (Singleton Orchestrator)
**Namespace:** `Core.Lifecycle`
**Inherits:** `MonoBehaviour`
**Location:** `Assets/Scripts/Core/Lifecycle/LifecycleManager.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/LifecycleManager.cs)
Central orchestrator that manages all `ManagedBehaviour` components and broadcasts lifecycle events.
### Static Properties
##### `Instance`
Singleton instance. Created automatically by `CustomBoot` before bootstrap begins.
### Public Methods
Core methods for registration, lifecycle broadcasting, and save/restore operations. Most are called automatically by the framework.
<details>
<summary>Click to see more details</summary>
##### `Register(ManagedBehaviour component)`
```csharp
public void Register(ManagedBehaviour component)
```
Registers a component with the lifecycle system. **Called automatically from `ManagedBehaviour.Awake()`.**
##### `Unregister(ManagedBehaviour component)`
```csharp
public void Unregister(ManagedBehaviour component)
```
Unregisters a component. **Called automatically from `ManagedBehaviour.OnDestroy()`.**
##### `OnBootCompletionTriggered()`
```csharp
public void OnBootCompletionTriggered()
```
Called by `CustomBoot` after bootstrap completes. Broadcasts `OnManagedStart()` to all registered components.
##### `BeginSceneLoad(string sceneName)`
```csharp
public void BeginSceneLoad(string sceneName)
```
Activates scene loading batching mode. Called by `SceneManagerService` when loading a scene.
##### `BroadcastSceneReady(string sceneName)`
```csharp
public void BroadcastSceneReady(string sceneName)
```
Processes batched components and broadcasts `OnSceneReady()` to all components in the scene. Called by `SceneManagerService` after scene load.
##### `BroadcastSceneUnloading(string sceneName)`
```csharp
public void BroadcastSceneUnloading(string sceneName)
```
Broadcasts `OnSceneUnloading()` to all components in the specified scene. Called by `SceneManagerService` before scene unload.
##### `BroadcastSceneSaveRequested()`
```csharp
public Dictionary<string, string> BroadcastSceneSaveRequested()
```
Broadcasts `OnSceneSaveRequested()` to all components with `AutoRegisterForSave == true`. Returns dictionary of SaveId → serialized data.
##### `BroadcastSceneRestoreRequested(Dictionary<string, string> saveData)`
```csharp
public void BroadcastSceneRestoreRequested(Dictionary<string, string> saveData)
```
Distributes save data to components by matching `SaveId`, then broadcasts `OnSceneRestoreCompleted()`.
##### `BroadcastGlobalSaveRequested()`
```csharp
public Dictionary<string, string> BroadcastGlobalSaveRequested()
```
Broadcasts `OnGlobalSaveRequested()` to all components with `AutoRegisterForSave == true`. Returns dictionary of SaveId → serialized data.
##### `BroadcastGlobalRestoreRequested(Dictionary<string, string> saveData)`
```csharp
public void BroadcastGlobalRestoreRequested(Dictionary<string, string> saveData)
```
Distributes save data to components by matching `SaveId`, then broadcasts `OnGlobalLoadCompleted()`.
##### `BroadcastGlobalSaveStarted()`
```csharp
public void BroadcastGlobalSaveStarted()
```
Broadcasts `OnGlobalSaveStarted()` to all components with `AutoRegisterForSave == true`.
## LifecyclePhase (Enum)
**Namespace:** `Core.Lifecycle`
**Location:** `Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs` → [View Source](../../Assets/Scripts/Core/Lifecycle/LifecycleEnums.cs)
Defines the different lifecycle phases for documentation and tooling purposes.
```csharp
public enum LifecyclePhase
{
ManagedAwake, // During registration (Awake)
ManagedStart, // After bootstrap or late registration
SceneUnloading, // Before scene unload
SceneReady, // After scene load
SaveRequested, // Before scene unload (save)
RestoreRequested, // After scene load (restore)
ManagedDestroy // During OnDestroy
}
```
---

View File

@@ -0,0 +1,522 @@
# ManagedBehaviour System - Quick Start & Use Cases
**TL;DR:** Inherit from `ManagedBehaviour` instead of `MonoBehaviour`. Override lifecycle hooks instead of Awake/OnDestroy. Get guaranteed initialization order and automatic registration.
---
## Table of Contents
1. [Lifecycle Methods Summary](#lifecycle-methods-summary)
2. [Quick Reference - Common Use Cases](#quick-reference---common-use-cases)
3. [Getting Started Examples](#getting-started-examples)
4. [Detailed Use Cases](#detailed-use-cases)
5. [Common Patterns](#common-patterns)
6. [Migration Checklist](#migration-checklist)
7. [Troubleshooting](#troubleshooting)
8. [Best Practices](#best-practices)
9. [FAQ](#faq)
---
## Summary
**ManagedBehaviour in 3 Sentences:**
Inherit from `ManagedBehaviour` to get automatic lifecycle management with guaranteed initialization order. Use `OnManagedStart()` to safely access manager singletons, and use built-in save/load hooks for persistence. The framework handles registration and cleanup automatically - you just override the hooks you need.
**When to Use:**
- ✅ Singleton managers
- ✅ Components that access managers
- ✅ Components that need save/load
- ✅ Components that need scene lifecycle events
**When to Skip:**
- ❌ Simple self-contained components
- ❌ Third-party code (can't change base class)
- ❌ Performance-critical code with no dependencies
**But can I still use regular MonoBehaviour?!** <br>
_Yes!_ Only use ManagedBehaviour when you need its features (manager access, save/load, etc.)
---
## Lifecycle Methods Summary
1. **OnManagedAwake()** - Called during Unity's Awake phase; use for **internal setup** and GetComponent calls.
2. **OnManagedStart()** - Called after bootstrap completes; **safe to access** manager singletons.
3. **OnSceneReady()** - Called after scene finishes loading; use for scene-specific initialization.
4. **OnSceneUnloading()** - Called before scene unloads; use for scene cleanup.
5. **OnSceneSaveRequested()** - Returns serialized scene-specific data before scene transitions.
6. **OnSceneRestoreRequested(data)** - Receives saved scene data after scene loads.
7. **OnSceneRestoreCompleted()** - Called after all scene restore operations complete.
8. **OnGlobalSaveRequested()** - Returns serialized persistent data when game saves.
9. **OnGlobalRestoreRequested(data)** - Receives persistent data on game boot.
10. **OnGlobalLoadCompleted()** - Called after all global restore operations complete on boot.
11. **OnGlobalSaveStarted()** - Called before save file is written; use for pre-save validation.
12. **OnManagedDestroy()** - Called during OnDestroy; use for cleanup and event unsubscription.
---
## Quick Reference - Common Use Cases
### Access Manager Singleton Safely
```csharp
internal override void OnManagedStart()
{
GameManager.Instance.DoSomething(); // Safe - managers guaranteed ready
}
```
### Create Singleton Manager
```csharp
private static MyManager _instance;
public static MyManager Instance => _instance;
internal override void OnManagedAwake()
{
_instance = this;
}
```
### Save Scene Progress
```csharp
public override bool AutoRegisterForSave => true;
internal override string OnSceneSaveRequested()
{
return JsonUtility.ToJson(myData);
}
internal override void OnSceneRestoreRequested(string data)
{
myData = JsonUtility.FromJson<MyData>(data);
}
```
### Auto-Register as Pausable
```csharp
public class MyComponent : ManagedBehaviour, IPausable
{
public override bool AutoRegisterPausable => true;
public void Pause() { /* pause logic */ }
public void Resume() { /* resume logic */ }
}
```
### Scene-Specific Initialization
```csharp
internal override void OnSceneReady()
{
FindObjectsInScene();
InitializeLevel();
}
```
### Cleanup on Destroy
```csharp
internal override void OnManagedDestroy()
{
EventBus.OnSomething -= HandleEvent;
}
```
---
## Getting Started Examples
### 1. Basic Component
```csharp
using Core.Lifecycle;
public class MyComponent : ManagedBehaviour
{
internal override void OnManagedAwake()
{
// Early initialization (singleton setup, GetComponent)
Debug.Log("MyComponent awakened");
}
internal override void OnManagedStart()
{
// Late initialization (safe to access managers)
Debug.Log("MyComponent started - managers are ready");
}
}
```
### 2. Singleton Manager
```csharp
using Core.Lifecycle;
public class MyManager : ManagedBehaviour
{
private static MyManager _instance;
public static MyManager Instance => _instance;
internal override void OnManagedAwake()
{
_instance = this; // Set singleton early
}
internal override void OnManagedStart()
{
// All other managers are ready here
AudioManager.Instance.PlaySound("ManagerReady");
}
}
```
### 3. Component with Save/Load
```csharp
using Core.Lifecycle;
public class PuzzleComponent : ManagedBehaviour
{
public override bool AutoRegisterForSave => true;
public override string SaveId => "MyPuzzle"; // Custom ID
private bool _isSolved;
internal override string OnSceneSaveRequested()
{
return JsonUtility.ToJson(new { isSolved = _isSolved });
}
internal override void OnSceneRestoreRequested(string data)
{
var saveData = JsonUtility.FromJson<SaveData>(data);
_isSolved = saveData.isSolved;
}
[System.Serializable]
private class SaveData
{
public bool isSolved;
}
}
```
### 4. Component with Cleanup
```csharp
using Core.Lifecycle;
public class EventSubscriber : ManagedBehaviour
{
internal override void OnManagedStart()
{
GameManager.Instance.OnGamePaused += HandlePause;
}
internal override void OnManagedDestroy()
{
// Automatic cleanup
if (GameManager.Instance != null)
GameManager.Instance.OnGamePaused -= HandlePause;
}
private void HandlePause() { }
}
```
---
## Detailed Use Cases
### Use Case 1: Accessing Singleton Managers Safely
**Problem:** Accessing `GameManager.Instance` in `Awake()` might fail if GameManager hasn't initialized yet.
**Solution:**
```csharp
public class Player : ManagedBehaviour
{
// ❌ DON'T: Risky in OnManagedAwake (managers may not be ready)
internal override void OnManagedAwake()
{
// var settings = GameManager.Instance.GetSettings(); // RISKY!
}
// ✅ DO: Safe in OnManagedStart (managers guaranteed ready)
internal override void OnManagedStart()
{
var settings = GameManager.Instance.GetSettings(); // SAFE!
ApplySettings(settings);
}
}
```
---
### Use Case 2: Scene-Specific Initialization
**Problem:** Need to initialize something after a scene finishes loading.
**Solution:**
```csharp
public class LevelManager : ManagedBehaviour
{
internal override void OnSceneReady()
{
// Scene is fully loaded, all objects exist
FindObjectiveMarkers();
SpawnEnemies();
PlayLevelMusic();
}
}
```
---
### Use Case 3: Saving Player Progress
**Problem:** Need to save level progress when transitioning between scenes.
**Solution:**
```csharp
public class LevelProgress : ManagedBehaviour
{
public override bool AutoRegisterForSave => true;
public override string SaveId => "LevelProgress";
private int _checkpointIndex;
private float _timeElapsed;
internal override string OnSceneSaveRequested()
{
return JsonUtility.ToJson(new
{
checkpoint = _checkpointIndex,
time = _timeElapsed
});
}
internal override void OnSceneRestoreRequested(string data)
{
var save = JsonUtility.FromJson<SaveData>(data);
_checkpointIndex = save.checkpoint;
_timeElapsed = save.time;
}
internal override void OnSceneRestoreCompleted()
{
// Restore complete, trigger UI update
UpdateProgressUI();
}
}
```
---
### Use Case 4: Global Persistent Data (Inventory)
**Problem:** Need to save player inventory across all scenes.
**Solution:**
```csharp
public class InventoryManager : ManagedBehaviour
{
public override bool AutoRegisterForSave => true;
public override string SaveId => "Inventory";
private List<string> _items = new List<string>();
internal override string OnGlobalSaveRequested()
{
// Save to global save file (not scene-specific)
return JsonUtility.ToJson(new { items = _items });
}
internal override void OnGlobalRestoreRequested(string data)
{
// Restore from global save file on boot
var save = JsonUtility.FromJson<SaveData>(data);
_items = new List<string>(save.items);
}
internal override void OnGlobalLoadCompleted()
{
// All global data loaded, safe to initialize UI
RefreshInventoryUI();
}
}
```
---
### Use Case 5: Auto-Registering as Pausable
**Problem:** Component implements `IPausable` and needs to register with `GameManager`.
**Solution:**
```csharp
public class AnimatedCharacter : ManagedBehaviour, IPausable
{
public override bool AutoRegisterPausable => true;
// IPausable implementation
public void Pause()
{
// Pause animations
}
public void Resume()
{
// Resume animations
}
}
// No manual registration needed - automatic!
```
---
### Use Case 6: Scene Cleanup
**Problem:** Need to clean up scene-specific state before transitioning.
**Solution:**
```csharp
public class ParticleSpawner : ManagedBehaviour
{
private List<GameObject> _activeParticles = new List<GameObject>();
internal override void OnSceneUnloading()
{
// Clean up before scene unloads
foreach (var particle in _activeParticles)
{
if (particle != null)
Destroy(particle);
}
_activeParticles.Clear();
}
}
```
---
### Use Case 7: First-Time vs Restored State
**Problem:** Need to play intro animation only on first visit, not when restoring from save.
**Solution:**
```csharp
public class LevelIntro : ManagedBehaviour
{
public override bool AutoRegisterForSave => true;
private bool _hasPlayedIntro;
internal override string OnSceneSaveRequested()
{
return JsonUtility.ToJson(new { hasPlayed = _hasPlayedIntro });
}
internal override void OnSceneRestoreRequested(string data)
{
var save = JsonUtility.FromJson<SaveData>(data);
_hasPlayedIntro = save.hasPlayed;
}
internal override void OnSceneRestoreCompleted()
{
// This fires whether we restored or not
if (!_hasPlayedIntro)
{
PlayIntroAnimation();
_hasPlayedIntro = true;
}
}
}
```
---
## Common Patterns
### Pattern: Manager Singleton
```csharp
public class MyManager : ManagedBehaviour
{
private static MyManager _instance;
public static MyManager Instance => _instance;
internal override void OnManagedAwake()
{
_instance = this;
}
}
```
### Pattern: Event Subscription
```csharp
internal override void OnManagedStart()
{
EventBus.OnSomething += HandleEvent;
}
internal override void OnManagedDestroy()
{
EventBus.OnSomething -= HandleEvent;
}
```
### Pattern: Save/Restore
```csharp
public override bool AutoRegisterForSave => true;
internal override string OnSceneSaveRequested()
{
return JsonUtility.ToJson(myData);
}
internal override void OnSceneRestoreRequested(string data)
{
myData = JsonUtility.FromJson<MyData>(data);
}
```
### Pattern: Custom SaveId
```csharp
// For singletons or special cases
public override string SaveId => "PlayerInventory";
// For scene-specific instances
public override string SaveId => $"{gameObject.scene.name}/MySpecialObject";
```
## Troubleshooting
### "NullReferenceException when accessing Manager.Instance"
**Problem:** Accessing singleton in `OnManagedAwake()`
**Solution:** Move to `OnManagedStart()` where managers are guaranteed ready
### "My SaveId is colliding with another component"
**Problem:** Two components have same GameObject name and type
**Solution:** Override `SaveId` property with unique value
### "My component isn't receiving lifecycle callbacks"
**Problem:** Not inheriting from `ManagedBehaviour`
**Solution:** Ensure class inherits `ManagedBehaviour` (not plain `MonoBehaviour`)
### "Save data isn't persisting"
**Problem:** `AutoRegisterForSave` is false
**Solution:** Set `public override bool AutoRegisterForSave => true;`
### "OnSceneRestoreCompleted isn't firing"
**Problem:** Missing implementation
**Solution:** Override the method even if just logging for now
---
**Quick Links:**
- [Technical Reference](01_technical_reference.md) - Complete API documentation
- [Architecture Overview](02_architecture_overview.md) - System design and principles

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Some files were not shown because too many files have changed in this diff Show More