You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何锁定Unity的LineRenderer,使其仅沿单一方向跟随触摸?

问题需求

我有两个预设点,需要在它们之间绘制一条线。目前绘制线条和对齐到最终位置都没有问题,但希望绘制过程中将线条限制在两点之间,而不是让它跟随触摸位置任意移动。

效果对比

  • 当前效果:
    当前效果
  • 期望效果(手指仍在屏幕上):
    期望效果
  • 最终效果(手指抬起后):
    最终效果

当前代码

public class Path : MonoBehaviour
{
    [SerializeField] private Vector3[] positions;

    [SerializeField] private GameObject child1, child2;

    private Vector3 startPos, currentPos, endPos;

    [SerializeField] private bool _firstChildSelected = false;
    [SerializeField] private bool _secondChildSelected = false;

    [SerializeField] private LineRenderer line;

    Vector3 direction;

    Touch touch;

    // Start is called before the first frame update
    void Start()
    {
        positions = new Vector3[4];
        endPos = child2.transform.position;
    }

    // Update is called once per frame
    void Update()
    {

        if (Input.touchCount > 0)
        {
            touch = Input.GetTouch(0);
            RaycastHit2D hit = Physics2D.Raycast(Camera.main.ScreenToWorldPoint(touch.position), Vector2.zero);


            if (touch.phase == TouchPhase.Moved || touch.phase == TouchPhase.Stationary)
            {
                if (hit)
                {
                    // If it collides with the first checkpoint for the first time...
                    if (hit.transform.gameObject == child1 && !_firstChildSelected)
                    {
                        positions[0] = child1.transform.position;
                        line.SetPosition(0, positions[0]);
                        line.SetPosition(1, Camera.main.ScreenToWorldPoint(touch.position));
                        _firstChildSelected = true;

                    }

                    // If it collides with the second checkpoint for the first time, and already passed by the first one... 
                    if (hit.transform.gameObject == child2 && _firstChildSelected && !_secondChildSelected)
                    {
                        _secondChildSelected = true;
                    }
                }

                // Follow the touch.position until it reached the final position...
                else if (_firstChildSelected)
                {
                    currentPos = Camera.main.ScreenToWorldPoint(touch.position) + new Vector3(0, 0, 10);
                    direction = (endPos - positions[0]).normalized;
                    line.SetPosition(1, currentPos);

                }

                // When both have been selected, lock the line in
                if (_firstChildSelected == true && _secondChildSelected == true)
                {
                    currentPos = child2.transform.position;
                    line.SetPosition(1, currentPos);
                }
            }

            // Clear all on finger lift, but keep the line drawn
            if (touch.phase == TouchPhase.Ended)
            {
                Array.Clear(positions, 0, positions.Length);
                _firstChildSelected = false;
                _secondChildSelected = false;
            }
        }
    }
}

修改方案

核心是将触摸点投影到两个预设点的线段上,确保线条末端始终落在这条线段范围内。关键步骤:

  1. 计算起点到终点的线段向量
  2. 计算触摸点到起点的向量
  3. 用点积计算触摸点在线段上的投影比例,限制比例在0~1之间(避免超出线段)
  4. 通过比例计算出线段上的目标位置,作为线条末端

修改后的完整代码

using System;
using UnityEngine;

public class Path : MonoBehaviour
{
    [SerializeField] private GameObject child1, child2;
    [SerializeField] private LineRenderer line;

    private Vector3 _startPos;
    private Vector3 _endPos;
    [SerializeField] private bool _firstChildSelected = false;
    [SerializeField] private bool _secondChildSelected = false;

    Touch _touch;

    void Start()
    {
        _endPos = child2.transform.position;
        line.positionCount = 2;
    }

    void Update()
    {
        if (Input.touchCount <= 0) return;

        _touch = Input.GetTouch(0);
        Vector3 touchWorldPos = Camera.main.ScreenToWorldPoint(_touch.position);
        touchWorldPos.z = 10;

        RaycastHit2D hit = Physics2D.Raycast(touchWorldPos, Vector2.zero);

        if (_touch.phase is TouchPhase.Moved or TouchPhase.Stationary)
        {
            if (hit)
            {
                if (hit.transform.gameObject == child1 && !_firstChildSelected)
                {
                    _startPos = child1.transform.position;
                    _startPos.z = 10;
                    line.SetPosition(0, _startPos);
                    _firstChildSelected = true;
                }

                if (hit.transform.gameObject == child2 && _firstChildSelected && !_secondChildSelected)
                {
                    _secondChildSelected = true;
                    line.SetPosition(1, _endPos);
                }
            }

            if (_firstChildSelected && !_secondChildSelected)
            {
                Vector3 segmentVector = _endPos - _startPos;
                Vector3 touchToStart = touchWorldPos - _startPos;

                float dotProduct = Vector3.Dot(touchToStart, segmentVector);
                float segmentLengthSqr = segmentVector.sqrMagnitude;

                float t = Mathf.Clamp01(dotProduct / segmentLengthSqr);

                Vector3 targetPos = _startPos + t * segmentVector;
                line.SetPosition(1, targetPos);
            }
        }

        if (_touch.phase == TouchPhase.Ended)
        {
            _firstChildSelected = false;
            _secondChildSelected = false;
        }
    }
}

关键修改说明

  • 移除冗余的positions数组,直接用_startPos和_endPos管理两点位置
  • 添加line.positionCount = 2确保LineRenderer只有两个有效点
  • 统一触摸点和预设点的Z轴为10,避免坐标混乱
  • 核心逻辑:通过向量点积计算投影比例,用Mathf.Clamp01限制范围,确保线条末端始终在两点线段内
  • 简化代码结构,移除无效变量和逻辑

内容的提问来源于stack exchange,提问作者Syz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 18:30:41