diff --git a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_A.prefab b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_A.prefab
index a7dbe993..0de837ba 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_A.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_A.prefab
@@ -46,7 +46,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
propertyPath: m_Name
- value: Difficulty1_A
+ value: Obstacle_Difficulty1_A
objectReference: {fileID: 0}
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
propertyPath: m_LocalPosition.y
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_C.prefab b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_C.prefab
index 75e8fe3a..8c0478c9 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_C.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_C.prefab
@@ -14,7 +14,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_Name
- value: Obstacle_Difficulty1_BA
+ value: Obstacle_Difficulty1_C
objectReference: {fileID: 0}
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_LocalPosition.x
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_D.prefab b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_D.prefab
index 08c458ab..3e8370f3 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_D.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_D.prefab
@@ -14,7 +14,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_Name
- value: Obstacle_Difficulty1_BB
+ value: Obstacle_Difficulty1_D
objectReference: {fileID: 0}
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_LocalPosition.x
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_E.prefab b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_E.prefab
index e67c16e1..8a8d755b 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_E.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_E.prefab
@@ -14,7 +14,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_Name
- value: Obstacle_Difficulty1_BC
+ value: Obstacle_Difficulty1_E
objectReference: {fileID: 0}
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
propertyPath: m_LocalPosition.x
diff --git a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_F.prefab b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_F.prefab
index 7bb764c1..c4085722 100644
--- a/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_F.prefab
+++ b/Assets/Prefabs/Minigames/BirdPoop/Obstacles/Difficulty1/Obstacle_Difficulty1_F.prefab
@@ -74,7 +74,7 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
propertyPath: m_Name
- value: Obstacle_Difficulty1_C
+ value: Obstacle_Difficulty1_F
objectReference: {fileID: 0}
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
propertyPath: m_LocalPosition.x
diff --git a/Assets/Scenes/MiniGames/BirdPoop.unity b/Assets/Scenes/MiniGames/BirdPoop.unity
index e2e94924..bec1a570 100644
--- a/Assets/Scenes/MiniGames/BirdPoop.unity
+++ b/Assets/Scenes/MiniGames/BirdPoop.unity
@@ -788,7 +788,7 @@ MonoBehaviour:
- {fileID: 1408173265900928789, guid: 65810bfd58ebbaf4482527452258ae50, type: 3}
- {fileID: 1408173265900928789, guid: ae3986a7db087c845b618a9c897705ec, type: 3}
minSpawnInterval: 2
- maxSpawnInterval: 9
+ maxSpawnInterval: 8
difficultyRampDuration: 360
difficultyCurve:
serializedVersion: 2
@@ -815,6 +815,8 @@ MonoBehaviour:
m_PostInfinity: 2
m_RotationOrder: 4
intervalJitter: 0.3
+ recentDecayDuration: 60
+ minRecentWeight: 0.05
--- !u!1 &941621855
GameObject:
m_ObjectHideFlags: 0
@@ -1839,6 +1841,8 @@ MonoBehaviour:
cameraAdapter: {fileID: 2103114179}
targetPrefabs:
- {fileID: 8373178063207716143, guid: 020f7494c613b06479ccad2c4cedde0f, type: 3}
+ minTargetSpawnInterval: 9
+ maxTargetSpawnInterval: 15
--- !u!1 &2103114174
GameObject:
m_ObjectHideFlags: 0
diff --git a/Assets/Scripts/Minigames/BirdPooper/ObstacleSpawner.cs b/Assets/Scripts/Minigames/BirdPooper/ObstacleSpawner.cs
index ca060304..efffb54b 100644
--- a/Assets/Scripts/Minigames/BirdPooper/ObstacleSpawner.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/ObstacleSpawner.cs
@@ -3,6 +3,7 @@ using Core;
using Core.Settings;
using Core.Lifecycle;
using AppleHillsCamera;
+using System.Text;
namespace Minigames.BirdPooper
{
@@ -45,6 +46,13 @@ namespace Minigames.BirdPooper
[Tooltip("Maximum jitter applied to computed interval (fractional). 0.1 = +/-10% jitter")]
[SerializeField] private float intervalJitter = 0.05f;
+ [Header("Recency / Diversity")]
+ [Tooltip("Time in seconds it takes for a recently-used prefab to recover back to full weight")]
+ [SerializeField] private float recentDecayDuration = 10f;
+ [Tooltip("Minimum weight (0..1) applied to a just-used prefab so it can still appear occasionally")]
+ [Range(0f, 1f)]
+ [SerializeField] private float minRecentWeight = 0.05f;
+
private IBirdPooperSettings settings;
private float spawnTimer;
private bool isSpawning;
@@ -53,6 +61,9 @@ namespace Minigames.BirdPooper
// difficulty tracking
private float _elapsedTime = 0f;
+ // recency tracking
+ private float[] _lastUsedTimes;
+
internal override void OnManagedAwake()
{
base.OnManagedAwake();
@@ -105,6 +116,17 @@ namespace Minigames.BirdPooper
// Clamp ramp duration
if (difficultyRampDuration < 0.01f) difficultyRampDuration = 0.01f;
+ // Clamp recency
+ if (recentDecayDuration < 0.01f) recentDecayDuration = 0.01f;
+ if (minRecentWeight < 0f) minRecentWeight = 0f;
+ if (minRecentWeight > 1f) minRecentWeight = 1f;
+
+ // Initialize last-used timestamps so prefabs start available (set to sufficiently negative so they appear with full weight)
+ int n = obstaclePrefabs != null ? obstaclePrefabs.Length : 0;
+ _lastUsedTimes = new float[n];
+ float initTime = -recentDecayDuration - 1f;
+ for (int i = 0; i < n; i++) _lastUsedTimes[i] = initTime;
+
Debug.Log("[ObstacleSpawner] Initialized successfully");
}
@@ -146,6 +168,7 @@ namespace Minigames.BirdPooper
///
/// Spawn a random obstacle at the spawn point position (Y = 0).
+ /// Uses timestamp/decay weighting so prefabs used recently are less likely.
///
private void SpawnObstacle()
{
@@ -161,8 +184,49 @@ namespace Minigames.BirdPooper
return;
}
- // Select random prefab
- GameObject selectedPrefab = obstaclePrefabs[Random.Range(0, obstaclePrefabs.Length)];
+ int count = obstaclePrefabs.Length;
+
+ // Defensive: ensure _lastUsedTimes is initialized and matches prefab count
+ if (_lastUsedTimes == null || _lastUsedTimes.Length != count)
+ {
+ _lastUsedTimes = new float[count];
+ float initTime = Time.time - recentDecayDuration - 1f;
+ for (int i = 0; i < count; i++) _lastUsedTimes[i] = initTime;
+ }
+
+ // compute weights based on recency (newer = lower weight)
+ float[] weights = new float[count];
+ float now = Time.time;
+ for (int i = 0; i < count; i++)
+ {
+ float age = now - _lastUsedTimes[i];
+ float normalized = Mathf.Clamp01(age / recentDecayDuration); // 0 = just used, 1 = fully recovered
+ float weight = Mathf.Max(minRecentWeight, normalized); // ensure minimum probability
+ weights[i] = weight; // base weight = 1.0, could be extended to per-prefab weights
+ }
+
+ // compute probabilities for logging
+ float totalW = 0f;
+ for (int i = 0; i < count; i++) totalW += Mathf.Max(0f, weights[i]);
+ if (totalW > 0f)
+ {
+ var sb = new StringBuilder();
+ sb.Append("[ObstacleSpawner] Prefab pick probabilities: ");
+ for (int i = 0; i < count; i++)
+ {
+ float p = weights[i] / totalW;
+ string name = obstaclePrefabs[i] != null ? obstaclePrefabs[i].name : i.ToString();
+ sb.AppendFormat("{0}:{1:P1}", name, p);
+ if (i < count - 1) sb.Append(", ");
+ }
+ Debug.Log(sb.ToString());
+ }
+
+ int chosenIndex = WeightedPickIndex(weights);
+ GameObject selectedPrefab = obstaclePrefabs[chosenIndex];
+
+ // record usage timestamp
+ _lastUsedTimes[chosenIndex] = Time.time;
// Spawn at spawn point position with Y = 0
Vector3 spawnPosition = new Vector3(spawnPoint.position.x, 0f, 0f);
@@ -183,6 +247,32 @@ namespace Minigames.BirdPooper
Debug.Log($"[ObstacleSpawner] Spawned obstacle '{selectedPrefab.name}' at position {spawnPosition}");
}
+ private int WeightedPickIndex(float[] weights)
+ {
+ int n = weights.Length;
+ float total = 0f;
+ for (int i = 0; i < n; i++)
+ {
+ if (weights[i] > 0f) total += weights[i];
+ }
+
+ if (total <= 0f)
+ {
+ return Random.Range(0, n);
+ }
+
+ float r = Random.value * total;
+ float acc = 0f;
+ for (int i = 0; i < n; i++)
+ {
+ acc += Mathf.Max(0f, weights[i]);
+ if (r <= acc)
+ return i;
+ }
+
+ return n - 1;
+ }
+
///
/// Start spawning obstacles.
/// Spawns the first obstacle immediately, then continues with interval-based spawning.
diff --git a/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
index 1d78b6ea..226a1aa1 100644
--- a/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
+++ b/Assets/Scripts/Minigames/BirdPooper/TargetSpawner.cs
@@ -34,6 +34,13 @@ namespace Minigames.BirdPooper
private IBirdPooperSettings settings;
private float spawnTimer;
private bool isSpawning;
+ private float _currentTargetInterval = 1f;
+
+ [Header("Spawn Timing")]
+ [Tooltip("Minimum interval between target spawns (seconds)")]
+ [SerializeField] private float minTargetSpawnInterval = 1f;
+ [Tooltip("Maximum interval between target spawns (seconds)")]
+ [SerializeField] private float maxTargetSpawnInterval = 2f;
internal override void OnManagedAwake()
{
@@ -44,7 +51,18 @@ namespace Minigames.BirdPooper
if (settings == null)
{
Debug.LogError("[TargetSpawner] BirdPooperSettings not found!");
- return;
+ // continue – we'll use inspector intervals
+ }
+
+ // Validate interval range
+ if (minTargetSpawnInterval < 0f) minTargetSpawnInterval = 0f;
+ if (maxTargetSpawnInterval < 0f) maxTargetSpawnInterval = 0f;
+ if (minTargetSpawnInterval > maxTargetSpawnInterval)
+ {
+ float tmp = minTargetSpawnInterval;
+ minTargetSpawnInterval = maxTargetSpawnInterval;
+ maxTargetSpawnInterval = tmp;
+ Debug.LogWarning("[TargetSpawner] minTargetSpawnInterval was greater than maxTargetSpawnInterval. Values were swapped.");
}
// Validate references
@@ -78,15 +96,17 @@ namespace Minigames.BirdPooper
private void Update()
{
- if (!isSpawning || settings == null)
+ if (!isSpawning)
return;
spawnTimer += Time.deltaTime;
- if (spawnTimer >= settings.TargetSpawnInterval)
+ if (spawnTimer >= _currentTargetInterval)
{
SpawnTarget();
spawnTimer = 0f;
+ // pick next random interval
+ _currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval);
}
}
@@ -153,6 +173,8 @@ namespace Minigames.BirdPooper
{
isSpawning = true;
spawnTimer = 0f;
+ // choose initial interval
+ _currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval);
Debug.Log("[TargetSpawner] Started spawning targets");
}
diff --git a/Assets/Settings/BirdPooperSettings.asset b/Assets/Settings/BirdPooperSettings.asset
index 79df099b..bcd58141 100644
--- a/Assets/Settings/BirdPooperSettings.asset
+++ b/Assets/Settings/BirdPooperSettings.asset
@@ -28,4 +28,4 @@ MonoBehaviour:
poopFallSpeed: 10
poopDestroyYPosition: -20
targetMoveSpeed: 8
- targetSpawnInterval: 20
+ targetSpawnInterval: 10