๐ŸซงArt_030 Surface Line Artwork

BamgasiJMยท2026๋…„ 4์›” 25์ผ

Unity GenArt

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

๊ธฐ๋ณธ LineRenderer ๊ตฌ์กฐ

  • Line Renderer๋Š” ์›”๋“œ ์ขŒํ‘œ์ƒ์˜ ์—ฌ๋Ÿฌ ์ (Position ๋ฐฐ์—ด)์„ ๋ฐ›์•„ ์„ ์„ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ๋‘ ์ ๋งŒ ์„ค์ •ํ•˜๋ฉด โ€œ์ง์„  ์„ธ๊ทธ๋จผํŠธโ€๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.
  • ๋‘๊ป˜๋Š” startWidth, endWidth
  • ์ƒ‰์ƒ์€ Material ๋˜๋Š” colorGradient๋กœ ์ œ์–ดํ•ฉ๋‹ˆ๋‹ค.
using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
public class SimpleLineBetweenPoints : MonoBehaviour
{
    [Header("๋ผ์ธ ์„ค์ •")]
    [SerializeField] private float lineWidth = 0.05f;

    [Header("์ž„์˜ ๋ฒ”์œ„")]
    [SerializeField] private Vector3 rangeMin = new Vector3(-5f, -5f, -5f);
    [SerializeField] private Vector3 rangeMax = new Vector3(5f, 5f, 5f);

    private LineRenderer lineRenderer;

    void Awake() // Line Renderer ์ดˆ๊ธฐํ™” ๋ฐ ๋ Œ๋”๋ง ํŒŒ๋ผ๋ฏธํ„ฐ ์„ค์ •
    {
        lineRenderer = GetComponent<LineRenderer>();

        // ๊ธฐ๋ณธ ์„ค์ •
        lineRenderer.positionCount = 2;
        lineRenderer.useWorldSpace = true; // ์˜ค๋ธŒ์ ํŠธ ์ด๋™๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ณต๊ฐ„์ƒ์˜ ๋‘ ์  ์œ ์ง€

        // ๋‘๊ป˜ ์„ค์ •
        lineRenderer.startWidth = lineWidth;
        lineRenderer.endWidth = lineWidth;

        // ๋จธํ‹ฐ๋ฆฌ์–ผ (Unlit ๊ณ„์—ด ์‚ฌ์šฉ)
        lineRenderer.material = new Material(Shader.Find("Sprites/Default"));

        // ์ƒ‰์ƒ (ํฐ์ƒ‰)
        lineRenderer.startColor = Color.white;
        lineRenderer.endColor = Color.white;

        // ๊ทธ๋ฆผ์ž/๋ผ์ดํŠธ ์˜ํ–ฅ ์ œ๊ฑฐ (์•„ํŠธ์›Œํฌ์šฉ)
        lineRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
        lineRenderer.receiveShadows = false;
    }

    void Start() // ๋‘ ๊ฐœ์˜ ๋žœ๋ค ํฌ์ธํŠธ ์ƒ์„ฑ ํ›„ ๋ผ์ธ ์—ฐ๊ฒฐ
    {
        Vector3 pointA = GetRandomPoint();
        Vector3 pointB = GetRandomPoint();
        
		// SetPosition(index, position) : ๋ผ์ธ์˜ ๊ฐ ์ ์„ ์ง์ ‘ ์ง€์ •
        lineRenderer.SetPosition(0, pointA);
        lineRenderer.SetPosition(1, pointB);
    }

    private Vector3 GetRandomPoint()
    {
        return new Vector3(
            Random.Range(rangeMin.x, rangeMax.x),
            Random.Range(rangeMin.y, rangeMax.y),
            Random.Range(rangeMin.z, rangeMax.z)
        );
    }
}

SurfaceLineManager (GameObject)
โ””โ”€โ”€ Line_000
โ”‚   โ”œโ”€โ”€ LineRenderer       โ† ๋‘ ์  ์‚ฌ์ด ์„ 
โ”‚   โ”œโ”€โ”€ TrailA             โ† ์  A์˜ ๊ถค์ 
โ”‚   โ”‚   โ””โ”€โ”€ TrailRenderer
โ”‚   โ””โ”€โ”€ TrailB             โ† ์  B์˜ ๊ถค์ 
โ”‚       โ””โ”€โ”€ TrailRenderer
โ””โ”€โ”€ Line_001
    โ””โ”€โ”€ ...
using System.Collections.Generic;
using UnityEngine;

public class SurfaceLineManager : MonoBehaviour
{
  // ===

  [Header("๋ฐ•์Šค ํฌ๊ธฐ (์ ˆ๋ฐ˜ extents)")]
  [SerializeField] private Vector3 boxHalfSize = new Vector3(5f, 5f, 5f);

  [Header("๋ผ์ธ ๊ฐœ์ˆ˜")]
  [SerializeField, Range(1, 256)] private int lineCount = 16;

  [Header("๋ผ์ธ ๋‘๊ป˜")]
  [SerializeField] private float lineWidth = 0.05f;

  [Header("์ด๋™ ์†๋„ ๋ฒ”์œ„")]
  [SerializeField] private float minSpeed = 0.5f;
  [SerializeField] private float maxSpeed = 3f;

  [Header("์ƒ‰์ƒ")]
  [SerializeField] private Color startColor = Color.white;
  [SerializeField] private Color endColor = Color.white;
  [SerializeField, Range(0f, 1f)] private float startAlpha = 1f;
  [SerializeField, Range(0f, 1f)] private float endAlpha = 1f;

  [Header("ํŠธ๋ ˆ์ผ")]
  [SerializeField] private bool enableTrail = true;
  [SerializeField] private float trailTime = 0.5f;         // ์ž”์ƒ ์ง€์† ์‹œ๊ฐ„
  [SerializeField] private float trailStartWidth = 0.04f;  // ํŠธ๋ ˆ์ผ ์‹œ์ž‘ ๋‘๊ป˜
  [SerializeField] private float trailEndWidth = 0f;       // ํŠธ๋ ˆ์ผ ๋ ๋‘๊ป˜ (0 = ์™„์ „ํžˆ ์‚ฌ๋ผ์ง)
  [SerializeField] private Color trailStartColor = Color.white;
  [SerializeField] private Color trailEndColor = Color.clear;  // ๋์ชฝ์€ ํˆฌ๋ช…

  // ---

  private struct SurfacePoint
  {
    public int faceIndex;
    public Vector2 uv;
    public Vector2 velocity;
  }

  // ๋ผ์ธ ํ•˜๋‚˜๋‹น ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ ๋ฌถ์Œ
  private struct LineEntry
  {
    public LineRenderer lineRenderer;
    public Transform trailTransformA;  // ์  A ์œ„์น˜ ์ถ”์ ์šฉ Transform
    public Transform trailTransformB;  // ์  B ์œ„์น˜ ์ถ”์ ์šฉ Transform
  }

  private List<LineEntry> lines = new List<LineEntry>();
  private SurfacePoint[] pointsA;
  private SurfacePoint[] pointsB;

  // ===

  void Awake()
  {
    InitLines();
  }

  void Update()
  {
    for (int i = 0; i < lineCount; i++)
    {
      pointsA[i] = MovePoint(pointsA[i]);
      pointsB[i] = MovePoint(pointsB[i]);

      Vector3 worldA = ToWorld(pointsA[i]);
      Vector3 worldB = ToWorld(pointsB[i]);

      lines[i].lineRenderer.SetPosition(0, worldA);
      lines[i].lineRenderer.SetPosition(1, worldB);

      // TrailRenderer๋Š” Transform ์œ„์น˜๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฏ€๋กœ ์œ„์น˜๋งŒ ๊ฐฑ์‹ 
      lines[i].trailTransformA.position = worldA;
      lines[i].trailTransformB.position = worldB;
    }
  }

  // ===

  private void InitLines()
  {
    for (int i = transform.childCount - 1; i >= 0; i--)
      Destroy(transform.GetChild(i).gameObject);

    lines.Clear();

    pointsA = new SurfacePoint[lineCount];
    pointsB = new SurfacePoint[lineCount];

    Shader lineShader = Shader.Find("Universal Render Pipeline/Particles/Unlit");
    if (lineShader == null)
      lineShader = Shader.Find("Sprites/Default");

    Gradient lineGradient = BuildGradient(startColor, endColor, startAlpha, endAlpha);
    Gradient trailGradient = BuildGradient(trailStartColor, trailEndColor, trailStartColor.a, 0f);

    for (int i = 0; i < lineCount; i++)
    {
      // --- ๋ผ์ธ ์˜ค๋ธŒ์ ํŠธ
      GameObject lineGO = new GameObject($"Line_{i:000}");
      lineGO.transform.SetParent(transform, false);

      LineRenderer lr = lineGO.AddComponent<LineRenderer>();
      SetupLineRenderer(lr, lineShader, lineGradient);

      // --- ํŠธ๋ ˆ์ผ A (์  A์šฉ)
      Transform trailA = CreateTrailObject("TrailA", lineGO.transform, lineShader, trailGradient);

      // --- ํŠธ๋ ˆ์ผ B (์  B์šฉ)
      Transform trailB = CreateTrailObject("TrailB", lineGO.transform, lineShader, trailGradient);

      lines.Add(new LineEntry
      {
        lineRenderer = lr,
        trailTransformA = trailA,
        trailTransformB = trailB
      });

      pointsA[i] = SpawnPoint();
      pointsB[i] = SpawnPoint();

      // ์ดˆ๊ธฐ ์œ„์น˜ ์„ค์ •
      Vector3 wa = ToWorld(pointsA[i]);
      Vector3 wb = ToWorld(pointsB[i]);
      lr.SetPosition(0, wa);
      lr.SetPosition(1, wb);
      trailA.position = wa;
      trailB.position = wb;
    }
  }

  // ---

  // LineRenderer ๊ธฐ๋ณธ ์„ธํŒ…
  private void SetupLineRenderer(LineRenderer lr, Shader shader, Gradient gradient)
  {
    lr.positionCount = 2;
    lr.useWorldSpace = true;
    lr.startWidth = lineWidth;
    lr.endWidth = lineWidth;
    lr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    lr.receiveShadows = false;
    lr.material = new Material(shader);
    lr.startColor = Color.white;
    lr.endColor = Color.white;
    lr.colorGradient = gradient;
  }

  // ---

  // TrailRenderer๊ฐ€ ๋ถ™์€ ์ž์‹ ์˜ค๋ธŒ์ ํŠธ ์ƒ์„ฑ
  private Transform CreateTrailObject(string name, Transform parent, Shader shader, Gradient gradient)
  {
    GameObject go = new GameObject(name);
    go.transform.SetParent(parent, false);

    TrailRenderer tr = go.AddComponent<TrailRenderer>();
    tr.time = trailTime;
    tr.startWidth = trailStartWidth;
    tr.endWidth = trailEndWidth;
    tr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
    tr.receiveShadows = false;
    tr.material = new Material(shader);

    // ํŠธ๋ ˆ์ผ ์ปฌ๋Ÿฌ: ์‹œ์ž‘(ํ˜„์žฌ ์œ„์น˜)์€ ๋ถˆํˆฌ๋ช…, ๋(์˜ค๋ž˜๋œ ์œ„์น˜)์€ ํˆฌ๋ช…
    tr.colorGradient = gradient;

    // ํŠธ๋ ˆ์ผ์ด ํ™œ์„ฑํ™” ์—ฌ๋ถ€
    tr.enabled = enableTrail;

    return go.transform;
  }

  // ---

  private SurfacePoint SpawnPoint()
  {
    int face = Random.Range(0, 6);
    Vector2 halfUV = GetFaceHalfExtents(face);

    Vector2 uv = new Vector2(
        Random.Range(-halfUV.x, halfUV.x),
        Random.Range(-halfUV.y, halfUV.y)
    );

    float angle = Random.Range(0f, Mathf.PI * 2f);
    float speed = Random.Range(minSpeed, maxSpeed);
    Vector2 vel = new Vector2(Mathf.Cos(angle), Mathf.Sin(angle)) * speed;

    return new SurfacePoint { faceIndex = face, uv = uv, velocity = vel };
  }

  // ---

  private SurfacePoint MovePoint(SurfacePoint p)
  {
    Vector2 half = GetFaceHalfExtents(p.faceIndex);
    Vector2 newUV = p.uv + p.velocity * Time.deltaTime;
    Vector2 newVel = p.velocity;

    if (newUV.x > half.x) { newUV.x = half.x; newVel.x = -newVel.x; }
    if (newUV.x < -half.x) { newUV.x = -half.x; newVel.x = -newVel.x; }
    if (newUV.y > half.y) { newUV.y = half.y; newVel.y = -newVel.y; }
    if (newUV.y < -half.y) { newUV.y = -half.y; newVel.y = -newVel.y; }

    return new SurfacePoint { faceIndex = p.faceIndex, uv = newUV, velocity = newVel };
  }

  // ---

  private Vector3 ToWorld(SurfacePoint p)
  {
    float u = p.uv.x;
    float v = p.uv.y;

    switch (p.faceIndex)
    {
      case 0: return new Vector3(boxHalfSize.x, u, v);
      case 1: return new Vector3(-boxHalfSize.x, u, v);
      case 2: return new Vector3(u, boxHalfSize.y, v);
      case 3: return new Vector3(u, -boxHalfSize.y, v);
      case 4: return new Vector3(u, v, boxHalfSize.z);
      case 5: return new Vector3(u, v, -boxHalfSize.z);
      default: return Vector3.zero;
    }
  }

  // ---

  private Vector2 GetFaceHalfExtents(int faceIndex)
  {
    switch (faceIndex)
    {
      case 0: case 1: return new Vector2(boxHalfSize.y, boxHalfSize.z);
      case 2: case 3: return new Vector2(boxHalfSize.x, boxHalfSize.z);
      case 4: case 5: return new Vector2(boxHalfSize.x, boxHalfSize.y);
      default: return Vector2.one;
    }
  }

  // ---

  // ๋ฒ”์šฉ Gradient ๋นŒ๋”
  private Gradient BuildGradient(Color colorA, Color colorB, float alphaA, float alphaB)
  {
    Gradient g = new Gradient();
    g.SetKeys(
        new GradientColorKey[] {
                new GradientColorKey(colorA, 0f),
                new GradientColorKey(colorB, 1f)
        },
        new GradientAlphaKey[] {
                new GradientAlphaKey(alphaA, 0f),
                new GradientAlphaKey(alphaB, 1f)
        }
    );
    return g;
  }

  // ===

#if UNITY_EDITOR
  void OnValidate()
  {
    if (!Application.isPlaying) return;

    Gradient lg = BuildGradient(startColor, endColor, startAlpha, endAlpha);
    Gradient tg = BuildGradient(trailStartColor, trailEndColor, trailStartColor.a, 0f);

    foreach (var entry in lines)
    {
      if (entry.lineRenderer == null) continue;

      entry.lineRenderer.startWidth = lineWidth;
      entry.lineRenderer.endWidth = lineWidth;
      entry.lineRenderer.colorGradient = lg;

      ApplyTrailSettings(entry.trailTransformA, tg);
      ApplyTrailSettings(entry.trailTransformB, tg);
    }
  }

  // OnValidate์—์„œ TrailRenderer ์„ค์ • ๊ฐฑ์‹ 
  private void ApplyTrailSettings(Transform t, Gradient gradient)
  {
    if (t == null) return;
    TrailRenderer tr = t.GetComponent<TrailRenderer>();
    if (tr == null) return;

    tr.time = trailTime;
    tr.startWidth = trailStartWidth;
    tr.endWidth = trailEndWidth;
    tr.colorGradient = gradient;
    tr.enabled = enableTrail;
  }
#endif
}

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

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