From d8b0583fac4429a29fa56f012a035350a2d1dff0 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 13 Dec 2025 00:01:29 +0100 Subject: [PATCH] Add Hunter NPC and Teleporter features with associated prefabs and effects --- .gitignore | 2 + Assets/Prefabs/HunterNpc.prefab | 162 ++++++++++++++++++ ...oted.prefab.meta => HunterNpc.prefab.meta} | 2 +- .../{Light footed.prefab => PowerUp.prefab} | 48 +++--- ... Boost.prefab.meta => PowerUp.prefab.meta} | 2 +- ...eed Boost.prefab => Teleporter pad.prefab} | 52 +++--- Assets/Prefabs/Teleporter pad.prefab.meta | 7 + Assets/Prefabs/Tile.prefab | 1 + Assets/Scenes/SampleScene.unity | 5 +- Assets/Scripts/Core/Domain/GameSession.cs | 106 +++++++++--- Assets/Scripts/Core/Domain/PowerUpType.cs | 2 + .../Domain/Status/Effects/EffectColors.cs | 2 + .../Core/Domain/Status/Effects/HoverEffect.cs | 32 ++++ .../Domain/Status/Effects/HoverEffect.cs.meta | 2 + .../Core/Domain/Status/PlayerCapabilities.cs | 6 +- Assets/Scripts/Core/Domain/Tile.cs | 25 ++- Assets/Scripts/Core/Domain/TileType.cs | 8 + Assets/Scripts/Core/Domain/TileType.cs.meta | 2 + .../Infrastructure/Unity/CameraController.cs | 28 ++- .../Infrastructure/Unity/GameBootstrap.cs | 115 ++++++++----- .../Unity/HunterNpcController.cs | 59 +++++++ .../Unity/HunterNpcController.cs.meta | 2 + .../Infrastructure/Unity/LevelGenerator.cs | 102 +++++++---- .../Infrastructure/Unity/PlayerController.cs | 41 +++-- .../Unity/PowerUpViewAdapter.cs | 24 ++- .../Infrastructure/Unity/TeleporterAdapter.cs | 60 +++++++ .../Unity/TeleporterAdapter.cs.meta | 2 + .../Infrastructure/Unity/ThemeManager.cs | 41 +++++ .../Infrastructure/Unity/ThemeManager.cs.meta | 2 + .../Scripts/Infrastructure/Unity/TilePool.cs | 36 ++++ .../Infrastructure/Unity/TilePool.cs.meta | 2 + .../Infrastructure/Unity/TileViewAdapter.cs | 60 +++++-- 32 files changed, 823 insertions(+), 217 deletions(-) create mode 100644 Assets/Prefabs/HunterNpc.prefab rename Assets/Prefabs/{Light footed.prefab.meta => HunterNpc.prefab.meta} (74%) rename Assets/Prefabs/{Light footed.prefab => PowerUp.prefab} (77%) rename Assets/Prefabs/{Speed Boost.prefab.meta => PowerUp.prefab.meta} (74%) rename Assets/Prefabs/{Speed Boost.prefab => Teleporter pad.prefab} (71%) create mode 100644 Assets/Prefabs/Teleporter pad.prefab.meta create mode 100644 Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs create mode 100644 Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs.meta create mode 100644 Assets/Scripts/Core/Domain/TileType.cs create mode 100644 Assets/Scripts/Core/Domain/TileType.cs.meta create mode 100644 Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs create mode 100644 Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs.meta create mode 100644 Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs create mode 100644 Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs.meta create mode 100644 Assets/Scripts/Infrastructure/Unity/ThemeManager.cs create mode 100644 Assets/Scripts/Infrastructure/Unity/ThemeManager.cs.meta create mode 100644 Assets/Scripts/Infrastructure/Unity/TilePool.cs create mode 100644 Assets/Scripts/Infrastructure/Unity/TilePool.cs.meta diff --git a/.gitignore b/.gitignore index 1b7935c..0324846 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /[Ll]ogs/ /[Uu]ser[Ss]ettings/ +.idea/ + # MemoryCaptures can get excessive in size. # They also could contain extremely sensitive data /[Mm]emoryCaptures/ diff --git a/Assets/Prefabs/HunterNpc.prefab b/Assets/Prefabs/HunterNpc.prefab new file mode 100644 index 0000000..439737e --- /dev/null +++ b/Assets/Prefabs/HunterNpc.prefab @@ -0,0 +1,162 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6956164580015912564 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2158095965342707446} + - component: {fileID: 522917209739125732} + - component: {fileID: 54217236340769722} + - component: {fileID: 121674684165614314} + - component: {fileID: 283610869708690177} + - component: {fileID: 4496988857626767934} + m_Layer: 0 + m_Name: HunterNpc + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2158095965342707446 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &522917209739125732 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &54217236340769722 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RayTracingAccelStructBuildFlagsOverride: 0 + m_RayTracingAccelStructBuildFlags: 1 + m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: a64e346b67480caceae337f37543db27, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_MaskInteraction: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!136 &121674684165614314 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 + m_IsTrigger: 0 + m_ProvidesContacts: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Height: 2 + m_Direction: 1 + m_Center: {x: 0, y: 0, z: 0} +--- !u!54 &283610869708690177 +Rigidbody: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + serializedVersion: 5 + m_Mass: 1 + m_LinearDamping: 0 + m_AngularDamping: 0.05 + m_CenterOfMass: {x: 0, y: 0, z: 0} + m_InertiaTensor: {x: 1, y: 1, z: 1} + m_InertiaRotation: {x: 0, y: 0, z: 0, w: 1} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ImplicitCom: 1 + m_ImplicitTensor: 1 + m_UseGravity: 1 + m_IsKinematic: 0 + m_Interpolate: 0 + m_Constraints: 0 + m_CollisionDetection: 0 +--- !u!114 &4496988857626767934 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6956164580015912564} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d87e12fdb2c23b238910064019f2ad59, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.HunterNpcController + moveSpeed: 7 + tileLayer: + serializedVersion: 2 + m_Bits: 8 + rb: {fileID: 283610869708690177} diff --git a/Assets/Prefabs/Light footed.prefab.meta b/Assets/Prefabs/HunterNpc.prefab.meta similarity index 74% rename from Assets/Prefabs/Light footed.prefab.meta rename to Assets/Prefabs/HunterNpc.prefab.meta index 9355ff9..a3e4c3c 100644 --- a/Assets/Prefabs/Light footed.prefab.meta +++ b/Assets/Prefabs/HunterNpc.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4ce47d492ed71cb5b9390762eab7a749 +guid: ab4e193839fef9a2189f27360914c044 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Prefabs/Light footed.prefab b/Assets/Prefabs/PowerUp.prefab similarity index 77% rename from Assets/Prefabs/Light footed.prefab rename to Assets/Prefabs/PowerUp.prefab index b504a4c..81e243b 100644 --- a/Assets/Prefabs/Light footed.prefab +++ b/Assets/Prefabs/PowerUp.prefab @@ -1,6 +1,6 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: ---- !u!1 &7219310182397398916 +--- !u!1 &6659026961635312632 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -8,25 +8,25 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 4070461624121405295} - - component: {fileID: 930667067315408562} - - component: {fileID: 2173926958931993968} - - component: {fileID: 5033597944775254142} - - component: {fileID: 2639250404559520579} + - component: {fileID: 663514258571720717} + - component: {fileID: 8432450954479424108} + - component: {fileID: 7819803615826365176} + - component: {fileID: 6615024751381665233} + - component: {fileID: 7381336953128067686} m_Layer: 0 - m_Name: Light footed + m_Name: PowerUp m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!4 &4070461624121405295 +--- !u!4 &663514258571720717 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 6659026961635312632} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} @@ -35,21 +35,21 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!33 &930667067315408562 +--- !u!33 &8432450954479424108 MeshFilter: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} - m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} ---- !u!23 &2173926958931993968 + m_GameObject: {fileID: 6659026961635312632} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &7819803615826365176 MeshRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 6659026961635312632} m_Enabled: 1 m_CastShadows: 1 m_ReceiveShadows: 1 @@ -92,13 +92,13 @@ MeshRenderer: m_SortingOrder: 0 m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!136 &5033597944775254142 -CapsuleCollider: +--- !u!65 &6615024751381665233 +BoxCollider: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 6659026961635312632} m_Material: {fileID: 0} m_IncludeLayers: serializedVersion: 2 @@ -110,24 +110,22 @@ CapsuleCollider: m_IsTrigger: 1 m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 - m_Radius: 0.5 - m_Height: 2 - m_Direction: 1 + serializedVersion: 3 + m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} ---- !u!114 &2639250404559520579 +--- !u!114 &7381336953128067686 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 6659026961635312632} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5886e2d0e6414ce98bf68f8e5ac887fc, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.PowerUpViewAdapter type: 0 - duration: 10 + duration: 5 pickupVfx: {fileID: 0} - meshRenderer: {fileID: 2173926958931993968} + meshRenderer: {fileID: 7819803615826365176} diff --git a/Assets/Prefabs/Speed Boost.prefab.meta b/Assets/Prefabs/PowerUp.prefab.meta similarity index 74% rename from Assets/Prefabs/Speed Boost.prefab.meta rename to Assets/Prefabs/PowerUp.prefab.meta index 634b6c4..aa83f13 100644 --- a/Assets/Prefabs/Speed Boost.prefab.meta +++ b/Assets/Prefabs/PowerUp.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 32d69de3e08c76e63a4b5086b87b12df +guid: 8b540be4548e610709c2f7eccf8bf9c6 PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Prefabs/Speed Boost.prefab b/Assets/Prefabs/Teleporter pad.prefab similarity index 71% rename from Assets/Prefabs/Speed Boost.prefab rename to Assets/Prefabs/Teleporter pad.prefab index ef1c7e3..0788555 100644 --- a/Assets/Prefabs/Speed Boost.prefab +++ b/Assets/Prefabs/Teleporter pad.prefab @@ -1,6 +1,6 @@ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: ---- !u!1 &7219310182397398916 +--- !u!1 &2198048708000581807 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -8,48 +8,48 @@ GameObject: m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - - component: {fileID: 4070461624121405295} - - component: {fileID: 930667067315408562} - - component: {fileID: 2173926958931993968} - - component: {fileID: 5033597944775254142} - - component: {fileID: 2639250404559520579} + - component: {fileID: 7276685985893193207} + - component: {fileID: 6152181000324460277} + - component: {fileID: 1559715898324095926} + - component: {fileID: 6093691026861389653} + - component: {fileID: 4601941687390792571} m_Layer: 0 - m_Name: Speed Boost + m_Name: Teleporter pad m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!4 &4070461624121405295 +--- !u!4 &7276685985893193207 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 2198048708000581807} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 0.5, y: 0.5, z: 0.5} + m_LocalScale: {x: 1, y: 0.1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!33 &930667067315408562 +--- !u!33 &6152181000324460277 MeshFilter: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} - m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} ---- !u!23 &2173926958931993968 + m_GameObject: {fileID: 2198048708000581807} + m_Mesh: {fileID: 10206, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &1559715898324095926 MeshRenderer: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 2198048708000581807} m_Enabled: 1 m_CastShadows: 1 m_ReceiveShadows: 1 @@ -68,7 +68,7 @@ MeshRenderer: m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: - - {fileID: 2100000, guid: aece89a4ea685f6969a31191b980f70b, type: 2} + - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -92,13 +92,13 @@ MeshRenderer: m_SortingOrder: 0 m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!136 &5033597944775254142 +--- !u!136 &6093691026861389653 CapsuleCollider: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 2198048708000581807} m_Material: {fileID: 0} m_IncludeLayers: serializedVersion: 2 @@ -111,23 +111,19 @@ CapsuleCollider: m_ProvidesContacts: 0 m_Enabled: 1 serializedVersion: 2 - m_Radius: 0.5 + m_Radius: 0.5000001 m_Height: 2 m_Direction: 1 - m_Center: {x: 0, y: 0, z: 0} ---- !u!114 &2639250404559520579 + m_Center: {x: 0.000000059604645, y: 0, z: -0.00000008940697} +--- !u!114 &4601941687390792571 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 7219310182397398916} + m_GameObject: {fileID: 2198048708000581807} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 5886e2d0e6414ce98bf68f8e5ac887fc, type: 3} + m_Script: {fileID: 11500000, guid: 82fa71545d10e6ee187b00ae7fba2eea, type: 3} m_Name: - m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.PowerUpViewAdapter - type: 1 - duration: 10 - pickupVfx: {fileID: 0} - meshRenderer: {fileID: 2173926958931993968} + m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.TeleporterAdapter diff --git a/Assets/Prefabs/Teleporter pad.prefab.meta b/Assets/Prefabs/Teleporter pad.prefab.meta new file mode 100644 index 0000000..8729b2a --- /dev/null +++ b/Assets/Prefabs/Teleporter pad.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 53f1de555c523511e9aaa1dee06fdf79 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Tile.prefab b/Assets/Prefabs/Tile.prefab index 787afa6..8530362 100644 --- a/Assets/Prefabs/Tile.prefab +++ b/Assets/Prefabs/Tile.prefab @@ -155,6 +155,7 @@ MonoBehaviour: m_EditorClassIdentifier: Assembly-CSharp::Infrastructure.Unity.TileViewAdapter stableColor: {r: 0.08627451, g: 0.12941177, b: 0.24313726, a: 1} warningColor: {r: 1, g: 0.18039216, b: 0.3882353, a: 1} + fragileColor: {r: 0.08627451, g: 0.12941177, b: 0.24313726, a: 0.5} colorSpeed: 2 rb: {fileID: 6985771395868845393} meshRenderer: {fileID: 1516947233778832648} diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 967ff44..459f669 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -633,6 +633,7 @@ MonoBehaviour: soundManager: {fileID: 1341134052} cameraController: {fileID: 1265730430} npcPrefab: {fileID: 6083523108754401876, guid: 4b3d84858334857368bde30df360ae3e, type: 3} + hunterNpcPrefab: {fileID: 4496988857626767934, guid: ab4e193839fef9a2189f27360914c044, type: 3} floorsCount: 3 floorHeightDistance: 15 scoreText: {fileID: 412275999} @@ -640,8 +641,7 @@ MonoBehaviour: gameOverUi: {fileID: 87831902} startScreenUi: {fileID: 1763855010} restartTime: 3 - lightFootedPrefab: {fileID: 2639250404559520579, guid: 4ce47d492ed71cb5b9390762eab7a749, type: 3} - speedBoostPrefab: {fileID: 2639250404559520579, guid: 32d69de3e08c76e63a4b5086b87b12df, type: 3} + powerUpPrefab: {fileID: 7381336953128067686, guid: 8b540be4548e610709c2f7eccf8bf9c6, type: 3} --- !u!4 &453022421 Transform: m_ObjectHideFlags: 0 @@ -672,6 +672,7 @@ MonoBehaviour: tilePrefab: {fileID: 2142595510769725438, guid: a84ad84e96942d35c824885a7c08a844, type: 3} tileBreakVfxPrefab: {fileID: 3395603910160708684, guid: 7c5c893ba21562b2aad6bffb1887f4b1, type: 3} jumpPadPrefab: {fileID: 3258547662887829175, guid: e1d1bd44370c9986ebd4bb7730430a12, type: 3} + teleporterPrefab: {fileID: 4601941687390792571, guid: 53f1de555c523511e9aaa1dee06fdf79, type: 3} gridSizeX: 20 gridSizeY: 20 floorsCount: 3 diff --git a/Assets/Scripts/Core/Domain/GameSession.cs b/Assets/Scripts/Core/Domain/GameSession.cs index 44f867e..91f5ed2 100644 --- a/Assets/Scripts/Core/Domain/GameSession.cs +++ b/Assets/Scripts/Core/Domain/GameSession.cs @@ -9,11 +9,13 @@ namespace Core.Domain private const string HighScoreKey = "HighScore"; private const float NpcSpawnTime = 4f; private const float PowerUpSpawnInterval = 25f; - + public int Score { get; private set; } public int HighScore { get; private set; } public bool IsGameOver { get; private set; } - + + public float TimeDilation { get; private set; } = 1.0f; + public event Action OnScoreChanged; public event Action OnOrbSpawned; public event Action OnOrbReset; @@ -29,14 +31,21 @@ namespace Core.Domain private bool _npcSpawned; private float _powerUpTimer; + private float _timeSlowTimer; + + // Combo System + private float _lastOrbTime; + public int ComboMultiplier { get; private set; } = 1; + public event Action OnComboUpdated; + public GameSession(List tiles, IPersistenceService persistenceService) { _tiles = tiles; _persistenceService = persistenceService; - + Score = 0; IsGameOver = false; - + HighScore = _persistenceService.Load(HighScoreKey, 0); } @@ -45,56 +54,88 @@ namespace Core.Domain _timeSinceStart = 0f; _powerUpTimer = 0f; _npcSpawned = false; - + TimeDilation = 1.0f; + + ComboMultiplier = 1; + _lastOrbTime = -999f; + SpawnNextOrb(); } public void Tick(float deltaTime) { if (IsGameOver) return; - + _timeSinceStart += deltaTime; if (!_npcSpawned && _timeSinceStart >= NpcSpawnTime) { _npcSpawned = true; OnSpawnNpc?.Invoke(); } - + _powerUpTimer += deltaTime; if (_powerUpTimer >= PowerUpSpawnInterval) { _powerUpTimer = 0f; SpawnRandomPowerUp(); } + + if (_timeSlowTimer > 0) + { + _timeSlowTimer -= deltaTime; + if (_timeSlowTimer <= 0) + { + TimeDilation = 1.0f; + } + } + } + + public void ActivateTimeSlow(float duration) + { + _timeSlowTimer = duration; + TimeDilation = 0.3f; // Slow down to 30% } public void OrbCollected() { if (IsGameOver) return; - Score += 10; + // Combo Check (2 second window) + if (_timeSinceStart - _lastOrbTime < 2.0f) + { + ComboMultiplier++; + } + else + { + ComboMultiplier = 1; + } + _lastOrbTime = _timeSinceStart; + + OnComboUpdated?.Invoke(ComboMultiplier); + + Score += 10 * ComboMultiplier; OnScoreChanged?.Invoke(Score); SpawnNextOrb(); } private void SpawnNextOrb() - { - var validTiles = _tiles.FindAll(t => - t.CurrentState == TileState.Stable && + { + var validTiles = _tiles.FindAll(t => + t.CurrentState == TileState.Stable && t.Floor == _playerFloorIndex ); - + if (validTiles.Count == 0) { validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable); } - + if (validTiles.Count == 0) { EndGame(); return; } - + var pick = validTiles[_rng.Next(validTiles.Count)]; OnOrbSpawned?.Invoke(pick.Id); } @@ -102,39 +143,56 @@ namespace Core.Domain public void EndGame() { if (IsGameOver) return; - + IsGameOver = true; - + if (Score > HighScore) { HighScore = Score; _persistenceService.Save(HighScoreKey, HighScore); } - + OnGameOver?.Invoke(); } public void SetPlayerFloor(int floorIndex) { if (_playerFloorIndex == floorIndex) return; - + _playerFloorIndex = floorIndex; if (IsGameOver) return; - + OnOrbReset?.Invoke(); SpawnNextOrb(); } private void SpawnRandomPowerUp() { - var validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable); + var validTiles = _tiles.FindAll(t => + t.CurrentState == TileState.Stable && + t.Floor == _playerFloorIndex + ); + + if (validTiles.Count == 0) + { + validTiles = _tiles.FindAll(t => t.CurrentState == TileState.Stable); + } + if (validTiles.Count == 0) return; - + var tile = validTiles[_rng.Next(validTiles.Count)]; - - var type = _rng.Next(0, 2) == 0 ? PowerUpType.LightFooted : PowerUpType.SpeedBoost; - + + var rand = _rng.Next(0, 4); + var type = PowerUpType.LightFooted; + switch (rand) + { + case 0: type = PowerUpType.LightFooted; break; + case 1: type = PowerUpType.SpeedBoost; break; + case 2: type = PowerUpType.Hover; break; + case 3: type = PowerUpType.TimeSlow; break; + } + OnSpawnPowerUp?.Invoke(type, tile.Id); } } diff --git a/Assets/Scripts/Core/Domain/PowerUpType.cs b/Assets/Scripts/Core/Domain/PowerUpType.cs index 3fb3d08..d697fde 100644 --- a/Assets/Scripts/Core/Domain/PowerUpType.cs +++ b/Assets/Scripts/Core/Domain/PowerUpType.cs @@ -4,5 +4,7 @@ namespace Core.Domain { LightFooted, SpeedBoost, + Hover, + TimeSlow } } \ No newline at end of file diff --git a/Assets/Scripts/Core/Domain/Status/Effects/EffectColors.cs b/Assets/Scripts/Core/Domain/Status/Effects/EffectColors.cs index 74e2ea2..a6b5a36 100644 --- a/Assets/Scripts/Core/Domain/Status/Effects/EffectColors.cs +++ b/Assets/Scripts/Core/Domain/Status/Effects/EffectColors.cs @@ -6,5 +6,7 @@ namespace Core.Domain.Status.Effects { public static readonly Color LightFootedColor = new Color(0.8f, 0.8f, 0.8f); public static readonly Color SpeedBoostColor = new Color(1f, 0.5f, 0f); + public static readonly Color HoverColor = new Color(0.5f, 1f, 0.5f); // Green + public static readonly Color TimeSlowColor = new Color(0.2f, 0.2f, 1f); // Blue } } \ No newline at end of file diff --git a/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs b/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs new file mode 100644 index 0000000..5d0db74 --- /dev/null +++ b/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs @@ -0,0 +1,32 @@ +namespace Core.Domain.Status.Effects +{ + public class HoverEffect : IStatusEffect + { + private float _duration; + + public bool IsExpired => _duration <= 0; + + public HoverEffect(float duration) + { + _duration = duration; + } + + public void Tick(float deltaTime) + { + _duration -= deltaTime; + } + + public void ModifyCapabilities(ref PlayerCapabilities caps) + { + caps.CanHover = true; + } + + public void OnApply() + { + } + + public void OnRemove() + { + } + } +} diff --git a/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs.meta b/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs.meta new file mode 100644 index 0000000..4f5a2bf --- /dev/null +++ b/Assets/Scripts/Core/Domain/Status/Effects/HoverEffect.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a774d5cade7c04beba69252a04501099 \ No newline at end of file diff --git a/Assets/Scripts/Core/Domain/Status/PlayerCapabilities.cs b/Assets/Scripts/Core/Domain/Status/PlayerCapabilities.cs index fce3f09..c423056 100644 --- a/Assets/Scripts/Core/Domain/Status/PlayerCapabilities.cs +++ b/Assets/Scripts/Core/Domain/Status/PlayerCapabilities.cs @@ -4,11 +4,13 @@ namespace Core.Domain.Status { public bool CanTriggerDecay; public float SpeedMultiplier; - + public bool CanHover; + public static PlayerCapabilities Default => new PlayerCapabilities { CanTriggerDecay = true, - SpeedMultiplier = 1f + SpeedMultiplier = 1f, + CanHover = false }; } } diff --git a/Assets/Scripts/Core/Domain/Tile.cs b/Assets/Scripts/Core/Domain/Tile.cs index e46d41d..18cb387 100644 --- a/Assets/Scripts/Core/Domain/Tile.cs +++ b/Assets/Scripts/Core/Domain/Tile.cs @@ -6,21 +6,23 @@ namespace Core.Domain { public string Id { get; } public int Floor { get; } - + public TileType Type { get; } + public TileState CurrentState { get; private set; } private readonly float _warningDuration; private readonly float _fallingDuration; - + private float _stateTimer; - + public event Action OnStateChanged; - public Tile(string id, int floor, float warningDuration, float fallingDuration) + public Tile(string id, int floor, float warningDuration, float fallingDuration, TileType type = TileType.Normal) { Id = id; Floor = floor; - + Type = type; + _warningDuration = warningDuration; _fallingDuration = fallingDuration; CurrentState = TileState.Stable; @@ -30,16 +32,23 @@ namespace Core.Domain { if (CurrentState == TileState.Stable) { - TransitionTo(TileState.Warning); + if (Type == TileType.Fragile) + { + TransitionTo(TileState.Falling); + } + else + { + TransitionTo(TileState.Warning); + } } } public void Tick(float deltaTime) { if (CurrentState == TileState.Stable || CurrentState == TileState.Destroyed) return; - + _stateTimer += deltaTime; - + if (CurrentState == TileState.Warning && _stateTimer >= _warningDuration) { TransitionTo(TileState.Falling); diff --git a/Assets/Scripts/Core/Domain/TileType.cs b/Assets/Scripts/Core/Domain/TileType.cs new file mode 100644 index 0000000..f67197a --- /dev/null +++ b/Assets/Scripts/Core/Domain/TileType.cs @@ -0,0 +1,8 @@ +namespace Core.Domain +{ + public enum TileType + { + Normal, + Fragile + } +} diff --git a/Assets/Scripts/Core/Domain/TileType.cs.meta b/Assets/Scripts/Core/Domain/TileType.cs.meta new file mode 100644 index 0000000..f3a7842 --- /dev/null +++ b/Assets/Scripts/Core/Domain/TileType.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0ac9ceadb8e587481aa83f4f78997207 \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/CameraController.cs b/Assets/Scripts/Infrastructure/Unity/CameraController.cs index 909e1cc..2b46ea3 100644 --- a/Assets/Scripts/Infrastructure/Unity/CameraController.cs +++ b/Assets/Scripts/Infrastructure/Unity/CameraController.cs @@ -9,6 +9,10 @@ namespace Infrastructure.Unity [SerializeField] private Vector3 offset = new(-20, 20, -20); [SerializeField] private float smoothSpeed = 5f; + private Vector3 _originalPos; // This field is declared but not used in the provided LateUpdate logic. + private float _shakeTimer; + private float _shakeMagnitude; + private void LateUpdate() { if (!target) return; @@ -17,14 +21,30 @@ namespace Infrastructure.Unity // so the camera doesn't jitter too much, only tracking Y (falling). // For a dynamic feel, let's track everything loosely. - var desiredPos = target.position + offset; - - transform.position = Vector3.Lerp(transform.position, desiredPos, Time.deltaTime * smoothSpeed); + var targetPos = target.position + offset; + var smoothPos = Vector3.Lerp(transform.position, targetPos, smoothSpeed * Time.deltaTime); + + if (_shakeTimer > 0) + { + _shakeTimer -= Time.deltaTime; + var shakeOffset = Random.insideUnitSphere * _shakeMagnitude; + transform.position = smoothPos + shakeOffset; + } + else + { + transform.position = smoothPos; + } } - + public void SetTarget(Transform newTarget) { target = newTarget; } + + public void Shake(float duration, float magnitude) + { + _shakeTimer = duration; + _shakeMagnitude = magnitude; + } } } \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs b/Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs index 616455f..50a9a99 100644 --- a/Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs +++ b/Assets/Scripts/Infrastructure/Unity/GameBootstrap.cs @@ -10,7 +10,7 @@ namespace Infrastructure.Unity { public class GameBootstrap : MonoBehaviour { - [Header("Infrastructure")] + [Header("Infrastructure")] [SerializeField] private LevelGenerator levelGenerator; [SerializeField] private OrbViewAdapter orbPrefab; [SerializeField] private PlayerController playerPrefab; @@ -18,23 +18,23 @@ namespace Infrastructure.Unity [SerializeField] private SoundManager soundManager; [SerializeField] private CameraController cameraController; [SerializeField] private NpcController npcPrefab; - + [SerializeField] private HunterNpcController hunterNpcPrefab; + [Header("Level Generation")] [SerializeField] private int floorsCount = 3; [SerializeField] private float floorHeightDistance = 15f; - + [Header("Ui")] [SerializeField] private TMP_Text scoreText; [SerializeField] private TMP_Text highScoreText; [SerializeField] private GameObject gameOverUi; [SerializeField] private GameObject startScreenUi; - + [Header("Settings")] [SerializeField] private float restartTime = 3f; - + [Header("Power Ups")] - [SerializeField] private PowerUpViewAdapter lightFootedPrefab; - [SerializeField] private PowerUpViewAdapter speedBoostPrefab; + [SerializeField] private PowerUpViewAdapter powerUpPrefab; private readonly List _allTiles = new(); private readonly Dictionary _tileViews = new(); @@ -59,17 +59,20 @@ namespace Infrastructure.Unity private void Start() { - if (levelGenerator) levelGenerator.Generate(soundManager, _allTiles, _tileViews); - - SpawnDeathPlane(); - SpawnPlayer(); - - if (gameOverUi) gameOverUi.SetActive(false); - if (startScreenUi) startScreenUi.SetActive(true); - _persistenceService = new PlayerPrefsPersistenceAdapter(); _gameSession = new GameSession(_allTiles, _persistenceService); + // Set Theme based on High Score + ThemeManager.CurrentTheme = ThemeManager.GetTheme(_gameSession.HighScore); + + if (levelGenerator) levelGenerator.Generate(soundManager, _allTiles, _tileViews, cameraController); + + SpawnDeathPlane(); + SpawnPlayer(); + + if (gameOverUi) gameOverUi.SetActive(false); + if (startScreenUi) startScreenUi.SetActive(true); + WireEvents(); UpdateScoreUi(_gameSession.Score); } @@ -90,34 +93,39 @@ namespace Infrastructure.Unity { var playerY = _playerInstance.transform.position.y; var currentFloor = Mathf.RoundToInt(-playerY / floorHeightDistance); - + currentFloor = Mathf.Clamp(currentFloor, 0, floorsCount - 1); - + _gameSession.SetPlayerFloor(currentFloor); } var dt = Time.deltaTime; - + if (_isGameRunning) _gameSession.Tick(dt); - + + var dilation = _gameSession.TimeDilation; + + // Hard Mode: Decay faster as score increases + var decayMultiplier = 1.0f + (_gameSession.Score / 500f); + for (var i = _allTiles.Count - 1; i >= 0; i--) { - _allTiles[i].Tick(dt); + _allTiles[i].Tick(dt * dilation * decayMultiplier); } } private void SpawnVisualOrb(string tileId) { if (_currentOrbInstance) Destroy(_currentOrbInstance); - + if (!_tileViews.TryGetValue(tileId, out var tileView)) return; if (!tileView) return; - + var spawnPos = tileView.transform.position + Vector3.up; var orb = Instantiate(orbPrefab, spawnPos, Quaternion.identity); orb.OnCollected += () => _gameSession.OrbCollected(); - + _currentOrbInstance = orb.gameObject; } @@ -132,22 +140,25 @@ namespace Infrastructure.Unity private void UpdateScoreUi(int newScore) { if (!scoreText) return; - - scoreText.text = $"Data: {newScore}"; - - if (highScoreText) highScoreText.text = $"BEST: {_gameSession.HighScore}"; + + var combo = _gameSession?.ComboMultiplier ?? 1; + var comboText = combo > 1 ? $" (x{combo})" : ""; + + scoreText.text = $"Data: {newScore}{comboText}"; + + if (highScoreText && _gameSession != null) highScoreText.text = $"BEST: {_gameSession.HighScore}"; } - + private void SpawnPlayer() { var spawnPos = new Vector3(0f, 5f, 0f); _playerInstance = Instantiate(playerPrefab, spawnPos, Quaternion.identity); - + _playerInstance.enabled = false; - + _playerInstance.Rigidbody.isKinematic = true; - + if (cameraController) { cameraController.SetTarget(_playerInstance.transform); @@ -162,7 +173,7 @@ namespace Infrastructure.Unity var pos = new Vector3(levelGenerator.GridSizeX / 2f, lowestY, levelGenerator.GridSizeY / 2f); var plane = Instantiate(deathPlanePrefab, pos, Quaternion.identity); plane.transform.localScale = new Vector3(levelGenerator.GridSizeX * 2f, 1f, levelGenerator.GridSizeY * 2f); - + plane.OnPlayerFell += () => _gameSession.EndGame(); } @@ -177,7 +188,7 @@ namespace Infrastructure.Unity private IEnumerator RestartRoutine() { yield return new WaitForSeconds(restartTime); - + SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex); } @@ -191,7 +202,7 @@ namespace Infrastructure.Unity _gameSession.OnSpawnPowerUp += SpawnPowerUp; if (!soundManager) return; - + _gameSession.OnScoreChanged += _ => soundManager.PlayScore(); _gameSession.OnGameOver += () => { @@ -202,18 +213,26 @@ namespace Infrastructure.Unity private void SpawnNpc() { - if (!npcPrefab) return; - var spawnPos = new Vector3(levelGenerator.GridSizeX / 2f, 7f, levelGenerator.GridSizeY / 2f); - Instantiate(npcPrefab, spawnPos, Quaternion.identity); - + + // 30% chance for Hunter if player available + if (_playerInstance && hunterNpcPrefab && Random.value < 0.3f) + { + var hunter = Instantiate(hunterNpcPrefab, spawnPos, Quaternion.identity); + hunter.Initialize(_playerInstance); + } + else if (npcPrefab) + { + Instantiate(npcPrefab, spawnPos, Quaternion.identity); + } + soundManager.PlayNpcSpawn(); } private void StartGameSequence() { _isGameRunning = true; - + if (startScreenUi) startScreenUi.SetActive(false); if (soundManager) @@ -227,7 +246,7 @@ namespace Infrastructure.Unity _playerInstance.enabled = true; _playerInstance.Rigidbody.isKinematic = false; } - + _gameSession.StartGame(); } @@ -237,15 +256,17 @@ namespace Infrastructure.Unity if (!tileView) return; var spawnPos = tileView.transform.position + Vector3.up * 0.5f; - - var prefabToSpawn = type == PowerUpType.LightFooted - ? lightFootedPrefab - : speedBoostPrefab; - - if (!prefabToSpawn) return; + var instance = Instantiate(powerUpPrefab, spawnPos, Quaternion.identity); - var instance = Instantiate(prefabToSpawn, spawnPos, Quaternion.identity); instance.Configure(type); + + instance.OnCollected += (t) => + { + if (t == PowerUpType.TimeSlow) + { + _gameSession.ActivateTimeSlow(10f); + } + }; } } } \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs b/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs new file mode 100644 index 0000000..0e4c03c --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs @@ -0,0 +1,59 @@ +using UnityEngine; +using KBCore.Refs; + +namespace Infrastructure.Unity +{ + [RequireComponent(typeof(Rigidbody))] + public class HunterNpcController : MonoBehaviour + { + [SerializeField] private float moveSpeed = 7f; + [SerializeField] private LayerMask tileLayer; + + [Self] [SerializeField] private Rigidbody rb; + + private PlayerController _target; + + public void Initialize(PlayerController target) + { + _target = target; + } + + private void FixedUpdate() + { + if (_target == null) return; + + if (IsGrounded()) + { + var direction = (_target.transform.position - transform.position).normalized; + // Flatten direction to avoid flying/digging + direction.y = 0; + direction.Normalize(); + + rb.MovePosition(rb.position + direction * (moveSpeed * Time.fixedDeltaTime)); + } + + // Trigger tiles + if (Physics.Raycast(transform.position, Vector3.down, out var hit, 1.5f, tileLayer)) + { + if (hit.collider.TryGetComponent(out var tile)) + { + tile.OnPlayerStep(); + } + } + } + + private bool IsGrounded() + { + return Physics.Raycast(transform.position, Vector3.down, out var hit, 1.15f, tileLayer); + } + + private void OnDrawGizmos() + { + if (_target) + { + Gizmos.color = Color.red; + Gizmos.DrawLine(transform.position, _target.transform.position); + } + } + } +} diff --git a/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs.meta b/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs.meta new file mode 100644 index 0000000..68f2fd4 --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/HunterNpcController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d87e12fdb2c23b238910064019f2ad59 \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs b/Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs index 9093bce..5faed9f 100644 --- a/Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs +++ b/Assets/Scripts/Infrastructure/Unity/LevelGenerator.cs @@ -10,6 +10,7 @@ namespace Infrastructure.Unity [SerializeField] private TileViewAdapter tilePrefab; [SerializeField] private ParticleSystem tileBreakVfxPrefab; [SerializeField] private JumpPadAdapter jumpPadPrefab; + [SerializeField] private TeleporterAdapter teleporterPrefab; [Header("Settings")] [SerializeField] private int gridSizeX = 10; @@ -18,23 +19,27 @@ namespace Infrastructure.Unity [SerializeField] private float floorHeightDistance = 15f; [SerializeField] private float decayTime = 0.5f; [SerializeField] private float fallingTime = 2.0f; - + public float FloorHeightDistance => floorHeightDistance; public int FloorsCount => floorsCount; public int GridSizeX => gridSizeX; public int GridSizeY => gridSizeY; - - public void Generate(SoundManager soundManager, List allTiles, Dictionary tileViews) + + private TilePool _tilePool; + + public void Generate(SoundManager soundManager, List allTiles, Dictionary tileViews, CameraController camera) { - GenerateFloor(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews); - - GenerateFloor(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews); - - GenerateFloor(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews); + _tilePool = new TilePool(tilePrefab, transform); + + GenerateFloor(0, MapPatterns.GenerateSquare(gridSizeX, gridSizeY), soundManager, allTiles, tileViews, camera); + + GenerateFloor(1, MapPatterns.GenerateDonut(gridSizeX, Mathf.FloorToInt(gridSizeX / 3f)), soundManager, allTiles, tileViews, camera); + + GenerateFloor(2, MapPatterns.GenerateCircle(gridSizeX), soundManager, allTiles, tileViews, camera); } - - private void GenerateFloor(int floorIndex, List coordinates, SoundManager soundManager, - List allTiles, Dictionary tileViews) + + private void GenerateFloor(int floorIndex, List coordinates, SoundManager soundManager, + List allTiles, Dictionary tileViews, CameraController camera) { var yOffset = -(floorIndex * floorHeightDistance); var xOffset = gridSizeX / 2f; @@ -43,46 +48,77 @@ namespace Infrastructure.Unity foreach (var coord in coordinates) { var pos = new Vector3(coord.x - xOffset, yOffset, coord.y - zOffset); - CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews); + CreateTile(pos, $"{floorIndex}_{coord.x}_{coord.y}", floorIndex, soundManager, allTiles, tileViews, camera); } - + if (floorIndex > 0 && jumpPadPrefab) { var validSpot = coordinates[Random.Range(0, coordinates.Count)]; var padPos = new Vector3(validSpot.x - xOffset, yOffset + 0.6f, validSpot.y - zOffset); - + Instantiate(jumpPadPrefab, padPos, Quaternion.identity, transform); } + + if (floorIndex > 0 && teleporterPrefab && coordinates.Count > 5) + { + // Spawn a pair of teleporters + var indexA = Random.Range(0, coordinates.Count); + int indexB; + do + { + indexB = Random.Range(0, coordinates.Count); + } while (indexB == indexA); + + var spotA = coordinates[indexA]; + var spotB = coordinates[indexB]; + + var posA = new Vector3(spotA.x - xOffset, yOffset + 0.6f, spotA.y - zOffset); + var posB = new Vector3(spotB.x - xOffset, yOffset + 0.6f, spotB.y - zOffset); + + var telA = Instantiate(teleporterPrefab, posA, Quaternion.identity, transform); + var telB = Instantiate(teleporterPrefab, posB, Quaternion.identity, transform); + + telA.Initialize(telB.transform); + telB.Initialize(telA.transform); + } } - private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager, - List allTiles, Dictionary tileViews) + private void CreateTile(Vector3 position, string id, int floorIndex, SoundManager soundManager, + List allTiles, Dictionary tileViews, CameraController camera) { - var go = Instantiate(tilePrefab, position, Quaternion.identity, transform); + var go = _tilePool.Get(); + go.transform.position = position; + go.transform.rotation = Quaternion.identity; go.transform.localScale = Vector3.one * 0.95f; - - var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime); - - go.Initialize(tileLogic); - if (soundManager) + // 15% chance to be Fragile + var isFragile = Random.value < 0.15f; + var type = isFragile ? TileType.Fragile : TileType.Normal; + + var tileLogic = new Tile(id, floorIndex, decayTime, fallingTime, type); + + go.Initialize(tileLogic, (t) => _tilePool.Return(t)); + + tileLogic.OnStateChanged += (t) => { - tileLogic.OnStateChanged += (t) => + if (t.CurrentState == TileState.Warning) { - if (t.CurrentState == TileState.Warning) + soundManager?.PlayTileWarning(position); + } + else if (t.CurrentState == TileState.Falling) + { + soundManager?.PlayTileBreak(position); + if (t.Type == TileType.Fragile) { - soundManager.PlayTileWarning(position); + camera?.Shake(0.1f, 0.05f); } - else if (t.CurrentState == TileState.Falling) + + if (tileBreakVfxPrefab) { - soundManager.PlayTileBreak(position); - if (tileBreakVfxPrefab) - { - Instantiate(tileBreakVfxPrefab, position, Quaternion.identity); - } + Instantiate(tileBreakVfxPrefab, position, Quaternion.identity); } - }; - } + } + }; allTiles.Add(tileLogic); tileViews.Add(id, go); diff --git a/Assets/Scripts/Infrastructure/Unity/PlayerController.cs b/Assets/Scripts/Infrastructure/Unity/PlayerController.cs index 3f5364f..7372e1a 100644 --- a/Assets/Scripts/Infrastructure/Unity/PlayerController.cs +++ b/Assets/Scripts/Infrastructure/Unity/PlayerController.cs @@ -14,27 +14,27 @@ namespace Infrastructure.Unity private float moveSpeed = 8f; [SerializeField] private float maxVelocityChange = 10f; [SerializeField] private float snapForce = 15f; - + [Header("Controls")] [SerializeField] private bool useCameraRelativeMovement = true; - + [Header("Interaction")] [SerializeField] private LayerMask tileLayer; [SerializeField] private float groundCheckDistance = 1.5f; - - [Self] [SerializeField] private Rigidbody rb; - + + [Self][SerializeField] private Rigidbody rb; + private InputSystem_Actions _actions; private Vector2 _moveInput; private Transform _camTransform; - + public Rigidbody Rigidbody => rb; public StatusManager Status { get; private set; } private void OnEnable() { _actions.Player.Enable(); - + _actions.Player.Move.performed += OnMovePerformed; _actions.Player.Move.canceled += OnMoveCanceled; } @@ -43,7 +43,7 @@ namespace Infrastructure.Unity { _actions.Player.Move.performed -= OnMovePerformed; _actions.Player.Move.canceled -= OnMoveCanceled; - + _actions.Player.Disable(); } @@ -51,19 +51,22 @@ namespace Infrastructure.Unity { _actions = new InputSystem_Actions(); Status = new StatusManager(); - + if (Camera.main) { _camTransform = Camera.main.transform; } - + rb.freezeRotation = true; - rb.useGravity = true; + // RB gravity is controlled by capabilities } private void Update() { Status.Tick(Time.deltaTime); + + // Apply Status logic + rb.useGravity = !Status.CurrentCapabilities.CanHover; } private void FixedUpdate() @@ -79,7 +82,7 @@ namespace Infrastructure.Unity var snapAxis = Vector3.zero; Vector3 desiredDirection; - + if (_moveInput.sqrMagnitude < 0.01f) { desiredDirection = Vector3.zero; @@ -100,7 +103,7 @@ namespace Infrastructure.Unity desiredDirection = new Vector3(_moveInput.x, 0, _moveInput.y).normalized; } - + if (desiredDirection.sqrMagnitude > 0.01f) { if (Mathf.Abs(desiredDirection.x) > Mathf.Abs(desiredDirection.z)) @@ -117,11 +120,11 @@ namespace Infrastructure.Unity var velocity = rb.linearVelocity; var velocityChange = (targetVelocity - velocity); - + velocityChange.x = Mathf.Clamp(velocityChange.x, -maxVelocityChange, maxVelocityChange); velocityChange.z = Mathf.Clamp(velocityChange.z, -maxVelocityChange, maxVelocityChange); velocityChange.y = 0f; - + rb.AddForce(velocityChange, ForceMode.VelocityChange); if (snapAxis != Vector3.zero) @@ -134,14 +137,14 @@ namespace Infrastructure.Unity ApplySnapping(Vector3.forward); } } - + private void ApplySnapping(Vector3 axis) { var currentPos = Vector3.Dot(transform.position, axis); var targetPos = Mathf.Round(currentPos); - + var diff = targetPos - currentPos; - + var correction = axis * (diff * snapForce); rb.AddForce(correction, ForceMode.Acceleration); } @@ -149,7 +152,7 @@ namespace Infrastructure.Unity private void DetectGround() { if (!Status.CurrentCapabilities.CanTriggerDecay) return; - + if (Physics.Raycast(transform.position, Vector3.down, out var hit, groundCheckDistance, tileLayer)) { if (hit.collider.TryGetComponent(out var tileAdapter)) diff --git a/Assets/Scripts/Infrastructure/Unity/PowerUpViewAdapter.cs b/Assets/Scripts/Infrastructure/Unity/PowerUpViewAdapter.cs index 4df2b15..7ef97da 100644 --- a/Assets/Scripts/Infrastructure/Unity/PowerUpViewAdapter.cs +++ b/Assets/Scripts/Infrastructure/Unity/PowerUpViewAdapter.cs @@ -11,12 +11,14 @@ namespace Infrastructure.Unity [SerializeField] private PowerUpType type; [SerializeField] private float duration = 10f; [SerializeField] private ParticleSystem pickupVfx; - - [Self] [SerializeField] private MeshRenderer meshRenderer; + + [Self][SerializeField] private MeshRenderer meshRenderer; private MaterialPropertyBlock _propBlock; private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor"); + public event Action OnCollected; + private void Awake() { _propBlock = new MaterialPropertyBlock(); @@ -33,6 +35,12 @@ namespace Infrastructure.Unity case PowerUpType.SpeedBoost: SetColor(EffectColors.SpeedBoostColor); break; + case PowerUpType.Hover: + SetColor(EffectColors.HoverColor); + break; + case PowerUpType.TimeSlow: + SetColor(EffectColors.TimeSlowColor); + break; default: throw new ArgumentOutOfRangeException(); } @@ -43,13 +51,14 @@ namespace Infrastructure.Unity if (other.TryGetComponent(out var player)) { ApplyEffect(player); - + OnCollected?.Invoke(type); + if (pickupVfx) { var vfx = Instantiate(pickupVfx, transform.position, Quaternion.identity); Destroy(vfx.gameObject, 2f); } - + Destroy(gameObject); } } @@ -64,6 +73,12 @@ namespace Infrastructure.Unity case PowerUpType.SpeedBoost: player.Status.AddEffect(new SpeedBoostEffect(duration, 1.5f)); break; + case PowerUpType.Hover: + player.Status.AddEffect(new HoverEffect(duration)); + break; + case PowerUpType.TimeSlow: + // Handled globally + break; default: throw new ArgumentOutOfRangeException(); } @@ -72,6 +87,7 @@ namespace Infrastructure.Unity public void Configure(PowerUpType newType) { type = newType; + ConfigureVisuals(); } private void SetColor(Color color) diff --git a/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs b/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs new file mode 100644 index 0000000..8e3dbea --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs @@ -0,0 +1,60 @@ +using System; +using UnityEngine; + +namespace Infrastructure.Unity +{ + public class TeleporterAdapter : MonoBehaviour + { + private Transform _targetDestination; + private TeleporterAdapter _targetAdapter; + private float _cooldownTimer; + + public void Initialize(Transform target) + { + _targetDestination = target; + _targetAdapter = target.GetComponent(); + } + + private void Update() + { + if (_cooldownTimer > 0f) + { + _cooldownTimer -= Time.deltaTime; + } + } + + public void Lock(float duration) + { + _cooldownTimer = duration; + } + + private void OnTriggerEnter(Collider other) + { + if (!_targetDestination) return; + if (_cooldownTimer > 0f) return; + + if (other.TryGetComponent(out var player)) + { + var dest = _targetDestination.position; + player.transform.position = dest + Vector3.up * 1.0f; + player.Rigidbody.linearVelocity = Vector3.zero; + + if (_targetAdapter) + { + _targetAdapter.Lock(1.0f); + } + + Lock(1.0f); + } + } + + private void OnDrawGizmos() + { + if (_targetDestination) + { + Gizmos.color = Color.magenta; + Gizmos.DrawLine(transform.position, _targetDestination.position); + } + } + } +} diff --git a/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs.meta b/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs.meta new file mode 100644 index 0000000..4528583 --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/TeleporterAdapter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 82fa71545d10e6ee187b00ae7fba2eea \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs b/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs new file mode 100644 index 0000000..f60ebbe --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs @@ -0,0 +1,41 @@ +using UnityEngine; + +namespace Infrastructure.Unity +{ + public static class ThemeManager + { + public struct ThemePalette + { + public Color StableColor; + public Color WarningColor; + public Color BackgroundColor; // Not used yet but good to have + } + + public static ThemePalette CurrentTheme { get; set; } = DefaultTheme; + + public static ThemePalette DefaultTheme => new ThemePalette + { + StableColor = new Color(0.2f, 0.2f, 0.2f), + WarningColor = new Color(1f, 0.2f, 0.2f) + }; + + public static ThemePalette CyberpunkTheme => new ThemePalette + { + StableColor = new Color(0.1f, 0.1f, 0.3f), // Dark Blue + WarningColor = new Color(1f, 0f, 1f) // Neon Magenta + }; + + public static ThemePalette GoldenTheme => new ThemePalette + { + StableColor = new Color(0.3f, 0.3f, 0.1f), // Dark Gold + WarningColor = new Color(1f, 0.8f, 0f) // Gold + }; + + public static ThemePalette GetTheme(int highScore) + { + if (highScore >= 500) return GoldenTheme; + if (highScore >= 100) return CyberpunkTheme; + return DefaultTheme; + } + } +} diff --git a/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs.meta b/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs.meta new file mode 100644 index 0000000..ea9c729 --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/ThemeManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cef34765e5cfa88c1b6e9d72e6c4fdc9 \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/TilePool.cs b/Assets/Scripts/Infrastructure/Unity/TilePool.cs new file mode 100644 index 0000000..7ded889 --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/TilePool.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Infrastructure.Unity +{ + public class TilePool + { + private readonly TileViewAdapter _prefab; + private readonly Transform _parent; + private readonly Stack _pool = new(); + + public TilePool(TileViewAdapter prefab, Transform parent) + { + _prefab = prefab; + _parent = parent; + } + + public TileViewAdapter Get() + { + if (_pool.Count > 0) + { + var item = _pool.Pop(); + item.gameObject.SetActive(true); + return item; + } + + return Object.Instantiate(_prefab, _parent); + } + + public void Return(TileViewAdapter item) + { + item.gameObject.SetActive(false); + _pool.Push(item); + } + } +} diff --git a/Assets/Scripts/Infrastructure/Unity/TilePool.cs.meta b/Assets/Scripts/Infrastructure/Unity/TilePool.cs.meta new file mode 100644 index 0000000..c143751 --- /dev/null +++ b/Assets/Scripts/Infrastructure/Unity/TilePool.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 64b83ab6062e454f5b8765c1d2bfc8a7 \ No newline at end of file diff --git a/Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs b/Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs index 7bcda40..1b497dd 100644 --- a/Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs +++ b/Assets/Scripts/Infrastructure/Unity/TileViewAdapter.cs @@ -4,7 +4,6 @@ using Core.Domain; using Core.Ports; using KBCore.Refs; using UnityEngine; -using Random = UnityEngine.Random; namespace Infrastructure.Unity { @@ -12,38 +11,58 @@ namespace Infrastructure.Unity public class TileViewAdapter : MonoBehaviour, ITileView { private Tile _linkedTile; - + public string TileId { get; private set; } - + [Header("Visuals Settings")] [SerializeField] private Color stableColor = new Color(0.2f, 0.2f, 0.2f); // Dark Grey [SerializeField] private Color warningColor = new Color(1f, 0.2f, 0.2f); // Neon Red + [SerializeField] private Color fragileColor = new Color(0.6f, 0.8f, 1f, 0.5f); // Cyan/Glass [SerializeField] private float colorSpeed = 2f; - - [Self] [SerializeField] private Rigidbody rb; - [Self] [SerializeField] private MeshRenderer meshRenderer; + + [Self][SerializeField] private Rigidbody rb; + [Self][SerializeField] private MeshRenderer meshRenderer; public Rigidbody Rigidbody => rb; public MeshRenderer MeshRenderer => meshRenderer; private MaterialPropertyBlock _propBlock; private static readonly int ColorProperty = Shader.PropertyToID("_BaseColor"); - + + private Action _onReturnToPool; + private void Awake() { _propBlock = new MaterialPropertyBlock(); - + rb.isKinematic = true; rb.useGravity = false; } public void Initialize(Tile tile) + { + Initialize(tile, null); + } + + public void Initialize(Tile tile, Action onReturnToPool = null) { _linkedTile = tile; TileId = tile.Id; - - SetColor(stableColor); - + _onReturnToPool = onReturnToPool; + + // Reset physics + rb.isKinematic = true; + rb.useGravity = false; + + if (_linkedTile.Type == TileType.Fragile) + { + SetColor(fragileColor); + } + else + { + SetColor(ThemeManager.CurrentTheme.StableColor); + } + _linkedTile.OnStateChanged += OnTileStateChanged; } @@ -57,18 +76,16 @@ namespace Infrastructure.Unity switch (state) { case TileState.Stable: - StartCoroutine(AnimateColor(stableColor)); + StartCoroutine(AnimateColor(ThemeManager.CurrentTheme.StableColor)); break; case TileState.Warning: - StartCoroutine(AnimateColor(warningColor)); + StartCoroutine(AnimateColor(ThemeManager.CurrentTheme.WarningColor)); break; case TileState.Falling: DropPhysics(); - // Optionally change material or add effects for falling state break; case TileState.Destroyed: Dispose(); - // Optionally change material or add effects for destroyed state break; default: throw new ArgumentOutOfRangeException(nameof(state), state, null); @@ -79,8 +96,6 @@ namespace Infrastructure.Unity { rb.isKinematic = false; rb.useGravity = true; - - // rb.AddTorque(Random.insideUnitSphere * 5f, ForceMode.Impulse); } public void Dispose() @@ -124,8 +139,15 @@ namespace Infrastructure.Unity transform.localScale = Vector3.Lerp(startScale, Vector3.zero, t); yield return null; } - - Destroy(gameObject); + + if (_onReturnToPool != null) + { + _onReturnToPool(this); + } + else + { + Destroy(gameObject); + } } private void OnDestroy()