123 lines
3.8 KiB
C#
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);
|
|
}
|
|
}
|