



using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
// ============================================================
// WaveFlowGenerator
// ์ฌ๋ฌ ๊ฐ์ ๋ฆฌ๋ณธ ๋ (Band)๊ฐ ํ๋์น๋ฉฐ ํ๋ฅด๋ ์ ๋๋ ์ดํฐ๋ธ ์ํธ์ํฌ.
// ์ด ์ค๋ธ์ ํธ ์ = bandCount x segmentCount
// [ExecuteAlways] ๋ก ์๋ํฐ Scene ๋ทฐ์์๋ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ ๋์ํฉ๋๋ค.
// ============================================================
[ExecuteAlways]
public class WaveFlowGenerator : MonoBehaviour
{
// -----------------------------------------------------------
// Inspector ํ๋ผ๋ฏธํฐ
// -----------------------------------------------------------
[Header("๊ตฌ์กฐ ์ค์ (๋ณ๊ฒฝ ์ ์ฌ์์ฑ)")]
public int bandCount = 40; // ๋ฆฌ๋ณธ ๋ ๊ฐ์
public int segmentCount = 30; // ๋ ๋น ์ฟผ๋ ์ธ๊ทธ๋จผํธ ์
public float bandSpread = 5f; // ๋ฐด๋ ์ ์ฒด X์ถ ํผ์นจ ๋๋น
public float flowLength = 10f; // ํ๋ฆ ๋ฐฉํฅ(Y์ถ) ์ ์ฒด ๊ธธ์ด
public float segmentWidth = 0.07f; // ์ฟผ๋ ํ๋์ ๋๋น (๋ฆฌ๋ณธ ๋๊ป)
[Header("์จ์ด๋ธ ํํ")]
public float waveAmp1 = 0.15f; // 1์ฐจ ์จ์ด๋ธ ์งํญ (์ฃผ์ ๊ตด๊ณก)
public float waveFreq1 = 0.5f; // 1์ฐจ ์จ์ด๋ธ ์ฃผํ์
public float waveAmp2 = 0.4f; // 2์ฐจ ์จ์ด๋ธ ์งํญ (์ธ๋ถ ๊ตด๊ณก)
public float waveFreq2 = 2.0f; // 2์ฐจ ์จ์ด๋ธ ์ฃผํ์
public float twistAmount = 1.5f; // ๋ฐด๋ ๊ฐ ์์ ์ฐจ์ด (์ ์ฒด ๋นํ๋ฆผ๋)
[Header("์ ๋๋ฉ์ด์
")]
public float flowSpeed = 0.15f; // ํ๋ฆ ์งํ ์๋
public float waveSpeed = 0.8f; // ์จ์ด๋ธ ์ง๋ ์๋
[Header("์์")]
[Range(0f, 1f)] public float hueStart = 0.0f;
[Range(0f, 1f)] public float hueEnd = 0.0f;
[Range(0f, 1f)] public float saturation = 0.0f;
[Range(0f, 1f)] public float brightness = 1.0f;
[Range(0f, 3f)] public float emissionIntensity = 0.0f;
// -----------------------------------------------------------
// ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ์บ์
// -----------------------------------------------------------
private int _cachedBandCount;
private int _cachedSegmentCount;
private float _cachedBandSpread;
private float _cachedFlowLength;
private float _cachedSegmentWidth;
// -----------------------------------------------------------
// ์ธ๊ทธ๋จผํธ ๋ฐ์ดํฐ
// -----------------------------------------------------------
struct SegData
{
public Transform tr;
public Material mat;
public float tBand; // 0~1 (๋ฐด๋ ํก๋ฐฉํฅ ์์น)
public float tSeg; // 0~1 (์ธ๊ทธ๋จผํธ ํ๋ฆ ๋ฐฉํฅ ์์น)
public float phaseOffset; // ๋ฐด๋๋ณ ๊ณ ์ ์์
}
SegData[] _segs = new SegData[0];
Mesh _sharedQuadMesh;
// segmentCount ์์ ๊ณ์ฐ๋๋ ์ฟผ๋ ๋์ด
private float _segHeight;
#if UNITY_EDITOR
bool _generateQueued = false;
#endif
// -----------------------------------------------------------
// Unity ์ด๋ฒคํธ
// -----------------------------------------------------------
void OnEnable()
{
if (_segs.Length == 0)
Generate();
CacheStructureParams();
}
void OnDisable() => Clear();
void OnDestroy() => Clear();
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 bandCount != _cachedBandCount
|| segmentCount != _cachedSegmentCount
|| bandSpread != _cachedBandSpread
|| flowLength != _cachedFlowLength
|| segmentWidth != _cachedSegmentWidth;
}
void CacheStructureParams()
{
_cachedBandCount = bandCount;
_cachedSegmentCount = segmentCount;
_cachedBandSpread = bandSpread;
_cachedFlowLength = flowLength;
_cachedSegmentWidth = segmentWidth;
}
// -----------------------------------------------------------
// ์์ฑ
// -----------------------------------------------------------
void Generate()
{
Clear();
Shader litShader = Shader.Find("Universal Render Pipeline/Lit");
if (litShader == null)
{
Debug.LogError("[WaveFlowGenerator] URP Lit ์
ฐ์ด๋๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. URP ํ๋ก์ ํธ์ธ์ง ํ์ธํ์ธ์.");
return;
}
_sharedQuadMesh = GetBuiltinQuadMesh();
if (_sharedQuadMesh == null)
{
Debug.LogError("[WaveFlowGenerator] Quad Mesh๋ฅผ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.");
return;
}
// ์ธ๊ทธ๋จผํธ ์ฟผ๋ ๋์ด: ํ๋ฆ ๊ธธ์ด๋ฅผ ์ธ๊ทธ๋จผํธ ์๋ก ๊ท ๋ฑ ๋ถํ
_segHeight = flowLength / segmentCount;
int total = bandCount * segmentCount;
_segs = new SegData[total];
for (int b = 0; b < bandCount; b++)
{
float tBand = (bandCount > 1) ? (float)b / (bandCount - 1) : 0f;
float phase = tBand * twistAmount * Mathf.PI * 2f;
for (int s = 0; s < segmentCount; s++)
{
float tSeg = (segmentCount > 1) ? (float)s / (segmentCount - 1) : 0f;
int idx = b * segmentCount + s;
_segs[idx] = SpawnSegment(idx, tBand, tSeg, phase, litShader);
}
}
ApplyAnimation(0f);
}
// ์ฟผ๋๋ฅผ ์๋์ผ๋ก ์กฐ๋ฆฝํด BoxCollider ์์ฑ์ ๋ง์
SegData SpawnSegment(int index, float tBand, float tSeg, float phaseOffset, Shader shader)
{
GameObject go = new GameObject($"Seg_{index}");
go.transform.SetParent(transform, false);
go.transform.localScale = new Vector3(segmentWidth, _segHeight, 1f);
MeshFilter mf = go.AddComponent<MeshFilter>();
mf.sharedMesh = _sharedQuadMesh;
MeshRenderer mr = go.AddComponent<MeshRenderer>();
mr.shadowCastingMode = ShadowCastingMode.Off;
mr.receiveShadows = false;
Material mat = new Material(shader);
mat.EnableKeyword("_EMISSION");
mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive;
mr.material = mat;
return new SegData
{
tr = go.transform,
mat = mat,
tBand = tBand,
tSeg = tSeg,
phaseOffset = phaseOffset
};
}
// ์์ ์ค๋ธ์ ํธ๋ก ๋นํธ์ธ Quad Mesh ํ๋ ํ ์ฆ์ ํ๊ดด
Mesh GetBuiltinQuadMesh()
{
GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Quad);
Mesh mesh = temp.GetComponent<MeshFilter>().sharedMesh;
DestroyImmediate(temp);
return mesh;
}
// -----------------------------------------------------------
// ์ ๋ฆฌ
// -----------------------------------------------------------
void Clear()
{
if (_segs != null)
{
for (int i = 0; i < _segs.Length; i++)
{
if (_segs[i].mat != null)
DestroyImmediate(_segs[i].mat);
}
}
_segs = new SegData[0];
while (transform.childCount > 0)
{
#if UNITY_EDITOR
DestroyImmediate(transform.GetChild(0).gameObject);
#else
Destroy(transform.GetChild(0).gameObject);
#endif
}
}
// -----------------------------------------------------------
// ์ ๋๋ฉ์ด์
// -----------------------------------------------------------
void ApplyAnimation(float t)
{
float invSegCount = 1f / segmentCount;
for (int i = 0; i < _segs.Length; i++)
{
ref SegData seg = ref _segs[i];
if (seg.tr == null || seg.mat == null) continue;
// ํ์ฌ ์์น ๋ฐ ๋ค์ ์์น (ํ์ ํธ ๊ณ์ฐ์ฉ)
Vector3 pos0 = CalcPosition(seg.tBand, seg.tSeg, seg.phaseOffset, t);
Vector3 pos1 = CalcPosition(seg.tBand, seg.tSeg + invSegCount, seg.phaseOffset, t);
seg.tr.localPosition = pos0;
// ์ฟผ๋ ๋ก์ปฌ Y๋ฅผ ํ๋ฆ ๋ฐฉํฅ ํ์ ํธ์ ์ ๋ ฌ
Vector3 tangent = pos1 - pos0;
if (tangent.sqrMagnitude > 0.0001f)
seg.tr.localRotation = Quaternion.FromToRotation(Vector3.up, tangent.normalized);
// ์์
// tBand ๋ก Hue ๊ฒฐ์ , tSeg ์ผ๋ก ๋ฐ๊ธฐ ๋ณ์กฐ (๋๋จ์ ์ฝ๊ฐ ์ด๋ก๊ฒ)
float hue = Mathf.Lerp(hueStart, hueEnd, seg.tBand);
float brightnessVar = brightness * (0.55f + 0.45f * Mathf.Abs(Mathf.Sin(seg.tSeg * Mathf.PI)));
Color albedo = Color.HSVToRGB(hue, saturation, brightnessVar);
Color emission = Color.HSVToRGB(hue, 1f, brightnessVar) * emissionIntensity;
seg.mat.SetColor("_BaseColor", albedo);
seg.mat.SetColor("_EmissionColor", emission);
}
}
// ์ธ๊ทธ๋จผํธ ์์น ๊ณ์ฐ (ApplyAnimation ์์ ํ์ ํธ์ฉ์ผ๋ก ๋ ๋ฒ ํธ์ถ)
Vector3 CalcPosition(float tBand, float tSeg, float phase, float t)
{
// tFlow: ์๊ฐ์ ๋ฐ๋ผ ํ๋ฆ ๋ฐฉํฅ์ผ๋ก ์ง์ ์ด๋
float tFlow = tSeg + t * flowSpeed;
// 1์ฐจ ์จ์ด๋ธ: X ๋ฐฉํฅ ์ฃผ์ ๊ตด๊ณก
float wave1X = Mathf.Sin(tFlow * waveFreq1 * Mathf.PI * 2f + phase) * waveAmp1;
// 2์ฐจ ์จ์ด๋ธ: X ๋ฐฉํฅ ์ธ๋ถ ๊ตด๊ณก + ๋
๋ฆฝ ์ง๋
float wave2X = Mathf.Sin(tFlow * waveFreq2 * Mathf.PI * 2f + phase * 0.7f + t * waveSpeed) * waveAmp2;
// Z ๊น์ด ์จ์ด๋ธ: ์ฝ์ฌ์ธ (X์ 90๋ ์์ ์ฐจ์ด โ ๋์ ๊ฐ)
float wave1Z = Mathf.Cos(tFlow * waveFreq1 * Mathf.PI * 2f + phase) * waveAmp1 * 0.4f;
float wave2Z = Mathf.Cos(tFlow * waveFreq2 * Mathf.PI * 2f + phase * 0.7f + t * waveSpeed) * waveAmp2 * 0.3f;
float x = (tBand - 0.5f) * bandSpread + wave1X + wave2X;
float y = (tSeg - 0.5f) * flowLength;
float z = wave1Z + wave2Z;
return new Vector3(x, y, z);
}
}
์ด ์ฝ๋๋ ํฌ๊ฒ 4๊ฐ์ ๊ตฌํ์ผ๋ก ๋๋ฉ๋๋ค:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Unity ์ด๋ฒคํธ (๋ผ์ดํ์ฌ์ดํด ๊ด๋ฆฌ) โ โ ์ํ ๋ณํ ๊ฐ์ง, ์ด๊ธฐํ, ์ ๋ฆฌ
โ โข OnEnable/Disable/Destroy โ
โ โข OnValidate, Update โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 2. ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ๊ด๋ฆฌ (๋ถ๊ธฐ ๋ก์ง) โ โ "์ฌ์์ฑ ํ์ํ์ง?" ํ๋จ
โ โข StructureParamsChanged() โ
โ โข CacheStructureParams() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 3. ์ค๋ธ์ ํธ ์์ฑ (๊ฒ์ ์ค๋ธ์ ํธ ๊ตฌ์ถ) โ โ ์ค์ GameObject, Mesh, Material ์์ฑ
โ โข Generate(), SpawnSegment() โ
โ โข GetBuiltinQuadMesh() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 4. ์ ๋๋ฉ์ด์
(ํ๋ ์๋ณ ์๊ฐ ์
๋ฐ์ดํธ) โ โ ์์น/ํ์ /์์ ์ค์๊ฐ ๊ณ์ฐ
โ โข ApplyAnimation(), CalcPosition()โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
void OnEnable()void OnEnable() { if (_segs.Length == 0) Generate(); // ์ต์ด ์คํ ์ ๊ตฌ์กฐ ์์ฑ CacheStructureParams(); // ํ์ฌ ํ๋ผ๋ฏธํฐ๊ฐ ์บ์ฑ }
_segs ๋ฐฐ์ด์ด ๋น์ด์์ผ๋ฉด Generate() ๋ฅผ ํธ์ถํด ์ค๋ธ์ ํธ๋ค์ ์ฒ์ ์์ฑbandCount, segmentCount ๋ฑ)์ _cached~ ๋ณ์์ ์ ์ฅvoid OnDisenable() / void OnDestroy()void OnDisable() => Clear(); void OnDestroy() => Clear();
Clear() ๋ฅผ ํธ์ถํด ์์ฑ๋ ๋ชจ๋ ์์ ์ค๋ธ์ ํธ์ ๋จธํฐ๋ฆฌ์ผ์ ์ ๋ฆฌvoid OnValidate() โจ (์๋ํฐ ์ ์ฉ)void OnValidate() { #if UNITY_EDITOR if (StructureParamsChanged()) // ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ ๋ณ๊ฒฝ ๊ฐ์ง { CacheStructureParams(); QueueGenerate(); // ์ฌ์์ฑ ํ ๋ฑ๋ก (๋น๋๊ธฐ) } else { ApplyAnimation(0f); // ์์/์ ๋ ํ๋ผ๋ฏธํฐ๋ง ๋ณ๊ฒฝ โ ์ฆ์ ๋ฏธ๋ฆฌ๋ณด๊ธฐ } #endif }
- ์๋ฏธ: ์ธ์คํํฐ์์ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์๋ํฐ์์ ์๋ ํธ์ถ
- ์๋:
- ๊ตฌ์กฐ ํ๋ผ๋ฏธํฐ(
bandCount,segmentCount๋ฑ) ๋ณ๊ฒฝ โQueueGenerate()๋ก ์ฌ์์ฑ ์์ฝ- ์๊ฐ ํ๋ผ๋ฏธํฐ(์์, ์งํญ, ์๋ ๋ฑ) ๋ณ๊ฒฝ โ ์ฆ์
ApplyAnimation(0f)์ผ๋ก ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐฑ์- ๋ชฉ์ : ์๋ํฐ์์ ์ค์๊ฐ ํ๋ฆฌ๋ทฐ ์ ๊ณต + ๋ถํ์ํ ์ฌ์์ฑ ๋ฐฉ์ง
void QueueGenerate() โจ (์๋ํฐ ์ ์ฉ)void QueueGenerate() { if (_generateQueued) return; _generateQueued = true; EditorApplication.delayCall += () => // ๋ค์ ์๋ํฐ ํ๋ ์์ผ๋ก ์ง์ฐ { _generateQueued = false; if (this == null) return; Generate(); // ์ค์ ์ฌ์์ฑ ์คํ }; }
- ์๋ฏธ: ์ฌ์์ฑ ์์ ์ ์๋ํฐ์ ๋ค์ ํ๋ ์์ผ๋ก ๋ฏธ๋ฃธ
- ์๋:
_generateQueuedํ๋๊ทธ๋ก ์ค๋ณต ํ ๋ฐฉ์งEditorApplication.delayCall๋ก ์์ ์ค์ผ์ค๋ง- ์ค์
Generate()์คํ ์ ์ null ์ฒดํฌ๋ก ์์ ์ฑ ํ๋ณด
๋ชฉ์ : ์ธ์คํํฐ์์ ์ฌ๋ผ์ด๋๋ฅผ ๋น ๋ฅด๊ฒ ์์ง์ผ ๋ ๋งค ํ๋ ์๋ง๋ค ์ฌ์์ฑ๋๋ ์ฑ๋ฅ ์ ํ ๋ฐฉ์ง
void Update()void Update() { #if UNITY_EDITOR if (!Application.isPlaying) return; // ์๋ํฐ์์ ํ๋ ์ด ๋ชจ๋์ผ ๋๋ง ์คํ #endif ApplyAnimation(Time.time); // ๊ฒ์ ์๊ฐ์ ๊ธฐ๋ฐํ ์ ๋๋ฉ์ด์ ์ ์ฉ }
Application.isPlaying ์ด ์ฐธ์ผ ๋๋ง ์คํ (ํ๋ฆฌ๋ทฐ์ ํ๋ ์ด ๋ชจ๋ ๋ถ๋ฆฌ)Time.time ์ ApplyAnimation() ์ ์ ๋ฌํด ์๊ฐ์ ๋ฐ๋ฅธ ์จ์ด๋ธ ์ ๋๋ฉ์ด์
๊ณ์ฐbool StructureParamsChanged()bool StructureParamsChanged() { return bandCount != _cachedBandCount || segmentCount != _cachedSegmentCount || bandSpread != _cachedBandSpread || flowLength != _cachedFlowLength || segmentWidth != _cachedSegmentWidth; }
OnValidate() ์์ ์ฌ์์ฑ ํ์ ์ฌ๋ถ๋ฅผ ๊ฒฐ์ ํ๋ ํต์ฌ ๋ก์งvoid CacheStructureParams()void CacheStructureParams() { _cachedBandCount = bandCount; _cachedSegmentCount = segmentCount; // ... ๋๋จธ์ง ํ๋ผ๋ฏธํฐ๋ ๋์ผํ๊ฒ ์บ์ฑ }
_cached~ ํ๋์ ๋ณต์ฌvoid Generate()void Generate() { Clear(); // ๊ธฐ์กด ์ค๋ธ์ ํธ ์ ๋ฆฌ // 1. ์ ฐ์ด๋ ๋ก๋ฉ (URP Lit) Shader litShader = Shader.Find("Universal Render Pipeline/Lit"); // 2. ๊ธฐ๋ณธ ์ฟผ๋ ๋ฉ์ฌ ํ๋ _sharedQuadMesh = GetBuiltinQuadMesh(); // 3. ์ธ๊ทธ๋จผํธ ๋์ด ๊ณ์ฐ _segHeight = flowLength / segmentCount; // 4. ์ธ๊ทธ๋จผํธ ๋ฐฐ์ด ํ ๋น int total = bandCount * segmentCount; _segs = new SegData[total]; // 5. ์ด์ค ๋ฃจํ๋ก ๋ชจ๋ ์ธ๊ทธ๋จผํธ ์์ฑ for (int b = 0; b < bandCount; b++) { float tBand = (bandCount > 1) ? (float)b / (bandCount - 1) : 0f; float phase = tBand * twistAmount * Mathf.PI * 2f; // ๋ฐด๋๋ณ ์์ ์คํ์ for (int s = 0; s < segmentCount; s++) { float tSeg = (segmentCount > 1) ? (float)s / (segmentCount - 1) : 0f; int idx = b * segmentCount + s; _segs[idx] = SpawnSegment(idx, tBand, tSeg, phase, litShader); } } ApplyAnimation(0f); // ์ด๊ธฐ ์๊ฐ ์ํ ์ ์ฉ }
Clear())bandCount ร segmentCount ๋งํผ SpawnSegment() ํธ์ถtBand(0~1, ๊ฐ๋ก ์์น), tSeg(0~1, ์ธ๋ก ์์น), phase(์์์ฐจ) ํ๋ผ๋ฏธํฐ ๋ถ์ฌSegData SpawnSegment(...)SegData SpawnSegment(int index, float tBand, float tSeg, float phaseOffset, Shader shader) { // 1. GameObject ์์ฑ ๋ฐ ๊ณ์ธต ์ค์ GameObject go = new GameObject($"Seg_{index}"); go.transform.SetParent(transform, false); go.transform.localScale = new Vector3(segmentWidth, _segHeight, 1f); // 2. MeshFilter: ๊ณต์ ์ฟผ๋ ๋ฉ์ฌ ํ ๋น MeshFilter mf = go.AddComponent<MeshFilter>(); mf.sharedMesh = _sharedQuadMesh; // 3. MeshRenderer: ๊ทธ๋ฆผ์ ์ค์ ๋๊ธฐ MeshRenderer mr = go.AddComponent<MeshRenderer>(); mr.shadowCastingMode = ShadowCastingMode.Off; mr.receiveShadows = false; // 4. Material: ๋ฐ๊ด ํค์๋ ํ์ฑํ Material mat = new Material(shader); mat.EnableKeyword("_EMISSION"); mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.RealtimeEmissive; mr.material = mat; // 5. ๋ฐ์ดํฐ ๊ตฌ์กฐ์ฒด๋ก ๋ฐํ return new SegData { tr = go.transform, mat = mat, tBand = tBand, tSeg = tSeg, phaseOffset = phaseOffset }; }
_EMISSION) ํค์๋ ํ์ฑํ๋ก ๋ค์จ ๋๋ ๊ตฌํSegData ๊ตฌ์กฐ์ฒด๋ก ๋ฌถ์ด ๋ฐํMesh GetBuiltinQuadMesh()Mesh GetBuiltinQuadMesh()
{
GameObject temp = GameObject.CreatePrimitive(PrimitiveType.Quad);
Mesh mesh = temp.GetComponent<MeshFilter>().sharedMesh;
DestroyImmediate(temp); // ์์ ์ค๋ธ์ ํธ๋ ์ฆ์ ์ญ์
return mesh; // ๋ฉ์ฌ ์ฐธ์กฐ๋ง ๋ฐํ (Unity ๋ด์ฅ ๋ฆฌ์์ค)
}
void Clear()void Clear()
{
// 1. ๋ชจ๋ ๋จธํฐ๋ฆฌ์ผ ์๋ ์ญ์ (๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐฉ์ง)
if (_segs != null)
{
for (int i = 0; i < _segs.Length; i++)
{
if (_segs[i].mat != null)
DestroyImmediate(_segs[i].mat); // ์๋ํฐ์ฉ
}
}
// 2. ๋ฐฐ์ด ์ด๊ธฐํ
_segs = new SegData[0];
// 3. ๋ชจ๋ ์์ GameObject ์ญ์
while (transform.childCount > 0)
{
#if UNITY_EDITOR
DestroyImmediate(transform.GetChild(0).gameObject);
#else
Destroy(transform.GetChild(0).gameObject); // ๋ฐํ์์ฉ
#endif
}
}
Material ์ DestroyImmediate ๋ก ์ฆ์ ํด์ (์๋ํฐ์์ ์ค์)GameObject ๋ ์๋ํฐ/๋ฐํ์์ ๋ฐ๋ผ DestroyImmediate / Destroy ๋ถ๊ธฐ ์ฒ๋ฆฌvoid ApplyAnimation(float t)void ApplyAnimation(float t)
{
float invSegCount = 1f / segmentCount;
for (int i = 0; i < _segs.Length; i++)
{
ref SegData seg = ref _segs[i];
if (seg.tr == null || seg.mat == null) continue; // ์์ ์ฒดํฌ
// 1. ์์น ๊ณ์ฐ: ํ์ฌ์ ๋ค์ ์ง์ ์ผ๋ก ํ์ ํธ(๋ฐฉํฅ) ๊ตฌํจ
Vector3 pos0 = CalcPosition(seg.tBand, seg.tSeg, seg.phaseOffset, t);
Vector3 pos1 = CalcPosition(seg.tBand, seg.tSeg + invSegCount, seg.phaseOffset, t);
seg.tr.localPosition = pos0; // ์์น ์ ์ฉ
// 2. ํ์ : ์ฟผ๋์ Y ์ถ์ ํ๋ฆ ๋ฐฉํฅ(ํ์ ํธ)์ ์ ๋ ฌ
Vector3 tangent = pos1 - pos0;
if (tangent.sqrMagnitude > 0.0001f)
seg.tr.localRotation = Quaternion.FromToRotation(Vector3.up, tangent.normalized);
// 3. ์์: tBand ๋ก Hue ๊ทธ๋ผ๋ฐ์ด์
, tSeg ๋ก ๋ฐ๊ธฐ ๋ณ์กฐ
float hue = Mathf.Lerp(hueStart, hueEnd, seg.tBand);
float brightnessVar = brightness * (0.55f + 0.45f * Mathf.Abs(Mathf.Sin(seg.tSeg * Mathf.PI)));
Color albedo = Color.HSVToRGB(hue, saturation, brightnessVar);
Color emission = Color.HSVToRGB(hue, 1f, brightnessVar) * emissionIntensity;
seg.mat.SetColor("_BaseColor", albedo);
seg.mat.SetColor("_EmissionColor", emission);
}
}
CalcPosition() ์ผ๋ก 3D ์ขํ ๊ณ์ฐtBand ๋ก hueStart~hueEnd ๋ฅผ ๋ณด๊ฐํ์ฌ ๊ฐ๋ก ๋ฐฉํฅ ๊ทธ๋ผ๋ฐ์ด์
tSeg ๋ก ์ฌ์ธ ํจ์๋ฅผ ์ด์ฉํด ๋๋จ์ด ์ด๋์์ง๋ ๋ฐ๊ธฐ ๋ณ์กฐ_BaseColor(ํ๋ฉด) ์ _EmissionColor(๋ฐ๊ด) ๋ถ๋ฆฌ ์ค์ Vector3 CalcPosition(float tBand, float tSeg, float phase, float t)Vector3 CalcPosition(float tBand, float tSeg, float phase, float t)
{
// 1. ํ๋ฆ ๋ฐฉํฅ ์ด๋: ์๊ฐ์ ๋ฐ๋ผ tSeg ๊ฐ ์ฆ๊ฐ
float tFlow = tSeg + t * flowSpeed;
// 2. X ์ถ ์จ์ด๋ธ: 1 ์ฐจ(์ฃผ์) + 2 ์ฐจ(์ธ๋ถ) ์ฌ์ธ ํ ์ค์ฒฉ
float wave1X = Mathf.Sin(tFlow * waveFreq1 * Mathf.PI * 2f + phase) * waveAmp1;
float wave2X = Mathf.Sin(tFlow * waveFreq2 * Mathf.PI * 2f + phase * 0.7f + t * waveSpeed) * waveAmp2;
// 3. Z ์ถ ์จ์ด๋ธ: ์ฝ์ฌ์ธ (X ์ 90 ๋ ์์์ฐจ โ ๋์ ํ ํจ๊ณผ)
float wave1Z = Mathf.Cos(tFlow * waveFreq1 * Mathf.PI * 2f + phase) * waveAmp1 * 0.4f;
float wave2Z = Mathf.Cos(tFlow * waveFreq2 * Mathf.PI * 2f + phase * 0.7f + t * waveSpeed) * waveAmp2 * 0.3f;
// 4. ์ต์ข
์ขํ ์กฐํฉ
float x = (tBand - 0.5f) * bandSpread + wave1X + wave2X; // ๊ฐ๋ก ์์น + ์จ์ด๋ธ
float y = (tSeg - 0.5f) * flowLength; // ์ธ๋ก ๊ธฐ๋ณธ ์์น
float z = wave1Z + wave2Z; // ๊น์ด ์จ์ด๋ธ
return new Vector3(x, y, z);
}
tFlow: ์๊ฐ์ ๋ฐ๋ผ ํ๋ฅด๋ ํ๋ผ๋ฏธํฐ (์ ๋๋ฉ์ด์
์ ํต์ฌ)wave1X / wave2X: ์๋ก ๋ค๋ฅธ ์ฃผํ์/์งํญ์ ์ฌ์ธํ ์ค์ฒฉ์ผ๋ก ๋ณต์กํ ๊ตด๊ณก ์์ฑwave1Z / wave2Z: ์ฝ์ฌ์ธํ๋ก X ์ ์์์ฐจ๋ฅผ ์ฃผ์ด 3 ์ฐจ์ ๋์ ๊ฐ ํํphase: ๋ฐด๋๋ง๋ค ๋ค๋ฅธ ์์ ์คํ์
์ผ๋ก ์ ์ฒด์ ์ธ ๋นํ๋ฆผ ํจ๊ณผ