【游戏开发解答】教你在Unity中使用LineRenderer制作行军蚂蚁线(行军 | 虚线 | 路径 | 线段)

本文详细介绍了在Unity中使用LineRenderer组件和材质的Tiling及Offset属性实现蚂蚁线效果,从直线的动态纹理滚动到曲线的绘制,包括代码控制Tiling和Offset、跟随鼠标设置目标坐标、小蚂蚁动态爬行等关键步骤,最后提供了一个实现曲线蚂蚁线的完整代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

嗨,大家好,我是新发。
有同学私信我,问了如下的问题,
在这里插入图片描述
这种线我们叫蚂蚁线,那么,我们在Unity中如何实现呢?今天我就来讲讲~

本文最终效果,(工程源码见文章末尾)
直线蚂蚁线:
请添加图片描述
曲线蚂蚁线:
请添加图片描述

二、实现方案

Unity中有一个LineRenderer组件,可以很方便得进行线段的绘制,比如钢铁侠的激光技能,就可以使用LineRenderer来实现,
请添加图片描述
我之前写过很多篇文章讲过LineRenderer的教程,大家可以看下:
《【游戏开发实战】Unity实现水果忍者切水果的刀痕效果教程(两种实现方式:TrailRenderer、LineRenderer)》

《【游戏开发进阶】玩转贝塞尔曲线,教你在Unity中画Bezier贝塞尔曲线(二阶、三阶),手把手教你推导公式》

《【游戏开发实战】TapTap物理画线游戏,教你使用Unity实现2D物理画线功能,看到我为你画的彩虹了吗》

《【游戏开发实验】Unity音频效果可视化显示(GetSpectrumData接口)》

那么,如何使用LineRenderer实现蚂蚁线的效果呢?
其实只需要在材质上做点文章就可以了,材质属性中有个TilingOffset来操作UV,讲到这里,估计部分小伙伴已经知道如何实现了,如果还没悟透,没关系,继续往下看吧~

三、具体实操

1、图片资源:line.png

我用PhotoShop做了一张线段图片,线段的两边留一些空白,如下,
在这里插入图片描述
保存为lin.png,导入到Unity中,勾选Alpha Is Transparency,点击Apply
在这里插入图片描述
效果如下
在这里插入图片描述

2、制造材质球:line.mat

接着我们创建一个材质球,重命名为line
在这里插入图片描述
选中line材质球,设置shaderUnlit/Transparent,设置贴图为刚刚的line.png,如下,
在这里插入图片描述
这样,我们的材质球就搞定啦~

3、创建LineRenderer

我们在Hierarchy视图中右键鼠标,点击菜单Effects / Line,就可以场景一个带LineRenderer组件的物体啦,
在这里插入图片描述
如下
在这里插入图片描述
接着我们设置LineRenderer的材质为刚刚的line.mat,如下,
在这里插入图片描述
此时我们在场景中就可以看到一条线段了,
在这里插入图片描述

4、调节材质的Tiling和Offset

我们先设置一下LineRenderer的两个坐标点,比如我设置为(0, 0, 0)(10, 0, 0)
在这里插入图片描述
可以看到此时线段变长了,
在这里插入图片描述
上面我们也看到,贴图被拉伸了,没关系,调整一下Tiling,如下,
请添加图片描述
一节一节的小线段就出来了,
在这里插入图片描述
如果我们想让线上的 “小蚂蚁” 动起来,只需要调节Offset参数即可,如下,
请添加图片描述

5、用代码控制Tiling和Offset

上面我们是手动调节TilingOffset,实际运行过程中是需要通过代码来控制的,
需要用到Material的两个方法:

// Material.cs
// 设置Tiling
public void SetTextureScale(int nameID, Vector2 value);
// 设置Offset
public void SetTextureOffset(int nameID, Vector2 value);

我们创建一个LineCtrler.cs脚本,
在这里插入图片描述
代码很简单,如下,

using UnityEngine;

public class LineCtrler : MonoBehaviour
{
    [SerializeField]
    private LineRenderer lineRenderer;
    private Material material;
    private Vector2 tiling;
    private Vector2 offset;
    private int mainTexProperty;

    void Start()
    {
        // 缓存材质实例
        material = lineRenderer.material;
        // 缓存属性id,防止下面设置属性的时候重复计算名字的哈希
        mainTexProperty = Shader.PropertyToID("_MainTex");

        tiling = new Vector2(20, 0);
        offset = new Vector2(0, 0);
        // 设置Tiling
        material.SetTextureScale(mainTexProperty, tiling);
        // 设置Offset
        material.SetTextureOffset(mainTexProperty, offset);
    }
}
6、挂脚本进行测试

我们给场景中的Line挂上LineCtrler脚本,并设置LineRenderer成员,如下,
在这里插入图片描述
运行测试,可以看到,代码正常工作,
请添加图片描述

7、LineRenderer起始点跟随飞机坐标

上面,我们的线段是静止的,实际需求是根据鼠标点击的目标位置设置线段的坐标,为了演示,我去找个飞机的图标,找图标,推荐在阿里图标库中,我在多篇文章中都安利过,地址:https://www.iconfont.cn/
在这里插入图片描述
把飞机图片导入工程中,设置Texture TypeSprite (2D and UI)
在这里插入图片描述
把飞机放入场景中,
请添加图片描述
然后我们加点代码,让LineRenderer的起始点跟随飞机坐标,如下

using UnityEngine;

public class LineCtrler : MonoBehaviour
{
	// ... 

	// 飞机
    [SerializeField]
    private Transform airplane;
	
	private void Update() {
        lineRenderer.SetPosition(0, airplane.position);
    }
}

回到场景中,给LineCtrler脚本设置Airplane对象,如下,
在这里插入图片描述
运行测试,可以看到,LineRenderer的起始坐标已经跟随飞机的坐标了,
请添加图片描述

9、根据线段长度计算Tiling

我们上面看到,线段长度变化的时候,纹理相应的发生拉伸和挤压,这是因为我们没有动态计算Tiling,我们只需要根据长度来计算Tiling即可,改一下代码,如下,

using UnityEngine;

public class LineCtrler : MonoBehaviour
{
	// ... 
	
    // 线长
    private float lineLen;
    // 密度
    [SerializeField]
    private float density = 2f;

	private void Update() 
	{
		// ...
		
		// 计算线长度
		lineLen = (lineRenderer.GetPosition(1) - lineRenderer.GetPosition(0)).magnitude;
		// 根据线段长度计算Tiling
		tiling = new Vector2(lineLen * density, 0);
		// 设置Tiling
		material.SetTextureScale(mainTexProperty, tiling);
	}
}

运行测试,可以看到,现在蚂蚁线纹理正常了,
请添加图片描述

10、小蚂蚁爬起来

我们上面看到,蚂蚁线上的 “小蚂蚁” 运行中没有动态地爬起来,我们在Update中加上对Offset的设置即可,如下,

using UnityEngine;

public class LineCtrler : MonoBehaviour
{
	// ...
	
    // 定时器
    private float timer = 0;
    // 频率间隔
    [SerializeField]
    private float frequency = 0.03f;
    // 小蚂蚁爬行速度
    [SerializeField]
    private float moveSpeed = 0.04f;

	private void Update() 
	{
		// ...
		
		timer += Time.deltaTime;
        if(timer >= frequency)
        {
            timer = 0;
            offset -= new Vector2(moveSpeed, 0);
            material.SetTextureOffset(mainTexProperty, offset);
        }
	}
}

运行测试,可以看到,小蚂蚁爬起来了,
请添加图片描述

11、点击设置目标坐标

最后一步就是获取鼠标点击的位置设置为LineRenderer的终点,让飞机飞过去,继续加逻辑,如下,(注意:严格来说,飞机的逻辑不应该写在LineCtrler.cs中,这里只是演示,所以我就不单独写到新的脚本啦)

using UnityEngine;

public class LineCtrler : MonoBehaviour
{
	// ...
	
    // 主摄像机
    private Camera mainCam;
    // 目标坐标
    private Vector3 targetPos;
    // 飞机飞行速度
    [SerializeField]
    private float flySpeed = 0.01f;
    // 是否到达目标坐标
    private bool reachTargetPos = false;

	private void Update()
	{
		// ...
		
		// 严格来说,飞机的逻辑不应该写在LineCtrler中
		// 这里只是演示,所以我就不单独写到新的脚本啦
		if (Input.GetMouseButtonDown(0))
		{
			var screenPos = Input.mousePosition;
			// 屏幕坐标转世界坐标,注意z轴是距离摄像机的距离
			targetPos = mainCam.ScreenToWorldPoint(new Vector3(screenPos.x, screenPos.y, 10));
			// 这里用up是因为飞机的朝向的方向是y轴的方向,如果你的飞机的朝向是z轴的,则用forward
			airplane.up = targetPos - airplane.position;
			// 设置LineRenderer的终点
			lineRenderer.SetPosition(1, targetPos);
			reachTargetPos = false;
			lineRenderer.enabled = true;
		}
		if (!reachTargetPos)
		{
			// 飞机飞向目标的
			airplane.position += airplane.up * flySpeed;
			
			// 检测是否到达目标坐标
			if (Vector3.Dot(airplane.up, targetPos - airplane.position) < 0)
			{
			    airplane.position = targetPos;
			    reachTargetPos = true;
			    lineRenderer.enabled = false;
			}
		}
	}
}

运行测试,可以看到,我们要的效果已经有了,
请添加图片描述
我们优化一下,加个背景图,换个飞机图片,效果如下
请添加图片描述

四、更新:实现曲线蚂蚁线

这位同学后面提问曲线的实现,我这里做一下补充,
在这里插入图片描述
其实曲线就是很多段线段组成的,我们在直线的基础上进行拓展即可。
我最终实现的效果如下
请添加图片描述
曲线蚂蚁线实现代码在CurveCtrler.cs脚本中,
在这里插入图片描述
脚本逻辑不复杂,我有写注释,应该能看懂,完整代码如下:

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 曲线蚂蚁线
/// </summary>
public class CurveCtrler : MonoBehaviour
{
    [SerializeField]
    private LineRenderer lineRenderer;
    private Material material;
    private Vector2 tiling;
    private Vector2 offset;
    private int mainTexProperty;

    // 飞机
    [SerializeField]
    private Transform airplane;

    // 线长
    private float lineLen;
    // 密度
    [SerializeField]
    private float density = 2f;

    // 定时器
    private float timer = 0;
    // 频率间隔
    [SerializeField]
    private float frequency = 0.03f;
    // 小蚂蚁爬行速度
    [SerializeField]
    private float moveSpeed = 0.04f;

    // 主摄像机
    private Camera mainCam;
    // 目标坐标
    private Vector3 targetPos;
    // 飞机飞行速度
    [SerializeField]
    private float flySpeed = 0.01f;
    // 是否到大目标坐标
    private bool reachTargetPos = false;


    /// <summary>
    /// 画线过程中点与点的最小距离
    /// </summary>
    float pointsMinDistance = 0.3f;
    private List<Vector3> points;
    private Vector3 targetDir;
    [SerializeField]
    private float rotateSpeed = 2;

    void Start()
    {
        points = new List<Vector3>();

        // 缓存材质实例
        material = lineRenderer.material;
        // 缓存属性id,防止下面设置属性的时候重复计算名字的哈希
        mainTexProperty = Shader.PropertyToID("_MainTex");

        tiling = new Vector2(20, 0);
        offset = new Vector2(0, 0);

        // 缓存摄像机
        mainCam = Camera.main;

        lineRenderer.enabled = false;
        points.Add(airplane.position);
    }

    private void Update()
    {
        lineRenderer.SetPosition(0, airplane.position);
        points[0] = airplane.position;

        // 计算线长度
        lineLen = CalculateTotalLen();
        // 根据线段长度计算Tiling
        tiling = new Vector2(lineLen * density, 0);

        // 设置Tiling
        material.SetTextureScale(mainTexProperty, tiling);


        timer += Time.deltaTime;
        if (timer >= frequency)
        {
            timer = 0;
            offset -= new Vector2(moveSpeed, 0);
            material.SetTextureOffset(mainTexProperty, offset);
        }

        // ----------------------------------------------------------------------------------
        // 严格来说,飞机的逻辑不应该写在LineCtrler中
        // 这里只是演示,所以我就不单独写到新的脚本啦
        if (Input.GetMouseButton(0))
        {
            var screenPos = Input.mousePosition;
            // 屏幕坐标转世界坐标,注意z轴是距离摄像机的距离
            var worldPos = mainCam.ScreenToWorldPoint(new Vector3(screenPos.x, screenPos.y, 10));
            AddPoint(worldPos);
        }
        if (!reachTargetPos)
        {
            // 飞机飞向目标的
            airplane.position += targetDir * flySpeed;
            airplane.up = Vector3.Lerp(airplane.up, targetDir, Time.deltaTime * rotateSpeed);

            // 检测是否到达目标坐标
            if (Vector3.Dot(targetDir, targetPos - airplane.position) < 0)
            {
                airplane.position = targetPos;
                reachTargetPos = true;
                points.RemoveAt(1);
                lineRenderer.positionCount = points.Count;
                lineRenderer.SetPositions(points.ToArray());
                if (points.Count >= 2)
                    ResetTargetPos();
                else
                    lineRenderer.enabled = false;
            }
        }
    }

    /// <summary>
    /// 设置飞机目标点
    /// </summary>
    private void ResetTargetPos()
    {
        targetPos = points[1];
        targetDir = (targetPos - points[0]).normalized;
        reachTargetPos = false;
    }

    /// <summary>
    /// 插入新的点
    /// </summary>
    /// <param name="newPoint"></param>
    private void AddPoint(Vector2 newPoint)
    {
        if (points.Count >= 1 && Vector2.Distance(newPoint, GetLastPoint()) < pointsMinDistance)
            return;

        points.Add(newPoint);
        lineRenderer.enabled = true;
        // Line Renderer
        lineRenderer.positionCount = points.Count;
        lineRenderer.SetPosition(points.Count - 1, newPoint);
        if (2 == points.Count)
        {
            ResetTargetPos();
        }
    }


    /// <summary>
    /// 获取最后一个点
    /// </summary>
    /// <returns></returns>
    private Vector2 GetLastPoint()
    {
        return lineRenderer.GetPosition(points.Count - 1);
    }

    /// <summary>
    /// 计算曲线总长度
    /// </summary>
    private float CalculateTotalLen()
    {
        float totalLen = 0;
        for (int i = 1, cnt = lineRenderer.positionCount; i < cnt; ++i)
        {
            totalLen += (lineRenderer.GetPosition(i) - lineRenderer.GetPosition(i - 1)).magnitude;
        }
        return totalLen;
    }
}

五、工程源码

本文Demo工程我已上传到CODE CHINA,感兴趣的同学可自行下载学习,
地址:https://codechina.csdn.net/linxinfa/UnityMarchingAnts
(注意,我使用的Unity版本为2021.1.7f1c1,如果你使用的版本与我不同,可能打开工程会有一些兼容问题)
在这里插入图片描述

六、完毕

好啦,就到这里吧~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~

### 回答1: UILineRenderer.cs是Unity的一个脚本,用于在Unity中绘制2D线段,该脚本通常用于UI元素,例如在屏幕上绘制HUD元素、指示器、边框等。 该脚本使用的是Unity的UI系统,因此可以直接添加到UI元素上,例如Image或RawImage。它包含以下属性: - Points: 一个Vector2类型的数组,用于指定线段的顶点。 - LineThickness: 线段的宽度。 - Color: 线段的颜色。 除了这些属性之外,该脚本还包含了一些方法,例如AddPoint和RemovePoint,用于在运行时添加或删除线段的顶点。 使用UILineRenderer.cs,你可以轻松地在Unity中创建自定义的线段,例如在UI中绘制进度条、血条等。 ### 回答2: UILineRenderer.cs 是 Unity 中的一个脚本,用于在屏幕上绘制线段。它是 Unity UI 系统中的一部分,可以在 UI 元素上绘制 2D 线条。 使用 UILineRenderer.cs 可以轻松地创建和控制线段的外观和行为。可以根据需要设置线段的起点和终点,在两个点之间绘制一条直线。还可以通过更改线段的宽度、颜色和材质来定制其外观。 UILineRenderer.cs 提供了一些方法和属性来方便地操控线段。其中最主要的方法是 SetPoints,可以通过传入一个 Vector2 数组来设置线段的顶点坐标。此外,还有 SetColor、SetWidth 和 SetMaterial 等方法可以用来设置线段的颜色、宽度和材质。 使用 UILineRenderer.cs 绘制线段的过程非常简单。首先,需要在场景中创建一个空的 GameObject,并添加 UILineRenderer.cs 脚本组件。然后,在脚本中调用相应的方法来设置线段的属性,比如起点、终点、颜色等。最后,将该脚本所在的 GameObject 放置在 UI 元素上,就可以在屏幕上绘制出线段了。 总的来说,UILineRenderer.cs 是 Unity 中一个非常有用的工具,可以方便地在 UI 元素上绘制线段,可以用于实现各种需要线条表达的功能和效果,如画笔工具、连接线路等。 ### 回答3: UILineRenderer.cs 是 Unity 引擎中的一个脚本,用于在 Unity UI 中绘制线段。该脚本利用了 Unity 的 GraphicRaycaster 和 RectTransform 来实现线段的绘制。 UILineRenderer.cs 提供了一系列属性和方法,用于控制线段的形状、颜色和宽度。通过设置线段的起点和终点坐标,可以在画布上绘制出直线。同时,也可以通过设置控制点来绘制曲线和多段线。 在绘制线段之前,需要将 UILineRenderer.cs 组件添加到 Canvas 对象上。在脚本中,需要将起点、终点、控制点等信息传递给 UILineRenderer,并通过调用其 DrawLine() 方法来实现线段的绘制。 UILineRenderer.cs 利用 Unity 的 GraphicRaycaster 来实现线段的交互功能。我们可以通过代码设置触发事件,如点击或拖拽线段。同时,UILineRenderer.cs 也支持对线段进行颜色、宽度等属性的设置,使其更加美观和有趣。 总的来说,UILineRenderer.cs 是一个在 Unity UI 中绘制线段的实用脚本。通过使用该脚本,我们可以方便地在 Unity UI 中绘制直线、曲线和多段线,同时也可以实现线段与用户交互的功能。
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林新发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值