From 50448c5bd36d920635e50b5fb99681cc3ea21e92 Mon Sep 17 00:00:00 2001 From: Michal Pikulski Date: Thu, 16 Oct 2025 19:43:19 +0200 Subject: [PATCH] Revamp the prompt system, the bootstrapper system, the starting cinematic --- .../AssetGroups/Default Local Group.asset | 12 +- Assets/Data/Puzzles/Quarry.meta | 8 + .../{ => Quarry}/ChocolateBirdPuzzle.meta | 0 .../ChocolateBirdPuzzle/LureWolter.asset | 2 +- .../ChocolateBirdPuzzle/LureWolter.asset.meta | 0 .../ChocolateBirdPuzzle/PickChocolate.asset | 20 + .../PickChocolate.asset.meta | 0 .../{ => Quarry}/FootballBirdPuzzle.meta | 0 .../InteractWithBallTree.asset | 0 .../InteractWithBallTree.asset.meta | 0 .../FootballBirdPuzzle/PickUpFootball.asset | 0 .../PickUpFootball.asset.meta | 0 .../PlaceFootballInLureSpotA.asset | 0 .../PlaceFootballInLureSpotA.asset.meta | 0 .../{ => Quarry}/HammerBirdPuzzle.meta | 0 .../HammerBirdPuzzle/InteractWLawnMower.asset | 0 .../InteractWLawnMower.asset.meta | 0 .../HammerBirdPuzzle/PickUpNails.asset | 0 .../HammerBirdPuzzle/PickUpNails.asset.meta | 0 .../HammerBirdPuzzle/PlaceNailsinlureC.asset | 0 .../PlaceNailsinlureC.asset.meta | 0 Assets/Data/Puzzles/Quarry/Quarry.asset | 31 + Assets/Data/Puzzles/Quarry/Quarry.asset.meta | 8 + .../Puzzles/{ => Quarry}/SoundBirdPuzzle.meta | 0 .../SoundBirdPuzzle/HeadbandPickup.asset | 20 + .../SoundBirdPuzzle/HeadbandPickup.asset.meta | 0 .../SoundBirdPuzzle/SoundBirdDeafen.asset | 19 + .../SoundBirdDeafen.asset.meta | 8 + Assets/Data/Puzzles/Testing.meta | 8 + .../{ => Testing}/ExampleAssPuzzle.meta | 0 .../{ => Testing}/ExampleAssPuzzle/Ass1.asset | 0 .../ExampleAssPuzzle/Ass1.asset.meta | 0 .../{ => Testing}/ExampleAssPuzzle/Ass2.asset | 0 .../ExampleAssPuzzle/Ass2.asset.meta | 0 .../{ => Testing}/ExampleAssPuzzle/Ass3.asset | 0 .../ExampleAssPuzzle/Ass3.asset.meta | 0 .../{ => Testing}/ExampleAssPuzzle/Head.asset | 0 .../ExampleAssPuzzle/Head.asset.meta | 0 Assets/Data/Puzzles/Testing/Testing.asset | 24 + .../Data/Puzzles/Testing/Testing.asset.meta | 8 + Assets/Editor/PuzzleAssetProcessor.cs | 321 +++++++ Assets/Editor/PuzzleAssetProcessor.cs.meta | 3 + Assets/Editor/PuzzleChainEditorWindow.cs | 116 --- Assets/Editor/PuzzleChainEditorWindow.cs.meta | 3 - Assets/Editor/PuzzleSystem.meta | 3 + .../Editor/PuzzleSystem/PuzzleEditorWindow.cs | 875 ++++++++++++++++++ .../PuzzleSystem/PuzzleEditorWindow.cs.meta | 3 + .../Environment/Placeholders/BallTree.prefab | 193 +++- Assets/Prefabs/Managers/SceneManager.prefab | 28 +- Assets/Prefabs/UI/LoadingScreen.prefab | 4 +- Assets/Scenes/StartingScene.unity | 452 +++++++++ Assets/Scenes/StartingScene.unity.meta | 7 + .../Bootstrap/BootCompletionService.cs | 150 +++ .../Bootstrap/BootCompletionService.cs.meta | 3 + .../Scripts/Bootstrap/BootSceneController.cs | 269 ++++++ .../Bootstrap/BootSceneController.cs.meta | 3 + Assets/Scripts/Bootstrap/CustomBoot.cs | 56 +- .../Scripts/Bootstrap/CustomBootSettings.cs | 42 +- .../Scripts/Bootstrap/InitialLoadingScreen.cs | 240 +++++ .../Bootstrap/InitialLoadingScreen.cs.meta | 3 + .../Scripts/Cinematics/CinematicsManager.cs | 144 ++- Assets/Scripts/Cinematics/SkipCinematic.cs | 82 +- Assets/Scripts/Core/GameManager.cs | 26 +- Assets/Scripts/Core/ItemManager.cs | 31 +- Assets/Scripts/Core/QuickAccess.cs | 24 +- Assets/Scripts/Core/SceneManagerService.cs | 118 ++- .../Scripts/Core/SceneOrientationEnforcer.cs | 40 +- .../Developer/DeveloperSettingsProvider.cs | 4 +- .../Data/CardSystem/CardSystemManager.cs | 10 + Assets/Scripts/Input/InputManager.cs | 66 +- Assets/Scripts/Interactions/Interactable.cs | 3 +- .../Scripts/PuzzleS/ObjectiveStepBehaviour.cs | 156 ++-- Assets/Scripts/PuzzleS/PuzzleChainSO.cs | 103 +++ Assets/Scripts/PuzzleS/PuzzleChainSO.cs.meta | 3 + Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs | 73 ++ .../Scripts/PuzzleS/PuzzleLevelDataSO.cs.meta | 3 + Assets/Scripts/PuzzleS/PuzzleManager.cs | 360 +++++-- Assets/Scripts/PuzzleS/PuzzleStepSO.cs | 55 ++ .../Settings/SceneOrientationConfig.cs | 2 +- .../Settings/ScreenOrientationRequirement.cs | 3 +- .../Scripts/UI/CardSystem/UIPageController.cs | 10 + Assets/Scripts/UI/LoadingScreenController.cs | 30 +- Assets/Scripts/UI/PauseMenu.cs | 46 +- Assets/Scripts/UI/Tutorial/DivingTutorial.cs | 7 +- Assets/Scripts/Utils/AppleHillsUtils.cs | 11 +- Assets/Scripts/found_instances | 207 +++++ Assets/Scripts/found_instances.meta | 7 + ProjectSettings/EditorBuildSettings.asset | 3 + ProjectSettings/ProjectSettings.asset | 72 +- 89 files changed, 3964 insertions(+), 677 deletions(-) create mode 100644 Assets/Data/Puzzles/Quarry.meta rename Assets/Data/Puzzles/{ => Quarry}/ChocolateBirdPuzzle.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/ChocolateBirdPuzzle/LureWolter.asset (95%) rename Assets/Data/Puzzles/{ => Quarry}/ChocolateBirdPuzzle/LureWolter.asset.meta (100%) create mode 100644 Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset rename Assets/Data/Puzzles/{ => Quarry}/ChocolateBirdPuzzle/PickChocolate.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/InteractWithBallTree.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/InteractWithBallTree.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/PickUpFootball.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/PickUpFootball.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/InteractWLawnMower.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/InteractWLawnMower.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/PickUpNails.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/PickUpNails.asset.meta (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/PlaceNailsinlureC.asset (100%) rename Assets/Data/Puzzles/{ => Quarry}/HammerBirdPuzzle/PlaceNailsinlureC.asset.meta (100%) create mode 100644 Assets/Data/Puzzles/Quarry/Quarry.asset create mode 100644 Assets/Data/Puzzles/Quarry/Quarry.asset.meta rename Assets/Data/Puzzles/{ => Quarry}/SoundBirdPuzzle.meta (100%) create mode 100644 Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset rename Assets/Data/Puzzles/{ => Quarry}/SoundBirdPuzzle/HeadbandPickup.asset.meta (100%) create mode 100644 Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset create mode 100644 Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset.meta create mode 100644 Assets/Data/Puzzles/Testing.meta rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle.meta (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass1.asset (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass1.asset.meta (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass2.asset (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass2.asset.meta (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass3.asset (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Ass3.asset.meta (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Head.asset (100%) rename Assets/Data/Puzzles/{ => Testing}/ExampleAssPuzzle/Head.asset.meta (100%) create mode 100644 Assets/Data/Puzzles/Testing/Testing.asset create mode 100644 Assets/Data/Puzzles/Testing/Testing.asset.meta create mode 100644 Assets/Editor/PuzzleAssetProcessor.cs create mode 100644 Assets/Editor/PuzzleAssetProcessor.cs.meta delete mode 100644 Assets/Editor/PuzzleChainEditorWindow.cs delete mode 100644 Assets/Editor/PuzzleChainEditorWindow.cs.meta create mode 100644 Assets/Editor/PuzzleSystem.meta create mode 100644 Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs create mode 100644 Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs.meta create mode 100644 Assets/Scenes/StartingScene.unity create mode 100644 Assets/Scenes/StartingScene.unity.meta create mode 100644 Assets/Scripts/Bootstrap/BootCompletionService.cs create mode 100644 Assets/Scripts/Bootstrap/BootCompletionService.cs.meta create mode 100644 Assets/Scripts/Bootstrap/BootSceneController.cs create mode 100644 Assets/Scripts/Bootstrap/BootSceneController.cs.meta create mode 100644 Assets/Scripts/Bootstrap/InitialLoadingScreen.cs create mode 100644 Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta create mode 100644 Assets/Scripts/PuzzleS/PuzzleChainSO.cs create mode 100644 Assets/Scripts/PuzzleS/PuzzleChainSO.cs.meta create mode 100644 Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs create mode 100644 Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs.meta create mode 100644 Assets/Scripts/found_instances create mode 100644 Assets/Scripts/found_instances.meta diff --git a/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset b/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset index c94410e0..34a52fdb 100644 --- a/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset +++ b/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset @@ -14,7 +14,17 @@ MonoBehaviour: m_EditorClassIdentifier: m_GroupName: Default Local Group m_GUID: 6f3207429a65b3e4b83935ac19791077 - m_SerializeEntries: [] + m_SerializeEntries: + - m_GUID: 1a9d24004b795e147b8544845a7a9ae3 + m_Address: Puzzles/Testing + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 + - m_GUID: d28c589c05c122f449a8b34e696cda53 + m_Address: Puzzles/Quarry + m_ReadOnly: 0 + m_SerializedLabels: [] + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 0 m_Settings: {fileID: 11400000, guid: 11da9bb90d9dd5848b4f7629415a6937, type: 2} m_SchemaSet: diff --git a/Assets/Data/Puzzles/Quarry.meta b/Assets/Data/Puzzles/Quarry.meta new file mode 100644 index 00000000..36f2ab7f --- /dev/null +++ b/Assets/Data/Puzzles/Quarry.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a17ef190d30246143a8f7a83591f2c1f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Puzzles/ChocolateBirdPuzzle.meta b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle.meta similarity index 100% rename from Assets/Data/Puzzles/ChocolateBirdPuzzle.meta rename to Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle.meta diff --git a/Assets/Data/Puzzles/ChocolateBirdPuzzle/LureWolter.asset b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/LureWolter.asset similarity index 95% rename from Assets/Data/Puzzles/ChocolateBirdPuzzle/LureWolter.asset rename to Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/LureWolter.asset index 8f148371..bebae427 100644 --- a/Assets/Data/Puzzles/ChocolateBirdPuzzle/LureWolter.asset +++ b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/LureWolter.asset @@ -13,7 +13,7 @@ MonoBehaviour: m_Name: LureWolter m_EditorClassIdentifier: stepId: LureWolter - displayName: Wolter Lured! + displayName: Wolter Lured!! description: Place Chocolate in the luring spot. icon: {fileID: 0} unlocks: diff --git a/Assets/Data/Puzzles/ChocolateBirdPuzzle/LureWolter.asset.meta b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/LureWolter.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ChocolateBirdPuzzle/LureWolter.asset.meta rename to Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/LureWolter.asset.meta diff --git a/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset new file mode 100644 index 00000000..5510f772 --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 84e39aac66cf4a10a89abc01b04b13af, type: 3} + m_Name: PickChocolate + m_EditorClassIdentifier: + stepId: PickChocolate + displayName: Picked Up Chocolate! + description: Avoid the gaze of the picnic lovers and steal their loot. + icon: {fileID: 0} + unlocks: + - {fileID: 11400000, guid: d0851a7610551104fa285c0748549d90, type: 2} diff --git a/Assets/Data/Puzzles/ChocolateBirdPuzzle/PickChocolate.asset.meta b/Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ChocolateBirdPuzzle/PickChocolate.asset.meta rename to Assets/Data/Puzzles/Quarry/ChocolateBirdPuzzle/PickChocolate.asset.meta diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle.meta b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle.meta similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle.meta rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle.meta diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/InteractWithBallTree.asset b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/InteractWithBallTree.asset similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/InteractWithBallTree.asset rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/InteractWithBallTree.asset diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/InteractWithBallTree.asset.meta b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/InteractWithBallTree.asset.meta similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/InteractWithBallTree.asset.meta rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/InteractWithBallTree.asset.meta diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/PickUpFootball.asset b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PickUpFootball.asset similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/PickUpFootball.asset rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PickUpFootball.asset diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/PickUpFootball.asset.meta b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PickUpFootball.asset.meta similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/PickUpFootball.asset.meta rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PickUpFootball.asset.meta diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset diff --git a/Assets/Data/Puzzles/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset.meta b/Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset.meta similarity index 100% rename from Assets/Data/Puzzles/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset.meta rename to Assets/Data/Puzzles/Quarry/FootballBirdPuzzle/PlaceFootballInLureSpotA.asset.meta diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle.meta b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle.meta similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle.meta rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle.meta diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/InteractWLawnMower.asset b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/InteractWLawnMower.asset similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/InteractWLawnMower.asset rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/InteractWLawnMower.asset diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/InteractWLawnMower.asset.meta b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/InteractWLawnMower.asset.meta similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/InteractWLawnMower.asset.meta rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/InteractWLawnMower.asset.meta diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/PickUpNails.asset b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PickUpNails.asset similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/PickUpNails.asset rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PickUpNails.asset diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/PickUpNails.asset.meta b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PickUpNails.asset.meta similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/PickUpNails.asset.meta rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PickUpNails.asset.meta diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/PlaceNailsinlureC.asset b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PlaceNailsinlureC.asset similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/PlaceNailsinlureC.asset rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PlaceNailsinlureC.asset diff --git a/Assets/Data/Puzzles/HammerBirdPuzzle/PlaceNailsinlureC.asset.meta b/Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PlaceNailsinlureC.asset.meta similarity index 100% rename from Assets/Data/Puzzles/HammerBirdPuzzle/PlaceNailsinlureC.asset.meta rename to Assets/Data/Puzzles/Quarry/HammerBirdPuzzle/PlaceNailsinlureC.asset.meta diff --git a/Assets/Data/Puzzles/Quarry/Quarry.asset b/Assets/Data/Puzzles/Quarry/Quarry.asset new file mode 100644 index 00000000..021875da --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/Quarry.asset @@ -0,0 +1,31 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0a79780a5a0d498084afd737d4515e3b, type: 3} + m_Name: Quarry + m_EditorClassIdentifier: AppleHillsScripts::PuzzleS.PuzzleLevelDataSO + levelId: Quarry + displayName: Quarry + allSteps: + - {fileID: 11400000, guid: d0851a7610551104fa285c0748549d90, type: 2} + - {fileID: 11400000, guid: ed967c44f3a8b914aabc1539f774efc7, type: 2} + - {fileID: 11400000, guid: 8ac614a698631554ab8ac39aed04a189, type: 2} + - {fileID: 11400000, guid: 6386246caab8faa40b2da221d9ab9b8a, type: 2} + - {fileID: 11400000, guid: 0fb0ab2b55d93a24685e9f6651adcf30, type: 2} + - {fileID: 11400000, guid: ea383d1dee861f54c9a1d4f32a2f6afc, type: 2} + - {fileID: 11400000, guid: f9da68caaae2a244885a13cf2e2e45c0, type: 2} + - {fileID: 11400000, guid: 28848561ff31fe24ea9f8590dee0bf8f, type: 2} + - {fileID: 11400000, guid: 5700dd3bf16fa9e4aa9905379118d1bd, type: 2} + - {fileID: 11400000, guid: 37409d749a15970438d761d1d658d7a6, type: 2} + initialSteps: + - {fileID: 11400000, guid: ed967c44f3a8b914aabc1539f774efc7, type: 2} + - {fileID: 11400000, guid: 8ac614a698631554ab8ac39aed04a189, type: 2} + - {fileID: 11400000, guid: ea383d1dee861f54c9a1d4f32a2f6afc, type: 2} diff --git a/Assets/Data/Puzzles/Quarry/Quarry.asset.meta b/Assets/Data/Puzzles/Quarry/Quarry.asset.meta new file mode 100644 index 00000000..fba4d3b7 --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/Quarry.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d28c589c05c122f449a8b34e696cda53 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Puzzles/SoundBirdPuzzle.meta b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle.meta similarity index 100% rename from Assets/Data/Puzzles/SoundBirdPuzzle.meta rename to Assets/Data/Puzzles/Quarry/SoundBirdPuzzle.meta diff --git a/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset new file mode 100644 index 00000000..e6d15665 --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset @@ -0,0 +1,20 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 84e39aac66cf4a10a89abc01b04b13af, type: 3} + m_Name: HeadbandPickup + m_EditorClassIdentifier: + stepId: HeadBandPickup + displayName: Picked up Headband! + description: Once Wolter jumps out of the bushes, the headband is interactable. + icon: {fileID: 0} + unlocks: + - {fileID: 11400000, guid: 37409d749a15970438d761d1d658d7a6, type: 2} diff --git a/Assets/Data/Puzzles/SoundBirdPuzzle/HeadbandPickup.asset.meta b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset.meta similarity index 100% rename from Assets/Data/Puzzles/SoundBirdPuzzle/HeadbandPickup.asset.meta rename to Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/HeadbandPickup.asset.meta diff --git a/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset new file mode 100644 index 00000000..3f9383a4 --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset @@ -0,0 +1,19 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 84e39aac66cf4a10a89abc01b04b13af, type: 3} + m_Name: SoundBirdDeafen + m_EditorClassIdentifier: + stepId: Deafen Bird + displayName: Deaf SoundBird + description: sds + icon: {fileID: 0} + unlocks: [] diff --git a/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset.meta b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset.meta new file mode 100644 index 00000000..efd5eb28 --- /dev/null +++ b/Assets/Data/Puzzles/Quarry/SoundBirdPuzzle/SoundBirdDeafen.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37409d749a15970438d761d1d658d7a6 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Puzzles/Testing.meta b/Assets/Data/Puzzles/Testing.meta new file mode 100644 index 00000000..f87db99d --- /dev/null +++ b/Assets/Data/Puzzles/Testing.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a9958845b52d0d7468c9c36a6969e9ea +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle.meta b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle.meta similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle.meta rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle.meta diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass1.asset b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass1.asset similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass1.asset rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass1.asset diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass1.asset.meta b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass1.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass1.asset.meta rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass1.asset.meta diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass2.asset b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass2.asset similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass2.asset rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass2.asset diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass2.asset.meta b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass2.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass2.asset.meta rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass2.asset.meta diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass3.asset b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass3.asset similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass3.asset rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass3.asset diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Ass3.asset.meta b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass3.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Ass3.asset.meta rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Ass3.asset.meta diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Head.asset b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Head.asset similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Head.asset rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Head.asset diff --git a/Assets/Data/Puzzles/ExampleAssPuzzle/Head.asset.meta b/Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Head.asset.meta similarity index 100% rename from Assets/Data/Puzzles/ExampleAssPuzzle/Head.asset.meta rename to Assets/Data/Puzzles/Testing/ExampleAssPuzzle/Head.asset.meta diff --git a/Assets/Data/Puzzles/Testing/Testing.asset b/Assets/Data/Puzzles/Testing/Testing.asset new file mode 100644 index 00000000..cf3ef9f7 --- /dev/null +++ b/Assets/Data/Puzzles/Testing/Testing.asset @@ -0,0 +1,24 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0a79780a5a0d498084afd737d4515e3b, type: 3} + m_Name: Testing + m_EditorClassIdentifier: AppleHillsScripts::PuzzleS.PuzzleLevelDataSO + levelId: Testing + displayName: Testing + allSteps: + - {fileID: 11400000, guid: 0b13ff4f31443b74281b13e0eef865c2, type: 2} + - {fileID: 11400000, guid: a84cbe9804e13f74e857c55d90cc10d1, type: 2} + - {fileID: 11400000, guid: 13b0c411066f85a41ba40c3bbbc281ed, type: 2} + - {fileID: 11400000, guid: 9de0c57af6191384e96e2ba7c04a3d0d, type: 2} + initialSteps: + - {fileID: 11400000, guid: 0b13ff4f31443b74281b13e0eef865c2, type: 2} + - {fileID: 11400000, guid: a84cbe9804e13f74e857c55d90cc10d1, type: 2} diff --git a/Assets/Data/Puzzles/Testing/Testing.asset.meta b/Assets/Data/Puzzles/Testing/Testing.asset.meta new file mode 100644 index 00000000..70d7ef48 --- /dev/null +++ b/Assets/Data/Puzzles/Testing/Testing.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1a9d24004b795e147b8544845a7a9ae3 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/PuzzleAssetProcessor.cs b/Assets/Editor/PuzzleAssetProcessor.cs new file mode 100644 index 00000000..a3fdb7d4 --- /dev/null +++ b/Assets/Editor/PuzzleAssetProcessor.cs @@ -0,0 +1,321 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using UnityEditor.AddressableAssets; +using UnityEditor.AddressableAssets.Settings; + +namespace PuzzleS.Editor +{ + /// + /// Handles asset post-processing for puzzle step data. + /// Automatically builds level data from folder structure. + /// + public class PuzzleAssetProcessor : AssetPostprocessor + { + // Base path for puzzle data + private const string PuzzleDataBasePath = "Assets/Data/Puzzles"; + + /// + /// Called after assets have been imported, deleted, or moved. + /// + private static void OnPostprocessAllAssets( + string[] importedAssets, + string[] deletedAssets, + string[] movedAssets, + string[] movedFromAssetPaths) + { + bool puzzleAssetsChanged = false; + + // Check for changes to puzzle step assets + foreach (string assetPath in importedAssets.Concat(movedAssets)) + { + if (IsPuzzleAssetPath(assetPath) && Path.GetExtension(assetPath) == ".asset") + { + var asset = AssetDatabase.LoadAssetAtPath(assetPath); + + if (asset is PuzzleStepSO) + { + // Find which level this step belongs to + string assetDir = Path.GetDirectoryName(assetPath); + string levelFolderPath = FindLevelFolder(assetDir); + + if (!string.IsNullOrEmpty(levelFolderPath)) + { + ProcessPuzzleLevelFolder(levelFolderPath); + puzzleAssetsChanged = true; + } + } + } + } + + // Check for changes to puzzle folder structure + foreach (string assetPath in deletedAssets.Concat(movedFromAssetPaths)) + { + if (IsPuzzleAssetPath(assetPath)) + { + // Extract parent folders for processing + string assetDir = Path.GetDirectoryName(assetPath); + string levelFolderPath = FindLevelFolder(assetDir); + + if (!string.IsNullOrEmpty(levelFolderPath) && Directory.Exists(levelFolderPath)) + { + ProcessPuzzleLevelFolder(levelFolderPath); + puzzleAssetsChanged = true; + } + } + } + + if (puzzleAssetsChanged) + { + // Make sure changes are saved + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + Debug.Log("[PuzzleProcessor] Puzzle data processing complete"); + } + } + + /// + /// Checks if an asset path is within the puzzle data folder structure + /// + private static bool IsPuzzleAssetPath(string assetPath) + { + // Unity's AssetDatabase always uses forward slashes, so we need to normalize for comparison + string normalizedPath = assetPath.Replace('\\', '/'); + string normalizedBasePath = PuzzleDataBasePath; + + return normalizedPath.StartsWith(normalizedBasePath) && + !normalizedPath.Contains("/.") && // Skip hidden folders + !Path.GetExtension(normalizedPath).Equals(".meta"); + } + + /// + /// Find the level folder that contains this asset path + /// Assumes level folders are direct children of PuzzleDataBasePath + /// + private static string FindLevelFolder(string assetPath) + { + if (string.IsNullOrEmpty(assetPath)) return null; + + // Unity's AssetDatabase always uses forward slashes + string normalizedPath = assetPath.Replace('\\', '/'); + + if (!normalizedPath.StartsWith(PuzzleDataBasePath)) + return null; + + // Get the relative path from the base path + string relativePath = normalizedPath.Substring(PuzzleDataBasePath.Length); + if (relativePath.StartsWith("/")) + relativePath = relativePath.Substring(1); + + // Split into path components and get the first folder (level name) + string[] pathComponents = relativePath.Split('/'); + + // First component after PuzzleDataBasePath should be the level name + if (pathComponents.Length > 0) + { + // Use proper path joining with Unity's forward slash convention + return PuzzleDataBasePath + "/" + pathComponents[0]; + } + + return null; + } + + /// + /// Process a level folder to build/update a PuzzleLevelDataSO with all puzzle steps + /// + private static void ProcessPuzzleLevelFolder(string folderPath) + { + if (!Directory.Exists(folderPath)) return; + + // Get level name from folder name + string levelName = Path.GetFileName(folderPath); + if (string.IsNullOrEmpty(levelName)) return; + + // Find all puzzle step assets in this level (including subfolders) + var stepAssets = FindAssetsOfTypeRecursive(folderPath); + if (stepAssets.Count == 0) return; + + // Get or create level data asset + var levelDataAsset = GetOrCreateLevelDataAsset(folderPath, levelName); + if (levelDataAsset == null) return; + + // Update level data + levelDataAsset.levelId = levelName; + levelDataAsset.displayName = levelName; // Default display name matches folder name + levelDataAsset.allSteps = stepAssets; + + // Pre-process initial steps (those with no dependencies) + levelDataAsset.initialSteps = FindInitialSteps(stepAssets); + + // Pre-process dependencies (which steps are required by each step) + PrecomputeDependencies(levelDataAsset); + + // Mark as dirty and save + EditorUtility.SetDirty(levelDataAsset); + + // Setup addressables + SetupAddressableAsset(AssetDatabase.GetAssetPath(levelDataAsset), $"Puzzles/{levelName}"); + + Debug.Log($"[PuzzleProcessor] Processed level: {levelName} with {stepAssets.Count} steps"); + } + + /// + /// Find all assets of a specific type in a folder and its subfolders + /// + private static List FindAssetsOfTypeRecursive(string folderPath) where T : ScriptableObject + { + var result = new List(); + + if (!Directory.Exists(folderPath)) return result; + + var guids = AssetDatabase.FindAssets("t:" + typeof(T).Name, new[] { folderPath }); + foreach (var guid in guids) + { + string assetPath = AssetDatabase.GUIDToAssetPath(guid); + var asset = AssetDatabase.LoadAssetAtPath(assetPath); + if (asset != null) + { + result.Add(asset); + } + } + + return result; + } + + /// + /// Get existing level data asset or create a new one + /// + private static PuzzleLevelDataSO GetOrCreateLevelDataAsset(string folderPath, string levelName) + { + // Check for existing level data asset + string levelDataAssetPath = $"{folderPath}/{levelName}.asset"; + var levelDataAsset = AssetDatabase.LoadAssetAtPath(levelDataAssetPath); + + if (levelDataAsset == null) + { + // Create new level data asset + levelDataAsset = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(levelDataAsset, levelDataAssetPath); + Debug.Log($"[PuzzleProcessor] Created new level data asset: {levelDataAssetPath}"); + } + + return levelDataAsset; + } + + /// + /// Find steps that have no dependencies (initial steps) + /// + private static List FindInitialSteps(List steps) + { + var initialSteps = new List(); + var dependentSteps = new HashSet(); + + // Find all steps that are dependencies of other steps + foreach (var step in steps) + { + if (step == null) continue; + + foreach (var unlockStep in step.unlocks) + { + if (unlockStep != null && steps.Contains(unlockStep)) + { + dependentSteps.Add(unlockStep); + } + } + } + + // Initial steps are those not depended on by any other step + foreach (var step in steps) + { + if (step != null && !dependentSteps.Contains(step)) + { + initialSteps.Add(step); + } + } + + return initialSteps; + } + + /// + /// Pre-compute dependency information for a puzzle level + /// + private static void PrecomputeDependencies(PuzzleLevelDataSO levelDataAsset) + { + levelDataAsset.stepDependencies.Clear(); + + // Build reverse dependency map (which steps are required by each step) + var reverseDependencies = new Dictionary>(); + + foreach (var step in levelDataAsset.allSteps) + { + if (step == null) continue; + + foreach (var unlockStep in step.unlocks) + { + if (unlockStep == null) continue; + + if (!reverseDependencies.ContainsKey(unlockStep.stepId)) + { + reverseDependencies[unlockStep.stepId] = new List(); + } + + reverseDependencies[unlockStep.stepId].Add(step.stepId); + } + } + + // Convert to serialized form + foreach (var entry in reverseDependencies) + { + levelDataAsset.stepDependencies[entry.Key] = entry.Value.ToArray(); + } + } + + /// + /// Set up an asset as an Addressable with the specified address + /// + private static void SetupAddressableAsset(string assetPath, string address) + { + if (!File.Exists(assetPath)) return; + + // Get Addressable settings + var settings = AddressableAssetSettingsDefaultObject.Settings; + if (settings == null) + { + Debug.LogWarning("[PuzzleProcessor] Addressable Asset Settings not found. Make sure Addressables is set up in your project."); + return; + } + + // Get default group + var defaultGroup = settings.DefaultGroup; + if (defaultGroup == null) + { + Debug.LogWarning("[PuzzleProcessor] Default Addressable Asset Group not found."); + return; + } + + // Get asset GUID + var guid = AssetDatabase.AssetPathToGUID(assetPath); + + // Check if entry exists + var existingEntry = settings.FindAssetEntry(guid); + if (existingEntry != null) + { + // Update existing entry + existingEntry.SetAddress(address); + } + else + { + // Create new entry + settings.CreateOrMoveEntry(guid, defaultGroup); + var newEntry = settings.FindAssetEntry(guid); + if (newEntry != null) + { + newEntry.SetAddress(address); + } + } + } + } +} diff --git a/Assets/Editor/PuzzleAssetProcessor.cs.meta b/Assets/Editor/PuzzleAssetProcessor.cs.meta new file mode 100644 index 00000000..1e34c156 --- /dev/null +++ b/Assets/Editor/PuzzleAssetProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5097566c60d341dbb6f2bf5175b048cb +timeCreated: 1760532147 \ No newline at end of file diff --git a/Assets/Editor/PuzzleChainEditorWindow.cs b/Assets/Editor/PuzzleChainEditorWindow.cs deleted file mode 100644 index 27c140ac..00000000 --- a/Assets/Editor/PuzzleChainEditorWindow.cs +++ /dev/null @@ -1,116 +0,0 @@ -using UnityEditor; -using UnityEngine; -using System.Collections.Generic; - -public class PuzzleChainEditorWindow : EditorWindow -{ - private List puzzleSteps = new List(); - private Dictionary> dependencyGraph; - private Vector2 scrollPos; - private const int INDENT_SIZE = 24; - - [MenuItem("AppleHills/Puzzle Chain Editor")] - public static void ShowWindow() - { - var window = GetWindow("Puzzle Chain Editor"); - window.minSize = new Vector2(600, 400); - window.maxSize = new Vector2(1200, 800); - window.position = new Rect(100, 100, 700, 500); // Reasonable default size and position - } - - private void OnEnable() - { - LoadPuzzleSteps(); - ProcessPuzzleChains(); - } - - private void LoadPuzzleSteps() - { - puzzleSteps.Clear(); - // Find all PuzzleStepSO assets in the project - string[] guids = AssetDatabase.FindAssets("t:PuzzleStepSO"); - foreach (var guid in guids) - { - var path = AssetDatabase.GUIDToAssetPath(guid); - // Only include those under Assets/Data/Puzzles (case-insensitive) - if (path.Replace('\\', '/').StartsWith("Assets/Data/Puzzles", System.StringComparison.OrdinalIgnoreCase)) - { - var step = AssetDatabase.LoadAssetAtPath(path); - if (step != null) - puzzleSteps.Add(step); - } - } - // Remove any nulls just in case - puzzleSteps.RemoveAll(s => s == null); - // Remove nulls from unlocks lists - foreach (var step in puzzleSteps) - { - if (step.unlocks != null) - step.unlocks.RemoveAll(u => u == null); - } - } - - private void ProcessPuzzleChains() - { - // Defensive: ensure no nulls in puzzleSteps or unlocks - puzzleSteps.RemoveAll(s => s == null); - foreach (var step in puzzleSteps) - { - if (step.unlocks != null) - step.unlocks.RemoveAll(u => u == null); - } - dependencyGraph = PuzzleGraphUtility.BuildDependencyGraph(puzzleSteps); - } - - private void OnGUI() - { - EditorGUILayout.LabelField("Puzzle Chain Visualization", EditorStyles.boldLabel); - if (puzzleSteps.Count == 0) - { - EditorGUILayout.HelpBox("No PuzzleStepSO assets found in Assets/Data/Puzzles.", MessageType.Warning); - return; - } - scrollPos = EditorGUILayout.BeginScrollView(scrollPos); - var initialSteps = dependencyGraph != null ? PuzzleGraphUtility.FindInitialSteps(dependencyGraph) : new List(); - foreach (var step in initialSteps) - { - if (step == null) continue; // Defensive - EditorGUILayout.BeginVertical("box"); - EditorGUILayout.LabelField($"Step Path: {step.displayName} ({step.stepId})", EditorStyles.largeLabel); - GUILayout.Space(6); - DrawStepTree(step, 0); - EditorGUILayout.EndVertical(); - GUILayout.Space(12); // Space between step paths - } - EditorGUILayout.EndScrollView(); - } - - private void DrawStepTree(PuzzleStepSO step, int indent) - { - if (step == null) { - EditorGUILayout.LabelField("[Missing Step]", EditorStyles.boldLabel); - return; - } - EditorGUILayout.BeginHorizontal(); - GUILayout.Space(indent * INDENT_SIZE); - EditorGUILayout.BeginVertical("box"); - EditorGUILayout.LabelField($"{step.displayName} ({step.stepId})", EditorStyles.boldLabel); - EditorGUILayout.LabelField(step.description ?? "", EditorStyles.wordWrappedLabel); - GUILayout.Space(4); - if (GUILayout.Button("Open in Inspector", GUILayout.Width(150))) - { - Selection.activeObject = step; - EditorGUIUtility.PingObject(step); - } - EditorGUILayout.EndVertical(); - EditorGUILayout.EndHorizontal(); - GUILayout.Space(6); - if (step.unlocks != null) - { - foreach (var unlock in step.unlocks) - { - DrawStepTree(unlock, indent + 1); - } - } - } -} diff --git a/Assets/Editor/PuzzleChainEditorWindow.cs.meta b/Assets/Editor/PuzzleChainEditorWindow.cs.meta deleted file mode 100644 index 26053c53..00000000 --- a/Assets/Editor/PuzzleChainEditorWindow.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f7bfaa69d42e45adaa4a44dd143efc2f -timeCreated: 1756991142 \ No newline at end of file diff --git a/Assets/Editor/PuzzleSystem.meta b/Assets/Editor/PuzzleSystem.meta new file mode 100644 index 00000000..66996b2e --- /dev/null +++ b/Assets/Editor/PuzzleSystem.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 453e2745b86c424da42227fbc38ed6d7 +timeCreated: 1760539457 \ No newline at end of file diff --git a/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs new file mode 100644 index 00000000..cbbef1d6 --- /dev/null +++ b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs @@ -0,0 +1,875 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEditor; +using UnityEngine; +using PuzzleS; + +namespace AppleHills.Editor.PuzzleSystem +{ + /// + /// Editor utility for managing puzzle steps and debugging puzzle state at runtime. + /// Provides tabs for both editing puzzle data and runtime debugging. + /// + public class PuzzleEditorWindow : EditorWindow + { + // Paths + private const string PuzzleDataBasePath = "Assets/Data"; + private const string MenuPath = "AppleHills/Puzzle Editor"; + + // Editor state + private List _allPuzzleSteps = new List(); + private Dictionary> _puzzleStepsByFolder = new Dictionary>(); + private PuzzleStepSO _selectedStep; + private Dictionary _folderExpanded = new Dictionary(); + private Vector2 _puzzleListScrollPosition; + private Vector2 _puzzleEditScrollPosition; + private string _searchQuery = ""; + private bool _isDirty = false; + + // Tab management + private int _selectedTab = 0; + private readonly string[] _tabNames = { "Edit", "Debug" }; + + // Runtime debug state + private Dictionary _stepUnlockState = new Dictionary(); + private Dictionary _stepCompletedState = new Dictionary(); + private Vector2 _debugScrollPosition; + private bool _isPlaying = false; + private bool _hasRuntimeData = false; + private PuzzleLevelDataSO _runtimeLevelData; + + // New step creation + private string _newStepName = "New Step"; + private string _selectedFolder = ""; + private bool _showCreateNewStepDialog = false; + + [MenuItem(MenuPath)] + public static void ShowWindow() + { + var window = GetWindow("Puzzle Editor"); + window.minSize = new Vector2(800, 600); + window.Show(); + } + + private void OnEnable() + { + // Load all puzzle steps + LoadAllPuzzleSteps(); + + // Register for undo/redo + Undo.undoRedoPerformed += OnUndoRedo; + + // Set up update callbacks + EditorApplication.update += OnEditorUpdate; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + } + + private void OnDisable() + { + // Unregister from undo/redo + Undo.undoRedoPerformed -= OnUndoRedo; + + // Unregister from update + EditorApplication.update -= OnEditorUpdate; + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; + } + + private void OnEditorUpdate() + { + // Check if we're in play mode + bool currentlyPlaying = EditorApplication.isPlaying && !EditorApplication.isPaused; + if (_isPlaying != currentlyPlaying) + { + _isPlaying = currentlyPlaying; + Repaint(); + } + + // In play mode, update runtime data periodically + if (_isPlaying) + { + UpdateRuntimeData(); + Repaint(); + } + } + + private void OnUndoRedo() + { + _isDirty = true; + Repaint(); + } + + private void OnPlayModeStateChanged(PlayModeStateChange state) + { + if (state == PlayModeStateChange.EnteredPlayMode) + { + _isPlaying = true; + _hasRuntimeData = false; + _stepUnlockState.Clear(); + _stepCompletedState.Clear(); + } + else if (state == PlayModeStateChange.ExitingPlayMode) + { + _isPlaying = false; + _hasRuntimeData = false; + _runtimeLevelData = null; + } + + Repaint(); + } + + private void OnGUI() + { + DrawHeader(); + + _selectedTab = GUILayout.Toolbar(_selectedTab, _tabNames); + + EditorGUILayout.Space(); + + switch (_selectedTab) + { + case 0: // Edit tab + DrawEditTab(); + break; + case 1: // Debug tab + DrawDebugTab(); + break; + } + + // Apply any pending changes + if (_isDirty) + { + SaveChanges(); + _isDirty = false; + } + } + + #region Header UI + + private void DrawHeader() + { + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + + if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60))) + { + LoadAllPuzzleSteps(); + } + + GUILayout.FlexibleSpace(); + + // Tab-specific toolbar options + if (_selectedTab == 0) // Edit tab + { + if (GUILayout.Button("Create New", EditorStyles.toolbarButton, GUILayout.Width(80))) + { + _showCreateNewStepDialog = true; + } + } + else if (_selectedTab == 1) // Debug tab + { + EditorGUILayout.LabelField(_isPlaying ? "Runtime Active" : "Editor Mode", EditorStyles.toolbarButton, GUILayout.Width(100)); + } + + EditorGUILayout.EndHorizontal(); + } + + #endregion + + #region Edit Tab + + private void DrawEditTab() + { + EditorGUILayout.BeginHorizontal(); + + // Left panel - puzzle step list + EditorGUILayout.BeginVertical(GUILayout.Width(250)); + DrawStepListPanel(); + EditorGUILayout.EndVertical(); + + // Separator + EditorGUILayout.Space(); + + // Right panel - puzzle step editor + EditorGUILayout.BeginVertical(); + DrawStepEditorPanel(); + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + + // Draw create new step dialog if needed + if (_showCreateNewStepDialog) + { + DrawCreateNewStepDialog(); + } + } + + private void DrawStepListPanel() + { + // Search field + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + _searchQuery = EditorGUILayout.TextField(_searchQuery, EditorStyles.toolbarSearchField); + if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(20)) && !string.IsNullOrEmpty(_searchQuery)) + { + _searchQuery = ""; + GUI.FocusControl(null); + } + EditorGUILayout.EndHorizontal(); + + _puzzleListScrollPosition = EditorGUILayout.BeginScrollView(_puzzleListScrollPosition); + + // If search query exists, show filtered results across all folders + if (!string.IsNullOrEmpty(_searchQuery)) + { + var filteredSteps = _allPuzzleSteps.Where(step => + step.displayName.ToLower().Contains(_searchQuery.ToLower()) || + step.stepId.ToLower().Contains(_searchQuery.ToLower())).ToList(); + + if (filteredSteps.Any()) + { + EditorGUILayout.LabelField($"Search Results ({filteredSteps.Count}):", EditorStyles.boldLabel); + + foreach (var step in filteredSteps) + { + if (DrawStepListItem(step)) + { + _selectedStep = step; + GUI.FocusControl(null); + } + } + } + else + { + EditorGUILayout.LabelField("No matching steps found"); + } + } + // Otherwise show organized by folder + else + { + foreach (var folderEntry in _puzzleStepsByFolder) + { + string folderName = folderEntry.Key; + List steps = folderEntry.Value; + + if (!_folderExpanded.ContainsKey(folderName)) + { + _folderExpanded[folderName] = true; // Default to expanded + } + + // Folder header with toggle + EditorGUILayout.BeginHorizontal(); + _folderExpanded[folderName] = EditorGUILayout.Foldout(_folderExpanded[folderName], folderName, true); + + // Show step count + EditorGUILayout.LabelField($"({steps.Count})", GUILayout.Width(40)); + EditorGUILayout.EndHorizontal(); + + // Draw steps in folder if expanded + if (_folderExpanded[folderName]) + { + EditorGUI.indentLevel++; + + foreach (var step in steps) + { + if (DrawStepListItem(step)) + { + _selectedStep = step; + GUI.FocusControl(null); + } + } + + EditorGUI.indentLevel--; + } + } + } + + EditorGUILayout.EndScrollView(); + } + + private bool DrawStepListItem(PuzzleStepSO step) + { + if (step == null) return false; + + bool isSelected = step == _selectedStep; + EditorGUILayout.BeginHorizontal(isSelected ? EditorStyles.selectionRect : EditorStyles.helpBox); + + // Icon if available + if (step.icon != null) + { + GUILayout.Label(new GUIContent(step.icon.texture), GUILayout.Width(20), GUILayout.Height(20)); + } + else + { + GUILayout.Space(24); + } + + // Name and ID + EditorGUILayout.BeginVertical(); + EditorGUILayout.LabelField(step.displayName, EditorStyles.boldLabel); + EditorGUILayout.LabelField(step.stepId, EditorStyles.miniLabel); + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + + // Check if this item was clicked + Rect itemRect = GUILayoutUtility.GetLastRect(); + bool wasClicked = Event.current.type == EventType.MouseDown && Event.current.button == 0 + && itemRect.Contains(Event.current.mousePosition); + + if (wasClicked) + { + Event.current.Use(); + } + + return wasClicked; + } + + private void DrawStepEditorPanel() + { + if (_selectedStep == null) + { + EditorGUILayout.HelpBox("Select a puzzle step to edit", MessageType.Info); + return; + } + + _puzzleEditScrollPosition = EditorGUILayout.BeginScrollView(_puzzleEditScrollPosition); + + EditorGUI.BeginChangeCheck(); + + // Header with name and ID + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Editing:", EditorStyles.boldLabel, GUILayout.Width(60)); + EditorGUILayout.LabelField(_selectedStep.displayName, EditorStyles.boldLabel); + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // Basic properties + EditorGUILayout.LabelField("Basic Properties", EditorStyles.boldLabel); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + // Step ID + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Step ID:", GUILayout.Width(100)); + string newStepId = EditorGUILayout.TextField(_selectedStep.stepId); + if (newStepId != _selectedStep.stepId) + { + Undo.RecordObject(_selectedStep, "Change Step ID"); + _selectedStep.stepId = newStepId; + _isDirty = true; + } + EditorGUILayout.EndHorizontal(); + + // Display Name + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Display Name:", GUILayout.Width(100)); + string newDisplayName = EditorGUILayout.TextField(_selectedStep.displayName); + if (newDisplayName != _selectedStep.displayName) + { + Undo.RecordObject(_selectedStep, "Change Display Name"); + _selectedStep.displayName = newDisplayName; + _isDirty = true; + } + EditorGUILayout.EndHorizontal(); + + // Description + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Description:", GUILayout.Width(100)); + string newDescription = EditorGUILayout.TextArea(_selectedStep.description, GUILayout.Height(60)); + if (newDescription != _selectedStep.description) + { + Undo.RecordObject(_selectedStep, "Change Description"); + _selectedStep.description = newDescription; + _isDirty = true; + } + EditorGUILayout.EndHorizontal(); + + // Icon + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Icon:", GUILayout.Width(100)); + Sprite newIcon = (Sprite)EditorGUILayout.ObjectField(_selectedStep.icon, typeof(Sprite), false); + if (newIcon != _selectedStep.icon) + { + Undo.RecordObject(_selectedStep, "Change Icon"); + _selectedStep.icon = newIcon; + _isDirty = true; + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + // Unlocks (dependencies) + EditorGUILayout.LabelField("Unlocks", EditorStyles.boldLabel); + EditorGUILayout.HelpBox("Steps that will be unlocked when this step is completed", MessageType.Info); + + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + // Show unlocked steps list + if (_selectedStep.unlocks.Count > 0) + { + for (int i = 0; i < _selectedStep.unlocks.Count; i++) + { + EditorGUILayout.BeginHorizontal(); + + // Draw step selector + PuzzleStepSO newUnlockedStep = (PuzzleStepSO)EditorGUILayout.ObjectField( + _selectedStep.unlocks[i], typeof(PuzzleStepSO), false); + + if (newUnlockedStep != _selectedStep.unlocks[i]) + { + Undo.RecordObject(_selectedStep, "Change Unlocked Step"); + _selectedStep.unlocks[i] = newUnlockedStep; + _isDirty = true; + } + + // Remove button + if (GUILayout.Button("-", GUILayout.Width(20))) + { + Undo.RecordObject(_selectedStep, "Remove Unlocked Step"); + _selectedStep.unlocks.RemoveAt(i); + _isDirty = true; + i--; + } + + EditorGUILayout.EndHorizontal(); + } + } + else + { + EditorGUILayout.LabelField("No steps will be unlocked"); + } + + // Add new dependency + if (GUILayout.Button("Add Unlocked Step")) + { + Undo.RecordObject(_selectedStep, "Add Unlocked Step"); + _selectedStep.unlocks.Add(null); + _isDirty = true; + } + + EditorGUILayout.EndVertical(); + + EditorGUILayout.Space(); + + // Asset path info + string assetPath = AssetDatabase.GetAssetPath(_selectedStep); + EditorGUILayout.LabelField("Asset Path:", EditorStyles.miniLabel); + EditorGUILayout.LabelField(assetPath, EditorStyles.miniLabel); + + // Delete button + EditorGUILayout.Space(); + if (GUILayout.Button("Delete Step", GUILayout.Width(100))) + { + if (EditorUtility.DisplayDialog("Delete Puzzle Step", + $"Are you sure you want to delete '{_selectedStep.displayName}'? This action cannot be undone.", + "Delete", "Cancel")) + { + DeletePuzzleStep(_selectedStep); + _selectedStep = null; + } + } + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(_selectedStep); + } + + EditorGUILayout.EndScrollView(); + } + + private void DrawCreateNewStepDialog() + { + // Create a centered window + Rect windowRect = new Rect( + (position.width - 400) / 2, + (position.height - 200) / 2, + 400, 200); + + GUI.Box(windowRect, "Create New Puzzle Step", EditorStyles.helpBox); + GUILayout.BeginArea(new Rect(windowRect.x + 10, windowRect.y + 30, windowRect.width - 20, windowRect.height - 40)); + + // Name field + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Name:", GUILayout.Width(80)); + _newStepName = EditorGUILayout.TextField(_newStepName); + EditorGUILayout.EndHorizontal(); + + // Folder selection + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Folder:", GUILayout.Width(80)); + + // Create folder dropdown + List folderNames = _puzzleStepsByFolder.Keys.ToList(); + int selectedFolderIndex = folderNames.IndexOf(_selectedFolder); + int newSelectedFolderIndex = EditorGUILayout.Popup(selectedFolderIndex >= 0 ? selectedFolderIndex : 0, folderNames.ToArray()); + if (newSelectedFolderIndex >= 0 && newSelectedFolderIndex < folderNames.Count) + { + _selectedFolder = folderNames[newSelectedFolderIndex]; + } + else if (folderNames.Count > 0) + { + _selectedFolder = folderNames[0]; + } + + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(); + + // Buttons + EditorGUILayout.BeginHorizontal(); + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Cancel", GUILayout.Width(100))) + { + _showCreateNewStepDialog = false; + } + + if (GUILayout.Button("Create", GUILayout.Width(100))) + { + if (!string.IsNullOrEmpty(_newStepName) && !string.IsNullOrEmpty(_selectedFolder)) + { + CreateNewPuzzleStep(_newStepName, _selectedFolder); + _showCreateNewStepDialog = false; + _newStepName = "New Step"; + } + } + + EditorGUILayout.EndHorizontal(); + + GUILayout.EndArea(); + } + + #endregion + + #region Debug Tab + + private void DrawDebugTab() + { + if (!_isPlaying) + { + EditorGUILayout.HelpBox("Enter Play Mode to debug puzzles at runtime", MessageType.Info); + return; + } + + if (!_hasRuntimeData || _runtimeLevelData == null) + { + EditorGUILayout.HelpBox("Waiting for puzzle data to be loaded...", MessageType.Info); + return; + } + + EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); + EditorGUILayout.LabelField($"Current Level: {_runtimeLevelData.levelId}", EditorStyles.boldLabel); + EditorGUILayout.EndHorizontal(); + + _debugScrollPosition = EditorGUILayout.BeginScrollView(_debugScrollPosition); + + // List all steps with their current state + EditorGUILayout.LabelField("Puzzle Steps", EditorStyles.boldLabel); + + // Show steps directly from the level data in a flat list + foreach (var step in _runtimeLevelData.allSteps) + { + if (step == null) continue; + + DrawRuntimeStepItem(step); + } + + EditorGUILayout.EndScrollView(); + } + + private void DrawRuntimeStepItem(PuzzleStepSO step) + { + bool isUnlocked = _stepUnlockState.ContainsKey(step.stepId) && _stepUnlockState[step.stepId]; + bool isCompleted = _stepCompletedState.ContainsKey(step.stepId) && _stepCompletedState[step.stepId]; + + // Set background color based on state + Color originalColor = GUI.backgroundColor; + + if (isCompleted) + GUI.backgroundColor = new Color(0.5f, 1f, 0.5f); // Green for completed + else if (isUnlocked) + GUI.backgroundColor = new Color(1f, 1f, 0.5f); // Yellow for unlocked but not completed + else + GUI.backgroundColor = new Color(1f, 0.5f, 0.5f); // Red for locked + + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + // Reset color + GUI.backgroundColor = originalColor; + + EditorGUILayout.BeginHorizontal(); + + // Step info + EditorGUILayout.BeginVertical(GUILayout.ExpandWidth(true)); + EditorGUILayout.LabelField(step.displayName, EditorStyles.boldLabel); + EditorGUILayout.LabelField(step.stepId, EditorStyles.miniLabel); + + // Status text + string statusText = isCompleted ? "Completed" : (isUnlocked ? "Unlocked" : "Locked"); + EditorGUILayout.LabelField($"Status: {statusText}", EditorStyles.miniLabel); + + EditorGUILayout.EndVertical(); + + // Action buttons + EditorGUILayout.BeginVertical(GUILayout.Width(100)); + + EditorGUI.BeginDisabledGroup(isCompleted); + if (GUILayout.Button(isUnlocked ? "Lock" : "Unlock")) + { + ToggleStepUnlocked(step); + } + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginDisabledGroup(!isUnlocked || isCompleted); + if (GUILayout.Button("Complete")) + { + CompleteStep(step); + } + EditorGUI.EndDisabledGroup(); + + EditorGUILayout.EndVertical(); + + EditorGUILayout.EndHorizontal(); + EditorGUILayout.EndVertical(); + } + + #endregion + + #region Data Management + + private void LoadAllPuzzleSteps() + { + _allPuzzleSteps.Clear(); + _puzzleStepsByFolder.Clear(); + + // Find all PuzzleStepSO assets in the project + string[] guids = AssetDatabase.FindAssets("t:PuzzleStepSO"); + + foreach (string guid in guids) + { + string assetPath = AssetDatabase.GUIDToAssetPath(guid); + + // Only include assets from the Data folder + if (assetPath.StartsWith(PuzzleDataBasePath)) + { + PuzzleStepSO step = AssetDatabase.LoadAssetAtPath(assetPath); + + if (step != null) + { + _allPuzzleSteps.Add(step); + + // Add to folder dictionary for organization + string folder = Path.GetDirectoryName(assetPath)?.Replace("\\", "/"); + + if (folder != null) + { + if (!_puzzleStepsByFolder.ContainsKey(folder)) + { + _puzzleStepsByFolder[folder] = new List(); + } + + _puzzleStepsByFolder[folder].Add(step); + } + } + } + } + + // Make sure each folder is sorted by name + foreach (var key in _puzzleStepsByFolder.Keys.ToList()) + { + _puzzleStepsByFolder[key] = _puzzleStepsByFolder[key] + .OrderBy(step => step.displayName) + .ToList(); + } + + _isDirty = false; + } + + private void SaveChanges() + { + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + } + + private void CreateNewPuzzleStep(string stepName, string folderPath) + { + // Create a new PuzzleStepSO + PuzzleStepSO newStep = CreateInstance(); + newStep.stepId = GenerateUniqueStepId(stepName); + newStep.displayName = stepName; + + // Create the path + string assetPath = Path.Combine(folderPath, $"{stepName}.asset").Replace("\\", "/"); + + // Make sure the directory exists + string directory = Path.GetDirectoryName(assetPath); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + // Create the asset + AssetDatabase.CreateAsset(newStep, assetPath); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + // Reload all steps and select the new one + LoadAllPuzzleSteps(); + _selectedStep = newStep; + } + + private void DeletePuzzleStep(PuzzleStepSO step) + { + if (step == null) return; + + string assetPath = AssetDatabase.GetAssetPath(step); + + if (!string.IsNullOrEmpty(assetPath)) + { + // Also need to remove all references to this step from other steps' unlocks lists + foreach (var otherStep in _allPuzzleSteps) + { + if (otherStep != null && otherStep != step) + { + if (otherStep.unlocks.Contains(step)) + { + Undo.RecordObject(otherStep, "Remove Deleted Step Reference"); + otherStep.unlocks.Remove(step); + EditorUtility.SetDirty(otherStep); + } + } + } + + AssetDatabase.DeleteAsset(assetPath); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + // Reload all steps + LoadAllPuzzleSteps(); + } + } + + private string GenerateUniqueStepId(string baseName) + { + // Convert to lowercase and replace spaces with underscores + string baseId = baseName.ToLower().Replace(" ", "_"); + + // Check if this ID already exists + bool idExists = _allPuzzleSteps.Any(step => step.stepId == baseId); + + if (!idExists) + { + return baseId; + } + + // Add a number suffix if ID already exists + int counter = 1; + while (_allPuzzleSteps.Any(step => step.stepId == $"{baseId}_{counter}")) + { + counter++; + } + + return $"{baseId}_{counter}"; + } + + #endregion + + #region Runtime Debug Helpers + + private void UpdateRuntimeData() + { + if (!_isPlaying) return; + + // Find PuzzleManager instance + PuzzleManager puzzleManager = Object.FindObjectOfType(); + + if (puzzleManager == null) + { + _hasRuntimeData = false; + return; + } + + // Get current level data + var levelData = puzzleManager.GetCurrentLevelData(); + if (levelData == null) + { + _hasRuntimeData = false; + return; + } + + _hasRuntimeData = true; + _runtimeLevelData = levelData; + + // Update step states + foreach (var step in _runtimeLevelData.allSteps) + { + if (step != null) + { + _stepUnlockState[step.stepId] = puzzleManager.IsStepUnlocked(step); + _stepCompletedState[step.stepId] = puzzleManager.IsPuzzleStepCompleted(step.stepId); + } + } + } + + private void ToggleStepUnlocked(PuzzleStepSO step) + { + if (!_isPlaying || step == null) return; + + PuzzleManager puzzleManager = Object.FindObjectOfType(); + if (puzzleManager == null) return; + + // Get current unlock state + bool isCurrentlyUnlocked = _stepUnlockState.ContainsKey(step.stepId) && _stepUnlockState[step.stepId]; + + // Call appropriate method using reflection since these might be private methods + System.Type managerType = puzzleManager.GetType(); + + if (isCurrentlyUnlocked) + { + // Find the LockStep method that takes a PuzzleStepSO parameter + System.Reflection.MethodInfo lockMethod = managerType.GetMethod("LockStep", + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic); + + if (lockMethod != null) + { + lockMethod.Invoke(puzzleManager, new object[] { step }); + } + } + else + { + // Find the UnlockStep method that takes a PuzzleStepSO parameter + System.Reflection.MethodInfo unlockMethod = managerType.GetMethod("UnlockStep", + System.Reflection.BindingFlags.Instance | + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic); + + if (unlockMethod != null) + { + unlockMethod.Invoke(puzzleManager, new object[] { step }); + } + } + + // Update state + UpdateRuntimeData(); + } + + private void CompleteStep(PuzzleStepSO step) + { + if (!_isPlaying || step == null) return; + + PuzzleManager puzzleManager = Object.FindObjectOfType(); + if (puzzleManager == null) return; + + // Complete the step + puzzleManager.MarkPuzzleStepCompleted(step); + + // Update state + UpdateRuntimeData(); + } + + #endregion + } +} diff --git a/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs.meta b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs.meta new file mode 100644 index 00000000..9eccad68 --- /dev/null +++ b/Assets/Editor/PuzzleSystem/PuzzleEditorWindow.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c733f3c6624e4e5486c07abfe4fab81e +timeCreated: 1760539457 \ No newline at end of file diff --git a/Assets/Prefabs/Environment/Placeholders/BallTree.prefab b/Assets/Prefabs/Environment/Placeholders/BallTree.prefab index f49f6fc6..918b50fa 100644 --- a/Assets/Prefabs/Environment/Placeholders/BallTree.prefab +++ b/Assets/Prefabs/Environment/Placeholders/BallTree.prefab @@ -26,7 +26,7 @@ Transform: m_GameObject: {fileID: 1646387898454772943} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: -4.4615383, y: 0.61538696, z: 0} + m_LocalPosition: {x: -1.854, y: -0, z: 0} m_LocalScale: {x: 0.7692308, y: 0.7692308, z: 0.7692308} m_ConstrainProportionsScale: 0 m_Children: [] @@ -46,6 +46,38 @@ MonoBehaviour: m_EditorClassIdentifier: AppleHillsScripts::Interactions.CharacterMoveToTarget characterType: 2 positionOffset: {x: 0, y: 0, z: 1} +--- !u!1 &2654542252039360806 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2084964592986606867} + m_Layer: 10 + m_Name: AnimContainer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2084964592986606867 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2654542252039360806} + serializedVersion: 2 + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0.826, y: 1.333, z: 0} + m_LocalScale: {x: 0.1851852, y: 0.1851852, z: 0.1851852} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 305861995533261809} + m_Father: {fileID: 4937390562043858043} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &3591802784221671576 GameObject: m_ObjectHideFlags: 0 @@ -72,7 +104,7 @@ Transform: m_GameObject: {fileID: 3591802784221671576} serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalPosition: {x: 0.9, y: -0.16, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 1 m_Children: [] @@ -124,10 +156,10 @@ PolygonCollider2D: m_AutoTiling: 0 m_Points: m_Paths: - - - {x: 0.69036514, y: -0.039288044} - - {x: -0.7429288, y: 0.004180908} - - {x: -0.73904544, y: 2.4819212} - - {x: 0.5286481, y: 2.5456142} + - - {x: 0.5431701, y: -0.027021673} + - {x: -0.62026507, y: -0.020351835} + - {x: -0.48145303, y: 1.4024833} + - {x: 0.36918524, y: 1.3803146} m_UseDelaunayMesh: 0 --- !u!1 &7379304988657006554 GameObject: @@ -144,7 +176,8 @@ GameObject: - component: {fileID: 492578671844741631} - component: {fileID: 8984729148657672365} - component: {fileID: 1569498917964935965} - - component: {fileID: 3871210969445384207} + - component: {fileID: 6417332830266550134} + - component: {fileID: 4544320034237251646} m_Layer: 10 m_Name: BallTree m_TagString: Untagged @@ -162,9 +195,10 @@ Transform: serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1.3, y: 1.3, z: 1.3} + m_LocalScale: {x: 2.7, y: 2.7, z: 2.7} m_ConstrainProportionsScale: 1 m_Children: + - {fileID: 2084964592986606867} - {fileID: 6631072601870453588} - {fileID: 7371967679236352629} m_Father: {fileID: 0} @@ -216,7 +250,7 @@ SpriteRenderer: m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 1 - m_Sprite: {fileID: 4974115186881715698, guid: e1aa947fcf3609045ba89a6ddb609ae3, type: 3} + m_Sprite: {fileID: 768869974157336279, guid: ab1e8f5d45ee00247af9314b56f4af91, type: 3} m_Color: {r: 1, g: 1, b: 1, a: 1} m_FlipX: 0 m_FlipY: 0 @@ -261,17 +295,17 @@ BoxCollider2D: m_UsedByEffector: 0 m_CompositeOperation: 0 m_CompositeOrder: 0 - m_Offset: {x: -0.1844703, y: 2.8477936} + m_Offset: {x: 0.76, y: 2.88} m_SpriteTilingProperty: border: {x: 0, y: 0, z: 0, w: 0} - pivot: {x: 0.5, y: 0.08} - oldSize: {x: 9.23, y: 11.48} + pivot: {x: 0.5, y: 0.2} + oldSize: {x: 14.969999, y: 9.060193} newSize: {x: 9.23, y: 11.48} adaptiveTilingThreshold: 0.5 drawMode: 0 adaptiveTiling: 0 m_AutoTiling: 0 - m_Size: {x: 3.437712, y: 7.532383} + m_Size: {x: 3.8, y: 7.34} m_EdgeRadius: 0 --- !u!114 &8437452310832126615 MonoBehaviour: @@ -313,7 +347,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: stepData: {fileID: 11400000, guid: 8ac614a698631554ab8ac39aed04a189, type: 2} - puzzleIndicator: {fileID: 0} + puzzleIndicator: {fileID: 214996082823304376} drawPromptRangeGizmo: 1 --- !u!114 &8984729148657672365 MonoBehaviour: @@ -327,18 +361,20 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 42e77a0c97604b6eb7674e58726c831a, type: 3} m_Name: m_EditorClassIdentifier: AppleHillsScripts::Interactions.InteractionTimelineAction - respondToEvents: 04000000 + respondToEvents: 02000000 pauseInteractionFlow: 1 - playableDirector: {fileID: 0} + playableDirector: {fileID: 1569498917964935965} timelineMappings: - - eventType: 4 - timelines: [] + - eventType: 2 + timelines: + - {fileID: 11400000, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} + - {fileID: 11400000, guid: ee609df51f47bd541a23d5425e289e30, type: 2} bindPlayerCharacter: 0 bindPulverCharacter: 0 - playerTrackName: - pulverTrackName: + playerTrackName: Player + pulverTrackName: Pulver timeoutSeconds: 30 - loopLast: 0 + loopLast: 1 loopAll: 0 --- !u!320 &1569498917964935965 PlayableDirector: @@ -349,21 +385,43 @@ PlayableDirector: m_GameObject: {fileID: 7379304988657006554} m_Enabled: 1 serializedVersion: 3 - m_PlayableAsset: {fileID: 11400000, guid: dd9566026364e814a8dad109e6c365ca, type: 2} + m_PlayableAsset: {fileID: 11400000, guid: ee609df51f47bd541a23d5425e289e30, type: 2} m_InitialState: 0 m_WrapMode: 2 m_DirectorUpdateMode: 1 m_InitialTime: 0 m_SceneBindings: - key: {fileID: -7584736085941489071, guid: dd9566026364e814a8dad109e6c365ca, type: 2} - value: {fileID: 3871210969445384207} + value: {fileID: 0} - key: {fileID: -2395336864975438248, guid: dd9566026364e814a8dad109e6c365ca, type: 2} value: {fileID: 0} - key: {fileID: -7231857257271738743, guid: dd9566026364e814a8dad109e6c365ca, type: 2} value: {fileID: 0} + - key: {fileID: -7584736085941489071, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} + value: {fileID: 4544320034237251646} + - key: {fileID: -2395336864975438248, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} + value: {fileID: 0} + - key: {fileID: 3942302933360259000, guid: 1791fd5a24a3142418ed441a2a25b374, type: 2} + value: {fileID: 0} + - key: {fileID: -2395336864975438248, guid: ee609df51f47bd541a23d5425e289e30, type: 2} + value: {fileID: 0} + - key: {fileID: -7584736085941489071, guid: ee609df51f47bd541a23d5425e289e30, type: 2} + value: {fileID: 4544320034237251646} m_ExposedReferences: m_References: [] ---- !u!95 &3871210969445384207 +--- !u!114 &6417332830266550134 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7379304988657006554} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 833a4ccef651449e973e623d9107bef5, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!95 &4544320034237251646 Animator: serializedVersion: 7 m_ObjectHideFlags: 0 @@ -385,3 +443,90 @@ Animator: m_AllowConstantClipSamplingOptimization: 1 m_KeepAnimatorStateOnDisable: 0 m_WriteDefaultValuesOnDisable: 0 +--- !u!1001 &5210361341888085501 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 2084964592986606867} + m_Modifications: + - target: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_Name + value: NextStepIndicator + objectReference: {fileID: 0} + - target: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalScale.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalScale.y + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalScale.z + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalRotation.x + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalRotation.y + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalRotation.z + value: -0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + propertyPath: m_ConstrainProportionsScale + value: 1 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} +--- !u!1 &214996082823304376 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 5383276844808284485, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + m_PrefabInstance: {fileID: 5210361341888085501} + m_PrefabAsset: {fileID: 0} +--- !u!4 &305861995533261809 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 5507990123417429516, guid: afbb486e5456a20479aee4cf8bc949b6, type: 3} + m_PrefabInstance: {fileID: 5210361341888085501} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Prefabs/Managers/SceneManager.prefab b/Assets/Prefabs/Managers/SceneManager.prefab index d28c028c..7c1883c3 100644 --- a/Assets/Prefabs/Managers/SceneManager.prefab +++ b/Assets/Prefabs/Managers/SceneManager.prefab @@ -30,7 +30,7 @@ Transform: m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: - - {fileID: 7090108953567368886} + - {fileID: 4689617562113187593} m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &5327225408302228741 @@ -45,8 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 360f320f4d7a48e38f5fd7cdfa28144a, type: 3} m_Name: m_EditorClassIdentifier: - loadingScreen: {fileID: 3391437592962192360} ---- !u!1001 &6967569849783118800 +--- !u!1001 &4881964705042195055 PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 @@ -138,32 +137,13 @@ PrefabInstance: propertyPath: m_Name value: LoadingScreen objectReference: {fileID: 0} - - target: {fileID: 4869161796575291839, guid: 19fad826fce26d34ba304620216a7f47, type: 3} - propertyPath: m_IsActive - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 5737877680156686392, guid: 19fad826fce26d34ba304620216a7f47, type: 3} - propertyPath: minimumDisplayTime - value: 1 - objectReference: {fileID: 0} m_RemovedComponents: [] m_RemovedGameObjects: [] m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 19fad826fce26d34ba304620216a7f47, type: 3} ---- !u!114 &3391437592962192360 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 5737877680156686392, guid: 19fad826fce26d34ba304620216a7f47, type: 3} - m_PrefabInstance: {fileID: 6967569849783118800} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 1494b10574e74acd880f9101b4248239, type: 3} - m_Name: - m_EditorClassIdentifier: AppleHillsScripts::UI.LoadingScreenController ---- !u!224 &7090108953567368886 stripped +--- !u!224 &4689617562113187593 stripped RectTransform: m_CorrespondingSourceObject: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} - m_PrefabInstance: {fileID: 6967569849783118800} + m_PrefabInstance: {fileID: 4881964705042195055} m_PrefabAsset: {fileID: 0} diff --git a/Assets/Prefabs/UI/LoadingScreen.prefab b/Assets/Prefabs/UI/LoadingScreen.prefab index ddbfae6f..cfd531d9 100644 --- a/Assets/Prefabs/UI/LoadingScreen.prefab +++ b/Assets/Prefabs/UI/LoadingScreen.prefab @@ -229,9 +229,7 @@ MonoBehaviour: m_EditorClassIdentifier: AppleHillsScripts::UI.LoadingScreenController loadingScreenContainer: {fileID: 7270617579256400696} progressBarImage: {fileID: 1674678211233966532} - minimumDisplayTime: 2 - animateProgressBar: 1 - progressBarSmoothTime: 0.1 + minimumDisplayTime: 1 progressUpdateInterval: 0.1 --- !u!1 &6888795583318782279 GameObject: diff --git a/Assets/Scenes/StartingScene.unity b/Assets/Scenes/StartingScene.unity new file mode 100644 index 00000000..fd4cd910 --- /dev/null +++ b/Assets/Scenes/StartingScene.unity @@ -0,0 +1,452 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1001 &180679694 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_Pivot.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_Pivot.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchorMax.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_SizeDelta.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 204042265062571366, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4869161796575291839, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + propertyPath: m_Name + value: LoadingScreen + objectReference: {fileID: 0} + m_RemovedComponents: + - {fileID: 5737877680156686392, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: + - targetCorrespondingSourceObject: {fileID: 4869161796575291839, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + insertIndex: -1 + addedObject: {fileID: 180679698} + m_SourcePrefab: {fileID: 100100000, guid: 19fad826fce26d34ba304620216a7f47, type: 3} +--- !u!1 &180679695 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 4869161796575291839, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + m_PrefabInstance: {fileID: 180679694} + m_PrefabAsset: {fileID: 0} +--- !u!114 &180679696 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 1674678211233966532, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + m_PrefabInstance: {fileID: 180679694} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image +--- !u!1 &180679697 stripped +GameObject: + m_CorrespondingSourceObject: {fileID: 7270617579256400696, guid: 19fad826fce26d34ba304620216a7f47, type: 3} + m_PrefabInstance: {fileID: 180679694} + m_PrefabAsset: {fileID: 0} +--- !u!114 &180679698 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 180679695} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8968b564891a474baae157792b88e75f, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::Bootstrap.InitialLoadingScreen + loadingScreenContainer: {fileID: 180679697} + progressBarImage: {fileID: 180679696} + minimumDisplayTime: 1 + progressUpdateInterval: 0.1 +--- !u!1 &400217123 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 400217126} + - component: {fileID: 400217125} + - component: {fileID: 400217124} + - component: {fileID: 400217127} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &400217124 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 400217123} + m_Enabled: 1 +--- !u!20 &400217125 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 400217123} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &400217126 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 400217123} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + 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} +--- !u!114 &400217127 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 400217123} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 +--- !u!1 &1710655392 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1710655394} + - component: {fileID: 1710655393} + m_Layer: 0 + m_Name: BootSceneController + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1710655393 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1710655392} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fdb797d6fcdc469bb9bfb9ad3c5f51b5, type: 3} + m_Name: + m_EditorClassIdentifier: AppleHillsScripts::Bootstrap.BootSceneController + mainMenuSceneName: MainMenu + minDelayAfterBoot: 0.5 + debugMode: 0 + initialLoadingScreen: {fileID: 180679698} + bootProgressWeight: 0.5 +--- !u!4 &1710655394 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1710655392} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -39.55614, y: 4.31573, 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} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 400217126} + - {fileID: 1710655394} + - {fileID: 180679694} diff --git a/Assets/Scenes/StartingScene.unity.meta b/Assets/Scenes/StartingScene.unity.meta new file mode 100644 index 00000000..7ba42526 --- /dev/null +++ b/Assets/Scenes/StartingScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cf01e2d0135b06c4486d00ef393d0274 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Bootstrap/BootCompletionService.cs b/Assets/Scripts/Bootstrap/BootCompletionService.cs new file mode 100644 index 00000000..c74f6c4e --- /dev/null +++ b/Assets/Scripts/Bootstrap/BootCompletionService.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using UnityEngine; + +namespace Bootstrap +{ + /// + /// 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. + /// + public static class BootCompletionService + { + /// + /// Indicates if the boot process has completed + /// + public static bool IsBootComplete { get; private set; } = false; + + /// + /// Event triggered when boot completes + /// + public static event Action OnBootComplete; + + /// + /// Represents an initialization action with priority + /// + 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 _initializationActions = new List(); + + // TaskCompletionSource for async await pattern + private static TaskCompletionSource _bootCompletionTask = new TaskCompletionSource(); + + /// + /// Called by CustomBoot when the boot process is complete + /// + internal static void HandleBootCompleted() + { + if (IsBootComplete) + return; + + IsBootComplete = true; + + Debug.Log("[BootCompletionService] 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); + + Debug.Log("[BootCompletionService] All boot completion handlers executed"); + } + + /// + /// Register an action to be executed when boot completes. + /// Lower priority numbers run first. + /// + /// The action to execute + /// Priority (lower numbers run first) + /// Name for debugging + 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 + Debug.Log($"[BootCompletionService] Executing late registration: {name} (Priority: {priority})"); + try + { + action(); + } + catch (Exception ex) + { + Debug.LogError($"[BootCompletionService] Error executing init action '{name}': {ex}"); + } + } + else + { + // Otherwise add to the queue + _initializationActions.Add(initAction); + Debug.Log($"[BootCompletionService] Registered init action: {name} (Priority: {priority})"); + } + } + + /// + /// Wait asynchronously for boot completion + /// + /// Task that completes when boot is complete + public static Task WaitForBootCompletionAsync() + { + if (IsBootComplete) + return Task.CompletedTask; + + return _bootCompletionTask.Task; + } + + /// + /// Execute all registered initialization actions in priority order + /// + private static void ExecuteInitializationActions() + { + // Sort by priority (lowest first) + var sortedActions = _initializationActions + .OrderBy(a => a.Priority) + .ToList(); + + foreach (var action in sortedActions) + { + try + { + Debug.Log($"[BootCompletionService] Executing: {action.Name} (Priority: {action.Priority})"); + action.Action(); + } + catch (Exception ex) + { + Debug.LogError($"[BootCompletionService] Error executing init action '{action.Name}': {ex}"); + } + } + + // Clear the list after execution + _initializationActions.Clear(); + } + } +} diff --git a/Assets/Scripts/Bootstrap/BootCompletionService.cs.meta b/Assets/Scripts/Bootstrap/BootCompletionService.cs.meta new file mode 100644 index 00000000..2f02bd4f --- /dev/null +++ b/Assets/Scripts/Bootstrap/BootCompletionService.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aa0228cf33a64515bc166b7a9bc8c0b9 +timeCreated: 1760606319 \ No newline at end of file diff --git a/Assets/Scripts/Bootstrap/BootSceneController.cs b/Assets/Scripts/Bootstrap/BootSceneController.cs new file mode 100644 index 00000000..eba66b8b --- /dev/null +++ b/Assets/Scripts/Bootstrap/BootSceneController.cs @@ -0,0 +1,269 @@ +using System; +using UnityEngine; +using UI; +using Core; +using UnityEngine.SceneManagement; +using Cinematics; + +namespace Bootstrap +{ + /// + /// Controller for the boot scene that coordinates bootstrap initialization with loading screen + /// + public class BootSceneController : MonoBehaviour + { + [SerializeField] private string mainMenuSceneName = "MainMenu"; + [SerializeField] private float minDelayAfterBoot = 0.5f; // Small delay after boot to ensure smooth transition + [SerializeField] private bool debugMode = false; + [SerializeField] private InitialLoadingScreen initialLoadingScreen; // Reference to our specialized loading screen + + // Progress distribution between bootstrap and scene loading + [SerializeField, Range(0.1f, 0.9f)] private float bootProgressWeight = 0.5f; // Default 50/50 split + + private enum LoadingPhase { Bootstrap, SceneLoading } + private LoadingPhase _currentPhase = LoadingPhase.Bootstrap; + + private bool _bootComplete = false; + private bool _hasStartedLoading = false; + private float _sceneLoadingProgress = 0f; + + private void Start() + { + Debug.Log("[BootSceneController] Boot scene started"); + + // Ensure the initial 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 + initialLoadingScreen.ShowLoadingScreen(GetCombinedProgress); + + // Subscribe to boot progress events + 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" + ); + + // In debug mode, log additional information + if (debugMode) + { + InvokeRepeating(nameof(LogDebugInfo), 0.1f, 0.5f); + } + } + + /// + /// Called when the initial loading screen is fully hidden + /// + private void OnInitialLoadingComplete() + { + Debug.Log("[BootSceneController] Initial loading screen fully hidden, boot sequence completed"); + + // Play the intro cinematic if available + if (CinematicsManager.Instance != null) + { + Debug.Log("[BootSceneController] Attempting to play intro cinematic"); + + // Use LoadAndPlayCinematic to play the intro sequence + CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence"); + + // Immediately unload the StartingScene - no need to wait for cinematic to finish + // since CinematicsManager is bootstrapped and won't be unloaded + UnloadStartingScene(); + } + else + { + // If no cinematics manager, unload the StartingScene directly + UnloadStartingScene(); + } + } + + private void OnDestroy() + { + // Clean up event subscriptions + CustomBoot.OnBootCompleted -= OnBootCompleted; + CustomBoot.OnBootProgressChanged -= OnBootProgressChanged; + + if (initialLoadingScreen != null) + { + initialLoadingScreen.OnLoadingScreenFullyHidden -= OnInitialLoadingComplete; + } + + if (debugMode) + { + CancelInvoke(nameof(LogDebugInfo)); + } + } + + /// + /// Progress provider that combines bootstrap and scene loading progress + /// + private float GetCombinedProgress() + { + switch (_currentPhase) + { + case LoadingPhase.Bootstrap: + // Scale bootstrap progress from 0 to bootProgressWeight + return CustomBoot.CurrentProgress * bootProgressWeight; + + case LoadingPhase.SceneLoading: + // Scale scene loading progress from bootProgressWeight to 1.0 + return bootProgressWeight + (_sceneLoadingProgress * (1f - bootProgressWeight)); + + default: + return 0f; + } + } + + private void OnBootProgressChanged(float progress) + { + if (debugMode) + { + Debug.Log($"[BootSceneController] Bootstrap progress: {progress:P0}, Combined: {GetCombinedProgress():P0}"); + } + } + + private void LogDebugInfo() + { + Debug.Log($"[BootSceneController] 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; + + Debug.Log("[BootSceneController] 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) + return; + + _hasStartedLoading = true; + _currentPhase = LoadingPhase.SceneLoading; + LoadMainMenu(); + } + + private async void LoadMainMenu() + { + Debug.Log($"[BootSceneController] Loading main menu scene: {mainMenuSceneName}"); + + try + { + // Initialize scene loading progress to 0 to ensure proper remapping + _sceneLoadingProgress = 0f; + + // Create a custom progress reporter using a custom class + var progressHandler = new ProgressHandler(value => { + // Store the raw scene loading progress (0-1) + _sceneLoadingProgress = value; + + if (debugMode) + { + Debug.Log($"[BootSceneController] Scene loading raw: {value:P0}, Combined: {GetCombinedProgress():P0}"); + } + }); + + // Step 1: Additively load the main menu scene - don't unload StartingScene yet + var op = SceneManager.LoadSceneAsync(mainMenuSceneName, LoadSceneMode.Additive); + + // Disable scene activation until we're ready to show it + op.allowSceneActivation = true; + + // Track progress while loading + while (!op.isDone) + { + progressHandler.ReportProgress(op.progress); + await System.Threading.Tasks.Task.Yield(); + } + + // Update the current gameplay scene in SceneManagerService + SceneManagerService.Instance.CurrentGameplayScene = mainMenuSceneName; + + // Ensure progress is complete + _sceneLoadingProgress = 1f; + + // Step 2: Scene is fully loaded, now hide the loading screen + // This will trigger OnInitialLoadingComplete via the event when animation completes + initialLoadingScreen.HideLoadingScreen(); + + // Step 3: The OnInitialLoadingComplete method will handle playing the intro cinematic + // Step 4: StartingScene will be unloaded after the cinematic completes in OnIntroCinematicFinished + } + catch (Exception e) + { + Debug.LogError($"[BootSceneController] Error loading main menu: {e.Message}"); + // Still try to hide the loading screen even if there was an error + initialLoadingScreen.HideLoadingScreen(); + } + } + + /// + /// Unloads the StartingScene, completing the transition to the main menu + /// + private async void UnloadStartingScene() + { + try + { + // Get the current scene (StartingScene) + Scene currentScene = SceneManager.GetActiveScene(); + string startingSceneName = currentScene.name; + + Debug.Log($"[BootSceneController] Unloading StartingScene: {startingSceneName}"); + + // Unload the StartingScene + await SceneManager.UnloadSceneAsync(startingSceneName); + + // Set the main menu scene as the active scene + Scene mainMenuScene = SceneManager.GetSceneByName(mainMenuSceneName); + SceneManager.SetActiveScene(mainMenuScene); + + Debug.Log($"[BootSceneController] Transition complete: {startingSceneName} unloaded, {mainMenuSceneName} 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}"); + } + } + + /// + /// Helper class to handle progress reporting without running into explicit interface implementation issues + /// + private class ProgressHandler + { + private Action _progressAction; + + public ProgressHandler(Action progressAction) + { + _progressAction = progressAction; + } + + public void ReportProgress(float value) + { + _progressAction?.Invoke(value); + } + } + } +} diff --git a/Assets/Scripts/Bootstrap/BootSceneController.cs.meta b/Assets/Scripts/Bootstrap/BootSceneController.cs.meta new file mode 100644 index 00000000..3ddffcb9 --- /dev/null +++ b/Assets/Scripts/Bootstrap/BootSceneController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fdb797d6fcdc469bb9bfb9ad3c5f51b5 +timeCreated: 1760604860 \ No newline at end of file diff --git a/Assets/Scripts/Bootstrap/CustomBoot.cs b/Assets/Scripts/Bootstrap/CustomBoot.cs index 54b5eeb4..6ff31ccc 100644 --- a/Assets/Scripts/Bootstrap/CustomBoot.cs +++ b/Assets/Scripts/Bootstrap/CustomBoot.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System; +using System.Threading.Tasks; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; @@ -14,9 +15,24 @@ namespace Bootstrap /// Current initialisation status /// public static bool Initialised { get; private set; } + + /// + /// Event triggered when boot progress changes + /// + public static event Action OnBootProgressChanged; + + /// + /// Event triggered when boot process completes + /// + public static event Action OnBootCompleted; + + /// + /// Current progress of the boot process (0-1) + /// + public static float CurrentProgress { get; private set; } /// - // Called as soon as the game begins + /// Called as soon as the game begins /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSplashScreen)] private static void Initialise() @@ -32,6 +48,9 @@ namespace Bootstrap /// public static void PerformInitialisation() { + //Reset progress + CurrentProgress = 0f; + //In editor, perform initialisation synchronously if (Application.isEditor) { @@ -72,6 +91,17 @@ namespace Bootstrap { await LoadCustomBootSettings(); Initialised = true; + CurrentProgress = 1f; + OnBootProgressChanged?.Invoke(1f); + OnBootCompleted?.Invoke(); + + // Notify the BootCompletionService that boot is complete + if (Application.isPlaying) + { + // Direct call to boot completion service + Debug.Log("[CustomBoot] Calling BootCompletionService.HandleBootCompleted()"); + BootCompletionService.HandleBootCompleted(); + } } /// @@ -81,6 +111,17 @@ namespace Bootstrap { LoadCustomBootSettingsSync(); Initialised = true; + CurrentProgress = 1f; + OnBootProgressChanged?.Invoke(1f); + OnBootCompleted?.Invoke(); + + // Notify the BootCompletionService that boot is complete + if (Application.isPlaying) + { + // Direct call to boot completion service + Debug.Log("[CustomBoot] Calling BootCompletionService.HandleBootCompleted()"); + BootCompletionService.HandleBootCompleted(); + } } @@ -177,5 +218,16 @@ namespace Bootstrap result.InitialiseSync(); return handle; } + + /// + /// Updates the current progress value and triggers the progress event + /// + /// Progress value between 0-1 + internal static void UpdateProgress(float progress) + { + CurrentProgress = Mathf.Clamp01(progress); + OnBootProgressChanged?.Invoke(CurrentProgress); + Debug.Log($"[CustomBoot] Progress: {CurrentProgress:P0}"); + } } } \ No newline at end of file diff --git a/Assets/Scripts/Bootstrap/CustomBootSettings.cs b/Assets/Scripts/Bootstrap/CustomBootSettings.cs index ffd2293f..03531f86 100644 --- a/Assets/Scripts/Bootstrap/CustomBootSettings.cs +++ b/Assets/Scripts/Bootstrap/CustomBootSettings.cs @@ -31,15 +31,37 @@ namespace Bootstrap RuntimeContainer = new GameObject($"{name}_Container"); DontDestroyOnLoad(RuntimeContainer); Instances = new GameObject[BootPrefabs.Length]; + + // Calculate total prefabs for progress tracking + int totalPrefabs = BootPrefabs.Length; + float progressPerPrefab = totalPrefabs > 0 ? 1f / totalPrefabs : 1f; + float currentProgress = 0f; + for (var i = 0; i < BootPrefabs.Length; i++) { - if (!BootPrefabs[i]) continue; + if (!BootPrefabs[i]) + { + // Report incremental progress even for null prefabs + currentProgress = (i + 1) * progressPerPrefab; + CustomBoot.UpdateProgress(currentProgress); + continue; + } var instance = GameObject.InstantiateAsync(BootPrefabs[i], RuntimeContainer.transform); while (!instance.isDone) + { + // Report partial progress while waiting + float progressInStep = instance.progress * progressPerPrefab; + float overallProgress = i * progressPerPrefab + progressInStep; + CustomBoot.UpdateProgress(overallProgress); await Task.Yield(); + } Instances[i] = instance.Result[0]; + + // Report completion of this step + currentProgress = (i + 1) * progressPerPrefab; + CustomBoot.UpdateProgress(currentProgress); } } @@ -55,12 +77,28 @@ namespace Bootstrap } Instances = new GameObject[BootPrefabs.Length]; + + // Calculate total prefabs for progress tracking + int totalPrefabs = BootPrefabs.Length; + float progressPerPrefab = totalPrefabs > 0 ? 1f / totalPrefabs : 1f; + float currentProgress = 0f; + for (var i = 0; i < BootPrefabs.Length; i++) { - if (!BootPrefabs[i]) continue; + if (!BootPrefabs[i]) + { + // Report incremental progress even for null prefabs + currentProgress = (i + 1) * progressPerPrefab; + CustomBoot.UpdateProgress(currentProgress); + continue; + } var instance = GameObject.Instantiate(BootPrefabs[i], RuntimeContainer.transform); Instances[i] = instance; + + // Report completion of this step + currentProgress = (i + 1) * progressPerPrefab; + CustomBoot.UpdateProgress(currentProgress); } } diff --git a/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs b/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs new file mode 100644 index 00000000..bebe4d9b --- /dev/null +++ b/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections; +using UnityEngine; +using UnityEngine.UI; +using Core; + +namespace Bootstrap +{ + /// + /// Specialized loading screen controller specifically for the initial boot sequence. + /// This handles the combined progress of bootstrap initialization and main menu loading. + /// + public class InitialLoadingScreen : MonoBehaviour + { + [Header("UI References")] + [SerializeField] private GameObject loadingScreenContainer; + [SerializeField] private Image progressBarImage; + + [Header("Settings")] + [SerializeField] private float minimumDisplayTime = 1.0f; + [SerializeField] private float progressUpdateInterval = 0.1f; + + private float _displayStartTime; + private Coroutine _progressCoroutine; + private bool _loadingComplete = false; + private bool _animationComplete = false; + private Action _onLoadingScreenFullyHidden; + + /// + /// Event that fires when the loading screen is fully hidden (both loading and animation completed) + /// + public event Action OnLoadingScreenFullyHidden; + + /// + /// Delegate for providing progress values from different sources + /// + public delegate float ProgressProvider(); + + /// + /// Current progress provider being used for the loading screen + /// + private ProgressProvider _currentProgressProvider; + + /// + /// Default progress provider that returns 0 (or 1 if loading is complete) + /// + private float DefaultProgressProvider() => _loadingComplete ? 1f : 0f; + + /// + /// Check if the loading screen is currently active + /// + public bool IsActive => loadingScreenContainer != null && loadingScreenContainer.activeSelf; + + private void Awake() + { + if (loadingScreenContainer == null) + loadingScreenContainer = gameObject; + + // Ensure the loading screen is initially hidden + if (loadingScreenContainer != null) + { + loadingScreenContainer.SetActive(false); + } + } + + /// + /// Shows the loading screen and resets the progress bar to zero + /// + /// Optional delegate to provide progress values (0-1). If null, uses default provider. + /// Optional callback when loading screen is fully hidden + public void ShowLoadingScreen(ProgressProvider progressProvider = null, Action onComplete = null) + { + // Store the completion callback + _onLoadingScreenFullyHidden = onComplete; + + // Set the progress provider, use default if none provided + _currentProgressProvider = progressProvider ?? DefaultProgressProvider; + + // Stop any existing progress coroutine + if (_progressCoroutine != null) + { + StopCoroutine(_progressCoroutine); + _progressCoroutine = null; + } + + _displayStartTime = Time.time; + _loadingComplete = false; + _animationComplete = false; + + if (progressBarImage != null) + { + progressBarImage.fillAmount = 0f; + } + + if (loadingScreenContainer != null) + { + loadingScreenContainer.SetActive(true); + } + + // Start the progress filling coroutine + _progressCoroutine = StartCoroutine(AnimateProgressBar()); + } + + /// + /// Animates the progress bar at a steady pace over the minimum display time, + /// while also checking actual loading progress from the current progress provider + /// + private IEnumerator AnimateProgressBar() + { + float startTime = Time.time; + + // Continue until both animation and loading are complete + while (!_animationComplete) + { + // Calculate the steady progress based on elapsed time + float elapsedTime = Time.time - startTime; + float steadyProgress = Mathf.Clamp01(elapsedTime / minimumDisplayTime); + + // Get the actual loading progress from the current provider + float actualProgress = _currentProgressProvider(); + + // If loading is complete, actualProgress should be 1.0 + if (_loadingComplete) + { + actualProgress = 1.0f; + } + + // Use the minimum of steady progress and actual progress + // This ensures we don't show more progress than actual loading + float displayProgress = Mathf.Min(steadyProgress, actualProgress); + + // Log the progress values for debugging + Debug.Log($"[InitialLoadingScreen] Progress - Default: {steadyProgress:F2}, Actual: {actualProgress:F2}, Display: {displayProgress:F2}"); + + // Directly set the progress bar fill amount without smoothing + if (progressBarImage != null) + { + progressBarImage.fillAmount = displayProgress; + } + + // Check if the animation has completed + // Animation is complete when we've reached the minimum display time AND we're at 100% progress + if (steadyProgress >= 1.0f && displayProgress >= 1.0f) + { + _animationComplete = true; + Debug.Log("[InitialLoadingScreen] Animation complete"); + break; + } + + // Wait for the configured interval before updating again + yield return new WaitForSeconds(progressUpdateInterval); + } + + // Ensure we end at 100% progress + if (progressBarImage != null) + { + progressBarImage.fillAmount = 1.0f; + Debug.Log("[InitialLoadingScreen] Final progress set to 1.0"); + } + + // Hide the screen if loading is also complete + if (_loadingComplete) + { + if (loadingScreenContainer != null) + { + loadingScreenContainer.SetActive(false); + Debug.Log("[InitialLoadingScreen] Animation AND loading complete, hiding screen"); + + // Invoke the callback when fully hidden + _onLoadingScreenFullyHidden?.Invoke(); + OnLoadingScreenFullyHidden?.Invoke(); + _onLoadingScreenFullyHidden = null; + } + } + + _progressCoroutine = null; + } + + /// + /// Called when the actual loading process is complete + /// + public void HideLoadingScreen() + { + Debug.Log("[InitialLoadingScreen] Loading complete, marking loading as finished"); + + // Mark that loading is complete + _loadingComplete = true; + + // If animation is already complete, we can hide the screen now + if (_animationComplete) + { + if (loadingScreenContainer != null) + { + loadingScreenContainer.SetActive(false); + Debug.Log("[InitialLoadingScreen] Animation already complete, hiding screen immediately"); + + // Invoke the callback when fully hidden + _onLoadingScreenFullyHidden?.Invoke(); + OnLoadingScreenFullyHidden?.Invoke(); + _onLoadingScreenFullyHidden = null; + } + } + else + { + Debug.Log("[InitialLoadingScreen] Animation still in progress, waiting for it to complete"); + // The coroutine will handle hiding when animation completes + } + } + + /// + /// Waits until the loading screen is fully hidden before continuing + /// + /// Task that completes when the loading screen is hidden + public System.Threading.Tasks.Task WaitForLoadingScreenToHideAsync() + { + var tcs = new System.Threading.Tasks.TaskCompletionSource(); + + // If the loading screen is not active, complete immediately + if (!IsActive) + { + tcs.SetResult(true); + return tcs.Task; + } + + // Store existing callback to chain it + Action existingCallback = _onLoadingScreenFullyHidden; + + // Set new callback + _onLoadingScreenFullyHidden = () => { + // Call existing callback if any + existingCallback?.Invoke(); + + // Complete the task + tcs.SetResult(true); + }; + + return tcs.Task; + } + } +} diff --git a/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta b/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta new file mode 100644 index 00000000..cf2bd6e7 --- /dev/null +++ b/Assets/Scripts/Bootstrap/InitialLoadingScreen.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8968b564891a474baae157792b88e75f +timeCreated: 1760613320 \ No newline at end of file diff --git a/Assets/Scripts/Cinematics/CinematicsManager.cs b/Assets/Scripts/Cinematics/CinematicsManager.cs index e8dbf1a5..66eafa0f 100644 --- a/Assets/Scripts/Cinematics/CinematicsManager.cs +++ b/Assets/Scripts/Cinematics/CinematicsManager.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using Bootstrap; using Core; using UnityEngine; using UnityEngine.AddressableAssets; @@ -26,24 +28,27 @@ namespace Cinematics private Dictionary> _addressableHandles = new Dictionary>(); - public static CinematicsManager Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("CinematicsManager"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + /// + /// Singleton instance of the CinematicsManager. No longer creates an instance if one doesn't exist. + /// + public static CinematicsManager Instance => _instance; + public PlayableDirector playableDirector; + + private void Awake() + { + _instance = this; + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // 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"); + } private void OnEnable() { @@ -62,15 +67,55 @@ namespace Cinematics private void OnApplicationQuit() { + _isQuitting = true; ReleaseAllHandles(); } + /// + /// Initializes required components for the CinematicsManager + /// + private void InitializeComponents() + { + // Initialize PlayableDirector if not set + if (playableDirector == null) + { + playableDirector = GetComponent(); + + // If still null, try to add the component + if (playableDirector == null) + { + playableDirector = gameObject.AddComponent(); + Debug.Log("[CinematicsManager] Added missing PlayableDirector component"); + } + } + + // Initialize _cinematicSprites if not set + if (_cinematicSprites == null) + { + // First try to find in children + _cinematicSprites = GetComponentInChildren(true); + + // If still null, create a new UI Image for cinematics + if (_cinematicSprites == null) + { + Debug.LogWarning("[CinematicsManager] No Image found for cinematics display. Cinematics may not display correctly."); + } + } + } + /// /// Plays a cinematic from an object reference and returns the PlayableDirector playing the timeline /// public PlayableDirector PlayCinematic(PlayableAsset assetToPlay) { - _cinematicSprites.enabled = true; + // Ensure components are initialized before playing + InitializeComponents(); + + if (_cinematicSprites != null) + { + _cinematicSprites.enabled = true; + } + playableDirector.stopped += OnPlayableDirectorStopped; playableDirector.Play(assetToPlay); Logging.Debug("Playing cinematic " + assetToPlay.name); @@ -145,68 +190,5 @@ namespace Cinematics } _addressableHandles.Clear(); } - - private void Awake() - { - PlayStartCinematicOnGameLoad(); - } - - /// - /// Loads a cinematic asynchronously while showing a loading screen, then plays it - /// - /// The addressable key of the cinematic to load - /// The PlayableDirector playing the cinematic - public async System.Threading.Tasks.Task PlayCinematicWithLoadingScreen(string key) - { - Logging.Debug($"[CinematicsManager] Preparing to load cinematic with loading screen: {key}"); - - // First, show the loading screen BEFORE creating any async operations - UI.LoadingScreenController.Instance.ShowLoadingScreen(); - - // Give the loading screen a frame to render - await System.Threading.Tasks.Task.Yield(); - - // Now create the load handle and track its progress - Logging.Debug($"[CinematicsManager] Starting cinematic asset load: {key}"); - AsyncOperationHandle handle = Addressables.LoadAssetAsync(key); - - // Update the loading screen with the progress provider after the handle is created - UI.LoadingScreenController.Instance.ShowLoadingScreen(() => handle.PercentComplete); - - // Wait for the loading to complete - var result = await handle.Task; - - // Store the handle for later release - _addressableHandles[playableDirector] = handle; - - Logging.Debug($"[CinematicsManager] Cinematic loaded: {key}"); - - // Hide the loading screen - UI.LoadingScreenController.Instance.HideLoadingScreen(); - - // Important: Wait for the loading screen to be fully hidden before playing the cinematic - await UI.LoadingScreenController.Instance.WaitForLoadingScreenToHideAsync(); - - Logging.Debug($"[CinematicsManager] Loading screen hidden, now playing cinematic: {key}"); - - // Play the cinematic - return PlayCinematic(result); - } - - private async void PlayStartCinematicOnGameLoad() - { - if (!SceneManager.GetActiveScene().name.ToLower().Contains("mainmenu")) - { - return; - } - - _instance = this; - - playableDirector = GetComponent(); - _cinematicSprites = GetComponentInChildren(true); - - // Use the new method with loading screen instead of direct load - await PlayCinematicWithLoadingScreen("IntroSequence"); - } } } diff --git a/Assets/Scripts/Cinematics/SkipCinematic.cs b/Assets/Scripts/Cinematics/SkipCinematic.cs index 17e546a9..7db5ecf1 100644 --- a/Assets/Scripts/Cinematics/SkipCinematic.cs +++ b/Assets/Scripts/Cinematics/SkipCinematic.cs @@ -1,3 +1,4 @@ +using Bootstrap; using Core; using Input; using UnityEngine; @@ -14,7 +15,14 @@ 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); + } + void Start() { // Reset the progress bar @@ -23,32 +31,76 @@ namespace Cinematics radialProgressBar.fillAmount = 0f; } } - - void OnEnable() - { - if (CinematicsManager.Instance.IsCinematicPlaying) - HandleCinematicStarted(); - - CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted; - CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped; - } void OnDisable() { - CinematicsManager.Instance.OnCinematicStarted -= HandleCinematicStarted; - CinematicsManager.Instance.OnCinematicStopped -= HandleCinematicStopped; - // If still registered, unregister input override - InputManager.Instance.UnregisterOverrideConsumer(this); + // 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"); + } + + private void SubscribeToCinematicsEvents() + { + if (CinematicsManager.Instance == null) return; + + // First unsubscribe to prevent duplicate subscriptions + UnsubscribeFromCinematicsEvents(); + + // Now subscribe + CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted; + CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped; + + // Check if a cinematic is already playing + if (CinematicsManager.Instance.IsCinematicPlaying) + HandleCinematicStarted(); + + Logging.Debug("[SkipCinematic] Subscribed to cinematics events"); + } + + private void UnsubscribeFromCinematicsEvents() + { + if (CinematicsManager.Instance != null) + { + CinematicsManager.Instance.OnCinematicStarted -= HandleCinematicStarted; + CinematicsManager.Instance.OnCinematicStopped -= HandleCinematicStopped; + Logging.Debug("[SkipCinematic] Unsubscribed from cinematics events"); + } + + // If still registered as an input override consumer, unregister + if (InputManager.Instance != null) + { + InputManager.Instance.UnregisterOverrideConsumer(this); + Logging.Debug("[SkipCinematic] Unregistered as input override consumer"); + } } private void HandleCinematicStarted() { - InputManager.Instance.RegisterOverrideConsumer(this); + if (InputManager.Instance != null) + { + InputManager.Instance.RegisterOverrideConsumer(this); + } } private void HandleCinematicStopped() { - InputManager.Instance.UnregisterOverrideConsumer(this); + if (InputManager.Instance != null) + { + InputManager.Instance.UnregisterOverrideConsumer(this); + } } void Update() diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index db282188..daf66e07 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using AppleHills.Core.Interfaces; using Core; using UI; +using Bootstrap; /// /// Singleton manager for global game state and settings. Provides accessors for various gameplay parameters. @@ -15,25 +16,9 @@ public class GameManager : MonoBehaviour private static bool _isQuitting = false; /// - /// Singleton instance of the GameManager. + /// Singleton instance of the GameManager. No longer creates an instance if one doesn't exist. /// - public static GameManager Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("GameManager"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + public static GameManager Instance => _instance; [Header("Settings Status")] [SerializeField] private bool _settingsLoaded = false; @@ -70,10 +55,13 @@ public class GameManager : MonoBehaviour InitializeSettings(); InitializeDeveloperSettings(); + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + // DontDestroyOnLoad(gameObject); } - private void Start() + private void InitializePostBoot() { // Find and subscribe to PauseMenu events PauseMenu pauseMenu = PauseMenu.Instance; diff --git a/Assets/Scripts/Core/ItemManager.cs b/Assets/Scripts/Core/ItemManager.cs index 43647028..fab7930b 100644 --- a/Assets/Scripts/Core/ItemManager.cs +++ b/Assets/Scripts/Core/ItemManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using UnityEngine; using Interactions; +using Bootstrap; namespace Core { @@ -14,23 +15,10 @@ namespace Core private static ItemManager _instance; private static bool _isQuitting; - public static ItemManager Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("ItemManager"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + /// + /// Singleton instance of the ItemManager. No longer creates an instance if one doesn't exist. + /// + public static ItemManager Instance => _instance; private readonly HashSet _pickups = new HashSet(); private readonly HashSet _itemSlots = new HashSet(); @@ -63,13 +51,16 @@ namespace Core void Awake() { _instance = this; + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); } - - void Start() + + private void InitializePostBoot() { // Subscribe to scene load completed so we can clear registrations when scenes change - // Access Instance directly to ensure the service is initialized and we get the event hookup. SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted; + Logging.Debug("[ItemManager] Subscribed to SceneManagerService events"); } void OnDestroy() diff --git a/Assets/Scripts/Core/QuickAccess.cs b/Assets/Scripts/Core/QuickAccess.cs index 954860c7..944e6217 100644 --- a/Assets/Scripts/Core/QuickAccess.cs +++ b/Assets/Scripts/Core/QuickAccess.cs @@ -18,23 +18,11 @@ namespace AppleHills.Core private static QuickAccess _instance; private static bool _isQuitting = false; - public static QuickAccess Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("QuickAccess"); - _instance = go.AddComponent(); - } - } - return _instance; - } - } - + /// + /// Singleton instance of QuickAccess. No longer creates an instance if one doesn't exist. + /// + public static QuickAccess Instance => _instance; + void OnApplicationQuit() { _isQuitting = true; @@ -146,6 +134,8 @@ namespace AppleHills.Core private void Awake() { + _instance = this; + if (!_initialized) { // Subscribe to scene changes diff --git a/Assets/Scripts/Core/SceneManagerService.cs b/Assets/Scripts/Core/SceneManagerService.cs index 3ecc6a8f..0244d624 100644 --- a/Assets/Scripts/Core/SceneManagerService.cs +++ b/Assets/Scripts/Core/SceneManagerService.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using UI; using UnityEngine; using UnityEngine.SceneManagement; +using Bootstrap; namespace Core { @@ -15,26 +16,11 @@ namespace Core private LoadingScreenController _loadingScreen; private static SceneManagerService _instance; private static bool _isQuitting = false; + /// - /// Singleton instance of the SceneManagerService. + /// Singleton instance of the SceneManagerService. No longer creates an instance if one doesn't exist. /// - public static SceneManagerService Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("SceneManagerService"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + public static SceneManagerService Instance => _instance; // Events for scene lifecycle public event Action SceneLoadStarted; @@ -48,30 +34,17 @@ namespace Core private readonly Dictionary _activeUnloads = new(); private const string BootstrapSceneName = "BootstrapScene"; - void Start() - { - _loadingScreen = LoadingScreenController.Instance; - - // Set up loading screen event handlers - SetupLoadingScreenEvents(); - } - - void Awake() { _instance = this; // DontDestroyOnLoad(gameObject); -#if UNITY_EDITOR - // In Editor, set CurrentGameplayScene to the currently open scene at play start - if (Application.isPlaying) - { - var activeScene = SceneManager.GetActiveScene(); - if (activeScene.IsValid()) - { - CurrentGameplayScene = activeScene.name; - } - } -#endif + + // Initialize current scene tracking immediately in Awake + InitializeCurrentSceneTracking(); + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + // Ensure BootstrapScene is loaded at startup var bootstrap = SceneManager.GetSceneByName(BootstrapSceneName); if (!bootstrap.isLoaded) @@ -79,6 +52,48 @@ namespace Core SceneManager.LoadScene(BootstrapSceneName, LoadSceneMode.Additive); } } + + /// + /// Initialize current scene tracking immediately in Awake + /// This ensures scene management works correctly regardless of boot timing + /// + private void InitializeCurrentSceneTracking() + { + // Get the active scene and use it as the current gameplay scene + Scene activeScene = SceneManager.GetActiveScene(); + + if (activeScene.IsValid()) + { + // If this is the MainMenu or another gameplay scene, track it + if (activeScene.name != BootstrapSceneName) + { + CurrentGameplayScene = activeScene.name; + Logging.Debug($"[SceneManagerService] Initialized with current scene: {CurrentGameplayScene}"); + } + // Otherwise default to MainMenu + else + { + CurrentGameplayScene = "MainMenu"; + Logging.Debug($"[SceneManagerService] Initialized with default scene: {CurrentGameplayScene}"); + } + } + else + { + CurrentGameplayScene = "MainMenu"; + Logging.Debug($"[SceneManagerService] 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(); + + Logging.Debug($"[SceneManagerService] Post-boot initialization complete, current scene is: {CurrentGameplayScene}"); + } private void SetupLoadingScreenEvents() { @@ -262,16 +277,27 @@ namespace Core } // Tracks the currently loaded gameplay scene (not persistent/bootstrapper) - public string CurrentGameplayScene { get; private set; } = "MainMenu"; + public string CurrentGameplayScene { get; set; } = "MainMenu"; - public async Task ReloadCurrentScene(IProgress progress = null) + public async Task ReloadCurrentScene(IProgress progress = null, bool autoHideLoadingScreen = true) { - await SwitchSceneAsync(CurrentGameplayScene, progress); + await SwitchSceneAsync(CurrentGameplayScene, progress, autoHideLoadingScreen); } - // Switches from current gameplay scene to a new one - public async Task SwitchSceneAsync(string newSceneName, IProgress progress = null) + /// + /// Switches from current gameplay scene to a new one + /// + /// Name of the scene to load + /// Optional progress reporter + /// Whether to automatically hide the loading screen when complete. If false, caller must hide it manually. + public async Task SwitchSceneAsync(string newSceneName, IProgress progress = null, bool autoHideLoadingScreen = true) { + // Show loading screen at the start (whether using auto-hide or not) + if (_loadingScreen != null && !_loadingScreen.IsActive) + { + _loadingScreen.ShowLoadingScreen(); + } + // Remove all AstarPath (A* Pathfinder) singletons before loading the new scene var astarPaths = FindObjectsByType(FindObjectsSortMode.None); foreach (var astar in astarPaths) @@ -304,6 +330,12 @@ namespace Core await LoadSceneAsync(newSceneName, progress); // Update tracker CurrentGameplayScene = newSceneName; + + // Only hide the loading screen if autoHideLoadingScreen is true + if (autoHideLoadingScreen && _loadingScreen != null) + { + _loadingScreen.HideLoadingScreen(); + } } } } diff --git a/Assets/Scripts/Core/SceneOrientationEnforcer.cs b/Assets/Scripts/Core/SceneOrientationEnforcer.cs index ca5c8b0c..7ab784ff 100644 --- a/Assets/Scripts/Core/SceneOrientationEnforcer.cs +++ b/Assets/Scripts/Core/SceneOrientationEnforcer.cs @@ -5,6 +5,8 @@ using Input; using Settings; using System.Collections; using Minigames.DivingForPictures; +using Bootstrap; +using Core; namespace Utility { @@ -12,23 +14,11 @@ namespace Utility { private static SceneOrientationEnforcer _instance; private static bool _isQuitting; - public static SceneOrientationEnforcer Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("SceneOrientationEnforcer"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); // Uncomment if you want persistence - } - } - return _instance; - } - } + + /// + /// Singleton instance of the SceneOrientationEnforcer. No longer creates an instance if one doesn't exist. + /// + public static SceneOrientationEnforcer Instance => _instance; [Header("Config")] public SceneOrientationConfig orientationConfig; @@ -48,10 +38,16 @@ namespace Utility { _instance = this; OnOrientationCorrect += HandleOrientationCorrect; + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); } - - void Start() + + private void InitializePostBoot() { + // Initialize any dependencies that require other services to be ready + Logging.Debug("[SceneOrientationEnforcer] Post-boot initialization complete"); + // Subscribe to sceneLoaded event SceneManager.sceneLoaded += OnSceneLoaded; // Manually invoke for the first scene (unless it's Main Menu) @@ -74,6 +70,8 @@ namespace Utility return Screen.orientation == ScreenOrientation.Portrait || Screen.orientation == ScreenOrientation.PortraitUpsideDown; case ScreenOrientationRequirement.Landscape: return Screen.orientation == ScreenOrientation.LandscapeLeft || Screen.orientation == ScreenOrientation.LandscapeRight; + case ScreenOrientationRequirement.NotApplicable: + return true; default: return true; } @@ -98,7 +96,7 @@ namespace Utility _isDivingMinigame = IsDivingMinigameScene(scene); - _requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.Portrait; + _requiredOrientation = orientationConfig != null ? orientationConfig.GetRequirementForScene(scene.name) : ScreenOrientationRequirement.NotApplicable; _orientationCorrect = IsOrientationCorrect(); if (!_orientationCorrect) @@ -186,7 +184,7 @@ namespace Utility _orientationCheckCoroutine = null; } - InputManager.Instance.SetInputMode(InputMode.Game); + InputManager.Instance.SetInputMode(InputMode.GameAndUI); } private void CleanupPromptAndCoroutine() diff --git a/Assets/Scripts/Core/Settings/Developer/DeveloperSettingsProvider.cs b/Assets/Scripts/Core/Settings/Developer/DeveloperSettingsProvider.cs index 7b491909..2db54849 100644 --- a/Assets/Scripts/Core/Settings/Developer/DeveloperSettingsProvider.cs +++ b/Assets/Scripts/Core/Settings/Developer/DeveloperSettingsProvider.cs @@ -24,7 +24,7 @@ namespace AppleHills.Core.Settings if (_instance == null && Application.isPlaying) { _instance = FindFirstObjectByType(); - + if (_instance == null) { GameObject go = new GameObject("DeveloperSettingsProvider"); @@ -33,7 +33,7 @@ namespace AppleHills.Core.Settings DontDestroyOnLoad(go); } } - + return _instance; } } diff --git a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs index f2027b21..b8ec90e6 100644 --- a/Assets/Scripts/Data/CardSystem/CardSystemManager.cs +++ b/Assets/Scripts/Data/CardSystem/CardSystemManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using AppleHills.Data.CardSystem; +using Bootstrap; using Core; using UnityEngine; @@ -61,6 +62,15 @@ namespace Data.CardSystem // Build lookup dictionary BuildDefinitionLookup(); + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // Initialize any dependencies that require other services to be ready + Logging.Debug("[CardSystemManager] Post-boot initialization complete"); } private void OnApplicationQuit() diff --git a/Assets/Scripts/Input/InputManager.cs b/Assets/Scripts/Input/InputManager.cs index ada4a561..1cd117d3 100644 --- a/Assets/Scripts/Input/InputManager.cs +++ b/Assets/Scripts/Input/InputManager.cs @@ -5,6 +5,7 @@ using UnityEngine.EventSystems; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using AppleHills.Core.Settings; +using Bootstrap; using Core; // Added for IInteractionSettings namespace Input @@ -35,22 +36,10 @@ namespace Input // Track which consumer is handling the current hold operation private ITouchInputConsumer _activeHoldConsumer; - public static InputManager Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("InputManager"); - _instance = go.AddComponent(); - } - } - return _instance; - } - } + /// + /// Singleton instance of the InputManager. No longer creates an instance if one doesn't exist. + /// + public static InputManager Instance => _instance; // Settings reference private IInteractionSettings _interactionSettings; @@ -66,6 +55,15 @@ namespace Input { _instance = this; + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // Subscribe to scene load completed events now that boot is complete + SceneManagerService.Instance.SceneLoadCompleted += SwitchInputOnSceneLoaded; + // Initialize settings reference _interactionSettings = GameManager.GetSettingsObject(); @@ -78,12 +76,25 @@ namespace Input tapMoveAction = playerInput.actions.FindAction("TapMove", false); holdMoveAction = playerInput.actions.FindAction("HoldMove", false); positionAction = playerInput.actions.FindAction("TouchPosition", false); - } - - private void Start() - { - // SceneManagerService.Instance.SceneLoadCompleted += SwitchInputOnSceneLoaded; + + if (tapMoveAction != null) + tapMoveAction.performed += OnTapMovePerformed; + if (holdMoveAction != null) + { + holdMoveAction.performed += OnHoldMoveStarted; + holdMoveAction.canceled += OnHoldMoveCanceled; + } + SwitchInputOnSceneLoaded(SceneManager.GetActiveScene().name); + + Logging.Debug("[InputManager] Subscribed to SceneManagerService events"); + } + + private void OnDestroy() + { + // Unsubscribe from SceneManagerService + if (SceneManagerService.Instance != null) + SceneManagerService.Instance.SceneLoadCompleted -= SwitchInputOnSceneLoaded; } private void SwitchInputOnSceneLoaded(string sceneName) @@ -122,17 +133,6 @@ namespace Input break; } } - - void OnEnable() - { - if (tapMoveAction != null) - tapMoveAction.performed += OnTapMovePerformed; - if (holdMoveAction != null) - { - holdMoveAction.performed += OnHoldMoveStarted; - holdMoveAction.canceled += OnHoldMoveCanceled; - } - } void OnDisable() { @@ -149,7 +149,7 @@ namespace Input { _isQuitting = true; } - + /// /// Sets the default ITouchInputConsumer to receive input events. /// diff --git a/Assets/Scripts/Interactions/Interactable.cs b/Assets/Scripts/Interactions/Interactable.cs index 521d3c1b..e21cec6a 100644 --- a/Assets/Scripts/Interactions/Interactable.cs +++ b/Assets/Scripts/Interactions/Interactable.cs @@ -418,7 +418,8 @@ namespace Interactions { // Check for ObjectiveStepBehaviour and lock state var step = GetComponent(); - if (step != null && !step.IsStepUnlocked()) + var slot = GetComponent(); + if (step != null && !step.IsStepUnlocked() && slot == null) { DebugUIMessage.Show("This step is locked!", Color.yellow); BroadcastInteractionComplete(false); diff --git a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs index e11e168d..91937df3 100644 --- a/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs +++ b/Assets/Scripts/PuzzleS/ObjectiveStepBehaviour.cs @@ -1,10 +1,7 @@ using Input; using Interactions; using UnityEngine; -using System; -using AppleHills.Core.Settings; using Core; -using UnityEngine.Serialization; namespace PuzzleS { @@ -37,22 +34,52 @@ namespace PuzzleS void Awake() { _interactable = GetComponent(); + + // Initialize the indicator if it exists, but ensure it's hidden initially + if (puzzleIndicator != null) + { + // The indicator should start inactive until we determine its proper state + puzzleIndicator.SetActive(false); + + // Get the IPuzzlePrompt component + _indicator = puzzleIndicator.GetComponent(); + + if (_indicator == null) + { + // Try to find it in children if not on the root + _indicator = puzzleIndicator.GetComponentInChildren(); + } + + if (_indicator == null) + { + Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt"); + } + } } void OnEnable() { if (_interactable == null) _interactable = GetComponent(); + if (_interactable != null) { _interactable.interactionStarted.AddListener(OnInteractionStarted); _interactable.interactionComplete.AddListener(OnInteractionComplete); } - PuzzleManager.Instance?.RegisterStepBehaviour(this); - // Check if this step was already unlocked - if (stepData != null && PuzzleManager.Instance != null && PuzzleManager.Instance.IsStepUnlocked(stepData)) + } + + void Start() + { + // Simply register with the PuzzleManager + // The manager will handle state updates appropriately based on whether data is loaded + if (stepData != null && PuzzleManager.Instance != null) { - UnlockStep(); + PuzzleManager.Instance.RegisterStepBehaviour(this); + } + else if (stepData == null) + { + Logging.Warning($"[Puzzles] Cannot register step on {gameObject.name}: stepData is null"); } } @@ -63,7 +90,11 @@ namespace PuzzleS _interactable.interactionStarted.RemoveListener(OnInteractionStarted); _interactable.interactionComplete.RemoveListener(OnInteractionComplete); } - PuzzleManager.Instance?.UnregisterStepBehaviour(this); + + if (PuzzleManager.Instance != null && stepData != null) + { + PuzzleManager.Instance.UnregisterStepBehaviour(this); + } } /// @@ -73,7 +104,7 @@ namespace PuzzleS public void UpdateProximityState(ProximityState newState) { if (_currentProximityState == newState) return; - if (_indicator == null) return; + if (!_isUnlocked) return; // Don't process state changes if locked // Determine state changes and call appropriate methods if (newState == ProximityState.Close) @@ -97,6 +128,9 @@ namespace PuzzleS /// public virtual void OnShow() { + if (puzzleIndicator != null) + puzzleIndicator.SetActive(true); + // Delegate to indicator if available if (IsIndicatorValid()) { @@ -104,7 +138,6 @@ namespace PuzzleS return; } - // Default fallback behavior Logging.Debug($"[Puzzles] Prompt shown for {stepData?.stepId} on {gameObject.name}"); } @@ -113,14 +146,15 @@ namespace PuzzleS /// public virtual void OnHide() { + if (puzzleIndicator != null) + puzzleIndicator.SetActive(false); + // Delegate to indicator if available if (IsIndicatorValid()) { _indicator.OnHide(); - return; } - // Default fallback behavior Logging.Debug($"[Puzzles] Prompt hidden for {stepData?.stepId} on {gameObject.name}"); } @@ -138,9 +172,6 @@ namespace PuzzleS _indicator.ShowFar(); return; } - - // Default fallback behavior - Logging.Debug($"[Puzzles] Player entered far range of {stepData?.stepId} on {gameObject.name}"); } /// @@ -157,9 +188,6 @@ namespace PuzzleS _indicator.ShowClose(); return; } - - // Default fallback behavior - Logging.Debug($"[Puzzles] Player entered close range of {stepData?.stepId} on {gameObject.name}"); } /// @@ -176,9 +204,6 @@ namespace PuzzleS _indicator.HideClose(); return; } - - // Default fallback behavior - Logging.Debug($"[Puzzles] Player exited close range of {stepData?.stepId} on {gameObject.name}"); } /// @@ -195,9 +220,6 @@ namespace PuzzleS _indicator.HideFar(); return; } - - // Default fallback behavior - Logging.Debug($"[Puzzles] Player exited far range of {stepData?.stepId} on {gameObject.name}"); } /// @@ -205,57 +227,42 @@ namespace PuzzleS /// public void UnlockStep() { + if (_isUnlocked) return; + _isUnlocked = true; Logging.Debug($"[Puzzles] Step unlocked: {stepData?.stepId} on {gameObject.name}"); - // Show indicator if enabled in settings - if (puzzleIndicator != null) + // Make the indicator visible since this step is now unlocked + OnShow(); + + if (IsIndicatorValid()) { - // Try to get the IPuzzlePrompt component from the spawned indicator - _indicator = puzzleIndicator.GetComponent(); - - if (_indicator == null) + // Set the correct state based on current player distance + Transform playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; + if (playerTransform != null) { - // Try to find it in children if not on the root - _indicator = puzzleIndicator.GetComponentInChildren(); - } - - if (_indicator == null) - { - Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt"); - } - else - { - // First show the indicator - _indicator.OnShow(); + float distance = Vector3.Distance(transform.position, playerTransform.position); + float promptRange = AppleHills.SettingsAccess.GetPuzzlePromptRange(); - // Then set the correct state based on current player distance - Transform playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; - if (playerTransform != null) + if (distance <= promptRange) { - float distance = Vector3.Distance(transform.position, playerTransform.position); - float promptRange = AppleHills.SettingsAccess.GetPuzzlePromptRange(); - - if (distance <= promptRange) - { - // Player is in close range - _currentProximityState = ProximityState.Close; - _indicator.ShowClose(); - } - else - { - // Player is in far range - _currentProximityState = ProximityState.Far; - _indicator.ShowFar(); - } + // Player is in close range + _currentProximityState = ProximityState.Close; + _indicator.ShowClose(); } else { - // Default to far if player not found + // Player is in far range _currentProximityState = ProximityState.Far; _indicator.ShowFar(); } } + else + { + // Default to far if player not found + _currentProximityState = ProximityState.Far; + _indicator.ShowFar(); + } } } @@ -264,14 +271,18 @@ namespace PuzzleS /// public void LockStep() { + if (!_isUnlocked && puzzleIndicator != null) + { + // Make sure indicator is hidden if we're already locked + puzzleIndicator.SetActive(false); + return; + } + _isUnlocked = false; Logging.Debug($"[Puzzles] Step locked: {stepData?.stepId} on {gameObject.name}"); - // Hide indicator - if (IsIndicatorValid()) - { - _indicator.OnHide(); - } + // Hide the indicator + OnHide(); } /// @@ -287,7 +298,7 @@ namespace PuzzleS /// private void OnInteractionStarted(PlayerTouchController playerRef, FollowerController followerRef) { - // Optionally handle started interaction (e.g. visual feedback) + // Empty - handled by Interactable } /// @@ -297,12 +308,17 @@ namespace PuzzleS private void OnInteractionComplete(bool success) { if (!_isUnlocked) return; - if (success) + if (success && !_isCompleted) { Logging.Debug($"[Puzzles] Step interacted: {stepData?.stepId} on {gameObject.name}"); _isCompleted = true; PuzzleManager.Instance?.MarkPuzzleStepCompleted(stepData); - Destroy(puzzleIndicator); + + if (puzzleIndicator != null) + { + Destroy(puzzleIndicator); + _indicator = null; + } } } @@ -323,7 +339,7 @@ namespace PuzzleS // Draw threshold circle Gizmos.color = Color.cyan; - Gizmos.DrawWireSphere(transform.position, promptRange / 2f); + Gizmos.DrawWireSphere(transform.position, promptRange); } } } diff --git a/Assets/Scripts/PuzzleS/PuzzleChainSO.cs b/Assets/Scripts/PuzzleS/PuzzleChainSO.cs new file mode 100644 index 00000000..3340dfa7 --- /dev/null +++ b/Assets/Scripts/PuzzleS/PuzzleChainSO.cs @@ -0,0 +1,103 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.AddressableAssets; + +namespace PuzzleS +{ + /// + /// Represents a complete chain of puzzle steps that form a logical sequence. + /// This is automatically generated from folder structure during asset import. + /// + [CreateAssetMenu(fileName = "PuzzleChain", menuName = "AppleHills/Items & Puzzles/PuzzleChain")] + public class PuzzleChainSO : ScriptableObject + { + /// + /// Unique identifier for this puzzle chain, automatically set to match folder name + /// + public string chainId; + + /// + /// Display name for this chain + /// + public string displayName; + + /// + /// Description of this puzzle chain + /// + [TextArea] + public string description; + + /// + /// All steps that belong to this puzzle chain + /// + public List allSteps = new List(); + + /// + /// Initial steps that should be unlocked when the puzzle chain starts + /// (steps with no dependencies) + /// + public List initialSteps = new List(); + + /// + /// Optional requirement for this entire chain to be activated + /// If not null, this chain requires the specified chain to be completed first + /// + public PuzzleChainSO requiredChain; + + /// + /// Pre-processed dependency data built at edit time. + /// Maps step IDs to arrays of dependency step IDs + /// + [HideInInspector] + public Dictionary stepDependencies = new Dictionary(); + + /// + /// Gets all steps that will be unlocked by completing the given step + /// + public List GetUnlockedSteps(string stepId) + { + var result = new List(); + foreach (var step in allSteps) + { + if (step.stepId == stepId && step != null) + { + return step.unlocks; + } + } + return result; + } + + /// + /// Gets all steps that will be unlocked by completing the given step + /// + public List GetUnlockedSteps(PuzzleStepSO completedStep) + { + return completedStep != null ? completedStep.unlocks : new List(); + } + + /// + /// Check if this step is an initial step (no dependencies) + /// + public bool IsInitialStep(PuzzleStepSO step) + { + return step != null && initialSteps.Contains(step); + } + + /// + /// Check if all steps in this chain are completed + /// + public bool IsChainComplete(HashSet completedSteps) + { + if (completedSteps == null) return false; + + foreach (var step in allSteps) + { + if (step != null && !completedSteps.Contains(step)) + { + return false; + } + } + return true; + } + } +} diff --git a/Assets/Scripts/PuzzleS/PuzzleChainSO.cs.meta b/Assets/Scripts/PuzzleS/PuzzleChainSO.cs.meta new file mode 100644 index 00000000..62518a80 --- /dev/null +++ b/Assets/Scripts/PuzzleS/PuzzleChainSO.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 58109a40325e47f2a8a3b9264d8938dd +timeCreated: 1760532067 \ No newline at end of file diff --git a/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs b/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs new file mode 100644 index 00000000..086a8a13 --- /dev/null +++ b/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.AddressableAssets; + +namespace PuzzleS +{ + /// + /// Represents all puzzle steps in a level. + /// This is automatically generated from folder structure during asset import. + /// + [CreateAssetMenu(fileName = "LevelPuzzleData", menuName = "AppleHills/Items & Puzzles/LevelPuzzleData")] + public class PuzzleLevelDataSO : ScriptableObject + { + /// + /// Unique identifier for this level, automatically set to match folder name + /// + public string levelId; + + /// + /// Display name for this level + /// + public string displayName; + + /// + /// All puzzle steps in this level + /// + public List allSteps = new List(); + + /// + /// Steps that should be unlocked at level start (no dependencies) + /// + public List initialSteps = new List(); + + /// + /// Pre-processed dependency data built at edit time. + /// Maps step IDs to arrays of dependency step IDs (which steps are required by each step) + /// + public Dictionary stepDependencies = new Dictionary(); + + /// + /// Check if all steps in the level are complete + /// + public bool IsLevelComplete(HashSet completedSteps) + { + if (completedSteps == null) return false; + + foreach (var step in allSteps) + { + if (step != null && !completedSteps.Contains(step)) + { + return false; + } + } + return true; + } + + /// + /// Gets all steps that will be unlocked by completing the given step + /// + public List GetUnlockedSteps(PuzzleStepSO completedStep) + { + return completedStep != null ? completedStep.unlocks : new List(); + } + + /// + /// Check if this step is an initial step (no dependencies) + /// + public bool IsInitialStep(PuzzleStepSO step) + { + return step != null && initialSteps.Contains(step); + } + } +} diff --git a/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs.meta b/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs.meta new file mode 100644 index 00000000..0fde9380 --- /dev/null +++ b/Assets/Scripts/PuzzleS/PuzzleLevelDataSO.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0a79780a5a0d498084afd737d4515e3b +timeCreated: 1760532084 \ No newline at end of file diff --git a/Assets/Scripts/PuzzleS/PuzzleManager.cs b/Assets/Scripts/PuzzleS/PuzzleManager.cs index 61e1a2b3..1c8f522f 100644 --- a/Assets/Scripts/PuzzleS/PuzzleManager.cs +++ b/Assets/Scripts/PuzzleS/PuzzleManager.cs @@ -5,7 +5,11 @@ using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; using AppleHills.Core.Settings; -using Core; // Added for IInteractionSettings +using Bootstrap; +using Core; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using Utils; namespace PuzzleS { @@ -26,71 +30,103 @@ namespace PuzzleS // Settings reference private IInteractionSettings _interactionSettings; + // Current level puzzle data + private PuzzleLevelDataSO _currentLevelData; + private AsyncOperationHandle _levelDataLoadOperation; + private bool _isDataLoaded = false; + + // Store registered behaviors that are waiting for data to be loaded + private List _registeredBehaviours = new List(); + /// /// Singleton instance of the PuzzleManager. /// - public static PuzzleManager Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("PuzzleManager"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + public static PuzzleManager Instance => _instance; // Events to notify about step lifecycle public event Action OnStepCompleted; public event Action OnStepUnlocked; + public event Action OnLevelDataLoaded; + public event Action OnAllPuzzlesComplete; private HashSet _completedSteps = new HashSet(); private HashSet _unlockedSteps = new HashSet(); // Registration for ObjectiveStepBehaviour private Dictionary _stepBehaviours = new Dictionary(); - // Runtime dependency graph - private Dictionary> _runtimeDependencies = new Dictionary>(); void Awake() { _instance = this; - // DontDestroyOnLoad(gameObject); - SceneManager.sceneLoaded += OnSceneLoaded; - + // Initialize settings reference _interactionSettings = GameManager.GetSettingsObject(); + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); } - - void Start() + + private void InitializePostBoot() { + // Subscribe to SceneManagerService events after boot is complete + SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted; + SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted; + // Find player transform _playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; // Start proximity check coroutine StartProximityChecks(); + + // Load puzzle data for the current scene if not already loading + if (_currentLevelData == null && !_isDataLoaded) + { + LoadPuzzleDataForCurrentScene(); + } + + Logging.Debug("[PuzzleManager] Subscribed to SceneManagerService events"); } - + void OnDestroy() { - SceneManager.sceneLoaded -= OnSceneLoaded; StopProximityChecks(); + + // Unsubscribe from scene manager events + if (SceneManagerService.Instance != null) + { + SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted; + SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted; + } + + // Release addressable handle if needed + if (_levelDataLoadOperation.IsValid()) + { + Addressables.Release(_levelDataLoadOperation); + } } - private void OnSceneLoaded(Scene scene, LoadSceneMode mode) + /// + /// Called when a scene is starting to load + /// + public void OnSceneLoadStarted(string sceneName) { - SceneManager.sceneLoaded -= OnSceneLoaded; + // Reset data loaded state when changing scenes to avoid using stale data + _isDataLoaded = false; + Logging.Debug($"[Puzzles] Scene load started: {sceneName}, marked puzzle data as not loaded"); + } + + /// + /// Called when a scene is loaded + /// + public void OnSceneLoadCompleted(string sceneName) + { + // Skip for non-gameplay scenes + if (sceneName == "BootstrapScene" || string.IsNullOrEmpty(sceneName)) + { + return; + } - Logging.Debug("[MDPI] OnSceneLoaded"); - _runtimeDependencies.Clear(); - BuildRuntimeDependencies(); - UnlockInitialSteps(); + Logging.Debug($"[Puzzles] Scene loaded: {sceneName}, loading puzzle data"); + LoadPuzzleDataForCurrentScene(sceneName); // Find player transform again in case it changed with scene load _playerTransform = GameObject.FindGameObjectWithTag("Player")?.transform; @@ -99,6 +135,72 @@ namespace PuzzleS StartProximityChecks(); } + /// + /// Load puzzle data for the current scene + /// + private void LoadPuzzleDataForCurrentScene(string sceneName = null) + { + string currentScene = sceneName ?? SceneManagerService.Instance.CurrentGameplayScene; + if (string.IsNullOrEmpty(currentScene)) + { + Logging.Warning("[Puzzles] Cannot load puzzle data: Current scene name is empty"); + return; + } + + _isDataLoaded = false; + string addressablePath = $"Puzzles/{currentScene}"; + + Logging.Debug($"[Puzzles] Loading puzzle data from addressable: {addressablePath}"); + + // Release previous handle if needed + if (_levelDataLoadOperation.IsValid()) + { + Addressables.Release(_levelDataLoadOperation); + } + + // Check if the addressable exists before trying to load it + if (!AppleHillsUtils.AddressableKeyExists(addressablePath)) + { + Logging.Warning($"[Puzzles] Puzzle data does not exist for scene: {currentScene}"); + _isDataLoaded = true; // Mark as loaded but with no data + _currentLevelData = null; + return; + } + + // Load the level data asset + _levelDataLoadOperation = Addressables.LoadAssetAsync(addressablePath); + _levelDataLoadOperation.Completed += handle => + { + if (handle.Status == AsyncOperationStatus.Succeeded) + { + _currentLevelData = handle.Result; + Logging.Debug($"[Puzzles] Loaded level data: {_currentLevelData.levelId} with {_currentLevelData.allSteps.Count} steps"); + + // Reset state + _completedSteps.Clear(); + _unlockedSteps.Clear(); + + // Unlock initial steps + UnlockInitialSteps(); + + // Update all registered behaviors now that data is loaded + UpdateAllRegisteredBehaviors(); + + // Mark data as loaded + _isDataLoaded = true; + + // Notify listeners + OnLevelDataLoaded?.Invoke(_currentLevelData); + } + else + { + Logging.Warning($"[Puzzles] Failed to load puzzle data for {currentScene}: {handle.OperationException?.Message}"); + _isDataLoaded = true; // Mark as loaded but with error + _currentLevelData = null; + } + }; + } + /// /// Start the proximity check coroutine. /// @@ -138,7 +240,7 @@ namespace PuzzleS foreach (var kvp in _stepBehaviours) { if (kvp.Value == null) continue; - if (IsPuzzleStepCompleted(kvp.Value.stepData.stepId)) continue; + if (IsPuzzleStepCompleted(kvp.Key.stepId)) continue; float distance = Vector3.Distance(_playerTransform.position, kvp.Value.transform.position); @@ -157,6 +259,24 @@ namespace PuzzleS } } + /// + /// Update all registered behaviors with their current state + /// + private void UpdateAllRegisteredBehaviors() + { + foreach (var behaviour in _registeredBehaviours) + { + if (behaviour == null) continue; + + // Only update if the step is in our dictionary + bool stepUnlocked = IsStepUnlocked(behaviour.stepData); + if (stepUnlocked) + { + UpdateStepState(behaviour); + } + } + } + /// /// Registers a step behaviour with the manager. /// @@ -164,21 +284,51 @@ namespace PuzzleS public void RegisterStepBehaviour(ObjectiveStepBehaviour behaviour) { if (behaviour?.stepData == null) return; + + // Always add to our registered behaviors list + if (!_registeredBehaviours.Contains(behaviour)) + { + _registeredBehaviours.Add(behaviour); + } + + // Add to the step behaviours dictionary if not already there if (!_stepBehaviours.ContainsKey(behaviour.stepData)) { _stepBehaviours.Add(behaviour.stepData, behaviour); - _runtimeDependencies.Clear(); - foreach (var step in _stepBehaviours.Values) - { - step.LockStep(); - } - _unlockedSteps.Clear(); - BuildRuntimeDependencies(); - UnlockInitialSteps(); Logging.Debug($"[Puzzles] Registered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}"); + + // Only update state if data is already loaded + if (_isDataLoaded && _currentLevelData != null) + { + UpdateStepState(behaviour); + } + // Otherwise, the state will be updated when data loads in UpdateAllRegisteredBehaviors } } - + + /// + /// Updates a step's state based on the current puzzle state. + /// + private void UpdateStepState(ObjectiveStepBehaviour behaviour) + { + if (behaviour?.stepData == null) return; + + // If step is already completed, ignore + if (_completedSteps.Contains(behaviour.stepData)) + return; + + // If step is already unlocked, update the behaviour + if (_unlockedSteps.Contains(behaviour.stepData)) + { + behaviour.UnlockStep(); + } + else + { + // Make sure it's locked + behaviour.LockStep(); + } + } + /// /// Unregisters a step behaviour from the manager. /// @@ -186,57 +336,27 @@ namespace PuzzleS public void UnregisterStepBehaviour(ObjectiveStepBehaviour behaviour) { if (behaviour?.stepData == null) return; + _stepBehaviours.Remove(behaviour.stepData); + _registeredBehaviours.Remove(behaviour); + Logging.Debug($"[Puzzles] Unregistered step: {behaviour.stepData.stepId} on {behaviour.gameObject.name}"); } /// - /// Builds the runtime dependency graph for all registered steps. - /// - private void BuildRuntimeDependencies() - { - _runtimeDependencies = PuzzleGraphUtility.BuildDependencyGraph(_stepBehaviours.Keys); - foreach (var step in _runtimeDependencies.Keys) - { - foreach (var dep in _runtimeDependencies[step]) - { - Logging.Debug($"[Puzzles] Step {step.stepId} depends on {dep.stepId}"); - } - } - Logging.Debug($"[Puzzles] Runtime dependencies built. Total steps: {_stepBehaviours.Count}"); - } - - /// - /// Unlocks all initial steps (those with no dependencies) and any steps whose dependencies are already met. + /// Unlocks all initial steps (those with no dependencies) /// private void UnlockInitialSteps() { - // First, unlock all steps with no dependencies (initial steps) - var initialSteps = PuzzleGraphUtility.FindInitialSteps(_runtimeDependencies); - foreach (var step in initialSteps) + if (_currentLevelData == null) return; + + // Unlock initial steps + foreach (var step in _currentLevelData.initialSteps) { - Logging.Debug($"[Puzzles] Initial step unlocked: {step.stepId}"); UnlockStep(step); } - // Keep trying to unlock steps as long as we're making progress - bool madeProgress; - do - { - madeProgress = false; - - // Check all steps that haven't been unlocked yet - foreach (var step in _runtimeDependencies.Keys.Where(s => !_unlockedSteps.Contains(s))) - { - // Check if all dependencies have been completed - if (AreRuntimeDependenciesMet(step)) - { - Logging.Debug($"[Puzzles] Chain step unlocked: {step.stepId}"); - UnlockStep(step); - madeProgress = true; - } - } - } while (madeProgress); + Logging.Debug($"[Puzzles] Unlocked {_unlockedSteps.Count} initial steps"); } /// @@ -246,24 +366,29 @@ namespace PuzzleS public void MarkPuzzleStepCompleted(PuzzleStepSO step) { if (_completedSteps.Contains(step)) return; + if (_currentLevelData == null) return; + _completedSteps.Add(step); Logging.Debug($"[Puzzles] Step completed: {step.stepId}"); // Broadcast completion OnStepCompleted?.Invoke(step); - foreach (var unlock in step.unlocks) + // Unlock steps that are unlocked by this step + foreach (var unlockStep in _currentLevelData.GetUnlockedSteps(step)) { - if (AreRuntimeDependenciesMet(unlock)) + if (AreStepDependenciesMet(unlockStep)) { - Logging.Debug($"[Puzzles] Unlocking step {unlock.stepId} after completing {step.stepId}"); - UnlockStep(unlock); + Logging.Debug($"[Puzzles] Unlocking step {unlockStep.stepId} after completing {step.stepId}"); + UnlockStep(unlockStep); } else { - Logging.Debug($"[Puzzles] Step {unlock.stepId} not unlocked yet, waiting for other dependencies"); + Logging.Debug($"[Puzzles] Step {unlockStep.stepId} not unlocked yet, waiting for other dependencies"); } } + + // Check if all puzzle steps are now complete CheckPuzzleCompletion(); } @@ -272,13 +397,33 @@ namespace PuzzleS /// /// The step to check. /// True if all dependencies are met, false otherwise. - private bool AreRuntimeDependenciesMet(PuzzleStepSO step) + private bool AreStepDependenciesMet(PuzzleStepSO step) { - if (!_runtimeDependencies.ContainsKey(step) || _runtimeDependencies[step].Count == 0) return true; - foreach (var dep in _runtimeDependencies[step]) + if (_currentLevelData == null || step == null) return false; + + // If it's an initial step, it has no dependencies + if (_currentLevelData.IsInitialStep(step)) return true; + + // Check if dependencies are met using pre-processed data + if (_currentLevelData.stepDependencies.TryGetValue(step.stepId, out string[] dependencies)) { - if (!_completedSteps.Contains(dep)) return false; + foreach (var depId in dependencies) + { + // Find the dependency step + bool dependencyMet = false; + foreach (var completedStep in _completedSteps) + { + if (completedStep.stepId == depId) + { + dependencyMet = true; + break; + } + } + + if (!dependencyMet) return false; + } } + return true; } @@ -290,6 +435,7 @@ namespace PuzzleS { if (_unlockedSteps.Contains(step)) return; _unlockedSteps.Add(step); + if (_stepBehaviours.TryGetValue(step, out var behaviour)) { behaviour.UnlockStep(); @@ -301,14 +447,18 @@ namespace PuzzleS } /// - /// Checks if the puzzle is complete (all steps finished). + /// Checks if the puzzle is complete (all steps in level finished). /// private void CheckPuzzleCompletion() { - if (_completedSteps.Count == _stepBehaviours.Count) + if (_currentLevelData == null) return; + + if (_currentLevelData.IsLevelComplete(_completedSteps)) { - Logging.Debug("[Puzzles] Puzzle complete! All steps finished."); - // TODO: Fire puzzle complete event or trigger outcome logic + Logging.Debug("[Puzzles] All puzzles complete! Level finished."); + + // Fire level complete event + OnAllPuzzlesComplete?.Invoke(_currentLevelData); } } @@ -317,9 +467,6 @@ namespace PuzzleS /// public bool IsStepUnlocked(PuzzleStepSO step) { - // _runtimeDependencies.Clear(); - // BuildRuntimeDependencies(); - // UnlockInitialSteps(); return _unlockedSteps.Contains(step); } @@ -332,6 +479,23 @@ namespace PuzzleS { return _completedSteps.Any(step => step.stepId == stepId); } + + /// + /// Get the current level puzzle data + /// + public PuzzleLevelDataSO GetCurrentLevelData() + { + return _currentLevelData; + } + + /// + /// Checks if puzzle data is loaded + /// + /// True if data loading has completed (whether successful or not) + public bool IsDataLoaded() + { + return _isDataLoaded; + } void OnApplicationQuit() { diff --git a/Assets/Scripts/PuzzleS/PuzzleStepSO.cs b/Assets/Scripts/PuzzleS/PuzzleStepSO.cs index 7a2bd299..8de0850c 100644 --- a/Assets/Scripts/PuzzleS/PuzzleStepSO.cs +++ b/Assets/Scripts/PuzzleS/PuzzleStepSO.cs @@ -1,5 +1,6 @@ using UnityEngine; using System.Collections.Generic; +using System; /// /// ScriptableObject representing a single puzzle step, its display info, and which steps it unlocks. @@ -29,4 +30,58 @@ public class PuzzleStepSO : ScriptableObject /// [Header("Unlocks")] public List unlocks = new List(); + + /// + /// Override Equals to compare by stepId rather than reference equality. + /// This ensures consistent behavior across different platforms (Editor vs Mobile). + /// + /// Object to compare to + /// True if the objects represent the same puzzle step + public override bool Equals(object obj) + { + if (obj == null) return false; + + // Check if the object is actually a PuzzleStepSO + PuzzleStepSO other = obj as PuzzleStepSO; + if (other == null) return false; + + // Compare by stepId instead of reference + return string.Equals(stepId, other.stepId, StringComparison.Ordinal); + } + + /// + /// Override GetHashCode to be consistent with the Equals method. + /// This is crucial for HashSet and Dictionary to work properly. + /// + /// Hash code based on stepId + public override int GetHashCode() + { + // Generate hash code from stepId to ensure consistent hashing + return stepId != null ? stepId.GetHashCode() : 0; + } + + /// + /// Override == operator to use our custom equality logic + /// + public static bool operator ==(PuzzleStepSO a, PuzzleStepSO b) + { + // Check if both are null or if they're the same instance + if (ReferenceEquals(a, b)) + return true; + + // Check if either is null (but not both, as that's handled above) + if (((object)a == null) || ((object)b == null)) + return false; + + // Use our custom Equals method + return a.Equals(b); + } + + /// + /// Override != operator to be consistent with == operator + /// + public static bool operator !=(PuzzleStepSO a, PuzzleStepSO b) + { + return !(a == b); + } } diff --git a/Assets/Scripts/Settings/SceneOrientationConfig.cs b/Assets/Scripts/Settings/SceneOrientationConfig.cs index fe92171c..34c76e11 100644 --- a/Assets/Scripts/Settings/SceneOrientationConfig.cs +++ b/Assets/Scripts/Settings/SceneOrientationConfig.cs @@ -23,7 +23,7 @@ namespace Settings return entry.requiredOrientation; } // Default to Portrait if not found - return ScreenOrientationRequirement.Portrait; + return ScreenOrientationRequirement.NotApplicable; } } } diff --git a/Assets/Scripts/Settings/ScreenOrientationRequirement.cs b/Assets/Scripts/Settings/ScreenOrientationRequirement.cs index d91adc13..60a52f01 100644 --- a/Assets/Scripts/Settings/ScreenOrientationRequirement.cs +++ b/Assets/Scripts/Settings/ScreenOrientationRequirement.cs @@ -3,7 +3,8 @@ public enum ScreenOrientationRequirement { Portrait, - Landscape + Landscape, + NotApplicable } } diff --git a/Assets/Scripts/UI/CardSystem/UIPageController.cs b/Assets/Scripts/UI/CardSystem/UIPageController.cs index 0d6c96ff..b9f4a220 100644 --- a/Assets/Scripts/UI/CardSystem/UIPageController.cs +++ b/Assets/Scripts/UI/CardSystem/UIPageController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Bootstrap; using Core; using UnityEngine; @@ -29,6 +30,15 @@ namespace AppleHills.UI.CardSystem } _instance = this; + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // Initialize any dependencies that require other services to be ready + Logging.Debug("[UIPageController] Post-boot initialization complete"); } /// diff --git a/Assets/Scripts/UI/LoadingScreenController.cs b/Assets/Scripts/UI/LoadingScreenController.cs index e902b7f7..045c9c23 100644 --- a/Assets/Scripts/UI/LoadingScreenController.cs +++ b/Assets/Scripts/UI/LoadingScreenController.cs @@ -1,5 +1,6 @@ using System.Collections; using System; +using Bootstrap; using UnityEngine; using UnityEngine.UI; using Core; @@ -48,22 +49,10 @@ namespace UI /// public bool IsActive => loadingScreenContainer != null && loadingScreenContainer.activeSelf; - public static LoadingScreenController Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("LoadingScreenController"); - _instance = go.AddComponent(); - } - } - return _instance; - } - } + /// + /// Singleton instance of the LoadingScreenController. No longer creates an instance if one doesn't exist. + /// + public static LoadingScreenController Instance => _instance; private void Awake() { @@ -77,6 +66,15 @@ namespace UI { loadingScreenContainer.SetActive(false); } + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() + { + // Initialize any dependencies that require other services to be ready + Logging.Debug("[LoadingScreenController] Post-boot initialization complete"); } /// diff --git a/Assets/Scripts/UI/PauseMenu.cs b/Assets/Scripts/UI/PauseMenu.cs index 54933f19..55f132e0 100644 --- a/Assets/Scripts/UI/PauseMenu.cs +++ b/Assets/Scripts/UI/PauseMenu.cs @@ -3,6 +3,7 @@ using Core; using UnityEngine; using UnityEngine.SceneManagement; using Input; +using Bootstrap; namespace UI { @@ -11,23 +12,10 @@ namespace UI private static PauseMenu _instance; private static bool _isQuitting; - public static PauseMenu Instance - { - get - { - if (_instance == null && Application.isPlaying && !_isQuitting) - { - _instance = FindAnyObjectByType(); - if (_instance == null) - { - var go = new GameObject("PauseMenu"); - _instance = go.AddComponent(); - // DontDestroyOnLoad(go); - } - } - return _instance; - } - } + /// + /// Singleton instance of the PauseMenu. No longer creates an instance if one doesn't exist. + /// + public static PauseMenu Instance => _instance; [Header("UI References")] [SerializeField] private GameObject pauseMenuPanel; @@ -43,18 +31,30 @@ namespace UI /// public bool IsPaused => _isPaused; - private void Start() + private void Awake() + { + _instance = this; + + // Register for post-boot initialization + BootCompletionService.RegisterInitAction(InitializePostBoot); + } + + private void InitializePostBoot() { // Subscribe to scene loaded events SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel; + // SceneManagerService subscription moved to InitializePostBoot + // Set initial state based on current scene SetPauseMenuByLevel(SceneManager.GetActiveScene().name); - #if UNITY_EDITOR +#if UNITY_EDITOR // Initialize pause menu state HidePauseMenu(false); - #endif +#endif + + Logging.Debug("[PauseMenu] Subscribed to SceneManagerService events"); } private void OnDestroy() @@ -81,9 +81,11 @@ namespace UI return; bool isMainMenu = levelName.ToLower().Contains("mainmenu"); - gameObject.SetActive(!isMainMenu); + bool isStartingLevel = levelName.ToLower().Contains("startingscene"); - if(!isMainMenu) + gameObject.SetActive(!(isMainMenu || isStartingLevel)); + + if(!isMainMenu && !isStartingLevel) HidePauseMenu(false); // Ensure menu is hidden when switching to a game level Logging.Debug($"[PauseMenu] Setting pause menu active: {!isMainMenu} for scene: {levelName}"); diff --git a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs index a7368ff4..16e9a8e6 100644 --- a/Assets/Scripts/UI/Tutorial/DivingTutorial.cs +++ b/Assets/Scripts/UI/Tutorial/DivingTutorial.cs @@ -42,22 +42,21 @@ public class DivingTutorial : MonoBehaviour, ITouchInputConsumer public void OnTap(Vector2 position) { - stateMachine.Next(true); } public void OnHoldStart(Vector2 position) { - throw new System.NotImplementedException(); + return; } public void OnHoldMove(Vector2 position) { - throw new System.NotImplementedException(); + return; } public void OnHoldEnd(Vector2 position) { - throw new System.NotImplementedException(); + return; } } diff --git a/Assets/Scripts/Utils/AppleHillsUtils.cs b/Assets/Scripts/Utils/AppleHillsUtils.cs index 62df8b17..7a90a65f 100644 --- a/Assets/Scripts/Utils/AppleHillsUtils.cs +++ b/Assets/Scripts/Utils/AppleHillsUtils.cs @@ -1,6 +1,9 @@ -using UnityEngine; +using System.Collections.Generic; +using UnityEngine; using AppleHills.Core.Settings; using Core; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.ResourceLocations; namespace Utils { @@ -68,5 +71,11 @@ namespace Utils // Apply screen normalization return frameAdjustedSpeed * screenNormalizationFactor; } + + public static bool AddressableKeyExists(object key) + { + IList locations; + return Addressables.LoadResourceLocationsAsync(key).WaitForCompletion()?.Count > 0; + } } } diff --git a/Assets/Scripts/found_instances b/Assets/Scripts/found_instances new file mode 100644 index 00000000..8dca3309 --- /dev/null +++ b/Assets/Scripts/found_instances @@ -0,0 +1,207 @@ +Targets + Occurrences of '.Instance' in Directory C:\Users\info\Desktop\repos\AppleHillsProduction\Assets\Scripts +Found usages (163 usages found) + (163 usages found) + Assets (163 usages found) + Scripts (163 usages found) + Bootstrap (3 usages found) + BootSceneController.cs (3 usages found) + 79 if (CinematicsManager.Instance != null) + 84 CinematicsManager.Instance.LoadAndPlayCinematic("IntroSequence"); + 205 SceneManagerService.Instance.CurrentGameplayScene = mainMenuSceneName; + Cinematics (16 usages found) + SkipCinematic.cs (16 usages found) + 57 if (CinematicsManager.Instance == null) return; + 63 CinematicsManager.Instance.OnCinematicStarted += HandleCinematicStarted; + 64 CinematicsManager.Instance.OnCinematicStopped += HandleCinematicStopped; + 67 if (CinematicsManager.Instance.IsCinematicPlaying) + 75 if (CinematicsManager.Instance != null) + 77 CinematicsManager.Instance.OnCinematicStarted -= HandleCinematicStarted; + 78 CinematicsManager.Instance.OnCinematicStopped -= HandleCinematicStopped; + 83 if (InputManager.Instance != null) + 85 InputManager.Instance.UnregisterOverrideConsumer(this); + 92 if (InputManager.Instance != null) + 94 InputManager.Instance.RegisterOverrideConsumer(this); + 100 if (InputManager.Instance != null) + 102 InputManager.Instance.UnregisterOverrideConsumer(this); + 109 if (_isHolding && CinematicsManager.Instance.IsCinematicPlaying) + 131 CinematicsManager.Instance.SkipCurrentCinematic(); + 141 InputManager.Instance.UnregisterOverrideConsumer(this); + Core (25 usages found) + GameManager.cs (8 usages found) + 65 SettingsProvider.Instance.gameObject.name = "Settings Provider"; + 68 DeveloperSettingsProvider.Instance.gameObject.name = "Developer Settings Provider"; + 88 PauseMenu pauseMenu = PauseMenu.Instance; + 220 var playerSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + 221 var interactionSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + 222 var minigameSettings = SettingsProvider.Instance.LoadSettingsSynchronous(); + 275 var divingDevSettings = DeveloperSettingsProvider.Instance.GetSettings(); + 318 return DeveloperSettingsProvider.Instance?.GetSettings(); + ItemManager.cs (3 usages found) + 80 SceneManagerService.Instance.SceneLoadStarted += OnSceneLoadStarted; + 87 if (SceneManagerService.Instance != null) + 88 SceneManagerService.Instance.SceneLoadStarted -= OnSceneLoadStarted; + QuickAccess.cs (7 usages found) + 47 public GameManager GameManager => GameManager.Instance; + 48 public ItemManager ItemManager => ItemManager.Instance; + 49 public SceneManagerService SceneManager => SceneManagerService.Instance; + 52 public InputManager InputManager => InputManager.Instance; + 53 public PuzzleManager PuzzleManager => PuzzleManager.Instance; + 54 public CinematicsManager CinematicsManager => CinematicsManager.Instance; + 55 public CardSystemManager CardSystemManager => CardSystemManager.Instance; + SceneManagerService.cs (1 usage found) + 110 _loadingScreen = LoadingScreenController.Instance; + SceneOrientationEnforcer.cs (3 usages found) + 106 InputManager.Instance.SetInputMode(InputMode.UI); + 159 InputManager.Instance.SetInputMode(InputMode.UI); + 189 InputManager.Instance.SetInputMode(InputMode.Game); + SettingsAccess.cs (3 usages found) + 43 return GameManager.Instance.PlayerStopDistance; + 55 return GameManager.Instance.PlayerStopDistanceDirectInteraction; + 67 return GameManager.Instance.DefaultPuzzlePromptRange; + Dialogue (26 usages found) + DialogueComponent.cs (26 usages found) + 39 if (PuzzleManager.Instance != null) + 40 PuzzleManager.Instance.OnStepCompleted += OnAnyPuzzleStepCompleted; + 42 if (ItemManager.Instance != null) + 44 ItemManager.Instance.OnItemPickedUp += OnAnyItemPickedUp; + 45 ItemManager.Instance.OnCorrectItemSlotted += OnAnyItemSlotted; + 46 ItemManager.Instance.OnIncorrectItemSlotted += OnAnyIncorrectItemSlotted; + 47 ItemManager.Instance.OnForbiddenItemSlotted += OnAnyForbiddenItemSlotted; + 48 ItemManager.Instance.OnItemSlotCleared += OnAnyItemSlotCleared; + 49 ItemManager.Instance.OnItemsCombined += OnAnyItemsCombined; + 170 if (PuzzleManager.Instance != null) + 171 PuzzleManager.Instance.OnStepCompleted -= OnAnyPuzzleStepCompleted; + 173 if (ItemManager.Instance != null) + 175 ItemManager.Instance.OnItemPickedUp -= OnAnyItemPickedUp; + 176 ItemManager.Instance.OnCorrectItemSlotted -= OnAnyItemSlotted; + 177 ItemManager.Instance.OnIncorrectItemSlotted -= OnAnyIncorrectItemSlotted; + 178 ItemManager.Instance.OnForbiddenItemSlotted -= OnAnyForbiddenItemSlotted; + 179 ItemManager.Instance.OnItemSlotCleared -= OnAnyItemSlotCleared; + 180 ItemManager.Instance.OnItemsCombined -= OnAnyItemsCombined; + 600 return PuzzleManager.Instance != null && + 601 PuzzleManager.Instance.IsPuzzleStepCompleted(stepID); + 606 if (ItemManager.Instance == null) return false; + 609 foreach (var pickup in ItemManager.Instance.Pickups) + 622 if (ItemManager.Instance == null) return false; + 625 foreach (var slot in ItemManager.Instance.ItemSlots) + 638 if (ItemManager.Instance == null) return false; + 641 return ItemManager.Instance.WasItemCreatedThroughCombination(resultItemId); + Input (4 usages found) + InputManager.cs (3 usages found) + 96 SceneManagerService.Instance.SceneLoadCompleted += SwitchInputOnSceneLoaded; + 103 if (SceneManagerService.Instance != null) + 104 SceneManagerService.Instance.SceneLoadCompleted -= SwitchInputOnSceneLoaded; + PlayerTouchController.cs (1 usage found) + 77 InputManager.Instance?.SetDefaultConsumer(this); + Interactions (6 usages found) + Interactable.cs (2 usages found) + 213 ? GameManager.Instance.PlayerStopDistance + 214 : GameManager.Instance.PlayerStopDistanceDirectInteraction; + ItemSlot.cs (2 usages found) + 222 ItemManager.Instance?.RegisterItemSlot(this); + 227 ItemManager.Instance?.UnregisterItemSlot(this); + Pickup.cs (2 usages found) + 49 ItemManager.Instance?.RegisterPickup(this); + 64 ItemManager.Instance?.UnregisterPickup(this); + LevelS (3 usages found) + LevelSwitch.cs (3 usages found) + 114 InputManager.Instance.SetInputMode(InputMode.UI); + 120 await SceneManagerService.Instance.SwitchSceneAsync(switchData.targetLevelSceneName, progress); + 126 InputManager.Instance.SetInputMode(InputMode.GameAndUI); + Minigames (43 usages found) + DivingForPictures (43 usages found) + Bubbles (2 usages found) + BubbleSpawner.cs (2 usages found) + 70 DivingGameManager.Instance.RegisterPausableComponent(this); + 78 DivingGameManager.Instance.UnregisterPausableComponent(this); + Obstacles (5 usages found) + ObstacleSpawner.cs (5 usages found) + 88 DivingGameManager.Instance.OnGameInitialized += Initialize; + 91 DivingGameManager.Instance.RegisterPausableComponent(this); + 94 if (DivingGameManager.Instance.GetType().GetField("_isGameInitialized", + 96 System.Reflection.BindingFlags.Instance)?.GetValue(DivingGameManager.Instance) is bool isInitialized && isInitialized) + 104 DivingGameManager.Instance.UnregisterPausableComponent(this); + PictureCamera (2 usages found) + Viewfinder.cs (2 usages found) + 89 InputManager.Instance.RegisterOverrideConsumer(this); + 94 InputManager.Instance.UnregisterOverrideConsumer(this); + Player (6 usages found) + PlayerCollisionBehavior.cs (1 usage found) + 262 .GetField("_targetFingerX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + PlayerController.cs (5 usages found) + 83 DivingGameManager.Instance.OnGameInitialized += Initialize; + 86 if (DivingGameManager.Instance.GetType().GetField("_isGameInitialized", + 88 System.Reflection.BindingFlags.Instance)?.GetValue(DivingGameManager.Instance) is bool isInitialized && isInitialized) + 102 InputManager.Instance?.SetDefaultConsumer(this); + 110 DivingGameManager.Instance.OnGameInitialized -= Initialize; + Tiles (5 usages found) + TrenchTileSpawner.cs (5 usages found) + 171 DivingGameManager.Instance.OnGameInitialized += Initialize; + 174 DivingGameManager.Instance.RegisterPausableComponent(this); + 177 if (DivingGameManager.Instance.GetType().GetField("_isGameInitialized", + 179 System.Reflection.BindingFlags.Instance)?.GetValue(DivingGameManager.Instance) is bool isInitialized && isInitialized) + 187 DivingGameManager.Instance.UnregisterPausableComponent(this); + Utilities (2 usages found) + BottlePauser.cs (1 usage found) + 14 DivingGameManager.Instance.RegisterPausableComponent(this); + RockPauser.cs (1 usage found) + 15 DivingGameManager.Instance.RegisterPausableComponent(this); + DivingGameManager.cs (16 usages found) + 142 PauseMenu pauseMenu = PauseMenu.Instance; + 156 if (GameManager.Instance != null) + 158 GameManager.Instance.RegisterPausableComponent(this); + 162 if (SceneOrientationEnforcer.Instance != null) + 164 SceneOrientationEnforcer.Instance.OnOrientationCorrect += InitializeGame; + 165 SceneOrientationEnforcer.Instance.OnOrientationIncorrect += Pause; + 169 if (SceneOrientationEnforcer.Instance.IsOrientationCorrect()) + 186 viewfinderManager = CameraViewfinderManager.Instance; + 212 if (SceneOrientationEnforcer.Instance != null) + 214 SceneOrientationEnforcer.Instance.OnOrientationCorrect -= InitializeGame; + 215 SceneOrientationEnforcer.Instance.OnOrientationIncorrect -= Pause; + 219 PauseMenu pauseMenu = PauseMenu.Instance; + 227 if (GameManager.Instance != null) + 229 GameManager.Instance.UnregisterPausableComponent(this); + 801 InputManager.Instance.SetInputMode(InputMode.GameAndUI); + 822 InputManager.Instance.SetInputMode(InputMode.GameAndUI); + DivingScoreUI.cs (5 usages found) + 15 DivingGameManager.Instance.OnScoreChanged += UpdateScoreDisplay; + 16 DivingGameManager.Instance.OnPictureTaken += ShowScorePopup; + 19 UpdateScoreDisplay(DivingGameManager.Instance.PlayerScore); + 32 DivingGameManager.Instance.OnScoreChanged -= UpdateScoreDisplay; + 33 DivingGameManager.Instance.OnPictureTaken -= ShowScorePopup; + PuzzleS (7 usages found) + ObjectiveStepBehaviour.cs (3 usages found) + 76 PuzzleManager.Instance?.RegisterStepBehaviour(this); + 86 PuzzleManager.Instance?.UnregisterStepBehaviour(this); + 304 PuzzleManager.Instance?.MarkPuzzleStepCompleted(stepData); + PuzzleManager.cs (4 usages found) + 102 SceneManagerService.Instance.SceneLoadCompleted += OnSceneLoadCompleted; + 111 if (SceneManagerService.Instance != null) + 113 SceneManagerService.Instance.SceneLoadCompleted -= OnSceneLoadCompleted; + 149 string currentScene = sceneName ?? SceneManagerService.Instance.CurrentGameplayScene; + StateMachines (2 usages found) + Quarry (2 usages found) + AnneLise (2 usages found) + TakePhotoState.cs (2 usages found) + 39 InputManager.Instance.SetInputMode(InputMode.InputDisabled); + 47 InputManager.Instance.SetInputMode(InputMode.Game); + UI (21 usages found) + Tutorial (2 usages found) + DivingTutorial.cs (2 usages found) + 30 InputManager.Instance.RegisterOverrideConsumer(this); + 39 InputManager.Instance.UnregisterOverrideConsumer(this); + MainMenu.cs (1 usage found) + 12 await SceneManagerService.Instance.SwitchSceneAsync("AppleHillsOverworld", progress); + PauseMenu.cs (11 usages found) + 71 SceneManagerService.Instance.SceneLoadCompleted += SetPauseMenuByLevel; + 78 if (SceneManagerService.Instance != null) + 80 SceneManagerService.Instance.SceneLoadCompleted -= SetPauseMenuByLevel; + 122 InputManager.Instance.SetInputMode(InputMode.UI); + 141 InputManager.Instance.SetInputMode(InputMode.GameAndUI); + 162 await SceneManagerService.Instance.SwitchSceneAsync("MainMenu", progress); + 180 await SceneManagerService.Instance.ReloadCurrentScene(progress); + 198 await SceneManagerService.Instance.SwitchSceneAsync("MainMenu", progress); + 201 await SceneManagerService.Instance.SwitchSceneAsync("AppleHillsOverworld", progress); + 204 await SceneManagerService.Instance.SwitchSceneAsync("Quarry", progress); + 207 await SceneManagerService.Instance.SwitchSceneAsync("DivingForPictures", progress); diff --git a/Assets/Scripts/found_instances.meta b/Assets/Scripts/found_instances.meta new file mode 100644 index 00000000..3fcffc60 --- /dev/null +++ b/Assets/Scripts/found_instances.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: faea13914b0c70740accf1eeb03f11aa +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index cf1d829f..9ac34527 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -5,6 +5,9 @@ EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: + - enabled: 1 + path: Assets/Scenes/StartingScene.unity + guid: cf01e2d0135b06c4486d00ef393d0274 - enabled: 1 path: Assets/Scenes/MainMenu.unity guid: b93f2f3b39a62684c8474ba79c8f698d diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 5f084eba..1e425d08 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -329,42 +329,6 @@ PlayerSettings: m_Height: 36 m_Kind: 0 m_SubKind: - - m_Textures: - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - m_Width: 432 - m_Height: 432 - m_Kind: 2 - m_SubKind: - - m_Textures: - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - m_Width: 324 - m_Height: 324 - m_Kind: 2 - m_SubKind: - - m_Textures: - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - - {fileID: 2800000, guid: 00354ded9d8f8d643acc14837a229544, type: 3} - m_Width: 216 - m_Height: 216 - m_Kind: 2 - m_SubKind: - - m_Textures: [] - m_Width: 162 - m_Height: 162 - m_Kind: 2 - m_SubKind: - - m_Textures: [] - m_Width: 108 - m_Height: 108 - m_Kind: 2 - m_SubKind: - - m_Textures: [] - m_Width: 81 - m_Height: 81 - m_Kind: 2 - m_SubKind: - m_Textures: [] m_Width: 192 m_Height: 192 @@ -395,6 +359,42 @@ PlayerSettings: m_Height: 36 m_Kind: 1 m_SubKind: + - m_Textures: + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + - {fileID: 2800000, guid: 6767e1e5c0a16f14e926a72a81bf95cb, type: 3} + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: - m_BuildTarget: iPhone m_Icons: - m_Textures: []