109 lines
3.8 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using Fusion;
using UnityEngine;
namespace TPSBR
{
public sealed class LootBoxSpawner : NetworkBehaviour
{
[Header("Prefab")]
[SerializeField] private NetworkPrefabRef _itemBoxPrefab;
[Header("Placement")]
[Min(1)] [SerializeField] private int _count = 10;
[Min(0f)] [SerializeField] private float _radius = 40f;
[Min(0f)] [SerializeField] private float _minSpacing = 3f;
[SerializeField] private LayerMask _groundMask = ~0;
[SerializeField] private float _raycastUp = 60f;
[SerializeField] private float _raycastDown = 120f;
[SerializeField] private uint _seed = 12345;
[SerializeField] private bool _randomYaw = true;
// Keep references to despawn/respawn later if you extend it
private readonly List<NetworkObject> _spawned = new();
public override void Spawned()
{
if (!HasStateAuthority) return;
SpawnAll();
}
private void SpawnAll()
{
// If this is re-entered (e.g., soft reset), clean previous
foreach (var no in _spawned)
if (no && no.IsValid) Runner.Despawn(no);
_spawned.Clear();
var rnd = new System.Random(unchecked((int)_seed));
var positions = SamplePositions(transform.position, _radius, _count, _minSpacing, rnd);
foreach (var p in positions)
{
var rot = _randomYaw ? Quaternion.Euler(0f, (float)rnd.NextDouble() * 360f, 0f) : Quaternion.identity;
// Spawn server-side; replicated to all clients
var obj = Runner.Spawn(_itemBoxPrefab, p, rot);
_spawned.Add(obj);
}
}
// Deterministic sampling inside a circle with ground projection + spacing
private List<Vector3> SamplePositions(Vector3 center, float radius, int count, float minSpacing, System.Random rnd)
{
var results = new List<Vector3>(count);
var maxTrials = 2500; // generous to pack in cluttered areas
int trials = 0;
while (results.Count < count && trials++ < maxTrials)
{
// pick point in disc (sqrt for uniform)
float r = radius * Mathf.Sqrt((float)rnd.NextDouble());
float ang = Mathf.PI * 2f * (float)rnd.NextDouble();
Vector3 local = new Vector3(Mathf.Cos(ang) * r, 0f, Mathf.Sin(ang) * r);
Vector3 rayFrom = center + local + Vector3.up * _raycastUp;
if (Physics.Raycast(rayFrom, Vector3.down, out var hit, _raycastUp + _raycastDown, _groundMask, QueryTriggerInteraction.Ignore))
{
var pos = hit.point;
// spacing test
bool ok = true;
if (minSpacing > 0f)
{
for (int i = 0; i < results.Count; i++)
{
if (Vector3.SqrMagnitude(results[i] - pos) < minSpacing * minSpacing) { ok = false; break; }
}
}
if (ok) results.Add(pos);
}
}
if (results.Count < count)
Debug.LogWarning($"[LootBoxSpawner] Only placed {results.Count}/{count}. Increase radius or lower spacing.");
return results;
}
// Scene gizmo previews the exact deterministic layout youll get at runtime
private void OnDrawGizmosSelected()
{
Gizmos.color = new Color(0.1f, 0.8f, 1f, 0.25f);
Gizmos.DrawWireSphere(transform.position, _radius);
// preview sample using same seed
var rnd = new System.Random(unchecked((int)_seed));
var preview = SamplePositions(transform.position, _radius, _count, _minSpacing, rnd);
Gizmos.color = new Color(0.1f, 0.8f, 1f, 0.7f);
foreach (var p in preview)
{
Gizmos.DrawSphere(p + Vector3.up * 0.15f, 0.25f);
Gizmos.DrawLine(p + Vector3.up * 3f, p); // drop line
}
}
}
}