using UnityEngine; using System.Collections.Generic; public class TreatSpawner : MonoBehaviour { [SerializeField] private GameObject treatPrefab; [SerializeField] private Transform treatParent; [SerializeField] private Vector2 spawnAreaCenter = Vector2.zero; [SerializeField] private Vector2 spawnAreaSize = new Vector2(10f, 5f); [SerializeField] private LayerMask groundMask = ~0; [SerializeField] private float groundRaycastHeight = 10f; [SerializeField] private float minSpawnInterval = 1f; [SerializeField] private float maxSpawnInterval = 2f; [SerializeField] private int maxTreats = 0; [SerializeField] private float minSpawnSeparation = 1.5f; [SerializeField] private int maxSampleAttempts = 8; [SerializeField] private int recentPositionsCount = 6; private float nextSpawnTime; private readonly Queue recentPositions = new Queue(); private void OnEnable() { ScheduleNextSpawn(); } private void Update() { if (treatPrefab == null || Time.time < nextSpawnTime) { return; } if (maxTreats > 0 && treatParent != null && treatParent.childCount >= maxTreats) { ScheduleNextSpawn(); return; } SpawnTreat(); ScheduleNextSpawn(); } private void SpawnTreat() { var position = FindSpawnPosition(); var instance = Instantiate(treatPrefab, position, Quaternion.identity); if (treatParent != null) { instance.transform.SetParent(treatParent); } if (recentPositionsCount > 0) { recentPositions.Enqueue(position); while (recentPositions.Count > recentPositionsCount) { recentPositions.Dequeue(); } } } private void ScheduleNextSpawn() { var min = Mathf.Max(0.01f, minSpawnInterval); var max = Mathf.Max(min, maxSpawnInterval); nextSpawnTime = Time.time + Random.Range(min, max); } private Vector2 FindSpawnPosition() { var halfSize = spawnAreaSize * 0.5f; var attempts = Mathf.Max(1, maxSampleAttempts); for (var i = 0; i < attempts; i++) { var randomX = Random.Range(spawnAreaCenter.x - halfSize.x, spawnAreaCenter.x + halfSize.x); var rayStart = new Vector2(randomX, spawnAreaCenter.y + groundRaycastHeight); var hit = Physics2D.Raycast(rayStart, Vector2.down, groundRaycastHeight * 2f, groundMask); var candidate = hit.collider != null ? hit.point : new Vector2(randomX, spawnAreaCenter.y); if (IsFarEnough(candidate)) { return candidate; } } var fallbackX = Random.Range(spawnAreaCenter.x - halfSize.x, spawnAreaCenter.x + halfSize.x); var fallbackStart = new Vector2(fallbackX, spawnAreaCenter.y + groundRaycastHeight); var fallbackHit = Physics2D.Raycast(fallbackStart, Vector2.down, groundRaycastHeight * 2f, groundMask); return fallbackHit.collider != null ? fallbackHit.point : new Vector2(fallbackX, spawnAreaCenter.y); } private bool IsFarEnough(Vector2 candidate) { if (minSpawnSeparation <= 0f || recentPositions.Count == 0) { return true; } foreach (var position in recentPositions) { if ((candidate - position).sqrMagnitude < minSpawnSeparation * minSpawnSeparation) { return false; } } return true; } private void OnDrawGizmosSelected() { Gizmos.color = new Color(1f, 0.85f, 0.1f, 0.35f); Gizmos.DrawWireCube(spawnAreaCenter, spawnAreaSize); } }