Done adding Obstacles and balancing BirdPooper
This commit is contained in:
@@ -46,7 +46,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Difficulty1_A
|
value: Obstacle_Difficulty1_A
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
||||||
propertyPath: m_LocalPosition.y
|
propertyPath: m_LocalPosition.y
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Obstacle_Difficulty1_BA
|
value: Obstacle_Difficulty1_C
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Obstacle_Difficulty1_BB
|
value: Obstacle_Difficulty1_D
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 2514399078413048981, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Obstacle_Difficulty1_BC
|
value: Obstacle_Difficulty1_E
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
- target: {fileID: 7518525353613855027, guid: ee834e7efcf7d8749881f71f8b0da99c, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ PrefabInstance:
|
|||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
- target: {fileID: 1863861968773980052, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
||||||
propertyPath: m_Name
|
propertyPath: m_Name
|
||||||
value: Obstacle_Difficulty1_C
|
value: Obstacle_Difficulty1_F
|
||||||
objectReference: {fileID: 0}
|
objectReference: {fileID: 0}
|
||||||
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
- target: {fileID: 4001830443026075700, guid: 0b18590141426314baa76ee19159178f, type: 3}
|
||||||
propertyPath: m_LocalPosition.x
|
propertyPath: m_LocalPosition.x
|
||||||
|
|||||||
@@ -788,7 +788,7 @@ MonoBehaviour:
|
|||||||
- {fileID: 1408173265900928789, guid: 65810bfd58ebbaf4482527452258ae50, type: 3}
|
- {fileID: 1408173265900928789, guid: 65810bfd58ebbaf4482527452258ae50, type: 3}
|
||||||
- {fileID: 1408173265900928789, guid: ae3986a7db087c845b618a9c897705ec, type: 3}
|
- {fileID: 1408173265900928789, guid: ae3986a7db087c845b618a9c897705ec, type: 3}
|
||||||
minSpawnInterval: 2
|
minSpawnInterval: 2
|
||||||
maxSpawnInterval: 9
|
maxSpawnInterval: 8
|
||||||
difficultyRampDuration: 360
|
difficultyRampDuration: 360
|
||||||
difficultyCurve:
|
difficultyCurve:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@@ -815,6 +815,8 @@ MonoBehaviour:
|
|||||||
m_PostInfinity: 2
|
m_PostInfinity: 2
|
||||||
m_RotationOrder: 4
|
m_RotationOrder: 4
|
||||||
intervalJitter: 0.3
|
intervalJitter: 0.3
|
||||||
|
recentDecayDuration: 60
|
||||||
|
minRecentWeight: 0.05
|
||||||
--- !u!1 &941621855
|
--- !u!1 &941621855
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -1839,6 +1841,8 @@ MonoBehaviour:
|
|||||||
cameraAdapter: {fileID: 2103114179}
|
cameraAdapter: {fileID: 2103114179}
|
||||||
targetPrefabs:
|
targetPrefabs:
|
||||||
- {fileID: 8373178063207716143, guid: 020f7494c613b06479ccad2c4cedde0f, type: 3}
|
- {fileID: 8373178063207716143, guid: 020f7494c613b06479ccad2c4cedde0f, type: 3}
|
||||||
|
minTargetSpawnInterval: 9
|
||||||
|
maxTargetSpawnInterval: 15
|
||||||
--- !u!1 &2103114174
|
--- !u!1 &2103114174
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Core;
|
|||||||
using Core.Settings;
|
using Core.Settings;
|
||||||
using Core.Lifecycle;
|
using Core.Lifecycle;
|
||||||
using AppleHillsCamera;
|
using AppleHillsCamera;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Minigames.BirdPooper
|
namespace Minigames.BirdPooper
|
||||||
{
|
{
|
||||||
@@ -45,6 +46,13 @@ namespace Minigames.BirdPooper
|
|||||||
[Tooltip("Maximum jitter applied to computed interval (fractional). 0.1 = +/-10% jitter")]
|
[Tooltip("Maximum jitter applied to computed interval (fractional). 0.1 = +/-10% jitter")]
|
||||||
[SerializeField] private float intervalJitter = 0.05f;
|
[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 IBirdPooperSettings settings;
|
||||||
private float spawnTimer;
|
private float spawnTimer;
|
||||||
private bool isSpawning;
|
private bool isSpawning;
|
||||||
@@ -53,6 +61,9 @@ namespace Minigames.BirdPooper
|
|||||||
// difficulty tracking
|
// difficulty tracking
|
||||||
private float _elapsedTime = 0f;
|
private float _elapsedTime = 0f;
|
||||||
|
|
||||||
|
// recency tracking
|
||||||
|
private float[] _lastUsedTimes;
|
||||||
|
|
||||||
internal override void OnManagedAwake()
|
internal override void OnManagedAwake()
|
||||||
{
|
{
|
||||||
base.OnManagedAwake();
|
base.OnManagedAwake();
|
||||||
@@ -105,6 +116,17 @@ namespace Minigames.BirdPooper
|
|||||||
// Clamp ramp duration
|
// Clamp ramp duration
|
||||||
if (difficultyRampDuration < 0.01f) difficultyRampDuration = 0.01f;
|
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");
|
Debug.Log("[ObstacleSpawner] Initialized successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +168,7 @@ namespace Minigames.BirdPooper
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Spawn a random obstacle at the spawn point position (Y = 0).
|
/// Spawn a random obstacle at the spawn point position (Y = 0).
|
||||||
|
/// Uses timestamp/decay weighting so prefabs used recently are less likely.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void SpawnObstacle()
|
private void SpawnObstacle()
|
||||||
{
|
{
|
||||||
@@ -161,8 +184,49 @@ namespace Minigames.BirdPooper
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select random prefab
|
int count = obstaclePrefabs.Length;
|
||||||
GameObject selectedPrefab = obstaclePrefabs[Random.Range(0, 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
|
// Spawn at spawn point position with Y = 0
|
||||||
Vector3 spawnPosition = new Vector3(spawnPoint.position.x, 0f, 0f);
|
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}");
|
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>
|
/// <summary>
|
||||||
/// Start spawning obstacles.
|
/// Start spawning obstacles.
|
||||||
/// Spawns the first obstacle immediately, then continues with interval-based spawning.
|
/// Spawns the first obstacle immediately, then continues with interval-based spawning.
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ namespace Minigames.BirdPooper
|
|||||||
private IBirdPooperSettings settings;
|
private IBirdPooperSettings settings;
|
||||||
private float spawnTimer;
|
private float spawnTimer;
|
||||||
private bool isSpawning;
|
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()
|
internal override void OnManagedAwake()
|
||||||
{
|
{
|
||||||
@@ -44,7 +51,18 @@ namespace Minigames.BirdPooper
|
|||||||
if (settings == null)
|
if (settings == null)
|
||||||
{
|
{
|
||||||
Debug.LogError("[TargetSpawner] BirdPooperSettings not found!");
|
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
|
// Validate references
|
||||||
@@ -78,15 +96,17 @@ namespace Minigames.BirdPooper
|
|||||||
|
|
||||||
private void Update()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (!isSpawning || settings == null)
|
if (!isSpawning)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
spawnTimer += Time.deltaTime;
|
spawnTimer += Time.deltaTime;
|
||||||
|
|
||||||
if (spawnTimer >= settings.TargetSpawnInterval)
|
if (spawnTimer >= _currentTargetInterval)
|
||||||
{
|
{
|
||||||
SpawnTarget();
|
SpawnTarget();
|
||||||
spawnTimer = 0f;
|
spawnTimer = 0f;
|
||||||
|
// pick next random interval
|
||||||
|
_currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +173,8 @@ namespace Minigames.BirdPooper
|
|||||||
{
|
{
|
||||||
isSpawning = true;
|
isSpawning = true;
|
||||||
spawnTimer = 0f;
|
spawnTimer = 0f;
|
||||||
|
// choose initial interval
|
||||||
|
_currentTargetInterval = Random.Range(minTargetSpawnInterval, maxTargetSpawnInterval);
|
||||||
Debug.Log("[TargetSpawner] Started spawning targets");
|
Debug.Log("[TargetSpawner] Started spawning targets");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,4 +28,4 @@ MonoBehaviour:
|
|||||||
poopFallSpeed: 10
|
poopFallSpeed: 10
|
||||||
poopDestroyYPosition: -20
|
poopDestroyYPosition: -20
|
||||||
targetMoveSpeed: 8
|
targetMoveSpeed: 8
|
||||||
targetSpawnInterval: 20
|
targetSpawnInterval: 10
|
||||||
|
|||||||
Reference in New Issue
Block a user