๐ŸซงArt_018 Flow Field Line

BamgasiJMยท2026๋…„ 3์›” 28์ผ

Unity GenArt

๋ชฉ๋ก ๋ณด๊ธฐ
29/41
post-thumbnail

  1. Materail์„ ๋งŒ๋“ค๊ณ  Shader์˜ ์ข…๋ฅ˜๋ฅผ Sprite/Default ๋˜๋Š” Particle/Standard Unlit์œผ๋กœ ์ง€์ •ํ•˜๊ธฐ

  1. GameObject์— FlowFieldLine.cs๋ฅผ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ถ™์ด๊ณ 
  2. Inspector์˜ Line Renderer ํ•ญ๋ชฉ์— ์žˆ๋Š” Material์— FlowFieldLine์„ ํ• ๋‹นํ•˜๊ธฐ

  1. Inspector์˜ Flow Field Line (Script) ์˜์—ญ์—์„œ ์„ค์ •ํ•˜๊ธฐ


Code

๐Ÿ“’ FlowFieldLine.cs

// FlowFieldLine.cs
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class FlowFieldLine : MonoBehaviour
{
    // โ”€โ”€โ”€ ํŒŒํ‹ฐํด ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    [Header("Particle Settings")]
    [SerializeField] private int count = 200;
    [SerializeField] private int randomSeed = 42;
    [SerializeField] private float radius = 5f;
    [SerializeField] private float speed = 1f;

    // โ”€โ”€โ”€ ๋…ธ์ด์ฆˆ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    [Header("Flow Field Settings")]
    [SerializeField] private float noiseScale = 0.3f;
    [SerializeField] private float noiseSpeed = 0.5f;

    // โ”€โ”€โ”€ ๋ผ์ธ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    [Header("Line Settings")]
    [SerializeField] private float startWidth = 0.05f;
    [SerializeField] private float endWidth = 0.01f;

    // โ”€โ”€โ”€ ์ƒ‰์ƒ ์„ค์ • โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    [Header("Color Settings")]
    [Tooltip("๋ผ์ธ ์‹œ์ž‘ ์ƒ‰์ƒ")]
    [SerializeField] private Color startColor = new Color(0.2f, 0.8f, 1f, 1f);
    [Tooltip("๋ผ์ธ ๋ ์ƒ‰์ƒ")]
    [SerializeField] private Color endColor = new Color(0.8f, 0.2f, 1f, 0f);
    [Tooltip("์ผœ๋ฉด ์•„๋ž˜ Gradient๋ฅผ ์ง์ ‘ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค (๋„๋ฉด Start/End Color ์ž๋™ ์ ์šฉ)")]
    [SerializeField] private bool useCustomGradient = false;
    [SerializeField] private Gradient customGradient;

    // โ”€โ”€โ”€ ๋‚ด๋ถ€ ๋ฐ์ดํ„ฐ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
    private struct ParticleData
    {
        public Vector3 position;
        public Vector3 velocity;
        public float life;
        public float maxLife;
    }

    private readonly List<ParticleData> particles = new List<ParticleData>();
    private Vector3[] positions;
    private LineRenderer lineRenderer;

    // โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    void Awake()
    {
        lineRenderer = GetComponent<LineRenderer>();
        positions = new Vector3[count];
        particles.Capacity = count;
    }

    void Start()
    {
        Random.InitState(randomSeed);
        InitializeParticles();
        SetupLineRenderer();
    }

    void Update()
    {
        Step(Time.deltaTime);
        ApplyLine();
    }

    // โ”€โ”€โ”€ ์ดˆ๊ธฐํ™” โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    void InitializeParticles()
    {
        particles.Clear();
        for (int i = 0; i < count; i++)
        {
            Vector3 pos = Random.insideUnitSphere * radius;
            particles.Add(new ParticleData
            {
                position = pos,
                velocity = Random.onUnitSphere * speed,
                life = Random.Range(2f, 5f),
                maxLife = 5f
            });
            positions[i] = pos;
        }
    }

    void SetupLineRenderer()
    {
        lineRenderer.positionCount = count;
        lineRenderer.useWorldSpace = true;
        lineRenderer.startWidth = startWidth;
        lineRenderer.endWidth = endWidth;
        lineRenderer.textureMode = LineTextureMode.Stretch;
        lineRenderer.alignment = LineAlignment.View;

        ApplyColor();
    }

    // โ”€โ”€โ”€ ์ƒ‰์ƒ ์ ์šฉ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    // Start/End Color โ†’ Gradient ์ž๋™ ๋นŒ๋“œ ํ›„ LineRenderer์— ์ ์šฉ
    void ApplyColor()
    {
        Gradient gradient;

        if (useCustomGradient && customGradient != null)
        {
            gradient = customGradient;
        }
        else
        {
            gradient = BuildGradientFromColors(startColor, endColor);
        }

        lineRenderer.colorGradient = gradient;
    }

    Gradient BuildGradientFromColors(Color a, Color b)
    {
        var gradient = new Gradient();
        gradient.SetKeys(
            new GradientColorKey[]
            {
                new GradientColorKey(a, 0f),
                new GradientColorKey(b, 1f)
            },
            new GradientAlphaKey[]
            {
                new GradientAlphaKey(a.a, 0f),
                new GradientAlphaKey(b.a, 1f)
            }
        );
        return gradient;
    }

    // โ”€โ”€โ”€ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

    void Step(float dt)
    {
        float t = Time.time * noiseSpeed;
        for (int i = 0; i < particles.Count; i++)
        {
            var p = particles[i];

            Vector3 flow = new Vector3(
                Mathf.PerlinNoise(p.position.y * noiseScale + t, p.position.z * noiseScale) - 0.5f,
                Mathf.PerlinNoise(p.position.z * noiseScale + t, p.position.x * noiseScale) - 0.5f,
                Mathf.PerlinNoise(p.position.x * noiseScale + t, p.position.y * noiseScale) - 0.5f
            );

            p.velocity += flow * dt;
            p.position += p.velocity * dt;
            p.life -= dt;

            if (p.life <= 0f)
            {
                p.position = Random.insideUnitSphere * radius;
                p.velocity = Random.onUnitSphere * speed;
                p.life = p.maxLife;
            }

            particles[i] = p;
            positions[i] = p.position;
        }
    }

    void ApplyLine()
    {
        lineRenderer.SetPositions(positions);
    }

    // โ”€โ”€โ”€ ์—๋””ํ„ฐ ํ•ซ ๋ฆฌ๋กœ๋“œ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#if UNITY_EDITOR
    // ์ธ์ŠคํŽ™ํ„ฐ์—์„œ ์ƒ‰์ƒ ์ˆ˜์ • ์‹œ ํ”Œ๋ ˆ์ด ์ค‘์—๋„ ์‹ค์‹œ๊ฐ„ ๋ฐ˜์˜
    void OnValidate()
    {
        if (lineRenderer == null) return;
        ApplyColor();
    }
#endif
}

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€