Compare commits

..

47 Commits

Author SHA1 Message Date
Michal Pikulski
32ecbf4dc4 Add docs to the logger 2025-11-11 09:48:00 +01:00
Michal Pikulski
4dfe6202fd Update logging calls to new system 2025-11-11 09:00:05 +01:00
Michal Pikulski
f8805dabe7 Custom logger 2025-11-11 08:44:15 +01:00
Michal Pikulski
961da5e729 Fix deprecation and other warnings 2025-11-10 23:18:01 +01:00
Michal Pikulski
a049c6a750 Update methods to be internal, remove invocation bloat 2025-11-10 21:59:47 +01:00
Michal Pikulski
01caca1878 Add docs and clear the zero-width-backspace-formatting 2025-11-10 15:56:30 +01:00
Michal Pikulski
7565b189b9 Add distinction between managed awake and managed start 2025-11-10 15:52:53 +01:00
Michal Pikulski
c4d356886f Dumb fix for data clearing 2025-11-10 14:27:14 +01:00
Michal Pikulski
64d7f19b83 ADd clearing saves 2025-11-10 14:11:02 +01:00
ce21e8b02e Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 13:13:36 +01:00
f44f8c5171 Made intro VO play in diving minigame 2025-11-10 13:13:32 +01:00
9772206362 Merge pull request 'Clean up logging' (#55) from clean_up_logging into main
Reviewed-on: #55
2025-11-10 12:06:05 +00:00
Michal Pikulski
3ebbecc277 Clean up logging 2025-11-10 13:03:36 +01:00
Michal Pikulski
c99aad49f3 Save bird states 2025-11-10 12:55:27 +01:00
3fe4c6afd9 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 12:53:07 +01:00
9c61065947 Fixed nullref in TakePhotoState 2025-11-10 12:53:03 +01:00
Michal Pikulski
7c09db641a Only show cards when in actual album 2025-11-10 12:41:28 +01:00
Michal Pikulski
e369660a8f Fix tab navigation on card select less wonky 2025-11-10 12:29:25 +01:00
a6e3413499 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 12:19:25 +01:00
7bb992acb8 Made apple audio sources use OnManagedAwake 2025-11-10 12:19:20 +01:00
Michal Pikulski
cefa488a92 Update issues with card appearing twice after reopening page 2025-11-10 12:17:17 +01:00
9344f06886 Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-10 11:37:53 +01:00
af7e081c9a Fixed Chocolate Puzzle, but does not work well on load (the destroyed chocolate appears again) 2025-11-10 11:37:45 +01:00
Michal Pikulski
4a6ac7281f Udate the booster page. New card text smaller, moved down. Album icon disappears when opening boosters. 2025-11-10 11:28:23 +01:00
861797ba41 Merge pull request 'DamianBranch' (#54) from DamianBranch into main
Reviewed-on: #54
2025-11-10 10:10:40 +00:00
98883bd382 Merge branch 'main' into DamianBranch 2025-11-10 11:10:14 +01:00
474941f421 Reffitting of the scrapbook 2025-11-10 11:10:04 +01:00
Michal Pikulski
75cd70a18a Play intro audio only once 2025-11-10 11:09:06 +01:00
e82ec90723 WIP Scrapbook rezising 2025-11-10 10:47:00 +01:00
Michal Pikulski
252cb99884 Update icons in minigame 2025-11-10 10:41:38 +01:00
3f548c3ed4 Update HUD updates, moving scattered prefabs into a central HUD manager (#53)
Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Reviewed-on: #53
2025-11-09 21:41:39 +00:00
Michal Pikulski
0c9a388433 Move buttons to HUD Manager 2025-11-09 13:23:03 +01:00
MacBuilder
a80aed8eb7 Make app switcher disappear when opening album 2025-11-09 00:20:48 +01:00
journaliciouz
5d0a9f999a Aligned card slots better in book 2025-11-08 22:46:16 +01:00
journaliciouz
eda7361702 Fixed most horrifying scaling issues with the book 2025-11-08 22:16:43 +01:00
journaliciouz
2ec53629c6 Added contextual UI switching depending on level type 2025-11-08 21:08:01 +01:00
journaliciouz
0e55248698 Fixed weird background breaks in outrocinematic 2025-11-08 19:56:59 +01:00
Michal Pikulski
4d7c48a681 Update Cinematic Manager, Hud Manager, fix log verbosity issue, update PauseMenu 2025-11-08 12:07:45 +01:00
Michal Pikulski
6e466cd7aa Add dev options ot the minigame menu 2025-11-07 17:50:46 +01:00
29d01df3ce Merge branch 'main' of https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction 2025-11-07 17:37:17 +01:00
de0a243b1e Added eagle eye button 2025-11-07 17:37:12 +01:00
fdfa4e0e09 Fixed a problem with buttons not rezising in the End game UI for the Minigame 2025-11-07 17:31:46 +01:00
Michal Pikulski
2127bf37b2 Add boosters to minigame again 2025-11-07 17:26:29 +01:00
ea12766cf7 Fixed button issue 2025-11-07 17:21:38 +01:00
41c71f07fd Fixed compile error 2025-11-07 17:08:02 +01:00
d2e79f058b Merge branch 'ui-overhaul' 2025-11-07 16:56:51 +01:00
e27bb7bfb6 Refactor interactions, introduce template-method lifecycle management, work on save-load system (#51)
# Lifecycle Management & Save System Revamp

## Overview
Complete overhaul of game lifecycle management, interactable system, and save/load architecture. Introduces centralized `ManagedBehaviour` base class for consistent initialization ordering and lifecycle hooks across all systems.

## Core Architecture

### New Lifecycle System
- **`LifecycleManager`**: Centralized coordinator for all managed objects
- **`ManagedBehaviour`**: Base class replacing ad-hoc initialization patterns
  - `OnManagedAwake()`: Priority-based initialization (0-100, lower = earlier)
  - `OnSceneReady()`: Scene-specific setup after managers ready
  - Replaces `BootCompletionService` (deleted)
- **Priority groups**: Infrastructure (0-20) → Game Systems (30-50) → Data (60-80) → UI/Gameplay (90-100)
- **Editor support**: `EditorLifecycleBootstrap` ensures lifecycle works in editor mode

### Unified SaveID System
- Consistent format: `{ParentName}_{ComponentType}`
- Auto-registration via `AutoRegisterForSave = true`
- New `DebugSaveIds` editor tool for inspection

## Save/Load Improvements

### Enhanced State Management
- **Extended SaveLoadData**: Unlocked minigames, card collection states, combination items, slot occupancy
- **Async loading**: `ApplyCardCollectionState()` waits for card definitions before restoring
- **New `SaveablePlayableDirector`**: Timeline sequences save/restore playback state
- **Fixed race conditions**: Proper initialization ordering prevents data corruption

## Interactable & Pickup System

- Migrated to `OnManagedAwake()` for consistent initialization
- Template method pattern for state restoration (`RestoreInteractionState()`)
- Fixed combination item save/load bugs (items in slots vs. follower hand)
- Dynamic spawning support for combined items on load
- **Breaking**: `Interactable.Awake()` now sealed, use `OnManagedAwake()` instead

##  UI System Changes

- **AlbumViewPage** and **BoosterNotificationDot**: Migrated to `ManagedBehaviour`
- **Fixed menu persistence bug**: Menus no longer reappear after scene transitions
- **Pause Menu**: Now reacts to all scene loads (not just first scene)
- **Orientation Enforcer**: Enforces per-scene via `SceneManagementService`
- **Loading Screen**: Integrated with new lifecycle

## ⚠️ Breaking Changes

1. **`BootCompletionService` removed** → Use `ManagedBehaviour.OnManagedAwake()` with priority
2. **`Interactable.Awake()` sealed** → Override `OnManagedAwake()` instead
3. **SaveID format changed** → Now `{ParentName}_{ComponentType}` consistently
4. **MonoBehaviours needing init ordering** → Must inherit from `ManagedBehaviour`

Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com>
Reviewed-on: #51
2025-11-07 15:38:31 +00:00
155 changed files with 22571 additions and 17286 deletions

View File

@@ -1,5 +0,0 @@
You are an expert code architect and developer. YOu prioritize clean, efficient, and maintainable code.
You priotize up-front though out planning before writing code.
You will always present implementaiton plan first and always ask for permission to implement it.
Never insert zero-width spaces or non-breaking spaces in my code.
DOn't produce .MD documentation unless i ask you to.

View File

@@ -15,7 +15,7 @@ MonoBehaviour:
m_DefaultGroup: 6f3207429a65b3e4b83935ac19791077
m_currentHash:
serializedVersion: 2
Hash: f08b6489862aaf7bfceb29f571e2ef6c
Hash: 589e22fe6cd2fe0e25d89bd44a35bcbc
m_OptimizeCatalogSize: 0
m_BuildRemoteCatalog: 0
m_CatalogRequestsTimeout: 0

View File

@@ -122,6 +122,32 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:

View File

@@ -122,6 +122,32 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:

View File

@@ -18,13 +18,10 @@ MonoBehaviour:
- {fileID: 458265635552197097, guid: a77d1e8b2fa8aa945a6f39b312536e0d, type: 3}
- {fileID: 552225285624929822, guid: e39992796d5459442be9967c77e27066, type: 3}
- {fileID: 7644433920135100480, guid: 12d242e44fe80ab44af852254b7cab0f, type: 3}
- {fileID: 1794231825201849485, guid: 6fb0d7fc6faad154b8c3e3cb7abb7c15, type: 3}
- {fileID: 6399527186463168339, guid: ac41583865245bc4bb3de6c15929b9fc, type: 3}
- {fileID: 2755712733105741372, guid: 70e6fca1164d9a140b271f4261f1f023, type: 3}
- {fileID: 5034240524438268576, guid: adbb9bfd5489f3f4995966535ca5f24b, type: 3}
- {fileID: 2326026072467672024, guid: c8d9eb8c3ca524b4eb67f6364b455b87, type: 3}
- {fileID: 3528960956969533010, guid: 53eea3840d3cde34a9768b8773a3a7e8, type: 3}
- {fileID: 9076822654798104907, guid: 4684df63af6398f4f9f624a35023f8d2, type: 3}
- {fileID: 3863019143023165617, guid: 774e30e3f0b1d0d49bad0c2abf11038a, type: 3}
- {fileID: 5034240524438268576, guid: b15ba9d3d508ef244b0eeb76404dc9de, type: 3}
- {fileID: 7207007194116694737, guid: 7180ae585f0db8044ba048426f72d995, type: 3}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c5d626da49844592981ef14524e3a308
timeCreated: 1762332131

View File

@@ -0,0 +1,144 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using Core.Lifecycle;
using Core.SaveLoad;
using AppleHills.Core.Settings;
using Bootstrap;
namespace Editor.Lifecycle
{
/// <summary>
/// Editor-only bootstrap that ensures OnSceneReady is triggered when playing directly from a scene in Unity Editor.
///
/// PROBLEM: When you press Play in the editor without going through the scene manager:
/// - CustomBoot runs and triggers OnBootCompletionTriggered (which broadcasts OnManagedStart)
/// - But BroadcastSceneReady is NEVER called for the initial scene
/// - Components in the scene never receive their OnSceneReady() callback
///
/// SOLUTION: After boot completes, detect the active scene and broadcast OnSceneReady for it.
/// This only runs in editor mode and mimics what SceneManagerService does during normal scene transitions.
/// </summary>
[InitializeOnLoad]
public static class EditorLifecycleBootstrap
{
private static bool hasTriggeredInitialSceneReady = false;
private static int framesSincePlayMode = 0;
private const int MaxFramesToWait = 300; // 5 seconds at 60fps
static EditorLifecycleBootstrap()
{
// Subscribe to play mode state changes
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
// Reset flag when exiting play mode
if (state == PlayModeStateChange.ExitingPlayMode || state == PlayModeStateChange.EnteredEditMode)
{
hasTriggeredInitialSceneReady = false;
framesSincePlayMode = 0;
return;
}
// When we enter play mode, wait for boot to complete then trigger scene ready
if (state == PlayModeStateChange.EnteredPlayMode)
{
hasTriggeredInitialSceneReady = false;
framesSincePlayMode = 0;
// Use EditorApplication.update to poll until boot completes
EditorApplication.update += WaitForBootAndTriggerSceneReady;
}
}
private static void WaitForBootAndTriggerSceneReady()
{
framesSincePlayMode++;
// Safety timeout - if boot hasn't completed after 5 seconds, something is wrong
if (framesSincePlayMode > MaxFramesToWait)
{
Debug.LogError($"[EditorLifecycleBootstrap] Timed out waiting for boot completion after {MaxFramesToWait} frames. " +
"CustomBoot may have failed to initialize properly.");
EditorApplication.update -= WaitForBootAndTriggerSceneReady;
return;
}
// Check if boot has completed
if (!CustomBoot.Initialised)
return;
// Check if LifecycleManager exists
if (LifecycleManager.Instance == null)
{
Debug.LogWarning("[EditorLifecycleBootstrap] LifecycleManager instance not found. " +
"Lifecycle may not be properly initialized.");
EditorApplication.update -= WaitForBootAndTriggerSceneReady;
return;
}
// Only trigger once per play session
if (hasTriggeredInitialSceneReady)
{
EditorApplication.update -= WaitForBootAndTriggerSceneReady;
return;
}
hasTriggeredInitialSceneReady = true;
EditorApplication.update -= WaitForBootAndTriggerSceneReady;
// Get the active scene
Scene activeScene = SceneManager.GetActiveScene();
if (!activeScene.isLoaded)
{
Debug.LogWarning($"[EditorLifecycleBootstrap] Active scene '{activeScene.name}' is not loaded.");
return;
}
// Skip bootstrap scene - it doesn't need scene ready
// Note: BootstrapScene is the infrastructure scene, not a gameplay scene
if (activeScene.name == "BootstrapScene" || activeScene.name == "Bootstrap")
{
Debug.Log($"[EditorLifecycleBootstrap] Skipping OnSceneReady for infrastructure scene: {activeScene.name}");
return;
}
Debug.Log($"<color=cyan>[EditorLifecycleBootstrap] Triggering lifecycle for initial scene: {activeScene.name}</color>");
// Broadcast scene ready for the initial scene
// This mimics what SceneManagerService does during scene transitions (Phase 10)
try
{
LifecycleManager.Instance.BroadcastSceneReady(activeScene.name);
}
catch (System.Exception ex)
{
Debug.LogError($"[EditorLifecycleBootstrap] Error broadcasting SceneReady: {ex.Message}\n{ex.StackTrace}");
return;
}
// Restore scene-specific data via SaveLoadManager
// This mimics SceneManagerService Phase 11
if (SaveLoadManager.Instance != null)
{
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
if (debugSettings.useSaveLoadSystem)
{
try
{
Debug.Log($"[EditorLifecycleBootstrap] Restoring scene data for: {activeScene.name}");
SaveLoadManager.Instance.RestoreSceneData();
}
catch (System.Exception ex)
{
Debug.LogError($"[EditorLifecycleBootstrap] Error restoring scene data: {ex.Message}\n{ex.StackTrace}");
}
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 7f3e8a9c4d5b6e7f8a9b0c1d2e3f4a5b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -19,6 +19,7 @@ namespace Editor.Tools
private string searchTypeName = "Select a Component...";
private string replaceTypeName = "Select a Component...";
private List<Type> allMonoBehaviourTypes = new List<Type>();
private bool includeDerivedTypes = true;
[MenuItem("Tools/Component Search & Replace")]
public static void ShowWindow()
@@ -102,6 +103,15 @@ namespace Editor.Tools
GUILayout.Space(5);
// Include Derived Types checkbox
includeDerivedTypes = EditorGUILayout.Toggle(
new GUIContent("Include Derived Types",
"When enabled, searches for the selected type and all types that inherit from it. " +
"When disabled, searches only for the exact type."),
includeDerivedTypes);
GUILayout.Space(5);
EditorGUI.BeginDisabledGroup(selectedSearchType == null);
if (GUILayout.Button("Search Scene", GUILayout.Height(30)))
{
@@ -242,7 +252,20 @@ namespace Editor.Tools
foreach (var go in allObjects)
{
var component = go.GetComponent(selectedSearchType);
Component component = null;
if (includeDerivedTypes)
{
// Search for the type and all derived types
component = go.GetComponent(selectedSearchType);
}
else
{
// Search for exact type only
var components = go.GetComponents<Component>();
component = components.FirstOrDefault(c => c != null && c.GetType() == selectedSearchType);
}
if (component != null)
{
foundComponents.Add(new ComponentInfo
@@ -256,7 +279,8 @@ namespace Editor.Tools
foundComponents = foundComponents.OrderBy(c => c.hierarchyPath).ToList();
Debug.Log($"Found {foundComponents.Count} objects with component type '{selectedSearchType.Name}'");
string searchMode = includeDerivedTypes ? "including derived types" : "exact type only";
Debug.Log($"Found {foundComponents.Count} objects with component type '{selectedSearchType.Name}' ({searchMode})");
Repaint();
}

View File

@@ -0,0 +1,31 @@
using UnityEngine;
using UnityEditor;
using Core.Lifecycle;
namespace Editor.Tools
{
/// <summary>
/// Editor utility to debug SaveIds for all ManagedBehaviours in the scene
/// </summary>
public class DebugSaveIds : EditorWindow
{
[MenuItem("Tools/Debug/Log All SaveIds")]
public static void LogAllSaveIds()
{
var allManaged = FindObjectsByType<ManagedBehaviour>(FindObjectsInactive.Include, FindObjectsSortMode.None);
Debug.Log($"=== Found {allManaged.Length} ManagedBehaviours ===");
foreach (var managed in allManaged)
{
if (managed.AutoRegisterForSave)
{
Debug.Log($"GameObject: {managed.gameObject.name} | Component: {managed.GetType().Name} | SaveID: {managed.SaveId}");
}
}
Debug.Log("=== End SaveIds ===");
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a34fbba4efbb4acd85d79a99abf00a08
timeCreated: 1762358959

View File

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

View File

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

View File

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

View File

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

View File

@@ -1978,11 +1978,11 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 3484825090253933040, guid: a8b0a1c6cf21352439dc24d3b03182db, type: 3}
propertyPath: m_AnchoredPosition.x
value: 1.85
value: 0.09
objectReference: {fileID: 0}
- target: {fileID: 3484825090253933040, guid: a8b0a1c6cf21352439dc24d3b03182db, type: 3}
propertyPath: m_AnchoredPosition.y
value: 5.14
value: 3.44
objectReference: {fileID: 0}
- target: {fileID: 3484825090253933040, guid: a8b0a1c6cf21352439dc24d3b03182db, type: 3}
propertyPath: m_LocalEulerAnglesHint.x

View File

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

View File

@@ -105,7 +105,7 @@ GameObject:
- component: {fileID: 3487003259787903584}
- component: {fileID: 2277261512137882881}
m_Layer: 10
m_Name: LureSpotA
m_Name: LureSpotA_Slot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -260,9 +260,9 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: aaf36cd26cf74334e9c7db6c1b03b3fb, type: 2}
iconRenderer: {fileID: 6258593095132504700}
slottedItemRenderer: {fileID: 4110666412151536905}
onItemSlotted:
m_PersistentCalls:
m_Calls: []
@@ -314,7 +314,6 @@ MonoBehaviour:
onForbiddenItemSlotted:
m_PersistentCalls:
m_Calls: []
slottedItemRenderer: {fileID: 4110666412151536905}
--- !u!114 &3487003259787903584
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@@ -1069,7 +1069,7 @@ GameObject:
- component: {fileID: 3093816592344978065}
- component: {fileID: 8758136668472096799}
m_Layer: 10
m_Name: LureSpotB
m_Name: LureSpotB_Slot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -1168,9 +1168,9 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: f97b9e24d6dceb145b56426c1152ebeb, type: 2}
iconRenderer: {fileID: 2343214996212089369}
slottedItemRenderer: {fileID: 7990414055343410434}
onItemSlotted:
m_PersistentCalls:
m_Calls: []
@@ -1234,7 +1234,6 @@ MonoBehaviour:
onForbiddenItemSlotted:
m_PersistentCalls:
m_Calls: []
slottedItemRenderer: {fileID: 7990414055343410434}
--- !u!114 &8758136668472096799
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@@ -247,7 +247,7 @@ GameObject:
- component: {fileID: 3169137887822749614}
- component: {fileID: 8370367816617117734}
m_Layer: 10
m_Name: LureSpotC
m_Name: LureSpotC_Slot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -346,9 +346,9 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: c68dea945fecbf44094359769db04f31, type: 2}
iconRenderer: {fileID: 2825253017896168654}
slottedItemRenderer: {fileID: 3806274462998212361}
onItemSlotted:
m_PersistentCalls:
m_Calls: []
@@ -412,7 +412,6 @@ MonoBehaviour:
onForbiddenItemSlotted:
m_PersistentCalls:
m_Calls: []
slottedItemRenderer: {fileID: 3806274462998212361}
--- !u!114 &6535246856440349519
MonoBehaviour:
m_ObjectHideFlags: 0

View File

@@ -44,10 +44,10 @@ GameObject:
- component: {fileID: 5057760771402457000}
- component: {fileID: 2433130051631076285}
- component: {fileID: 7290110366808972859}
- component: {fileID: 4831635791684479552}
- component: {fileID: 9196152289301358918}
- component: {fileID: 2596311128101197840}
m_Layer: 10
m_Name: SoundBird
m_Name: SoundBird_Slot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -201,9 +201,9 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: d28f5774afad9d14f823601707150700, type: 2}
iconRenderer: {fileID: 8875860401447896107}
slottedItemRenderer: {fileID: 6941190210788968874}
onItemSlotted:
m_PersistentCalls:
m_Calls: []
@@ -231,7 +231,6 @@ MonoBehaviour:
onForbiddenItemSlotted:
m_PersistentCalls:
m_Calls: []
slottedItemRenderer: {fileID: 6941190210788968874}
--- !u!114 &7290110366808972859
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -246,18 +245,6 @@ MonoBehaviour:
m_EditorClassIdentifier:
luredBird: {fileID: 4624889622840393752}
annaLiseSpot: {fileID: 22512726373136855}
--- !u!114 &4831635791684479552
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 588897581313790951}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: eaefd3d5a2a864ca5b5d9ec5f2a7040f, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!95 &9196152289301358918
Animator:
serializedVersion: 7
@@ -280,6 +267,18 @@ Animator:
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!114 &2596311128101197840
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 588897581313790951}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 95e46aacea5b42888ee7881894193c11, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleState
--- !u!1 &4624889622840393752
GameObject:
m_ObjectHideFlags: 0

View File

@@ -166,7 +166,6 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: 0c6986639ca176a419c92f5a327d95ce, type: 2}
iconRenderer: {fileID: 7494677664706785084}
--- !u!1001 &8589202998731622905

View File

@@ -140,6 +140,5 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: 43f22dbbb4c0eec4f8108d0f0eea43c2, type: 2}
iconRenderer: {fileID: 4055726361761331703}

View File

@@ -140,6 +140,5 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: a8baa800efa25a344a95b190cf349e2d, type: 2}
iconRenderer: {fileID: 4774534086162962138}

View File

@@ -140,6 +140,5 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: 560ba2059ce14dc4da580e2f43b2e65f, type: 2}
iconRenderer: {fileID: 4986096986936361008}

View File

@@ -140,6 +140,5 @@ MonoBehaviour:
interactionComplete:
m_PersistentCalls:
m_Calls: []
customSaveId:
itemData: {fileID: 11400000, guid: 3b1f3472171abc943bb099ce31d6fc7c, type: 2}
iconRenderer: {fileID: 4266110216568578813}

View File

@@ -11,7 +11,6 @@ GameObject:
- component: {fileID: 1965178107275650778}
- component: {fileID: 2828019556092814789}
- component: {fileID: 3751190220360366860}
- component: {fileID: 803378854493527400}
m_Layer: 0
m_Name: SpilSlut
m_TagString: Untagged
@@ -93,7 +92,7 @@ MonoBehaviour:
m_faceColor:
serializedVersion: 2
rgba: 4294967295
m_fontSize: 136.55
m_fontSize: 136.85
m_fontSizeBase: 72
m_fontWeight: 400
m_enableAutoSizing: 1
@@ -409,9 +408,9 @@ RectTransform:
m_Children: []
m_Father: {fileID: 754661265897109340}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 683.9956, y: -157.62965}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 640.62054, y: 0}
m_SizeDelta: {x: 268, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &1768576830901014477
@@ -974,15 +973,15 @@ RectTransform:
m_GameObject: {fileID: 3935369085067290987}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_LocalScale: {x: 3, y: 3, z: 3}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 754661265897109340}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 174.99854, y: -157.62965}
m_SizeDelta: {x: 150, y: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 270.45056, y: 0}
m_SizeDelta: {x: 42, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &2782310018074465201
CanvasRenderer:
@@ -1121,15 +1120,15 @@ RectTransform:
m_GameObject: {fileID: 4068599104538777648}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_LocalScale: {x: 3, y: 3, z: 3}
m_ConstrainProportionsScale: 1
m_Children: []
m_Father: {fileID: 754661265897109340}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 1192.9927, y: -157.62965}
m_SizeDelta: {x: 150, y: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 1010.7905, y: 0}
m_SizeDelta: {x: 42, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8344156654954553365
CanvasRenderer:
@@ -1249,7 +1248,6 @@ GameObject:
m_Component:
- component: {fileID: 754661265897109340}
- component: {fileID: 7750609879083104855}
- component: {fileID: 5441545775684538830}
m_Layer: 0
m_Name: Buttons
m_TagString: Untagged
@@ -1276,8 +1274,8 @@ RectTransform:
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.10938991, y: 0.11800001}
m_AnchorMax: {x: 0.94400007, y: 0.41016972}
m_AnchoredPosition: {x: 0, y: 4}
m_SizeDelta: {x: 0, y: -4}
m_AnchoredPosition: {x: -0.00024414062, y: 0}
m_SizeDelta: {x: -86.75, y: -20.246}
m_Pivot: {x: 0.5, y: 0}
--- !u!114 &7750609879083104855
MonoBehaviour:
@@ -1297,11 +1295,11 @@ MonoBehaviour:
m_Top: 0
m_Bottom: 0
m_ChildAlignment: 4
m_Spacing: 100
m_ChildForceExpandWidth: 1
m_ChildForceExpandHeight: 1
m_ChildControlWidth: 0
m_ChildControlHeight: 0
m_Spacing: 215.17
m_ChildForceExpandWidth: 0
m_ChildForceExpandHeight: 0
m_ChildControlWidth: 1
m_ChildControlHeight: 1
m_ChildScaleWidth: 0
m_ChildScaleHeight: 0
m_ReverseArrangement: 0

File diff suppressed because it is too large Load Diff

View File

@@ -221,12 +221,12 @@ GameObject:
serializedVersion: 6
m_Component:
- component: {fileID: 2071071585578300598}
- component: {fileID: 1454372124634854912}
- component: {fileID: 4122067414526815177}
- component: {fileID: 2314863751758196186}
- component: {fileID: 2741639361616064442}
- component: {fileID: 4903273501345439385}
- component: {fileID: 1054459649399154791}
- component: {fileID: 7319925080429004531}
m_Layer: 10
m_Name: Hidden
m_TagString: Untagged
@@ -252,18 +252,6 @@ Transform:
- {fileID: 852327051512792946}
m_Father: {fileID: 8259693476957892150}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1454372124634854912
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1011363502278351410}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: eaefd3d5a2a864ca5b5d9ec5f2a7040f, type: 3}
m_Name:
m_EditorClassIdentifier: PixelplacementAssembly::Pixelplacement.State
--- !u!61 &4122067414526815177
BoxCollider2D:
m_ObjectHideFlags: 0
@@ -463,6 +451,18 @@ MonoBehaviour:
audioSource: {fileID: 0}
clipPriority: 0
sourcePriority: 1
--- !u!114 &7319925080429004531
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1011363502278351410}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 95e46aacea5b42888ee7881894193c11, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleState
--- !u!1 &1674229500073894281
GameObject:
m_ObjectHideFlags: 0
@@ -777,11 +777,11 @@ GameObject:
m_Component:
- component: {fileID: 8259693476957892150}
- component: {fileID: 2995561023563842343}
- component: {fileID: 7053055077639234121}
- component: {fileID: 578146208477020881}
- component: {fileID: 1193493154550576580}
- component: {fileID: 7652960462502122104}
- component: {fileID: 989520896849684110}
- component: {fileID: 5862718108034728596}
m_Layer: 0
m_Name: AnneLiseBaseBush
m_TagString: Untagged
@@ -818,42 +818,6 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 55938fb1577dd4ad3af7e994048c86f6, type: 3}
m_Name:
m_EditorClassIdentifier: PixelplacementAssembly::Pixelplacement.Initialization
--- !u!114 &7053055077639234121
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5943355783477523754}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9e0b24e2f2ad54cc09940c320ed3cf4b, type: 3}
m_Name:
m_EditorClassIdentifier: PixelplacementAssembly::Pixelplacement.StateMachine
defaultState: {fileID: 1011363502278351410}
currentState: {fileID: 0}
_unityEventsFolded: 0
verbose: 0
allowReentry: 0
returnToDefaultOnDisable: 1
OnStateExited:
m_PersistentCalls:
m_Calls: []
OnStateEntered:
m_PersistentCalls:
m_Calls: []
OnFirstStateEntered:
m_PersistentCalls:
m_Calls: []
OnFirstStateExited:
m_PersistentCalls:
m_Calls: []
OnLastStateEntered:
m_PersistentCalls:
m_Calls: []
OnLastStateExited:
m_PersistentCalls:
m_Calls: []
--- !u!114 &578146208477020881
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -1001,6 +965,43 @@ MonoBehaviour:
audioSource: {fileID: 0}
clipPriority: 0
sourcePriority: 0
--- !u!114 &5862718108034728596
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5943355783477523754}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6f56763d30b94bf6873d395a6c116eb5, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleMachine
defaultState: {fileID: 1011363502278351410}
currentState: {fileID: 0}
_unityEventsFolded: 0
verbose: 0
allowReentry: 0
returnToDefaultOnDisable: 1
OnStateExited:
m_PersistentCalls:
m_Calls: []
OnStateEntered:
m_PersistentCalls:
m_Calls: []
OnFirstStateEntered:
m_PersistentCalls:
m_Calls: []
OnFirstStateExited:
m_PersistentCalls:
m_Calls: []
OnLastStateEntered:
m_PersistentCalls:
m_Calls: []
OnLastStateExited:
m_PersistentCalls:
m_Calls: []
customSaveId:
--- !u!1 &6948354193133336628
GameObject:
m_ObjectHideFlags: 0

View File

@@ -11,7 +11,7 @@ GameObject:
- component: {fileID: 2326086342663433936}
- component: {fileID: 243176356944356711}
- component: {fileID: 6657093817085841540}
- component: {fileID: 7932498922414502976}
- component: {fileID: 2239999147194587249}
m_Layer: 0
m_Name: BirdEyes
m_TagString: Untagged
@@ -48,6 +48,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 13d59d3c42170824b8f92557822d9bf0, type: 3}
m_Name:
m_EditorClassIdentifier:
correctItemIsIn: 0
bushAnimator: {fileID: 0}
--- !u!114 &6657093817085841540
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -60,7 +62,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 55938fb1577dd4ad3af7e994048c86f6, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &7932498922414502976
--- !u!114 &2239999147194587249
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -69,9 +71,9 @@ MonoBehaviour:
m_GameObject: {fileID: 1370564349707122423}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9e0b24e2f2ad54cc09940c320ed3cf4b, type: 3}
m_Script: {fileID: 11500000, guid: 6f56763d30b94bf6873d395a6c116eb5, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleMachine
defaultState: {fileID: 3532512445619884959}
currentState: {fileID: 0}
_unityEventsFolded: 0
@@ -96,6 +98,7 @@ MonoBehaviour:
OnLastStateExited:
m_PersistentCalls:
m_Calls: []
customSaveId:
--- !u!1 &3532512445619884959
GameObject:
m_ObjectHideFlags: 0
@@ -107,7 +110,7 @@ GameObject:
- component: {fileID: 4477179922705334961}
- component: {fileID: 3013218424693156287}
- component: {fileID: 7343439013600968102}
- component: {fileID: 3842054004304041864}
- component: {fileID: 4451815010323250894}
m_Layer: 0
m_Name: BirdHiding
m_TagString: Untagged
@@ -150,6 +153,8 @@ SpriteRenderer:
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -171,6 +176,7 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
@@ -207,7 +213,7 @@ Animator:
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!114 &3842054004304041864
--- !u!114 &4451815010323250894
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -216,9 +222,9 @@ MonoBehaviour:
m_GameObject: {fileID: 3532512445619884959}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: eaefd3d5a2a864ca5b5d9ec5f2a7040f, type: 3}
m_Script: {fileID: 11500000, guid: 95e46aacea5b42888ee7881894193c11, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleState
--- !u!1 &8828658103663197825
GameObject:
m_ObjectHideFlags: 0
@@ -230,7 +236,7 @@ GameObject:
- component: {fileID: 7698905571408300091}
- component: {fileID: 5210033153524231666}
- component: {fileID: 4408373410605328204}
- component: {fileID: 3873868413538144635}
- component: {fileID: 2709364368411520279}
m_Layer: 0
m_Name: BirdSpawned
m_TagString: Untagged
@@ -273,6 +279,8 @@ SpriteRenderer:
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@@ -294,6 +302,7 @@ SpriteRenderer:
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 2
@@ -330,7 +339,7 @@ Animator:
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!114 &3873868413538144635
--- !u!114 &2709364368411520279
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -339,6 +348,6 @@ MonoBehaviour:
m_GameObject: {fileID: 8828658103663197825}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: eaefd3d5a2a864ca5b5d9ec5f2a7040f, type: 3}
m_Script: {fileID: 11500000, guid: 95e46aacea5b42888ee7881894193c11, type: 3}
m_Name:
m_EditorClassIdentifier:
m_EditorClassIdentifier: AppleHillsScripts::Core.SaveLoad.AppleState

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 774e30e3f0b1d0d49bad0c2abf11038a
guid: 97f767ded753d524086106f3c39a645f
PrefabImporter:
externalObjects: {}
userData:

View File

@@ -135,13 +135,13 @@ AnimatorStateMachine:
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: -7925442279578022247}
m_Position: {x: 400, y: 370, z: 0}
m_Position: {x: 410, y: 370, z: 0}
- serializedVersion: 1
m_State: {fileID: -892202757677891130}
m_Position: {x: -20, y: 260, z: 0}
- serializedVersion: 1
m_State: {fileID: 1937208649765278840}
m_Position: {x: 489.1548, y: 156.18289, z: 0}
m_Position: {x: 490, y: 150, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []

View File

@@ -120,6 +120,44 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &879012544333199198
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 8293076336493130222}
m_Layer: 5
m_Name: Rainbows
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &8293076336493130222
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 879012544333199198}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6108475066390421500}
- {fileID: 6717870941799174515}
- {fileID: 4136733570236406132}
m_Father: {fileID: 1315170081792486277}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &904161782565348054
GameObject:
m_ObjectHideFlags: 0
@@ -166,7 +204,7 @@ RectTransform:
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 200}
m_SizeDelta: {x: 1550, y: 700}
m_SizeDelta: {x: 1550, y: 750}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!223 &4291423916360628965
Canvas:
@@ -252,7 +290,7 @@ MonoBehaviour:
m_StartCorner: 0
m_StartAxis: 0
m_CellSize: {x: 350, y: 350}
m_Spacing: {x: 50, y: 0}
m_Spacing: {x: 50, y: 50}
m_Constraint: 2
m_ConstraintCount: 2
--- !u!114 &7425566603516801919
@@ -281,7 +319,6 @@ GameObject:
- component: {fileID: 6669614972729775195}
- component: {fileID: 2981106092574900430}
- component: {fileID: 8074691980395114238}
- component: {fileID: 1630362919770549177}
m_Layer: 5
m_Name: AppSwitcher
m_TagString: Untagged
@@ -301,8 +338,7 @@ RectTransform:
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 6108475066390421500}
- {fileID: 1495118611075801417}
- {fileID: 8293076336493130222}
- {fileID: 808554455652734252}
- {fileID: 2831878373711017175}
m_Father: {fileID: 0}
@@ -364,36 +400,14 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: bac6124b3ada8a048a0b87a729a22312, type: 3}
m_Name:
m_EditorClassIdentifier: '::'
iconIdle: {fileID: 4900000, guid: 50e22b5bb8a496840952f2563758c13c, type: 3}
iconEstablish: {fileID: 4900000, guid: 975218623da47f8428c484d886554a6c, type: 3}
rainbowEstablish: {fileID: 4900000, guid: 622be2ef9d5e27d45a9deaf7ed805f5f, type: 3}
rainbowRemove: {fileID: 4900000, guid: 589505308c5daf449800f30dd4b92ce7, type: 3}
rainbow: {fileID: 5382650426034128680}
icon: {fileID: 6784053660381911346}
PageName:
transitionDuration: 0.3
rainbowIn: {fileID: 5382650426034128680}
rainbowOut: {fileID: 3983328028282460839}
gameLayoutContainer: {fileID: 904161782565348054}
exitButton: {fileID: 8427602740714176801}
--- !u!95 &1630362919770549177
Animator:
serializedVersion: 7
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1498581815400593087}
m_Enabled: 1
m_Avatar: {fileID: 0}
m_Controller: {fileID: 9100000, guid: fc380d833ededb441a23b106de60bedd, type: 2}
m_CullingMode: 0
m_UpdateMode: 2
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_StabilizeFeet: 0
m_AnimatePhysics: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
rectMask: {fileID: 7425566603516801919}
slideDuration: 0.6
--- !u!1 &2426870979657684456
GameObject:
m_ObjectHideFlags: 0
@@ -514,6 +528,99 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &3983328028282460839
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6717870941799174515}
- component: {fileID: 6334910097310371923}
- component: {fileID: 8821021733826365168}
- component: {fileID: 9163925342151684341}
m_Layer: 5
m_Name: RainbowOut
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6717870941799174515
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3983328028282460839}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8293076336493130222}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &6334910097310371923
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3983328028282460839}
m_CullTransparentMesh: 1
--- !u!114 &8821021733826365168
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3983328028282460839}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RawImage
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Texture: {fileID: 0}
m_UVRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: -1
--- !u!114 &9163925342151684341
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3983328028282460839}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2fd09147b9e9d42a48d6ddc915ddc3d2, type: 3}
m_Name:
m_EditorClassIdentifier: SkiaSharp.Unity::SkiaSharp.Unity.SkottiePlayerV2
lottieFile: {fileID: 4900000, guid: 589505308c5daf449800f30dd4b92ce7, type: 3}
customResolution: 0
resWidth: 250
resHeight: 250
stateName:
resetAfterFinished: 0
autoPlay: 0
loop: 0
--- !u!1 &4270065472017787841
GameObject:
m_ObjectHideFlags: 0
@@ -634,6 +741,81 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &4404111907760364827
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4136733570236406132}
- component: {fileID: 102352512179716898}
- component: {fileID: 3911817807614671446}
m_Layer: 5
m_Name: BackgroundOverlay
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4136733570236406132
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4404111907760364827}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 8293076336493130222}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &102352512179716898
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4404111907760364827}
m_CullTransparentMesh: 1
--- !u!114 &3911817807614671446
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4404111907760364827}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.5019608}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &5189738780074141096
GameObject:
m_ObjectHideFlags: 0
@@ -767,7 +949,7 @@ GameObject:
- component: {fileID: 7920249735731934357}
- component: {fileID: 3566391948883171773}
m_Layer: 5
m_Name: Rainbow
m_Name: RainbowIn
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
@@ -780,12 +962,12 @@ RectTransform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5382650426034128680}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1315170081792486277}
m_Father: {fileID: 8293076336493130222}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
@@ -967,156 +1149,6 @@ MonoBehaviour:
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &6784053660381911346
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1495118611075801417}
- component: {fileID: 9118909078554628244}
- component: {fileID: 8490254979303535795}
- component: {fileID: 4316986085632541161}
- component: {fileID: 6597789131808754055}
m_Layer: 5
m_Name: Icon
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1495118611075801417
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6784053660381911346}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1315170081792486277}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 1, y: 1}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: -50, y: -50}
m_SizeDelta: {x: 200, y: 200}
m_Pivot: {x: 1, y: 1}
--- !u!222 &9118909078554628244
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6784053660381911346}
m_CullTransparentMesh: 1
--- !u!114 &8490254979303535795
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6784053660381911346}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.RawImage
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Texture: {fileID: 0}
m_UVRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: -1
--- !u!114 &4316986085632541161
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6784053660381911346}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2fd09147b9e9d42a48d6ddc915ddc3d2, type: 3}
m_Name:
m_EditorClassIdentifier: SkiaSharp.Unity::SkiaSharp.Unity.SkottiePlayerV2
lottieFile: {fileID: 4900000, guid: 50e22b5bb8a496840952f2563758c13c, type: 3}
customResolution: 0
resWidth: 250
resHeight: 250
stateName: Idle
resetAfterFinished: 0
autoPlay: 1
loop: 1
--- !u!114 &6597789131808754055
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6784053660381911346}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 8490254979303535795}
m_OnClick:
m_PersistentCalls:
m_Calls:
- m_Target: {fileID: 8074691980395114238}
m_TargetAssemblyTypeName: AppSwitcher, AppleHillsScripts
m_MethodName: OpenAppSwitcher
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine
m_IntArgument: 0
m_FloatArgument: 0
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!1 &7713997083397969887
GameObject:
m_ObjectHideFlags: 0
@@ -1489,6 +1521,7 @@ GameObject:
- component: {fileID: 5972191288640444065}
- component: {fileID: 236099612463072939}
- component: {fileID: 447310668687539451}
- component: {fileID: 6704345904811573523}
m_Layer: 5
m_Name: CloseUIButton
m_TagString: Untagged
@@ -1599,7 +1632,7 @@ MonoBehaviour:
m_Calls:
- m_Target: {fileID: 8074691980395114238}
m_TargetAssemblyTypeName: AppSwitcher, AppleHillsScripts
m_MethodName: CloseAppSwitcher
m_MethodName: OnBackPressed
m_Mode: 1
m_Arguments:
m_ObjectArgument: {fileID: 0}
@@ -1609,3 +1642,15 @@ MonoBehaviour:
m_StringArgument:
m_BoolArgument: 0
m_CallState: 2
--- !u!114 &6704345904811573523
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8427602740714176801}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 494d0aedce9744308499355006071138, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::UI.DummyInput

File diff suppressed because it is too large Load Diff

View File

@@ -34,7 +34,9 @@ RectTransform:
- {fileID: 5027301639700053608}
- {fileID: 2081116343754364062}
- {fileID: 4830022034953347571}
- {fileID: 4247146273316450423}
- {fileID: 7968396929263690413}
- {fileID: 1477338421424306197}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -79,7 +81,7 @@ MonoBehaviour:
boosterDisappearDuration: 0.5
impulseSource: {fileID: 4448843358972162772}
openingParticleSystem: {fileID: 0}
albumIcon: {fileID: 0}
albumIcon: {fileID: 2290549912955623947}
--- !u!114 &4448843358972162772
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -214,6 +216,170 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
curveHeight: 50
--- !u!1 &2290549912955623947
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4247146273316450423}
- component: {fileID: 5793188825053435727}
- component: {fileID: 5726314423424208945}
- component: {fileID: 299670167085314888}
- component: {fileID: 6513785969721658917}
m_Layer: 5
m_Name: ScrapbookButton
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4247146273316450423
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2290549912955623947}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1.06667, y: 1.06667, z: 1.06667}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5228380266581535650}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 80.000015, y: 79.999985}
m_SizeDelta: {x: 241, y: 239}
m_Pivot: {x: 0, y: 0}
--- !u!222 &5793188825053435727
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2290549912955623947}
m_CullTransparentMesh: 1
--- !u!114 &5726314423424208945
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2290549912955623947}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: -4354454609415314374, guid: 1ba1f8cf73f79214190f1432fe1e3bc6, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &299670167085314888
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2290549912955623947}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 5726314423424208945}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!114 &6513785969721658917
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2290549912955623947}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 494d0aedce9744308499355006071138, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::UI.DummyInput
--- !u!1 &2898816812112041854
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1477338421424306197}
m_Layer: 0
m_Name: GameObject (1)
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1477338421424306197
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2898816812112041854}
serializedVersion: 2
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: -297.3595, y: -196.74738, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5228380266581535650}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &4303341275909682426
GameObject:
m_ObjectHideFlags: 0

View File

@@ -0,0 +1,483 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &1439929750438628637
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1966378914653314124}
- component: {fileID: 1175208421330333144}
m_Layer: 0
m_Name: MiniGameBoosterGiver
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1966378914653314124
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439929750438628637}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 2499229096808986326}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1175208421330333144
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1439929750438628637}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 7d8f3e9a2b4c5f6d1a8e9c0b3d4f5a6b, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::UI.CardSystem.MinigameBoosterGiver
visualContainer: {fileID: 8617171489468030463}
boosterImage: {fileID: 3680365639323743419}
glowImage: {fileID: 4006246129058447062}
continueButton: {fileID: 2988510625873934392}
hoverAmount: 20
hoverDuration: 1.5
glowPulseMin: 0.9
glowPulseMax: 1.1
glowPulseDuration: 1.2
targetBottomLeftOffset: {x: 100, y: 100}
disappearDuration: 0.8
disappearScale: 0.2
--- !u!1 &2923535299741790846
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 5109945643968698326}
m_Layer: 0
m_Name: BoosterContainer
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &5109945643968698326
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2923535299741790846}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 4006246129058447062}
- {fileID: 3680365639323743419}
m_Father: {fileID: 2499229096808986326}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 1}
m_AnchorMax: {x: 0.5, y: 1}
m_AnchoredPosition: {x: 0, y: -300}
m_SizeDelta: {x: 750, y: 760}
m_Pivot: {x: 0.5, y: 1}
--- !u!1 &4323719263405703996
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 6841858894429745291}
- component: {fileID: 1590188508543769496}
- component: {fileID: 4489841151491567959}
- component: {fileID: 2988510625873934392}
m_Layer: 0
m_Name: Button
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &6841858894429745291
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4323719263405703996}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2499229096808986326}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0}
m_AnchorMax: {x: 0.5, y: 0}
m_AnchoredPosition: {x: 0, y: 160}
m_SizeDelta: {x: 250, y: 250}
m_Pivot: {x: 0.5, y: 0}
--- !u!222 &1590188508543769496
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4323719263405703996}
m_CullTransparentMesh: 1
--- !u!114 &4489841151491567959
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4323719263405703996}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 2636902231072113825, guid: ee014bd71cac2bc4ab845f435726f383, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!114 &2988510625873934392
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4323719263405703996}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Selected
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 4489841151491567959}
m_OnClick:
m_PersistentCalls:
m_Calls: []
--- !u!1 &5931931042366245593
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 4006246129058447062}
- component: {fileID: 5796229481733252802}
- component: {fileID: 6215049078676414306}
m_Layer: 0
m_Name: Glow
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &4006246129058447062
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5931931042366245593}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5109945643968698326}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 800, y: 800}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &5796229481733252802
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5931931042366245593}
m_CullTransparentMesh: 1
--- !u!114 &6215049078676414306
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 5931931042366245593}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: -8836962644236845764, guid: c5cc7367a37a7944abb3876352b0e0ff, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &8617171489468030463
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2499229096808986326}
m_Layer: 0
m_Name: VisualContainer
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &2499229096808986326
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8617171489468030463}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1338508664922812659}
- {fileID: 6841858894429745291}
- {fileID: 5109945643968698326}
m_Father: {fileID: 1966378914653314124}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!1 &8914844459546715980
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1338508664922812659}
- component: {fileID: 4173866009683612467}
- component: {fileID: 570826085774513514}
m_Layer: 0
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1338508664922812659
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8914844459546715980}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 2499229096808986326}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 0, y: 0}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &4173866009683612467
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8914844459546715980}
m_CullTransparentMesh: 1
--- !u!114 &570826085774513514
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 8914844459546715980}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 0, g: 0, b: 0, a: 0.49411765}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!1 &9035675646436554328
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 3680365639323743419}
- component: {fileID: 8906420622179058179}
- component: {fileID: 3765065913677958559}
m_Layer: 0
m_Name: BoosterPack
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &3680365639323743419
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9035675646436554328}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 5109945643968698326}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 411, y: 729}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!222 &8906420622179058179
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9035675646436554328}
m_CullTransparentMesh: 1
--- !u!114 &3765065913677958559
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 9035675646436554328}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 4365544765984126881, guid: 9dac643e78ad86e4988c11a92f9c7a6d, type: 3}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a855ba60e86bf1e449197f1f5f9b9b73
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6fb0d7fc6faad154b8c3e3cb7abb7c15
guid: fe049e0d73eadd7479140c8e7bd10efe
PrefabImporter:
externalObjects: {}
userData:

File diff suppressed because it is too large Load Diff

View File

@@ -1,50 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1 &3863019143023165617
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 272997100784137721}
- component: {fileID: 497632815361153787}
m_Layer: 5
m_Name: UIPageController
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &272997100784137721
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3863019143023165617}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 100, y: 100}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &497632815361153787
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 3863019143023165617}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b1ae6c1745e44e22a0fa9209ebe45ee3, type: 3}
m_Name:
m_EditorClassIdentifier: AppleHillsScripts::UI.Core.UIPageController

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -305,6 +305,7 @@ RectTransform:
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1224833349}
- {fileID: 471921060}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
@@ -418,16 +419,16 @@ LineRenderer:
m_SortingOrder: 0
m_Positions:
- {x: -0.15602553, y: 4.074945, z: 0}
- {x: -0.1566351, y: 3.9736383, z: 0}
- {x: -0.1572447, y: 3.8729858, z: 0}
- {x: -0.1566351, y: 3.973638, z: 0}
- {x: -0.1572447, y: 3.8729856, z: 0}
- {x: -0.15785426, y: 3.7729874, z: 0}
- {x: -0.15846384, y: 3.673644, z: 0}
- {x: -0.15907341, y: 3.5749543, z: 0}
- {x: -0.15968299, y: 3.4769197, z: 0}
- {x: -0.15846384, y: 3.6736436, z: 0}
- {x: -0.15907341, y: 3.574954, z: 0}
- {x: -0.15968299, y: 3.4769192, z: 0}
- {x: -0.16029257, y: 3.3795385, z: 0}
- {x: -0.16090216, y: 3.2828126, z: 0}
- {x: -0.16151173, y: 3.1867406, z: 0}
- {x: -0.16212131, y: 3.0913231, z: 0}
- {x: -0.16151173, y: 3.1867409, z: 0}
- {x: -0.16212131, y: 3.0913236, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -1098,6 +1099,112 @@ Animator:
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!1001 &471921059
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 116234201}
m_Modifications:
- target: {fileID: 1439929750438628637, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_Name
value: MiniGameBoosterGiver
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_Pivot.x
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_Pivot.y
value: 0.5
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchorMax.x
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchorMax.y
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchorMin.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchorMin.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_SizeDelta.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_SizeDelta.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalPosition.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalRotation.w
value: 1
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalRotation.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalRotation.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalRotation.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchoredPosition.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_AnchoredPosition.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalEulerAnglesHint.x
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalEulerAnglesHint.y
value: 0
objectReference: {fileID: 0}
- target: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_LocalEulerAnglesHint.z
value: 0
objectReference: {fileID: 0}
- target: {fileID: 8617171489468030463, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
propertyPath: m_IsActive
value: 0
objectReference: {fileID: 0}
m_RemovedComponents: []
m_RemovedGameObjects: []
m_AddedGameObjects: []
m_AddedComponents: []
m_SourcePrefab: {fileID: 100100000, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
--- !u!224 &471921060 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 1966378914653314124, guid: a855ba60e86bf1e449197f1f5f9b9b73, type: 3}
m_PrefabInstance: {fileID: 471921059}
m_PrefabAsset: {fileID: 0}
--- !u!1 &730962732
GameObject:
m_ObjectHideFlags: 0
@@ -1455,7 +1562,7 @@ AudioSource:
OutputAudioMixerGroup: {fileID: 3533147658878909314, guid: 727a7e4b6df4b0d47897f7d8ee7fa323, type: 2}
m_audioClip: {fileID: 0}
m_Resource: {fileID: 0}
m_PlayOnAwake: 1
m_PlayOnAwake: 0
m_Volume: 1
m_Pitch: 1
Loop: 0
@@ -1783,14 +1890,14 @@ LineRenderer:
- {x: -0.15602553, y: 4.0749445, z: 0}
- {x: -0.11662118, y: 3.879622, z: 0}
- {x: -0.07721684, y: 3.7057445, z: 0}
- {x: -0.03781248, y: 3.5533104, z: 0}
- {x: 0.0015918687, y: 3.4223213, z: 0}
- {x: 0.040996216, y: 3.312776, z: 0}
- {x: 0.08040057, y: 3.2246757, z: 0}
- {x: 0.11980491, y: 3.1580195, z: 0}
- {x: 0.15920927, y: 3.1128078, z: 0}
- {x: 0.1986136, y: 3.0890403, z: 0}
- {x: 0.23801796, y: 3.0867171, z: 0}
- {x: -0.03781248, y: 3.5533106, z: 0}
- {x: 0.0015918687, y: 3.4223216, z: 0}
- {x: 0.040996216, y: 3.3127766, z: 0}
- {x: 0.08040057, y: 3.2246761, z: 0}
- {x: 0.11980491, y: 3.15802, z: 0}
- {x: 0.15920927, y: 3.1128082, z: 0}
- {x: 0.1986136, y: 3.0890405, z: 0}
- {x: 0.23801796, y: 3.0867176, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -2503,14 +2610,14 @@ LineRenderer:
- {x: -0.15602553, y: 4.0749445, z: 0}
- {x: -0.18956745, y: 3.8764973, z: 0}
- {x: -0.22310936, y: 3.7000232, z: 0}
- {x: -0.25665125, y: 3.54552, z: 0}
- {x: -0.29019317, y: 3.4129906, z: 0}
- {x: -0.32373506, y: 3.302433, z: 0}
- {x: -0.35727698, y: 3.213848, z: 0}
- {x: -0.39081886, y: 3.1472354, z: 0}
- {x: -0.4243608, y: 3.1025953, z: 0}
- {x: -0.45790267, y: 3.0799277, z: 0}
- {x: -0.4914446, y: 3.0792325, z: 0}
- {x: -0.25665125, y: 3.5455203, z: 0}
- {x: -0.29019317, y: 3.412991, z: 0}
- {x: -0.32373506, y: 3.3024335, z: 0}
- {x: -0.35727698, y: 3.2138484, z: 0}
- {x: -0.39081886, y: 3.1472359, z: 0}
- {x: -0.4243608, y: 3.1025958, z: 0}
- {x: -0.45790267, y: 3.0799282, z: 0}
- {x: -0.4914446, y: 3.079233, z: 0}
m_Parameters:
serializedVersion: 3
widthMultiplier: 1
@@ -3358,7 +3465,7 @@ Transform:
m_GameObject: {fileID: 2106431001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -0.165, y: 2.697517, z: 0}
m_LocalPosition: {x: -0.165, y: 2.6975174, z: 0}
m_LocalScale: {x: 0.57574, y: 0.57574, z: 0.57574}
m_ConstrainProportionsScale: 0
m_Children:
@@ -3526,6 +3633,18 @@ PrefabInstance:
propertyPath: m_Name
value: Tutorial
objectReference: {fileID: 0}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: introVO
value:
objectReference: {fileID: 8300000, guid: fca641cdc8dcd074483fad3db1cbe24c, type: 3}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: playTutorial
value: 1
objectReference: {fileID: 0}
- target: {fileID: 4267886887244421663, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: bottleAudioPlayer
value:
objectReference: {fileID: 747976408}
- target: {fileID: 8452897808363562605, guid: a4dd78ff48942854ebb4c65025a8dc36, type: 3}
propertyPath: m_Sprite
value:

File diff suppressed because it is too large Load Diff

View File

@@ -1,43 +1,82 @@
using Core.Lifecycle;
using Core.SaveLoad;
using UnityEngine;
using Pixelplacement;
public class BirdEyesBehavior : MonoBehaviour
public class BirdEyesBehavior : ManagedBehaviour
{
private StateMachine statemachine;
private Animator animator;
// Animator Hashes
private static readonly int RightGuess = Animator.StringToHash("RightGuess");
private static readonly int WrongGuess = Animator.StringToHash("WrongGuess");
private static readonly int NoGuess = Animator.StringToHash("NoGuess");
private static readonly int Wolterisout = Animator.StringToHash("wolterisout");
private AppleMachine _statemachine;
private Animator _animator;
public bool correctItemIsIn;
[SerializeField] private Animator bushAnimator; // Assign in Inspector
// Save state
private bool _wolterisoutTriggered;
// Enable save/load participation
public override bool AutoRegisterForSave => true;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
statemachine = GetComponent<StateMachine>();
animator = GetComponentInChildren<Animator>();
_statemachine = GetComponent<AppleMachine>();
_animator = GetComponentInChildren<Animator>();
}
public void CorrectItem()
{
correctItemIsIn = true;
animator.SetTrigger("RightGuess");
_animator.SetTrigger(RightGuess);
BirdReveal();
}
public void IncorrectItem()
{
correctItemIsIn = false;
animator.SetTrigger("WrongGuess");
_animator.SetTrigger(WrongGuess);
}
public void NoItem()
{
animator.SetTrigger("NoGuess");
_animator.SetTrigger(NoGuess);
}
public void BirdReveal()
{
if (bushAnimator != null)
{
bushAnimator.SetTrigger("wolterisout");
statemachine.ChangeState("BirdSpawned");
return;
bushAnimator.SetTrigger(Wolterisout);
_wolterisoutTriggered = true;
}
statemachine.ChangeState ("BirdSpawned");
_statemachine.ChangeState("BirdSpawned");
}
internal override void OnSceneRestoreRequested(string serializedData)
{
base.OnSceneRestoreRequested(serializedData);
if (!string.IsNullOrEmpty(serializedData))
{
if (bool.TryParse(serializedData, out bool wasTriggered))
{
_wolterisoutTriggered = wasTriggered;
// If it was triggered before, set it again on restore
if (_wolterisoutTriggered && bushAnimator != null)
{
bushAnimator.SetTrigger(Wolterisout);
}
}
}
}
internal override string OnSceneSaveRequested()
{
return _wolterisoutTriggered.ToString();
}
}

View File

@@ -1,161 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core;
using UnityEngine;
namespace Bootstrap
{
/// <summary>
/// Service that provides notification and management of boot completion status.
/// Allows systems to subscribe to boot completion events, register initialization actions with priorities,
/// or await boot completion asynchronously.
/// </summary>
public static class BootCompletionService
{
/// <summary>
/// Indicates if the boot process has completed
/// </summary>
public static bool IsBootComplete { get; private set; } = false;
/// <summary>
/// Event triggered when boot completes
/// </summary>
public static event Action OnBootComplete;
/// <summary>
/// Represents an initialization action with priority
/// </summary>
private class InitializationAction
{
public Action Action { get; }
public int Priority { get; }
public string Name { get; }
public InitializationAction(Action action, int priority, string name)
{
Action = action;
Priority = priority;
Name = name;
}
}
// List of initialization actions to be executed once boot completes
private static List<InitializationAction> _initializationActions = new List<InitializationAction>();
// TaskCompletionSource for async await pattern
private static TaskCompletionSource<bool> _bootCompletionTask = new TaskCompletionSource<bool>();
/// <summary>
/// Called by CustomBoot when the boot process is complete
/// </summary>
internal static void HandleBootCompleted()
{
if (IsBootComplete)
return;
IsBootComplete = true;
LogDebugMessage("Boot process completed, executing initialization actions");
// Execute initialization actions in priority order (lower number = higher priority)
ExecuteInitializationActions();
// Trigger the event
OnBootComplete?.Invoke();
// Complete the task for async waiters
_bootCompletionTask.TrySetResult(true);
LogDebugMessage("All boot completion handlers executed");
}
/// <summary>
/// Register an action to be executed when boot completes.
/// Lower priority numbers run first.
/// </summary>
/// <param name="action">The action to execute</param>
/// <param name="priority">Priority (lower numbers run first)</param>
/// <param name="name">Name for debugging</param>
public static void RegisterInitAction(Action action, int priority = 100, string name = null)
{
if (action == null)
return;
if (string.IsNullOrEmpty(name))
name = $"Action_{_initializationActions.Count}";
var initAction = new InitializationAction(action, priority, name);
if (IsBootComplete)
{
// If boot is already complete, execute immediately
LogDebugMessage($"Executing late registration: {name} (Priority: {priority})");
try
{
action();
}
catch (Exception ex)
{
LogDebugMessage($"Error executing init action '{name}': {ex}");
}
}
else
{
// Otherwise add to the queue
_initializationActions.Add(initAction);
LogDebugMessage($"Registered init action: {name} (Priority: {priority})");
}
}
/// <summary>
/// Wait asynchronously for boot completion
/// </summary>
/// <returns>Task that completes when boot is complete</returns>
public static Task WaitForBootCompletionAsync()
{
if (IsBootComplete)
return Task.CompletedTask;
return _bootCompletionTask.Task;
}
/// <summary>
/// Execute all registered initialization actions in priority order
/// </summary>
private static void ExecuteInitializationActions()
{
// Sort by priority (lowest first)
var sortedActions = _initializationActions
.OrderBy(a => a.Priority)
.ToList();
foreach (var action in sortedActions)
{
try
{
LogDebugMessage($"Executing: {action.Name} (Priority: {action.Priority})");
action.Action();
}
catch (Exception ex)
{
LogDebugMessage($"Error executing init action '{action.Name}': {ex}");
}
}
// Clear the list after execution
_initializationActions.Clear();
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[BootCompletionService] {message}");
}
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: aa0228cf33a64515bc166b7a9bc8c0b9
timeCreated: 1760606319

View File

@@ -1,18 +1,18 @@
using System;
using AppleHills.Core.Settings;
using UnityEngine;
using UI;
using Core;
using Core.Lifecycle;
using UnityEngine.SceneManagement;
using Cinematics;
using UnityEngine.Serialization;
using Core.SaveLoad;
namespace Bootstrap
{
/// <summary>
/// Controller for the boot scene that coordinates bootstrap initialization with loading screen
/// </summary>
public class BootSceneController : MonoBehaviour
public class BootSceneController : ManagedBehaviour
{
[SerializeField] private string mainSceneName = "AppleHillsOverworld";
[SerializeField] private float minDelayAfterBoot = 0.5f; // Small delay after boot to ensure smooth transition
@@ -30,36 +30,37 @@ namespace Bootstrap
private float _sceneLoadingProgress = 0f;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
private void Start()
// Run very early - need to set up loading screen before other systems initialize
public override int ManagedAwakePriority => 5;
internal override void OnManagedAwake()
{
LogDebugMessage("Boot scene started");
Logging.Debug("BootSceneController.Awake() - Initializing loading screen DURING bootstrap");
// Ensure the initial loading screen exists
// Validate loading screen exists
if (initialLoadingScreen == null)
{
Debug.LogError("[BootSceneController] No InitialLoadingScreen assigned! Please assign it in the inspector.");
return;
}
// Subscribe to the loading screen completion event
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
// Show the loading screen immediately with our combined progress provider
// This needs to happen DURING bootstrap to show progress
initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress);
// Subscribe to boot progress events
// Subscribe to loading screen completion event
initialLoadingScreen.OnLoadingScreenFullyHidden += OnInitialLoadingComplete;
// Subscribe to boot progress for real-time updates during bootstrap
CustomBoot.OnBootProgressChanged += OnBootProgressChanged;
// Register our boot completion handler with the BootCompletionService
// This will execute either immediately if boot is already complete,
// or when the boot process completes
BootCompletionService.RegisterInitAction(
OnBootCompleted,
50, // Higher priority (lower number)
"BootSceneController.OnBootCompleted"
);
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity;
#if UNITY_EDITOR
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity;
#elif DEVELOPMENT_BUILD
_logVerbosity = LogVerbosity.Debug;
#elif RELEASE_BUILD
_logVerbosity = LogVerbosity.Warning;
#endif
// In debug mode, log additional information
if (debugMode)
@@ -67,18 +68,44 @@ namespace Bootstrap
InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f);
}
}
internal override void OnManagedStart()
{
Logging.Debug("BootSceneController.OnManagedStart() - Boot is GUARANTEED complete, starting scene loading");
// Boot is GUARANTEED complete at this point - that's the whole point of OnManagedStart!
// No need to subscribe to OnBootCompleted or check CustomBoot.Initialised
_bootComplete = true;
_currentPhase = LoadingPhase.SceneLoading;
// Start loading the main scene after a small delay
// This prevents jerky transitions if boot happens very quickly
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
protected override void OnDestroy()
{
base.OnDestroy();
// Manual cleanup for events
if (initialLoadingScreen != null)
{
initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete;
}
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
}
/// <summary>
/// Called when the initial loading screen is fully hidden
/// </summary>
private void OnInitialLoadingComplete()
{
LogDebugMessage("Initial loading screen fully hidden, boot sequence completed");
Logging.Debug("Initial loading screen fully hidden, boot sequence completed");
// Play the intro cinematic if available
if (CinematicsManager.Instance != null)
{
LogDebugMessage("Attempting to play intro cinematic");
Logging.Debug("Attempting to play intro cinematic");
// Use LoadAndPlayCinematic to play the intro sequence
CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence", false);
@@ -96,23 +123,6 @@ namespace Bootstrap
}
}
private void OnDestroy()
{
// Clean up event subscriptions
CustomBoot.OnBootCompleted -= OnBootCompleted;
CustomBoot.OnBootProgressChanged -= OnBootProgressChanged;
if (initialLoadingScreen != null)
{
initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete;
}
if (debugMode)
{
CancelInvoke(nameof(LogDebugInfo));
}
}
/// <summary>
/// Progress provider that combines bootstrap and scene loading progress
/// </summary>
@@ -137,29 +147,17 @@ namespace Bootstrap
{
if (debugMode)
{
LogDebugMessage($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
Logging.Debug($"Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}");
}
}
private void LogDebugInfo()
{
LogDebugMessage($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
Logging.Debug($"Debug - Phase: {_currentPhase}, Bootstrap: {CustomBoot.CurrentProgress:P0}, " +
$"Scene: {_sceneLoadingProgress:P0}, Combined: {GetCombinedProgress():P0}, Boot Complete: {_bootComplete}");
}
private void OnBootCompleted()
{
// Unsubscribe to prevent duplicate calls
CustomBoot.OnBootCompleted -= OnBootCompleted;
LogDebugMessage("Boot process completed");
_bootComplete = true;
// After a small delay, start loading the main menu
// This prevents jerky transitions if boot happens very quickly
Invoke(nameof(StartLoadingMainMenu), minDelayAfterBoot);
}
private void StartLoadingMainMenu()
{
if (_hasStartedLoading)
@@ -172,7 +170,7 @@ namespace Bootstrap
private async void LoadMainScene()
{
LogDebugMessage($"Loading main menu scene: {mainSceneName}");
Logging.Debug($"Loading main menu scene: {mainSceneName}");
try
{
@@ -186,7 +184,7 @@ namespace Bootstrap
if (debugMode)
{
LogDebugMessage($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
Logging.Debug($"Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}");
}
});
@@ -209,6 +207,17 @@ namespace Bootstrap
// Ensure progress is complete
_sceneLoadingProgress = 1f;
// CRITICAL: Broadcast lifecycle events so components get their OnSceneReady callbacks
Logging.Debug($"Broadcasting OnSceneReady for: {mainSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(mainSceneName);
// Restore scene data for the main menu
if (SaveLoadManager.Instance != null)
{
Logging.Debug($"Restoring scene data for: {mainSceneName}");
SaveLoadManager.Instance.RestoreSceneData();
}
// Step 2: Scene is fully loaded, now hide the loading screen
// This will trigger OnInitialLoadingComplete via the event when animation completes
initialLoadingScreen.HideLoadingScreen();
@@ -235,7 +244,7 @@ namespace Bootstrap
Scene currentScene = SceneManager.GetActiveScene();
string startingSceneName = currentScene.name;
LogDebugMessage($"Unloading StartingScene: {startingSceneName}");
Logging.Debug($"Unloading StartingScene: {startingSceneName}");
// Unload the StartingScene
await SceneManager.UnloadSceneAsync(startingSceneName);
@@ -244,14 +253,14 @@ namespace Bootstrap
Scene mainMenuScene = SceneManager.GetSceneByName(mainSceneName);
SceneManager.SetActiveScene(mainMenuScene);
LogDebugMessage($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active");
Logging.Debug($"Transition complete: {startingSceneName} unloaded, {mainSceneName} is now active");
// Destroy the boot scene controller since its job is done
Destroy(gameObject);
}
catch (Exception e)
{
Logging.Warning($"[BootSceneController] Error unloading StartingScene: {e.Message}");
Logging.Warning($"Error unloading StartingScene: {e.Message}");
}
}
@@ -272,13 +281,5 @@ namespace Bootstrap
_progressAction?.Invoke(value);
}
}
private void LogDebugMessage(string message)
{
if ( _logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[BootSceneController] {message}");
}
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
@@ -39,6 +39,10 @@ namespace Bootstrap
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)]
private static void Initialise()
{
// Create LifecycleManager FIRST - before any bootstrap logic
// This ensures it exists when boot completes
LifecycleManager.CreateInstance();
//We should always clean up after Addressables, so let's take care of that immediately
Application.quitting += ApplicationOnUnloading;
@@ -97,12 +101,14 @@ namespace Bootstrap
OnBootProgressChanged?.Invoke(1f);
OnBootCompleted?.Invoke();
// Notify the BootCompletionService that boot is complete
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
// Direct call to boot completion service
LogDebugMessage("Calling BootCompletionService.HandleBootCompleted()");
BootCompletionService.HandleBootCompleted();
Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
}
}
}
@@ -117,12 +123,14 @@ namespace Bootstrap
OnBootProgressChanged?.Invoke(1f);
OnBootCompleted?.Invoke();
// Notify the BootCompletionService that boot is complete
// Notify the LifecycleManager that boot is complete
if (Application.isPlaying)
{
// Direct call to boot completion service
LogDebugMessage("Calling BootCompletionService.HandleBootCompleted()");
BootCompletionService.HandleBootCompleted();
Logging.Debug("Calling LifecycleManager.OnBootCompletionTriggered()");
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.OnBootCompletionTriggered();
}
}
}
@@ -229,16 +237,7 @@ namespace Bootstrap
{
CurrentProgress = Mathf.Clamp01(progress);
OnBootProgressChanged?.Invoke(CurrentProgress);
LogDebugMessage($"Progress: {CurrentProgress:P0}");
}
private static void LogDebugMessage(string message)
{
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().bootstrapLogVerbosity <=
LogVerbosity.Debug)
{
Logging.Debug($"[CustomBoot] {message}");
}
Logging.Debug($"Progress: {CurrentProgress:P0}");
}
}
}

View File

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

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using Bootstrap;
using Core;
using Core.Lifecycle;
using UI;
using UnityEngine;
using UnityEngine.AddressableAssets;
@@ -14,7 +14,7 @@ namespace Cinematics
/// <summary>
/// Handles loading, playing and unloading cinematics
/// </summary>
public class CinematicsManager : MonoBehaviour
public class CinematicsManager : ManagedBehaviour
{
public event System.Action OnCinematicStarted;
public event System.Action OnCinematicStopped;
@@ -37,20 +37,19 @@ namespace Cinematics
public PlayableDirector playableDirector;
private void Awake()
public override int ManagedAwakePriority => 170; // Cinematic systems
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void InitializePostBoot()
internal override void OnManagedStart()
{
// Initialize any dependencies that require other services to be ready
// For example, subscribe to SceneManagerService events if needed
Logging.Debug("[CinematicsManager] Post-boot initialization complete");
Logging.Debug("[CinematicsManager] Initialized");
}
private void OnEnable()
{
@@ -85,7 +84,7 @@ namespace Cinematics
// If still null, try to add the component
if (playableDirector == null)
{
Debug.Log("[CinematicsManager] Could not find Playable Director on the PlayerHudManager");
Logging.Debug("[CinematicsManager] Could not find Playable Director on the PlayerHudManager");
}
}
@@ -98,7 +97,7 @@ namespace Cinematics
// If still null, return error
if (_cinematicSprites == null)
{
Debug.LogWarning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly.");
Logging.Warning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly.");
}
}
@@ -146,6 +145,8 @@ namespace Cinematics
/// </summary>
public PlayableDirector LoadAndPlayCinematic(string key, bool playPortraitMode)
{
InitializeComponents();
// Load the asset via addressables
var handle = Addressables.LoadAssetAsync<PlayableAsset>(key);
var result = handle.WaitForCompletion();
@@ -205,7 +206,10 @@ namespace Cinematics
}
public void ShowGameOverScreen()
{
{
// Diagnostic: log time and call stack to track when/why game over is triggered
Logging.Debug($"[CinematicsManager] ShowGameOverScreen called at time={Time.time:F2}");
if (divingGameOverScreen != null && UIPageController.Instance != null)
{
UIPageController.Instance.PushPage(divingGameOverScreen);

View File

@@ -1,12 +1,12 @@
using Bootstrap;
using Core;
using Core.Lifecycle;
using Input;
using UnityEngine;
using UnityEngine.UI;
namespace Cinematics
{
public class SkipCinematic : MonoBehaviour, ITouchInputConsumer
public class SkipCinematic : ManagedBehaviour, ITouchInputConsumer
{
[Header("Configuration")]
[SerializeField] private float holdDuration = 2.0f;
@@ -15,41 +15,29 @@ namespace Cinematics
private float _holdStartTime;
private bool _isHolding;
private bool _skipPerformed;
private bool _initialized = false;
void Awake()
{
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
public override int ManagedAwakePriority => 180; // Cinematic UI
void Start()
internal override void OnManagedStart()
{
// Reset the progress bar
if (radialProgressBar != null)
{
radialProgressBar.fillAmount = 0f;
}
}
void OnDisable()
{
// Clean up subscriptions regardless of initialization state
UnsubscribeFromCinematicsEvents();
}
private void InitializePostBoot()
{
// Safe initialization of manager dependencies after boot is complete
if (_initialized)
return;
_initialized = true;
// Subscribe to CinematicsManager events now that boot is complete
SubscribeToCinematicsEvents();
Logging.Debug("[SkipCinematic] Post-boot initialization complete");
Logging.Debug("[SkipCinematic] Initialized");
}
protected override void OnDestroy()
{
base.OnDestroy();
// Clean up subscriptions
UnsubscribeFromCinematicsEvents();
}
private void SubscribeToCinematicsEvents()

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using AppleHills.Core.Interfaces;
using AppleHills.Core.Settings;
using Bootstrap;
using Core.Lifecycle;
using Core.Settings;
using Input;
using UnityEngine;
@@ -12,7 +12,7 @@ namespace Core
/// <summary>
/// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters.
/// </summary>
public class GameManager : MonoBehaviour
public class GameManager : ManagedBehaviour
{
// Singleton implementation
private static GameManager _instance;
@@ -34,33 +34,31 @@ namespace Core
public event Action OnGamePaused;
public event Action OnGameResumed;
void Awake()
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 10; // Core infrastructure - runs early
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Create settings providers if it doesn't exist
// Create settings providers - must happen in OnManagedAwake so other managers can access settings in their ManagedStart
SettingsProvider.Instance.gameObject.name = "Settings Provider";
DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider";
// Load all settings synchronously during Awake
// Load all settings synchronously - critical infrastructure for other managers
InitializeSettings();
InitializeDeveloperSettings();
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
// DontDestroyOnLoad(gameObject);
}
private void Start()
{
// Load verbosity settings early
_settingsLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().settingsLogVerbosity;
_managerLogVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().gameManagerLogVerbosity;
}
private void InitializePostBoot()
internal override void OnManagedStart()
{
// For post-boot correct initialization order
// Settings are already initialized in OnManagedAwake()
// This is available for future initialization that depends on other managers
}
/// <summary>
@@ -79,7 +77,7 @@ namespace Core
component.Pause();
}
LogDebugMessage($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
Logging.Debug($"Registered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
}
}
@@ -92,7 +90,7 @@ namespace Core
if (component != null && _pausableComponents.Contains(component))
{
_pausableComponents.Remove(component);
LogDebugMessage($"Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
Logging.Debug($"Unregistered pausable component: {(component as MonoBehaviour)?.name ?? "Unknown"}");
}
}
@@ -108,7 +106,7 @@ namespace Core
ApplyPause(true);
}
LogDebugMessage($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
Logging.Debug($"Pause requested by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
}
/// <summary>
@@ -123,7 +121,7 @@ namespace Core
ApplyPause(false);
}
LogDebugMessage($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
Logging.Debug($"Pause released by {requester?.ToString() ?? "Unknown"}. pauseCount = {_pauseCount}");
}
/// <summary>
@@ -162,12 +160,12 @@ namespace Core
OnGameResumed?.Invoke();
}
LogDebugMessage($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components.");
Logging.Debug($"Game {(shouldPause ? "paused" : "resumed")}. Paused {_pausableComponents.Count} components.");
}
private void InitializeSettings()
{
LogDebugMessage("Starting settings initialization...", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("Starting settings initialization...");
// Load settings synchronously
var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous<PlayerFollowerSettings>();
@@ -178,7 +176,7 @@ namespace Core
if (playerSettings != null)
{
ServiceLocator.Register<IPlayerFollowerSettings>(playerSettings);
LogDebugMessage("PlayerFollowerSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("PlayerFollowerSettings registered successfully");
}
else
{
@@ -188,7 +186,7 @@ namespace Core
if (interactionSettings != null)
{
ServiceLocator.Register<IInteractionSettings>(interactionSettings);
LogDebugMessage("InteractionSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("InteractionSettings registered successfully");
}
else
{
@@ -198,7 +196,7 @@ namespace Core
if (minigameSettings != null)
{
ServiceLocator.Register<IDivingMinigameSettings>(minigameSettings);
LogDebugMessage("MinigameSettings registered successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("MinigameSettings registered successfully");
}
else
{
@@ -209,7 +207,7 @@ namespace Core
_settingsLoaded = playerSettings != null && interactionSettings != null && minigameSettings != null;
if (_settingsLoaded)
{
LogDebugMessage("All settings loaded and registered with ServiceLocator", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("All settings loaded and registered with ServiceLocator");
}
else
{
@@ -222,7 +220,7 @@ namespace Core
/// </summary>
private void InitializeDeveloperSettings()
{
LogDebugMessage("Starting developer settings initialization...", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("Starting developer settings initialization...");
// Load developer settings
var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings<DivingDeveloperSettings>();
@@ -232,7 +230,7 @@ namespace Core
if (_developerSettingsLoaded)
{
LogDebugMessage("All developer settings loaded successfully", "SettingsInitialization", _settingsLogVerbosity);
Logging.Debug("All developer settings loaded successfully");
}
else
{
@@ -266,19 +264,6 @@ namespace Core
{
return DeveloperSettingsProvider.Instance?.GetSettings<T>();
}
private void LogDebugMessage(string message, string prefix = "GameManager", LogVerbosity verbosity = LogVerbosity.None)
{
if (verbosity == LogVerbosity.None)
{
verbosity = _managerLogVerbosity;
}
if ( verbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[{prefix}] {message}");
}
}
// LEFTOVER LEGACY SETTINGS
public float PlayerStopDistance => GetSettings<IInteractionSettings>()?.PlayerStopDistance ?? 6.0f;

View File

@@ -2,7 +2,7 @@
using System.Collections.Generic;
using UnityEngine;
using Interactions;
using Bootstrap;
using Core.Lifecycle;
using Core.SaveLoad;
namespace Core
@@ -11,7 +11,7 @@ namespace Core
/// Central registry for pickups and item slots.
/// Mirrors the singleton pattern used by PuzzleManager.
/// </summary>
public class ItemManager : MonoBehaviour
public class ItemManager : ManagedBehaviour
{
private static ItemManager _instance;
@@ -48,35 +48,30 @@ namespace Core
// Args: first item data, second item data, result item data
public event Action<PickupItemData, PickupItemData, PickupItemData> OnItemsCombined;
void Awake()
public override int ManagedAwakePriority => 75; // Item registry
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void InitializePostBoot()
internal override void OnManagedStart()
{
// Subscribe to scene load completed so we can clear registrations when scenes change
SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted;
Logging.Debug("[ItemManager] Subscribed to SceneManagerService events");
Logging.Debug("[ItemManager] Initialized");
}
void OnDestroy()
internal override void OnSceneReady()
{
// Unsubscribe from SceneManagerService
if (SceneManagerService.Instance != null)
SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted;
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
// Replaces SceneLoadStarted subscription for clearing registrations
ClearAllRegistrations();
}
private void OnSceneLoadStarted(string sceneName)
protected override void OnDestroy()
{
// Clear all registrations when a new scene is loaded, so no stale references persist
base.OnDestroy();
// Ensure we clean up any subscriptions from registered items when the manager is destroyed
ClearAllRegistrations();
}
@@ -147,7 +142,6 @@ namespace Core
{
s.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
s.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
s.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
s.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
}
}
@@ -194,7 +188,6 @@ namespace Core
// Subscribe to all slot events
slot.OnCorrectItemSlotted += ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted += ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted += ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved += ItemSlot_OnItemSlotRemoved;
}
}
@@ -207,7 +200,6 @@ namespace Core
// Unsubscribe from all slot events
slot.OnCorrectItemSlotted -= ItemSlot_OnCorrectItemSlotted;
slot.OnIncorrectItemSlotted -= ItemSlot_OnIncorrectItemSlotted;
slot.OnForbiddenItemSlotted -= ItemSlot_OnForbiddenItemSlotted;
slot.OnItemSlotRemoved -= ItemSlot_OnItemSlotRemoved;
}
}
@@ -269,7 +261,7 @@ namespace Core
// Search through all registered pickups
foreach (var pickup in _pickups)
{
if (pickup is SaveableInteractable saveable && saveable.GetSaveId() == saveId)
if (pickup is SaveableInteractable saveable && saveable.SaveId == saveId)
{
return pickup.gameObject;
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 06a2c07342e5422eae1eb613f614ed61
timeCreated: 1762206473

View File

@@ -0,0 +1,48 @@
namespace Core.Lifecycle
{
/// <summary>
/// Defines the different lifecycle phases that can be broadcast by the LifecycleManager.
/// All ManagedBehaviours participate in all lifecycle phases by default.
/// </summary>
public enum LifecyclePhase
{
/// <summary>
/// Called once per component after bootstrap completes.
/// Guaranteed to be called after all bootstrap resources are loaded.
/// For late-registered components, called immediately upon registration.
/// </summary>
ManagedAwake,
/// <summary>
/// Called before a scene is unloaded.
/// Only called for components in the scene being unloaded.
/// </summary>
SceneUnloading,
/// <summary>
/// Called after a scene has finished loading.
/// Only called for components in the scene being loaded.
/// </summary>
SceneReady,
/// <summary>
/// Called before scene unloads to save data via SaveLoadManager.
/// Integrates with existing SaveLoadManager save system.
/// </summary>
SaveRequested,
/// <summary>
/// Called after scene loads to restore data via SaveLoadManager.
/// Integrates with existing SaveLoadManager restore system.
/// </summary>
RestoreRequested,
/// <summary>
/// Called during OnDestroy before component is destroyed.
/// Use for custom cleanup logic.
/// Most cleanup is automatic (managed events, auto-registrations).
/// </summary>
ManagedDestroy
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5f5f0f19f08240d4d9863b6be6a3cf03

View File

@@ -0,0 +1,675 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Core.Lifecycle
{
/// <summary>
/// Central orchestrator for ManagedBehaviour lifecycle events.
/// Singleton that broadcasts lifecycle events in priority-ordered manner.
/// </summary>
public class LifecycleManager : MonoBehaviour
{
#region Singleton
private static LifecycleManager _instance;
/// <summary>
/// Singleton instance of the LifecycleManager.
/// Created by CustomBoot.Initialise() before bootstrap begins.
/// </summary>
public static LifecycleManager Instance => _instance;
/// <summary>
/// Create LifecycleManager instance. Called by CustomBoot.Initialise() before bootstrap begins.
/// </summary>
public static void CreateInstance()
{
if (_instance != null)
{
Logging.Warning("[LifecycleManager] Instance already exists");
return;
}
var go = new GameObject("LifecycleManager");
_instance = go.AddComponent<LifecycleManager>();
DontDestroyOnLoad(go);
Logging.Debug("[LifecycleManager] Instance created");
}
#endregion
#region Lifecycle Lists
private List<ManagedBehaviour> managedAwakeList = new List<ManagedBehaviour>();
private List<ManagedBehaviour> sceneUnloadingList = new List<ManagedBehaviour>();
private List<ManagedBehaviour> sceneReadyList = new List<ManagedBehaviour>();
private List<ManagedBehaviour> saveRequestedList = new List<ManagedBehaviour>();
private List<ManagedBehaviour> restoreRequestedList = new List<ManagedBehaviour>();
private List<ManagedBehaviour> destroyList = new List<ManagedBehaviour>();
#endregion
#region Tracking Dictionaries
private Dictionary<ManagedBehaviour, string> componentScenes = new Dictionary<ManagedBehaviour, string>();
#endregion
#region State Flags
private bool isBootComplete = false;
private string currentSceneReady = "";
// Scene loading state tracking
private bool isLoadingScene = false;
private string sceneBeingLoaded = "";
private List<ManagedBehaviour> pendingSceneComponents = new List<ManagedBehaviour>();
[SerializeField] private bool enableDebugLogging = true;
#endregion
#region Unity Lifecycle
void Awake()
{
// Instance should already be set by CreateInstance() called from CustomBoot
// This Awake is backup in case LifecycleManager was manually added to a scene
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(gameObject);
LogDebug("LifecycleManager initialized via Awake (fallback)");
}
else if (_instance != this)
{
Logging.Warning("[LifecycleManager] Duplicate instance detected. Destroying.");
Destroy(gameObject);
}
}
void OnDestroy()
{
if (_instance == this)
{
_instance = null;
}
}
#endregion
#region Registration
/// <summary>
/// Register a ManagedBehaviour with the lifecycle system.
/// Called automatically from ManagedBehaviour.Awake().
/// All components participate in all lifecycle hooks.
/// </summary>
public void Register(ManagedBehaviour component)
{
if (component == null)
{
Logging.Warning("[LifecycleManager] Attempted to register null component");
return;
}
var sceneName = component.gameObject.scene.name;
// Track which scene this component belongs to
componentScenes[component] = sceneName;
// ALWAYS add to managedAwakeList - this is the master list used for save/load
InsertSorted(managedAwakeList, component, component.ManagedAwakePriority);
// 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)
{
// Check if we're currently loading a scene
if (isLoadingScene && sceneName == sceneBeingLoaded)
{
// Batch this component - will be processed in priority order when scene load completes
pendingSceneComponents.Add(component);
LogDebug($"Batched component for scene load: {component.gameObject.name} (Scene: {sceneName})");
}
else
{
// Truly late registration (component enabled after scene is ready)
// Call OnManagedStart immediately since boot already completed
LogDebug($"Late registration: Calling OnManagedStart immediately for {component.gameObject.name}");
try
{
component.OnManagedStart();
HandleAutoRegistrations(component);
}
catch (Exception 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 BroadcastManagedStart()
// If this scene is already ready (and we're not in loading mode), call OnSceneReady immediately
if (!isLoadingScene && currentSceneReady == sceneName)
{
LogDebug($"Late registration: Calling OnSceneReady immediately for {component.gameObject.name}");
try
{
component.OnSceneReady();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnSceneReady for {component.gameObject.name}: {ex}");
}
}
LogDebug($"Registered {component.gameObject.name} (Scene: {sceneName})");
}
/// <summary>
/// Unregister a ManagedBehaviour from the lifecycle system.
/// Called automatically from ManagedBehaviour.OnDestroy().
/// </summary>
public void Unregister(ManagedBehaviour component)
{
if (component == null)
return;
managedAwakeList.Remove(component);
sceneUnloadingList.Remove(component);
sceneReadyList.Remove(component);
saveRequestedList.Remove(component);
restoreRequestedList.Remove(component);
destroyList.Remove(component);
componentScenes.Remove(component);
LogDebug($"Unregistered {component.gameObject.name}");
}
#endregion
#region Broadcast Methods
/// <summary>
/// Called by CustomBoot when boot completes.
/// Broadcasts ManagedStart to all registered components.
/// </summary>
public void OnBootCompletionTriggered()
{
if (isBootComplete)
return;
LogDebug("=== Boot Completion Triggered ===");
BroadcastManagedStart();
isBootComplete = true;
}
/// <summary>
/// Broadcast OnManagedStart to all registered components (priority ordered).
/// </summary>
private void BroadcastManagedStart()
{
LogDebug($"Broadcasting ManagedStart to {managedAwakeList.Count} components");
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null) continue;
try
{
component.OnManagedStart();
HandleAutoRegistrations(component);
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for {component.gameObject.name}: {ex}");
}
}
// NOTE: We do NOT clear managedAwakeList here!
// This list is reused for save/load broadcasts and must persist for the lifetime of the game.
// Components are added during registration and removed during Unregister (OnDestroy).
}
/// <summary>
/// Begins scene loading mode for the specified scene.
/// Components that register during this time will be batched and processed in priority order.
/// Call this BEFORE starting to load a scene.
/// </summary>
public void BeginSceneLoad(string sceneName)
{
isLoadingScene = true;
sceneBeingLoaded = sceneName;
pendingSceneComponents.Clear();
LogDebug($"Began scene loading mode for: {sceneName}");
}
/// <summary>
/// Processes all batched components from the scene load in priority order.
/// Called automatically by BroadcastSceneReady.
/// </summary>
private void ProcessBatchedSceneComponents()
{
if (pendingSceneComponents.Count == 0)
{
isLoadingScene = false;
sceneBeingLoaded = "";
return;
}
LogDebug($"Processing {pendingSceneComponents.Count} batched components for scene: {sceneBeingLoaded}");
// Sort by ManagedAwake priority (lower values first)
pendingSceneComponents.Sort((a, b) => a.ManagedAwakePriority.CompareTo(b.ManagedAwakePriority));
// Call OnManagedStart in priority order
foreach (var component in pendingSceneComponents)
{
if (component == null) continue;
try
{
component.OnManagedStart();
HandleAutoRegistrations(component);
LogDebug($"Processed batched component: {component.gameObject.name} (Priority: {component.ManagedAwakePriority})");
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnManagedStart for batched component {component.gameObject.name}: {ex}");
}
}
// Clear state
pendingSceneComponents.Clear();
isLoadingScene = false;
sceneBeingLoaded = "";
}
/// <summary>
/// Broadcast OnSceneUnloading to components in the specified scene (reverse priority order).
/// </summary>
public void BroadcastSceneUnloading(string sceneName)
{
LogDebug($"Broadcasting SceneUnloading for scene: {sceneName}");
// Iterate backwards (high priority → low priority)
for (int i = sceneUnloadingList.Count - 1; i >= 0; i--)
{
var component = sceneUnloadingList[i];
if (component == null) continue;
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
{
try
{
component.OnSceneUnloading();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnSceneUnloading for {component.gameObject.name}: {ex}");
}
}
}
}
/// <summary>
/// Broadcast OnSceneReady to components in the specified scene (priority order).
/// If scene loading mode is active, processes batched components first.
/// </summary>
public void BroadcastSceneReady(string sceneName)
{
LogDebug($"Broadcasting SceneReady for scene: {sceneName}");
currentSceneReady = sceneName;
// If we were in scene loading mode for this scene, process batched components first
if (isLoadingScene && sceneBeingLoaded == sceneName)
{
ProcessBatchedSceneComponents();
}
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(sceneReadyList);
foreach (var component in componentsCopy)
{
if (component == null) continue;
if (componentScenes.TryGetValue(component, out string compScene) && compScene == sceneName)
{
try
{
component.OnSceneReady();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Error in OnSceneReady for {component.gameObject.name}: {ex}");
}
}
}
}
/// <summary>
/// Broadcasts scene save request to all registered components that opt-in.
/// Collects and returns serialized data from components that return non-null values.
/// Called by SaveLoadManager during scene transitions.
/// </summary>
public Dictionary<string, string> BroadcastSceneSaveRequested()
{
var saveData = new Dictionary<string, string>();
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
try
{
string serializedData = component.OnSceneSaveRequested();
if (!string.IsNullOrEmpty(serializedData))
{
string saveId = component.SaveId;
saveData[saveId] = serializedData;
LogDebug($"Collected scene save data from: {saveId} (Type: {component.GetType().Name})");
}
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during scene save for {component.SaveId}: {ex}");
}
}
LogDebug($"Collected scene save data from {saveData.Count} components");
return saveData;
}
/// <summary>
/// Broadcasts global save request to all registered components that opt-in.
/// Collects and returns serialized data from components that return non-null values.
/// Called by SaveLoadManager when writing save file to disk (quit, manual save).
/// </summary>
public Dictionary<string, string> BroadcastGlobalSaveRequested()
{
var saveData = new Dictionary<string, string>();
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
try
{
string serializedData = component.OnGlobalSaveRequested();
if (!string.IsNullOrEmpty(serializedData))
{
saveData[component.SaveId] = serializedData;
LogDebug($"Collected global save data from: {component.SaveId}");
}
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during global save for {component.SaveId}: {ex}");
}
}
LogDebug($"Collected global save data from {saveData.Count} components");
return saveData;
}
/// <summary>
/// Broadcasts scene restore request to all registered components that opt-in.
/// Distributes serialized data to matching components by SaveId.
/// Called by SaveLoadManager during scene load.
/// </summary>
public void BroadcastSceneRestoreRequested(Dictionary<string, string> saveData)
{
if (saveData == null) return;
int restoredCount = 0;
// Create a copy to avoid collection modification during iteration
// (components might destroy themselves during restoration)
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
if (saveData.TryGetValue(component.SaveId, out string serializedData))
{
try
{
component.OnSceneRestoreRequested(serializedData);
restoredCount++;
LogDebug($"Restored scene data to: {component.SaveId}");
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during scene restore for {component.SaveId}: {ex}");
}
}
}
LogDebug($"Restored scene data to {restoredCount} components");
}
/// <summary>
/// Broadcasts scene restore completed event to all registered components.
/// Called AFTER all OnSceneRestoreRequested calls complete.
/// </summary>
public void BroadcastSceneRestoreCompleted()
{
LogDebug("Broadcasting SceneRestoreCompleted");
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null) continue;
try
{
component.OnSceneRestoreCompleted();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during scene restore completed for {component.SaveId}: {ex}");
}
}
LogDebug("SceneRestoreCompleted broadcast complete");
}
/// <summary>
/// Broadcasts global restore request to all registered components that opt-in.
/// Distributes serialized data to matching components by SaveId.
/// Called by SaveLoadManager during initial boot load.
/// </summary>
public void BroadcastGlobalRestoreRequested(Dictionary<string, string> saveData)
{
if (saveData == null) return;
int restoredCount = 0;
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
if (saveData.TryGetValue(component.SaveId, out string serializedData))
{
try
{
component.OnGlobalRestoreRequested(serializedData);
restoredCount++;
LogDebug($"Restored global data to: {component.SaveId}");
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during global restore for {component.SaveId}: {ex}");
}
}
}
LogDebug($"Restored global data to {restoredCount} components");
}
/// <summary>
/// Broadcasts global load completed event to all registered components that opt-in.
/// Called ONCE after save file is successfully loaded on game boot.
/// NOT called during scene transitions.
/// </summary>
public void BroadcastGlobalLoadCompleted()
{
LogDebug("Broadcasting GlobalLoadCompleted");
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
try
{
component.OnGlobalLoadCompleted();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during global load for {component.name}: {ex}");
}
}
}
/// <summary>
/// Broadcasts global save started event to all registered components that opt-in.
/// Called ONCE before save file is written to disk.
/// NOT called during scene transitions.
/// </summary>
public void BroadcastGlobalSaveStarted()
{
LogDebug("Broadcasting GlobalSaveStarted");
// Create a copy to avoid collection modification during iteration
var componentsCopy = new List<ManagedBehaviour>(managedAwakeList);
foreach (var component in componentsCopy)
{
if (component == null || !component.AutoRegisterForSave) continue;
try
{
component.OnGlobalSaveStarted();
}
catch (Exception ex)
{
Debug.LogError($"[LifecycleManager] Exception during global save for {component.name}: {ex}");
}
}
}
#endregion
#region Auto-Registration
/// <summary>
/// Handle automatic registration with GameManager.
/// </summary>
private void HandleAutoRegistrations(ManagedBehaviour component)
{
// Auto-register IPausable
if (component.AutoRegisterPausable && component is AppleHills.Core.Interfaces.IPausable pausable)
{
if (GameManager.Instance != null)
{
GameManager.Instance.RegisterPausableComponent(pausable);
LogDebug($"Auto-registered IPausable: {component.gameObject.name}");
}
}
}
#endregion
#region Helper Methods
/// <summary>
/// Insert component into list maintaining sorted order by priority.
/// Uses binary search for efficient insertion.
/// </summary>
private void InsertSorted(List<ManagedBehaviour> list, ManagedBehaviour component, int priority)
{
// Simple linear insertion for now (can optimize with binary search later if needed)
int index = 0;
for (int i = 0; i < list.Count; i++)
{
int existingPriority = GetPriorityForList(list[i], list);
if (priority < existingPriority)
{
index = i;
break;
}
index = i + 1;
}
list.Insert(index, component);
}
/// <summary>
/// Get the priority value for a component based on which list it's in.
/// </summary>
private int GetPriorityForList(ManagedBehaviour component, List<ManagedBehaviour> list)
{
if (list == managedAwakeList) return component.ManagedAwakePriority;
if (list == sceneUnloadingList) return component.SceneUnloadingPriority;
if (list == sceneReadyList) return component.SceneReadyPriority;
if (list == saveRequestedList) return component.SavePriority;
if (list == restoreRequestedList) return component.RestorePriority;
if (list == destroyList) return component.DestroyPriority;
return 100;
}
/// <summary>
/// Log debug message if debug logging is enabled.
/// </summary>
private void LogDebug(string message)
{
if (enableDebugLogging)
{
Logging.Debug($"[LifecycleManager] {message}");
}
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: db6d4743867a3a44381d511cea39218d

View File

@@ -0,0 +1,328 @@
using UnityEngine;
namespace Core.Lifecycle
{
/// <summary>
/// Base class for all managed behaviours with deterministic lifecycle hooks.
/// Automatically registers with LifecycleManager and provides ordered lifecycle callbacks.
/// </summary>
public abstract class ManagedBehaviour : MonoBehaviour
{
#region Priority Properties
/// <summary>
/// Priority for OnManagedStart (lower values execute first).
/// Default: 100
/// </summary>
public virtual int ManagedAwakePriority => 100;
/// <summary>
/// Priority for OnSceneUnloading (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int SceneUnloadingPriority => 100;
/// <summary>
/// Priority for OnSceneReady (lower values execute first).
/// Default: 100
/// </summary>
public virtual int SceneReadyPriority => 100;
/// <summary>
/// Priority for OnSaveRequested (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int SavePriority => 100;
/// <summary>
/// Priority for OnRestoreRequested (lower values execute first).
/// Default: 100
/// </summary>
public virtual int RestorePriority => 100;
/// <summary>
/// Priority for OnManagedDestroy (executed in reverse: higher values execute first).
/// Default: 100
/// </summary>
public virtual int DestroyPriority => 100;
#endregion
#region Configuration Properties
/// <summary>
/// If true and component implements IPausable, automatically registers with GameManager.
/// Default: false
/// </summary>
public virtual bool AutoRegisterPausable => false;
/// <summary>
/// If true, this component participates in the save/load system.
/// Components should override OnSaveRequested() and OnRestoreRequested().
/// Default: false
/// </summary>
public virtual bool AutoRegisterForSave => false;
/// <summary>
/// Unique identifier for this component in the save system.
/// Default: "SceneName/GameObjectName/ComponentType"
/// Override ONLY for special cases (e.g., singletons like "PlayerController", or custom IDs).
/// </summary>
public virtual string SaveId
{
get
{
string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene";
string componentType = GetType().Name;
return $"{sceneName}/{gameObject.name}/{componentType}";
}
}
#endregion
#region Private Fields
private bool _isRegistered;
#endregion
#region Unity Lifecycle
/// <summary>
/// Unity Awake - automatically registers with LifecycleManager.
/// SEALED: Cannot be overridden. Use OnManagedAwake() for early initialization or OnManagedStart() for late initialization.
/// </summary>
private void Awake()
{
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.Register(this);
_isRegistered = true;
}
else
{
Logging.Warning($"[ManagedBehaviour] LifecycleManager not found for {gameObject.name}. Component will not receive lifecycle callbacks.");
}
}
/// <summary>
/// Unity OnDestroy - automatically unregisters and cleans up.
/// IMPORTANT: Derived classes that override OnDestroy MUST call base.OnDestroy()
/// </summary>
protected virtual void OnDestroy()
{
if (!_isRegistered)
return;
// Unregister from LifecycleManager
if (LifecycleManager.Instance != null)
{
LifecycleManager.Instance.Unregister(this);
}
// Auto-unregister from GameManager if auto-registered
if (AutoRegisterPausable && this is AppleHills.Core.Interfaces.IPausable pausable)
{
GameManager.Instance?.UnregisterPausableComponent(pausable);
}
_isRegistered = false;
}
#endregion
#region Managed Lifecycle Hooks
/// <summary>
/// Called immediately during registration (during Awake).
/// Use for early initialization such as setting singleton instances.
/// TIMING: Fires during component's Awake(), no execution order guarantees between components.
/// NOT priority-ordered - fires whenever Unity calls this component's Awake().
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnManagedAwake()
{
// Override in derived classes
}
/// <summary>
/// Called once per component after bootstrap completes.
/// GUARANTEE: Bootstrap resources are available, all managers are initialized.
/// For boot-time components: Called during LifecycleManager.BroadcastManagedStart (priority ordered).
/// For late-registered components: Called immediately upon registration (bootstrap already complete).
/// Use for initialization that depends on other systems.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnManagedStart()
{
// Override in derived classes
}
/// <summary>
/// Called before the scene this component belongs to is unloaded.
/// Called in REVERSE priority order (higher values execute first).
/// Use for scene-specific cleanup.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnSceneUnloading()
{
// Override in derived classes
}
/// <summary>
/// Called after the scene this component belongs to has finished loading.
/// Called in priority order (lower values execute first).
/// Use for scene-specific initialization.
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnSceneReady()
{
// Override in derived classes
}
/// <summary>
/// Called during scene transitions to save scene-specific state.
/// Return serialized data (e.g., JsonUtility.ToJson(myData)).
/// Return null if component has no scene-specific state to save.
///
/// TIMING:
/// - Called BEFORE scene unload during scene transitions
/// - Frequency: Every scene transition
/// - Use for: Level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual string OnSceneSaveRequested()
{
return null; // Default: no data to save
}
/// <summary>
/// Called during scene transitions to restore scene-specific state.
/// Receives previously serialized data (from OnSceneSaveRequested).
///
/// IMPORTANT: This method MUST be synchronous. Do not use coroutines or async/await.
/// OnSceneRestoreCompleted is called immediately after all restore calls complete,
/// so any async operations would still be running when it fires.
///
/// TIMING:
/// - Called AFTER scene load, during OnSceneReady phase
/// - Frequency: Every scene transition
/// - Use for: Restoring level progress, object positions, puzzle states
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnSceneRestoreRequested(string serializedData)
{
// Default: no-op
}
/// <summary>
/// Called after all scene restore operations complete.
/// Does NOT receive data - use OnSceneRestoreRequested for that.
///
/// GUARANTEE:
/// - ALWAYS called after scene load, whether there's save data or not
/// - All OnSceneRestoreRequested() calls have RETURNED (but async operations may still be running)
/// - Safe for synchronous restore operations (JSON deserialization, setting fields, etc.)
///
/// TIMING:
/// - Called AFTER all OnSceneRestoreRequested calls complete (or immediately if no save data exists)
/// - Frequency: Every scene transition
/// - Use for: Post-restore initialization, first-time initialization, triggering events after state is restored
///
/// COMMON PATTERN:
/// Use this to perform actions that depend on whether data was restored or not.
/// Example: Play one-time audio only if it hasn't been played before (_hasPlayed == false).
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnSceneRestoreCompleted()
{
// Default: no-op
}
/// <summary>
/// Called once on game boot to restore global persistent state.
/// Receives data that was saved via OnGlobalSaveRequested.
///
/// TIMING:
/// - Called ONCE on game boot after save file is read
/// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnGlobalRestoreRequested(string serializedData)
{
// Default: no-op
}
/// <summary>
/// Called once before game save file is written to disk.
/// Return serialized data for global persistent state.
/// Return null if component has no global state to save.
///
/// TIMING:
/// - Called ONCE before save file is written (on quit, manual save, etc.)
/// - NOT called during scene transitions
/// - Use for: Player inventory, unlocked features, card collections
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual string OnGlobalSaveRequested()
{
return null; // Default: no data to save
}
/// <summary>
/// Called once when game save data is initially loaded from disk.
/// Use for global managers that need to react to load completion.
/// Does NOT receive data - use OnGlobalRestoreRequested for that.
///
/// TIMING:
/// - Called ONCE on game boot after all restore operations complete
/// - NOT called during scene transitions
/// - Use for: Triggering UI updates, broadcasting load events
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnGlobalLoadCompleted()
{
// Default: no-op
}
/// <summary>
/// Called once before save file is written to disk.
/// Use for global managers that need to perform cleanup before save.
/// Does NOT return data - use OnGlobalSaveRequested for that.
///
/// TIMING:
/// - Called ONCE before save file is written
/// - NOT called during scene transitions
/// - Use for: Final validation, cleanup operations
///
/// NOTE: Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnGlobalSaveStarted()
{
// Default: no-op
}
/// <summary>
/// Called during OnDestroy before component is destroyed.
/// Called in REVERSE priority order (higher values execute first).
/// NOTE: Most cleanup is automatic (managed events, auto-registrations).
/// Only override if you need custom cleanup logic.
/// Internal visibility allows LifecycleManager to call directly. Override in derived classes.
/// </summary>
internal virtual void OnManagedDestroy()
{
// Override in derived classes
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: af776ef1493d6e543aa3cbe2601f4ef2

View File

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

View File

@@ -1,7 +1,7 @@
using UnityEngine;
using AppleHills.Data.CardSystem;
using Cinematics;
using Core;
using Core.Lifecycle;
using Data.CardSystem;
using Input;
using PuzzleS;
@@ -12,7 +12,7 @@ namespace AppleHills.Core
/// Provides quick access to frequently used game objects, components, and manager instances.
/// References are cached for performance and automatically invalidated on scene changes.
/// </summary>
public class QuickAccess : MonoBehaviour
public class QuickAccess : ManagedBehaviour
{
#region Singleton Setup
private static QuickAccess _instance;
@@ -24,6 +24,9 @@ namespace AppleHills.Core
#endregion Singleton Setup
// Very early initialization - QuickAccess should be available immediately
public override int ManagedAwakePriority => 5;
#region Manager Instances
// Core Managers
@@ -46,7 +49,6 @@ namespace AppleHills.Core
private PlayerTouchController _playerController;
private FollowerController _followerController;
private Camera _mainCamera;
private bool _initialized = false;
/// <summary>
/// Returns the player GameObject. Finds it if not already cached.
@@ -125,31 +127,29 @@ namespace AppleHills.Core
#endregion
#region Initialization and Scene Management
#region Lifecycle Methods
private void Awake()
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
if (!_initialized)
{
// Subscribe to scene changes
if (SceneManager != null)
{
SceneManager.SceneLoadCompleted += OnSceneLoadCompleted;
}
_initialized = true;
}
}
/// <summary>
/// Handle scene changes by clearing cached references.
/// </summary>
private void OnSceneLoadCompleted(string sceneName)
internal override void OnManagedStart()
{
// QuickAccess has minimal initialization
}
internal override void OnSceneUnloading()
{
// Clear references BEFORE scene unloads for better cleanup timing
ClearReferences();
}
#endregion
#region Reference Management
/// <summary>
/// Clear all cached references.
/// </summary>

View File

@@ -1,6 +1,5 @@
using UnityEngine;
using Pixelplacement;
using Bootstrap;
namespace Core.SaveLoad
{
@@ -82,18 +81,15 @@ namespace Core.SaveLoad
private void Start()
{
// Register with save system (no validation needed - we auto-generate ID)
BootCompletionService.RegisterInitAction(() =>
// Direct registration - SaveLoadManager guaranteed available (priority 25)
if (SaveLoadManager.Instance != null)
{
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.RegisterParticipant(this);
}
else
{
Debug.LogWarning($"[SaveableStateMachine] SaveLoadManager.Instance is null, cannot register '{name}'", this);
}
});
SaveLoadManager.Instance.RegisterParticipant(this);
}
else
{
Logging.Warning($"[AppleMachine] SaveLoadManager not available for '{name}'");
}
}
#if UNITY_EDITOR
@@ -102,7 +98,7 @@ namespace Core.SaveLoad
// Optional: Log the auto-generated ID in verbose mode
if (verbose && string.IsNullOrEmpty(customSaveId))
{
Debug.Log($"[SaveableStateMachine] '{name}' will use auto-generated Save ID: {GetSaveId()}", this);
Logging.Debug($"[SaveableStateMachine] '{name}' will use auto-generated Save ID: {GetSaveId()}");
}
}
#endif
@@ -127,9 +123,8 @@ namespace Core.SaveLoad
return $"{sceneName}/{customSaveId}";
}
// Auto-generate from hierarchy path
string hierarchyPath = GetHierarchyPath();
return $"{sceneName}/StateMachine_{hierarchyPath}";
// Match ManagedBehaviour convention: SceneName/GameObjectName/ComponentType
return $"{sceneName}/{gameObject.name}/AppleMachine";
}
private string GetSceneName()
@@ -137,19 +132,6 @@ namespace Core.SaveLoad
return gameObject.scene.name;
}
private string GetHierarchyPath()
{
string path = gameObject.name;
Transform parent = transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
public string SerializeState()
{
@@ -176,7 +158,7 @@ namespace Core.SaveLoad
{
if (verbose)
{
Debug.LogWarning($"[SaveableStateMachine] No data to restore for '{name}'", this);
Logging.Warning($"[SaveableStateMachine] No data to restore for '{name}'");
}
return;
}
@@ -189,7 +171,7 @@ namespace Core.SaveLoad
{
if (verbose)
{
Debug.LogWarning($"[SaveableStateMachine] No state name in save data for '{name}'", this);
Logging.Warning($"[SaveableStateMachine] No state name in save data for '{name}'");
}
return;
}
@@ -215,7 +197,7 @@ namespace Core.SaveLoad
if (verbose)
{
Debug.Log($"[SaveableStateMachine] Restored '{name}' to state: {saveData.stateName}", this);
Logging.Debug($"[SaveableStateMachine] Restored '{name}' to state: {saveData.stateName}");
}
}
catch (System.Exception ex)
@@ -233,14 +215,14 @@ namespace Core.SaveLoad
[ContextMenu("Log Save ID")]
private void LogSaveId()
{
Debug.Log($"Save ID: {GetSaveId()}", this);
Logging.Debug($"Save ID: {GetSaveId()}");
}
[ContextMenu("Test Serialize")]
private void TestSerialize()
{
string serialized = SerializeState();
Debug.Log($"Serialized state: {serialized}", this);
Logging.Debug($"Serialized state: {serialized}");
}
#endif

View File

@@ -4,20 +4,20 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using AppleHills.Core.Settings;
using Bootstrap;
using Core.Lifecycle;
using UnityEngine;
namespace Core.SaveLoad
{
/// <summary>
/// Save/Load manager that follows the project's bootstrap pattern.
/// Save/Load manager that follows the project's lifecycle pattern.
/// - Singleton instance
/// - Registers a post-boot init action with BootCompletionService
/// - Inherits from ManagedBehaviour for lifecycle integration
/// - Manages participant registration for save/load operations
/// - Exposes simple async Save/Load methods
/// - Fires events on completion
/// </summary>
public class SaveLoadManager : MonoBehaviour
public class SaveLoadManager : ManagedBehaviour
{
private static SaveLoadManager _instance;
public static SaveLoadManager Instance => _instance;
@@ -43,24 +43,47 @@ namespace Core.SaveLoad
public event Action<string> OnLoadCompleted;
public event Action OnParticipantStatesRestored;
void Awake()
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 20; // After GameManager and SceneManagerService
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Initialize critical state immediately
IsSaveDataLoaded = false;
IsRestoringState = false;
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void Start()
internal override void OnManagedStart()
{
#if UNITY_EDITOR
OnSceneLoadCompleted("RestoreInEditor");
#endif
Logging.Debug("[SaveLoadManager] Initialized");
// Load save data if save system is enabled (depends on settings from GameManager)
if (DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().useSaveLoadSystem)
{
Load();
}
}
internal override void OnSceneReady()
{
// SaveableInteractables now auto-register via ManagedBehaviour lifecycle
// No need to discover and register them manually
}
internal override string OnSceneSaveRequested()
{
// SaveLoadManager orchestrates saves, doesn't participate in them
return null;
}
internal override string OnGlobalSaveRequested()
{
// SaveLoadManager orchestrates saves, doesn't participate in them
return null;
}
private void OnApplicationQuit()
{
@@ -70,30 +93,14 @@ namespace Core.SaveLoad
}
}
private void InitializePostBoot()
{
Logging.Debug("[SaveLoadManager] Post-boot initialization complete");
// Subscribe to scene lifecycle events if SceneManagerService is available
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
SceneManagerService.Instance.SceneUnloadStarted += OnSceneUnloadStarted;
Logging.Debug("[SaveLoadManager] Subscribed to SceneManagerService events");
}
}
// ...existing code...
void OnDestroy()
protected override void OnDestroy()
{
base.OnDestroy(); // Important: call base to unregister from LifecycleManager
if (_instance == this)
{
// Unsubscribe from scene events
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
SceneManagerService.Instance.SceneUnloadStarted -= OnSceneUnloadStarted;
}
_instance = null;
}
}
@@ -173,40 +180,93 @@ namespace Core.SaveLoad
#endregion
#region Scene Lifecycle
#region Unlocked Minigames Management
private void OnSceneLoadCompleted(string sceneName)
/// <summary>
/// Marks a minigame as unlocked in the global save data.
/// This is separate from scene-specific participant states and persists across all saves.
/// </summary>
/// <param name="minigameName">The name/identifier of the minigame (typically scene name)</param>
public void UnlockMinigame(string minigameName)
{
Logging.Debug($"[SaveLoadManager] Scene '{sceneName}' loaded. Discovering inactive SaveableInteractables...");
// Find ONLY INACTIVE SaveableInteractables (active ones will register themselves via Start())
var inactiveSaveables = FindObjectsByType(
typeof(Interactions.SaveableInteractable),
FindObjectsInactive.Include,
FindObjectsSortMode.None
);
int registeredCount = 0;
foreach (var obj in inactiveSaveables)
if (string.IsNullOrEmpty(minigameName))
{
var saveable = obj as Interactions.SaveableInteractable;
if (saveable != null && !saveable.gameObject.activeInHierarchy)
{
// Only register if it's actually inactive
RegisterParticipant(saveable);
registeredCount++;
}
Logging.Warning("[SaveLoadManager] Attempted to unlock minigame with null or empty name");
return;
}
if (currentSaveData == null)
{
Logging.Warning("[SaveLoadManager] Cannot unlock minigame - no save data loaded");
return;
}
if (currentSaveData.unlockedMinigames == null)
{
currentSaveData.unlockedMinigames = new System.Collections.Generic.List<string>();
}
if (!currentSaveData.unlockedMinigames.Contains(minigameName))
{
currentSaveData.unlockedMinigames.Add(minigameName);
Logging.Debug($"[SaveLoadManager] Unlocked minigame: {minigameName}");
}
Logging.Debug($"[SaveLoadManager] Discovered and registered {registeredCount} inactive SaveableInteractables");
}
private void OnSceneUnloadStarted(string sceneName)
/// <summary>
/// Checks if a minigame has been unlocked.
/// </summary>
/// <param name="minigameName">The name/identifier of the minigame</param>
/// <returns>True if the minigame is unlocked, false otherwise</returns>
public bool IsMinigameUnlocked(string minigameName)
{
Logging.Debug($"[SaveLoadManager] Scene '{sceneName}' unloading. Note: Participants should unregister themselves.");
// We don't force-clear here because participants should manage their own lifecycle
// This allows for proper cleanup in OnDestroy
if (string.IsNullOrEmpty(minigameName))
return false;
if (currentSaveData == null || currentSaveData.unlockedMinigames == null)
return false;
return currentSaveData.unlockedMinigames.Contains(minigameName);
}
/// <summary>
/// Gets a read-only list of all unlocked minigames.
/// </summary>
public System.Collections.Generic.IReadOnlyList<string> GetUnlockedMinigames()
{
if (currentSaveData == null || currentSaveData.unlockedMinigames == null)
return new System.Collections.Generic.List<string>();
return currentSaveData.unlockedMinigames.AsReadOnly();
}
/// <summary>
/// Clears all save data for a specific level/scene.
/// Removes all participant states that belong to the specified scene.
/// Useful for "restart level" functionality to wipe progress.
/// </summary>
/// <param name="sceneName">The name of the scene to clear data for</param>
public void ClearLevelData(string sceneName)
{
if (string.IsNullOrEmpty(sceneName))
{
Logging.Warning("[SaveLoadManager] Cannot clear level data - scene name is null or empty");
return;
}
if (currentSaveData == null || currentSaveData.participantStates == null)
{
Logging.Warning("[SaveLoadManager] Cannot clear level data - no save data loaded");
return;
}
// SaveId format is "SceneName/ObjectName/ComponentType"
// Remove all entries that start with "sceneName/"
string scenePrefix = $"{sceneName}/";
int removedCount = currentSaveData.participantStates.RemoveAll(entry =>
entry.saveId.StartsWith(scenePrefix));
Logging.Debug($"[SaveLoadManager] Cleared {removedCount} save entries for level: {sceneName}");
}
#endregion
@@ -254,8 +314,31 @@ namespace Core.SaveLoad
return;
IsRestoringState = true;
int restoredCount = 0;
// Build dictionary for efficient lookup
var saveDataDict = new Dictionary<string, string>();
foreach (var entry in currentSaveData.participantStates)
{
saveDataDict[entry.saveId] = entry.serializedState;
}
// NEW: Restore GLOBAL data via LifecycleManager (called ONCE on boot)
if (Lifecycle.LifecycleManager.Instance != null)
{
Lifecycle.LifecycleManager.Instance.BroadcastGlobalRestoreRequested(saveDataDict);
Logging.Debug($"[SaveLoadManager] Broadcast GLOBAL restore to LifecycleManager");
}
// NEW: Restore SCENE data via LifecycleManager (for currently loaded scenes)
if (Lifecycle.LifecycleManager.Instance != null)
{
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreRequested(saveDataDict);
Logging.Debug($"[SaveLoadManager] Broadcast SCENE restore to LifecycleManager");
}
// EXISTING: Restore ISaveParticipants (backward compatibility)
int restoredCount = 0;
// Clear pending queue at the start
pendingParticipants.Clear();
@@ -266,19 +349,17 @@ namespace Core.SaveLoad
string saveId = kvp.Key;
ISaveParticipant participant = kvp.Value;
// Find the participant state in the list
var entry = currentSaveData.participantStates.Find(e => e.saveId == saveId);
if (entry != null && !string.IsNullOrEmpty(entry.serializedState))
if (saveDataDict.TryGetValue(saveId, out string serializedState))
{
try
{
participant.RestoreState(entry.serializedState);
participant.RestoreState(serializedState);
restoredCount++;
Logging.Debug($"[SaveLoadManager] Restored state for participant: {saveId}");
Logging.Debug($"[SaveLoadManager] Restored ISaveParticipant: {saveId}");
}
catch (Exception ex)
{
Logging.Warning($"[SaveLoadManager] Exception while restoring state for '{saveId}': {ex}");
Logging.Warning($"[SaveLoadManager] Exception while restoring '{saveId}': {ex}");
}
}
}
@@ -324,7 +405,7 @@ namespace Core.SaveLoad
pendingParticipants.Clear();
IsRestoringState = false;
Logging.Debug($"[SaveLoadManager] Restored state for {restoredCount} participants + {totalPendingRestored} pending participants");
Logging.Debug($"[SaveLoadManager] Restored {restoredCount} ISaveParticipants + {totalPendingRestored} pending participants");
OnParticipantStatesRestored?.Invoke();
}
@@ -335,6 +416,126 @@ namespace Core.SaveLoad
return Path.Combine(DefaultSaveFolder, $"save_{slot}.json");
}
/// <summary>
/// Saves scene-specific data during scene transitions.
/// This updates the in-memory save data but does NOT write to disk.
/// Call Save() to persist to disk.
/// </summary>
public void SaveSceneData()
{
if (currentSaveData == null)
{
Logging.Warning("[SaveLoadManager] Cannot save scene data - no save data loaded");
return;
}
Logging.Debug("[SaveLoadManager] Saving scene-specific data...");
// Build a dictionary of all data to save
var allSceneData = new Dictionary<string, string>();
// Collect scene data from ManagedBehaviours via LifecycleManager
if (Lifecycle.LifecycleManager.Instance != null)
{
var sceneData = Lifecycle.LifecycleManager.Instance.BroadcastSceneSaveRequested();
foreach (var kvp in sceneData)
{
allSceneData[kvp.Key] = kvp.Value;
}
Logging.Debug($"[SaveLoadManager] Collected {sceneData.Count} ManagedBehaviour scene states");
}
// Collect data from ISaveParticipants (all currently registered, identified by SaveId)
foreach (var kvp in participants.ToList())
{
string saveId = kvp.Key;
ISaveParticipant participant = kvp.Value;
try
{
string serializedState = participant.SerializeState();
allSceneData[saveId] = serializedState;
Logging.Debug($"[SaveLoadManager] Captured state for ISaveParticipant: {saveId}");
}
catch (Exception ex)
{
Logging.Warning($"[SaveLoadManager] Exception while serializing ISaveParticipant '{saveId}': {ex}");
}
}
// Update existing entries or add new ones (matches SaveAsync() pattern)
if (currentSaveData.participantStates != null)
{
int updatedCount = 0;
foreach (var kvp in allSceneData)
{
var existingEntry = currentSaveData.participantStates.Find(e => e.saveId == kvp.Key);
if (existingEntry != null)
{
// Update existing entry in place
existingEntry.serializedState = kvp.Value;
}
else
{
// Add new entry
currentSaveData.participantStates.Add(new ParticipantStateEntry
{
saveId = kvp.Key,
serializedState = kvp.Value
});
}
updatedCount++;
}
Logging.Debug($"[SaveLoadManager] Updated {updatedCount} scene data entries in memory");
}
else
{
Logging.Warning("[SaveLoadManager] participantStates list is null, cannot save scene data");
}
}
/// <summary>
/// Restores scene-specific data after scene load.
/// Distributes data to components in the newly loaded scene.
/// </summary>
public void RestoreSceneData()
{
if (Lifecycle.LifecycleManager.Instance == null)
{
Logging.Warning("[SaveLoadManager] LifecycleManager not available for scene restore");
return;
}
if (currentSaveData == null || currentSaveData.participantStates == null)
{
Logging.Debug("[SaveLoadManager] No scene data to restore (first visit or no save data)");
// Still broadcast restore completed so components can initialize properly
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreCompleted();
Logging.Debug($"[SaveLoadManager] Scene restore completed (no data)");
return;
}
Logging.Debug("[SaveLoadManager] Restoring scene-specific data...");
// Build dictionary for efficient lookup
var saveDataDict = new Dictionary<string, string>();
foreach (var entry in currentSaveData.participantStates)
{
saveDataDict[entry.saveId] = entry.serializedState;
}
// Restore scene data via LifecycleManager
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreRequested(saveDataDict);
Logging.Debug($"[SaveLoadManager] Broadcast scene restore to LifecycleManager");
// Broadcast scene restore completed - ALWAYS called, whether there's data or not
Lifecycle.LifecycleManager.Instance.BroadcastSceneRestoreCompleted();
Logging.Debug($"[SaveLoadManager] Scene restore completed");
}
/// <summary>
/// Entry point to save to a named slot. Starts an async coroutine that writes to disk.
/// Fires OnSaveCompleted when finished.
@@ -391,14 +592,44 @@ namespace Core.SaveLoad
{
currentSaveData.participantStates = new List<ParticipantStateEntry>();
}
else
// NOTE: We do NOT clear participantStates here!
// We preserve data from all scenes and update/add as needed.
// This allows Level A data to persist when saving from Level B.
int savedCount = 0;
// NEW: Broadcast global save started event (ONCE)
if (Lifecycle.LifecycleManager.Instance != null)
{
currentSaveData.participantStates.Clear();
Lifecycle.LifecycleManager.Instance.BroadcastGlobalSaveStarted();
}
// Capture state from all registered participants directly into the list
// Create a snapshot to avoid collection modification during iteration
int savedCount = 0;
// Build a dictionary of all new data to save
var allNewData = new Dictionary<string, string>();
// NEW: Collect GLOBAL data from ManagedBehaviours via LifecycleManager
if (Lifecycle.LifecycleManager.Instance != null)
{
var globalData = Lifecycle.LifecycleManager.Instance.BroadcastGlobalSaveRequested();
foreach (var kvp in globalData)
{
allNewData[kvp.Key] = kvp.Value;
}
Logging.Debug($"[SaveLoadManager] Collected {globalData.Count} GLOBAL save states");
}
// NEW: Collect SCENE data from all loaded scenes
if (Lifecycle.LifecycleManager.Instance != null)
{
var sceneData = Lifecycle.LifecycleManager.Instance.BroadcastSceneSaveRequested();
foreach (var kvp in sceneData)
{
allNewData[kvp.Key] = kvp.Value;
}
Logging.Debug($"[SaveLoadManager] Collected {sceneData.Count} SCENE save states");
}
// EXISTING: Collect data from ISaveParticipants (backward compatibility)
foreach (var kvp in participants.ToList())
{
string saveId = kvp.Key;
@@ -407,13 +638,8 @@ namespace Core.SaveLoad
try
{
string serializedState = participant.SerializeState();
currentSaveData.participantStates.Add(new ParticipantStateEntry
{
saveId = saveId,
serializedState = serializedState
});
savedCount++;
Logging.Debug($"[SaveLoadManager] Captured state for participant: {saveId}");
allNewData[saveId] = serializedState;
Logging.Debug($"[SaveLoadManager] Captured state for ISaveParticipant: {saveId}");
}
catch (Exception ex)
{
@@ -421,7 +647,28 @@ namespace Core.SaveLoad
}
}
Logging.Debug($"[SaveLoadManager] Captured state from {savedCount} participants");
// Update existing entries or add new ones (preserves data from unloaded scenes)
foreach (var kvp in allNewData)
{
var existingEntry = currentSaveData.participantStates.Find(e => e.saveId == kvp.Key);
if (existingEntry != null)
{
// Update existing entry
existingEntry.serializedState = kvp.Value;
}
else
{
// Add new entry
currentSaveData.participantStates.Add(new ParticipantStateEntry
{
saveId = kvp.Key,
serializedState = kvp.Value
});
}
savedCount++;
}
Logging.Debug($"[SaveLoadManager] Captured state from {savedCount} total participants");
json = JsonUtility.ToJson(currentSaveData, true);
@@ -468,7 +715,7 @@ namespace Core.SaveLoad
{
IsSaving = false;
OnSaveCompleted?.Invoke(slot);
Debug.Log($"[SaveLoadManager] Save completed for slot '{slot}'");
Logging.Debug($"[SaveLoadManager] Save completed for slot '{slot}'");
}
}
@@ -540,6 +787,12 @@ namespace Core.SaveLoad
// Restore state for any already-registered participants
RestoreAllParticipantStates();
// NEW: Broadcast global load completed event (ONCE, on boot)
if (Lifecycle.LifecycleManager.Instance != null)
{
Lifecycle.LifecycleManager.Instance.BroadcastGlobalLoadCompleted();
}
OnLoadCompleted?.Invoke(slot);
Logging.Debug($"[SaveLoadManager] Load completed for slot '{slot}'");
}

View File

@@ -0,0 +1,148 @@
using UnityEngine;
using UnityEngine.Playables;
using Core.Lifecycle;
namespace Core
{
/// <summary>
/// Saveable data for PlayableDirector state
/// </summary>
[System.Serializable]
public class PlayableDirectorSaveData
{
public bool wasPlayed; // Has the timeline been played?
public bool wasCompleted; // Did it complete playback?
public double playbackTime; // Current playback position
}
/// <summary>
/// Makes a PlayableDirector (Timeline) saveable.
/// On load, if the timeline was completed, it seeks to the end to ensure
/// all timeline-activated objects are in their final state.
/// </summary>
[RequireComponent(typeof(PlayableDirector))]
public class SaveablePlayableDirector : ManagedBehaviour
{
private PlayableDirector _director;
private bool _hasPlayed = false;
private bool _hasCompleted = false;
// Enable save/load participation
public override bool AutoRegisterForSave => true;
internal override void OnManagedAwake()
{
base.OnManagedAwake();
_director = GetComponent<PlayableDirector>();
if (_director != null)
{
_director.stopped += OnDirectorStopped;
_director.played += OnDirectorPlayed;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (_director != null)
{
_director.stopped -= OnDirectorStopped;
_director.played -= OnDirectorPlayed;
}
}
private void OnDirectorPlayed(PlayableDirector director)
{
_hasPlayed = true;
}
private void OnDirectorStopped(PlayableDirector director)
{
_hasCompleted = true;
}
#region Save/Load Implementation
internal override string OnSceneSaveRequested()
{
var saveData = new PlayableDirectorSaveData
{
wasPlayed = _hasPlayed,
wasCompleted = _hasCompleted,
playbackTime = _director != null ? _director.time : 0
};
return JsonUtility.ToJson(saveData);
}
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Warning($"[SaveablePlayableDirector] No save data to restore for {gameObject.name}");
return;
}
var saveData = JsonUtility.FromJson<PlayableDirectorSaveData>(serializedData);
if (saveData == null || _director == null)
return;
_hasPlayed = saveData.wasPlayed;
_hasCompleted = saveData.wasCompleted;
if (_hasCompleted)
{
// Seek to the end of the timeline to apply all final states
// This ensures objects activated/deactivated by the timeline are in correct state
_director.time = _director.duration;
_director.Evaluate(); // Force evaluation to apply the state
Logging.Debug($"[SaveablePlayableDirector] Restored completed timeline '{gameObject.name}' - seeked to end");
}
else if (_hasPlayed && saveData.playbackTime > 0)
{
// If it was playing but not completed, restore the playback position
_director.time = saveData.playbackTime;
_director.Evaluate();
Logging.Debug($"[SaveablePlayableDirector] Restored timeline '{gameObject.name}' at time {saveData.playbackTime}");
}
else
{
// Timeline hasn't been played yet, ensure it's at the start
_director.time = 0;
_director.Evaluate();
Logging.Debug($"[SaveablePlayableDirector] Timeline '{gameObject.name}' not yet played - at start");
}
}
#endregion
#region Public API
/// <summary>
/// Check if this timeline has been played
/// </summary>
public bool HasPlayed => _hasPlayed;
/// <summary>
/// Check if this timeline completed playback
/// </summary>
public bool HasCompleted => _hasCompleted;
/// <summary>
/// Manually mark the timeline as completed (useful for triggering completion via code)
/// </summary>
public void MarkAsCompleted()
{
_hasCompleted = true;
_hasPlayed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a5c5614fc04140cb81e5bda7451f7b14
timeCreated: 1762360145

View File

@@ -2,17 +2,18 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using AppleHills.Core.Settings;
using Core.Lifecycle;
using Core.SaveLoad;
using UI;
using UnityEngine;
using UnityEngine.SceneManagement;
using Bootstrap;
namespace Core
{
/// <summary>
/// Singleton service for loading and unloading Unity scenes asynchronously, with events for progress and completion.
/// </summary>
public class SceneManagerService : MonoBehaviour
public class SceneManagerService : ManagedBehaviour
{
private LoadingScreenController _loadingScreen;
private static SceneManagerService _instance;
@@ -23,29 +24,37 @@ namespace Core
public static SceneManagerService Instance => _instance;
// Events for scene lifecycle
// NOTE: Most components should use lifecycle hooks (OnSceneReady, OnSceneUnloading)
// instead of subscribing to these events. Events are primarily for orchestration.
/// <summary>
/// Fired when a scene starts loading. Used by loading screen orchestration.
/// </summary>
public event Action<string> SceneLoadStarted;
public event Action<string, float> SceneLoadProgress;
/// <summary>
/// Fired when a scene finishes loading.
/// Used by loading screen orchestration and cross-scene components (e.g., PauseMenu).
/// For component initialization, use OnSceneReady() lifecycle hook instead.
/// </summary>
public event Action<string> SceneLoadCompleted;
public event Action<string> SceneUnloadStarted;
public event Action<string, float> SceneUnloadProgress;
public event Action<string> SceneUnloadCompleted;
private readonly Dictionary<string, AsyncOperation> _activeLoads = new();
private readonly Dictionary<string, AsyncOperation> _activeUnloads = new();
private LogVerbosity _logVerbosity = LogVerbosity.Debug;
private const string BootstrapSceneName = "BootstrapScene";
void Awake()
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 15; // Core infrastructure, after GameManager
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// DontDestroyOnLoad(gameObject);
// Initialize current scene tracking immediately in Awake
// Initialize current scene tracking - critical for scene management
InitializeCurrentSceneTracking();
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
// Ensure BootstrapScene is loaded at startup
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
@@ -54,9 +63,17 @@ namespace Core
}
}
private void Start()
internal override void OnManagedStart()
{
// Set up loading screen reference and events
// This must happen in ManagedStart because LoadingScreenController instance needs to be set first
_loadingScreen = LoadingScreenController.Instance;
SetupLoadingScreenEvents();
// Load verbosity settings
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
Logging.Debug($"SceneManagerService initialized, current scene is: {CurrentGameplayScene}");
}
/// <summary>
@@ -74,32 +91,21 @@ namespace Core
if (activeScene.name != BootstrapSceneName)
{
CurrentGameplayScene = activeScene.name;
LogDebugMessage($"Initialized with current scene: {CurrentGameplayScene}");
Logging.Debug($"Initialized with current scene: {CurrentGameplayScene}");
}
// Otherwise default to MainMenu
else
{
CurrentGameplayScene = "AppleHillsOverworld";
LogDebugMessage($"Initialized with default scene: {CurrentGameplayScene}");
Logging.Debug($"Initialized with default scene: {CurrentGameplayScene}");
}
}
else
{
CurrentGameplayScene = "AppleHillsOverworld";
LogDebugMessage($"No valid active scene, defaulting to: {CurrentGameplayScene}");
Logging.Debug($"No valid active scene, defaulting to: {CurrentGameplayScene}");
}
}
private void InitializePostBoot()
{
// Set up loading screen reference and events after boot is complete
_loadingScreen = LoadingScreenController.Instance;
// Set up loading screen event handlers if available
SetupLoadingScreenEvents();
LogDebugMessage($"Post-boot initialization complete, current scene is: {CurrentGameplayScene}");
}
private void SetupLoadingScreenEvents()
{
@@ -122,7 +128,6 @@ namespace Core
while (!op.isDone)
{
progress?.Report(op.progress);
SceneLoadProgress?.Invoke(sceneName, op.progress);
await Task.Yield();
}
_activeLoads.Remove(sceneName);
@@ -142,17 +147,15 @@ namespace Core
Logging.Warning($"[SceneManagerService] Attempted to unload scene '{sceneName}', but it is not loaded.");
return;
}
SceneUnloadStarted?.Invoke(sceneName);
var op = SceneManager.UnloadSceneAsync(sceneName);
_activeUnloads[sceneName] = op;
while (!op.isDone)
{
progress?.Report(op.progress);
SceneUnloadProgress?.Invoke(sceneName, op.progress);
await Task.Yield();
}
_activeUnloads.Remove(sceneName);
SceneUnloadCompleted?.Invoke(sceneName);
}
/// <summary>
@@ -230,7 +233,6 @@ namespace Core
var op = SceneManager.UnloadSceneAsync(name);
_activeUnloads[name] = op;
ops.Add(op);
SceneUnloadStarted?.Invoke(name);
}
while (done < total)
@@ -251,7 +253,6 @@ namespace Core
foreach (var name in sceneNames)
{
_activeUnloads.Remove(name);
SceneUnloadCompleted?.Invoke(name);
}
// Hide loading screen after all scenes are unloaded
@@ -280,9 +281,9 @@ namespace Core
// Tracks the currently loaded gameplay scene (not persistent/bootstrapper)
public string CurrentGameplayScene { get; set; } = "AppleHillsOverworld";
public async Task ReloadCurrentScene(IProgress<float> progress = null, bool autoHideLoadingScreen = true)
public async Task ReloadCurrentScene(IProgress<float> progress = null, bool autoHideLoadingScreen = true, bool skipSave = false)
{
await SwitchSceneAsync(CurrentGameplayScene, progress, autoHideLoadingScreen);
await SwitchSceneAsync(CurrentGameplayScene, progress, autoHideLoadingScreen, skipSave);
}
/// <summary>
@@ -291,15 +292,45 @@ namespace Core
/// <param name="newSceneName">Name of the scene to load</param>
/// <param name="progress">Optional progress reporter</param>
/// <param name="autoHideLoadingScreen">Whether to automatically hide the loading screen when complete. If false, caller must hide it manually.</param>
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null, bool autoHideLoadingScreen = true)
/// <param name="skipSave">If true, skips saving scene data during transition. Useful for level restart to prevent re-saving cleared data.</param>
public async Task SwitchSceneAsync(string newSceneName, IProgress<float> progress = null, bool autoHideLoadingScreen = true, bool skipSave = false)
{
// Show loading screen at the start (whether using auto-hide or not)
if (_loadingScreen != null && !_loadingScreen.IsActive)
string oldSceneName = CurrentGameplayScene;
// PHASE 1: Show loading screen at the start
// Use explicit progress provider to combine unload + load progress
if (_loadingScreen != null)
{
_loadingScreen.ShowLoadingScreen();
_loadingScreen.ShowLoadingScreen(() => GetAggregateLoadProgress());
}
// Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
// PHASE 2: Broadcast scene unloading - notify components to cleanup
Logging.Debug($"Broadcasting OnSceneUnloading for: {oldSceneName}");
LifecycleManager.Instance?.BroadcastSceneUnloading(oldSceneName);
// PHASE 3: Save scene-specific data via SaveLoadManager (unless skipSave is true)
if (!skipSave && SaveLoadManager.Instance != null)
{
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
if (debugSettings.useSaveLoadSystem)
{
Logging.Debug($"Saving scene data for: {oldSceneName}");
SaveLoadManager.Instance.SaveSceneData();
}
}
else if (skipSave)
{
Logging.Debug($"Skipping save for: {oldSceneName} (skipSave=true)");
}
// PHASE 4: Clear PuzzleManager state before scene transition
if (PuzzleS.PuzzleManager.Instance != null)
{
Logging.Debug($"Clearing puzzle state before scene transition");
PuzzleS.PuzzleManager.Instance.ClearPuzzleState();
}
// PHASE 5: Remove all AstarPath (A* Pathfinder) singletons before loading the new scene
var astarPaths = FindObjectsByType<AstarPath>(FindObjectsSortMode.None);
foreach (var astar in astarPaths)
{
@@ -308,43 +339,61 @@ namespace Core
else
DestroyImmediate(astar.gameObject);
}
// Unload previous gameplay scene (if not BootstrapScene and not same as new)
if (!string.IsNullOrEmpty(CurrentGameplayScene)&& CurrentGameplayScene != BootstrapSceneName)
// PHASE 6: Unload previous gameplay scene (Unity will call OnDestroy → OnManagedDestroy)
if (!string.IsNullOrEmpty(oldSceneName) && oldSceneName != BootstrapSceneName)
{
var prevScene = SceneManager.GetSceneByName(CurrentGameplayScene);
var prevScene = SceneManager.GetSceneByName(oldSceneName);
if (prevScene.isLoaded)
{
await UnloadSceneAsync(CurrentGameplayScene);
await UnloadSceneAsync(oldSceneName);
}
else
{
Logging.Warning($"[SceneManagerService] Previous scene '{CurrentGameplayScene}' is not loaded, skipping unload.");
Logging.Warning($"Previous scene '{oldSceneName}' is not loaded, skipping unload.");
}
}
// Ensure BootstrapScene is loaded before loading new scene
// PHASE 7: Ensure BootstrapScene is loaded before loading new scene
var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName);
if (!bootstrap.isLoaded)
{
SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive);
}
// Load new gameplay scene
// PHASE 8: Begin scene loading mode - enables priority-ordered component initialization
Logging.Debug($"Beginning scene load for: {newSceneName}");
LifecycleManager.Instance?.BeginSceneLoad(newSceneName);
// PHASE 9: Load new gameplay scene
await LoadSceneAsync(newSceneName, progress);
// Update tracker
CurrentGameplayScene = newSceneName;
// Only hide the loading screen if autoHideLoadingScreen is true
// PHASE 10: Broadcast scene ready - processes batched components in priority order, then calls OnSceneReady
Logging.Debug($"Broadcasting OnSceneReady for: {newSceneName}");
LifecycleManager.Instance?.BroadcastSceneReady(newSceneName);
// PHASE 11: Restore scene-specific data via SaveLoadManager
if (!skipSave && SaveLoadManager.Instance != null)
{
var debugSettings = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>();
if (debugSettings.useSaveLoadSystem)
{
Logging.Debug($"Restoring scene data for: {newSceneName}");
SaveLoadManager.Instance.RestoreSceneData();
}
}
else if (skipSave)
{
SaveLoadManager.Instance.RestoreSceneData();
Logging.Debug($"Skipping restore for: {newSceneName} (skipSave=true)");
}
// PHASE 12: Only hide the loading screen if autoHideLoadingScreen is true
if (autoHideLoadingScreen && _loadingScreen != null)
{
_loadingScreen.HideLoadingScreen();
}
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[SceneManagerService] {message}");
}
}
}
}

View File

@@ -1,15 +1,13 @@
using System;
using System.Collections;
using System.Collections;
using AppleHills.Core.Settings;
using Bootstrap;
using Input;
using Core.Lifecycle;
using Settings;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Core
{
public class SceneOrientationEnforcer : MonoBehaviour
public class SceneOrientationEnforcer : ManagedBehaviour
{
// Singleton instance
private static SceneOrientationEnforcer _instance;
@@ -20,46 +18,60 @@ namespace Core
public GameObject orientationPromptPrefab;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
void Awake()
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 70; // Platform-specific utility
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
// Load verbosity settings early (GameManager sets up settings in its OnManagedAwake)
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
Logging.Debug("Initialized");
}
internal override void OnManagedStart()
{
// Subscribe to SceneManagerService to enforce orientation on every scene load
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
}
#if UNITY_EDITOR
// When playing in the editor, manually invoke OnSceneLoaded for the currently active scene
// When playing in the editor, manually invoke orientation check for the currently active scene
if (Application.isPlaying)
{
OnSceneLoaded(SceneManager.GetActiveScene(), LoadSceneMode.Single);
HandleSceneOrientation(SceneManager.GetActiveScene().name);
}
#endif
}
private void Start()
internal override void OnSceneReady()
{
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().sceneLogVerbosity;
}
private void InitializePostBoot()
{
// Initialize any dependencies that require other services to be ready
LogDebugMessage("Post-boot initialization complete");
// Subscribe to sceneLoaded event
SceneManager.sceneLoaded += OnSceneLoaded;
// Handle orientation when scene is ready (initial scene)
// Note: This fires for the scene that just loaded, LifecycleManager tracks which scene
string sceneName = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
HandleSceneOrientation(sceneName);
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
private void OnSceneLoadCompleted(string sceneName)
{
// Enforce orientation every time a scene is loaded via SceneManagerService
HandleSceneOrientation(sceneName);
}
private void HandleSceneOrientation(string sceneName)
{
// Determine desired orientation for this scene
string sceneName = scene.name;
ScreenOrientationRequirement requirement = ScreenOrientationRequirement.NotApplicable;
if (sceneName.ToLower().Contains("bootstrap"))
{
// Bootstrap being loaded additively, don't do anything
LogDebugMessage($"Detected bootstrapped scene: '{sceneName}'. Skipping orientation enforcement.");
Logging.Debug($"Detected bootstrapped scene: '{sceneName}'. Skipping orientation enforcement.");
return;
}
@@ -69,31 +81,37 @@ namespace Core
}
else
{
LogDebugMessage($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'");
Logging.Debug($"No orientationConfig assigned. Defaulting to Landscape for scene '{sceneName}'");
}
switch (requirement)
{
case ScreenOrientationRequirement.Portrait:
LogDebugMessage($"Forcing Portrait for scene '{sceneName}'");
Logging.Debug($"Forcing Portrait for scene '{sceneName}'");
StartCoroutine(ForcePortrait());
break;
case ScreenOrientationRequirement.Landscape:
LogDebugMessage($"Forcing Landscape for scene '{sceneName}'");
Logging.Debug($"Forcing Landscape for scene '{sceneName}'");
StartCoroutine(ForceLandscape());
break;
case ScreenOrientationRequirement.NotApplicable:
default:
// Default to landscape when no specific requirement is found
LogDebugMessage($"No specific orientation for scene '{sceneName}'. Defaulting to Landscape");
Logging.Debug($"No specific orientation for scene '{sceneName}'. Defaulting to Landscape");
StartCoroutine(ForceLandscape());
break;
}
}
void OnDestroy()
protected override void OnDestroy()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
// Unsubscribe from events to prevent memory leaks
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
base.OnDestroy(); // Important: call base
}
/// <summary>
@@ -107,12 +125,12 @@ namespace Core
if (!currentlyLandscape)
{
// Lock it to portrait and allow the device to orient itself
LogDebugMessage($"Actually forcing Portrait from previous: {Screen.orientation}");
Logging.Debug($"Actually forcing Portrait from previous: {Screen.orientation}");
Screen.orientation = ScreenOrientation.LandscapeRight;
}
else
{
LogDebugMessage($"Skipping Landscape enforcement, device already in: {Screen.orientation}");
Logging.Debug($"Skipping Landscape enforcement, device already in: {Screen.orientation}");
}
yield return null;
@@ -140,12 +158,12 @@ namespace Core
if (!currentlyPortrait)
{
// Lock it to portrait and allow the device to orient itself
LogDebugMessage($"Actually forcing Portrait from previous: {Screen.orientation}");
Logging.Debug($"Actually forcing Portrait from previous: {Screen.orientation}");
Screen.orientation = ScreenOrientation.PortraitUpsideDown;
}
else
{
LogDebugMessage($"Skipping Portrait enforcement, device already in: {Screen.orientation}");
Logging.Debug($"Skipping Portrait enforcement, device already in: {Screen.orientation}");
}
yield return null;
@@ -161,13 +179,5 @@ namespace Core
// Allow device to auto-rotate to correct portrait orientation
Screen.orientation = ScreenOrientation.AutoRotation;
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[SceneOrientationEnforcer] {message}");
}
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using Interactions;
namespace AppleHills.Core.Settings
{
@@ -88,5 +89,31 @@ namespace AppleHills.Core.Settings
}
return null;
}
/// <summary>
/// Finds a pickup prefab by its itemData.itemId.
/// Searches through combination rules to find result prefabs.
/// Used to spawn dynamically created items during save/load.
/// </summary>
public GameObject FindPickupPrefabByItemId(string itemId)
{
if (string.IsNullOrEmpty(itemId) || combinationRules == null)
return null;
// Search through combination rules to find a result prefab with matching itemId
foreach (var rule in combinationRules)
{
if (rule.resultPrefab != null)
{
var pickup = rule.resultPrefab.GetComponent<Pickup>();
if (pickup != null && pickup.itemData != null && pickup.itemData.itemId == itemId)
{
return rule.resultPrefab;
}
}
}
return null;
}
}
}

View File

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

View File

@@ -57,6 +57,7 @@ namespace AppleHills.Core.Settings
// Methods to query item configurations
CombinationRule GetCombinationRule(PickupItemData item1, PickupItemData item2);
SlotItemConfig GetSlotItemConfig(PickupItemData slotItem);
GameObject FindPickupPrefabByItemId(string itemId);
}
/// <summary>

View File

@@ -1,9 +1,9 @@
using UnityEngine;
using System;
using System.Collections;
using Core;
using Pathfinding;
// TODO: Remove movement based logic
public class AnneLiseBehaviour : MonoBehaviour
{
[SerializeField] public float moveSpeed;

View File

@@ -1,5 +1,6 @@
using UnityEngine;
// TODO: Remove this
public class LureSpot : MonoBehaviour
{
[SerializeField] public GameObject luredBird;

View File

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

View File

@@ -97,90 +97,7 @@ AnimationClip:
- time: 1.5833334
value: {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
attribute: m_Sprite
path: SoundBird
classID: 212
script: {fileID: 0}
flags: 2
- serializedVersion: 2
curve:
- time: 0
value: {fileID: -1035714051, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.033333335
value: {fileID: -740831527, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.05
value: {fileID: -648204482, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.11666667
value: {fileID: -960280295, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.13333334
value: {fileID: -1144832505, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.2
value: {fileID: -1860215682, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.25
value: {fileID: 519773293, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.26666668
value: {fileID: -1067281986, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.33333334
value: {fileID: -36811272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.38333333
value: {fileID: -1592089404, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.41666666
value: {fileID: -1729322987, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.45
value: {fileID: -91858778, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.5
value: {fileID: -26124593, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.53333336
value: {fileID: 259088195, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.6
value: {fileID: 1746085375, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.6166667
value: {fileID: -182272111, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.68333334
value: {fileID: 1436667360, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.73333335
value: {fileID: 545467259, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.75
value: {fileID: 121392657, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8
value: {fileID: 938631806, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8333333
value: {fileID: 1943282875, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8833333
value: {fileID: -1918772169, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.93333334
value: {fileID: -1252794517, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.96666664
value: {fileID: -927331073, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.0166667
value: {fileID: -1038168376, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.0833334
value: {fileID: 1855149249, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1
value: {fileID: -2116798272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1666666
value: {fileID: 2078607702, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1833333
value: {fileID: -633261939, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.2333333
value: {fileID: -86103801, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.2833333
value: {fileID: 1380056380, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.3166667
value: {fileID: 1797284751, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.3666667
value: {fileID: 2004539437, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.4166666
value: {fileID: 1984933759, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.45
value: {fileID: -89013944, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5
value: {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5166667
value: {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5833334
value: {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
attribute: m_Sprite
path: SoundBirdTakeoff/SoundBirdTakeOffAnim
path:
classID: 212
script: {fileID: 0}
flags: 2
@@ -192,16 +109,7 @@ AnimationClip:
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 1707885837
attribute: 0
script: {fileID: 0}
typeID: 212
customType: 23
isPPtrCurve: 1
isIntCurve: 0
isSerializeReferenceCurve: 0
- serializedVersion: 2
path: 631576921
path: 0
attribute: 0
script: {fileID: 0}
typeID: 212
@@ -248,44 +156,6 @@ AnimationClip:
- {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1035714051, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -740831527, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -648204482, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -960280295, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1144832505, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1860215682, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 519773293, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1067281986, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -36811272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1592089404, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1729322987, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -91858778, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -26124593, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 259088195, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1746085375, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -182272111, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1436667360, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 545467259, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 121392657, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 938631806, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1943282875, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1918772169, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1252794517, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -927331073, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1038168376, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1855149249, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -2116798272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 2078607702, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -633261939, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -86103801, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1380056380, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1797284751, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 2004539437, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1984933759, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -89013944, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}

View File

@@ -1,4 +1,5 @@
using Core;
using Core.SaveLoad;
using Pixelplacement;
using UnityEngine;
@@ -8,7 +9,7 @@ public class SoundGenerator : MonoBehaviour
[SerializeField] private Sprite exitSprite;
[SerializeField] private AudioClip enterSound;
[SerializeField] private AppleAudioSource audioSource;
[SerializeField] private StateMachine soundBirdSMRef;
[SerializeField] private AppleMachine soundBirdSMRef;
[SerializeField] private soundBird_CanFly soundbirdHearingCheck;
private bool playerInside = false;
@@ -37,7 +38,7 @@ public class SoundGenerator : MonoBehaviour
{
audioSource.audioSource.PlayOneShot(enterSound);
}
if (soundBirdSMRef != null && soundBirdSMRef.currentState.name == "SoundBird" && soundbirdHearingCheck.canFly == true)
if (soundBirdSMRef != null && soundBirdSMRef.currentState.name.ToLower().Contains("soundbird_slot") && soundbirdHearingCheck.canFly == true)
{
soundBirdSMRef.ChangeState("SoundBirdTakeoff");

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using Unity.Cinemachine;
using System.Collections;
using Core.SaveLoad;
using Pixelplacement;
public class cameraSwitcher : MonoBehaviour
@@ -12,7 +13,7 @@ public class cameraSwitcher : MonoBehaviour
[SerializeField] private float transitionDuration = 0.5f; // Duration of the transition
[SerializeField] private soundBird_FlyingBehaviour flyingBehaviour;
[SerializeField] private soundBird_TakeOffBehaviour takeOffBehaviour; // New reference
[SerializeField] private StateMachine birdStateMachine;
[SerializeField] private AppleMachine birdStateMachine;
private int playerInsideCount = 0;
private Coroutine zoomCoroutine;
@@ -32,6 +33,9 @@ public class cameraSwitcher : MonoBehaviour
private void OnTriggerExit2D(Collider2D other)
{
if (!gameObject.activeInHierarchy)
return;
if (other.CompareTag("Player"))
{
playerInsideCount--;

View File

@@ -1,23 +1,52 @@
using Core;
using UnityEngine;
using Core.Lifecycle;
public class soundBird_CanFly : MonoBehaviour
[System.Serializable]
public class SoundBirdSaveData
{
public bool canFly;
}
public class soundBird_CanFly : ManagedBehaviour
{
public bool canFly = true;
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
}
// Enable save/load participation
public override bool AutoRegisterForSave => true;
public void birdCanHear(bool canhear)
{
if (canhear)
canFly = canhear;
}
#region Save/Load Implementation
internal override string OnSceneSaveRequested()
{
var saveData = new SoundBirdSaveData
{
canFly = true;
canFly = this.canFly
};
return JsonUtility.ToJson(saveData);
}
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Warning($"[soundBird_CanFly] No save data to restore for {gameObject.name}");
return;
}
else
var saveData = JsonUtility.FromJson<SoundBirdSaveData>(serializedData);
if (saveData != null)
{
canFly = false;
canFly = saveData.canFly;
Logging.Debug($"[soundBird_CanFly] Restored canFly state: {canFly}");
}
}
#endregion
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -10,7 +11,7 @@ public class soundBird_FlyingBehaviour : MonoBehaviour
public float flightDelay;
public float cooldownTime;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
//private Coroutine cooldownCoroutine;
@@ -21,7 +22,7 @@ public class soundBird_FlyingBehaviour : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -9,7 +10,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
public float flightDuration;
public float flightDelay;
public soundBird_FlyingBehaviour flyingBehaviour;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
@@ -18,7 +19,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}
@@ -52,7 +53,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
if (stateMachine != null)
{
animator.SetBool("isScared", false);
stateMachine.ChangeState("SoundBird"); // Change to the desired state name
stateMachine.ChangeState(0); // Change to the desired state name
}
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -9,7 +10,7 @@ public class soundBird_TakeOffBehaviour : MonoBehaviour
public Transform SoundBirdObject;
public float flightDuration;
public float flightDelay;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
public soundBird_FlyingBehaviour flyingBehaviour;
@@ -18,7 +19,7 @@ public class soundBird_TakeOffBehaviour : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}
// Start is called once before the first execution of Update after the MonoBehaviour is created

View File

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

View File

@@ -2,23 +2,27 @@
using System.Collections.Generic;
using System.Linq;
using AppleHills.Data.CardSystem;
using Bootstrap;
using Core;
using Core.SaveLoad;
using Core.Lifecycle;
using UnityEngine;
namespace Data.CardSystem
{
/// <summary>
/// Manages the player's card collection, booster packs, and related operations.
/// Uses a singleton pattern for global access.
/// Implements ISaveParticipant to integrate with the save/load system.
/// Manages the card collection system for the game.
/// Handles unlocking cards, tracking collections, and integrating with the save/load system.
/// </summary>
public class CardSystemManager : MonoBehaviour, ISaveParticipant
public class CardSystemManager : ManagedBehaviour
{
private static CardSystemManager _instance;
public static CardSystemManager Instance => _instance;
// Save system configuration
public override bool AutoRegisterForSave => true;
public override string SaveId => "CardSystemManager";
[Header("Card Collection")]
[SerializeField] private List<CardDefinition> availableCards = new List<CardDefinition>();
@@ -30,30 +34,30 @@ namespace Data.CardSystem
private HashSet<string> _placedInAlbumCardIds = new HashSet<string>();
// Dictionary to quickly look up card definitions by ID
private Dictionary<string, CardDefinition> _definitionLookup = new Dictionary<string, CardDefinition>();
private Dictionary<string, CardDefinition> _definitionLookup;
private bool _lookupInitialized = false;
// Event callbacks using System.Action
public event Action<List<CardData>> OnBoosterOpened;
public event Action<CardData> OnCardCollected;
public event Action<CardData> OnCardRarityUpgraded;
public event Action<int> OnBoosterCountChanged;
public event Action<CardData> OnPendingCardAdded;
public event Action<CardData> OnCardPlacedInAlbum;
private void Awake()
public override int ManagedAwakePriority => 60; // Data systems
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void InitializePostBoot()
{
// Load card definitions from Addressables, then register with save system
LoadCardDefinitionsFromAddressables();
Logging.Debug("[CardSystemManager] Post-boot initialization complete");
}
internal override void OnManagedStart()
{
Logging.Debug("[CardSystemManager] Initialized");
}
/// <summary>
@@ -88,17 +92,6 @@ namespace Data.CardSystem
BuildDefinitionLookup();
Logging.Debug($"[CardSystemManager] Loaded {availableCards.Count} card definitions from Addressables");
// NOW register with save/load system (definitions are ready for state restoration)
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.RegisterParticipant(this);
Logging.Debug("[CardSystemManager] Registered with SaveLoadManager after definitions loaded");
}
else
{
Logging.Warning("[CardSystemManager] SaveLoadManager not available for registration");
}
}
else
{
@@ -106,20 +99,17 @@ namespace Data.CardSystem
}
}
private void OnDestroy()
{
// Unregister from save/load system
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.UnregisterParticipant(GetSaveId());
}
}
/// <summary>
/// Builds a lookup dictionary for quick access to card definitions by ID
/// </summary>
private void BuildDefinitionLookup()
{
if (_definitionLookup == null)
{
_definitionLookup = new Dictionary<string, CardDefinition>();
}
_definitionLookup.Clear();
foreach (var cardDef in availableCards)
@@ -139,6 +129,8 @@ namespace Data.CardSystem
cardData.SetDefinition(def);
}
}
_lookupInitialized = true;
}
/// <summary>
@@ -647,10 +639,16 @@ namespace Data.CardSystem
/// <summary>
/// Apply a previously saved snapshot to the runtime inventory
/// </summary>
public void ApplyCardCollectionState(CardCollectionState state)
public async void ApplyCardCollectionState(CardCollectionState state)
{
if (state == null) return;
// Wait for lookup to be initialized before loading
while (!_lookupInitialized)
{
await System.Threading.Tasks.Task.Yield();
}
playerInventory.ClearAllCards();
_pendingRevealCards.Clear();
_placedInAlbumCardIds.Clear();
@@ -701,42 +699,35 @@ namespace Data.CardSystem
}
}
#region ISaveParticipant Implementation
private bool hasBeenRestored;
/// <summary>
/// Returns true if this participant has already had its state restored.
/// Clears all card collection data - inventory, pending cards, boosters, and placement tracking
/// Used for dev reset functionality
/// </summary>
public bool HasBeenRestored => hasBeenRestored;
/// <summary>
/// Returns the unique save ID for the CardSystemManager.
/// Since this is a singleton global system, the ID is constant.
/// </summary>
public string GetSaveId()
public void ClearAllCollectionData()
{
return "CardSystemManager";
playerInventory.ClearAllCards();
playerInventory.BoosterPackCount = 0;
_pendingRevealCards.Clear();
_placedInAlbumCardIds.Clear();
OnBoosterCountChanged?.Invoke(0);
Logging.Debug("[CardSystemManager] Cleared all collection data (inventory, boosters, pending, placement tracking)");
}
/// <summary>
/// Serializes the current card collection state to JSON.
/// </summary>
public string SerializeState()
#region Save/Load Lifecycle Hooks
internal override string OnGlobalSaveRequested()
{
var state = ExportCardCollectionState();
return JsonUtility.ToJson(state);
}
/// <summary>
/// Restores the card collection state from serialized JSON data.
/// </summary>
public void RestoreState(string serializedData)
internal override void OnGlobalRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Debug("[CardSystemManager] No saved state to restore, using defaults");
hasBeenRestored = true;
return;
}
@@ -746,7 +737,6 @@ namespace Data.CardSystem
if (state != null)
{
ApplyCardCollectionState(state);
hasBeenRestored = true;
Logging.Debug("[CardSystemManager] Successfully restored card collection state");
}
else

View File

@@ -1,8 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Bootstrap;
using System.Collections.Generic;
using Core;
using Core.Lifecycle;
using Interactions;
using UnityEngine;
using PuzzleS;
@@ -12,7 +10,7 @@ namespace Dialogue
{
[AddComponentMenu("AppleHills/Dialogue/Dialogue Component")]
[RequireComponent(typeof(AppleAudioSource))]
public class DialogueComponent : MonoBehaviour
public class DialogueComponent : ManagedBehaviour
{
[SerializeField] private RuntimeDialogueGraph dialogueGraph;
@@ -35,7 +33,9 @@ namespace Dialogue
public string CurrentSpeakerName => dialogueGraph?.speakerName;
private void Start()
public override int ManagedAwakePriority => 150; // Dialogue systems
internal override void OnManagedStart()
{
// Get required components
appleAudioSource = GetComponent<AppleAudioSource>();
@@ -58,11 +58,6 @@ namespace Dialogue
speechBubble.UpdatePromptVisibility(HasAnyLines());
}
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void InitializePostBoot()
{
// Register for global events
PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted;
ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp;
@@ -189,8 +184,10 @@ namespace Dialogue
return null;
}
private void OnDestroy()
protected override void OnDestroy()
{
base.OnDestroy();
// Unregister from events
if (PuzzleManager.Instance != null)
PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted;

View File

@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic; // Added for List<ITouchInputConsumer>
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using AppleHills.Core.Settings;
using Bootstrap;
using Core; // Added for IInteractionSettings
using Core;
using Core.Lifecycle;
namespace Input
{
@@ -22,7 +21,7 @@ namespace Input
/// Handles input events and dispatches them to the appropriate ITouchInputConsumer.
/// Supports tap and hold/drag logic, with interactable delegation and debug logging.
/// </summary>
public class InputManager : MonoBehaviour
public class InputManager : ManagedBehaviour
{
private const string UiActions = "UI";
private const string GameActions = "PlayerTouch";
@@ -51,33 +50,27 @@ namespace Input
private bool isHoldActive;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
void Awake()
public override int ManagedAwakePriority => 25; // Input infrastructure
internal override void OnManagedAwake()
{
// Set instance immediately (early initialization)
_instance = this;
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
private void Start()
{
// Load verbosity settings early
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().inputLogVerbosity;
}
private void InitializePostBoot()
{
// Subscribe to scene load completed events now that boot is complete
SceneManagerService.Instance.SceneLoadCompleted += SwitchInputOnSceneLoaded;
// Initialize settings reference
// Initialize settings reference early (GameManager sets these up in its Awake)
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
// Set up PlayerInput component and actions - critical for input to work
playerInput = GetComponent<PlayerInput>();
if (playerInput == null)
{
Debug.LogError("[InputManager] InputManager requires a PlayerInput component attached to the same GameObject.");
return;
}
tapMoveAction = playerInput.actions.FindAction("TapMove", false);
holdMoveAction = playerInput.actions.FindAction("HoldMove", false);
positionAction = playerInput.actions.FindAction("TouchPosition", false);
@@ -90,14 +83,39 @@ namespace Input
holdMoveAction.canceled += OnHoldMoveCanceled;
}
// Initialize input mode for current scene
SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name);
}
private void OnDestroy()
internal override void OnManagedStart()
{
// Unsubscribe from SceneManagerService
// Subscribe to scene load events from SceneManagerService
// This must happen in ManagedStart because SceneManagerService instance needs to be set first
if (SceneManagerService.Instance != null)
SceneManagerService.Instance.SceneLoadCompleted -= SwitchInputOnSceneLoaded;
{
SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted;
}
}
/// <summary>
/// Called when any scene finishes loading. Restores input to GameAndUI mode.
/// </summary>
private void OnSceneLoadCompleted(string sceneName)
{
Logging.Debug($"Scene loaded: {sceneName}, restoring input mode");
SwitchInputOnSceneLoaded(sceneName);
}
protected override void OnDestroy()
{
// Unsubscribe from SceneManagerService events
if (SceneManagerService.Instance != null)
{
SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted;
}
base.OnDestroy();
// Input action cleanup happens automatically
}
private void SwitchInputOnSceneLoaded(string sceneName)
@@ -162,24 +180,24 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"TapMove performed at {worldPos2D}");
Logging.Debug($"TapMove performed at {worldPos2D}");
// First try to delegate to an override consumer if available
if (TryDelegateToOverrideConsumer(screenPos, worldPos2D))
{
LogDebugMessage("Tap delegated to override consumer");
Logging.Debug("Tap delegated to override consumer");
return;
}
// Then try to delegate to any ITouchInputConsumer (UI or world interactable)
if (!TryDelegateToAnyInputConsumer(screenPos, worldPos2D))
{
LogDebugMessage("No input consumer found, forwarding tap to default consumer");
Logging.Debug("No input consumer found, forwarding tap to default consumer");
defaultConsumer?.OnTap(worldPos2D);
}
else
{
LogDebugMessage("Tap delegated to input consumer");
Logging.Debug("Tap delegated to input consumer");
}
}
@@ -192,13 +210,13 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"HoldMove started at {worldPos2D}");
Logging.Debug($"HoldMove started at {worldPos2D}");
// First check for override consumers
if (_overrideConsumers.Count > 0)
{
_activeHoldConsumer = _overrideConsumers[_overrideConsumers.Count - 1];
LogDebugMessage($"Hold delegated to override consumer: {_activeHoldConsumer}");
Logging.Debug($"Hold delegated to override consumer: {_activeHoldConsumer}");
_activeHoldConsumer.OnHoldStart(worldPos2D);
return;
}
@@ -218,7 +236,7 @@ namespace Input
Vector2 screenPos = positionAction.ReadValue<Vector2>();
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
Vector2 worldPos2D = new Vector2(worldPos.x, worldPos.y);
LogDebugMessage($"HoldMove canceled at {worldPos2D}");
Logging.Debug($"HoldMove canceled at {worldPos2D}");
// Notify the active hold consumer that the hold has ended
_activeHoldConsumer?.OnHoldEnd(worldPos2D);
@@ -282,7 +300,7 @@ namespace Input
}
if (consumer != null)
{
LogDebugMessage($"Delegating tap to UI consumer at {screenPos} (GameObject: {result.gameObject.name})");
Logging.Debug($"Delegating tap to UI consumer at {screenPos} (GameObject: {result.gameObject.name})");
consumer.OnTap(screenPos);
return true;
}
@@ -311,7 +329,7 @@ namespace Input
}
if (consumer != null)
{
LogDebugMessage($"Delegating tap to consumer at {worldPos} (GameObject: {hitWithMask.gameObject.name})");
Logging.Debug($"Delegating tap to consumer at {worldPos} (GameObject: {hitWithMask.gameObject.name})");
consumer.OnTap(worldPos);
return true;
}
@@ -325,15 +343,15 @@ namespace Input
var consumer = hit.GetComponent<ITouchInputConsumer>();
if (consumer != null)
{
LogDebugMessage($"Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})");
Logging.Debug($"Delegating tap to consumer at {worldPos} (GameObject: {hit.gameObject.name})");
consumer.OnTap(worldPos);
return true;
}
LogDebugMessage($"Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found.");
Logging.Debug($"Collider2D hit at {worldPos} (GameObject: {hit.gameObject.name}), but no ITouchInputConsumer found.");
}
else
{
LogDebugMessage($"No Collider2D found at {worldPos} for interactable delegation.");
Logging.Debug($"No Collider2D found at {worldPos} for interactable delegation.");
}
return false;
}
@@ -348,7 +366,7 @@ namespace Input
return;
_overrideConsumers.Add(consumer);
LogDebugMessage($"Override consumer registered: {consumer}");
Logging.Debug($"Override consumer registered: {consumer}");
}
/// <summary>
@@ -366,7 +384,7 @@ namespace Input
}
_overrideConsumers.Remove(consumer);
LogDebugMessage($"Override consumer unregistered: {consumer}");
Logging.Debug($"Override consumer unregistered: {consumer}");
}
/// <summary>
@@ -376,7 +394,7 @@ namespace Input
{
_activeHoldConsumer = null;
_overrideConsumers.Clear();
LogDebugMessage("All override consumers cleared.");
Logging.Debug("All override consumers cleared.");
}
/// <summary>
@@ -389,17 +407,9 @@ namespace Input
// Get the topmost override consumer (last registered)
var consumer = _overrideConsumers[_overrideConsumers.Count - 1];
LogDebugMessage($"Delegating tap to override consumer at {worldPos} (GameObject: {consumer})");
Logging.Debug($"Delegating tap to override consumer at {worldPos} (GameObject: {consumer})");
consumer.OnTap(worldPos);
return true;
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[InputManager] {message}");
}
}
}
}

View File

@@ -2,8 +2,7 @@
using Pathfinding;
using AppleHills.Core.Settings;
using Core;
using Core.SaveLoad;
using Bootstrap;
using Core.Lifecycle;
namespace Input
{
@@ -21,7 +20,7 @@ namespace Input
/// Handles player movement in response to tap and hold input events.
/// Supports both direct and pathfinding movement modes, and provides event/callbacks for arrival/cancellation.
/// </summary>
public class PlayerTouchController : MonoBehaviour, ITouchInputConsumer, ISaveParticipant
public class PlayerTouchController : ManagedBehaviour, ITouchInputConsumer
{
// --- Movement State ---
private Vector3 targetPosition;
@@ -67,10 +66,13 @@ namespace Input
private bool interruptMoveTo;
private LogVerbosity _logVerbosity = LogVerbosity.Warning;
// Save system tracking
private bool hasBeenRestored;
void Awake()
// Save system configuration
public override bool AutoRegisterForSave => true;
// Scene-specific SaveId - each level has its own player state
public override string SaveId => $"{gameObject.scene.name}/PlayerController";
public override int ManagedAwakePriority => 100; // Player controller
internal override void OnManagedStart()
{
aiPath = GetComponent<AIPath>();
artTransform = transform.Find("CharacterArt");
@@ -87,39 +89,12 @@ namespace Input
// Initialize settings reference using GetSettingsObject
_settings = GameManager.GetSettingsObject<IPlayerFollowerSettings>();
// Register for post-boot initialization
BootCompletionService.RegisterInitAction(InitializePostBoot);
}
void Start()
{
// Set default input consumer
InputManager.Instance?.SetDefaultConsumer(this);
_logVerbosity = DeveloperSettingsProvider.Instance.GetSettings<DebugSettings>().inputLogVerbosity;
}
private void InitializePostBoot()
{
// Register with save system after boot
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.RegisterParticipant(this);
Logging.Debug("[PlayerTouchController] Registered with SaveLoadManager");
}
else
{
Logging.Warning("[PlayerTouchController] SaveLoadManager not available for registration");
}
}
void OnDestroy()
{
// Unregister from save system
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.UnregisterParticipant(GetSaveId());
}
}
/// <summary>
/// Handles tap input. Always uses pathfinding to move to the tapped location.
/// Cancels any in-progress MoveToAndNotify.
@@ -127,7 +102,7 @@ namespace Input
public void OnTap(Vector2 worldPosition)
{
InterruptMoveTo();
LogDebugMessage($"OnTap at {worldPosition}");
Logging.Debug($"OnTap at {worldPosition}");
if (aiPath != null)
{
aiPath.enabled = true;
@@ -146,7 +121,7 @@ namespace Input
public void OnHoldStart(Vector2 worldPosition)
{
InterruptMoveTo();
LogDebugMessage($"OnHoldStart at {worldPosition}");
Logging.Debug($"OnHoldStart at {worldPosition}");
lastHoldPosition = worldPosition;
isHolding = true;
if (_settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding &&
@@ -183,7 +158,7 @@ namespace Input
/// </summary>
public void OnHoldEnd(Vector2 worldPosition)
{
LogDebugMessage($"OnHoldEnd at {worldPosition}");
Logging.Debug($"OnHoldEnd at {worldPosition}");
isHolding = false;
directMoveVelocity = Vector3.zero;
if (aiPath != null && _settings.DefaultHoldMovementMode ==
@@ -359,13 +334,13 @@ namespace Input
{
_isMoving = true;
OnMovementStarted?.Invoke();
LogDebugMessage("Movement started");
Logging.Debug("Movement started");
}
else if (!isCurrentlyMoving && _isMoving)
{
_isMoving = false;
OnMovementStopped?.Invoke();
LogDebugMessage("Movement stopped");
Logging.Debug("Movement stopped");
}
}
@@ -448,25 +423,10 @@ namespace Input
OnArrivedAtTarget?.Invoke();
}
}
private void LogDebugMessage(string message)
{
if (_logVerbosity <= LogVerbosity.Debug)
{
Logging.Debug($"[PlayerTouchController] {message}");
}
}
#region ISaveParticipant Implementation
#region Save/Load Lifecycle Hooks
public bool HasBeenRestored => hasBeenRestored;
public string GetSaveId()
{
return "PlayerController";
}
public string SerializeState()
internal override string OnSceneSaveRequested()
{
var saveData = new PlayerSaveData
{
@@ -476,12 +436,11 @@ namespace Input
return JsonUtility.ToJson(saveData);
}
public void RestoreState(string serializedData)
internal override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Debug("[PlayerTouchController] No saved state to restore");
hasBeenRestored = true;
return;
}
@@ -492,7 +451,6 @@ namespace Input
{
transform.position = saveData.worldPosition;
transform.rotation = saveData.worldRotation;
hasBeenRestored = true;
Logging.Debug($"[PlayerTouchController] Restored position: {saveData.worldPosition}");
}
}

View File

@@ -5,6 +5,7 @@ using System.Collections.Generic;
using UnityEngine.Events;
using System.Threading.Tasks;
using Core;
using Core.Lifecycle;
namespace Interactions
{
@@ -20,7 +21,7 @@ namespace Interactions
/// Base class for interactable objects that can respond to tap input events.
/// Subclasses should override OnCharacterArrived() to implement interaction-specific logic.
/// </summary>
public class InteractableBase : MonoBehaviour, ITouchInputConsumer
public class InteractableBase : ManagedBehaviour, ITouchInputConsumer
{
[Header("Interaction Settings")]
public bool isOneTime;
@@ -33,21 +34,16 @@ namespace Interactions
public UnityEvent characterArrived;
public UnityEvent<bool> interactionComplete;
// Helpers for managing interaction state
private bool _interactionInProgress;
protected PlayerTouchController _playerRef;
protected FollowerController _followerController;
private bool _isActive = true;
private InteractionEventType _currentEventType;
private PlayerTouchController playerRef;
protected FollowerController FollowerController;
private bool isActive = true;
// Action component system
private List<InteractionActionBase> _registeredActions = new List<InteractionActionBase>();
private void Awake()
{
// Subscribe to interactionComplete event
interactionComplete.AddListener(OnInteractionComplete);
}
// ManagedBehaviour configuration
public override int ManagedAwakePriority => 100; // Gameplay base classes
/// <summary>
/// Register an action component with this interactable
@@ -73,14 +69,12 @@ namespace Interactions
/// </summary>
private async Task DispatchEventAsync(InteractionEventType eventType)
{
_currentEventType = eventType;
// Collect all tasks from actions that want to respond
List<Task<bool>> tasks = new List<Task<bool>>();
foreach (var action in _registeredActions)
{
Task<bool> task = action.OnInteractionEvent(eventType, _playerRef, _followerController);
Task<bool> task = action.OnInteractionEvent(eventType, playerRef, FollowerController);
if (task != null)
{
tasks.Add(task);
@@ -97,39 +91,178 @@ namespace Interactions
/// <summary>
/// Handles tap input. Triggers interaction logic.
/// Can be overridden for fully custom interaction logic.
/// </summary>
public void OnTap(Vector2 worldPosition)
public virtual void OnTap(Vector2 worldPosition)
{
if (!_isActive)
// 1. High-level validation
if (!CanBeClicked())
{
Logging.Debug($"[Interactable] Is disabled!");
return;
return; // Silent failure
}
Logging.Debug($"[Interactable] OnTap at {worldPosition} on {gameObject.name}");
// Start the interaction process asynchronously
_ = TryInteractAsync();
_ = StartInteractionFlowAsync();
}
private async Task TryInteractAsync()
/// <summary>
/// Template method that orchestrates the entire interaction flow.
/// </summary>
private async Task StartInteractionFlowAsync()
{
_interactionInProgress = true;
// 2. Find characters
playerRef = FindFirstObjectByType<PlayerTouchController>();
FollowerController = FindFirstObjectByType<FollowerController>();
_playerRef = FindFirstObjectByType<PlayerTouchController>();
_followerController = FindFirstObjectByType<FollowerController>();
// 3. Virtual hook: Setup
OnInteractionStarted();
interactionStarted?.Invoke(_playerRef, _followerController);
// Dispatch the InteractionStarted event to action components
// 4. Fire events
interactionStarted?.Invoke(playerRef, FollowerController);
await DispatchEventAsync(InteractionEventType.InteractionStarted);
// After all InteractionStarted actions complete, proceed to player movement
await StartPlayerMovementAsync();
// 5. Orchestrate character movement
await MoveCharactersAsync();
// 6. Virtual hook: Arrival reaction
OnInteractingCharacterArrived();
// 7. Fire arrival events
characterArrived?.Invoke();
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
// 8. Validation (base + child)
var (canProceed, errorMessage) = ValidateInteraction();
if (!canProceed)
{
if (!string.IsNullOrEmpty(errorMessage))
{
DebugUIMessage.Show(errorMessage, Color.yellow);
}
FinishInteraction(false);
return;
}
// 9. Virtual main logic: Do the thing!
bool success = DoInteraction();
// 10. Finish up
FinishInteraction(success);
}
private async Task StartPlayerMovementAsync()
#region Virtual Lifecycle Methods
/// <summary>
/// High-level clickability check. Called BEFORE interaction starts.
/// Override to add custom high-level validation (is active, on cooldown, etc.)
/// </summary>
/// <returns>True if interaction can start, false for silent rejection</returns>
protected virtual bool CanBeClicked()
{
if (_playerRef == null)
if (!isActive) return false;
// Note: isOneTime and cooldown handled in FinishInteraction
return true;
}
/// <summary>
/// Called after characters are found but before movement starts.
/// Override to perform setup logic.
/// </summary>
protected virtual void OnInteractionStarted()
{
// Default: do nothing
}
/// <summary>
/// Called when the interacting character reaches destination.
/// Override to trigger animations or other arrival reactions.
/// </summary>
protected virtual void OnInteractingCharacterArrived()
{
// Default: do nothing
}
/// <summary>
/// Main interaction logic. OVERRIDE THIS in child classes.
/// </summary>
/// <returns>True if interaction succeeded, false otherwise</returns>
protected virtual bool DoInteraction()
{
Logging.Warning($"[Interactable] DoInteraction not implemented for {GetType().Name}");
return false;
}
/// <summary>
/// Called after interaction completes. Override to perform cleanup logic.
/// </summary>
/// <param name="success">Whether the interaction succeeded</param>
protected virtual void OnInteractionFinished(bool success)
{
// Default: do nothing
}
/// <summary>
/// Child-specific validation. Override to add interaction-specific validation.
/// </summary>
/// <returns>Tuple of (canProceed, errorMessage)</returns>
protected virtual (bool canProceed, string errorMessage) CanProceedWithInteraction()
{
return (true, null); // Default: always allow
}
#endregion
#region Validation
/// <summary>
/// Combines base and child validation.
/// </summary>
private (bool, string) ValidateInteraction()
{
// Base validation (always runs)
var (baseValid, baseError) = ValidateInteractionBase();
if (!baseValid)
return (false, baseError);
// Child validation (optional override)
var (childValid, childError) = CanProceedWithInteraction();
if (!childValid)
return (false, childError);
return (true, null);
}
/// <summary>
/// Base validation that always runs. Checks puzzle step locks and common prerequisites.
/// </summary>
private (bool canProceed, string errorMessage) ValidateInteractionBase()
{
// Check if there's an ObjectiveStepBehaviour attached
var step = GetComponent<PuzzleS.ObjectiveStepBehaviour>();
if (step != null && !step.IsStepUnlocked())
{
// Special case: ItemSlots can still be interacted with when locked (to swap items)
if (!(this is ItemSlot))
{
return (false, "This step is locked!");
}
}
return (true, null);
}
#endregion
#region Character Movement Orchestration
/// <summary>
/// Orchestrates character movement based on characterToInteract setting.
/// </summary>
private async Task MoveCharactersAsync()
{
if (playerRef == null)
{
Logging.Debug($"[Interactable] Player character could not be found. Aborting interaction.");
interactionInterrupted.Invoke();
@@ -137,350 +270,222 @@ namespace Interactions
return;
}
// If characterToInteract is None, immediately trigger the characterArrived event
// If characterToInteract is None, skip movement
if (characterToInteract == CharacterToInteract.None)
{
await BroadcastCharacterArrivedAsync();
return;
return; // Continue to arrival
}
// Check for a CharacterMoveToTarget component for Trafalgar (player) or Both
Vector3 stopPoint;
// Move player and optionally follower based on characterToInteract setting
if (characterToInteract == CharacterToInteract.Trafalgar)
{
await MovePlayerAsync();
}
else if (characterToInteract == CharacterToInteract.Pulver || characterToInteract == CharacterToInteract.Both)
{
await MovePlayerAsync(); // Move player to range first
await MoveFollowerAsync(); // Then move follower to interaction point
}
}
/// <summary>
/// Moves the player to the interaction point or custom target.
/// </summary>
private async Task MovePlayerAsync()
{
Vector3 stopPoint = transform.position; // Default to interactable position
bool customTargetFound = false;
// Check for a CharacterMoveToTarget component for Trafalgar or Both
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
foreach (var target in moveTargets)
{
// Target is valid if it matches Trafalgar specifically or is set to Both
if (target.characterType == CharacterToInteract.Trafalgar || target.characterType == CharacterToInteract.Both)
{
stopPoint = target.GetTargetPosition();
customTargetFound = true;
// We need to wait for the player to arrive, so use a TaskCompletionSource
var tcs = new TaskCompletionSource<bool>();
// Use local functions instead of circular lambda references
void OnPlayerArrivedLocal()
{
// First remove both event handlers to prevent memory leaks
if (_playerRef != null)
{
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
// Then continue with the interaction flow
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
}
void OnPlayerMoveCancelledLocal()
{
// First remove both event handlers to prevent memory leaks
if (_playerRef != null)
{
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
// Then handle the cancellation
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
}
// Unsubscribe previous handlers (if any)
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
// Subscribe our new handlers
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
// Start the player movement
_playerRef.MoveToAndNotify(stopPoint);
// Await player arrival
await tcs.Task;
return;
break;
}
}
// If no custom target was found, use the default behavior
// If no custom target, use default distance
if (!customTargetFound)
{
// Compute closest point on the interaction radius
Vector3 interactablePos = transform.position;
Vector3 playerPos = _playerRef.transform.position;
Vector3 playerPos = playerRef.transform.position;
float stopDistance = characterToInteract == CharacterToInteract.Pulver
? GameManager.Instance.PlayerStopDistance
: GameManager.Instance.PlayerStopDistanceDirectInteraction;
Vector3 toPlayer = (playerPos - interactablePos).normalized;
stopPoint = interactablePos + toPlayer * stopDistance;
// We need to wait for the player to arrive, so use a TaskCompletionSource
var tcs = new TaskCompletionSource<bool>();
// Use local functions instead of circular lambda references
void OnPlayerArrivedLocal()
{
// First remove both event handlers to prevent memory leaks
if (_playerRef != null)
{
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
// Then continue with the interaction flow
OnPlayerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
}
void OnPlayerMoveCancelledLocal()
{
// First remove both event handlers to prevent memory leaks
if (_playerRef != null)
{
_playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
// Then handle the cancellation
OnPlayerMoveCancelledAsync().ContinueWith(_ => tcs.TrySetResult(false));
}
// Unsubscribe previous handlers (if any)
_playerRef.OnArrivedAtTarget -= OnPlayerArrived;
_playerRef.OnMoveToCancelled -= OnPlayerMoveCancelled;
// Subscribe our new handlers
_playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
_playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
// Start the player movement
_playerRef.MoveToAndNotify(stopPoint);
// Await player arrival
await tcs.Task;
}
}
private async Task OnPlayerMoveCancelledAsync()
{
_interactionInProgress = false;
interactionInterrupted?.Invoke();
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
}
private async Task OnPlayerArrivedAsync()
{
if (!_interactionInProgress)
return;
// Dispatch PlayerArrived event
await DispatchEventAsync(InteractionEventType.PlayerArrived);
// Wait for player to arrive
var tcs = new TaskCompletionSource<bool>();
// After all PlayerArrived actions complete, proceed to character interaction
await HandleCharacterInteractionAsync();
void OnPlayerArrivedLocal()
{
if (playerRef != null)
{
playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
tcs.TrySetResult(true);
}
void OnPlayerMoveCancelledLocal()
{
if (playerRef != null)
{
playerRef.OnArrivedAtTarget -= OnPlayerArrivedLocal;
playerRef.OnMoveToCancelled -= OnPlayerMoveCancelledLocal;
}
_ = HandleInteractionCancelledAsync();
tcs.TrySetResult(false);
}
playerRef.OnArrivedAtTarget += OnPlayerArrivedLocal;
playerRef.OnMoveToCancelled += OnPlayerMoveCancelledLocal;
playerRef.MoveToAndNotify(stopPoint);
await tcs.Task;
}
private async Task HandleCharacterInteractionAsync()
/// <summary>
/// Moves the follower to the interaction point or custom target.
/// </summary>
private async Task MoveFollowerAsync()
{
if (characterToInteract == CharacterToInteract.Pulver)
{
// We need to wait for the follower to arrive, so use a TaskCompletionSource
var tcs = new TaskCompletionSource<bool>();
// Create a proper local function for the event handler
void OnFollowerArrivedLocal()
{
// First remove the event handler to prevent memory leaks
if (_followerController != null)
{
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
}
// Then continue with the interaction flow
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
}
// Register our new local function handler
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
// Check for a CharacterMoveToTarget component for Pulver or Both
Vector3 targetPosition = transform.position;
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
foreach (var target in moveTargets)
{
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
{
targetPosition = target.GetTargetPosition();
break;
}
}
// Use the new GoToPoint method instead of GoToPointAndReturn
_followerController.GoToPoint(targetPosition);
// Await follower arrival
await tcs.Task;
}
else if (characterToInteract == CharacterToInteract.Trafalgar)
{
await BroadcastCharacterArrivedAsync();
}
else if (characterToInteract == CharacterToInteract.Both)
{
// We need to wait for the follower to arrive, so use a TaskCompletionSource
var tcs = new TaskCompletionSource<bool>();
// Create a proper local function for the event handler
void OnFollowerArrivedLocal()
{
// First remove the event handler to prevent memory leaks
if (_followerController != null)
{
_followerController.OnPickupArrived -= OnFollowerArrivedLocal;
}
// Then continue with the interaction flow
OnFollowerArrivedAsync().ContinueWith(_ => tcs.TrySetResult(true));
}
// Register our new local function handler
_followerController.OnPickupArrived += OnFollowerArrivedLocal;
// Check for a CharacterMoveToTarget component for Pulver or Both
Vector3 targetPosition = transform.position;
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
foreach (var target in moveTargets)
{
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
{
targetPosition = target.GetTargetPosition();
break;
}
}
// Use the new GoToPoint method instead of GoToPointAndReturn
_followerController.GoToPoint(targetPosition);
// Await follower arrival
await tcs.Task;
}
}
private async Task OnFollowerArrivedAsync()
{
if (!_interactionInProgress)
if (FollowerController == null)
return;
// Dispatch InteractingCharacterArrived event and WAIT for all actions to complete
// This ensures we wait for any timeline animations to finish before proceeding
Logging.Debug("[Interactable] Follower arrived, dispatching InteractingCharacterArrived event and waiting for completion");
await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
Logging.Debug("[Interactable] All InteractingCharacterArrived actions completed, proceeding with interaction");
// Check if we have any components that might have paused the interaction flow
foreach (var action in _registeredActions)
// Check for a CharacterMoveToTarget component for Pulver or Both
Vector3 targetPosition = transform.position;
CharacterMoveToTarget[] moveTargets = GetComponentsInChildren<CharacterMoveToTarget>();
foreach (var target in moveTargets)
{
if (action is InteractionTimelineAction timelineAction &&
timelineAction.respondToEvents.Contains(InteractionEventType.InteractingCharacterArrived) &&
timelineAction.pauseInteractionFlow)
if (target.characterType == CharacterToInteract.Pulver || target.characterType == CharacterToInteract.Both)
{
targetPosition = target.GetTargetPosition();
break;
}
}
// Tell the follower to return to the player
if (_followerController != null && _playerRef != null)
// Wait for follower to arrive
var tcs = new TaskCompletionSource<bool>();
void OnFollowerArrivedLocal()
{
_followerController.ReturnToPlayer(_playerRef.transform);
if (FollowerController != null)
{
FollowerController.OnPickupArrived -= OnFollowerArrivedLocal;
}
// Tell follower to return to player
if (FollowerController != null && playerRef != null)
{
FollowerController.ReturnToPlayer(playerRef.transform);
}
tcs.TrySetResult(true);
}
// After all InteractingCharacterArrived actions complete, proceed to character arrived
await BroadcastCharacterArrivedAsync();
}
// Legacy non-async method to maintain compatibility with existing code
private void OnPlayerArrived()
{
// This is now just a wrapper for the async version
_ = OnPlayerArrivedAsync();
}
// Legacy non-async method to maintain compatibility with existing code
private void OnPlayerMoveCancelled()
{
// This is now just a wrapper for the async version
_ = OnPlayerMoveCancelledAsync();
}
private Task BroadcastCharacterArrivedAsync()
{
// Check for ObjectiveStepBehaviour and lock state
var step = GetComponent<PuzzleS.ObjectiveStepBehaviour>();
var slot = GetComponent<ItemSlot>();
if (step != null && !step.IsStepUnlocked() && slot == null)
{
DebugUIMessage.Show("This step is locked!", Color.yellow);
CompleteInteraction(false);
// Reset variables for next time
_interactionInProgress = false;
_playerRef = null;
_followerController = null;
return Task.CompletedTask;
}
// Dispatch CharacterArrived event
// await DispatchEventAsync(InteractionEventType.InteractingCharacterArrived);
FollowerController.OnPickupArrived += OnFollowerArrivedLocal;
FollowerController.GoToPoint(targetPosition);
// Broadcast appropriate event
characterArrived?.Invoke();
// Call the virtual method for subclasses to override
OnCharacterArrived();
// Reset variables for next time
_interactionInProgress = false;
_playerRef = null;
_followerController = null;
return Task.CompletedTask;
await tcs.Task;
}
/// <summary>
/// Called when the character has arrived at the interaction point.
/// Subclasses should override this to implement interaction-specific logic
/// and call CompleteInteraction(bool success) when done.
/// Handles interaction being cancelled (player stopped moving).
/// </summary>
protected virtual void OnCharacterArrived()
private async Task HandleInteractionCancelledAsync()
{
// Default implementation does nothing - subclasses should override
// and call CompleteInteraction when their logic is complete
interactionInterrupted?.Invoke();
await DispatchEventAsync(InteractionEventType.InteractionInterrupted);
}
private async void OnInteractionComplete(bool success)
#endregion
#region Finalization
/// <summary>
/// Finalizes the interaction after DoInteraction completes.
/// </summary>
private async void FinishInteraction(bool success)
{
// Dispatch InteractionComplete event
// Virtual hook: Cleanup
OnInteractionFinished(success);
// Fire completion events
interactionComplete?.Invoke(success);
await DispatchEventAsync(InteractionEventType.InteractionComplete);
// Handle one-time / cooldown
if (success)
{
if (isOneTime)
{
_isActive = false;
isActive = false;
}
else if (cooldown >= 0f)
{
StartCoroutine(HandleCooldown());
}
}
// Reset state
playerRef = null;
FollowerController = null;
}
private System.Collections.IEnumerator HandleCooldown()
{
_isActive = false;
isActive = false;
yield return new WaitForSeconds(cooldown);
_isActive = true;
isActive = true;
}
#endregion
#region Legacy Methods & Compatibility
/// <summary>
/// DEPRECATED: Override DoInteraction() instead.
/// This method is kept temporarily for backward compatibility during migration.
/// </summary>
[Obsolete("Override DoInteraction() instead")]
protected virtual void OnCharacterArrived()
{
// Default implementation does nothing
// Children should override DoInteraction() in the new pattern
}
/// <summary>
/// Call this from subclasses to mark the interaction as complete.
/// NOTE: In the new pattern, just return true/false from DoInteraction().
/// This is kept for backward compatibility during migration.
/// </summary>
protected void CompleteInteraction(bool success)
{
// For now, this manually triggers completion
// After migration, DoInteraction() return value will replace this
interactionComplete?.Invoke(success);
}
/// <summary>
/// Legacy method for backward compatibility.
/// </summary>
[Obsolete("Use CompleteInteraction instead")]
public void BroadcastInteractionComplete(bool success)
{
CompleteInteraction(success);
}
#endregion
#region ITouchInputConsumer Implementation
public void OnHoldStart(Vector2 position)
{
throw new NotImplementedException();
@@ -495,25 +500,8 @@ namespace Interactions
{
throw new NotImplementedException();
}
/// <summary>
/// Call this from subclasses to mark the interaction as complete.
/// </summary>
/// <param name="success">Whether the interaction was successful</param>
protected void CompleteInteraction(bool success)
{
interactionComplete?.Invoke(success);
}
/// <summary>
/// Legacy method for backward compatibility. Use CompleteInteraction instead.
/// </summary>
/// TODO: Remove this method in future versions
[Obsolete("Use CompleteInteraction instead")]
public void BroadcastInteractionComplete(bool success)
{
CompleteInteraction(success);
}
#endregion
#if UNITY_EDITOR
/// <summary>

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