From e54b8805d8f87ab5c2fcd5916b477aba5aa2de68 Mon Sep 17 00:00:00 2001 From: Damian-Fools Date: Mon, 15 Dec 2025 02:08:01 +0100 Subject: [PATCH] Done adding Obstacles and balancing BirdPooper --- .../Difficulty1/Obstacle_Difficulty1_A.prefab | 2 +- .../Difficulty1/Obstacle_Difficulty1_C.prefab | 2 +- .../Difficulty1/Obstacle_Difficulty1_D.prefab | 2 +- .../Difficulty1/Obstacle_Difficulty1_E.prefab | 2 +- .../Difficulty1/Obstacle_Difficulty1_F.prefab | 2 +- Assets/Scenes/MiniGames/BirdPoop.unity | 6 +- .../Minigames/BirdPooper/ObstacleSpawner.cs | 94 ++++++++++++++++++- .../Minigames/BirdPooper/TargetSpawner.cs | 28 +++++- Assets/Settings/BirdPooperSettings.asset | 2 +- 9 files changed, 128 insertions(+), 12 deletions(-) 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