

๐ RandomSphereGenerator.cs
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class RandomSphereGenerator : MonoBehaviour
{
[Header("๊ตฌ์ฒด ๊ตฌ์กฐ (๋ณ๊ฒฝ ์ ์ค๋ธ์ ํธ ์ฌ์์ฑ)")]
public int sphereCount = 500;
public float spawnRange = 5f;
public int randomSeed = 42;
[Header("๋ถ์ ์ ๋๋ฉ์ด์
์ค์ ")]
public float floatSpeed = 0.8f;
public float floatAmplitude = 0.4f;
public float phaseSpread = 3.0f;
[Header("ํฌ๊ธฐ ์ค์ ")]
public float scaleMin = 0.15f;
public float scaleMax = 0.6f;
[Header("์์")]
[Range(0f, 1f)] public float hueBase = 0.6f;
[Range(0f, 1f)] public float hueRange = 0.4f;
[Range(0f, 1f)] public float saturation = 0.75f;
[Range(0f, 1f)] public float brightness = 1.0f;
private int _cachedSphereCount;
private float _cachedSpawnRange;
private int _cachedRandomSeed;
struct SphereData
{
public Transform tr;
public Material mat;
public Vector3 basePos;
public float phase;
public float normalized;
}
List<SphereData> _spheres = new List<SphereData>();
#if UNITY_EDITOR
bool _generateQueued = false;
#endif
void OnEnable()
{
Generate();
CacheStructureParams();
}
void OnValidate()
{
#if UNITY_EDITOR
if (StructureParamsChanged())
{
CacheStructureParams();
QueueGenerate();
}
else
{
ApplyAnimation(0f);
}
#endif
}
#if UNITY_EDITOR
void QueueGenerate()
{
if (_generateQueued) return;
_generateQueued = true;
EditorApplication.delayCall += () =>
{
_generateQueued = false;
if (this == null) return;
Generate();
};
}
#endif
void Update()
{
#if UNITY_EDITOR
if (!Application.isPlaying) return;
#endif
ApplyAnimation(Time.time);
}
bool StructureParamsChanged()
{
return sphereCount != _cachedSphereCount
|| spawnRange != _cachedSpawnRange
|| randomSeed != _cachedRandomSeed;
}
void CacheStructureParams()
{
_cachedSphereCount = sphereCount;
_cachedSpawnRange = spawnRange;
_cachedRandomSeed = randomSeed;
}
void Generate()
{
Clear();
Random.InitState(randomSeed);
for (int i = 0; i < sphereCount; i++)
{
float t = (sphereCount > 1) ? (float)i / (sphereCount - 1) : 0f;
Vector3 pos = new Vector3(
Random.Range(-spawnRange, spawnRange),
Random.Range(-spawnRange, spawnRange),
Random.Range(-spawnRange, spawnRange)
);
float phase = Random.value * phaseSpread;
SpawnSphere(pos, phase, t);
}
ApplyAnimation(0f);
}
void SpawnSphere(Vector3 pos, float phase, float normalized)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Sphere);
go.transform.SetParent(transform);
go.transform.localPosition = pos;
Material mat = new Material(Shader.Find("Universal Render Pipeline/Lit"));
mat.EnableKeyword("_EMISSION");
mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
go.GetComponent<Renderer>().material = mat;
_spheres.Add(new SphereData
{
tr = go.transform,
mat = mat,
basePos = pos,
phase = phase,
normalized = normalized
});
}
void Clear()
{
_spheres.Clear();
while (transform.childCount > 0)
DestroyImmediate(transform.GetChild(0).gameObject);
}
void ApplyAnimation(float t)
{
for (int i = 0; i < _spheres.Count; i++)
{
SphereData s = _spheres[i];
if (s.tr == null || s.mat == null) continue;
float bob = Mathf.Sin(t * floatSpeed + s.phase) * floatAmplitude;
s.tr.localPosition = new Vector3(
s.basePos.x,
s.basePos.y + bob,
s.basePos.z
);
float bobNormalized = (bob / floatAmplitude + 1f) * 0.5f;
float scale = Mathf.Lerp(scaleMin, scaleMax, bobNormalized);
s.tr.localScale = Vector3.one * scale;
float hue = (hueBase + s.normalized * hueRange) % 1f;
Color albedo = Color.HSVToRGB(hue, saturation, brightness);
Color emission = Color.HSVToRGB(hue, 1f, bobNormalized) * 0.5f;
s.mat.SetColor("_BaseColor", albedo);
s.mat.SetColor("_EmissionColor", emission);
}
}
}