

๐ Art_001_Spiral_Cube_Generator.cs
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class SpiralCubeGenerator : MonoBehaviour
{
[Header("๋์ ๊ตฌ์กฐ (๋ณ๊ฒฝ ์ ์ค๋ธ์ ํธ ์ฌ์์ฑ)")]
public int cubeCount = 2000;
public float angleStep = 0.5f;
public float radiusStep = 0.04f;
public float depthStep = 0.1f;
[Header("์ ๋๋ฉ์ด์
์ค์ ")]
public float rotationSpeed = 0.3f;
public float waveAmplitude = 0.2f;
public float waveFrequency = 2.0f;
[Header("์์")]
[Range(0f, 1f)] public float hueBase = 0.46f;
[Range(0f, 1f)] public float hueRange = 0.24f;
[Range(0f, 1f)] public float saturation = 0.4f;
[Range(0f, 1f)] public float brightness = 1.0f;
private int _cachedCubeCount;
private float _cachedAngleStep;
private float _cachedRadiusStep;
private float _cachedDepthStep;
struct CubeData
{
public Transform tr;
public Material mat;
public float baseAngle;
public float radius;
public float baseDepth;
public float normalized;
}
List<CubeData> _cubes = new List<CubeData>();
#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 cubeCount != _cachedCubeCount
|| angleStep != _cachedAngleStep
|| radiusStep != _cachedRadiusStep
|| depthStep != _cachedDepthStep;
}
void CacheStructureParams()
{
_cachedCubeCount = cubeCount;
_cachedAngleStep = angleStep;
_cachedRadiusStep = radiusStep;
_cachedDepthStep = depthStep;
}
void Generate()
{
Clear();
for (int i = 0; i < cubeCount; i++)
{
float t = (cubeCount > 1) ? (float)i / (cubeCount - 1) : 0f;
float angle = i * angleStep;
float r = i * radiusStep;
float z = i * depthStep;
Vector3 pos = new Vector3(
Mathf.Cos(angle) * r,
Mathf.Sin(angle) * r,
z
);
SpawnCube(pos, angle, r, z, t);
}
ApplyAnimation(0f);
}
void SpawnCube(Vector3 pos, float angle, float radius, float depth, float normalized)
{
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
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;
_cubes.Add(new CubeData
{
tr = go.transform,
mat = mat,
baseAngle = angle,
radius = radius,
baseDepth = depth,
normalized = normalized
});
}
void Clear()
{
_cubes.Clear();
while (transform.childCount > 0)
DestroyImmediate(transform.GetChild(0).gameObject);
}
void ApplyAnimation(float t)
{
for (int i = 0; i < _cubes.Count; i++)
{
CubeData c = _cubes[i];
if (c.tr == null || c.mat == null) continue;
float animAngle = c.baseAngle + t * rotationSpeed;
float wave = Mathf.Sin(c.normalized * waveFrequency * Mathf.PI * 2f - t) * waveAmplitude;
c.tr.localPosition = new Vector3(
Mathf.Cos(animAngle) * c.radius,
Mathf.Sin(animAngle) * c.radius,
c.baseDepth + wave
);
c.tr.localRotation = Quaternion.Euler(0f, 0f, animAngle * Mathf.Rad2Deg);
float hue = (hueBase + c.normalized * hueRange) % 1f;
Color albedo = Color.HSVToRGB(hue, saturation, brightness);
Color emission = Color.HSVToRGB(hue, 1f, c.normalized) * 0.5f;
c.mat.SetColor("_BaseColor", albedo);
c.mat.SetColor("_EmissionColor", emission);
}
}
}
๐Art_001_Spiral_Cube_Generator_Ver2.cs
using UnityEngine;
using System.Collections.Generic;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class SpiralCubeGeneratorVer2 : MonoBehaviour
{
[Header("๋์ ๊ตฌ์กฐ (๋ณ๊ฒฝ ์ ์ค๋ธ์ ํธ ์ฌ์์ฑ)")]
public int cubeCount = 2000;
public float angleStep = 0.5f;
public float radiusStep = 0.04f;
public float depthStep = 0.1f;
[Header("ํ๋ธ ํฌ๊ธฐ")]
public float cubeScale = 0.15f;
[Header("์ ๋๋ฉ์ด์
์ค์ ")]
public float rotationSpeed = 0.3f;
public float waveAmplitude = 0.2f;
public float waveFrequency = 2.0f;
[Header("์์")]
[Range(0f, 1f)] public float hueBase = 0.46f;
[Range(0f, 1f)] public float hueRange = 0.24f;
[Range(0f, 1f)] public float saturation = 0.4f;
[Range(0f, 1f)] public float brightness = 1.0f;
private int _cachedCubeCount;
private float _cachedAngleStep;
private float _cachedRadiusStep;
private float _cachedDepthStep;
private float _cachedCubeScale;
struct CubeData
{
public Transform tr;
public Material mat;
public float baseAngle;
public float radius;
public float baseDepth;
public float normalized;
}
CubeData[] _cubes = new CubeData[0];
private Mesh _sharedCubeMesh;
private float _waveFreqFull;
#if UNITY_EDITOR
private bool _generateQueued = false;
#endif
void OnEnable()
{
if (_cubes.Length == 0)
Generate();
CacheStructureParams();
CacheWaveFreq();
}
void OnDisable()
{
Clear();
}
void OnDestroy()
{
Clear();
}
void OnValidate()
{
#if UNITY_EDITOR
CacheWaveFreq();
if (StructureParamsChanged())
{
CacheStructureParams();
QueueGenerate();
}
else
{
ApplyAnimation(0f);
}
#endif
}
void Update()
{
#if UNITY_EDITOR
if (!Application.isPlaying) return;
#endif
ApplyAnimation(Time.time);
}
#if UNITY_EDITOR
void QueueGenerate()
{
if (_generateQueued) return;
_generateQueued = true;
EditorApplication.delayCall += () =>
{
_generateQueued = false;
if (this == null) return;
Generate();
};
}
#endif
bool StructureParamsChanged()
{
return cubeCount != _cachedCubeCount
|| angleStep != _cachedAngleStep
|| radiusStep != _cachedRadiusStep
|| depthStep != _cachedDepthStep
|| cubeScale != _cachedCubeScale;
}
void CacheStructureParams()
{
_cachedCubeCount = cubeCount;
_cachedAngleStep = angleStep;
_cachedRadiusStep = radiusStep;
_cachedDepthStep = depthStep;
_cachedCubeScale = cubeScale;
}
void CacheWaveFreq()
{
_waveFreqFull = waveFrequency * Mathf.PI * 2f;
}
void Generate()
{
Clear();
Shader litShader = Shader.Find("Universal Render Pipeline/Lit");
if (litShader == null)
{
Debug.LogError("[SpiralCubeGenerator] URP Lit ์
ฐ์ด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. URP ํ๋ก์ ํธ์ธ์ง ํ์ธํ์ธ์.");
return;
}
_sharedCubeMesh = GetBuiltinCubeMesh();
if (_sharedCubeMesh == null)
{
Debug.LogError("[SpiralCubeGenerator] ๊ธฐ๋ณธ ํ๋ธ Mesh ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.");
return;
}
_cubes = new CubeData[cubeCount];
for (int i = 0; i < cubeCount; i++)
{
float t = (cubeCount > 1) ? (float)i / (cubeCount - 1) : 0f;
float angle = i * angleStep;
float r = i * radiusStep;
float z = i * depthStep;
Vector3 pos = new Vector3(
Mathf.Cos(angle) * r,
Mathf.Sin(angle) * r,
z
);
_cubes[i] = SpawnCube(i, pos, angle, r, z, t, litShader);
}
ApplyAnimation(0f);
}
CubeData SpawnCube(int index, Vector3 pos, float angle, float radius, float depth, float normalized, Shader shader)
{
GameObject go = new GameObject($"Cube_{index}");
go.transform.SetParent(transform, false);
go.transform.localPosition = pos;
go.transform.localScale = Vector3.one * cubeScale;
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.sharedMesh = _sharedCubeMesh;
MeshRenderer mr = go.AddComponent<MeshRenderer>();
Material mat = new Material(shader);
mat.EnableKeyword("_EMISSION");
mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
mr.material = mat;
return new CubeData
{
tr = go.transform,
mat = mat,
baseAngle = angle,
radius = radius,
baseDepth = depth,
normalized = normalized
};
}
Mesh GetBuiltinCubeMesh()
{
GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Cube);
Mesh mesh = temp.GetComponent<MeshFilter>().sharedMesh;
DestroyImmediate(temp);
return mesh;
}
void Clear()
{
if (_cubes != null)
{
for (int i = 0; i < _cubes.Length; i++)
{
if (_cubes[i].mat != null)
DestroyImmediate(_cubes[i].mat);
}
}
_cubes = new CubeData[0];
while (transform.childCount > 0)
{
#if UNITY_EDITOR
DestroyImmediate(transform.GetChild(0).gameObject);
#else
Destroy(transform.GetChild(0).gameObject);
#endif
}
}
void ApplyAnimation(float t)
{
for (int i = 0; i < _cubes.Length; i++)
{
ref CubeData c = ref _cubes[i];
if (c.tr == null || c.mat == null) continue;
float animAngle = c.baseAngle + t * rotationSpeed;
float wave = Mathf.Sin(c.normalized * _waveFreqFull - t) * waveAmplitude;
c.tr.localPosition = new Vector3(
Mathf.Cos(animAngle) * c.radius,
Mathf.Sin(animAngle) * c.radius,
c.baseDepth + wave
);
c.tr.localRotation = Quaternion.Euler(0f, 0f, animAngle * Mathf.Rad2Deg);
float hue = (hueBase + c.normalized * hueRange) % 1f;
Color albedo = Color.HSVToRGB(hue, saturation, brightness);
Color emission = Color.HSVToRGB(hue, 1f, c.normalized) * 0.5f;
c.mat.SetColor("_BaseColor", albedo);
c.mat.SetColor("_EmissionColor", emission);
}
}
}