Done adding Obstacles and balancing BirdPooper

This commit is contained in:
2025-12-15 02:08:01 +01:00
parent bf04f93d72
commit e54b8805d8
9 changed files with 128 additions and 12 deletions

View File

@@ -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
/// <summary>
/// Spawn a random obstacle at the spawn point position (Y = 0).
/// Uses timestamp/decay weighting so prefabs used recently are less likely.
/// </summary>
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;
}
/// <summary>
/// Start spawning obstacles.
/// Spawns the first obstacle immediately, then continues with interval-based spawning.

View File

@@ -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");
}