Lifecycle System Refactor & Logging Centralization #56

Merged
tschesky merged 7 commits from update_tooling_and_docs into main 2025-11-11 08:48:29 +00:00
68 changed files with 1541 additions and 1916 deletions

View File

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

View File

@@ -1,4 +1,4 @@
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
namespace Interactions 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); string scenePath = AssetDatabase.GUIDToAssetPath(guid);
var scene = EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); 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) foreach (var component in allComponents)
{ {
if (component == null || component.gameObject.scene != scene) continue; 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. /// 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: /// 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 /// - But BroadcastSceneReady is NEVER called for the initial scene
/// - Components in the scene never receive their OnSceneReady() callback /// - Components in the scene never receive their OnSceneReady() callback
/// ///

View File

@@ -1,8 +1,6 @@
using UnityEditor; using UnityEditor;
using AppleHills.Core.Settings; using AppleHills.Core.Settings;
using Core; using Core;
using UnityEngine;
using UnityEngine.Rendering.VirtualTexturing;
namespace AppleHills.Editor namespace AppleHills.Editor
{ {
@@ -65,7 +63,7 @@ namespace AppleHills.Editor
GetPuzzlePromptRange GetPuzzlePromptRange
); );
LogDebugMessage("Editor settings loaded for Scene View use"); Logging.Debug("Editor settings loaded for Scene View use");
} }
public static void RefreshSceneViews() public static void RefreshSceneViews()
@@ -102,14 +100,5 @@ namespace AppleHills.Editor
return null; 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

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -1,4 +1,4 @@
using System; using System;
using AppleHills.Core.Settings; using AppleHills.Core.Settings;
using UnityEngine; using UnityEngine;
using Core; using Core;
@@ -33,11 +33,9 @@ namespace Bootstrap
// Run very early - need to set up loading screen before other systems initialize // Run very early - need to set up loading screen before other systems initialize
public override int ManagedAwakePriority => 5; public override int ManagedAwakePriority => 5;
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // Register with LifecycleManager Logging.Debug("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
LogDebugMessage("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
// Validate loading screen exists // Validate loading screen exists
if (initialLoadingScreen == null) if (initialLoadingScreen == null)
@@ -71,11 +69,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 // No need to subscribe to OnBootCompleted or check CustomBoot.Initialised
_bootComplete = true; _bootComplete = true;
_currentPhase = LoadingPhase.SceneLoading; _currentPhase = LoadingPhase.SceneLoading;
@@ -102,12 +100,12 @@ namespace Bootstrap
/// </summary> /// </summary>
private void OnInitialLoadingComplete() 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 // Play the intro cinematic if available
if (CinematicsManager.Instance != null) if (CinematicsManager.Instance != null)
{ {
LogDebugMessage("Attempting to play intro cinematic"); Logging.Debug("Attempting to play intro cinematic");
// Use LoadAndPlayCinematic to play the intro sequence // Use LoadAndPlayCinematic to play the intro sequence
CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false); CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false);
@@ -149,13 +147,13 @@ namespace Bootstrap
{ {
if (debugMode) if (debugMode)
{ {
LogDebugMessage($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}"); Logging.Debug($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
} }
} }
private void LogDebugInfo() 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}"); $"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
} }
@@ -172,7 +170,7 @@ namespace Bootstrap
private async void LoadMainScene() private async void LoadMainScene()
{ {
LogDebugMessage($"Loading main menu scene: {mainSceneName}"); Logging.Debug($"Loading main menu scene: {mainSceneName}");
try try
{ {
@@ -186,7 +184,7 @@ namespace Bootstrap
if (debugMode) if (debugMode)
{ {
LogDebugMessage($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}"); Logging.Debug($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
} }
}); });
@@ -210,13 +208,13 @@ namespace Bootstrap
_sceneLoadingProgress = 1f; _sceneLoadingProgress = 1f;
// CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks // 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); LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName);
// Restore scene data for the main menu // Restore scene data for the main menu
if (SaveLoadManager.Instance != null) if (SaveLoadManager.Instance != null)
{ {
LogDebugMessage($"Restoring scene data for: {mainSceneName}"); Logging.Debug($"Restoring scene data for: {mainSceneName}");
SaveLoadManager.Instance.RestoreSceneData(); SaveLoadManager.Instance.RestoreSceneData();
} }
@@ -246,7 +244,7 @@ namespace Bootstrap
Scene currentScene = SceneManager.GetActiveScene(); Scene currentScene = SceneManager.GetActiveScene();
string startingSceneName = currentScene.name; string startingSceneName = currentScene.name;
LogDebugMessage($"Unloading StartingScene: {startingSceneName}"); Logging.Debug($"Unloading StartingScene: {startingSceneName}");
// Unload the StartingScene // Unload the StartingScene
await SceneManager.UnloadSceneAsync(startingSceneName); await SceneManager.UnloadSceneAsync(startingSceneName);
@@ -255,14 +253,14 @@ namespace Bootstrap
Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName); Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName);
SceneManager.SetActiveScene(mainMenuScene); 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 the boot scene controller since its job is done
Destroy(gameObject); Destroy(gameObject);
} }
catch (Exception e) catch (Exception e)
{ {
Logging.Warning($"[BootSceneController] Error unloading StartingScene: {e.Message}"); Logging.Warning($"Error unloading StartingScene: {e.Message}");
} }
} }
@@ -283,13 +281,5 @@ namespace Bootstrap
_progressAction?.Invoke(value); _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;
using System.Threading.Tasks; using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core; using Core;
using Core.Lifecycle; using Core.Lifecycle;
using UnityEngine; using UnityEngine;
@@ -105,7 +104,7 @@ namespace Bootstrap
// Notify the LifecycleManager that boot is complete // Notify the LifecycleManager that boot is complete
if (Application.isPlaying) if (Application.isPlaying)
{ {
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()"); Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null) if (LifecycleManager.Instance != null)
{ {
LifecycleManager.Instance.OnBootCompletionTriggered(); LifecycleManager.Instance.OnBootCompletionTriggered();
@@ -127,7 +126,7 @@ namespace Bootstrap
// Notify the LifecycleManager that boot is complete // Notify the LifecycleManager that boot is complete
if (Application.isPlaying) if (Application.isPlaying)
{ {
LogDebugMessage("Calling LifecycleManager.OnBootCompletionTriggered()"); Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null) if (LifecycleManager.Instance != null)
{ {
LifecycleManager.Instance.OnBootCompletionTriggered(); LifecycleManager.Instance.OnBootCompletionTriggered();
@@ -238,16 +237,7 @@ namespace Bootstrap
{ {
CurrentProgress = Mathf.Clamp01(progress); CurrentProgress = Mathf.Clamp01(progress);
OnBootProgressChanged?.Invoke(CurrentProgress); OnBootProgressChanged?.Invoke(CurrentProgress);
LogDebugMessage($"Progress: {CurrentProgress:P0}"); Logging.Debug($"Progress: {CurrentProgress:P0}");
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[CustomBoot] {message}");
}
} }
} }
} }

View File

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

View File

@@ -39,15 +39,13 @@ namespace Cinematics
public override int ManagedAwakePriority => 170; // Cinematic systems public override int ManagedAwakePriority => 170; // Cinematic systems
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug("[CinematicsManager] Initialized"); Logging.Debug("[CinematicsManager] Initialized");
} }

View File

@@ -15,11 +15,10 @@ namespace Cinematics
private float _holdStartTime; private float _holdStartTime;
private bool _isHolding; private bool _isHolding;
private bool _skipPerformed; private bool _skipPerformed;
private bool _initialized = false;
public override int ManagedAwakePriority => 180; // Cinematic UI public override int ManagedAwakePriority => 180; // Cinematic UI
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Reset the progress bar // Reset the progress bar
if (radialProgressBar != null) if (radialProgressBar != null)

View File

@@ -37,14 +37,12 @@ namespace Core
// ManagedBehaviour configuration // ManagedBehaviour configuration
public override int ManagedAwakePriority => 10; // Core infrastructure - runs early 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 (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _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"; SettingsProvider.Instance.gameObject.name = "Settings Provider";
DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider"; DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider";
@@ -57,9 +55,9 @@ namespace Core
_managerLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().gameManagerLogVerbosity; _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 // This is available for future initialization that depends on other managers
} }
@@ -79,7 +77,7 @@ namespace Core
component.Pause(); component.Pause();
} }
LogDebugMessage($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}"); Logging.Debug($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
} }
} }
@@ -92,7 +90,7 @@ namespace Core
if (component != null && _pausableComponents.Contains(component)) if (component != null && _pausableComponents.Contains(component))
{ {
_pausableComponents.Remove(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 +106,7 @@ namespace Core
ApplyPause(true); ApplyPause(true);
} }
LogDebugMessage($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}"); Logging.Debug($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
} }
/// <summary> /// <summary>
@@ -123,7 +121,7 @@ namespace Core
ApplyPause(false); ApplyPause(false);
} }
LogDebugMessage($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}"); Logging.Debug($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
} }
/// <summary> /// <summary>
@@ -162,12 +160,12 @@ namespace Core
OnGameResumed?.Invoke(); OnGameResumed?.Invoke();
} }
LogDebugMessage($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components."); Logging.Debug($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components.");
} }
private void InitializeSettings() private void InitializeSettings()
{ {
LogDebugMessage("Starting settings initialization...", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("Starting settings initialization...");
// Load settings synchronously // Load settings synchronously
var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous<PlayerFollowerSettings>(); var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous<PlayerFollowerSettings>();
@@ -178,7 +176,7 @@ namespace Core
if (playerSettings != null) if (playerSettings != null)
{ {
ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings); ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings);
LogDebugMessage("PlayerFollowerSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("PlayerFollowerSettings registered successfully");
} }
else else
{ {
@@ -188,7 +186,7 @@ namespace Core
if (interactionSettings != null) if (interactionSettings != null)
{ {
ServiceLocator.Register<IInteractionSettings>(interactionSettings); ServiceLocator.Register<IInteractionSettings>(interactionSettings);
LogDebugMessage("InteractionSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("InteractionSettings registered successfully");
} }
else else
{ {
@@ -198,7 +196,7 @@ namespace Core
if (minigameSettings != null) if (minigameSettings != null)
{ {
ServiceLocator.Register<IDivingMinigameSettings>(minigameSettings); ServiceLocator.Register<IDivingMinigameSettings>(minigameSettings);
LogDebugMessage("MinigameSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("MinigameSettings registered successfully");
} }
else else
{ {
@@ -209,7 +207,7 @@ namespace Core
_settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null; _settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null;
if (_settingsLoaded) if (_settingsLoaded)
{ {
LogDebugMessage("All settings loaded and registered with ServiceLocator", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("All settings loaded and registered with ServiceLocator");
} }
else else
{ {
@@ -222,7 +220,7 @@ namespace Core
/// </summary> /// </summary>
private void InitializeDeveloperSettings() private void InitializeDeveloperSettings()
{ {
LogDebugMessage("Starting developer settings initialization...", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("Starting developer settings initialization...");
// Load developer settings // Load developer settings
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>(); var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
@@ -232,7 +230,7 @@ namespace Core
if (_developerSettingsLoaded) if (_developerSettingsLoaded)
{ {
LogDebugMessage("All developer settings loaded successfully", "SettingsInitialization", _settingsLogVerbosity); Logging.Debug("All developer settings loaded successfully");
} }
else else
{ {
@@ -267,19 +265,6 @@ namespace Core
return DeveloperSettingsProvider.Instance?.GetSettings<T>(); 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 // LEFTOVER LEGACY SETTINGS
public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f; public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f;
public float PlayerStopDistanceDirectInteraction => GetSettings<IInteractionSettings>()?.PlayerStopDistanceDirectInteraction ?? 2.0f; public float PlayerStopDistanceDirectInteraction => GetSettings<IInteractionSettings>()?.PlayerStopDistanceDirectInteraction ?? 2.0f;

View File

@@ -50,20 +50,18 @@ namespace Core
public override int ManagedAwakePriority => 75; // Item registry public override int ManagedAwakePriority => 75; // Item registry
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug("[ItemManager] Initialized"); Logging.Debug("[ItemManager] Initialized");
} }
protected override void OnSceneReady() internal override void OnSceneReady()
{ {
// Replaces SceneLoadStarted subscription for clearing registrations // Replaces SceneLoadStarted subscription for clearing registrations
ClearAllRegistrations(); ClearAllRegistrations();
@@ -144,7 +142,6 @@ namespace Core
{ {
s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted; s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted; s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
s.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved; s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
} }
} }
@@ -191,7 +188,6 @@ namespace Core
// Subscribe to all slot events // Subscribe to all slot events
slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted; slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted; slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted += ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved; slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved;
} }
} }
@@ -204,7 +200,6 @@ namespace Core
// Unsubscribe from all slot events // Unsubscribe from all slot events
slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted; slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted; slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved; slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEngine; using UnityEngine;
@@ -123,7 +123,24 @@ namespace Core.Lifecycle
// ALWAYS add to managedAwakeList - this is the master list used for save/load // ALWAYS add to managedAwakeList - this is the master list used for save/load
InsertSorted(managedAwakeList, component, component.ManagedAwakePriority); InsertSorted(managedAwakeList, component, component.ManagedAwakePriority);
// Handle ManagedAwake timing based on boot state // 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);
// Call OnManagedAwake immediately after registration (early initialization hook)
try
{
component.OnManagedAwake();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedAwake for {component.gameObject.name}: {ex}");
}
// Handle OnManagedStart timing based on boot state
if (isBootComplete) if (isBootComplete)
{ {
// Check if we're currently loading a scene // Check if we're currently loading a scene
@@ -136,27 +153,20 @@ namespace Core.Lifecycle
else else
{ {
// Truly late registration (component enabled after scene is ready) // Truly late registration (component enabled after scene is ready)
// Call OnManagedAwake immediately since boot already completed // Call OnManagedStart immediately since boot already completed
LogDebug($"Late registration: Calling OnManagedAwake immediately for {component.gameObject.name}"); LogDebug($"Late registration: Calling OnManagedStart immediately for {component.gameObject.name}");
try try
{ {
component.InvokeManagedAwake(); component.OnManagedStart();
HandleAutoRegistrations(component); HandleAutoRegistrations(component);
} }
catch (Exception ex) 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() // If boot not complete, component stays in list and will be processed by BroadcastManagedStart()
// 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 this scene is already ready (and we're not in loading mode), call OnSceneReady immediately // If this scene is already ready (and we're not in loading mode), call OnSceneReady immediately
if (!isLoadingScene && currentSceneReady == sceneName) if (!isLoadingScene && currentSceneReady == sceneName)
@@ -164,7 +174,7 @@ namespace Core.Lifecycle
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}"); LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
try try
{ {
component.InvokeSceneReady(); component.OnSceneReady();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -202,7 +212,7 @@ namespace Core.Lifecycle
/// <summary> /// <summary>
/// Called by CustomBoot when boot completes. /// Called by CustomBoot when boot completes.
/// Broadcasts ManagedAwake to all registered components. /// Broadcasts ManagedStart to all registered components.
/// </summary> /// </summary>
public void OnBootCompletionTriggered() public void OnBootCompletionTriggered()
{ {
@@ -210,16 +220,16 @@ namespace Core.Lifecycle
return; return;
LogDebug("=== Boot Completion Triggered ==="); LogDebug("=== Boot Completion Triggered ===");
BroadcastManagedAwake(); BroadcastManagedStart();
isBootComplete = true; isBootComplete = true;
} }
/// <summary> /// <summary>
/// Broadcast OnManagedAwake to all registered components (priority ordered). /// Broadcast OnManagedStart to all registered components (priority ordered).
/// </summary> /// </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 // Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList); var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
@@ -230,12 +240,12 @@ namespace Core.Lifecycle
try try
{ {
component.InvokeManagedAwake(); component.OnManagedStart();
HandleAutoRegistrations(component); HandleAutoRegistrations(component);
} }
catch (Exception ex) 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}");
} }
} }
@@ -275,20 +285,20 @@ namespace Core.Lifecycle
// Sort by ManagedAwake priority (lower values first) // Sort by ManagedAwake priority (lower values first)
pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority)); pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority));
// Call OnManagedAwake in priority order // Call OnManagedStart in priority order
foreach (var component in pendingSceneComponents) foreach (var component in pendingSceneComponents)
{ {
if (component == null) continue; if (component == null) continue;
try try
{ {
component.InvokeManagedAwake(); component.OnManagedStart();
HandleAutoRegistrations(component); HandleAutoRegistrations(component);
LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})"); LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})");
} }
catch (Exception ex) 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}");
} }
} }
@@ -315,7 +325,7 @@ namespace Core.Lifecycle
{ {
try try
{ {
component.InvokeSceneUnloading(); component.OnSceneUnloading();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -351,7 +361,7 @@ namespace Core.Lifecycle
{ {
try try
{ {
component.InvokeSceneReady(); component.OnSceneReady();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -379,7 +389,7 @@ namespace Core.Lifecycle
try try
{ {
string serializedData = component.InvokeSceneSaveRequested(); string serializedData = component.OnSceneSaveRequested();
if (!string.IsNullOrEmpty(serializedData)) if (!string.IsNullOrEmpty(serializedData))
{ {
string saveId = component.SaveId; string saveId = component.SaveId;
@@ -415,7 +425,7 @@ namespace Core.Lifecycle
try try
{ {
string serializedData = component.InvokeGlobalSaveRequested(); string serializedData = component.OnGlobalSaveRequested();
if (!string.IsNullOrEmpty(serializedData)) if (!string.IsNullOrEmpty(serializedData))
{ {
saveData[component.SaveId] = serializedData; saveData[component.SaveId] = serializedData;
@@ -455,7 +465,7 @@ namespace Core.Lifecycle
{ {
try try
{ {
component.InvokeSceneRestoreRequested(serializedData); component.OnSceneRestoreRequested(serializedData);
restoredCount++; restoredCount++;
LogDebug($"Restored scene data to: {component.SaveId}"); LogDebug($"Restored scene data to: {component.SaveId}");
} }
@@ -486,7 +496,7 @@ namespace Core.Lifecycle
try try
{ {
component.InvokeSceneRestoreCompleted(); component.OnSceneRestoreCompleted();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -519,7 +529,7 @@ namespace Core.Lifecycle
{ {
try try
{ {
component.InvokeGlobalRestoreRequested(serializedData); component.OnGlobalRestoreRequested(serializedData);
restoredCount++; restoredCount++;
LogDebug($"Restored global data to: {component.SaveId}"); LogDebug($"Restored global data to: {component.SaveId}");
} }
@@ -551,7 +561,7 @@ namespace Core.Lifecycle
try try
{ {
component.InvokeGlobalLoadCompleted(); component.OnGlobalLoadCompleted();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -578,7 +588,7 @@ namespace Core.Lifecycle
try try
{ {
component.InvokeGlobalSaveStarted(); component.OnGlobalSaveStarted();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -1,5 +1,4 @@
using System; using UnityEngine;
using UnityEngine;
namespace Core.Lifecycle namespace Core.Lifecycle
{ {
@@ -12,7 +11,7 @@ namespace Core.Lifecycle
#region Priority Properties #region Priority Properties
/// <summary> /// <summary>
/// Priority for OnManagedAwake (lower values execute first). /// Priority for OnManagedStart (lower values execute first).
/// Default: 100 /// Default: 100
/// </summary> /// </summary>
public virtual int ManagedAwakePriority => 100; public virtual int ManagedAwakePriority => 100;
@@ -81,23 +80,6 @@ namespace Core.Lifecycle
#endregion #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 void InvokeSceneRestoreCompleted() => OnSceneRestoreCompleted();
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 #region Private Fields
private bool _isRegistered; private bool _isRegistered;
@@ -108,9 +90,9 @@ namespace Core.Lifecycle
/// <summary> /// <summary>
/// Unity Awake - automatically registers with LifecycleManager. /// 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> /// </summary>
protected virtual void Awake() private void Awake()
{ {
if (LifecycleManager.Instance != null) if (LifecycleManager.Instance != null)
{ {
@@ -152,14 +134,27 @@ namespace Core.Lifecycle
#region Managed Lifecycle Hooks #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> /// <summary>
/// Called once per component after bootstrap completes. /// Called once per component after bootstrap completes.
/// GUARANTEE: Bootstrap resources are available, all managers are initialized. /// 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 (priority ordered).
/// For late-registered components: Called immediately upon registration (bootstrap already complete). /// 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> /// </summary>
protected virtual void OnManagedAwake() internal virtual void OnManagedStart()
{ {
// Override in derived classes // Override in derived classes
} }
@@ -168,8 +163,9 @@ namespace Core.Lifecycle
/// Called before the scene this component belongs to is unloaded. /// Called before the scene this component belongs to is unloaded.
/// Called in REVERSE priority order (higher values execute first). /// Called in REVERSE priority order (higher values execute first).
/// Use for scene-specific cleanup. /// Use for scene-specific cleanup.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnSceneUnloading() internal virtual void OnSceneUnloading()
{ {
// Override in derived classes // Override in derived classes
} }
@@ -178,8 +174,9 @@ namespace Core.Lifecycle
/// Called after the scene this component belongs to has finished loading. /// Called after the scene this component belongs to has finished loading.
/// Called in priority order (lower values execute first). /// Called in priority order (lower values execute first).
/// Use for scene-specific initialization. /// Use for scene-specific initialization.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnSceneReady() internal virtual void OnSceneReady()
{ {
// Override in derived classes // Override in derived classes
} }
@@ -193,8 +190,10 @@ namespace Core.Lifecycle
/// - Called BEFORE scene unload during scene transitions /// - Called BEFORE scene unload during scene transitions
/// - Frequency: Every scene transition /// - Frequency: Every scene transition
/// - Use for: Level progress, object positions, puzzle states /// - Use for: Level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual string OnSceneSaveRequested() internal virtual string OnSceneSaveRequested()
{ {
return null; // Default: no data to save return null; // Default: no data to save
} }
@@ -211,8 +210,10 @@ namespace Core.Lifecycle
/// - Called AFTER scene load, during OnSceneReady phase /// - Called AFTER scene load, during OnSceneReady phase
/// - Frequency: Every scene transition /// - Frequency: Every scene transition
/// - Use for: Restoring level progress, object positions, puzzle states /// - Use for: Restoring level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnSceneRestoreRequested(string serializedData) internal virtual void OnSceneRestoreRequested(string serializedData)
{ {
// Default: no-op // Default: no-op
} }
@@ -234,8 +235,10 @@ namespace Core.Lifecycle
/// COMMON PATTERN: /// COMMON PATTERN:
/// Use this to perform actions that depend on whether data was restored or not. /// 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). /// 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> /// </summary>
protected virtual void OnSceneRestoreCompleted() internal virtual void OnSceneRestoreCompleted()
{ {
// Default: no-op // Default: no-op
} }
@@ -248,8 +251,10 @@ namespace Core.Lifecycle
/// - Called ONCE on game boot after save file is read /// - Called ONCE on game boot after save file is read
/// - NOT called during scene transitions /// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections /// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnGlobalRestoreRequested(string serializedData) internal virtual void OnGlobalRestoreRequested(string serializedData)
{ {
// Default: no-op // Default: no-op
} }
@@ -263,8 +268,10 @@ namespace Core.Lifecycle
/// - Called ONCE before save file is written (on quit, manual save, etc.) /// - Called ONCE before save file is written (on quit, manual save, etc.)
/// - NOT called during scene transitions /// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections /// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual string OnGlobalSaveRequested() internal virtual string OnGlobalSaveRequested()
{ {
return null; // Default: no data to save return null; // Default: no data to save
} }
@@ -278,8 +285,10 @@ namespace Core.Lifecycle
/// - Called ONCE on game boot after all restore operations complete /// - Called ONCE on game boot after all restore operations complete
/// - NOT called during scene transitions /// - NOT called during scene transitions
/// - Use for: Triggering UI updates, broadcasting load events /// - Use for: Triggering UI updates, broadcasting load events
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnGlobalLoadCompleted() internal virtual void OnGlobalLoadCompleted()
{ {
// Default: no-op // Default: no-op
} }
@@ -293,8 +302,10 @@ namespace Core.Lifecycle
/// - Called ONCE before save file is written /// - Called ONCE before save file is written
/// - NOT called during scene transitions /// - NOT called during scene transitions
/// - Use for: Final validation, cleanup operations /// - Use for: Final validation, cleanup operations
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnGlobalSaveStarted() internal virtual void OnGlobalSaveStarted()
{ {
// Default: no-op // Default: no-op
} }
@@ -304,8 +315,9 @@ namespace Core.Lifecycle
/// Called in REVERSE priority order (higher values execute first). /// Called in REVERSE priority order (higher values execute first).
/// NOTE: Most cleanup is automatic (managed events, auto-registrations). /// NOTE: Most cleanup is automatic (managed events, auto-registrations).
/// Only override if you need custom cleanup logic. /// Only override if you need custom cleanup logic.
/// Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary> /// </summary>
protected virtual void OnManagedDestroy() internal virtual void OnManagedDestroy()
{ {
// Override in derived classes // 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 public static class Logging
{ {
[System.Diagnostics.Conditional("ENABLE_LOG")] /// <summary>
public static void Debug(object message) /// 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")] [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

@@ -129,20 +129,18 @@ namespace AppleHills.Core
#region Lifecycle Methods #region Lifecycle Methods
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// QuickAccess has minimal initialization // QuickAccess has minimal initialization
} }
protected override void OnSceneUnloading() internal override void OnSceneUnloading()
{ {
// Clear references BEFORE scene unloads for better cleanup timing // Clear references BEFORE scene unloads for better cleanup timing
ClearReferences(); ClearReferences();

View File

@@ -46,11 +46,9 @@ namespace Core.SaveLoad
// ManagedBehaviour configuration // ManagedBehaviour configuration
public override int ManagedAwakePriority => 20; // After GameManager and SceneManagerService 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 (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
// Initialize critical state immediately // Initialize critical state immediately
@@ -58,7 +56,7 @@ namespace Core.SaveLoad
IsRestoringState = false; IsRestoringState = false;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug("[SaveLoadManager] Initialized"); Logging.Debug("[SaveLoadManager] Initialized");
@@ -69,19 +67,19 @@ namespace Core.SaveLoad
} }
} }
protected override void OnSceneReady() internal override void OnSceneReady()
{ {
// SaveableInteractables now auto-register via ManagedBehaviour lifecycle // SaveableInteractables now auto-register via ManagedBehaviour lifecycle
// No need to discover and register them manually // No need to discover and register them manually
} }
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
// SaveLoadManager orchestrates saves, doesn't participate in them // SaveLoadManager orchestrates saves, doesn't participate in them
return null; return null;
} }
protected override string OnGlobalSaveRequested() internal override string OnGlobalSaveRequested()
{ {
// SaveLoadManager orchestrates saves, doesn't participate in them // SaveLoadManager orchestrates saves, doesn't participate in them
return null; return null;

View File

@@ -30,9 +30,9 @@ namespace Core
// Enable save/load participation // Enable save/load participation
public override bool AutoRegisterForSave => true; public override bool AutoRegisterForSave => true;
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); base.OnManagedAwake();
_director = GetComponent<PlayableDirector>(); _director = GetComponent<PlayableDirector>();
if (_director != null) if (_director != null)
@@ -65,7 +65,7 @@ namespace Core
#region Save/Load Implementation #region Save/Load Implementation
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
var saveData = new PlayableDirectorSaveData var saveData = new PlayableDirectorSaveData
{ {
@@ -77,7 +77,7 @@ namespace Core
return JsonUtility.ToJson(saveData); return JsonUtility.ToJson(saveData);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (string.IsNullOrEmpty(serializedData)) if (string.IsNullOrEmpty(serializedData))
{ {

View File

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

View File

@@ -21,20 +21,18 @@ namespace Core
// ManagedBehaviour configuration // ManagedBehaviour configuration
public override int ManagedAwakePriority => 70; // Platform-specific utility 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 (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _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; _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 // Subscribe to SceneManagerService to enforce orientation on every scene load
if (SceneManagerService.Instance != null) if (SceneManagerService.Instance != null)
@@ -51,7 +49,7 @@ namespace Core
#endif #endif
} }
protected override void OnSceneReady() internal override void OnSceneReady()
{ {
// Handle orientation when scene is ready (initial scene) // Handle orientation when scene is ready (initial scene)
// Note: This fires for the scene that just loaded, LifecycleManager tracks which scene // Note: This fires for the scene that just loaded, LifecycleManager tracks which scene
@@ -73,7 +71,7 @@ namespace Core
if (sceneName.ToLower().Contains("bootstrap")) if (sceneName.ToLower().Contains("bootstrap"))
{ {
// Bootstrap being loaded additively, don't do anything // 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; return;
} }
@@ -83,23 +81,23 @@ namespace Core
} }
else else
{ {
LogDebugMessage($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'"); Logging.Debug($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'");
} }
switch (requirement) switch (requirement)
{ {
case ScreenOrientationRequirement.Portrait: case ScreenOrientationRequirement.Portrait:
LogDebugMessage($"Forcing Portrait for scene '{sceneName}'"); Logging.Debug($"Forcing Portrait for scene '{sceneName}'");
StartCoroutine(ForcePortrait()); StartCoroutine(ForcePortrait());
break; break;
case ScreenOrientationRequirement.Landscape: case ScreenOrientationRequirement.Landscape:
LogDebugMessage($"Forcing Landscape for scene '{sceneName}'"); Logging.Debug($"Forcing Landscape for scene '{sceneName}'");
StartCoroutine(ForceLandscape()); StartCoroutine(ForceLandscape());
break; break;
case ScreenOrientationRequirement.NotApplicable: case ScreenOrientationRequirement.NotApplicable:
default: default:
// Default to landscape when no specific requirement is found // 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()); StartCoroutine(ForceLandscape());
break; break;
} }
@@ -127,12 +125,12 @@ namespace Core
if (!currentlyLandscape) if (!currentlyLandscape)
{ {
// Lock it to portrait and allow the device to orient itself // 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; Screen.orientation = ScreenOrientation.LandscapeRight;
} }
else else
{ {
LogDebugMessage($"Skipping Landscape enforcement, device already in: {Screen.orientation}"); Logging.Debug($"Skipping Landscape enforcement, device already in: {Screen.orientation}");
} }
yield return null; yield return null;
@@ -160,12 +158,12 @@ namespace Core
if (!currentlyPortrait) if (!currentlyPortrait)
{ {
// Lock it to portrait and allow the device to orient itself // 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; Screen.orientation = ScreenOrientation.PortraitUpsideDown;
} }
else else
{ {
LogDebugMessage($"Skipping Portrait enforcement, device already in: {Screen.orientation}"); Logging.Debug($"Skipping Portrait enforcement, device already in: {Screen.orientation}");
} }
yield return null; yield return null;
@@ -181,13 +179,5 @@ namespace Core
// Allow device to auto-rotate to correct portrait orientation // Allow device to auto-rotate to correct portrait orientation
Screen.orientation = ScreenOrientation.AutoRotation; 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;
using System.Collections.Generic; using System.Collections.Generic;
using AppleHills.Core.Settings;
using UnityEngine;
namespace Core.Settings namespace Core.Settings
{ {
@@ -21,7 +19,7 @@ namespace Core.Settings
public static void Register<T>(T service) where T : class public static void Register<T>(T service) where T : class
{ {
Services[typeof(T)] = service; Services[typeof(T)] = service;
LogDebugMessage($"Service registered: {typeof(T).Name}"); Logging.Debug($"Service registered: {typeof(T).Name}");
} }
/// <summary> /// <summary>
@@ -36,7 +34,7 @@ namespace Core.Settings
return service as T; 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; return null;
} }
@@ -46,16 +44,7 @@ namespace Core.Settings
public static void Clear() public static void Clear()
{ {
Services.Clear(); Services.Clear();
LogDebugMessage("All services cleared"); Logging.Debug("All services cleared");
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().settingsLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[ServiceLocator] {message}");
}
} }
} }
} }

View File

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

@@ -22,7 +22,7 @@ public class soundBird_CanFly : ManagedBehaviour
#region Save/Load Implementation #region Save/Load Implementation
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
var saveData = new SoundBirdSaveData var saveData = new SoundBirdSaveData
{ {
@@ -32,7 +32,7 @@ public class soundBird_CanFly : ManagedBehaviour
return JsonUtility.ToJson(saveData); return JsonUtility.ToJson(saveData);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (string.IsNullOrEmpty(serializedData)) if (string.IsNullOrEmpty(serializedData))
{ {

View File

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

View File

@@ -1,6 +1,4 @@
using System; using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using Core; using Core;
using Core.Lifecycle; using Core.Lifecycle;
using Interactions; using Interactions;
@@ -37,7 +35,7 @@ namespace Dialogue
public override int ManagedAwakePriority => 150; // Dialogue systems public override int ManagedAwakePriority => 150; // Dialogue systems
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Get required components // Get required components
appleAudioSource = GetComponent<AppleAudioSource>(); appleAudioSource = GetComponent<AppleAudioSource>();
@@ -186,8 +184,10 @@ namespace Dialogue
return null; return null;
} }
private void OnDestroy() protected override void OnDestroy()
{ {
base.OnDestroy();
// Unregister from events // Unregister from events
if (PuzzleManager.Instance != null) if (PuzzleManager.Instance != null)
PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted; PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted;

View File

@@ -52,11 +52,9 @@ namespace Input
public override int ManagedAwakePriority => 25; // Input infrastructure public override int ManagedAwakePriority => 25; // Input infrastructure
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
// Load verbosity settings early // Load verbosity settings early
@@ -89,10 +87,10 @@ namespace Input
SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name); SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name);
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Subscribe to scene load events from SceneManagerService // 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) if (SceneManagerService.Instance != null)
{ {
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted; SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
@@ -104,7 +102,7 @@ namespace Input
/// </summary> /// </summary>
private void OnSceneLoadCompleted(string sceneName) private void OnSceneLoadCompleted(string sceneName)
{ {
LogDebugMessage($"Scene loaded: {sceneName}, restoring input mode"); Logging.Debug($"Scene loaded: {sceneName}, restoring input mode");
SwitchInputOnSceneLoaded(sceneName); SwitchInputOnSceneLoaded(sceneName);
} }
@@ -182,24 +180,24 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>(); Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); 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 // First try to delegate to an override consumer if available
if (TryDelegateToOverrideConsumer(screenPos, worldPos2D)) if (TryDelegateToOverrideConsumer(screenPos, worldPos2D))
{ {
LogDebugMessage("Tap delegated to override consumer"); Logging.Debug("Tap delegated to override consumer");
return; return;
} }
// Then try to delegate to any ITouchInputConsumer (UI or world interactable) // Then try to delegate to any ITouchInputConsumer (UI or world interactable)
if (!TryDelegateToAnyInputConsumer(screenPos, worldPos2D)) 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); defaultConsumer?.OnTap(worldPos2D);
} }
else else
{ {
LogDebugMessage("Tap delegated to input consumer"); Logging.Debug("Tap delegated to input consumer");
} }
} }
@@ -212,13 +210,13 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>(); Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"HoldMove started at {worldPos2D}"); Logging.Debug($"HoldMove started at {worldPos2D}");
// First check for override consumers // First check for override consumers
if (_overrideConsumers.Count > 0) if (_overrideConsumers.Count > 0)
{ {
_activeHoldConsumer = _overrideConsumers[_overrideConsumers.Count - 1]; _activeHoldConsumer = _overrideConsumers[_overrideConsumers.Count - 1];
LogDebugMessage($"Hold delegated to override consumer: {_activeHoldConsumer}"); Logging.Debug($"Hold delegated to override consumer: {_activeHoldConsumer}");
_activeHoldConsumer.OnHoldStart(worldPos2D); _activeHoldConsumer.OnHoldStart(worldPos2D);
return; return;
} }
@@ -238,7 +236,7 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>(); Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos); Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y); 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 // Notify the active hold consumer that the hold has ended
_activeHoldConsumer?.OnHoldEnd(worldPos2D); _activeHoldConsumer?.OnHoldEnd(worldPos2D);
@@ -302,7 +300,7 @@ namespace Input
} }
if (consumer != null) 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); consumer.OnTap(screenPos);
return true; return true;
} }
@@ -331,7 +329,7 @@ namespace Input
} }
if (consumer != null) 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); consumer.OnTap(worldPos);
return true; return true;
} }
@@ -345,15 +343,15 @@ namespace Input
var consumer = hit.GetComponent<ITouchInputConsumer>(); var consumer = hit.GetComponent<ITouchInputConsumer>();
if (consumer != null) 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); consumer.OnTap(worldPos);
return true; 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 else
{ {
LogDebugMessage($"No Collider2D found at {worldPos} for interactable delegation."); Logging.Debug($"No Collider2D found at {worldPos} for interactable delegation.");
} }
return false; return false;
} }
@@ -368,7 +366,7 @@ namespace Input
return; return;
_overrideConsumers.Add(consumer); _overrideConsumers.Add(consumer);
LogDebugMessage($"Override consumer registered: {consumer}"); Logging.Debug($"Override consumer registered: {consumer}");
} }
/// <summary> /// <summary>
@@ -386,7 +384,7 @@ namespace Input
} }
_overrideConsumers.Remove(consumer); _overrideConsumers.Remove(consumer);
LogDebugMessage($"Override consumer unregistered: {consumer}"); Logging.Debug($"Override consumer unregistered: {consumer}");
} }
/// <summary> /// <summary>
@@ -396,7 +394,7 @@ namespace Input
{ {
_activeHoldConsumer = null; _activeHoldConsumer = null;
_overrideConsumers.Clear(); _overrideConsumers.Clear();
LogDebugMessage("All override consumers cleared."); Logging.Debug("All override consumers cleared.");
} }
/// <summary> /// <summary>
@@ -409,17 +407,9 @@ namespace Input
// Get the topmost override consumer (last registered) // Get the topmost override consumer (last registered)
var consumer = _overrideConsumers[_overrideConsumers.Count - 1]; 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); consumer.OnTap(worldPos);
return true; 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 AppleHills.Core.Settings;
using Core; using Core;
using Core.Lifecycle; using Core.Lifecycle;
using Core.SaveLoad;
namespace Input namespace Input
{ {
@@ -73,7 +72,7 @@ namespace Input
public override string SaveId => $"{gameObject.scene.name}/PlayerController"; public override string SaveId => $"{gameObject.scene.name}/PlayerController";
public override int ManagedAwakePriority => 100; // Player controller public override int ManagedAwakePriority => 100; // Player controller
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
aiPath = GetComponent<AIPath>(); aiPath = GetComponent<AIPath>();
artTransform = transform.Find("CharacterArt"); artTransform = transform.Find("CharacterArt");
@@ -103,7 +102,7 @@ namespace Input
public void OnTap(Vector2 worldPosition) public void OnTap(Vector2 worldPosition)
{ {
InterruptMoveTo(); InterruptMoveTo();
LogDebugMessage($"OnTap at {worldPosition}"); Logging.Debug($"OnTap at {worldPosition}");
if (aiPath != null) if (aiPath != null)
{ {
aiPath.enabled = true; aiPath.enabled = true;
@@ -122,7 +121,7 @@ namespace Input
public void OnHoldStart(Vector2 worldPosition) public void OnHoldStart(Vector2 worldPosition)
{ {
InterruptMoveTo(); InterruptMoveTo();
LogDebugMessage($"OnHoldStart at {worldPosition}"); Logging.Debug($"OnHoldStart at {worldPosition}");
lastHoldPosition = worldPosition; lastHoldPosition = worldPosition;
isHolding = true; isHolding = true;
if (_settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding && if (_settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding &&
@@ -159,7 +158,7 @@ namespace Input
/// </summary> /// </summary>
public void OnHoldEnd(Vector2 worldPosition) public void OnHoldEnd(Vector2 worldPosition)
{ {
LogDebugMessage($"OnHoldEnd at {worldPosition}"); Logging.Debug($"OnHoldEnd at {worldPosition}");
isHolding = false; isHolding = false;
directMoveVelocity = Vector3.zero; directMoveVelocity = Vector3.zero;
if (aiPath != null && _settings.DefaultHoldMovementMode == if (aiPath != null && _settings.DefaultHoldMovementMode ==
@@ -335,13 +334,13 @@ namespace Input
{ {
_isMoving = true; _isMoving = true;
OnMovementStarted?.Invoke(); OnMovementStarted?.Invoke();
LogDebugMessage("Movement started"); Logging.Debug("Movement started");
} }
else if (!isCurrentlyMoving && _isMoving) else if (!isCurrentlyMoving && _isMoving)
{ {
_isMoving = false; _isMoving = false;
OnMovementStopped?.Invoke(); OnMovementStopped?.Invoke();
LogDebugMessage("Movement stopped"); Logging.Debug("Movement stopped");
} }
} }
@@ -425,17 +424,9 @@ namespace Input
} }
} }
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[PlayerTouchController] {message}");
}
}
#region Save/Load Lifecycle Hooks #region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
var saveData = new PlayerSaveData var saveData = new PlayerSaveData
{ {
@@ -445,7 +436,7 @@ namespace Input
return JsonUtility.ToJson(saveData); return JsonUtility.ToJson(saveData);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (string.IsNullOrEmpty(serializedData)) if (string.IsNullOrEmpty(serializedData))
{ {

View File

@@ -68,8 +68,6 @@ namespace Interactions
public event Action<PickupItemData, PickupItemData> OnIncorrectItemSlotted; public event Action<PickupItemData, PickupItemData> OnIncorrectItemSlotted;
public UnityEvent onForbiddenItemSlotted; public UnityEvent onForbiddenItemSlotted;
// Native C# event alternative for code-only subscribers
public event Action<PickupItemData, PickupItemData> OnForbiddenItemSlotted;
public GameObject GetSlottedObject() 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 // Setup visuals
if (iconRenderer == null) if (iconRenderer == null)

View File

@@ -32,9 +32,9 @@ namespace Interactions
public event Action<PickupItemData> OnItemPickedUp; public event Action<PickupItemData> OnItemPickedUp;
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined; 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) if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>(); iconRenderer = GetComponent<SpriteRenderer>();
@@ -44,8 +44,9 @@ namespace Interactions
// Always register with ItemManager, even if picked up // Always register with ItemManager, even if picked up
// This allows the save/load system to find held items when restoring state // 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); ItemManager.Instance?.RegisterPickup(this);
} }

View File

@@ -24,7 +24,7 @@ namespace Interactions
#region Save/Load Lifecycle Hooks #region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
object stateData = GetSerializableState(); object stateData = GetSerializableState();
if (stateData == null) if (stateData == null)
@@ -35,7 +35,7 @@ namespace Interactions
return JsonUtility.ToJson(stateData); return JsonUtility.ToJson(stateData);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (string.IsNullOrEmpty(serializedData)) if (string.IsNullOrEmpty(serializedData))
{ {

View File

@@ -21,9 +21,9 @@ namespace Levels
/// <summary> /// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers. /// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary> /// </summary>
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); base.OnManagedAwake();
Logging.Debug($"[LevelSwitch] Awake called for {gameObject.name} in scene {gameObject.scene.name}"); Logging.Debug($"[LevelSwitch] Awake called for {gameObject.name} in scene {gameObject.scene.name}");
@@ -36,12 +36,12 @@ namespace Levels
ApplySwitchData(); ApplySwitchData();
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug($"[LevelSwitch] OnManagedAwake called for {gameObject.name}"); Logging.Debug($"[LevelSwitch] OnManagedStart called for {gameObject.name}");
} }
protected override void OnSceneReady() internal override void OnSceneReady()
{ {
Logging.Debug($"[LevelSwitch] OnSceneReady called for {gameObject.name}"); Logging.Debug($"[LevelSwitch] OnSceneReady called for {gameObject.name}");
} }

View File

@@ -35,21 +35,16 @@ namespace Levels
[SerializeField] private bool startUnlocked = false; [SerializeField] private bool startUnlocked = false;
private SpriteRenderer iconRenderer; private SpriteRenderer iconRenderer;
// Settings reference
private IInteractionSettings interactionSettings; private IInteractionSettings interactionSettings;
private bool switchActive = true;
private bool isUnlocked; private bool isUnlocked;
/// <summary> /// <summary>
/// Unity Awake callback. Sets up icon, interactable, and event handlers. /// Unity Awake callback. Sets up icon, interactable, and event handlers.
/// </summary> /// </summary>
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); base.OnManagedAwake();
switchActive = true;
if (iconRenderer == null) if (iconRenderer == null)
iconRenderer = GetComponent<SpriteRenderer>(); iconRenderer = GetComponent<SpriteRenderer>();
@@ -64,10 +59,9 @@ namespace Levels
ApplySwitchData(); ApplySwitchData();
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
base.OnManagedAwake(); base.OnManagedStart();
// If startUnlocked is true, always start active // If startUnlocked is true, always start active
if (startUnlocked) if (startUnlocked)
{ {
@@ -145,14 +139,6 @@ namespace Levels
return base.CanBeClicked() && isUnlocked; return base.CanBeClicked() && isUnlocked;
} }
/// <summary>
/// Setup: Prevent re-entry while interaction is in progress.
/// </summary>
protected override void OnInteractionStarted()
{
switchActive = false;
}
/// <summary> /// <summary>
/// Main interaction logic: Spawn menu and switch input mode. /// Main interaction logic: Spawn menu and switch input mode.
/// </summary> /// </summary>
@@ -203,7 +189,7 @@ namespace Levels
private void OnMenuCancel() private void OnMenuCancel()
{ {
switchActive = true; // Allow interaction again if cancelled
InputManager.Instance.SetInputMode(InputMode.GameAndUI); InputManager.Instance.SetInputMode(InputMode.GameAndUI);
} }

View File

@@ -107,10 +107,8 @@ namespace Minigames.DivingForPictures
public override int ManagedAwakePriority => 190; public override int ManagedAwakePriority => 190;
public override bool AutoRegisterPausable => true; // Automatic GameManager registration public override bool AutoRegisterPausable => true; // Automatic GameManager registration
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake();
if (_instance == null) if (_instance == null)
{ {
_instance = this; _instance = this;
@@ -121,7 +119,7 @@ namespace Minigames.DivingForPictures
} }
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
_settings = GameManager.GetSettingsObject<IDivingMinigameSettings>(); _settings = GameManager.GetSettingsObject<IDivingMinigameSettings>();
_currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f; _currentSpawnProbability = _settings?.BaseSpawnProbability ?? 0.2f;
@@ -132,7 +130,7 @@ namespace Minigames.DivingForPictures
Logging.Debug("[DivingGameManager] Initialized"); Logging.Debug("[DivingGameManager] Initialized");
} }
protected override void OnSceneReady() internal override void OnSceneReady()
{ {
InitializeGame(); InitializeGame();

View File

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

View File

@@ -16,7 +16,7 @@ namespace PuzzleS
// Save system configuration // Save system configuration
public override bool AutoRegisterForSave => true; public override bool AutoRegisterForSave => true;
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Initialize after all managers are ready // Initialize after all managers are ready
} }
@@ -28,7 +28,7 @@ namespace PuzzleS
#region Save/Load Lifecycle Hooks #region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
// Save scene-specific progress // Save scene-specific progress
var state = new BirdGameState var state = new BirdGameState
@@ -38,7 +38,7 @@ namespace PuzzleS
return JsonUtility.ToJson(state); return JsonUtility.ToJson(state);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (string.IsNullOrEmpty(serializedData)) if (string.IsNullOrEmpty(serializedData))
{ {

View File

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

View File

@@ -96,15 +96,13 @@ namespace PuzzleS
public override int ManagedAwakePriority => 80; // Puzzle systems public override int ManagedAwakePriority => 80; // Puzzle systems
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Initialize settings reference // Initialize settings reference
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>(); _interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
@@ -591,7 +589,7 @@ namespace PuzzleS
#region Save/Load Lifecycle Hooks #region Save/Load Lifecycle Hooks
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
if (_currentLevelData == null) if (_currentLevelData == null)
{ {
@@ -611,7 +609,7 @@ namespace PuzzleS
return json; return json;
} }
protected override void OnSceneRestoreRequested(string data) internal override void OnSceneRestoreRequested(string data)
{ {
Logging.Debug("[XAXA] PuzzleManager loading with data: " + data); Logging.Debug("[XAXA] PuzzleManager loading with data: " + data);

View File

@@ -20,15 +20,13 @@ public class AppleAudioSource : ManagedBehaviour
public int sourcePriority; public int sourcePriority;
protected override void Awake() internal override void OnManagedAwake()
{ {
base.Awake();
audioSource = GetComponent<AudioSource>(); audioSource = GetComponent<AudioSource>();
} }
// Start is called once before the first execution of Update after the MonoBehaviour is created
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
audioSource = GetComponent<AudioSource>();
AudioManager.Instance.RegisterNewAudioSource(this); AudioManager.Instance.RegisterNewAudioSource(this);
_audioMixer = AudioManager.Instance.audioMixer; _audioMixer = AudioManager.Instance.audioMixer;
InitializeAudioSource(); InitializeAudioSource();

View File

@@ -44,15 +44,13 @@ public class AudioManager : ManagedBehaviour, IPausable
public override int ManagedAwakePriority => 30; // Audio infrastructure public override int ManagedAwakePriority => 30; // Audio infrastructure
public override bool AutoRegisterPausable => true; // Auto-register as IPausable 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 (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Initialize lists if they were not set in inspector // Initialize lists if they were not set in inspector
criticalVOSources = criticalVOSources ?? new List<AppleAudioSource>(); criticalVOSources = criticalVOSources ?? new List<AppleAudioSource>();
@@ -72,7 +70,7 @@ public class AudioManager : ManagedBehaviour, IPausable
} }
else else
{ {
Logging.Warning("[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 // Diagnostic

View File

@@ -23,7 +23,7 @@ public class BushAudioController : ManagedBehaviour
// Start is called once before the first execution of Update after the MonoBehaviour is created // Start is called once before the first execution of Update after the MonoBehaviour is created
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
_eventSource = VOPlayer.audioSource.RequestEventHandlers(); _eventSource = VOPlayer.audioSource.RequestEventHandlers();
_eventSource.AudioStopped += PlayBirdCounter; _eventSource.AudioStopped += PlayBirdCounter;

View File

@@ -26,7 +26,7 @@ public class LevelAudioObject : ManagedBehaviour
public override bool AutoRegisterForSave => isOneTime; // Only save if one-time audio public override bool AutoRegisterForSave => isOneTime; // Only save if one-time audio
protected override string OnSceneSaveRequested() internal override string OnSceneSaveRequested()
{ {
if (!isOneTime) if (!isOneTime)
return null; // No need to save if not one-time return null; // No need to save if not one-time
@@ -39,7 +39,7 @@ public class LevelAudioObject : ManagedBehaviour
return JsonUtility.ToJson(saveData); return JsonUtility.ToJson(saveData);
} }
protected override void OnSceneRestoreRequested(string serializedData) internal override void OnSceneRestoreRequested(string serializedData)
{ {
if (!isOneTime || string.IsNullOrEmpty(serializedData)) if (!isOneTime || string.IsNullOrEmpty(serializedData))
return; return;
@@ -55,7 +55,7 @@ public class LevelAudioObject : ManagedBehaviour
} }
} }
protected override void OnSceneRestoreCompleted() internal override void OnSceneRestoreCompleted()
{ {
if (isOneTime && !_hasPlayed) if (isOneTime && !_hasPlayed)
{ {

View File

@@ -10,7 +10,7 @@ public class PulverAudioController : ManagedBehaviour
private FollowerController followerController; private FollowerController followerController;
public ItemManager itemManager; public ItemManager itemManager;
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
followerController = GetComponent<FollowerController>(); followerController = GetComponent<FollowerController>();
followerController.PulverIsCombining.AddListener(PulverIsCombining); followerController.PulverIsCombining.AddListener(PulverIsCombining);

View File

@@ -25,7 +25,7 @@ public class AppSwitcher : UIPage
private TweenBase slideInTween; private TweenBase slideInTween;
private TweenBase slideOutTween; private TweenBase slideOutTween;
protected override void OnManagedAwake() internal override void OnManagedAwake()
{ {
base.OnManagedAwake(); base.OnManagedAwake();

View File

@@ -44,10 +44,8 @@ namespace UI.CardSystem
private List<AlbumCardPlacementDraggable> _activeCards = new List<AlbumCardPlacementDraggable>(); private List<AlbumCardPlacementDraggable> _activeCards = new List<AlbumCardPlacementDraggable>();
private const int MAX_VISIBLE_CARDS = 3; private const int MAX_VISIBLE_CARDS = 3;
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
base.OnManagedAwake();
// Discover zone tabs from container // Discover zone tabs from container
DiscoverZoneTabs(); DiscoverZoneTabs();

View File

@@ -40,10 +40,8 @@ namespace UI.CardSystem
private TweenBase _activeTween; private TweenBase _activeTween;
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
base.OnManagedAwake();
// Store original scale for pulse animation // Store original scale for pulse animation
if (dotBackground != null) if (dotBackground != null)
{ {

View File

@@ -34,7 +34,6 @@ namespace UI.CardSystem
[SerializeField] private float cardSpacing = 150f; [SerializeField] private float cardSpacing = 150f;
[Header("Settings")] [Header("Settings")]
[SerializeField] private float cardRevealDelay = 0.5f;
[SerializeField] private float boosterDisappearDuration = 0.5f; [SerializeField] private float boosterDisappearDuration = 0.5f;
[SerializeField] private CinemachineImpulseSource impulseSource; [SerializeField] private CinemachineImpulseSource impulseSource;
[SerializeField] private ParticleSystem openingParticleSystem; [SerializeField] private ParticleSystem openingParticleSystem;
@@ -77,8 +76,10 @@ namespace UI.CardSystem
gameObject.SetActive(false); gameObject.SetActive(false);
} }
private void OnDestroy() protected override void OnDestroy()
{ {
base.OnDestroy();
// Unsubscribe from dismiss button // Unsubscribe from dismiss button
if (_dismissButton != null) if (_dismissButton != null)
{ {

View File

@@ -73,7 +73,7 @@ namespace UI.CardSystem
albumCard.SetParentSlot(this); albumCard.SetParentSlot(this);
// Register with AlbumViewPage for enlarge/shrink handling // Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>(); AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null) if (albumPage != null)
{ {
albumPage.RegisterAlbumCard(albumCard); albumPage.RegisterAlbumCard(albumCard);
@@ -208,7 +208,7 @@ namespace UI.CardSystem
} }
// Register with AlbumViewPage for enlarge/shrink handling // Register with AlbumViewPage for enlarge/shrink handling
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>(); AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null) if (albumPage != null)
{ {
albumPage.RegisterAlbumCard(albumCard); albumPage.RegisterAlbumCard(albumCard);
@@ -284,7 +284,7 @@ namespace UI.CardSystem
previewCardDisplay.transform.localScale = _previewOriginalScale; previewCardDisplay.transform.localScale = _previewOriginalScale;
// Get AlbumViewPage to show backdrop and reparent // Get AlbumViewPage to show backdrop and reparent
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>(); AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null) if (albumPage != null)
{ {
albumPage.ShowSlotPreview(this, previewCardDisplay.transform); albumPage.ShowSlotPreview(this, previewCardDisplay.transform);
@@ -311,7 +311,7 @@ namespace UI.CardSystem
previewCardDisplay.SetPreviewMode(false, null); previewCardDisplay.SetPreviewMode(false, null);
// Get AlbumViewPage to hide backdrop // Get AlbumViewPage to hide backdrop
AlbumViewPage albumPage = FindObjectOfType<AlbumViewPage>(); AlbumViewPage albumPage = FindFirstObjectByType<AlbumViewPage>();
if (albumPage != null) if (albumPage != null)
{ {
albumPage.HideSlotPreview(this, previewCardDisplay.transform, () => albumPage.HideSlotPreview(this, previewCardDisplay.transform, () =>

View File

@@ -41,7 +41,6 @@ namespace UI.CardSystem
// State // State
private bool _isFlipped = false; private bool _isFlipped = false;
private bool _isFlipping = false; private bool _isFlipping = false;
private bool _isHovering = false;
private TweenBase _idleHoverTween; private TweenBase _idleHoverTween;
private CardData _cardData; private CardData _cardData;
private Vector2 _originalPosition; // Track original spawn position private Vector2 _originalPosition; // Track original spawn position
@@ -242,8 +241,6 @@ namespace UI.CardSystem
if (_isFlipped || _isFlipping) if (_isFlipped || _isFlipping)
return; return;
_isHovering = true;
// Scale up slightly on hover // Scale up slightly on hover
Tween.LocalScale(transform, Vector3.one * hoverScaleMultiplier, 0.2f, 0f, Tween.EaseOutBack); Tween.LocalScale(transform, Vector3.one * hoverScaleMultiplier, 0.2f, 0f, Tween.EaseOutBack);
} }
@@ -253,8 +250,6 @@ namespace UI.CardSystem
if (_isFlipped || _isFlipping) if (_isFlipped || _isFlipping)
return; return;
_isHovering = false;
// Scale back to normal // Scale back to normal
Tween.LocalScale(transform, Vector3.one, 0.2f, 0f, Tween.EaseOutBack); Tween.LocalScale(transform, Vector3.one, 0.2f, 0f, Tween.EaseOutBack);
} }

View File

@@ -27,7 +27,6 @@ namespace UI.CardSystem
[Header("Animation Settings")] [Header("Animation Settings")]
[SerializeField] private float hoverAmount = 20f; [SerializeField] private float hoverAmount = 20f;
[SerializeField] private float hoverDuration = 1.5f; [SerializeField] private float hoverDuration = 1.5f;
[SerializeField] private float glowPulseMin = 0.9f;
[SerializeField] private float glowPulseMax = 1.1f; [SerializeField] private float glowPulseMax = 1.1f;
[SerializeField] private float glowPulseDuration = 1.2f; [SerializeField] private float glowPulseDuration = 1.2f;

View File

@@ -39,15 +39,13 @@ namespace UI.Core
public override int ManagedAwakePriority => 50; // UI infrastructure public override int ManagedAwakePriority => 50; // UI infrastructure
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug("[UIPageController] Initialized"); Logging.Debug("[UIPageController] Initialized");
} }

View File

@@ -346,7 +346,7 @@ namespace UI.DragAndDrop.Core
protected virtual void FindAndSnapToSlot() protected virtual void FindAndSnapToSlot()
{ {
SlotContainer[] containers = FindObjectsOfType<SlotContainer>(); SlotContainer[] containers = FindObjectsByType<SlotContainer>(FindObjectsSortMode.None);
DraggableSlot closestSlot = null; DraggableSlot closestSlot = null;
float closestDistance = float.MaxValue; float closestDistance = float.MaxValue;

View File

@@ -56,11 +56,9 @@ namespace UI
// ManagedBehaviour configuration // ManagedBehaviour configuration
public override int ManagedAwakePriority => 45; // UI infrastructure, before UIPageController 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 (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
// Set up container reference early // Set up container reference early
@@ -74,7 +72,7 @@ namespace UI
} }
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
Logging.Debug("[LoadingScreenController] Initialized"); Logging.Debug("[LoadingScreenController] Initialized");
} }

View File

@@ -31,11 +31,9 @@ namespace UI
// After UIPageController (50) // After UIPageController (50)
public override int ManagedAwakePriority => 55; public override int ManagedAwakePriority => 55;
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake(); // CRITICAL: Register with LifecycleManager! // Set instance immediately (early initialization)
// Set instance immediately so it's available before OnManagedAwake() is called
_instance = this; _instance = this;
// Ensure we have a CanvasGroup for transitions // Ensure we have a CanvasGroup for transitions
@@ -51,9 +49,9 @@ namespace UI
gameObject.SetActive(false); 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 // because PauseMenu is in DontDestroyOnLoad and OnSceneReady only fires once
if (SceneManagerService.Instance != null) if (SceneManagerService.Instance != null)
{ {
@@ -73,7 +71,7 @@ namespace UI
Logging.Debug("[PauseMenu] Subscribed to SceneManagerService events"); 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 // This only fires once for DontDestroyOnLoad objects, so we handle scene loads in OnManagedAwake
} }

View File

@@ -115,14 +115,14 @@ namespace UI
private UIPageController _uiPageController; private UIPageController _uiPageController;
private AppSwitcher _appSwitcherComponent; private AppSwitcher _appSwitcherComponent;
private new void Awake() internal override void OnManagedAwake()
{ {
base.Awake();
if (Instance != null) if (Instance != null)
{ {
Destroy(this); Destroy(this);
return; return;
} }
// Set instance immediately (early initialization)
_instance = this; _instance = this;
// Get UIPageController on same GameObject // Get UIPageController on same GameObject
@@ -135,7 +135,7 @@ namespace UI
InitializeReferences(); InitializeReferences();
} }
protected override void OnManagedAwake() internal override void OnManagedStart()
{ {
// Subscribe to UIPageController page changes for auto HUD management // Subscribe to UIPageController page changes for auto HUD management
if (_uiPageController != null) if (_uiPageController != null)

View File

@@ -32,7 +32,7 @@ namespace UI.Tutorial
public override int ManagedAwakePriority => 200; // Tutorial runs late, after other systems 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) // Ensure prompt is hidden initially (even before tutorial initialization)
if (tapPrompt != null) if (tapPrompt != null)

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB