diff --git a/Assets/Prefabs/Minigames/BirdPoop/Poop.prefab b/Assets/Prefabs/Minigames/BirdPoop/Poop.prefab
index e948e930..924b39f0 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Poop.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Poop.prefab
@@ -15,7 +15,7 @@ GameObject:
- component: {fileID: 4086097097060867018}
m_Layer: 0
m_Name: Poop
- m_TagString: Untagged
+ m_TagString: Projectile
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
@@ -150,7 +150,7 @@ BoxCollider2D:
m_CallbackLayers:
serializedVersion: 2
m_Bits: 4294967295
- m_IsTrigger: 0
+ m_IsTrigger: 1
m_UsedByEffector: 0
m_CompositeOperation: 0
m_CompositeOrder: 0
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Targets.meta b/Assets/Prefabs/Minigames/BirdPoop/Targets.meta
new file mode 100644
index 00000000..54c708b6
--- /dev/null
+++ b/Assets/Prefabs/Minigames/BirdPoop/Targets.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dbcdce294136cfa4aa1091cf3ff03bcf
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab b/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab
new file mode 100644
index 00000000..9fa56ef5
--- /dev/null
+++ b/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab
@@ -0,0 +1,217 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &3536052400313117972
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2159841359414636212}
+ - component: {fileID: 4795053012425497095}
+ m_Layer: 0
+ m_Name: Visual
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2159841359414636212
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 3536052400313117972}
+ 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: 6842023794578555096}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!212 &4795053012425497095
+SpriteRenderer:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 3536052400313117972}
+ m_Enabled: 1
+ m_CastShadows: 0
+ m_ReceiveShadows: 0
+ m_DynamicOccludee: 1
+ m_StaticShadowCaster: 0
+ m_MotionVectors: 1
+ m_LightProbeUsage: 1
+ m_ReflectionProbeUsage: 1
+ m_RayTracingMode: 0
+ 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: 9dfc825aed78fcd4ba02077103263b40, 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: 0
+ 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_Sprite: {fileID: 2133529702, guid: 99d4c3083e9c24142bc20deaeaf95720, type: 3}
+ m_Color: {r: 1, g: 1, b: 1, a: 1}
+ m_FlipX: 0
+ m_FlipY: 0
+ m_DrawMode: 0
+ m_Size: {x: 2.56, y: 4.94}
+ m_AdaptiveModeThreshold: 0.5
+ m_SpriteTileMode: 0
+ m_WasSpriteAssigned: 1
+ m_MaskInteraction: 0
+ m_SpriteSortPoint: 0
+--- !u!1 &8373178063207716143
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 6842023794578555096}
+ - component: {fileID: 1509565078017969516}
+ - component: {fileID: 11467650667563993}
+ - component: {fileID: 8135420306913345847}
+ m_Layer: 0
+ m_Name: Target
+ m_TagString: Target
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &6842023794578555096
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8373178063207716143}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 7.27167, y: -14.259, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 2159841359414636212}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!61 &1509565078017969516
+BoxCollider2D:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8373178063207716143}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_Density: 1
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_ForceSendLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_ForceReceiveLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_ContactCaptureLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_CallbackLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_IsTrigger: 0
+ m_UsedByEffector: 0
+ m_CompositeOperation: 0
+ m_CompositeOrder: 0
+ m_Offset: {x: 0.10432935, y: 1.5258197}
+ m_SpriteTilingProperty:
+ border: {x: 0, y: 0, z: 0, w: 0}
+ pivot: {x: 0, y: 0}
+ oldSize: {x: 0, y: 0}
+ newSize: {x: 0, y: 0}
+ adaptiveTilingThreshold: 0
+ drawMode: 0
+ adaptiveTiling: 0
+ m_AutoTiling: 0
+ m_Size: {x: 2.773602, y: 4.729781}
+ m_EdgeRadius: 0
+--- !u!114 &11467650667563993
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8373178063207716143}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ed380d10e1e04ae7990e5c726c929063, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: AppleHillsScripts::AppleHillsCamera.EdgeAnchor
+ referenceMarker: {fileID: 0}
+ cameraAdapter: {fileID: 0}
+ anchorEdge: 2
+ useReferenceMargin: 0
+ customMargin: 0
+ adjustOnStart: 1
+ adjustOnScreenResize: 1
+ preserveOtherAxes: 1
+ accountForObjectSize: 1
+ customAnchorPoint: {fileID: 0}
+ showVisualization: 1
+ visualizationColor: {r: 1, g: 0, b: 0, a: 0.8}
+ showObjectBounds: 1
+ debugMode: 0
+--- !u!114 &8135420306913345847
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 8373178063207716143}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5331a770bc634a738b82f9450441de12, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: AppleHillsScripts::Minigames.BirdPooper.Target
+ verticalAnchor: 2
+ spriteRenderer: {fileID: 4795053012425497095}
+ hitColor: {r: 1, g: 0, b: 0, a: 1}
+ onTargetHit:
+ m_PersistentCalls:
+ m_Calls: []
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab.meta b/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab.meta
new file mode 100644
index 00000000..ca99e295
--- /dev/null
+++ b/Assets/Prefabs/Minigames/BirdPoop/Targets/Target.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 020f7494c613b06479ccad2c4cedde0f
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scenes/MiniGames/BirdPoop.unity b/Assets/Scenes/MiniGames/BirdPoop.unity
index ec505bee..897c3daa 100644
--- a/Assets/Scenes/MiniGames/BirdPoop.unity
+++ b/Assets/Scenes/MiniGames/BirdPoop.unity
@@ -306,6 +306,7 @@ MonoBehaviour:
m_EditorClassIdentifier: AppleHillsScripts::Minigames.BirdPooper.BirdPooperGameManager
player: {fileID: 941621859}
obstacleSpawner: {fileID: 938885957}
+ targetSpawner: {fileID: 1838778561}
gameOverScreen: {fileID: 81231374}
poopPrefab: {fileID: 5552423787977869117, guid: 066f9990a9b1f5547b387633d5d204c0, type: 3}
--- !u!4 &128829408
@@ -682,12 +683,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 690060017}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 30.01, y: 0, z: 0}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 33.56759, y: 1.13668, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
- m_Father: {fileID: 938885956}
+ m_Father: {fileID: 1498486831}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &938473625
GameObject:
@@ -713,12 +714,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 938473625}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -30.12, y: 0, z: 0}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: -26.56241, y: 1.13668, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
- m_Father: {fileID: 938885956}
+ m_Father: {fileID: 1498486831}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &938885955
GameObject:
@@ -745,14 +746,12 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 938885955}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
+ m_LocalPosition: {x: 3.55759, y: 1.13668, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
- m_Children:
- - {fileID: 690060018}
- - {fileID: 938473626}
- m_Father: {fileID: 0}
+ m_Children: []
+ m_Father: {fileID: 1498486831}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &938885957
MonoBehaviour:
@@ -775,18 +774,10 @@ MonoBehaviour:
- {fileID: 5356906417396349863, guid: cc2a11f7e5edd7640921d1db442a7224, type: 3}
- {fileID: 1771686652490758453, guid: c2dcfdcc678ff3248b40d189f46a4d3b, type: 3}
- {fileID: 2166804132462075410, guid: c60915cb9b989c04caf075ed31cb2a53, type: 3}
- - {fileID: 8872570883018587233, guid: ef4923a2e60ffa540b99d955668c9491, type: 3}
- - {fileID: 6378685721593782219, guid: 6b5e79bd10362854e96e56400a25794d, type: 3}
- - {fileID: 5221014930798827916, guid: c0d26b0d29c2d5a41a60dc01f80bd500, type: 3}
- {fileID: 9077746715280142631, guid: 830960246de8eec4d9535097ce3653db, type: 3}
- {fileID: 7335086015568222999, guid: 06674917a922d6c48a2d0ac0f6056e01, type: 3}
- {fileID: 8558432647259683993, guid: 8c71dd9ad06dafc41a3308f566726ac5, type: 3}
- {fileID: 461075067585331030, guid: 6a77320ba6ef47f448aa934a22bf396f, type: 3}
- - {fileID: 552236068384934285, guid: a9a0c00b5622246429ebd7eaa351d175, type: 3}
- - {fileID: 2022439803908362932, guid: 75f3874bd8cb48f4c8c7ff4452ec1c5f, type: 3}
- - {fileID: 1526963434598685192, guid: 1a8e2dd4ee8bcab44850e0e63f14777d, type: 3}
- - {fileID: 539450891493405750, guid: 700d5f9584069e940baa7107695d2788, type: 3}
- - {fileID: 8553623782462946796, guid: de509c8e31091fc469f238050ff49c20, type: 3}
--- !u!1 &941621855
GameObject:
m_ObjectHideFlags: 0
@@ -797,12 +788,12 @@ GameObject:
m_Component:
- component: {fileID: 941621856}
- component: {fileID: 941621858}
- - component: {fileID: 941621857}
- component: {fileID: 941621859}
- component: {fileID: 941621860}
+ - component: {fileID: 941621861}
m_Layer: 0
m_Name: Bird
- m_TagString: Untagged
+ m_TagString: Player
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
@@ -823,52 +814,6 @@ Transform:
- {fileID: 989743355}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!61 &941621857
-BoxCollider2D:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 941621855}
- m_Enabled: 1
- serializedVersion: 3
- m_Density: 1
- m_Material: {fileID: 0}
- m_IncludeLayers:
- serializedVersion: 2
- m_Bits: 0
- m_ExcludeLayers:
- serializedVersion: 2
- m_Bits: 0
- m_LayerOverridePriority: 0
- m_ForceSendLayers:
- serializedVersion: 2
- m_Bits: 4294967295
- m_ForceReceiveLayers:
- serializedVersion: 2
- m_Bits: 4294967295
- m_ContactCaptureLayers:
- serializedVersion: 2
- m_Bits: 4294967295
- m_CallbackLayers:
- serializedVersion: 2
- m_Bits: 4294967295
- m_IsTrigger: 1
- m_UsedByEffector: 0
- m_CompositeOperation: 0
- m_CompositeOrder: 0
- m_Offset: {x: 0.10795927, y: 0.15422785}
- m_SpriteTilingProperty:
- border: {x: 0, y: 0, z: 0, w: 0}
- pivot: {x: 0, y: 0}
- oldSize: {x: 0, y: 0}
- newSize: {x: 0, y: 0}
- adaptiveTilingThreshold: 0
- drawMode: 0
- adaptiveTiling: 0
- m_AutoTiling: 0
- m_Size: {x: 3.1489096, y: 3.1797535}
- m_EdgeRadius: 0
--- !u!50 &941621858
Rigidbody2D:
serializedVersion: 5
@@ -940,6 +885,83 @@ MonoBehaviour:
visualizationColor: {r: 1, g: 0, b: 0, a: 0.8}
showObjectBounds: 1
debugMode: 0
+--- !u!60 &941621861
+PolygonCollider2D:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 941621855}
+ m_Enabled: 1
+ serializedVersion: 3
+ m_Density: 1
+ m_Material: {fileID: 0}
+ m_IncludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_ExcludeLayers:
+ serializedVersion: 2
+ m_Bits: 0
+ m_LayerOverridePriority: 0
+ m_ForceSendLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_ForceReceiveLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_ContactCaptureLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_CallbackLayers:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_IsTrigger: 1
+ m_UsedByEffector: 0
+ m_CompositeOperation: 0
+ m_CompositeOrder: 0
+ m_Offset: {x: 0, y: 0}
+ m_SpriteTilingProperty:
+ border: {x: 0, y: 0, z: 0, w: 0}
+ pivot: {x: 0, y: 0}
+ oldSize: {x: 0, y: 0}
+ newSize: {x: 0, y: 0}
+ adaptiveTilingThreshold: 0
+ drawMode: 0
+ adaptiveTiling: 0
+ m_AutoTiling: 0
+ m_Points:
+ m_Paths:
+ - - {x: 1.1532116, y: 0.58190906}
+ - {x: 1.4644184, y: 0.5569483}
+ - {x: 1.4214172, y: 0.31169766}
+ - {x: 1.6079073, y: 0.28156418}
+ - {x: 1.8019552, y: 0.67698365}
+ - {x: 1.8297558, y: 1.323832}
+ - {x: 1.654233, y: 1.3764026}
+ - {x: 1.5695343, y: 1.1152095}
+ - {x: 0.92802143, y: 1.1187038}
+ - {x: 0.7329979, y: 1.503772}
+ - {x: 0.46102428, y: 1.5575173}
+ - {x: 0.085243225, y: 1.2721097}
+ - {x: -0.123518944, y: 1.4144537}
+ - {x: -0.55174446, y: 1.6402422}
+ - {x: -0.64256763, y: 1.5012914}
+ - {x: -0.6007347, y: 1.0087695}
+ - {x: -0.7314367, y: 0.7691624}
+ - {x: -0.92963314, y: 0.907363}
+ - {x: -1.0350323, y: 0.77632636}
+ - {x: -0.7913971, y: 0.43193945}
+ - {x: -1.1180935, y: 0.46667978}
+ - {x: -1.1118784, y: 0.20180213}
+ - {x: -0.65509033, y: 0.111432254}
+ - {x: -0.5556206, y: -0.24077833}
+ - {x: -0.1923418, y: -0.6376203}
+ - {x: 0.0838753, y: -0.5945872}
+ - {x: 0.32236862, y: -0.4514704}
+ - {x: 0.4662609, y: -0.059037298}
+ - {x: 0.802371, y: 0.12178908}
+ - {x: 0.9939432, y: 0.33046013}
+ m_UseDelaunayMesh: 0
--- !u!1 &989743352
GameObject:
m_ObjectHideFlags: 0
@@ -1302,6 +1324,41 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1498486830
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1498486831}
+ m_Layer: 0
+ m_Name: Spawners
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1498486831
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1498486830}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: -3.55759, y: -1.13668, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children:
+ - {fileID: 938885956}
+ - {fileID: 1838778560}
+ - {fileID: 690060018}
+ - {fileID: 938473626}
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1536057436
GameObject:
m_ObjectHideFlags: 0
@@ -1575,6 +1632,56 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!1 &1838778559
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 1838778560}
+ - component: {fileID: 1838778561}
+ m_Layer: 0
+ m_Name: TargetSpawner
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &1838778560
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1838778559}
+ 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: 1498486831}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &1838778561
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1838778559}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 16beae843b5f431f9256a56aab02b53d, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: AppleHillsScripts::Minigames.BirdPooper.TargetSpawner
+ spawnPoint: {fileID: 690060018}
+ despawnPoint: {fileID: 938473626}
+ referenceMarker: {fileID: 1143700529}
+ cameraAdapter: {fileID: 2103114179}
+ targetPrefabs:
+ - {fileID: 8373178063207716143, guid: 020f7494c613b06479ccad2c4cedde0f, type: 3}
--- !u!1 &2103114174
GameObject:
m_ObjectHideFlags: 0
@@ -1675,6 +1782,6 @@ SceneRoots:
- {fileID: 580848255}
- {fileID: 941621856}
- {fileID: 1143700530}
- - {fileID: 938885956}
+ - {fileID: 1498486831}
- {fileID: 1536057440}
- {fileID: 128829408}
diff --git a/Assets/Scripts/Core/Settings/BirdPooperSettings.cs b/Assets/Scripts/Core/Settings/BirdPooperSettings.cs
index ffea64e8..e0f6af68 100644
--- a/Assets/Scripts/Core/Settings/BirdPooperSettings.cs
+++ b/Assets/Scripts/Core/Settings/BirdPooperSettings.cs
@@ -55,6 +55,13 @@ namespace Core.Settings
[Tooltip("Y position where poop is destroyed (off-screen bottom)")]
[SerializeField] private float poopDestroyYPosition = -10f;
+ [Header("Targets")]
+ [Tooltip("Target scroll speed in units/s (can be different from obstacles)")]
+ [SerializeField] private float targetMoveSpeed = 4f;
+
+ [Tooltip("Time between target spawns in seconds")]
+ [SerializeField] private float targetSpawnInterval = 3f;
+
// Interface implementation
public float Gravity => gravity;
public float FlapForce => flapForce;
@@ -71,6 +78,8 @@ namespace Core.Settings
public float ObstacleMaxSpawnY => obstacleMaxSpawnY;
public float PoopFallSpeed => poopFallSpeed;
public float PoopDestroyYPosition => poopDestroyYPosition;
+ public float TargetMoveSpeed => targetMoveSpeed;
+ public float TargetSpawnInterval => targetSpawnInterval;
public override void OnValidate()
{
@@ -83,6 +92,8 @@ namespace Core.Settings
maxRotationAngle = Mathf.Clamp(maxRotationAngle, 0f, 90f);
rotationSpeed = Mathf.Max(0.1f, rotationSpeed);
obstacleSpawnInterval = Mathf.Max(0.1f, obstacleSpawnInterval);
+ targetMoveSpeed = Mathf.Max(0.1f, targetMoveSpeed);
+ targetSpawnInterval = Mathf.Max(0.1f, targetSpawnInterval);
}
}
}
diff --git a/Assets/Scripts/Core/Settings/IBirdPooperSettings.cs b/Assets/Scripts/Core/Settings/IBirdPooperSettings.cs
index fd08547f..43e3b388 100644
--- a/Assets/Scripts/Core/Settings/IBirdPooperSettings.cs
+++ b/Assets/Scripts/Core/Settings/IBirdPooperSettings.cs
@@ -28,6 +28,10 @@
// Poop Projectile
float PoopFallSpeed { get; }
float PoopDestroyYPosition { get; }
+
+ // Targets
+ float TargetMoveSpeed { get; }
+ float TargetSpawnInterval { get; }
}
}
diff --git a/Assets/Scripts/Minigames/BirdPooper/BirdPlayerController.cs b/Assets/Scripts/Minigames/BirdPooper/BirdPlayerController.cs
index 9331c247..401d31a0 100644
--- a/Assets/Scripts/Minigames/BirdPooper/BirdPlayerController.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/BirdPlayerController.cs
@@ -56,12 +56,12 @@ namespace Minigames.BirdPooper
return;
}
- // Register as override consumer to capture ALL input (except UI button)
- // Register as override consumer to capture ALL input (except UI button)
+ // Register as default consumer (gets input if nothing else consumes it)
+ // This allows UI buttons to work while still flapping when tapping empty space
if (Input.InputManager.Instance != null)
{
- Input.InputManager.Instance.RegisterOverrideConsumer(this);
- Debug.Log("[BirdPlayerController] Registered as override input consumer");
+ Input.InputManager.Instance.SetDefaultConsumer(this);
+ Debug.Log("[BirdPlayerController] Registered as default input consumer");
}
else
{
@@ -161,12 +161,12 @@ namespace Minigames.BirdPooper
///
/// Called when a trigger collider enters this object's trigger.
- /// Used for detecting obstacles without physics interactions.
+ /// Used for detecting obstacles and targets without physics interactions.
///
private void OnTriggerEnter2D(Collider2D other)
{
- // Check if the colliding object is tagged as an obstacle
- if (other.CompareTag("Obstacle"))
+ // Check if the colliding object is tagged as an obstacle or target
+ if (other.CompareTag("Obstacle") || other.CompareTag("Target"))
{
HandleDeath();
}
diff --git a/Assets/Scripts/Minigames/BirdPooper/BirdPooperGameManager.cs b/Assets/Scripts/Minigames/BirdPooper/BirdPooperGameManager.cs
index bc555f4a..937691c7 100644
--- a/Assets/Scripts/Minigames/BirdPooper/BirdPooperGameManager.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/BirdPooperGameManager.cs
@@ -16,6 +16,7 @@ namespace Minigames.BirdPooper
[Header("References")]
[SerializeField] private BirdPlayerController player;
[SerializeField] private ObstacleSpawner obstacleSpawner;
+ [SerializeField] private TargetSpawner targetSpawner;
[SerializeField] private GameOverScreen gameOverScreen;
[SerializeField] private GameObject poopPrefab;
@@ -47,6 +48,11 @@ namespace Minigames.BirdPooper
Debug.LogError("[BirdPooperGameManager] ObstacleSpawner reference not assigned!");
}
+ if (targetSpawner == null)
+ {
+ Debug.LogWarning("[BirdPooperGameManager] TargetSpawner reference not assigned! Targets will not spawn.");
+ }
+
if (gameOverScreen == null)
{
Debug.LogError("[BirdPooperGameManager] GameOverScreen reference not assigned!");
@@ -84,6 +90,13 @@ namespace Minigames.BirdPooper
obstacleSpawner.StartSpawning();
Debug.Log("[BirdPooperGameManager] Started obstacle spawning");
}
+
+ // Start target spawning
+ if (targetSpawner != null)
+ {
+ targetSpawner.StartSpawning();
+ Debug.Log("[BirdPooperGameManager] Started target spawning");
+ }
}
internal override void OnManagedDestroy()
@@ -120,6 +133,12 @@ namespace Minigames.BirdPooper
obstacleSpawner.StopSpawning();
}
+ // Stop spawning targets
+ if (targetSpawner != null)
+ {
+ targetSpawner.StopSpawning();
+ }
+
// Show game over screen
if (gameOverScreen != null)
{
diff --git a/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs
index 0168acf2..b2793fdc 100644
--- a/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs
@@ -1,300 +1,28 @@
-using UnityEngine;
-using Core;
-using Core.Settings;
-using AppleHillsCamera;
-
+
namespace Minigames.BirdPooper
{
///
- /// Individual obstacle behavior for Bird Pooper minigame.
- /// Scrolls left at constant speed and self-destructs when reaching despawn position.
- /// Uses trigger colliders for collision detection (no Rigidbody2D needed).
- /// Uses EdgeAnchor for vertical positioning (Top/Middle/Bottom).
+ /// Obstacle entity for Bird Pooper minigame.
+ /// Inherits scrolling, anchoring, and despawn behavior from ScrollingEntity.
+ /// Player dies on collision with obstacles.
///
- [RequireComponent(typeof(Collider2D))]
- [RequireComponent(typeof(EdgeAnchor))]
- public class Obstacle : MonoBehaviour
+ public class Obstacle : ScrollingEntity
{
- [Header("Positioning")]
- [Tooltip("Which vertical edge to anchor to (Top/Middle/Bottom)")]
- [SerializeField] private EdgeAnchor.AnchorEdge verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
-
- private IBirdPooperSettings settings;
- private float despawnXPosition;
- private bool isInitialized;
- private EdgeAnchor edgeAnchor;
-
///
- /// Initialize the obstacle with despawn position and EdgeAnchor references.
- /// Called by ObstacleSpawner immediately after instantiation.
+ /// Returns obstacle move speed from settings.
///
- /// X position where obstacle should be destroyed
- /// ScreenReferenceMarker for EdgeAnchor
- /// CameraScreenAdapter for EdgeAnchor
- public void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
+ protected override float GetMoveSpeed()
{
- despawnXPosition = despawnX;
- isInitialized = true;
-
- // Load settings
- settings = GameManager.GetSettingsObject();
- if (settings == null)
- {
- Debug.LogError("[Obstacle] BirdPooperSettings not found!");
- }
-
- // Tag all child GameObjects with colliders as "Obstacle" for trigger detection
- TagChildCollidersRecursive(transform);
-
- // Configure and update EdgeAnchor
- edgeAnchor = GetComponent();
- if (edgeAnchor != null)
- {
- // Assign references from spawner
- edgeAnchor.referenceMarker = referenceMarker;
- edgeAnchor.cameraAdapter = cameraAdapter;
-
- // Only allow Top, Middle, or Bottom anchoring
- if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
- {
- Debug.LogWarning("[Obstacle] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
- verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
- }
-
- edgeAnchor.anchorEdge = verticalAnchor;
- edgeAnchor.useReferenceMargin = false; // No custom offset
- edgeAnchor.customMargin = 0f;
- edgeAnchor.preserveOtherAxes = true; // Keep X position (for scrolling)
- edgeAnchor.accountForObjectSize = true;
-
- // Trigger position update
- edgeAnchor.UpdatePosition();
-
- Debug.Log($"[Obstacle] EdgeAnchor configured to {verticalAnchor} at position {transform.position}");
- }
- else
- {
- Debug.LogError("[Obstacle] EdgeAnchor component not found! Make sure the prefab has an EdgeAnchor component.");
- }
-
- Debug.Log($"[Obstacle] Initialized at position {transform.position} with despawn X: {despawnX}");
+ return settings != null ? settings.ObstacleMoveSpeed : 5f;
}
///
- /// Recursively tag all GameObjects with Collider2D as "Obstacle" for player collision detection.
+ /// Returns "Obstacle" tag for collision detection.
///
- private void TagChildCollidersRecursive(Transform current)
+ protected override string GetColliderTag()
{
- // Tag this GameObject if it has a collider
- Collider2D col = current.GetComponent();
- if (col != null && !current.CompareTag("Obstacle"))
- {
- current.tag = "Obstacle";
- Debug.Log($"[Obstacle] Tagged '{current.name}' as Obstacle");
- }
-
- // Recurse to children
- foreach (Transform child in current)
- {
- TagChildCollidersRecursive(child);
- }
+ return "Obstacle";
}
-
-#if UNITY_EDITOR
- ///
- /// Called when values are changed in the Inspector (Editor only).
- /// Updates EdgeAnchor configuration to match Obstacle settings.
- /// Also finds and assigns ScreenReferenceMarker and CameraScreenAdapter for visual updates.
- ///
- private void OnValidate()
- {
- // Only run in editor, not during play mode
- if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
- return;
-
- EdgeAnchor anchor = GetComponent();
- if (anchor != null)
- {
- // Auto-find and assign references if not set (for editor-time visual updates)
- if (anchor.referenceMarker == null)
- {
- anchor.referenceMarker = FindAnyObjectByType();
- if (anchor.referenceMarker == null)
- {
- Debug.LogWarning("[Obstacle] No ScreenReferenceMarker found in scene. EdgeAnchor positioning won't work in editor.");
- }
- }
-
- if (anchor.cameraAdapter == null)
- {
- anchor.cameraAdapter = FindAnyObjectByType();
- // CameraScreenAdapter is optional - EdgeAnchor can auto-find camera
- }
-
- // Validate and set anchor edge
- if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
- {
- Debug.LogWarning("[Obstacle] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
- verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
- }
-
- // Configure EdgeAnchor to match Obstacle settings
- anchor.anchorEdge = verticalAnchor;
- anchor.useReferenceMargin = false;
- anchor.customMargin = 0f;
- anchor.preserveOtherAxes = true;
- anchor.accountForObjectSize = true;
-
- // Mark as dirty so Unity saves the changes
- UnityEditor.EditorUtility.SetDirty(anchor);
- }
-
- // Tag all child GameObjects with colliders as "Obstacle" for collision detection
- TagChildCollidersRecursiveEditor(transform);
- }
-
- ///
- /// Editor version of recursive tagging for child colliders.
- ///
- private void TagChildCollidersRecursiveEditor(Transform current)
- {
- // Tag this GameObject if it has a collider
- Collider2D col = current.GetComponent();
- if (col != null && !current.CompareTag("Obstacle"))
- {
- current.tag = "Obstacle";
- UnityEditor.EditorUtility.SetDirty(current.gameObject);
- }
-
- // Recurse to children
- foreach (Transform child in current)
- {
- TagChildCollidersRecursiveEditor(child);
- }
- }
-#endif
-
- private void Update()
- {
- if (!isInitialized || settings == null) return;
-
- MoveLeft();
- CheckBounds();
- }
-
- ///
- /// Move obstacle left at constant speed (manual movement, no physics).
- ///
- private void MoveLeft()
- {
- transform.position += Vector3.left * (settings.ObstacleMoveSpeed * Time.deltaTime);
- }
-
- ///
- /// Check if obstacle has passed despawn position and destroy if so.
- ///
- private void CheckBounds()
- {
- if (transform.position.x < despawnXPosition)
- {
- Debug.Log($"[Obstacle] Reached despawn position, destroying at X: {transform.position.x}");
- Destroy(gameObject);
- }
- }
-
-#if UNITY_EDITOR
- ///
- /// Draw debug visualization of the obstacle's anchor point.
- /// Red horizontal line through custom anchor point OR bounds edge (top/bottom).
- ///
- private void OnDrawGizmos()
- {
- EdgeAnchor anchor = GetComponent();
- if (anchor == null) return;
-
- // Determine what Y position to visualize
- float visualY;
-
- // If using custom anchor point, draw line through it
- if (anchor.customAnchorPoint != null)
- {
- visualY = anchor.customAnchorPoint.position.y;
- }
- else
- {
- // Get bounds and determine which edge to visualize
- Bounds bounds = GetVisualBounds();
-
- // Check which vertical anchor is configured
- EdgeAnchor.AnchorEdge edge = anchor.anchorEdge;
-
- if (edge == EdgeAnchor.AnchorEdge.Top)
- {
- // Show top edge of bounds
- visualY = bounds.max.y;
- }
- else if (edge == EdgeAnchor.AnchorEdge.Bottom)
- {
- // Show bottom edge of bounds
- visualY = bounds.min.y;
- }
- else // Middle
- {
- // Show center of bounds
- visualY = bounds.center.y;
- }
- }
-
- // Draw thick red horizontal line through the anchor point
- Color oldColor = Gizmos.color;
- Gizmos.color = Color.red;
-
- // Draw multiple lines to make it thicker
- float lineLength = 2f; // Extend 2 units on each side
- Vector3 leftPoint = new Vector3(transform.position.x - lineLength, visualY, transform.position.z);
- Vector3 rightPoint = new Vector3(transform.position.x + lineLength, visualY, transform.position.z);
-
- // Draw 5 lines stacked vertically to create thickness
- for (int i = -2; i <= 2; i++)
- {
- float offset = i * 0.02f; // Small vertical offset for thickness
- Vector3 offsetLeft = leftPoint + Vector3.up * offset;
- Vector3 offsetRight = rightPoint + Vector3.up * offset;
- Gizmos.DrawLine(offsetLeft, offsetRight);
- }
-
- Gizmos.color = oldColor;
- }
-
- ///
- /// Get bounds for visualization purposes (works in editor without initialized settings).
- ///
- private Bounds GetVisualBounds()
- {
- // Get all renderers in this object and its children
- Renderer[] renderers = GetComponentsInChildren();
-
- if (renderers.Length > 0)
- {
- Bounds bounds = renderers[0].bounds;
- for (int i = 1; i < renderers.Length; i++)
- {
- bounds.Encapsulate(renderers[i].bounds);
- }
- return bounds;
- }
-
- // Fallback to collider bounds
- Collider2D col = GetComponent();
- if (col != null)
- {
- return col.bounds;
- }
-
- // Default small bounds
- return new Bounds(transform.position, new Vector3(0.5f, 0.5f, 0.1f));
- }
-#endif
}
}
diff --git a/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup
new file mode 100644
index 00000000..0168acf2
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup
@@ -0,0 +1,300 @@
+using UnityEngine;
+using Core;
+using Core.Settings;
+using AppleHillsCamera;
+
+namespace Minigames.BirdPooper
+{
+ ///
+ /// Individual obstacle behavior for Bird Pooper minigame.
+ /// Scrolls left at constant speed and self-destructs when reaching despawn position.
+ /// Uses trigger colliders for collision detection (no Rigidbody2D needed).
+ /// Uses EdgeAnchor for vertical positioning (Top/Middle/Bottom).
+ ///
+ [RequireComponent(typeof(Collider2D))]
+ [RequireComponent(typeof(EdgeAnchor))]
+ public class Obstacle : MonoBehaviour
+ {
+ [Header("Positioning")]
+ [Tooltip("Which vertical edge to anchor to (Top/Middle/Bottom)")]
+ [SerializeField] private EdgeAnchor.AnchorEdge verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+
+ private IBirdPooperSettings settings;
+ private float despawnXPosition;
+ private bool isInitialized;
+ private EdgeAnchor edgeAnchor;
+
+ ///
+ /// Initialize the obstacle with despawn position and EdgeAnchor references.
+ /// Called by ObstacleSpawner immediately after instantiation.
+ ///
+ /// X position where obstacle should be destroyed
+ /// ScreenReferenceMarker for EdgeAnchor
+ /// CameraScreenAdapter for EdgeAnchor
+ public void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
+ {
+ despawnXPosition = despawnX;
+ isInitialized = true;
+
+ // Load settings
+ settings = GameManager.GetSettingsObject();
+ if (settings == null)
+ {
+ Debug.LogError("[Obstacle] BirdPooperSettings not found!");
+ }
+
+ // Tag all child GameObjects with colliders as "Obstacle" for trigger detection
+ TagChildCollidersRecursive(transform);
+
+ // Configure and update EdgeAnchor
+ edgeAnchor = GetComponent();
+ if (edgeAnchor != null)
+ {
+ // Assign references from spawner
+ edgeAnchor.referenceMarker = referenceMarker;
+ edgeAnchor.cameraAdapter = cameraAdapter;
+
+ // Only allow Top, Middle, or Bottom anchoring
+ if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
+ {
+ Debug.LogWarning("[Obstacle] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
+ verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+ }
+
+ edgeAnchor.anchorEdge = verticalAnchor;
+ edgeAnchor.useReferenceMargin = false; // No custom offset
+ edgeAnchor.customMargin = 0f;
+ edgeAnchor.preserveOtherAxes = true; // Keep X position (for scrolling)
+ edgeAnchor.accountForObjectSize = true;
+
+ // Trigger position update
+ edgeAnchor.UpdatePosition();
+
+ Debug.Log($"[Obstacle] EdgeAnchor configured to {verticalAnchor} at position {transform.position}");
+ }
+ else
+ {
+ Debug.LogError("[Obstacle] EdgeAnchor component not found! Make sure the prefab has an EdgeAnchor component.");
+ }
+
+ Debug.Log($"[Obstacle] Initialized at position {transform.position} with despawn X: {despawnX}");
+ }
+
+ ///
+ /// Recursively tag all GameObjects with Collider2D as "Obstacle" for player collision detection.
+ ///
+ private void TagChildCollidersRecursive(Transform current)
+ {
+ // Tag this GameObject if it has a collider
+ Collider2D col = current.GetComponent();
+ if (col != null && !current.CompareTag("Obstacle"))
+ {
+ current.tag = "Obstacle";
+ Debug.Log($"[Obstacle] Tagged '{current.name}' as Obstacle");
+ }
+
+ // Recurse to children
+ foreach (Transform child in current)
+ {
+ TagChildCollidersRecursive(child);
+ }
+ }
+
+#if UNITY_EDITOR
+ ///
+ /// Called when values are changed in the Inspector (Editor only).
+ /// Updates EdgeAnchor configuration to match Obstacle settings.
+ /// Also finds and assigns ScreenReferenceMarker and CameraScreenAdapter for visual updates.
+ ///
+ private void OnValidate()
+ {
+ // Only run in editor, not during play mode
+ if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
+ return;
+
+ EdgeAnchor anchor = GetComponent();
+ if (anchor != null)
+ {
+ // Auto-find and assign references if not set (for editor-time visual updates)
+ if (anchor.referenceMarker == null)
+ {
+ anchor.referenceMarker = FindAnyObjectByType();
+ if (anchor.referenceMarker == null)
+ {
+ Debug.LogWarning("[Obstacle] No ScreenReferenceMarker found in scene. EdgeAnchor positioning won't work in editor.");
+ }
+ }
+
+ if (anchor.cameraAdapter == null)
+ {
+ anchor.cameraAdapter = FindAnyObjectByType();
+ // CameraScreenAdapter is optional - EdgeAnchor can auto-find camera
+ }
+
+ // Validate and set anchor edge
+ if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
+ {
+ Debug.LogWarning("[Obstacle] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
+ verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+ }
+
+ // Configure EdgeAnchor to match Obstacle settings
+ anchor.anchorEdge = verticalAnchor;
+ anchor.useReferenceMargin = false;
+ anchor.customMargin = 0f;
+ anchor.preserveOtherAxes = true;
+ anchor.accountForObjectSize = true;
+
+ // Mark as dirty so Unity saves the changes
+ UnityEditor.EditorUtility.SetDirty(anchor);
+ }
+
+ // Tag all child GameObjects with colliders as "Obstacle" for collision detection
+ TagChildCollidersRecursiveEditor(transform);
+ }
+
+ ///
+ /// Editor version of recursive tagging for child colliders.
+ ///
+ private void TagChildCollidersRecursiveEditor(Transform current)
+ {
+ // Tag this GameObject if it has a collider
+ Collider2D col = current.GetComponent();
+ if (col != null && !current.CompareTag("Obstacle"))
+ {
+ current.tag = "Obstacle";
+ UnityEditor.EditorUtility.SetDirty(current.gameObject);
+ }
+
+ // Recurse to children
+ foreach (Transform child in current)
+ {
+ TagChildCollidersRecursiveEditor(child);
+ }
+ }
+#endif
+
+ private void Update()
+ {
+ if (!isInitialized || settings == null) return;
+
+ MoveLeft();
+ CheckBounds();
+ }
+
+ ///
+ /// Move obstacle left at constant speed (manual movement, no physics).
+ ///
+ private void MoveLeft()
+ {
+ transform.position += Vector3.left * (settings.ObstacleMoveSpeed * Time.deltaTime);
+ }
+
+ ///
+ /// Check if obstacle has passed despawn position and destroy if so.
+ ///
+ private void CheckBounds()
+ {
+ if (transform.position.x < despawnXPosition)
+ {
+ Debug.Log($"[Obstacle] Reached despawn position, destroying at X: {transform.position.x}");
+ Destroy(gameObject);
+ }
+ }
+
+#if UNITY_EDITOR
+ ///
+ /// Draw debug visualization of the obstacle's anchor point.
+ /// Red horizontal line through custom anchor point OR bounds edge (top/bottom).
+ ///
+ private void OnDrawGizmos()
+ {
+ EdgeAnchor anchor = GetComponent();
+ if (anchor == null) return;
+
+ // Determine what Y position to visualize
+ float visualY;
+
+ // If using custom anchor point, draw line through it
+ if (anchor.customAnchorPoint != null)
+ {
+ visualY = anchor.customAnchorPoint.position.y;
+ }
+ else
+ {
+ // Get bounds and determine which edge to visualize
+ Bounds bounds = GetVisualBounds();
+
+ // Check which vertical anchor is configured
+ EdgeAnchor.AnchorEdge edge = anchor.anchorEdge;
+
+ if (edge == EdgeAnchor.AnchorEdge.Top)
+ {
+ // Show top edge of bounds
+ visualY = bounds.max.y;
+ }
+ else if (edge == EdgeAnchor.AnchorEdge.Bottom)
+ {
+ // Show bottom edge of bounds
+ visualY = bounds.min.y;
+ }
+ else // Middle
+ {
+ // Show center of bounds
+ visualY = bounds.center.y;
+ }
+ }
+
+ // Draw thick red horizontal line through the anchor point
+ Color oldColor = Gizmos.color;
+ Gizmos.color = Color.red;
+
+ // Draw multiple lines to make it thicker
+ float lineLength = 2f; // Extend 2 units on each side
+ Vector3 leftPoint = new Vector3(transform.position.x - lineLength, visualY, transform.position.z);
+ Vector3 rightPoint = new Vector3(transform.position.x + lineLength, visualY, transform.position.z);
+
+ // Draw 5 lines stacked vertically to create thickness
+ for (int i = -2; i <= 2; i++)
+ {
+ float offset = i * 0.02f; // Small vertical offset for thickness
+ Vector3 offsetLeft = leftPoint + Vector3.up * offset;
+ Vector3 offsetRight = rightPoint + Vector3.up * offset;
+ Gizmos.DrawLine(offsetLeft, offsetRight);
+ }
+
+ Gizmos.color = oldColor;
+ }
+
+ ///
+ /// Get bounds for visualization purposes (works in editor without initialized settings).
+ ///
+ private Bounds GetVisualBounds()
+ {
+ // Get all renderers in this object and its children
+ Renderer[] renderers = GetComponentsInChildren();
+
+ if (renderers.Length > 0)
+ {
+ Bounds bounds = renderers[0].bounds;
+ for (int i = 1; i < renderers.Length; i++)
+ {
+ bounds.Encapsulate(renderers[i].bounds);
+ }
+ return bounds;
+ }
+
+ // Fallback to collider bounds
+ Collider2D col = GetComponent();
+ if (col != null)
+ {
+ return col.bounds;
+ }
+
+ // Default small bounds
+ return new Bounds(transform.position, new Vector3(0.5f, 0.5f, 0.1f));
+ }
+#endif
+ }
+}
+
diff --git a/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup.meta b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup.meta
new file mode 100644
index 00000000..2b7f6239
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/Obstacle.cs.backup.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bc9c7bac4482311439b4c2e7879f3d73
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Scripts/Minigames/BirdPooper/PoopProjectile.cs b/Assets/Scripts/Minigames/BirdPooper/PoopProjectile.cs
index 118a66fd..d39416b6 100644
--- a/Assets/Scripts/Minigames/BirdPooper/PoopProjectile.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/PoopProjectile.cs
@@ -21,6 +21,12 @@ namespace Minigames.BirdPooper
{
base.OnManagedAwake();
+ // Tag as Projectile for target detection
+ if (!gameObject.CompareTag("Projectile"))
+ {
+ gameObject.tag = "Projectile";
+ }
+
// Load settings
settings = GameManager.GetSettingsObject();
if (settings == null)
@@ -37,16 +43,19 @@ namespace Minigames.BirdPooper
Rigidbody2D rb = GetComponent();
if (rb != null)
{
- rb.bodyType = RigidbodyType2D.Dynamic;
- rb.gravityScale = 0f; // Manual gravity
+ rb.bodyType = RigidbodyType2D.Kinematic; // Kinematic = manual control, no physics
rb.collisionDetectionMode = CollisionDetectionMode2D.Continuous;
}
- // Verify collider is trigger (for target detection in Phase 5)
- Collider2D col = GetComponent();
- if (col != null && !col.isTrigger)
+ // Find and set all colliders to trigger (we use OnTriggerEnter2D)
+ Collider2D[] colliders = GetComponentsInChildren(true);
+ foreach (Collider2D col in colliders)
{
- Debug.LogWarning("[PoopProjectile] Collider should be set as Trigger for target detection!");
+ if (!col.isTrigger)
+ {
+ col.isTrigger = true;
+ Debug.Log($"[PoopProjectile] Set collider '{col.name}' to trigger");
+ }
}
}
@@ -91,12 +100,10 @@ namespace Minigames.BirdPooper
///
/// Trigger collision detection for targets (Phase 5).
- /// TODO: Uncomment when Target.cs is implemented in Phase 5
+ /// Uses OnTriggerEnter2D with trigger collider.
///
private void OnTriggerEnter2D(Collider2D other)
{
- // Phase 5 integration - currently commented out
- /*
if (other.CompareTag("Target"))
{
// Notify target it was hit
@@ -108,7 +115,6 @@ namespace Minigames.BirdPooper
Destroy(gameObject);
}
- */
}
}
}
diff --git a/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs b/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs
new file mode 100644
index 00000000..e9d043b8
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs
@@ -0,0 +1,230 @@
+using UnityEngine;
+using Core;
+using Core.Settings;
+using AppleHillsCamera;
+
+namespace Minigames.BirdPooper
+{
+ ///
+ /// Abstract base class for all scrolling entities in Bird Pooper minigame.
+ /// Provides common functionality: manual left-scrolling, EdgeAnchor integration, despawn detection.
+ /// Subclasses: Obstacle, Target
+ ///
+ [RequireComponent(typeof(Collider2D))]
+ [RequireComponent(typeof(EdgeAnchor))]
+ public abstract class ScrollingEntity : MonoBehaviour
+ {
+ [Header("Positioning")]
+ [Tooltip("Which vertical edge to anchor to (Top/Middle/Bottom)")]
+ [SerializeField] protected EdgeAnchor.AnchorEdge verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+
+ protected IBirdPooperSettings settings;
+ protected float despawnXPosition;
+ protected bool isInitialized;
+ protected EdgeAnchor edgeAnchor;
+
+ ///
+ /// Initialize the entity with despawn position and EdgeAnchor references.
+ /// Called by spawner immediately after instantiation.
+ ///
+ public virtual void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
+ {
+ despawnXPosition = despawnX;
+ isInitialized = true;
+
+ // Load settings
+ settings = GameManager.GetSettingsObject();
+ if (settings == null)
+ {
+ Debug.LogError($"[{GetType().Name}] BirdPooperSettings not found!");
+ }
+
+ // Tag all child GameObjects with colliders
+ TagChildCollidersRecursive(transform);
+
+ // Find and set all colliders to trigger (we use OnTriggerEnter2D)
+ Collider2D[] colliders = GetComponentsInChildren(true);
+ foreach (Collider2D col in colliders)
+ {
+ if (!col.isTrigger)
+ {
+ col.isTrigger = true;
+ Debug.Log($"[{GetType().Name}] Set collider '{col.name}' to trigger");
+ }
+ }
+
+ // Configure and update EdgeAnchor
+ edgeAnchor = GetComponent();
+ if (edgeAnchor != null)
+ {
+ // Assign references from spawner
+ edgeAnchor.referenceMarker = referenceMarker;
+ edgeAnchor.cameraAdapter = cameraAdapter;
+
+ // Only allow Top, Middle, or Bottom anchoring
+ if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
+ {
+ Debug.LogWarning($"[{GetType().Name}] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
+ verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+ }
+
+ edgeAnchor.anchorEdge = verticalAnchor;
+ edgeAnchor.useReferenceMargin = false; // No custom offset
+ edgeAnchor.customMargin = 0f;
+ edgeAnchor.preserveOtherAxes = true; // Keep X position (for scrolling)
+ edgeAnchor.accountForObjectSize = true;
+
+ // Trigger position update
+ edgeAnchor.UpdatePosition();
+
+ Debug.Log($"[{GetType().Name}] EdgeAnchor configured to {verticalAnchor} at position {transform.position}");
+ }
+ else
+ {
+ Debug.LogError($"[{GetType().Name}] EdgeAnchor component not found! Make sure the prefab has an EdgeAnchor component.");
+ }
+
+ Debug.Log($"[{GetType().Name}] Initialized at position {transform.position} with despawn X: {despawnX}");
+ }
+
+ ///
+ /// Recursively tag all GameObjects with Collider2D for collision detection.
+ /// Subclasses override GetColliderTag() to specify their tag.
+ ///
+ protected virtual void TagChildCollidersRecursive(Transform current)
+ {
+ string tagToApply = GetColliderTag();
+
+ // Tag this GameObject if it has a collider
+ Collider2D col = current.GetComponent();
+ if (col != null && !current.CompareTag(tagToApply))
+ {
+ current.tag = tagToApply;
+ Debug.Log($"[{GetType().Name}] Tagged '{current.name}' as {tagToApply}");
+ }
+
+ // Recurse to children
+ foreach (Transform child in current)
+ {
+ TagChildCollidersRecursive(child);
+ }
+ }
+
+#if UNITY_EDITOR
+ ///
+ /// Called when values are changed in the Inspector (Editor only).
+ /// Updates EdgeAnchor configuration to match entity settings.
+ ///
+ protected virtual void OnValidate()
+ {
+ // Only run in editor, not during play mode
+ if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
+ return;
+
+ EdgeAnchor anchor = GetComponent();
+ if (anchor != null)
+ {
+ // Auto-find and assign references if not set (for editor-time visual updates)
+ if (anchor.referenceMarker == null)
+ {
+ anchor.referenceMarker = FindAnyObjectByType();
+ if (anchor.referenceMarker == null)
+ {
+ Debug.LogWarning($"[{GetType().Name}] No ScreenReferenceMarker found in scene. EdgeAnchor positioning won't work in editor.");
+ }
+ }
+
+ if (anchor.cameraAdapter == null)
+ {
+ anchor.cameraAdapter = FindAnyObjectByType();
+ // CameraScreenAdapter is optional - EdgeAnchor can auto-find camera
+ }
+
+ // Validate and set anchor edge
+ if (verticalAnchor == EdgeAnchor.AnchorEdge.Left || verticalAnchor == EdgeAnchor.AnchorEdge.Right)
+ {
+ Debug.LogWarning($"[{GetType().Name}] Invalid anchor edge (Left/Right not supported). Defaulting to Middle.");
+ verticalAnchor = EdgeAnchor.AnchorEdge.Middle;
+ }
+
+ // Configure EdgeAnchor to match entity settings
+ anchor.anchorEdge = verticalAnchor;
+ anchor.useReferenceMargin = false;
+ anchor.customMargin = 0f;
+ anchor.preserveOtherAxes = true;
+ anchor.accountForObjectSize = true;
+
+ // Mark as dirty so Unity saves the changes
+ UnityEditor.EditorUtility.SetDirty(anchor);
+ }
+
+ // Tag all child GameObjects with colliders
+ TagChildCollidersRecursiveEditor(transform);
+ }
+
+ ///
+ /// Editor version of recursive tagging for child colliders.
+ ///
+ protected virtual void TagChildCollidersRecursiveEditor(Transform current)
+ {
+ string tagToApply = GetColliderTag();
+
+ // Tag this GameObject if it has a collider
+ Collider2D col = current.GetComponent();
+ if (col != null && !current.CompareTag(tagToApply))
+ {
+ current.tag = tagToApply;
+ UnityEditor.EditorUtility.SetDirty(current.gameObject);
+ }
+
+ // Recurse to children
+ foreach (Transform child in current)
+ {
+ TagChildCollidersRecursiveEditor(child);
+ }
+ }
+#endif
+
+ protected virtual void Update()
+ {
+ if (!isInitialized || settings == null) return;
+
+ MoveLeft();
+ CheckBounds();
+ }
+
+ ///
+ /// Move entity left at constant speed (manual movement, no physics).
+ /// Override GetMoveSpeed() to customize speed per entity type.
+ ///
+ protected virtual void MoveLeft()
+ {
+ transform.position += Vector3.left * (GetMoveSpeed() * Time.deltaTime);
+ }
+
+ ///
+ /// Check if entity has passed despawn position and destroy if so.
+ ///
+ protected virtual void CheckBounds()
+ {
+ if (transform.position.x < despawnXPosition)
+ {
+ Debug.Log($"[{GetType().Name}] Reached despawn position, destroying at X: {transform.position.x}");
+ Destroy(gameObject);
+ }
+ }
+
+ ///
+ /// Get the move speed for this entity type.
+ /// Subclasses override to return appropriate speed from settings.
+ ///
+ protected abstract float GetMoveSpeed();
+
+ ///
+ /// Get the tag to apply to colliders for this entity type.
+ /// Subclasses override to return "Obstacle", "Target", etc.
+ ///
+ protected abstract string GetColliderTag();
+ }
+}
+
diff --git a/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs.meta b/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs.meta
new file mode 100644
index 00000000..00097016
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/ScrollingEntity.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 69a3d32fc5cf4789a5e6e8dbc5f64996
+timeCreated: 1763713709
\ No newline at end of file
diff --git a/Assets/Scripts/Minigames/BirdPooper/Target.cs b/Assets/Scripts/Minigames/BirdPooper/Target.cs
new file mode 100644
index 00000000..6151e095
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/Target.cs
@@ -0,0 +1,117 @@
+using UnityEngine;
+using UnityEngine.Events;
+using AppleHillsCamera;
+
+namespace Minigames.BirdPooper
+{
+ ///
+ /// Target entity for Bird Pooper minigame.
+ /// Inherits scrolling, anchoring, and despawn behavior from ScrollingEntity.
+ /// Can be hit by poop projectiles for scoring. Player still dies on collision.
+ ///
+ public class Target : ScrollingEntity
+ {
+ [Header("Visual Feedback")]
+ [SerializeField] private SpriteRenderer spriteRenderer;
+ [SerializeField] private Color hitColor = Color.green;
+
+ [Header("Events")]
+ public UnityEvent onTargetHit;
+
+ private bool isHit;
+
+ ///
+ /// Initialize target and set up event.
+ ///
+ public override void Initialize(float despawnX, ScreenReferenceMarker referenceMarker, CameraScreenAdapter cameraAdapter)
+ {
+ base.Initialize(despawnX, referenceMarker, cameraAdapter);
+
+ isHit = false;
+
+ // Initialize event
+ if (onTargetHit == null)
+ {
+ onTargetHit = new UnityEvent();
+ }
+
+ // Find and set all colliders to trigger (we use OnTriggerEnter2D)
+ Collider2D[] colliders = GetComponentsInChildren(true);
+ foreach (Collider2D col in colliders)
+ {
+ if (!col.isTrigger)
+ {
+ col.isTrigger = true;
+ Debug.Log($"[Target] Set collider '{col.name}' to trigger");
+ }
+ }
+ }
+
+ ///
+ /// Override Update to stop movement when hit.
+ ///
+ protected override void Update()
+ {
+ if (isHit) return; // Don't move or check bounds if hit
+
+ base.Update();
+ }
+
+ ///
+ /// Returns target move speed from settings.
+ ///
+ protected override float GetMoveSpeed()
+ {
+ return settings != null ? settings.TargetMoveSpeed : 4f;
+ }
+
+ ///
+ /// Returns "Target" tag for collision detection.
+ ///
+ protected override string GetColliderTag()
+ {
+ return "Target";
+ }
+
+ ///
+ /// Trigger collision detection for both player and projectiles.
+ /// Single trigger collider handles both cases.
+ ///
+ private void OnTriggerEnter2D(Collider2D other)
+ {
+ // Check for projectile collision
+ if (other.CompareTag("Projectile") && !isHit)
+ {
+ OnHitByProjectile();
+ }
+ // Player collision is handled by BirdPlayerController's OnTriggerEnter2D
+ // (both have trigger colliders, so trigger occurs naturally)
+ }
+
+ ///
+ /// Called when target is hit by a poop projectile.
+ /// Shows visual feedback and notifies manager.
+ ///
+ public void OnHitByProjectile()
+ {
+ if (isHit) return;
+
+ isHit = true;
+
+ // Visual feedback
+ if (spriteRenderer != null)
+ {
+ spriteRenderer.color = hitColor;
+ }
+
+ // Notify manager
+ onTargetHit?.Invoke();
+
+ Debug.Log($"[Target] Hit by projectile at position {transform.position}");
+
+ // Destroy after brief delay for visual feedback
+ Destroy(gameObject, 0.2f);
+ }
+ }
+}
+
diff --git a/Assets/Scripts/Minigames/BirdPooper/Target.cs.meta b/Assets/Scripts/Minigames/BirdPooper/Target.cs.meta
new file mode 100644
index 00000000..14ddb9db
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/Target.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5331a770bc634a738b82f9450441de12
+timeCreated: 1763713776
\ No newline at end of file
diff --git a/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
new file mode 100644
index 00000000..1d78b6ea
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
@@ -0,0 +1,198 @@
+using UnityEngine;
+using Core;
+using Core.Settings;
+using Core.Lifecycle;
+using AppleHillsCamera;
+
+namespace Minigames.BirdPooper
+{
+ ///
+ /// Spawns targets at regular intervals for Bird Pooper minigame.
+ /// Uses Transform references for spawn and despawn positions.
+ /// All targets are spawned at Y = 0 (EdgeAnchor positions them vertically).
+ ///
+ public class TargetSpawner : ManagedBehaviour
+ {
+ [Header("Spawn Configuration")]
+ [Tooltip("Transform marking where targets spawn (off-screen right)")]
+ [SerializeField] private Transform spawnPoint;
+
+ [Tooltip("Transform marking where targets despawn (off-screen left)")]
+ [SerializeField] private Transform despawnPoint;
+
+ [Header("EdgeAnchor References")]
+ [Tooltip("ScreenReferenceMarker to pass to spawned targets")]
+ [SerializeField] private ScreenReferenceMarker referenceMarker;
+
+ [Tooltip("CameraScreenAdapter to pass to spawned targets")]
+ [SerializeField] private CameraScreenAdapter cameraAdapter;
+
+ [Header("Target Prefabs")]
+ [Tooltip("Array of target prefabs to spawn randomly")]
+ [SerializeField] private GameObject[] targetPrefabs;
+
+ private IBirdPooperSettings settings;
+ private float spawnTimer;
+ private bool isSpawning;
+
+ internal override void OnManagedAwake()
+ {
+ base.OnManagedAwake();
+
+ // Load settings
+ settings = GameManager.GetSettingsObject();
+ if (settings == null)
+ {
+ Debug.LogError("[TargetSpawner] BirdPooperSettings not found!");
+ return;
+ }
+
+ // Validate references
+ if (spawnPoint == null)
+ {
+ Debug.LogError("[TargetSpawner] Spawn Point not assigned! Please assign a Transform in the Inspector.");
+ }
+
+ if (despawnPoint == null)
+ {
+ Debug.LogError("[TargetSpawner] Despawn Point not assigned! Please assign a Transform in the Inspector.");
+ }
+
+ if (targetPrefabs == null || targetPrefabs.Length == 0)
+ {
+ Debug.LogError("[TargetSpawner] No target prefabs assigned! Please assign at least one prefab in the Inspector.");
+ }
+
+ if (referenceMarker == null)
+ {
+ Debug.LogError("[TargetSpawner] ScreenReferenceMarker not assigned! Targets need this for EdgeAnchor positioning.");
+ }
+
+ if (cameraAdapter == null)
+ {
+ Debug.LogWarning("[TargetSpawner] CameraScreenAdapter not assigned. EdgeAnchor will attempt to auto-find camera.");
+ }
+
+ Debug.Log("[TargetSpawner] Initialized successfully");
+ }
+
+ private void Update()
+ {
+ if (!isSpawning || settings == null)
+ return;
+
+ spawnTimer += Time.deltaTime;
+
+ if (spawnTimer >= settings.TargetSpawnInterval)
+ {
+ SpawnTarget();
+ spawnTimer = 0f;
+ }
+ }
+
+ ///
+ /// Spawns a random target prefab at the spawn point.
+ /// Target is spawned at Y = 0, EdgeAnchor will position it vertically.
+ ///
+ private void SpawnTarget()
+ {
+ if (targetPrefabs == null || targetPrefabs.Length == 0 || spawnPoint == null)
+ {
+ Debug.LogError("[TargetSpawner] Cannot spawn target - missing prefabs or spawn point!");
+ return;
+ }
+
+ // Randomly select target prefab
+ GameObject prefab = targetPrefabs[Random.Range(0, targetPrefabs.Length)];
+
+ // Spawn at spawn point X, but Y = 0 (EdgeAnchor will position vertically)
+ Vector3 spawnPosition = new Vector3(spawnPoint.position.x, 0f, 0f);
+ GameObject targetObj = Instantiate(prefab, spawnPosition, Quaternion.identity);
+
+ // Initialize target
+ Target target = targetObj.GetComponent();
+ if (target != null)
+ {
+ float despawnX = despawnPoint != null ? despawnPoint.position.x : -12f;
+ target.Initialize(despawnX, referenceMarker, cameraAdapter);
+
+ // Subscribe to target hit event to notify manager
+ target.onTargetHit.AddListener(OnTargetHit);
+
+ Debug.Log($"[TargetSpawner] Spawned target at {spawnPosition}");
+ }
+ else
+ {
+ Debug.LogError($"[TargetSpawner] Spawned prefab '{prefab.name}' does not have Target component!");
+ Destroy(targetObj);
+ }
+ }
+
+ ///
+ /// Called when a target is hit by a projectile.
+ /// Notifies the game manager for score tracking.
+ ///
+ private void OnTargetHit()
+ {
+ // Find and notify manager
+ BirdPooperGameManager manager = BirdPooperGameManager.Instance;
+ if (manager != null)
+ {
+ manager.OnTargetHit();
+ }
+ else
+ {
+ Debug.LogWarning("[TargetSpawner] BirdPooperGameManager not found!");
+ }
+ }
+
+ ///
+ /// Start spawning targets at regular intervals.
+ ///
+ public void StartSpawning()
+ {
+ isSpawning = true;
+ spawnTimer = 0f;
+ Debug.Log("[TargetSpawner] Started spawning targets");
+ }
+
+ ///
+ /// Stop spawning new targets (existing targets continue).
+ ///
+ public void StopSpawning()
+ {
+ isSpawning = false;
+ Debug.Log("[TargetSpawner] Stopped spawning targets");
+ }
+
+ ///
+ /// Check if spawner is currently spawning.
+ ///
+ public bool IsSpawning => isSpawning;
+
+ ///
+ /// Draw gizmos to visualize spawn and despawn points in the editor.
+ ///
+ private void OnDrawGizmos()
+ {
+ if (spawnPoint != null)
+ {
+ Gizmos.color = Color.cyan;
+ Gizmos.DrawLine(
+ new Vector3(spawnPoint.position.x, -10f, 0f),
+ new Vector3(spawnPoint.position.x, 10f, 0f)
+ );
+ }
+
+ if (despawnPoint != null)
+ {
+ Gizmos.color = Color.magenta;
+ Gizmos.DrawLine(
+ new Vector3(despawnPoint.position.x, -10f, 0f),
+ new Vector3(despawnPoint.position.x, 10f, 0f)
+ );
+ }
+ }
+ }
+}
+
diff --git a/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs.meta b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs.meta
new file mode 100644
index 00000000..02c61148
--- /dev/null
+++ b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 16beae843b5f431f9256a56aab02b53d
+timeCreated: 1763713803
\ No newline at end of file
diff --git a/ProjectSettings/TagManager.asset b/ProjectSettings/TagManager.asset
index 513b242d..8d40c99f 100644
--- a/ProjectSettings/TagManager.asset
+++ b/ProjectSettings/TagManager.asset
@@ -8,6 +8,8 @@ TagManager:
- Pulver
- Rock
- Obstacle
+ - Projectile
+ - Target
layers:
- Default
- TransparentFX