From 082ce98f7965e747f4c1f110817b18367224964c Mon Sep 17 00:00:00 2001 From: tschesky Date: Wed, 10 Dec 2025 11:14:10 +0000 Subject: [PATCH] Trash maze MVP (#79) Co-authored-by: Michal Pikulski Co-authored-by: Michal Pikulski Reviewed-on: https://homelab.tailf7f81b.ts.net/tschesky/AppleHillsProduction/pulls/79 --- Assets/Art/Materials/TrashMaze.meta | 8 + .../Materials/TrashMaze/MazeBackground.mat | 59 + .../TrashMaze/MazeBackground.mat.meta | 8 + .../MazeObjectProgressiveTemplate.mat | 59 + .../MazeObjectProgressiveTemplate.mat.meta | 8 + .../TrashMaze/MazeObjectTemplate.mat | 56 + .../TrashMaze/MazeObjectTemplate.mat.meta | 8 + Assets/External/Placeholders/dark_maze.png | Bin 0 -> 17356 bytes .../External/Placeholders/dark_maze.png.meta | 195 +++ Assets/External/Placeholders/item_normal.png | Bin 0 -> 8510 bytes .../Placeholders/item_normal.png.meta | 195 +++ Assets/External/Placeholders/item_outline.png | Bin 0 -> 2590 bytes .../Placeholders/item_outline.png.meta | 195 +++ Assets/External/Placeholders/light_maze.png | Bin 0 -> 17355 bytes .../External/Placeholders/light_maze.png.meta | 195 +++ .../Prefabs/Levels/Dump/MazeBackground.prefab | 93 ++ .../Levels/Dump/MazeBackground.prefab.meta | 7 + Assets/Prefabs/Levels/Dump/Pulver.prefab | 222 ++++ Assets/Prefabs/Levels/Dump/Pulver.prefab.meta | 7 + .../Levels/Dump/RevealableObject.prefab | 157 +++ .../Levels/Dump/RevealableObject.prefab.meta | 7 + .../Scenes/MiniGames/StatueDecoration.unity | 1119 +---------------- .../MichalTesting_TrashMaze.unity | 1112 ++++++++++++++++ .../MichalTesting_TrashMaze.unity.meta | 7 + .../CardSystem/UI/Pages/AlbumViewPage.cs | 1 - Assets/Scripts/Core/GameManager.cs | 2 +- .../Core/Settings/PlayerFollowerSettings.cs | 85 +- .../Core/Settings/SettingsInterfaces.cs | 28 +- .../Input/BasePlayerMovementController.cs | 330 +++++ .../BasePlayerMovementController.cs.meta | 3 + Assets/Scripts/Input/PlayerTouchController.cs | 376 +----- Assets/Scripts/Interactions/ItemSlot.cs | 5 +- Assets/Scripts/Minigames/TrashMaze.meta | 9 + Assets/Scripts/Minigames/TrashMaze/Core.meta | 9 + .../TrashMaze/Core/PulverController.cs | 91 ++ .../TrashMaze/Core/PulverController.cs.meta | 12 + .../TrashMaze/Core/TrashMazeController.cs | 168 +++ .../Core/TrashMazeController.cs.meta | 12 + .../Scripts/Minigames/TrashMaze/Objects.meta | 9 + .../Minigames/TrashMaze/Objects/Editor.meta | 3 + .../Objects/Editor/RevealableObjectEditor.cs | 99 ++ .../Editor/RevealableObjectEditor.cs.meta | 3 + .../TrashMaze/Objects/RevealableObject.cs | 559 ++++++++ .../Objects/RevealableObject.cs.meta | 12 + Assets/Scripts/Movement/FollowerController.cs | 7 +- Assets/Scripts/Utils/Measurements.cs | 148 +++ Assets/Scripts/Utils/Measurements.cs.meta | 18 + Assets/Settings/PlayerFollowerSettings.asset | 34 +- Assets/Shaders.meta | 8 + Assets/Shaders/TrashMaze.meta | 9 + .../TrashMaze/BackgroundVisibility.shader | 82 ++ .../BackgroundVisibility.shader.meta | 10 + .../Shaders/TrashMaze/ObjectVisibility.shader | 91 ++ .../TrashMaze/ObjectVisibility.shader.meta | 10 + .../ObjectVisibilityProgressive.shader | 106 ++ .../ObjectVisibilityProgressive.shader.meta | 3 + Assets/Shaders/TrashMaze/RevealStamp.shader | 64 + .../Shaders/TrashMaze/RevealStamp.shader.meta | 3 + 58 files changed, 4624 insertions(+), 1502 deletions(-) create mode 100644 Assets/Art/Materials/TrashMaze.meta create mode 100644 Assets/Art/Materials/TrashMaze/MazeBackground.mat create mode 100644 Assets/Art/Materials/TrashMaze/MazeBackground.mat.meta create mode 100644 Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat create mode 100644 Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat.meta create mode 100644 Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat create mode 100644 Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat.meta create mode 100644 Assets/External/Placeholders/dark_maze.png create mode 100644 Assets/External/Placeholders/dark_maze.png.meta create mode 100644 Assets/External/Placeholders/item_normal.png create mode 100644 Assets/External/Placeholders/item_normal.png.meta create mode 100644 Assets/External/Placeholders/item_outline.png create mode 100644 Assets/External/Placeholders/item_outline.png.meta create mode 100644 Assets/External/Placeholders/light_maze.png create mode 100644 Assets/External/Placeholders/light_maze.png.meta create mode 100644 Assets/Prefabs/Levels/Dump/MazeBackground.prefab create mode 100644 Assets/Prefabs/Levels/Dump/MazeBackground.prefab.meta create mode 100644 Assets/Prefabs/Levels/Dump/Pulver.prefab create mode 100644 Assets/Prefabs/Levels/Dump/Pulver.prefab.meta create mode 100644 Assets/Prefabs/Levels/Dump/RevealableObject.prefab create mode 100644 Assets/Prefabs/Levels/Dump/RevealableObject.prefab.meta create mode 100644 Assets/Scenes/TestingStuff/MichalTesting_TrashMaze.unity create mode 100644 Assets/Scenes/TestingStuff/MichalTesting_TrashMaze.unity.meta create mode 100644 Assets/Scripts/Input/BasePlayerMovementController.cs create mode 100644 Assets/Scripts/Input/BasePlayerMovementController.cs.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Core.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs create mode 100644 Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs create mode 100644 Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects/Editor.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs.meta create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs create mode 100644 Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs.meta create mode 100644 Assets/Scripts/Utils/Measurements.cs create mode 100644 Assets/Scripts/Utils/Measurements.cs.meta create mode 100644 Assets/Shaders.meta create mode 100644 Assets/Shaders/TrashMaze.meta create mode 100644 Assets/Shaders/TrashMaze/BackgroundVisibility.shader create mode 100644 Assets/Shaders/TrashMaze/BackgroundVisibility.shader.meta create mode 100644 Assets/Shaders/TrashMaze/ObjectVisibility.shader create mode 100644 Assets/Shaders/TrashMaze/ObjectVisibility.shader.meta create mode 100644 Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader create mode 100644 Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader.meta create mode 100644 Assets/Shaders/TrashMaze/RevealStamp.shader create mode 100644 Assets/Shaders/TrashMaze/RevealStamp.shader.meta diff --git a/Assets/Art/Materials/TrashMaze.meta b/Assets/Art/Materials/TrashMaze.meta new file mode 100644 index 00000000..6e46148c --- /dev/null +++ b/Assets/Art/Materials/TrashMaze.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 59f400cf78b0d3949a1280b33dd96e3c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Art/Materials/TrashMaze/MazeBackground.mat b/Assets/Art/Materials/TrashMaze/MazeBackground.mat new file mode 100644 index 00000000..b7941929 --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeBackground.mat @@ -0,0 +1,59 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: MazeBackground + m_Shader: {fileID: 4800000, guid: b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _AlphaTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _LitTex: + m_Texture: {fileID: 2800000, guid: 2355d01d9d23cfe49a3e05ea9b4958e6, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MaskTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _NormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _UnlitTex: + m_Texture: {fileID: 2800000, guid: 979d8dc9d4d55f043b7426e437365c30, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _EnableExternalAlpha: 0 + - _TransitionSoftness: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _RendererColor: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/Art/Materials/TrashMaze/MazeBackground.mat.meta b/Assets/Art/Materials/TrashMaze/MazeBackground.mat.meta new file mode 100644 index 00000000..1ba7fcbd --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeBackground.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 943d8f5cb68e5d64ea448054ba6c28f0 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat b/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat new file mode 100644 index 00000000..0aea4403 --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat @@ -0,0 +1,59 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: MazeObjectProgressiveTemplate + m_Shader: {fileID: 4800000, guid: 732fa975ac924d89bb0078279d2cdb0b, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _AlphaTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MaskTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _NormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OutlineTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _RevealMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _EnableExternalAlpha: 0 + - _IsInVision: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _RendererColor: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat.meta b/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat.meta new file mode 100644 index 00000000..3502b15d --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeObjectProgressiveTemplate.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6e053220514a0c64883d9484863533fe +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat b/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat new file mode 100644 index 00000000..f79130fb --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat @@ -0,0 +1,56 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: MazeObjectTemplate + m_Shader: {fileID: 4800000, guid: c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4, type: 3} + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_LockedProperties: + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _AlphaTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MaskTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _NormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OutlineTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _EnableExternalAlpha: 0 + - _IsInVision: 0 + - _IsRevealed: 0 + - _ZWrite: 0 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _RendererColor: {r: 1, g: 1, b: 1, a: 1} + m_BuildTextureStacks: [] + m_AllowLocking: 1 diff --git a/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat.meta b/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat.meta new file mode 100644 index 00000000..b893d445 --- /dev/null +++ b/Assets/Art/Materials/TrashMaze/MazeObjectTemplate.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 07b06abfc5d910b4a8c525979cd29f29 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/Placeholders/dark_maze.png b/Assets/External/Placeholders/dark_maze.png new file mode 100644 index 0000000000000000000000000000000000000000..c360f279d65473a85f9517bef812fa08b611342c GIT binary patch literal 17356 zcmeI4`%6=C6u^&_xvi@W-%WE{6`GZ+EKDVbl9CWaWTor9Kx;}hPn0In)!Ogw;qJrsao92=HcVln}o4(VuGqoQe9jv+fkw<8@O)L39#qI?kLZ%J8K zy>X^no>(u4KR-|zR91Z~wn5V?s#{dCX~bDADfjeIw3)5ej?^b@mgFRtq-gON=zY$4 zZmw$o8B**m5Ab)qdD-0Ya&c7F`m@`!AIo+_xWGbGUStb zhH@uMQ$ANlc8$I;ZH)@6pzI~>@)|$yp@~BSHPyTOX1LwFQ6)Bctd%g)uNC!O;Vt^` zg|qG54Mug3$B`0CyieEW?uUn0M$-Oq38llu=ek56<~-VFe$?xDJD2imJWV~-l?5FP znPw~!Ovz2q6c>lqNy4T-Zj?IgCl~ziY)UgEwsu}Glq4w2q+^r!=Wq9}t$k6Y@wXLR zA3Uj-HB4%h-1=Uy<9VG&>rZNvZt8(LVRu1!pDoK6tj+PebLUgma6nGecxu_l^1y}V zrW(!cai254sP}4Z-h%q3X0t;-0%@18zVowM#TB7(-|lwY8@;=4)UoPq!W?DtZh-J$@^#u%y)2JC>B6Ygu`VAep?GE(Q(;bIPfcNBH!yVe&YkJI7@!U5 zTbT4TS-y_Rk||VbP0~R(9{?GsVxklF^bS0Wb_JQZHNu)JoNmA7E?qTiOaEX~wiA0D zfH?|c1AtA=1(0>}u~0UiKPaO3D42(TAOw&D`alR^Uhp0u2jsvK!1aRH068EBLIC?H z-UH--90&nyn|TkA19Bh)ux;i&Kn}=(C4g-+uK{vE4urs;Zkw0x%i{*T5uN=b0V%o( z>@8`25DbUO5B5U|`RMD;$B1cs)GGPddWw&Qf&3&HFTnCZh{%AX1$uxC&;vpMJwOJW z23QmSMlM*s9<}}63xFLP05U)Zq#U6K$N)Vc1keLyz-fRr5p#hP0oTTVqb6D|Oyzd# zQp30JcO*%0UW+qpHh*z$j|@l|M-PwzdO!%E2grca0Ba)V0w)5ljaU=0CSpy*y-tAp zz1TEB50C+=hX1z*#|^6o*>^se$a?{}+K*c~0sc0Fi={;=X literal 0 HcmV?d00001 diff --git a/Assets/External/Placeholders/dark_maze.png.meta b/Assets/External/Placeholders/dark_maze.png.meta new file mode 100644 index 00000000..bebc78a3 --- /dev/null +++ b/Assets/External/Placeholders/dark_maze.png.meta @@ -0,0 +1,195 @@ +fileFormatVersion: 2 +guid: 979d8dc9d4d55f043b7426e437365c30 +TextureImporter: + internalIDToNameTable: + - first: + 213: 5438675420682048811 + second: dark_maze_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: dark_maze_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 2048 + height: 2048 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: b25d662da7e0a7b40800000000000000 + internalID: 5438675420682048811 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + dark_maze_0: 5438675420682048811 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/Placeholders/item_normal.png b/Assets/External/Placeholders/item_normal.png new file mode 100644 index 0000000000000000000000000000000000000000..0f67a8138f31754f16dc910782405e941efaa9d6 GIT binary patch literal 8510 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 literal 0 HcmV?d00001 diff --git a/Assets/External/Placeholders/item_normal.png.meta b/Assets/External/Placeholders/item_normal.png.meta new file mode 100644 index 00000000..c11ab895 --- /dev/null +++ b/Assets/External/Placeholders/item_normal.png.meta @@ -0,0 +1,195 @@ +fileFormatVersion: 2 +guid: 22bb2c64be7aa8e438277c533ecb8bee +TextureImporter: + internalIDToNameTable: + - first: + 213: -9213196934004512817 + second: item_normal_0 + externalObjects: {} + serializedVersion: 13 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 1 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 2 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 4 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: iOS + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WebGL + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: WindowsStoreApps + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: + - serializedVersion: 2 + name: item_normal_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 385 + height: 257 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: fc761feb336242080800000000000000 + internalID: -9213196934004512817 + vertices: [] + indices: + edges: [] + weights: [] + outline: [] + customData: + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spriteCustomMetadata: + entries: [] + nameFileIdTable: + item_normal_0: -9213196934004512817 + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/External/Placeholders/item_outline.png b/Assets/External/Placeholders/item_outline.png new file mode 100644 index 0000000000000000000000000000000000000000..addcae71ca1e246d6fd14f893ba8ae3cbbaafa3d GIT binary patch literal 2590 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.None); - foreach (var slot in allSlots) { if (slot.TargetCardDefinition != null && diff --git a/Assets/Scripts/Core/GameManager.cs b/Assets/Scripts/Core/GameManager.cs index 545e3370..fc552a65 100644 --- a/Assets/Scripts/Core/GameManager.cs +++ b/Assets/Scripts/Core/GameManager.cs @@ -182,7 +182,7 @@ namespace Core // Register settings with service locator if (playerSettings != null) { - ServiceLocator.Register(playerSettings); + ServiceLocator.Register(playerSettings); Logging.Debug("PlayerFollowerSettings registered successfully"); } else diff --git a/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs index 2d25d74d..60e8f8ec 100644 --- a/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs +++ b/Assets/Scripts/Core/Settings/PlayerFollowerSettings.cs @@ -3,37 +3,79 @@ namespace AppleHills.Core.Settings { /// - /// Settings related to player and follower behavior + /// Settings related to player and follower behavior. + /// Implements IPlayerMovementConfigs to provide separate configurations for different movement contexts. /// [CreateAssetMenu(fileName = "PlayerFollowerSettings", menuName = "AppleHills/Settings/Player & Follower", order = 1)] - public class PlayerFollowerSettings : BaseSettings, IPlayerFollowerSettings + public class PlayerFollowerSettings : BaseSettings, IPlayerMovementConfigs + { + [Header("Default Player Movement (Overworld)")] + [SerializeField] private PlayerMovementSettingsData defaultPlayerMovement = new PlayerMovementSettingsData(); + + [Header("Trash Maze - Pulver Movement")] + [SerializeField] private PlayerMovementSettingsData trashMazeMovement = new PlayerMovementSettingsData(); + + [Header("Follower Settings")] + [SerializeField] private FollowerSettingsData followerMovement = new FollowerSettingsData(); + + // IPlayerMovementConfigs implementation + public IPlayerMovementSettings DefaultPlayerMovement => defaultPlayerMovement; + public IPlayerMovementSettings TrashMazeMovement => trashMazeMovement; + public IFollowerSettings FollowerMovement => followerMovement; + + public override void OnValidate() + { + base.OnValidate(); + defaultPlayerMovement?.Validate(); + trashMazeMovement?.Validate(); + followerMovement?.Validate(); + } + } + + /// + /// Serializable data for player movement settings + /// + [System.Serializable] + public class PlayerMovementSettingsData : IPlayerMovementSettings { - [Header("Player Settings")] [SerializeField] private float moveSpeed = 5f; - [SerializeField] private float moveAcceleration = 10000f; + [SerializeField] private float maxAcceleration = 10000f; [SerializeField] private float stopDistance = 0.1f; [SerializeField] private bool useRigidbody = true; [SerializeField] private HoldMovementMode defaultHoldMovementMode = HoldMovementMode.Pathfinding; - [Header("Follower Settings")] + public float MoveSpeed => moveSpeed; + public float MaxAcceleration => maxAcceleration; + public float StopDistance => stopDistance; + public bool UseRigidbody => useRigidbody; + public HoldMovementMode DefaultHoldMovementMode => defaultHoldMovementMode; + + public void Validate() + { + moveSpeed = Mathf.Max(0.1f, moveSpeed); + maxAcceleration = Mathf.Max(0.1f, maxAcceleration); + stopDistance = Mathf.Max(0.01f, stopDistance); + } + } + + /// + /// Serializable data for follower settings + /// + [System.Serializable] + public class FollowerSettingsData : IFollowerSettings + { [SerializeField] private float followDistance = 1.5f; [SerializeField] private float manualMoveSmooth = 8f; [SerializeField] private float thresholdFar = 2.5f; [SerializeField] private float thresholdNear = 0.5f; [SerializeField] private float stopThreshold = 0.1f; - - [Header("Backend Settings")] - [Tooltip("Technical parameters, not for design tuning")] [SerializeField] private float followUpdateInterval = 0.1f; [SerializeField] private float followerSpeedMultiplier = 1.2f; [SerializeField] private float heldIconDisplayHeight = 2.0f; + + [Header("Trash Maze Vision")] + [SerializeField] private float trashMazeVisionRadius = 8f; - // IPlayerFollowerSettings implementation - public float MoveSpeed => moveSpeed; - public float MaxAcceleration => moveAcceleration; - public float StopDistance => stopDistance; - public bool UseRigidbody => useRigidbody; - public HoldMovementMode DefaultHoldMovementMode => defaultHoldMovementMode; public float FollowDistance => followDistance; public float ManualMoveSmooth => manualMoveSmooth; public float ThresholdFar => thresholdFar; @@ -42,14 +84,19 @@ namespace AppleHills.Core.Settings public float FollowUpdateInterval => followUpdateInterval; public float FollowerSpeedMultiplier => followerSpeedMultiplier; public float HeldIconDisplayHeight => heldIconDisplayHeight; - - public override void OnValidate() + public float TrashMazeVisionRadius => trashMazeVisionRadius; + + public void Validate() { - base.OnValidate(); - // Validate values - moveSpeed = Mathf.Max(0.1f, moveSpeed); followDistance = Mathf.Max(0.1f, followDistance); + manualMoveSmooth = Mathf.Max(0.1f, manualMoveSmooth); + thresholdFar = Mathf.Max(0.1f, thresholdFar); + thresholdNear = Mathf.Max(0.01f, thresholdNear); + stopThreshold = Mathf.Max(0.01f, stopThreshold); + followUpdateInterval = Mathf.Max(0.01f, followUpdateInterval); followerSpeedMultiplier = Mathf.Max(0.1f, followerSpeedMultiplier); + heldIconDisplayHeight = Mathf.Max(0f, heldIconDisplayHeight); + trashMazeVisionRadius = Mathf.Max(1f, trashMazeVisionRadius); } } } diff --git a/Assets/Scripts/Core/Settings/SettingsInterfaces.cs b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs index c5c369cc..ffb4ec92 100644 --- a/Assets/Scripts/Core/Settings/SettingsInterfaces.cs +++ b/Assets/Scripts/Core/Settings/SettingsInterfaces.cs @@ -14,18 +14,33 @@ namespace AppleHills.Core.Settings } /// - /// Interface for player and follower settings + /// Interface for player movement settings (used by all player controllers) /// - public interface IPlayerFollowerSettings + public interface IPlayerMovementSettings { - // Player settings float MoveSpeed { get; } - float MaxAcceleration { get; } // Added new property for player acceleration + float MaxAcceleration { get; } float StopDistance { get; } bool UseRigidbody { get; } HoldMovementMode DefaultHoldMovementMode { get; } - - // Follower settings + } + + /// + /// Container interface that holds multiple player movement configurations. + /// Allows different controllers to use the same base settings interface with different values. + /// + public interface IPlayerMovementConfigs + { + IPlayerMovementSettings DefaultPlayerMovement { get; } + IPlayerMovementSettings TrashMazeMovement { get; } + IFollowerSettings FollowerMovement { get; } + } + + /// + /// Interface for follower-specific settings (completely separate from player movement) + /// + public interface IFollowerSettings + { float FollowDistance { get; } float ManualMoveSmooth { get; } float ThresholdFar { get; } @@ -34,6 +49,7 @@ namespace AppleHills.Core.Settings float FollowUpdateInterval { get; } float FollowerSpeedMultiplier { get; } float HeldIconDisplayHeight { get; } + float TrashMazeVisionRadius { get; } } /// diff --git a/Assets/Scripts/Input/BasePlayerMovementController.cs b/Assets/Scripts/Input/BasePlayerMovementController.cs new file mode 100644 index 00000000..d4c61760 --- /dev/null +++ b/Assets/Scripts/Input/BasePlayerMovementController.cs @@ -0,0 +1,330 @@ +using UnityEngine; +using Pathfinding; +using AppleHills.Core.Settings; +using Core; +using Core.Lifecycle; + +namespace Input +{ + /// + /// Base class for player movement controllers. + /// Handles tap-to-move and hold-to-move input with pathfinding or direct movement. + /// Derived classes can override to add specialized behavior (e.g., shader updates). + /// + public abstract class BasePlayerMovementController : ManagedBehaviour, ITouchInputConsumer + { + [Header("Movement")] + [SerializeField] protected float moveSpeed = 5f; + + [Header("Collision Simulation")] + [SerializeField] protected LayerMask obstacleMask; + [SerializeField] protected float colliderRadius = 0.5f; + + // Movement state + protected Vector3 _targetPosition; + protected Vector3 _directMoveVelocity; + protected bool _isHolding; + protected Vector2 _lastHoldPosition; + protected Coroutine _pathfindingDragCoroutine; + protected float _pathfindingDragUpdateInterval = 0.1f; + + // Settings reference (populated by derived classes in LoadSettings) + protected IPlayerMovementSettings _movementSettings; + protected IPlayerMovementSettings Settings => _movementSettings; + + // Abstract method for derived classes to load their specific settings + protected abstract void LoadSettings(); + + // Movement tracking + protected bool _isMoving; + public bool IsMoving => _isMoving; + public bool IsHolding => _isHolding; + public event System.Action OnMovementStarted; + public event System.Action OnMovementStopped; + + // Components + protected AIPath _aiPath; + protected Animator _animator; + protected Transform _artTransform; + protected SpriteRenderer _spriteRenderer; + + // Animation tracking + protected Vector3 _lastDirectMoveDir = Vector3.right; + public Vector3 LastDirectMoveDir => _lastDirectMoveDir; + protected float _lastDirX; + protected float _lastDirY = -1f; + + protected LogVerbosity _logVerbosity = LogVerbosity.Warning; + + internal override void OnManagedAwake() + { + base.OnManagedAwake(); + LoadSettings(); // Let derived class load appropriate settings + InitializeComponents(); + } + + internal override void OnManagedStart() + { + base.OnManagedStart(); + + // Register with InputManager + if (InputManager.Instance != null) + { + InputManager.Instance.SetDefaultConsumer(this); + Logging.Debug($"[{GetType().Name}] Registered as default input consumer"); + } + + _logVerbosity = DeveloperSettingsProvider.Instance.GetSettings().inputLogVerbosity; + } + + protected virtual void InitializeComponents() + { + _aiPath = GetComponent(); + _artTransform = transform.Find("CharacterArt"); + if (_artTransform != null) + _animator = _artTransform.GetComponent(); + else + _animator = GetComponentInChildren(); + + if (_artTransform != null) + _spriteRenderer = _artTransform.GetComponent(); + if (_spriteRenderer == null) + _spriteRenderer = GetComponentInChildren(); + } + + protected virtual void Update() + { + UpdateMovementState(); + UpdateAnimation(); + } + + #region ITouchInputConsumer Implementation + + public virtual void OnTap(Vector2 worldPosition) + { + Logging.Debug($"[{GetType().Name}] OnTap at {worldPosition}"); + if (_aiPath != null) + { + _aiPath.enabled = true; + _aiPath.canMove = true; + _aiPath.isStopped = false; + SetTargetPosition(worldPosition); + _directMoveVelocity = Vector3.zero; + _isHolding = false; + } + } + + public virtual void OnHoldStart(Vector2 worldPosition) + { + Logging.Debug($"[{GetType().Name}] OnHoldStart at {worldPosition}"); + _lastHoldPosition = worldPosition; + _isHolding = true; + + if (Settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding && _aiPath != null) + { + _aiPath.enabled = true; + if (_pathfindingDragCoroutine != null) StopCoroutine(_pathfindingDragCoroutine); + _pathfindingDragCoroutine = StartCoroutine(PathfindingDragUpdateCoroutine()); + } + else // Direct movement + { + if (_aiPath != null) _aiPath.enabled = false; + _directMoveVelocity = Vector3.zero; + } + } + + public virtual void OnHoldUpdate(Vector2 worldPosition) + { + if (!_isHolding) return; + _lastHoldPosition = worldPosition; + + if (Settings.DefaultHoldMovementMode == HoldMovementMode.Direct) + { + if (_aiPath != null && _aiPath.enabled) _aiPath.enabled = false; + MoveDirectlyTo(worldPosition); + } + } + + public virtual void OnHoldMove(Vector2 worldPosition) + { + // Alias for OnHoldUpdate for interface compatibility + OnHoldUpdate(worldPosition); + } + + public virtual void OnHoldEnd(Vector2 worldPosition) + { + Logging.Debug($"[{GetType().Name}] OnHoldEnd at {worldPosition}"); + _isHolding = false; + _directMoveVelocity = Vector3.zero; + + if (_aiPath != null && Settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding) + { + if (_pathfindingDragCoroutine != null) + { + StopCoroutine(_pathfindingDragCoroutine); + _pathfindingDragCoroutine = null; + } + } + + if (_aiPath != null && Settings.DefaultHoldMovementMode == HoldMovementMode.Direct) + { + _aiPath.enabled = false; + } + } + + #endregion + + #region Movement Methods + + protected virtual void SetTargetPosition(Vector2 worldPosition) + { + if (_aiPath != null) + { + _aiPath.destination = worldPosition; + _aiPath.maxSpeed = Settings.MoveSpeed; + _aiPath.maxAcceleration = Settings.MaxAcceleration; + _aiPath.canMove = true; + _aiPath.isStopped = false; + } + } + + protected virtual void MoveDirectlyTo(Vector2 worldPosition) + { + if (_aiPath == null) return; + + Vector3 current = transform.position; + Vector3 target = new Vector3(worldPosition.x, worldPosition.y, current.z); + Vector3 toTarget = (target - current); + Vector3 direction = toTarget.normalized; + + float maxSpeed = Settings.MoveSpeed; + float acceleration = Settings.MaxAcceleration; + + _directMoveVelocity = Vector3.MoveTowards(_directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime); + if (_directMoveVelocity.magnitude > maxSpeed) + { + _directMoveVelocity = _directMoveVelocity.normalized * maxSpeed; + } + + Vector3 move = _directMoveVelocity * Time.deltaTime; + if (move.magnitude > toTarget.magnitude) + { + move = toTarget; + } + + // Collision simulation + Vector3 adjustedVelocity = AdjustVelocityForObstacles(current, _directMoveVelocity); + Vector3 adjustedMove = adjustedVelocity * Time.deltaTime; + if (adjustedMove.magnitude > toTarget.magnitude) + { + adjustedMove = toTarget; + } + + transform.position += adjustedMove; + _lastDirectMoveDir = _directMoveVelocity.normalized; + } + + protected virtual Vector3 AdjustVelocityForObstacles(Vector3 position, Vector3 velocity) + { + if (velocity.sqrMagnitude < 0.0001f) + return velocity; + + float moveDistance = velocity.magnitude * Time.deltaTime; + Vector2 origin = new Vector2(position.x, position.y); + Vector2 dir = velocity.normalized; + float rayLength = colliderRadius + moveDistance; + + RaycastHit2D hit = Physics2D.Raycast(origin, dir, rayLength, obstacleMask); + + if (hit.collider != null) + { + Vector2 tangent = new Vector2(-hit.normal.y, hit.normal.x); + float slideAmount = Vector2.Dot(velocity, tangent); + Vector3 slideVelocity = tangent * slideAmount; + return slideVelocity; + } + + return velocity; + } + + protected virtual System.Collections.IEnumerator PathfindingDragUpdateCoroutine() + { + while (_isHolding && _aiPath != null) + { + SetTargetPosition(_lastHoldPosition); + yield return new WaitForSeconds(_pathfindingDragUpdateInterval); + } + } + + #endregion + + #region State and Animation + + protected virtual void UpdateMovementState() + { + bool isCurrentlyMoving = false; + + if (_isHolding && Settings.DefaultHoldMovementMode == HoldMovementMode.Direct) + { + isCurrentlyMoving = _directMoveVelocity.sqrMagnitude > 0.001f; + } + else if (_aiPath != null && _aiPath.enabled) + { + isCurrentlyMoving = _aiPath.velocity.sqrMagnitude > 0.001f; + } + + if (isCurrentlyMoving && !_isMoving) + { + _isMoving = true; + OnMovementStarted?.Invoke(); + Logging.Debug($"[{GetType().Name}] Movement started"); + } + else if (!isCurrentlyMoving && _isMoving) + { + _isMoving = false; + OnMovementStopped?.Invoke(); + Logging.Debug($"[{GetType().Name}] Movement stopped"); + } + } + + protected virtual void UpdateAnimation() + { + if (_animator == null || _aiPath == null) return; + + float normalizedSpeed = 0f; + Vector3 velocity = Vector3.zero; + float maxSpeed = Settings.MoveSpeed; + + if (_isHolding && Settings.DefaultHoldMovementMode == HoldMovementMode.Direct) + { + normalizedSpeed = _directMoveVelocity.magnitude / maxSpeed; + velocity = _directMoveVelocity; + } + else if (_aiPath.enabled) + { + normalizedSpeed = _aiPath.velocity.magnitude / maxSpeed; + velocity = _aiPath.velocity; + } + + _animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); + + if (velocity.sqrMagnitude > 0.01f) + { + Vector3 normalizedVelocity = velocity.normalized; + _lastDirX = normalizedVelocity.x; + _lastDirY = normalizedVelocity.y; + + _animator.SetFloat("DirX", _lastDirX); + _animator.SetFloat("DirY", _lastDirY); + } + else + { + _animator.SetFloat("DirX", _lastDirX); + _animator.SetFloat("DirY", _lastDirY); + } + } + + #endregion + } +} + diff --git a/Assets/Scripts/Input/BasePlayerMovementController.cs.meta b/Assets/Scripts/Input/BasePlayerMovementController.cs.meta new file mode 100644 index 00000000..ac6cd320 --- /dev/null +++ b/Assets/Scripts/Input/BasePlayerMovementController.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 059db1587c2c42389f98a1d3a52bec4b +timeCreated: 1765206060 \ No newline at end of file diff --git a/Assets/Scripts/Input/PlayerTouchController.cs b/Assets/Scripts/Input/PlayerTouchController.cs index 39950db1..e2c7d426 100644 --- a/Assets/Scripts/Input/PlayerTouchController.cs +++ b/Assets/Scripts/Input/PlayerTouchController.cs @@ -1,8 +1,6 @@ using UnityEngine; -using Pathfinding; using AppleHills.Core.Settings; using Core; -using Core.Lifecycle; namespace Input { @@ -19,341 +17,43 @@ namespace Input /// /// Handles player movement in response to tap and hold input events. /// Supports both direct and pathfinding movement modes, and provides event/callbacks for arrival/cancellation. + /// Extends BasePlayerMovementController with save/load and MoveToAndNotify functionality. /// - public class PlayerTouchController : ManagedBehaviour, ITouchInputConsumer + public class PlayerTouchController : BasePlayerMovementController { - // --- Movement State --- - private Vector3 targetPosition; - private Vector3 directMoveVelocity; // default is Vector3.zero - internal bool isHolding; - private Vector2 lastHoldPosition; - private Coroutine pathfindingDragCoroutine; - private float pathfindingDragUpdateInterval = 0.1f; // Interval in seconds - [Header("Collision Simulation")] - public LayerMask obstacleMask; - public float colliderRadius = 0.5f; - - // --- Settings Reference --- - private IPlayerFollowerSettings _settings; - - // --- Movement Events --- - private bool _isMoving = false; - public bool IsMoving => _isMoving; - public event System.Action OnMovementStarted; - public event System.Action OnMovementStopped; - - // --- Unity/Component References --- - private AIPath aiPath; - - // Note: String-based property lookup is flagged as inefficient, but is common in Unity for dynamic children. - private Animator animator; - private Transform artTransform; - private SpriteRenderer spriteRenderer; - - // --- Last direct movement direction --- - private Vector3 _lastDirectMoveDir = Vector3.right; - public Vector3 LastDirectMoveDir => _lastDirectMoveDir; - - // --- Last movement directions for animation blend tree --- - private float _lastDirX = 0f; // -1 (left) to 1 (right) - private float _lastDirY = -1f; // -1 (down) to 1 (up) - - // --- MoveToAndNotify State --- + // --- PlayerTouchController-specific features (MoveToAndNotify) --- public delegate void ArrivedAtTargetHandler(); - private Coroutine moveToCoroutine; + private Coroutine _moveToCoroutine; public event ArrivedAtTargetHandler OnArrivedAtTarget; public event System.Action OnMoveToCancelled; - private bool interruptMoveTo; - private LogVerbosity _logVerbosity = LogVerbosity.Warning; + private bool _interruptMoveTo; // Save system configuration public override bool AutoRegisterForSave => true; // Scene-specific SaveId - each level has its own player state public override string SaveId => $"{gameObject.scene.name}/PlayerController"; - internal override void OnManagedStart() + protected override void LoadSettings() { - aiPath = GetComponent(); - artTransform = transform.Find("CharacterArt"); - if (artTransform != null) - animator = artTransform.GetComponent(); - else - animator = GetComponentInChildren(); - // Cache SpriteRenderer for flipping - if (artTransform != null) - spriteRenderer = artTransform.GetComponent(); - if (spriteRenderer == null) - spriteRenderer = GetComponentInChildren(); - - // Initialize settings reference using GetSettingsObject - _settings = GameManager.GetSettingsObject(); - - // Set default input consumer - InputManager.Instance?.SetDefaultConsumer(this); - - _logVerbosity = DeveloperSettingsProvider.Instance.GetSettings().inputLogVerbosity; + var configs = GameManager.GetSettingsObject(); + _movementSettings = configs.DefaultPlayerMovement; } - /// - /// Handles tap input. Always uses pathfinding to move to the tapped location. - /// Cancels any in-progress MoveToAndNotify. - /// - public void OnTap(Vector2 worldPosition) + #region ITouchInputConsumer Overrides (Add InterruptMoveTo) + + public override void OnTap(Vector2 worldPosition) { InterruptMoveTo(); - Logging.Debug($"OnTap at {worldPosition}"); - if (aiPath != null) - { - aiPath.enabled = true; - aiPath.canMove = true; - aiPath.isStopped = false; - SetTargetPosition(worldPosition); - directMoveVelocity = Vector3.zero; - isHolding = false; - } + base.OnTap(worldPosition); } - - /// - /// Handles the start of a hold input. Begins tracking the finger and uses the correct movement mode. - /// Cancels any in-progress MoveToAndNotify. - /// - public void OnHoldStart(Vector2 worldPosition) + + public override void OnHoldStart(Vector2 worldPosition) { InterruptMoveTo(); - Logging.Debug($"OnHoldStart at {worldPosition}"); - lastHoldPosition = worldPosition; - isHolding = true; - if (_settings.DefaultHoldMovementMode == HoldMovementMode.Pathfinding && - aiPath != null) - { - aiPath.enabled = true; - if (pathfindingDragCoroutine != null) StopCoroutine(pathfindingDragCoroutine); - pathfindingDragCoroutine = StartCoroutine(PathfindingDragUpdateCoroutine()); - } - else // Direct movement - { - if (aiPath != null) aiPath.enabled = false; - directMoveVelocity = Vector3.zero; - } - } - - /// - /// Handles hold move input. Updates the target position for direct or pathfinding movement. - /// /// - public void OnHoldMove(Vector2 worldPosition) - { - if (!isHolding) return; - lastHoldPosition = worldPosition; - if (_settings.DefaultHoldMovementMode == HoldMovementMode.Direct) - { - if (aiPath != null && aiPath.enabled) aiPath.enabled = false; - MoveDirectlyTo(worldPosition); - } - // If pathfinding, coroutine will update destination - } - - /// - /// Handles the end of a hold input. Stops tracking and disables movement as needed. - /// - public void OnHoldEnd(Vector2 worldPosition) - { - Logging.Debug($"OnHoldEnd at {worldPosition}"); - isHolding = false; - directMoveVelocity = Vector3.zero; - if (aiPath != null && _settings.DefaultHoldMovementMode == - HoldMovementMode.Pathfinding) - { - if (pathfindingDragCoroutine != null) - { - StopCoroutine(pathfindingDragCoroutine); - pathfindingDragCoroutine = null; - } - } - - if (aiPath != null && _settings.DefaultHoldMovementMode == HoldMovementMode.Direct) - { - aiPath.enabled = false; - } - } - - /// - /// Sets the target position for pathfinding movement. - /// - private void SetTargetPosition(Vector2 worldPosition) - { - if (aiPath != null) - { - aiPath.destination = worldPosition; - // Apply both speed and acceleration from settings - aiPath.maxSpeed = _settings.MoveSpeed; - aiPath.maxAcceleration = _settings.MaxAcceleration; - aiPath.canMove = true; - aiPath.isStopped = false; - } - } - - /// - /// Moves the player directly towards the specified world position. - /// - public void MoveDirectlyTo(Vector2 worldPosition) - { - if (aiPath == null) - { - return; - } - Vector3 current = transform.position; - Vector3 target = new Vector3(worldPosition.x, worldPosition.y, current.z); - Vector3 toTarget = (target - current); - Vector3 direction = toTarget.normalized; - - // Get speed and acceleration directly from settings - float maxSpeed = _settings.MoveSpeed; - float acceleration = _settings.MaxAcceleration; - - directMoveVelocity = Vector3.MoveTowards(directMoveVelocity, direction * maxSpeed, acceleration * Time.deltaTime); - if (directMoveVelocity.magnitude > maxSpeed) - { - directMoveVelocity = directMoveVelocity.normalized * maxSpeed; - } - Vector3 move = directMoveVelocity * Time.deltaTime; - if (move.magnitude > toTarget.magnitude) - { - move = toTarget; - } - // --- Collision simulation --- - Vector3 adjustedVelocity = AdjustVelocityForObstacles(current, directMoveVelocity); - Vector3 adjustedMove = adjustedVelocity * Time.deltaTime; - if (adjustedMove.magnitude > toTarget.magnitude) - { - adjustedMove = toTarget; - } - transform.position += adjustedMove; - - // Cache the last direct movement direction - _lastDirectMoveDir = directMoveVelocity.normalized; - } - - /// - /// Simulates collision with obstacles by raycasting in the direction of velocity and projecting the velocity if a collision is detected. - /// - /// Player's current position. - /// Intended velocity for this frame. - /// Adjusted velocity after collision simulation. - private Vector3 AdjustVelocityForObstacles(Vector3 position, Vector3 velocity) - { - if (velocity.sqrMagnitude < 0.0001f) - return velocity; - float moveDistance = velocity.magnitude * Time.deltaTime; - Vector2 origin = new Vector2(position.x, position.y); - Vector2 dir = velocity.normalized; - float rayLength = colliderRadius + moveDistance; - RaycastHit2D hit = Physics2D.Raycast(origin, dir, rayLength, obstacleMask); - Debug.DrawLine(origin, origin + dir * rayLength, Color.red, 0.1f); - if (hit.collider != null) - { - // Draw normal and tangent for debug - Debug.DrawLine(hit.point, hit.point + hit.normal, Color.green, 0.2f); - Vector2 tangent = new Vector2(-hit.normal.y, hit.normal.x); - Debug.DrawLine(hit.point, hit.point + tangent, Color.blue, 0.2f); - // Project velocity onto tangent to simulate sliding - float slideAmount = Vector2.Dot(velocity, tangent); - Vector3 slideVelocity = tangent * slideAmount; - return slideVelocity; - } - return velocity; - } - - void Update() - { - UpdateMovementState(); - - if (animator != null && aiPath != null) - { - float normalizedSpeed = 0f; - Vector3 velocity = Vector3.zero; - float maxSpeed = _settings.MoveSpeed; - - if (isHolding && _settings.DefaultHoldMovementMode == HoldMovementMode.Direct) - { - normalizedSpeed = directMoveVelocity.magnitude / maxSpeed; - velocity = directMoveVelocity; - } - else if (aiPath.enabled) - { - normalizedSpeed = aiPath.velocity.magnitude / maxSpeed; - velocity = aiPath.velocity; - } - - // Set speed parameter as before - animator.SetFloat("Speed", Mathf.Clamp01(normalizedSpeed)); - - // Calculate and set X and Y directions for 2D blend tree - if (velocity.sqrMagnitude > 0.01f) - { - // Normalize the velocity vector to get direction - Vector3 normalizedVelocity = velocity.normalized; - - // Update the stored directions when actively moving - _lastDirX = normalizedVelocity.x; - _lastDirY = normalizedVelocity.y; - - // Set the animator parameters - animator.SetFloat("DirX", _lastDirX); - animator.SetFloat("DirY", _lastDirY); - } - else - { - // When not moving, keep using the last direction - animator.SetFloat("DirX", _lastDirX); - animator.SetFloat("DirY", _lastDirY); - } - } - } - - /// - /// Checks if the player is currently moving and fires appropriate events when movement state changes. - /// - private void UpdateMovementState() - { - bool isCurrentlyMoving = false; - - // Check direct movement - if (isHolding && _settings.DefaultHoldMovementMode == HoldMovementMode.Direct) - { - isCurrentlyMoving = directMoveVelocity.sqrMagnitude > 0.001f; - } - // Check pathfinding movement - else if (aiPath != null && aiPath.enabled) - { - isCurrentlyMoving = aiPath.velocity.sqrMagnitude > 0.001f; - } - - // Fire events only when state changes - if (isCurrentlyMoving && !_isMoving) - { - _isMoving = true; - OnMovementStarted?.Invoke(); - Logging.Debug("Movement started"); - } - else if (!isCurrentlyMoving && _isMoving) - { - _isMoving = false; - OnMovementStopped?.Invoke(); - Logging.Debug("Movement stopped"); - } - } - - /// - /// Coroutine for updating the AIPath destination during pathfinding hold movement. - /// - private System.Collections.IEnumerator PathfindingDragUpdateCoroutine() - { - while (isHolding && aiPath != null) - { - aiPath.destination = new Vector3(lastHoldPosition.x, lastHoldPosition.y, transform.position.z); - yield return new WaitForSeconds(pathfindingDragUpdateInterval); - } + base.OnHoldStart(worldPosition); } + + #endregion /// /// Moves the player to a specific target position and notifies via events when arrived or cancelled. @@ -362,20 +62,20 @@ namespace Input public void MoveToAndNotify(Vector3 target) { // Cancel any previous move-to coroutine - if (moveToCoroutine != null) + if (_moveToCoroutine != null) { - StopCoroutine(moveToCoroutine); + StopCoroutine(_moveToCoroutine); } - interruptMoveTo = false; + _interruptMoveTo = false; // Ensure pathfinding is enabled for MoveToAndNotify - if (aiPath != null) + if (_aiPath != null) { - aiPath.enabled = true; - aiPath.canMove = true; - aiPath.isStopped = false; + _aiPath.enabled = true; + _aiPath.canMove = true; + _aiPath.isStopped = false; } - moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target)); + _moveToCoroutine = StartCoroutine(MoveToTargetCoroutine(target)); } /// @@ -383,11 +83,11 @@ namespace Input /// public void InterruptMoveTo() { - interruptMoveTo = true; - isHolding = false; - directMoveVelocity = Vector3.zero; - if (_settings.DefaultHoldMovementMode == HoldMovementMode.Direct && aiPath != null) - aiPath.enabled = false; + _interruptMoveTo = true; + _isHolding = false; + _directMoveVelocity = Vector3.zero; + if (Settings.DefaultHoldMovementMode == HoldMovementMode.Direct && _aiPath != null) + _aiPath.enabled = false; OnMoveToCancelled?.Invoke(); } @@ -396,19 +96,19 @@ namespace Input /// private System.Collections.IEnumerator MoveToTargetCoroutine(Vector3 target) { - if (aiPath != null) + if (_aiPath != null) { - aiPath.destination = target; - aiPath.maxSpeed = _settings.MoveSpeed; - aiPath.maxAcceleration = _settings.MaxAcceleration; + _aiPath.destination = target; + _aiPath.maxSpeed = Settings.MoveSpeed; + _aiPath.maxAcceleration = Settings.MaxAcceleration; } - while (!interruptMoveTo) + while (!_interruptMoveTo) { Vector2 current2D = new Vector2(transform.position.x, transform.position.y); Vector2 target2D = new Vector2(target.x, target.y); float dist = Vector2.Distance(current2D, target2D); - if (dist <= _settings.StopDistance + 0.2f) + if (dist <= Settings.StopDistance + 0.2f) { break; } @@ -416,8 +116,8 @@ namespace Input yield return null; } - moveToCoroutine = null; - if (!interruptMoveTo) + _moveToCoroutine = null; + if (!_interruptMoveTo) { OnArrivedAtTarget?.Invoke(); } diff --git a/Assets/Scripts/Interactions/ItemSlot.cs b/Assets/Scripts/Interactions/ItemSlot.cs index 07fd8c8a..6fe5a4c4 100644 --- a/Assets/Scripts/Interactions/ItemSlot.cs +++ b/Assets/Scripts/Interactions/ItemSlot.cs @@ -64,7 +64,6 @@ namespace Interactions // Settings reference private IInteractionSettings interactionSettings; - private IPlayerFollowerSettings playerFollowerSettings; /// /// Read-only access to the current slotted item state. @@ -180,7 +179,6 @@ namespace Interactions // Initialize settings references interactionSettings = GameManager.GetSettingsObject(); - playerFollowerSettings = GameManager.GetSettingsObject(); } #if UNITY_EDITOR @@ -580,7 +578,8 @@ namespace Interactions slotRenderer.sprite = slottedData.mapSprite; // Scale sprite to desired height, preserve aspect ratio, compensate for parent scale - float desiredHeight = playerFollowerSettings?.HeldIconDisplayHeight ?? 2.0f; + var configs = GameManager.GetSettingsObject(); + float desiredHeight = configs?.FollowerMovement?.HeldIconDisplayHeight ?? 2.0f; var sprite = slottedData.mapSprite; float spriteHeight = sprite.bounds.size.y; Vector3 parentScale = slotRenderer.transform.parent != null diff --git a/Assets/Scripts/Minigames/TrashMaze.meta b/Assets/Scripts/Minigames/TrashMaze.meta new file mode 100644 index 00000000..f8ced11e --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Minigames/TrashMaze/Core.meta b/Assets/Scripts/Minigames/TrashMaze/Core.meta new file mode 100644 index 00000000..09056619 --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Core.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs b/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs new file mode 100644 index 00000000..43598a9a --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs @@ -0,0 +1,91 @@ +using Core; +using UnityEngine; +using Input; +using AppleHills.Core.Settings; + +namespace Minigames.TrashMaze.Core +{ + /// + /// Controls Pulver character movement in the Trash Maze. + /// Inherits from BasePlayerMovementController for tap-to-move and hold-to-move. + /// Updates global shader properties for vision radius system. + /// + public class PulverController : BasePlayerMovementController + { + public static PulverController Instance { get; private set; } + + // Cached shader property IDs for performance + private static readonly int PlayerWorldPosID = Shader.PropertyToID("_PlayerWorldPos"); + private static readonly int VisionRadiusID = Shader.PropertyToID("_VisionRadius"); + + // Vision radius loaded from settings + private float _visionRadius; + + // Public accessors for other systems + public static Vector2 PlayerPosition => Instance != null ? Instance.transform.position : Vector2.zero; + public static float VisionRadius => Instance != null ? Instance._visionRadius : 8f; + + internal override void OnManagedAwake() + { + // Singleton pattern + if (Instance != null && Instance != this) + { + Logging.Warning("[PulverController] Duplicate instance detected. Destroying duplicate."); + Destroy(gameObject); + return; + } + + Instance = this; + + base.OnManagedAwake(); + + Logging.Debug("[PulverController] Initialized"); + } + + protected override void LoadSettings() + { + var configs = GameManager.GetSettingsObject(); + _movementSettings = configs.TrashMazeMovement; + + // Load vision radius from follower settings + _visionRadius = configs.FollowerMovement.TrashMazeVisionRadius; + Logging.Debug($"[PulverController] Loaded vision radius from settings: {_visionRadius}"); + } + + protected override void Update() + { + base.Update(); // Call base for movement and animation + + // Update global shader properties for vision system + UpdateShaderGlobals(); + } + + /// + /// Updates global shader properties used by visibility shaders + /// + private void UpdateShaderGlobals() + { + Shader.SetGlobalVector(PlayerWorldPosID, transform.position); + Shader.SetGlobalFloat(VisionRadiusID, _visionRadius); + } + + internal override void OnManagedDestroy() + { + base.OnManagedDestroy(); + + if (Instance == this) + { + Instance = null; + } + } + + /// + /// Set vision radius at runtime + /// + public void SetVisionRadius(float radius) + { + _visionRadius = Mathf.Max(0.1f, radius); + } + } +} + diff --git a/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs.meta b/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs.meta new file mode 100644 index 00000000..6356fbf2 --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Core/PulverController.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs b/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs new file mode 100644 index 00000000..0c9e33ab --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs @@ -0,0 +1,168 @@ +using Core; +using Core.Lifecycle; +using UnityEngine; + +namespace Minigames.TrashMaze.Core +{ + /// + /// Main controller for the Trash Maze minigame. + /// Initializes the vision system and manages game flow. + /// + public class TrashMazeController : ManagedBehaviour + { + public static TrashMazeController Instance { get; private set; } + + [Header("Player")] + [SerializeField] private PulverController pulverPrefab; + [SerializeField] private Transform startPosition; + + [Header("Background")] + [Tooltip("Background sprite renderer - world size and center are inferred from its bounds")] + [SerializeField] private SpriteRenderer backgroundRenderer; + + [Header("Exit")] + [SerializeField] private Transform exitPosition; + + // Cached shader property IDs for performance + private static readonly int WorldSizeID = Shader.PropertyToID("_WorldSize"); + private static readonly int WorldCenterID = Shader.PropertyToID("_WorldCenter"); + + private PulverController _pulverInstance; + private bool _mazeCompleted; + + internal override void OnManagedAwake() + { + base.OnManagedAwake(); + + // Singleton pattern + if (Instance != null && Instance != this) + { + Logging.Warning("[TrashMazeController] Duplicate instance detected. Destroying duplicate."); + Destroy(gameObject); + return; + } + + Instance = this; + } + + internal override void OnManagedStart() + { + base.OnManagedStart(); + + Logging.Debug("[TrashMazeController] Initializing Trash Maze"); + + InitializeMaze(); + } + + private void InitializeMaze() + { + // Infer world bounds from background renderer and set shader globals + ApplyBackgroundBoundsToShader(); + + // Spawn player + SpawnPulver(); + + Logging.Debug("[TrashMazeController] Trash Maze initialized"); + } + + /// + /// Automatically infers world size and center from the background sprite renderer bounds + /// and applies them to shader global properties. + /// This handles scaled, rotated, or transformed backgrounds correctly. + /// + private void ApplyBackgroundBoundsToShader() + { + if (backgroundRenderer == null) + { + Logging.Error("[TrashMazeController] Background renderer not assigned! World bounds cannot be inferred."); + Logging.Warning("[TrashMazeController] Using fallback bounds: size=(100,100), center=(0,0)"); + + // Fallback values + Shader.SetGlobalVector(WorldSizeID, new Vector4(100f, 100f, 0f, 0f)); + Shader.SetGlobalVector(WorldCenterID, new Vector4(0f, 0f, 0f, 0f)); + return; + } + + // Get the material instance (avoid modifying shared asset) + Material material = backgroundRenderer.material; + if (material == null) + { + Logging.Warning("[TrashMazeController] Background material missing on renderer."); + return; + } + + // Use renderer.bounds (world-space, accounts for scale/rotation/parent transforms) + Bounds bounds = backgroundRenderer.bounds; + Vector3 worldSize = bounds.size; + Vector3 worldCenter = bounds.center; + + // Apply to shader globals (used by both background and object shaders) + Shader.SetGlobalVector(WorldSizeID, new Vector4(worldSize.x, worldSize.y, 0f, 0f)); + Shader.SetGlobalVector(WorldCenterID, new Vector4(worldCenter.x, worldCenter.y, 0f, 0f)); + + // Also apply directly to background material for its shader + material.SetVector(WorldSizeID, new Vector4(worldSize.x, worldSize.y, 0f, 0f)); + material.SetVector(WorldCenterID, new Vector4(worldCenter.x, worldCenter.y, 0f, 0f)); + + Logging.Debug($"[TrashMazeController] World bounds inferred from background: " + + $"Size=({worldSize.x:F2}, {worldSize.y:F2}), Center=({worldCenter.x:F2}, {worldCenter.y:F2})"); + } + + private void SpawnPulver() + { + if (pulverPrefab == null) + { + Logging.Error("[TrashMazeController] Pulver prefab not assigned!"); + return; + } + + Vector3 spawnPosition = startPosition != null ? startPosition.position : Vector3.zero; + _pulverInstance = Instantiate(pulverPrefab, spawnPosition, Quaternion.identity); + + Logging.Debug($"[TrashMazeController] Pulver spawned at {spawnPosition}"); + } + + /// + /// Called when player reaches the maze exit + /// + public void OnExitReached() + { + if (_mazeCompleted) + { + Logging.Debug("[TrashMazeController] Maze already completed"); + return; + } + + _mazeCompleted = true; + + Logging.Debug("[TrashMazeController] Maze completed! Player reached exit."); + + // TODO: Trigger completion events + // - Award booster packs collected + // - Open gate for Trafalgar + // - Switch control to Trafalgar + } + + /// + /// Called when player collects a booster pack + /// + public void OnBoosterPackCollected() + { + Logging.Debug("[TrashMazeController] Booster pack collected"); + + // TODO: Integrate with card album system + // CardAlbum.AddBoosterPack(); + } + + internal override void OnManagedDestroy() + { + base.OnManagedDestroy(); + + if (Instance == this) + { + Instance = null; + } + } + } +} + diff --git a/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs.meta b/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs.meta new file mode 100644 index 00000000..90e8c6ce --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Core/TrashMazeController.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects.meta b/Assets/Scripts/Minigames/TrashMaze/Objects.meta new file mode 100644 index 00000000..6e38c956 --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects/Editor.meta b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor.meta new file mode 100644 index 00000000..81c056a8 --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3a935f5e791c46df8920c2c33f1c24c0 +timeCreated: 1765361215 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs new file mode 100644 index 00000000..befd7ac0 --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs @@ -0,0 +1,99 @@ +using UnityEngine; +using UnityEditor; +using Minigames.TrashMaze.Objects; + +namespace Minigames.TrashMaze.Editor +{ + [CustomEditor(typeof(RevealableObject))] + public class RevealableObjectEditor : UnityEditor.Editor + { + private RenderTexture _cachedStampTexture; + private Texture2D _previewTexture; + + public override void OnInspectorGUI() + { + DrawDefaultInspector(); + + // Only show debug info in play mode + if (!Application.isPlaying) + { + return; + } + + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("Progressive Reveal Debug (Play Mode Only)", EditorStyles.boldLabel); + EditorGUILayout.HelpBox("Shows the current stamp texture for Progressive reveal mode.", MessageType.Info); + + RevealableObject revealableObject = (RevealableObject)target; + + // Use reflection to get private _revealStampTexture field + var field = typeof(RevealableObject).GetField("_revealStampTexture", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + if (field != null) + { + RenderTexture stampTexture = field.GetValue(revealableObject) as RenderTexture; + + if (stampTexture != null) + { + // Display stamp texture info + EditorGUILayout.LabelField("Stamp Texture:", $"{stampTexture.width}x{stampTexture.height}"); + + // Show preview of stamp texture + GUILayout.Label("Reveal Mask Preview (White = Revealed):"); + + // Create preview texture if needed + if (_cachedStampTexture != stampTexture || _previewTexture == null) + { + _cachedStampTexture = stampTexture; + + if (_previewTexture != null) + { + DestroyImmediate(_previewTexture); + } + + _previewTexture = new Texture2D(stampTexture.width, stampTexture.height, TextureFormat.R8, false); + _previewTexture.filterMode = FilterMode.Point; + } + + // Copy RenderTexture to Texture2D for preview + RenderTexture.active = stampTexture; + _previewTexture.ReadPixels(new Rect(0, 0, stampTexture.width, stampTexture.height), 0, 0); + _previewTexture.Apply(); + RenderTexture.active = null; + + // Display preview with fixed size + float previewSize = 256f; + float aspectRatio = (float)stampTexture.height / stampTexture.width; + Rect previewRect = GUILayoutUtility.GetRect(previewSize, previewSize * aspectRatio); + EditorGUI.DrawPreviewTexture(previewRect, _previewTexture, null, ScaleMode.ScaleToFit); + + // Auto-refresh in play mode + if (Application.isPlaying) + { + Repaint(); + } + } + else + { + EditorGUILayout.HelpBox("No stamp texture found. Make sure object is in Progressive reveal mode.", MessageType.Warning); + } + } + else + { + EditorGUILayout.HelpBox("Could not access stamp texture via reflection.", MessageType.Error); + } + } + + private void OnDisable() + { + // Clean up preview texture + if (_previewTexture != null) + { + DestroyImmediate(_previewTexture); + _previewTexture = null; + } + } + } +} + diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs.meta b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs.meta new file mode 100644 index 00000000..f338d84d --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects/Editor/RevealableObjectEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1d081993ee424269bf8eae99db36a54c +timeCreated: 1765361215 \ No newline at end of file diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs b/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs new file mode 100644 index 00000000..f75eeb1a --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs @@ -0,0 +1,559 @@ +using Core; +using Minigames.TrashMaze.Core; +using UnityEngine; +using System.Collections; +using AppleHills.Core.Settings; + +namespace Minigames.TrashMaze.Objects +{ + /// + /// Reveal mode for object visibility + /// + public enum RevealMode + { + Binary, // Simple on/off reveal (current system) + Progressive // Pixel-by-pixel progressive reveal with stamp texture + } + + /// + /// Component for objects that need reveal memory (obstacles, booster packs, treasures). + /// Tracks if object has been revealed and updates material properties accordingly. + /// + [RequireComponent(typeof(SpriteRenderer))] + public class RevealableObject : MonoBehaviour + { + [Header("Reveal Settings")] + [SerializeField] private RevealMode revealMode = RevealMode.Binary; + + [Header("Textures")] + [SerializeField] private Sprite normalSprite; + [SerializeField] private Sprite outlineSprite; + + [Header("Progressive Reveal Settings")] + [SerializeField, Range(0.5f, 3f)] private float stampWorldRadius = 1.5f; + [Tooltip("Size of each stamp in world units. Smaller = more gradual reveal. Should be smaller than vision radius.")] + + [Header("Object Type")] + [SerializeField] private bool isBoosterPack = false; + [SerializeField] private bool isExit = false; + + private SpriteRenderer _spriteRenderer; + private Material _instanceMaterial; + private bool _hasBeenRevealed = false; + private bool _isCollected = false; + + // Material property IDs (cached for performance) + private static readonly int IsRevealedID = Shader.PropertyToID("_IsRevealed"); + private static readonly int IsInVisionID = Shader.PropertyToID("_IsInVision"); + private static readonly int RevealMaskID = Shader.PropertyToID("_RevealMask"); + + // Progressive reveal system + private RenderTexture _revealStampTexture; + private Coroutine _stampCoroutine; + private Bounds _objectBounds; + private float _activationDistance; + + // Binary reveal system + private Coroutine _binaryRevealCoroutine; + + private void Awake() + { + _spriteRenderer = GetComponent(); + + if (_spriteRenderer.material == null) + { + Logging.Error($"[RevealableObject] No material assigned to {gameObject.name}"); + return; + } + + // Create instance material + _instanceMaterial = new Material(_spriteRenderer.material); + _spriteRenderer.material = _instanceMaterial; + + // Call mode-specific initialization - COMPLETELY SEPARATE + if (revealMode == RevealMode.Binary) + { + InitializeBinaryMode(); + } + else if (revealMode == RevealMode.Progressive) + { + InitializeProgressiveMode(); + } + } + + // ======================================== + // BINARY MODE INITIALIZATION + // ======================================== + + private void InitializeBinaryMode() + { + // Validate Binary shader + string shaderName = _instanceMaterial.shader.name; + if (!shaderName.Contains("ObjectVisibility") || shaderName.Contains("Progressive")) + { + Logging.Error($"[RevealableObject] {gameObject.name} Binary mode needs shader 'TrashMaze/ObjectVisibility', currently: {shaderName}"); + } + + // Set initial Binary mode properties + _instanceMaterial.SetFloat(IsRevealedID, 0f); + _instanceMaterial.SetFloat(IsInVisionID, 0f); + + // Set textures + if (normalSprite != null) + { + _instanceMaterial.SetTexture("_MainTex", normalSprite.texture); + } + if (outlineSprite != null) + { + _instanceMaterial.SetTexture("_OutlineTex", outlineSprite.texture); + } + + Logging.Debug($"[RevealableObject] {gameObject.name} Binary mode initialized"); + } + + // ======================================== + // PROGRESSIVE MODE INITIALIZATION + // ======================================== + + private void InitializeProgressiveMode() + { + // Validate Progressive shader + string shaderName = _instanceMaterial.shader.name; + if (!shaderName.Contains("ObjectVisibilityProgressive")) + { + Logging.Error($"[RevealableObject] {gameObject.name} Progressive mode needs shader 'TrashMaze/ObjectVisibilityProgressive', currently: {shaderName}"); + } + + // Initialize progressive reveal system + InitializeProgressiveReveal(); + } + + /// + /// Initialize progressive reveal system with dynamic texture sizing + /// + private void InitializeProgressiveReveal() + { + // Get object bounds for UV calculations + _objectBounds = _spriteRenderer.bounds; + + // Load activation distance from settings (use vision radius from follower settings) + var configs = GameManager.GetSettingsObject(); + _activationDistance = configs.FollowerMovement.TrashMazeVisionRadius; + + Logging.Debug($"[RevealableObject] {gameObject.name} loaded activation distance from settings: {_activationDistance}"); + + // Dynamically determine texture size from sprite + int textureWidth = 128; + int textureHeight = 128; + + if (normalSprite != null && normalSprite.texture != null) + { + // Use sprite's texture resolution (match 1:1 for pixel-perfect) + textureWidth = normalSprite.texture.width; + textureHeight = normalSprite.texture.height; + } + else + { + Logging.Warning($"[RevealableObject] {gameObject.name} in Progressive mode but normalSprite not assigned! Using default 128x128 texture. Assign Normal Sprite in inspector!"); + } + + // Create reveal stamp texture (R8 format = 8-bit grayscale, minimal memory) + _revealStampTexture = new RenderTexture(textureWidth, textureHeight, 0, RenderTextureFormat.R8); + _revealStampTexture.filterMode = FilterMode.Point; // Sharp edges for binary reveals + _revealStampTexture.wrapMode = TextureWrapMode.Clamp; + _revealStampTexture.Create(); // Explicitly create the texture + + // Clear to black (nothing revealed initially) + RenderTexture previousActive = RenderTexture.active; + RenderTexture.active = _revealStampTexture; + GL.Clear(false, true, Color.black); + RenderTexture.active = previousActive; + + Logging.Debug($"[RevealableObject] {gameObject.name} cleared reveal texture to black - should be invisible initially"); + + // Set Progressive shader properties + _instanceMaterial.SetTexture(RevealMaskID, _revealStampTexture); + + // Progressive shader uses global _PlayerWorldPos and _VisionRadius (set by PulverController) + // No need to set _IsInVision - shader does per-pixel distance checks + + // Set textures + if (normalSprite != null) + { + _instanceMaterial.SetTexture("_MainTex", normalSprite.texture); + } + if (outlineSprite != null) + { + _instanceMaterial.SetTexture("_OutlineTex", outlineSprite.texture); + } + + Logging.Debug($"[RevealableObject] {gameObject.name} Progressive mode initialized: {textureWidth}x{textureHeight} reveal texture"); + } + + private void Start() + { + if (PulverController.Instance == null) + { + Logging.Error($"[RevealableObject] {gameObject.name} cannot start - PulverController not found!"); + return; + } + + // Start mode-specific runtime logic - COMPLETELY SEPARATE + if (revealMode == RevealMode.Binary) + { + StartBinaryModeTracking(); + } + else if (revealMode == RevealMode.Progressive) + { + StartProgressiveModeTracking(); + } + } + + // ======================================== + // BINARY MODE: Start tracking + // ======================================== + + private void StartBinaryModeTracking() + { + _binaryRevealCoroutine = StartCoroutine(BinaryRevealTrackingCoroutine()); + Logging.Debug($"[RevealableObject] {gameObject.name} Binary mode tracking started"); + } + + // ======================================== + // PROGRESSIVE MODE: Start tracking + // ======================================== + + private void StartProgressiveModeTracking() + { + // Subscribe to movement events for stamping + PulverController.Instance.OnMovementStarted += OnPlayerMovementStarted; + PulverController.Instance.OnMovementStopped += OnPlayerMovementStopped; + + // NO vision tracking coroutine - Progressive shader does per-pixel distance checks using global _PlayerWorldPos + + Logging.Debug($"[RevealableObject] {gameObject.name} Progressive mode tracking started"); + } + + + // ======================================== + // BINARY MODE: Vision-based reveal coroutine + // ======================================== + + /// + /// Binary mode coroutine - tracks player distance and updates vision/reveal flags + /// Runs continuously, checks every 0.1s for performance + /// + private IEnumerator BinaryRevealTrackingCoroutine() + { + while (!_isCollected && _instanceMaterial != null) + { + // Calculate distance to player + float distance = Vector2.Distance(transform.position, PulverController.PlayerPosition); + bool isInRadius = distance < PulverController.VisionRadius; + + // Set real-time vision flag (controls shader color vs outline) + _instanceMaterial.SetFloat(IsInVisionID, isInRadius ? 1f : 0f); + + // Set reveal flag (once revealed, stays revealed) + if (isInRadius && !_hasBeenRevealed) + { + _hasBeenRevealed = true; + _instanceMaterial.SetFloat(IsRevealedID, 1f); + Logging.Debug($"[RevealableObject] {gameObject.name} revealed!"); + } + + // Wait before next check (reduces CPU load) + yield return new WaitForSeconds(0.1f); + } + } + + // ======================================== + // PROGRESSIVE MODE: Event-based stamp reveal + // ======================================== + + /// + /// Called when player starts moving - begin stamping if near object + /// + private void OnPlayerMovementStarted() + { + if (_isCollected || revealMode != RevealMode.Progressive) return; + + // Check if player's vision circle could overlap with object bounds + // Use closest point on bounds to check distance + Vector2 playerPos = PulverController.PlayerPosition; + Vector2 closestPoint = _objectBounds.ClosestPoint(playerPos); + float distanceToBounds = Vector2.Distance(playerPos, closestPoint); + + // Start stamping if vision radius could reach the object + // Add padding to account for vision radius overlap + float activationThreshold = _activationDistance + _objectBounds.extents.magnitude; + + if (distanceToBounds < activationThreshold && _stampCoroutine == null) + { + _stampCoroutine = StartCoroutine(StampRevealCoroutine()); + Logging.Debug($"[RevealableObject] {gameObject.name} started stamping coroutine (distanceToBounds: {distanceToBounds:F2}, threshold: {activationThreshold:F2})"); + } + } + + /// + /// Called when player stops moving - stop stamping + /// + private void OnPlayerMovementStopped() + { + if (_stampCoroutine != null) + { + StopCoroutine(_stampCoroutine); + _stampCoroutine = null; + } + } + + /// + /// Coroutine that stamps reveal texture while player is moving and near object + /// + private IEnumerator StampRevealCoroutine() + { + while (!_isCollected) + { + // Check if player's vision circle overlaps with object bounds + Vector2 playerPos = PulverController.PlayerPosition; + Vector2 closestPoint = _objectBounds.ClosestPoint(playerPos); + float distanceToBounds = Vector2.Distance(playerPos, closestPoint); + + // Calculate activation threshold with padding + float activationThreshold = _activationDistance + _objectBounds.extents.magnitude; + + // If player moved too far away, stop stamping + if (distanceToBounds >= activationThreshold) + { + Logging.Debug($"[RevealableObject] {gameObject.name} stopping stamping coroutine (too far)"); + _stampCoroutine = null; + yield break; + } + + // Stamp if player's vision radius reaches any part of the object + if (distanceToBounds < PulverController.VisionRadius) + { + StampPlayerPosition(); + } + + // Wait before next stamp (reduces GPU writes) + yield return new WaitForSeconds(0.1f); + } + } + + /// + /// Stamp the player's current position onto the reveal texture + /// Direct CPU-based stamping - calculates circle-rectangle intersection in world space + /// + private void StampPlayerPosition() + { + if (_revealStampTexture == null) + { + Logging.Warning($"[RevealableObject] {gameObject.name} cannot stamp: texture is null"); + return; + } + + // Get player position and vision radius in world space + Vector2 playerWorldPos = PulverController.PlayerPosition; + float visionRadius = PulverController.VisionRadius; + + // Get object bounds in world space + Vector2 boundsMin = _objectBounds.min; + Vector2 boundsSize = _objectBounds.size; + + // Get texture dimensions + int texWidth = _revealStampTexture.width; + int texHeight = _revealStampTexture.height; + + // Calculate the bounding box of the circle in world space + Vector2 circleMin = playerWorldPos - Vector2.one * visionRadius; + Vector2 circleMax = playerWorldPos + Vector2.one * visionRadius; + + // Calculate intersection of circle bounding box with object bounds + Vector2 intersectMin = new Vector2( + Mathf.Max(circleMin.x, boundsMin.x), + Mathf.Max(circleMin.y, boundsMin.y) + ); + Vector2 intersectMax = new Vector2( + Mathf.Min(circleMax.x, boundsMin.x + boundsSize.x), + Mathf.Min(circleMax.y, boundsMin.y + boundsSize.y) + ); + + // Check if there's any intersection + if (intersectMin.x >= intersectMax.x || intersectMin.y >= intersectMax.y) + { + return; // No intersection, nothing to stamp + } + + // Convert world space intersection to texture pixel coordinates + int pixelMinX = Mathf.FloorToInt((intersectMin.x - boundsMin.x) / boundsSize.x * texWidth); + int pixelMaxX = Mathf.CeilToInt((intersectMax.x - boundsMin.x) / boundsSize.x * texWidth); + int pixelMinY = Mathf.FloorToInt((intersectMin.y - boundsMin.y) / boundsSize.y * texHeight); + int pixelMaxY = Mathf.CeilToInt((intersectMax.y - boundsMin.y) / boundsSize.y * texHeight); + + // Clamp to texture bounds + pixelMinX = Mathf.Max(0, pixelMinX); + pixelMaxX = Mathf.Min(texWidth, pixelMaxX); + pixelMinY = Mathf.Max(0, pixelMinY); + pixelMaxY = Mathf.Min(texHeight, pixelMaxY); + + // Read current texture data + RenderTexture.active = _revealStampTexture; + Texture2D tempTex = new Texture2D(texWidth, texHeight, TextureFormat.R8, false); + tempTex.ReadPixels(new Rect(0, 0, texWidth, texHeight), 0, 0); + tempTex.Apply(); + + // Stamp pixels within the circle + bool anyPixelStamped = false; + float radiusSquared = visionRadius * visionRadius; + + for (int py = pixelMinY; py < pixelMaxY; py++) + { + for (int px = pixelMinX; px < pixelMaxX; px++) + { + // Convert pixel coordinates back to world space + float worldX = boundsMin.x + (px / (float)texWidth) * boundsSize.x; + float worldY = boundsMin.y + (py / (float)texHeight) * boundsSize.y; + + // Check if this pixel is within the circle + float dx = worldX - playerWorldPos.x; + float dy = worldY - playerWorldPos.y; + float distSquared = dx * dx + dy * dy; + + if (distSquared <= radiusSquared) + { + // Stamp this pixel (set to white) + tempTex.SetPixel(px, py, Color.white); + anyPixelStamped = true; + } + } + } + + if (anyPixelStamped) + { + // Upload modified texture back to GPU + tempTex.Apply(); + Graphics.CopyTexture(tempTex, _revealStampTexture); + + Logging.Debug($"[RevealableObject] {gameObject.name} stamped pixels at world pos ({playerWorldPos.x:F2}, {playerWorldPos.y:F2}), radius {visionRadius:F2}"); + } + + RenderTexture.active = null; + Destroy(tempTex); + } + + private void OnTriggerEnter2D(Collider2D other) + { + // Check if player is interacting + if (other.CompareTag("Player") && _hasBeenRevealed && !_isCollected) + { + HandleInteraction(); + } + } + + private void HandleInteraction() + { + if (isBoosterPack) + { + CollectBoosterPack(); + } + else if (isExit) + { + ActivateExit(); + } + } + + private void CollectBoosterPack() + { + _isCollected = true; + + Logging.Debug($"[RevealableObject] Booster pack collected: {gameObject.name}"); + + // Notify controller + if (TrashMazeController.Instance != null) + { + TrashMazeController.Instance.OnBoosterPackCollected(); + } + + // Destroy object + Destroy(gameObject); + } + + private void ActivateExit() + { + Logging.Debug($"[RevealableObject] Exit activated: {gameObject.name}"); + + // Notify controller + if (TrashMazeController.Instance != null) + { + TrashMazeController.Instance.OnExitReached(); + } + } + + private void OnDestroy() + { + // Stop Binary mode coroutine + if (_binaryRevealCoroutine != null) + { + StopCoroutine(_binaryRevealCoroutine); + } + + // Unsubscribe from Progressive mode movement events + if (revealMode == RevealMode.Progressive && PulverController.Instance != null) + { + PulverController.Instance.OnMovementStarted -= OnPlayerMovementStarted; + PulverController.Instance.OnMovementStopped -= OnPlayerMovementStopped; + } + + // Stop Progressive mode stamping coroutine + if (_stampCoroutine != null) + { + StopCoroutine(_stampCoroutine); + } + + // Clean up progressive reveal resources + if (_revealStampTexture != null) + { + _revealStampTexture.Release(); + Destroy(_revealStampTexture); + } + + + // Clean up instance material + if (_instanceMaterial != null) + { + Destroy(_instanceMaterial); + } + } + + /// + /// Check if object is currently visible to player + /// + public bool IsVisible() + { + float distance = Vector2.Distance(transform.position, PulverController.PlayerPosition); + return distance < PulverController.VisionRadius; + } + + /// + /// Check if object has been revealed at any point + /// + public bool HasBeenRevealed() + { + return _hasBeenRevealed; + } + + /// + /// Force reveal the object (for debugging or special cases) + /// + public void ForceReveal() + { + _hasBeenRevealed = true; + if (_instanceMaterial != null) + { + _instanceMaterial.SetFloat(IsRevealedID, 1f); + } + } + } +} + diff --git a/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs.meta b/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs.meta new file mode 100644 index 00000000..3172d8cd --- /dev/null +++ b/Assets/Scripts/Minigames/TrashMaze/Objects/RevealableObject.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Scripts/Movement/FollowerController.cs b/Assets/Scripts/Movement/FollowerController.cs index 4d3710ba..22412813 100644 --- a/Assets/Scripts/Movement/FollowerController.cs +++ b/Assets/Scripts/Movement/FollowerController.cs @@ -38,7 +38,7 @@ public class FollowerController : ManagedBehaviour public float manualMoveSmooth = 8f; // Settings reference - private IPlayerFollowerSettings _settings; + private IFollowerSettings _settings; private IInteractionSettings _interactionSettings; private GameObject _playerRef; @@ -123,7 +123,8 @@ public class FollowerController : ManagedBehaviour } // Initialize settings references - _settings = GameManager.GetSettingsObject(); + var configs = GameManager.GetSettingsObject(); + _settings = configs.FollowerMovement; _interactionSettings = GameManager.GetSettingsObject(); } @@ -295,7 +296,7 @@ public class FollowerController : ManagedBehaviour moveDir = _playerAIPath.velocity.normalized; _lastMoveDir = moveDir; } - else if (_playerTouchController != null && _playerTouchController.isHolding && _playerTouchController.LastDirectMoveDir.sqrMagnitude > 0.01f) + else if (_playerTouchController != null && _playerTouchController.IsHolding && _playerTouchController.LastDirectMoveDir.sqrMagnitude > 0.01f) { moveDir = _playerTouchController.LastDirectMoveDir; _lastMoveDir = moveDir; diff --git a/Assets/Scripts/Utils/Measurements.cs b/Assets/Scripts/Utils/Measurements.cs new file mode 100644 index 00000000..24c0c5e9 --- /dev/null +++ b/Assets/Scripts/Utils/Measurements.cs @@ -0,0 +1,148 @@ +using UnityEditor; +using UnityEngine; + +namespace Utils +{ + public class Measurements : MonoBehaviour + { + public enum MeasurementUnit + { + UnityUnits, + Centimeters, + Meters, + Kilometers, + Inches, + Feet, + Yards, + Miles + } + + public enum MeasurementSource + { + Collider, + Renderer, + Mesh + } + + public MeasurementUnit measurementUnit = MeasurementUnit.Feet; + public MeasurementSource measurementSource = MeasurementSource.Collider; + public GameObject distanceObject; + + internal Vector3 Dimensions; + internal float CenterToCenter; + internal float EdgeToEdge; + + void Update() + { + CalculateDimensions(); + if (distanceObject != null) + { + CalculateDistances(); + } + } + + void CalculateDimensions() + { + Bounds bounds = new Bounds(); + + switch (measurementSource) + { + case MeasurementSource.Collider: + Collider objectCollider = GetComponent(); + if (objectCollider != null) bounds = objectCollider.bounds; + break; + case MeasurementSource.Renderer: + Renderer objectRenderer = GetComponent(); + if (objectRenderer != null) bounds = objectRenderer.bounds; + break; + case MeasurementSource.Mesh: + MeshFilter meshFilter = GetComponent(); + if (meshFilter != null && meshFilter.mesh != null) bounds = meshFilter.mesh.bounds; + break; + } + + Dimensions = ConvertToSelectedUnit(bounds.size); + } + + void CalculateDistances() + { + Vector3 thisPosition = transform.position; + Vector3 otherPosition = distanceObject.transform.position; + + CenterToCenter = ConvertToSelectedUnit(Vector3.Distance(thisPosition, otherPosition)); + + Bounds thisBounds = GetComponent().bounds; + Bounds otherBounds = distanceObject.GetComponent().bounds; + + Vector3 closestPoint1 = thisBounds.ClosestPoint(otherPosition); + Vector3 closestPoint2 = otherBounds.ClosestPoint(thisPosition); + + EdgeToEdge = ConvertToSelectedUnit(Vector3.Distance(closestPoint1, closestPoint2)); + } + + float ConvertToSelectedUnit(float unityUnits) + { + switch (measurementUnit) + { + case MeasurementUnit.Centimeters: + return unityUnits * 100f; + case MeasurementUnit.Meters: + return unityUnits; + case MeasurementUnit.Kilometers: + return unityUnits / 1000f; + case MeasurementUnit.Inches: + return unityUnits * 39.3701f; + case MeasurementUnit.Feet: + return unityUnits * 3.28084f; + case MeasurementUnit.Yards: + return unityUnits * 1.09361f; + case MeasurementUnit.Miles: + return unityUnits * 0.000621371f; + default: + return unityUnits; + } + } + + Vector3 ConvertToSelectedUnit(Vector3 unityUnits) + { + return new Vector3( + ConvertToSelectedUnit(unityUnits.x), + ConvertToSelectedUnit(unityUnits.y), + ConvertToSelectedUnit(unityUnits.z) + ); + } + } + + #if UNITY_EDITOR + [CustomEditor(typeof(Measurements))] + public class MeasurementsEditor : Editor + { + public override void OnInspectorGUI() + { + DrawDefaultInspector(); + + Measurements measurements = (Measurements)target; + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Dimensions", EditorStyles.boldLabel); + EditorGUILayout.LabelField($"Width\t\t\t{measurements.Dimensions.x:F6} {measurements.measurementUnit}", new GUIStyle(EditorStyles.label) { richText = true }); + EditorGUILayout.LabelField($"Height\t\t\t{measurements.Dimensions.y:F6} {measurements.measurementUnit}", new GUIStyle(EditorStyles.label) { richText = true }); + EditorGUILayout.LabelField($"Depth\t\t\t{measurements.Dimensions.z:F6} {measurements.measurementUnit}", new GUIStyle(EditorStyles.label) { richText = true }); + + if (measurements.distanceObject != null) + { + EditorGUILayout.Space(); + EditorGUILayout.LabelField($"Distance to {measurements.distanceObject.name}", EditorStyles.boldLabel); + EditorGUILayout.LabelField($"Center to Center\t\t{measurements.CenterToCenter:F6} {measurements.measurementUnit}", new GUIStyle(EditorStyles.label) { richText = true }); + EditorGUILayout.LabelField($"Edge to Edge\t\t{measurements.EdgeToEdge:F6} {measurements.measurementUnit}", new GUIStyle(EditorStyles.label) { richText = true }); + } + + // This will update the inspector view every frame + if (Application.isPlaying) + { + Repaint(); + } + } + } + #endif +} \ No newline at end of file diff --git a/Assets/Scripts/Utils/Measurements.cs.meta b/Assets/Scripts/Utils/Measurements.cs.meta new file mode 100644 index 00000000..3a47e348 --- /dev/null +++ b/Assets/Scripts/Utils/Measurements.cs.meta @@ -0,0 +1,18 @@ +fileFormatVersion: 2 +guid: efe9f7f2c8df3c0408ff67a95354f616 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: +AssetOrigin: + serializedVersion: 1 + productId: 294420 + packageName: MeasureMaster + packageVersion: 1.0 + assetPath: Assets/MeasureMaster/Scripts/Measurements.cs + uploadId: 691048 diff --git a/Assets/Settings/PlayerFollowerSettings.asset b/Assets/Settings/PlayerFollowerSettings.asset index 3c3440fc..da46b8da 100644 --- a/Assets/Settings/PlayerFollowerSettings.asset +++ b/Assets/Settings/PlayerFollowerSettings.asset @@ -12,16 +12,24 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 32cd6d14d9304d5ba0fd590da1346654, type: 3} m_Name: PlayerFollowerSettings m_EditorClassIdentifier: - moveSpeed: 15 - moveAcceleration: 10000 - stopDistance: 2 - useRigidbody: 1 - defaultHoldMovementMode: 1 - followDistance: 5 - manualMoveSmooth: 2 - thresholdFar: 10 - thresholdNear: 7 - stopThreshold: 0.5 - followUpdateInterval: 0.1 - followerSpeedMultiplier: 1.2 - heldIconDisplayHeight: 2 + defaultPlayerMovement: + moveSpeed: 15 + maxAcceleration: 10000 + stopDistance: 0.1 + useRigidbody: 0 + defaultHoldMovementMode: 1 + trashMazeMovement: + moveSpeed: 15 + maxAcceleration: 10000 + stopDistance: 0.1 + useRigidbody: 0 + defaultHoldMovementMode: 1 + followerMovement: + followDistance: 1.5 + manualMoveSmooth: 8 + thresholdFar: 2.5 + thresholdNear: 0.5 + stopThreshold: 0.1 + followUpdateInterval: 0.1 + followerSpeedMultiplier: 1.2 + heldIconDisplayHeight: 2 diff --git a/Assets/Shaders.meta b/Assets/Shaders.meta new file mode 100644 index 00000000..7a1a1b14 --- /dev/null +++ b/Assets/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3d826fecc684bae4f94e7928c9c95d83 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Shaders/TrashMaze.meta b/Assets/Shaders/TrashMaze.meta new file mode 100644 index 00000000..efd96824 --- /dev/null +++ b/Assets/Shaders/TrashMaze.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Shaders/TrashMaze/BackgroundVisibility.shader b/Assets/Shaders/TrashMaze/BackgroundVisibility.shader new file mode 100644 index 00000000..1cdc90c1 --- /dev/null +++ b/Assets/Shaders/TrashMaze/BackgroundVisibility.shader @@ -0,0 +1,82 @@ +Shader "TrashMaze/BackgroundVisibility" +{ + Properties + { + _LitTex ("Lit Texture (Color)", 2D) = "white" {} + _UnlitTex ("Unlit Texture (Dark)", 2D) = "white" {} + _TransitionSoftness ("Transition Softness", Range(0, 2)) = 0.5 + } + + SubShader + { + Tags + { + "Queue"="Background" + "RenderType"="Opaque" + } + + LOD 100 + + Pass + { + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float2 uv : TEXCOORD0; + float3 positionWS : TEXCOORD1; + float4 positionCS : SV_POSITION; + }; + + TEXTURE2D(_LitTex); + SAMPLER(sampler_LitTex); + TEXTURE2D(_UnlitTex); + SAMPLER(sampler_UnlitTex); + float4 _LitTex_ST; + float _TransitionSoftness; + + // Global properties set by PulverController + float3 _PlayerWorldPos; + float _VisionRadius; + + Varyings vert(Attributes input) + { + Varyings output; + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + output.positionCS = vertexInput.positionCS; + output.positionWS = vertexInput.positionWS; + output.uv = TRANSFORM_TEX(input.uv, _LitTex); + return output; + } + + half4 frag(Varyings input) : SV_Target + { + // Calculate distance from pixel to player + float dist = distance(input.positionWS.xy, _PlayerWorldPos.xy); + + // Create smooth transition between lit and unlit + float t = smoothstep(_VisionRadius - _TransitionSoftness, _VisionRadius, dist); + + // Sample both textures + half4 litColor = SAMPLE_TEXTURE2D(_LitTex, sampler_LitTex, input.uv); + half4 unlitColor = SAMPLE_TEXTURE2D(_UnlitTex, sampler_UnlitTex, input.uv); + + // Blend based on distance + return lerp(litColor, unlitColor, t); + } + ENDHLSL + } + } + + FallBack "Diffuse" +} + diff --git a/Assets/Shaders/TrashMaze/BackgroundVisibility.shader.meta b/Assets/Shaders/TrashMaze/BackgroundVisibility.shader.meta new file mode 100644 index 00000000..f6a464b5 --- /dev/null +++ b/Assets/Shaders/TrashMaze/BackgroundVisibility.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Shaders/TrashMaze/ObjectVisibility.shader b/Assets/Shaders/TrashMaze/ObjectVisibility.shader new file mode 100644 index 00000000..9065ac76 --- /dev/null +++ b/Assets/Shaders/TrashMaze/ObjectVisibility.shader @@ -0,0 +1,91 @@ +Shader "TrashMaze/ObjectVisibility" +{ + Properties + { + _MainTex ("Normal Texture (Color)", 2D) = "white" {} + _OutlineTex ("Outline Texture (White)", 2D) = "white" {} + [PerRendererData] _IsRevealed ("Is Revealed", Float) = 0 + [PerRendererData] _IsInVision ("Is In Vision", Float) = 0 + } + + SubShader + { + Tags + { + "Queue"="Transparent" + "RenderType"="Transparent" + "IgnoreProjector"="True" + } + + Blend SrcAlpha OneMinusSrcAlpha + ZWrite Off + Cull Off + + Pass + { + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float2 uv : TEXCOORD0; + float4 positionCS : SV_POSITION; + }; + + TEXTURE2D(_MainTex); + SAMPLER(sampler_MainTex); + TEXTURE2D(_OutlineTex); + SAMPLER(sampler_OutlineTex); + float4 _MainTex_ST; + + // Per-instance properties (set by RevealableObject component) + float _IsRevealed; + float _IsInVision; + + Varyings vert(Attributes input) + { + Varyings output; + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + output.positionCS = vertexInput.positionCS; + output.uv = TRANSFORM_TEX(input.uv, _MainTex); + return output; + } + + half4 frag(Varyings input) : SV_Target + { + // Three-state logic: + // 1. In vision radius -> show normal texture (color) + // 2. Revealed but outside vision -> show outline texture (white) + // 3. Never revealed -> transparent (hidden) + + if (_IsInVision > 0.5) + { + // Inside vision radius - show color + return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv); + } + else if (_IsRevealed > 0.5) + { + // Revealed but outside vision - show outline + return SAMPLE_TEXTURE2D(_OutlineTex, sampler_OutlineTex, input.uv); + } + else + { + // Never revealed - transparent (hidden) + return half4(0, 0, 0, 0); + } + } + ENDHLSL + } + } + + FallBack "Transparent/Diffuse" +} + diff --git a/Assets/Shaders/TrashMaze/ObjectVisibility.shader.meta b/Assets/Shaders/TrashMaze/ObjectVisibility.shader.meta new file mode 100644 index 00000000..6f04f0dc --- /dev/null +++ b/Assets/Shaders/TrashMaze/ObjectVisibility.shader.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: + diff --git a/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader b/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader new file mode 100644 index 00000000..91c2dd08 --- /dev/null +++ b/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader @@ -0,0 +1,106 @@ +Shader "TrashMaze/ObjectVisibilityProgressive" +{ + Properties + { + _MainTex ("Normal Texture (Color)", 2D) = "white" {} + _OutlineTex ("Outline Texture (White)", 2D) = "white" {} + _RevealMask ("Reveal Mask", 2D) = "black" {} + [PerRendererData] _IsInVision ("Is In Vision", Float) = 0 + } + + SubShader + { + Tags + { + "Queue"="Transparent" + "RenderType"="Transparent" + "IgnoreProjector"="True" + } + + Blend SrcAlpha OneMinusSrcAlpha + ZWrite Off + Cull Off + + Pass + { + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float2 uv : TEXCOORD0; + float4 positionCS : SV_POSITION; + float3 positionWS : TEXCOORD1; + }; + + TEXTURE2D(_MainTex); + SAMPLER(sampler_MainTex); + TEXTURE2D(_OutlineTex); + SAMPLER(sampler_OutlineTex); + TEXTURE2D(_RevealMask); + SAMPLER(sampler_RevealMask); + float4 _MainTex_ST; + + // Global shader properties (set by PulverController) + float2 _PlayerWorldPos; + float _VisionRadius; + + Varyings vert(Attributes input) + { + Varyings output; + VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz); + output.positionCS = vertexInput.positionCS; + output.positionWS = vertexInput.positionWS; + output.uv = TRANSFORM_TEX(input.uv, _MainTex); + return output; + } + + half4 frag(Varyings input) : SV_Target + { + // Sample reveal mask (0 = not revealed, 1 = revealed) + float revealAmount = SAMPLE_TEXTURE2D(_RevealMask, sampler_RevealMask, input.uv).r; + + // Binary decision: is this pixel revealed? + bool isRevealed = revealAmount > 0.5; + + // If pixel was never revealed, hide it completely + if (!isRevealed) + { + return half4(0, 0, 0, 0); + } + + // Calculate per-pixel distance to player in world space + float2 pixelWorldPos = input.positionWS.xy; + float distanceToPlayer = distance(pixelWorldPos, _PlayerWorldPos); + + // Three-state logic per pixel: + // 1. Never revealed -> hide (handled above) + // 2. Revealed + currently in player vision radius -> show color + // 3. Revealed + outside player vision radius -> show outline + + if (distanceToPlayer < _VisionRadius) + { + // Pixel is revealed AND currently in vision - show color + return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv); + } + else + { + // Pixel is revealed but NOT currently in vision - show outline + return SAMPLE_TEXTURE2D(_OutlineTex, sampler_OutlineTex, input.uv); + } + } + ENDHLSL + } + } + + FallBack "Transparent/Diffuse" +} + diff --git a/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader.meta b/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader.meta new file mode 100644 index 00000000..888bdade --- /dev/null +++ b/Assets/Shaders/TrashMaze/ObjectVisibilityProgressive.shader.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 732fa975ac924d89bb0078279d2cdb0b +timeCreated: 1765358086 \ No newline at end of file diff --git a/Assets/Shaders/TrashMaze/RevealStamp.shader b/Assets/Shaders/TrashMaze/RevealStamp.shader new file mode 100644 index 00000000..4c2506c1 --- /dev/null +++ b/Assets/Shaders/TrashMaze/RevealStamp.shader @@ -0,0 +1,64 @@ +Shader "Hidden/TrashMaze/RevealStamp" +{ + Properties + { + _StampPos ("Stamp Position", Vector) = (0.5, 0.5, 0, 0) + _StampRadius ("Stamp Radius", Float) = 0.2 + } + + SubShader + { + Tags { "Queue"="Overlay" "RenderType"="Opaque" } + + // Additive blend to accumulate stamps + Blend One One + ZTest Always + ZWrite Off + Cull Off + + Pass + { + HLSLPROGRAM + #pragma vertex vert + #pragma fragment frag + #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" + + struct Attributes + { + float4 positionOS : POSITION; + float2 uv : TEXCOORD0; + }; + + struct Varyings + { + float4 positionCS : SV_POSITION; + float2 uv : TEXCOORD0; + }; + + float4 _StampPos; + float _StampRadius; + + Varyings vert(Attributes input) + { + Varyings output; + output.positionCS = TransformObjectToHClip(input.positionOS.xyz); + output.uv = input.uv; + return output; + } + + half4 frag(Varyings input) : SV_Target + { + // Calculate distance from stamp center + float dist = distance(input.uv, _StampPos.xy); + + // Binary circle: 1.0 inside radius, 0.0 outside + float alpha = dist < _StampRadius ? 1.0 : 0.0; + + // Return white with calculated alpha (additive blend accumulates) + return half4(alpha, alpha, alpha, alpha); + } + ENDHLSL + } + } +} + diff --git a/Assets/Shaders/TrashMaze/RevealStamp.shader.meta b/Assets/Shaders/TrashMaze/RevealStamp.shader.meta new file mode 100644 index 00000000..6edb3cd6 --- /dev/null +++ b/Assets/Shaders/TrashMaze/RevealStamp.shader.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 33afb80a55e64e53b8552498ad61acfa +timeCreated: 1765358067 \ No newline at end of file