Unity-LineRenderer

我要画线!

正文

How To Draw a Line in Unity | Line Renderer Tutorial 1

png

往一个 GameObject 里加上 LineRenderer 组件,Positions 里设好坐标点的坐标,Materials 里设好线条的材质,即可在游戏场景里画线。

写一个脚本,SetupLine() 功能是在 Transform 的坐标处连线:

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class lr_LineController : MonoBehaviour
{
    private LineRenderer lr;
    private Transform[] points;
 
    private void Awake()
    {
        lr = GetComponent<LineRenderer>();
    }
 
    public void SetupLine(Transform[] points)
    {
        lr.positionCount = points.Length;
        this.points = points;
    }
 
    private void Update()
    {
        for(int i = 0; i < points.Length; i++)
        {
            lr.SetPosition(i, points[i].position);
        }
    }
}

再写一个脚本调用 SetupLine()

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class lr_Testing : MonoBehaviour
{
    [SerializeField] private Transform[] points;
    [SerializeField] private lr_LineController line;
 
    private void Start()
    {
        line.SetupLine(points);
    }
}

给这个 lr_Testing 绑定若干 Transform,开跑!

png

How to Draw UI Lines in Unity in 10 Minutes | Line Renderer Tutorial 2

png

如何在 UI 层画线?将 Canvas 里的 Render Mode 设为 Screen Space - Camera

png

上一节的代码就可以对 Canvas 里的 Transform 生效。

How to Draw Shapes in Unity | Line Renderer Unity Tutorial 3

png

构建场景如图所示。设计两个 Render ModeScreen Space - Camera 的画布:Pen Tool CanvasDots Canvas

{% tabs course3_1%}

png

绑定着 Pen Tool,控制画线的逻辑。

png

绑定着 Pen Canvas,表示画布。

png

绑定着 Dot Controller,表示点。

png

绑定着 Line Prefab,表示线条。

png

切换绘制模式(是否 loop)的开关。

{% endtabs %}

{% tabs course3_2%}

C#
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System;
 
public class PenCanvas : MonoBehaviour, IPointerClickHandler {
    public Action OnPenCanvasLeftClickEvent;
    public Action OnPenCanvasRightClickEvent;
    void IPointerClickHandler.OnPointerClick(PointerEventData eventData) {
        if (eventData.pointerId == -1) {
            OnPenCanvasLeftClickEvent?.Invoke();
        }
        else if (eventData.pointerId == -2) {
            OnPenCanvasRightClickEvent?.Invoke();
        }
    }
}

这段 Unity 代码定义了一个名为 PenCanvas 的脚本,实现了 IPointerClickHandler 接口,用于处理在画布上的点击事件。主要功能如下:

  1. 定义了两个事件处理函数 OnPenCanvasLeftClickEventOnPenCanvasRightClickEvent,分别表示在画布上左键点击和右键点击事件。
  2. 实现了 IPointerClickHandler 接口中的 OnPointerClick 方法,用于处理点击事件。当用户在画布上点击时,根据 eventData 中的 pointerId 判断点击的是左键(-1)还是右键(-2),然后触发相应的事件处理函数。

这段代码的作用是在 Unity 中实现了对画布上左键和右键点击事件的监听,并在点击事件发生时触发相应的事件处理函数。

C#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class LineController : MonoBehaviour
{
    private LineRenderer lr;
    private List<DotController> dots;
 
    private void Awake() {
        lr = GetComponent<LineRenderer>();
        lr.positionCount = 0;
 
        dots = new List<DotController>();
    }
 
    public DotController GetFirstPoint() {
        return dots[0];
    }
 
    public void AddDot(DotController dot) {
 
        dot.SetLine(this);
        dot.SetIndex(dots.Count);
 
        lr.positionCount++;
        dots.Add(dot);
    }
 
    public void SplitPointsAtIndex(int index, out List<DotController> beforePoints, out List<DotController> afterPoints) {
        List<DotController> before = new List<DotController>();
        List<DotController> after = new List<DotController>();
 
        int i = 0;
        for (; i < index; i++) {
            before.Add(dots[i]);
        }
        i++;
        for (; i < dots.Count; i++) {
            after.Add(dots[i]);
        }
 
        beforePoints = before;
        afterPoints = after;
 
        dots.RemoveAt(index);
    }
 
    public void SetColor(Color color) {
        lr.startColor = color;
        lr.endColor = color;
    }
 
    public void ToggleLoop() {
        lr.loop = !lr.loop;
    }
 
    public bool isLooped() {
        return lr.loop;
    }
 
    private void LateUpdate() {
        if (dots.Count >= 2) {
            for (int i = 0; i < dots.Count; i++) {
                Vector3 position = dots[i].transform.position;
                position += new Vector3(0, 0, 5);
 
                lr.SetPosition(i, position);
            }
        }
    }
}

这段代码是一个名为 LineController 的 Unity 脚本,用于控制线条的行为。主要功能包括:

  1. Awake 方法中初始化 LineRenderer 和点列表,并设置初始位置数为 0
  2. 提供了 GetFirstPoint 方法来获取线条上的第一个点。
  3. 提供了 AddDot 方法,用于向线条中添加点。该方法会设置点的索引,并更新 LineRenderer 的位置数和点列表。
  4. 提供了 SplitPointsAtIndex 方法,用于在指定索引处分割线条上的点,并返回分割后的两部分点列表。
  5. 提供了 SetColor 方法,用于设置线条的颜色。
  6. 提供了 ToggleLoop 方法和 isLooped 方法,用于切换线条是否闭合的状态,并获取当前线条是否闭合的状态。
  7. LateUpdate 方法中,根据点的位置更新 LineRenderer 的位置,以确保线条能够实时跟随点的移动而更新。

总体来说,这个脚本负责管理线条对象的行为,包括线条位置的更新、点的添加和删除、线条颜色的设置等功能。

C#
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System;
public class DotController : MonoBehaviour, IDragHandler, IPointerClickHandler, IBeginDragHandler {
 
    [HideInInspector] public LineController line;
    [HideInInspector] public int index;
 
    public Action<DotController> OnDragEvent;
    public void OnDrag(PointerEventData eventData) {
        OnDragEvent?.Invoke(this);
    }
 
    public Action<DotController> OnRightClickEvent;
    public Action<LineController> OnLeftClickEvent;
    public void OnPointerClick(PointerEventData eventData) {
        if (eventData.pointerId == -2) {
            //Right Click
            OnRightClickEvent?.Invoke(this);
        }
        else if (eventData.pointerId == -1) {
            //Left Click
            OnLeftClickEvent?.Invoke(line);
        }
    }
 
    public void SetLine(LineController line) {
        this.line = line;
    }
 
    public void SetIndex(int index) {
        this.index = index;
    }
 
    public void OnBeginDrag(PointerEventData eventData) {
        if (eventData.pointerId == -1) {
            //Left Drag
            OnLeftClickEvent?.Invoke(line);
        }
    }
}

这段代码是一个名为 DotController 的 Unity 脚本,用于控制点的行为。主要功能包括:

  1. 实现了 IDragHandlerIPointerClickHandlerIBeginDragHandler 接口,以处理点的拖拽、鼠标点击和开始拖拽事件。
  2. 包含了一个公开的字段 line,用于存储点所属的线条对象。
  3. 包含了一个公开的字段 index,用于存储点在线条中的索引。
  4. 包含了 OnDragEventOnRightClickEventOnLeftClickEvent 三个事件,分别用于处理拖拽事件、右键点击事件和左键点击事件。
  5. 实现了 OnDrag 方法,用于在点被拖拽时触发相应的事件。
  6. 实现了 OnPointerClick 方法,根据鼠标点击的类型(左键或右键)来触发相应的事件。
  7. 实现了 SetLine 方法,用于设置点所属的线条对象。
  8. 实现了 SetIndex 方法,用于设置点在线条中的索引。
  9. 实现了 OnBeginDrag 方法,在开始拖拽事件中根据鼠标点击的类型来触发相应的事件。

总体来说,这个脚本负责管理点对象的交互行为,包括拖拽、点击和开始拖拽事件的处理,以及与线条对象的关联和索引设置。

c#
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
 
public class PenTool : MonoBehaviour
{
    [Header("Pen Canvas")]
    [SerializeField] private PenCanvas penCanvas;
 
    [Header("Dots")]
    [SerializeField] private GameObject dotPrefab;
    [SerializeField] private Transform dotParent;
 
    [Header("Lines")]
    [SerializeField] private GameObject linePrefab;
    [SerializeField] private Transform lineParent;
    private LineController currentLine;
 
    [Header("Colors")]
    [SerializeField] private Color activeColor;
    [SerializeField] private Color normalColor;
 
    [Header("Loop Toggle")]
    [SerializeField] Image loopToggle;
    [SerializeField] Sprite loopSprite;
    [SerializeField] Sprite unloopSprite;
 
    private void Start() {
        penCanvas.OnPenCanvasLeftClickEvent += AddDot;
        penCanvas.OnPenCanvasRightClickEvent += EndCurrentLine;
    }
 
    public void ToggleLoop() {
        if (currentLine != null) {
            currentLine.ToggleLoop();
            loopToggle.sprite = (currentLine.isLooped()) ? unloopSprite : loopSprite;
        }
    }
 
    private void AddDot() {
        if (currentLine == null) {
            LineController lineController = Instantiate(linePrefab, Vector3.zero, Quaternion.identity, lineParent).GetComponent<LineController>();
            SetCurrentLine(lineController);
        }
 
        DotController dot = Instantiate(dotPrefab, GetMousePosition(), Quaternion.identity, dotParent).GetComponent<DotController>();
        dot.OnDragEvent += MoveDot;
        dot.OnRightClickEvent += RemoveDot;
        dot.OnLeftClickEvent += SetCurrentLine;
 
        currentLine.AddDot(dot);
    }
 
    private void RemoveDot(DotController dot) {
        dot.line.SplitPointsAtIndex(dot.index, out List<DotController> before, out List<DotController> after);
 
        Destroy(dot.line.gameObject);
        Destroy(dot.gameObject);
 
        LineController beforeLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity, lineParent).GetComponent<LineController>();
        for (int i = 0; i < before.Count; i++) {
            beforeLine.AddDot(before[i]);
        }
 
        LineController afterLine = Instantiate(linePrefab, Vector3.zero, Quaternion.identity, lineParent).GetComponent<LineController>();
        for (int i = 0; i < after.Count; i++) {
            afterLine.AddDot(after[i]);
        }
    }
 
    private void EndCurrentLine() {
        if (currentLine != null) {
            currentLine.SetColor(normalColor);
            loopToggle.enabled = false;
            currentLine = null;
        }
    }
 
    private void SetCurrentLine(LineController newLine) {
        EndCurrentLine();
 
        currentLine = newLine;
        currentLine.SetColor(activeColor);
 
        loopToggle.enabled = true;
        loopToggle.sprite = (currentLine.isLooped()) ? unloopSprite : loopSprite;
    }
 
    private void MoveDot(DotController dot) {
        dot.transform.position = GetMousePosition();
    }
 
    private Vector3 GetMousePosition() {
        Vector3 worldMousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        worldMousePosition.z = 0;
 
        return worldMousePosition;
    }
}

这段代码是一个 Unity 脚本,名为 PenTool,用于实现一个绘图工具的功能。主要包括以下内容:

  1. Start 方法中订阅了 PenCanvas 的左键点击事件和右键点击事件,分别触发 AddDotEndCurrentLine 方法。
  2. ToggleLoop 方法用于切换当前线条是否闭合,并更新对应的 UI 图标。
  3. AddDot 方法用于在画布上添加一个点,并将点添加到当前线条中。
  4. RemoveDot 方法用于移除指定的点,并重新生成两个新的线条来连接剩余的点。
  5. EndCurrentLine 方法用于结束当前线条的绘制,并重置相关状态。
  6. SetCurrentLine 方法用于设置当前操作的线条,并更新相关状态。
  7. MoveDot 方法用于移动点的位置到鼠标位置。
  8. GetMousePosition 方法用于获取鼠标在世界坐标系中的位置。

整体来说,这个脚本实现了绘图工具的核心功能,包括点和线条的创建、操作以及相关 UI 的更新。

{% endtabs %}

How to Detect COLLISIONS on a Line Renderer in Unity

Line Prefab 绑上两个脚本:

png

{% tabs course4%}

C#
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
 
[RequireComponent(typeof(LineController), typeof(PolygonCollider2D))]
public class LineCollision : MonoBehaviour {
 
    LineController lineController;
    PolygonCollider2D polygonCollider;
 
    private void Awake() {
        lineController = GetComponent<LineController>();
        polygonCollider = GetComponent<PolygonCollider2D>();
    }
 
    private void LateUpdate() { 
 
        //Get all the positions from the line renderer
        Vector3[] positions = lineController.GetPositions();
 
        //If we have enough points to draw a line
        if (positions.Count() >= 2) {
 
            //Get the number of line between two points
            int numberOfLines = positions.Length - 1;
 
            //Make as many paths for each different line as we have lines
            polygonCollider.pathCount = numberOfLines;
 
            //Get Collider points between two consecutive points
            for (int i = 0; i < numberOfLines; i++) {
                //Get the two next points
                List<Vector2> currentPositions = new List<Vector2> {
                    positions[i],
                    positions[i+1]
                };
 
                List<Vector2> currentColliderPoints = CalculateColliderPoints(currentPositions);
                polygonCollider.SetPath(i, currentColliderPoints.ConvertAll(p => (Vector2)transform.InverseTransformPoint(p)));
            }
        }
        else {
 
            polygonCollider.pathCount = 0;
        }
    }
 
    private List<Vector2> CalculateColliderPoints(List<Vector2> positions) {
        //Get The Width of the Line
        float width = lineController.GetWidth();
 
        // m = (y2 - y1) / (x2 - x1)
        float m = (positions[1].y - positions[0].y) / (positions[1].x - positions[0].x);
        float deltaX = (width / 2f) * (m / Mathf.Pow(m * m + 1, 0.5f));
        float deltaY = (width / 2f) * (1 / Mathf.Pow(1 + m * m, 0.5f));
 
        //Calculate Vertex Offset from Line Point
        Vector2[] offsets = new Vector2[2];
        offsets[0] = new Vector2(-deltaX, deltaY);
        offsets[1] = new Vector2(deltaX, -deltaY);
 
        List<Vector2> colliderPoints = new List<Vector2> {
            positions[0] + offsets[0],
            positions[1] + offsets[0],
            positions[1] + offsets[1],
            positions[0] + offsets[1]
        };
 
        return colliderPoints;
    }
}
c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
public class LineController : MonoBehaviour
{
    private LineRenderer lr;
    private List<DotController> dots;
 
    private void Awake() {
        lr = GetComponent<LineRenderer>();
        lr.positionCount = 0;
 
        dots = new List<DotController>();
    }
 
    public DotController GetFirstPoint() {
        return dots[0];
    }
 
    public void AddDot(DotController dot) {
 
        dot.SetLine(this);
        dot.SetIndex(dots.Count);
 
        lr.positionCount++;
        dots.Add(dot);
    }
 
    public void SplitPointsAtIndex(int index, out List<DotController> beforePoints, out List<DotController> afterPoints) {
        List<DotController> before = new List<DotController>();
        List<DotController> after = new List<DotController>();
 
        int i = 0;
        for (; i < index; i++) {
            before.Add(dots[i]);
        }
        i++;
        for (; i < dots.Count; i++) {
            after.Add(dots[i]);
        }
 
        beforePoints = before;
        afterPoints = after;
 
        dots.RemoveAt(index);
    }
 
    public void SetColor(Color color) {
        lr.startColor = color;
        lr.endColor = color;
    }
 
    public void ToggleLoop() {
        lr.loop = !lr.loop;
    }
 
    public bool isLooped() {
        return lr.loop;
    }
 
    private void LateUpdate() {
        if (dots.Count >= 2) {
            for (int i = 0; i < dots.Count; i++) {
                Vector3 position = dots[i].transform.position;
                position += new Vector3(0, 0, 5);
 
                lr.SetPosition(i, position);
            }
        }
    }
 
    public Vector3[] GetPositions() {
        Vector3[] positions = new Vector3[lr.positionCount];
        lr.GetPositions(positions);
        return positions;
    }
 
    public float GetWidth() {
        return lr.startWidth;
    }
}

{% endtabs %}

如此,生成线条时就会附带对应的碰撞体。