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

Posted 林新发

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了游戏开发解答教你在Unity中使用LineRenderer制作行军蚂蚁线(行军 | 虚线 | 路径 | 线段)相关的知识,希望对你有一定的参考价值。

一、前言

嗨,大家好,我是新发。
有同学私信我,问了如下的问题,

这种线我们叫蚂蚁线,那么,我们在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
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~

以上是关于游戏开发解答教你在Unity中使用LineRenderer制作行军蚂蚁线(行军 | 虚线 | 路径 | 线段)的主要内容,如果未能解决你的问题,请参考以下文章

游戏开发实战手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)

游戏开发实战手把手教你在Unity中使用lua实现红点系统(前缀树 | 数据结构 | 设计模式 | 算法 | 含工程源码)

游戏开发实战教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)

游戏开发实战教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)

游戏开发实战教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)

游戏开发实战教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)