



์ด ํํฐํด ์์คํ ์ ๋ค์๊ณผ ๊ฐ์ ํต์ฌ ๋ก์ง์ผ๋ก ์๋ํฉ๋๋ค.
Random.onUnitSphere๋ฅผ ์ฌ์ฉํญ์ฌ ๋จ์ ๊ตฌ(๋ฐ์ง๋ฆ 1) ํ๋ฉด์ ๋๋คํ ์ขํ๋ฅผ ์์ฑ.sphereRadius๋ฅผ ๊ณฑํ์ฌ ์ํ๋ ํฌ๊ธฐ์ ๊ตฌ ํ๋ฉด ์์ ํํฐํด์ ๋ฐฐ์น.normalized(์ ๊ทํ)ํ์ฌ ๋ฐฉํฅ ๋ฒกํฐ๋ง ์ถ์ถ.sphereRadius๋ฅผ ๋ค์ ๊ณฑํจ์ผ๋ก์จ, ํํฐํด์ด ํญ์ ๊ตฌ์ ํ๋ฉด(์์ ์์ ์ผ์ ํ ๊ฑฐ๋ฆฌ) ์์ ์์นํ๋๋ก ๊ฐ์ ํจ.Mathf.PerlinNoise๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ํํฐํด์ ๊ณ ์ ํ๋ฉด์๋ ์์ฐ์ค๋ฌ์ด ์คํ์
๊ฐ์ ์์ฑ.i)์ ๊ณ ์ ํ ์์ ๊ณ์๋ฅผ ๊ณฑํ์ฌ ์๋ ๊ฐ์ ๋ค๋ฅด๊ฒ ์ค์ ํจ์ผ๋ก์จ, ๊ฐ ํํฐํด์ด ์๋ก ๋ค๋ฅธ ํจํด์ผ๋ก ์์ง์ด๋๋ก ํจ.LateUpdate์์ ๋งค ํ๋ ์ remainingLifetime์ ๊ฐ์ ๋ก lifetime์ผ๋ก ์ฌ์ค์ .์คํฌ๋ฆฝํธ๋ฅผ ์ ์ฉํ๊ธฐ ์ , Unity ์๋ํฐ์์ ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํด์ผ ํจ.
GenerativeSphereParticles.cs๋ฅผ ํ๋ก์ ํธ์ ์ถ๊ฐํ๊ณ , ์ค๋ธ์ ํธ์ ๋๋๊ทธํ์ฌ ์ฒจ๋ถ.์คํฌ๋ฆฝํธ์ SetupParticleSystem ๋ฉ์๋๊ฐ Start์์ ์ ๋๋ถ๋ถ์ ์ค์ ์ ๋ฎ์ด์ฐ์ง๋ง, ์ด๊ธฐ ์ธํ
์ ๋ช
ํํ ํ๊ธฐ ์ํด ๋ค์ ํญ๋ชฉ๋ค์ ์ฒดํฌ.
Start Lifetime : ์คํฌ๋ฆฝํธ ๋ณ์ lifetime์ผ๋ก ์ ์ด๋๋ฏ๋ก ์์์ ๊ฐ ์ค์ ๊ฐ๋ฅ.Start Size : ์คํฌ๋ฆฝํธ ๋ณ์ particleSize๋ก ์ ์ด.Emission over Time : ์ฒดํฌ ํด์ (์คํฌ๋ฆฝํธ์์ emission.enabled = false๋ก ์ค์ ํ์ฌ ์๋ ๋ฐฉ์ถ์ ๋. ํํฐํด์ ์คํฌ๋ฆฝํธ ์์ ์ ํ ๋ฒ๋ง ์์ฑ๋จ).Shape : ์ฒดํฌ ํด์ (์คํฌ๋ฆฝํธ์์ ์ง์ ์์น๋ฅผ ๊ณ์ฐํ๋ฏ๋ก ๊ธฐ๋ณธ ์
ฐ์ดํ ๋ฐฉ์ถ ๊ธฐ๋ฅ์ ์ฌ์ฉ ์ํจ).Render Mode : Billboard (์นด๋ฉ๋ผ๋ฅผ ํญ์ ๋ฐ๋๋ณด๋๋ก ์ค์ )Material : URP Lit์ ๊ฐ์ง ๋จธํฐ๋ฆฌ์ผ์ ํ ๋น.ํํฐํด์ด ํฌ๋ช ๋๋ฅผ ๊ฐ์ง๊ณ ์ฌ๋ฐ๋ฅด๊ฒ ๋ ๋๋ง๋๊ธฐ ์ํ ์ค์ .
Default ๋๋ Sprite (2D and UI)๋ก ์ค์ ๋์ด ์๋์ง ํ์ธ.Input Texture Alpha๋ก ์ค์ .Apply ๋ฒํผ์ ๋๋ฌ ์ค์ ์ ์ ์ฅ.Universal Render Pipeline/Lit๋ก ์ค์ .Opaque๊ฐ ์๋ Transparent๋ก ์ค์ ๋์ด ์๋์ง ํ์ธ.Renderer > Material ์ฌ๋กฏ์ ํ ๋น.์ค๋ธ์ ํธ๋ฅผ ์ ํํ ์ํ์์ Inspector์ Generative Sphere Particles ์ปดํฌ๋ํธ ๋ณ์๋ฅผ ์กฐ์ ํ์ฌ ์ํ๋ ์๊ฐ ํจ๊ณผ๋ฅผ ์์ฑ.
Settings
Max Particles: ์์ฑํ ํํฐํด์ ์ต๋ ๊ฐ์Sphere Radius: ๊ตฌ์ ๋ฐ์ง๋ฆParticle Size: ๊ฐ๋ณ ํํฐํด์ ํฌ๊ธฐLifetime: ํํฐํด์ ์๋ช
(์คํฌ๋ฆฝํธ์์ ์ฌ์์ฑ๋๋ฏ๋ก ์๊ฐ์ ์ธ ๊ฐฑ์ ์ฃผ๊ธฐ์ ์ํฅ)Noise & Motion
Noise Strength: ๋
ธ์ด์ฆ์ ์ํ ์์ง์์ ์งํญ (ํด์๋ก ๋ฉ๋ฆฌ ์์ง์)Noise Frequency: ๋
ธ์ด์ฆ์ ์ฃผํ์ (ํด์๋ก ๋ ์๊ฒ, ๋น ๋ฅด๊ฒ ๋ณํ)Drift Speed: ํํฐํด์ด ํ๋ฅด๋ ์๋using System.Numerics;
using UnityEngine;
// ์ด ์คํฌ๋ฆฝํธ๊ฐ ์ฒจ๋ถ๋ ๊ฒ์ ์ค๋ธ์ ํธ์๋ ๋ฐ๋์ ParticleSystem ์ปดํฌ๋ํธ๊ฐ ์์ด์ผ ํจ์ ๋ช
์
[RequireComponent(typeof(ParticleSystem))]
public class GenerativeSphereParticles : MonoBehaviour
{
// ==============================================================================
// [Inspector ๋ณ์ : ํํฐํด ์์ฑ]
// ==============================================================================
[Header("Settings")]
public int maxParticles = 5000; // ํํฐํด ์ต๋ ๊ฐ์
public float sphereRadius = 5f; // ํํฐํด ๋ถํฌ ๊ตฌ์ ๋ฐ์ง๋ฆ
public float partcleSize = 0.1f; // ๊ฐ๋ณ ํํฐํด์ ์์ ํฌ๊ธฐ
public float lifetime = 5f; // ํํฐํด ์์กด ์๊ฐ (์ด)
// ==============================================================================
// [Inspector ๋ณ์ : ๋
ธ์ด์ฆ ๋ฐ ๋ชจ์
]
// ==============================================================================
[Header("Noise & Motion")]
public float noiseStrength = 1.8f; // ๋
ธ์ด์ฆ๊ฐ ์์น ๋ณ๋์ ๋ฏธ์น๋ ์ํฅ๋ ฅ (์งํญ)
public float noiseFrequency = 2.4f; // ๋
ธ์ด์ฆ ํจ์์ ์ฃผํ์ (์์ง์์ ๋ฐ๋/๋น๋)
public float driftSpeed = 0.08f; // ๋
ธ์ด์ฆ ์ํ๋ง ์๊ฐ์ ์ฆ๊ฐ ์๋ (์์ง์ ์๋)
// ==============================================================================
// [Private ๋ฉค๋ฒ ๋ณ์]
// ==============================================================================
private ParticleSystem _ps; // ์บ์ฑ๋ ParticleSystem ์ปดํฌ๋ํธ ์ฐธ์กฐ
private ParticleSystem.Particle[] _particles; // ํํฐํด ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ๋ฐฐ์ด
// ==============================================================================
// [Unity LifeCycle : Start]
// ==============================================================================
void Start()
{
// ์ปดํฌ๋ํธ ์ฐธ์กฐ ๊ฐ์ ธ์ค๊ธฐ
_ps = GetComponent<ParticleSystem>();
// ํํฐํด ์์คํ
์ ๊ธฐ๋ณธ ๋ชจ๋ ์ค์ ์ ์ฉ
SetupParticleSystem();
// ๋ ๋๋ฌ ์ค์ ์ ์ฉ
SetupRenderer();
// ํํฐํด ๋ฐ์ดํฐ ์ ์ฅ์ฉ ๋ฐฐ์ด ์ด๊ธฐํ (์ต๋ ๊ฐ์๋งํผ ํ ๋น)
_particles = new ParticleSystem.Particle[maxParticles];
// ๊ตฌ ํ๋ฉด ์์ ํํฐํด์ ์ด๊ธฐ ๋ฐฐ์นํ๋ ํจ์ ํธ์ถ
PlaceParticlesOnSphere();
}
// ==============================================================================
// [Private Method: ParticleSystem ๋ชจ๋ ์ด๊ธฐํ]
// ==============================================================================
void SetupParticleSystem()
{
var main = _ps.main;
// Main ๋ชจ๋ ์์ฑ ์ค์
main.maxParticles = maxParticles; // ์ต๋ ํํฐํด ์
main.startLifetime = lifetime; // ์์ฑ ์ ๋ถ์ฌ๋ ์๋ช
main.startSize = particleSize; // ์์ฑ ์ ๋ถ์ฌ๋ ํฌ๊ธฐ
main.simulationSpace = ParticleSystemSimulationSpace.World; // ์๋ ์ขํ๊ณ ๊ธฐ์ค ์๋ฎฌ๋ ์ด์
(์ค๋ธ์ ํธ ์ด๋๊ณผ ๋
๋ฆฝ์ )
main.loop = false; // ์์คํ
๋ฃจํ ๋นํ์ฑํ (์๋ ์ ์ด์ฉ)
main.playOnAwake = false; // ์๋ํฐ ์คํ ์ ์๋ ์ฌ์ ๋ฐฉ์ง
// Emission ๋ชจ๋ ์ค์ : ์๋ ๋ฐฉ์ถ ๋นํ์ฑํ (์คํธ๋ฆฝํธ์์ ์ด๊ธฐ 1ํ๋ง ๋ฐฐ์นํ๋ฏ๋ก)
var emission = _ps.emission;
emission.enabled = false;
// Shape ๋ชจ๋ ์ค์ : ๊ธฐ๋ณธ ์
ฐ์ดํ ๋ฐฉ์ถ ๋นํ์ฑํ (์๋ ์์น ์ง์ ์ฌ์ฉ)
var shape = _ps.shape;
shape.enabled = false;
}
// ==============================================================================
// [Private Method: Renderer ์ค์ ]
// ==============================================================================
void SetupRenderer()
{
var psRenderer = GetComponent<ParticleSystemRenderer>();
// ๋ ๋ ๋ชจ๋๋ฅผ Billboard๋ก ์ค์ : ํํฐํด์ด ํญ์ ์นด๋ฉ๋ผ๋ฅผ ๋ฐ๋ผ๋ณด๊ฒ ํจ (2D ์คํ๋ผ์ดํธ์ฒ๋ผ ๋ณด์)
psRenderer.renderMode = ParticleSystemRenderMode.Billboard;
// ์ฐธ๊ณ : Material์ Inspector์์ ์ง์ ํ ๋น
}
// ==============================================================================
// [Private Method: ๊ตฌ ํ๋ฉด ์ ํํฐํด ์ด๊ธฐ ๋ฐฐ์น]
// ==============================================================================
void PlaceParticlesOnSphere()
{
// ์ต๋ ํํฐํด ๊ฐ์๋งํผ ๋ฐ๋ณต
for (int i = 0; i < maxParticles; i++)
{
// Random.outUnitSphere: ๋ฐ์ง๋ฆ 1์ธ ๊ตฌ ํ๋ฉด์ ๋๋คํ ๋ฐฉํฅ ๋ฒกํฐ๋ฅผ ๋ฐํ
// ์ฌ๊ธฐ์ sphereRadius๋ฅผ ๊ณฑํ์ฌ ์ค์ ํฌ๊ธฐ์ ๊ตฌ ํ๋ฉด ์ขํ๋ก ๋ณํ
_particles[i].position = Random.onUnitSphere * sphereRadius;
// ํํฐํด์ ์๋ช
์์ฑ ์ค์
_particles[i].startLifetime = lifetime;
_particles[i].remainingLifetime = lifetime; // ํ์ฌ ๋จ์ ์๋ช
๋ ์ต๋ ์๋ช
์ผ๋ก ์ด๊ธฐํ
// ํํฐํด ํฌ๊ธฐ ์ค์
_particles[i].startSize = particleSize;
}
// ์ค์ ๋ ํํฐํด ๋ฐ์ดํฐ ๋ฐฐ์ด์ ์ค์ ParticleSystem์ ์ ์ฉ (๊ฐ์ ์ง์ )
_ps.SetParticles(_particles, maxParticles);
}
// ==============================================================================
// [Unity LifeCycle: LateUpdate]
// ==============================================================================
void LateUpdate()
{
// ํ์ฌ ํ์ฑํ๋ ํํฐํด์ ๊ฐ์๋ฅผ ๊ฐ์ ธ์ค๊ณ , ๋ฐฐ์ด์ ๋ฐ์ดํฐ๋ฅผ ์ฑ์
// ์ฐธ๊ณ : ์ด ์์คํ
์ ํํฐํด์ ์๋ฉธ์ํค์น ์๊ณ ์ฌํ์ฉํ๋ฏ๋ก ํต์ใ
์ ์ผ๋ก count == maxParticles
int count = _ps.GetParticles(_particles);
float time = Time.time; // ํ์ฌ ๊ฒ์ ์๊ฐ ์ ์ (๋งค ํ๋ ์ ํธ์ถ ๋ฐฉ์ง์ฉ)
// ํ์ฑํ๋ ๋ชจ๋ ํํฐํด์ ๋ํด ์ฒ๋ฆฌ
for (int i = 0; i < count; i++)
{
// --------------------------------------------------------------------------
// [Perlin Noise ๋ฅผ ์ด์ฉํ ์ ๊ธฐ์ ์คํ์
๊ณ์ฐ]
// --------------------------------------------------------------------------
// Mathf.PerlinNoise(x,y): 0.0 ~ 1.0 ์ฌ์ด์ ๊ฐ์ ๋ฐํํ๋ ๋
ธ์ด์ฆ ํจ์
// 1. ์ธ๋ฑ์ค(i)์ ๊ณ ์ ํ ์์(0.3171f ๋ฑ)๋ฅผ ๊ณฑํ์ฌ ๊ฐ ํํฐํด๋ง๋ค ๋ค๋ฅธ ๋
ธ์ด์ฆ ํจํด ์์ฑ
// 2. time * driftSpeed ๋ฅผ ์
๋ ฅํ์ฌ ์๊ฐ์ ๋ฐ๋ผ ๋
ธ์ด์ฆ ๊ฐ์ด ๋ณํ๋๋ก (์ ๋๋ฉ์ด์
ํจ๊ณผ)
// 3. ๊ฒฐ๊ณผ๊ฐ์์ 0.5๋ฅผ ๋นผ์ -0.5 ~ 0.5 ๋ฒ์์ ์ค์ฌ๊ฐ์ ๊ฐ์ง ์คํ์
์ผ๋ก ๋ณํ
float nX = MathF.PerlinNoise(i * 0.3171f, time * driftSpeed) - 0.5f;
float nY = MathF.PerlinNoise(i * 0.6457f, time * driftSpeed) - 0.5f;
float nZ = MathF.PerlinNoise(i * 0.9133f, time * driftSpeed) - 0.5f;
// ๊ณ์ฐ๋ ๋
ธ์ด์ฆ ๊ฐ์ ๋ฒกํฐ๋ก ๊ตฌ์ฑํ๊ณ , Strength๋ฅผ ๊ณฑํ์ฌ ์ด๋ ๊ฑฐ๋ฆฌ๋ฅผ ์กฐ์
Vector3 noiseOffset = new Vector3(nX, nY, nZ) * noiseStrength;
// --------------------------------------------------------------------------
// [๊ตฌ ํ๋ฉด ์์ ํํฐํด์ ๊ตฌ์ํ๋ ๋ก์ง]
// --------------------------------------------------------------------------
// 1. ํ์ฌ ํํฐํด ์์น๋ฅผ ์ ๊ทํ (.normalized): ์์ ์์์ ๋ฐฉํฅ ๋ฒกํฐ๋ง ์ถ์ถ (๊ธธ์ด = 1)
// 2. ์ฌ๊ธฐ์ ๋
ธ์ด์ฆ ์คํ์
์ ๋ํจ: ๋ฐฉํฅ์ ์ด์ง ๋นํ์ด ์์ง์์ ๋ถ์ฌ
// 3. ๋ค์ ์ ๊ทํ (.normalized): ๋นํ์ด์ง ๋ฒกํฐ๋ฅผ ๋ค์ ๊ธธ์ด 1์ ๋จ์ ๋ฒกํฐ๋ก ๋ง๋ฆ (ํ๋ฉด ์ฌ๊ณ ์ )
// 4. sphereRadius๋ฅผ ๊ณฑํจ : ์ค์ ๊ตฌ์ ํฌ๊ธฐ๋ก ์ค์ผ์ผ๋ง
// ๊ฒฐ๊ณผ: ํํฐํด์ ๊ตฌ์ ์ค์ฌ์์ ์ผ์ ํ ๊ฑฐ๋ฆฌ(๋ฐ์ง๋ฆ)๋ฅผ ์ ์งํ๋ฉฐ ํ๋ฉด ์๋ฅผ ๋ฏธ๋๋ฌ์ง๋ฏ ์ด๋
Vector3 nextPos = (_particles[i].position.nomalized + noiseOffset).normalized * sphereRadius;
// ๊ณ์ฐ๋ ์ ์์น๋ฅผ ํํฐํด์ ์ ์ฉ
_particles[i].position = nextPos;
// ๋จ์ ์๋ช
์ ๊ฐ์ ๋ก ์ต๋ ์๋ช
์ผ๋ก ์ฌ์์
// ํํฐํด์ด ์๋ฉธํ๋ ๊ฒ์ ๋ฐฉ์ง, ๊ณ์ ์์ง์ด๋๋ก
_particles[i].remainingLifetime = lifetime;
}
// ์์ ๋ ํํฐํด ๋ฐ์ดํฐ ๋ฐฐ์ด์ ParticleSystem์ ๋ค์ ์
๋ก๋ํ์ฌ ํ๋ฉด์ ๋ฐ์
_ps.SetParticles(_particles, count);
}
}