Files
2026-01-31 12:01:32 -06:00

123 lines
3.8 KiB
C#

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<Vector2> recentPositions = new Queue<Vector2>();
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);
}
}