- 一、前言
- 二、实现思路
- 1、曲线的本质是什么?
- 2、如何绘制曲线?
- 3、如何构造曲线的点?
- 4、如何在球的表面选取两个点?
- 5、如何让曲线有动画效果?
- 三、具体实操
- 1、创建Unity工程
- 2、制作宇宙天空盒
- 2.1、天空盒贴图
- 2.2、天空盒材质球
- 2.3、设置场景天空盒
- 3、制作地球
- 3.1、创建球体
- 3.2、地球贴图
- 3.3、制作地球材质球
- 3.4、制作云层
- 4、制作LineRenderer
- 4.1、创建LineRenderer
- 4.2、调节宽度
- 4.3、设置材质球
- 5、Line脚本:曲线逻辑
- 6、Earth脚本:地球逻辑
- 7、运行效果
- 四、动态效果
- 1、Line脚本:动态效果
- 2、运行效果
- 五、加点粒子特效
- 六、工程源码
- 七、完毕
嗨,大家伙,我是新发。
好久不见,这是2022年第一篇博客,今天有同学私信我,问我在Unity中如何实现这种地球辐射线的效果,
这一看,我就想到了GitHub主页的地球射线,
那么,今天就来讲讲如何实现这个效果吧~
本文最终效果如下:
本文工程源码见文章末尾~
我们先把问题进行拆解,
现在挨个问题进行思考与解答。
1、曲线的本质是什么?一条曲线,它本质上是由N条直线段组成的,当N足够大的时候,曲线就会看起来很平滑,我用Blender给大家演示一下,
2、如何绘制曲线?我在之前的一些篇文章中有讲过使用LineRenderer来绘制曲线,这里我们是用LineRenderer来实现就好啦。
往期相关文章:
《【游戏开发解答】教你在Unity中使用LineRenderer制作行军蚂蚁线(行军 | 虚线 | 路径 | 线段)》
《【游戏开发实战】Unity实现水果忍者切水果的刀痕效果教程(两种实现方式:TrailRenderer、LineRenderer)》
《【游戏开发实战】TapTap物理画线游戏,教你使用Unity实现2D物理画线功能》
上面我们说使用LineRenderer来绘制曲线,而LineRenderer需要我们告诉它点的坐标,那么我们如何来构造曲线的点坐标呢?常用的曲线有B样条、贝塞尔曲线等,关于贝塞尔曲线,我之前也有专门写过文章,
《【游戏开发进阶】玩转贝塞尔曲线,教你在Unity中画Bezier贝塞尔曲线(二阶、三阶),手把手教你推导公式》
这里我们就用贝塞尔曲线即可,这里我打算使用三阶贝塞尔曲线,三阶贝塞尔曲线需要知道四个点坐标,现在问题变成了我们如何确定这四个点的坐标,其中起始点和终止点是在球的表面选取,中间两个点我们通过一些几何运算来获得,现在问题变成了如何在球的表面获取两个点。
这是一个几何问题,我们已知球的球心坐标,想要在球的表面随机选取两个点,我们只需要在球心处随机两个方向向量,然后从球心出发,分别沿着这两个方向走一个半径长度的距离即可到达球的表面,画个图方便大家理解,
写成代码大概是这样子:
// 球心坐标 Vector3 centerPos = Vector3.zero; // 球半径 float radius = 1; // 随机一个单位向量作为方向向量 Vector3 randomDir = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f)).normalized; // 球表面的点 Vector pos = centerPos + randomDir * radius;
我们使用上面的方法分别取到球表面的两个点即可。
上面我们说使用三阶贝塞尔曲线,现在还差中间两个控制点的坐标,我们可以用下面这样的方法来计算控制点的坐标:先求出球表面两个点的连线的中点,然后从球心指向这个中点,得到一个方向向量,再分别从点1和点2朝这这个方向向量走一段距离,得到控制点1和控制点2的坐标,如下
我们可以看到曲线是有一个从起始点飞向目标点的效果的,
这个我们可以在脚本中动态设置LineRenderer的点来达到这个效果。
讲完了理论,下面我们就来具体实操吧~
1、创建Unity工程工程名就叫UnityEarthRay好了,工程模板使用3D,点击创建,
我们先去找6张宇宙天空盒的图片,导入到工程目录中,如下
创建一个材质球,重命名为Skybox,如下
设置材质球的Shader为Skybox/6 Sided,并设置六个面的贴图,如下,
将Skybox材质球拖入Scene窗口中,
我们也可以通过点击菜单Window / Rendering / Lighting,
然后点击Environment标签页,设置Skybox Material来设置天空盒,
如果想改回原来的天空盒,只需把Skybox Material设置为默认的天空盒材质即可,如下
在Hierarchy窗口空白处右键鼠标,点击菜单3D Object / Sphere,创建一个球体,
重命名为Earth,作为地球的外形,
如下
去找一下地球的贴图,导入工程目录中,我找了云层贴图、地球贴图、灯光贴图,如下
创建一个材质球,重命名为Earth,
设置材质球的Shader为Standard,设置Albedo贴图,调节Matallic(金属度)和Smoothness(光滑度),开启Emission(自发光),并设置发光贴图,设置发光颜色为橘黄色,如下
然后把材质球赋值给模型,如下
此时效果,
同理,我们在Earth子节点下再创建一个球体,重命名为Clouds,如下
调节Clouds的缩放,让它比Earth大一点点,
创建一个材质球,重命名为Clouds,
设置材质球的Shader为Standard,设置Rendering Mode为Transparent(透明),设置Albedo贴图,设置颜色值的A通道为0,调节Matallic(金属度)和Smoothness(光滑度),开启Emission(自发光),并设置发光贴图,设置发光颜色为灰色,如下
把Clouds材质球赋值给Clouds节点,
此时效果
在Earth节点右键鼠标,点击菜单Effects / Line,创建一个LineRenderer,
如下
此时可以看到场景中多了一条粗粗的白色短线,它就是LineRenderer本君了,
我们可以调节一下它的宽度,让它细一点,
如下
我们可以给他创建一个材质球,
如下
创建一个C#脚本,重命名为Line,编写曲线的逻辑代码,
首先我们封装一个三阶贝塞尔曲线的函数,如下
注:这里三阶贝塞尔曲线代码看不懂的建议先看我之前的这篇文章,《【游戏开发进阶】玩转贝塞尔曲线,教你在Unity中画Bezier贝塞尔曲线(二阶、三阶),手把手教你推导公式》
// Line.cs
// 三阶贝塞尔曲线
private Vector3 cubicBezier(Vector3 a, Vector3 b, Vector3 c, Vector3 d, float t)
{
Vector3 aa = a + (b - a) * t;
Vector3 bb = b + (c - b) * t;
Vector3 cc = c + (d - c) * t;
Vector3 aaa = aa + (bb - aa) * t;
Vector3 bbb = bb + (cc - bb) * t;
return aaa + (bbb - aaa) * t;
}
接着我们定义曲线的最大点数,
////// 曲线最大点数 /// public int MAX_FRAG_CNT = 100;
声明一个List用于存储点坐标
private ListposList = new List ();
我们声明一个lineRenderer成员,并在Awake中获取它,
[RequireComponent(typeof(LineRenderer))]
public class RadiationLine : MonoBehaviour
{
private LineRenderer lineRenderer;
...
void Awake()
{
lineRenderer = GetComponent();
}
...
}
封装一个绘制曲线的方法,如下
// Line.cs ////// 绘制曲线 /// /// 起始坐标 /// 控制点1 /// 控制点2 /// 目标坐标 public void DrawRay(Vector3 fromPos, Vector3 ctrlPoint1, Vector3 ctrlPoint2, Vector3 toPos) { posList.Clear(); for (int i = 0; i <= MAX_FRAG_CNT; ++i) { posList.Add(cubicBezier(fromPos, ctrlPoint1, ctrlPoint2, toPos, (float)i / MAX_FRAG_CNT)); lineRenderer.positionCount = posList.Count; } lineRenderer.SetPositions(posList.ToArray()); }
把Line脚本挂到Line节点上,如下
创建一个C#脚本,重命名为Earth,编写地球逻辑代码,
代码很简单,这里就不拆开讲解了,我都有写注释,
using System.Collections; using System.Collections.Generic; using UnityEngine; ////// 地球逻辑 /// public class Earth : MonoBehaviour { private Transform selfTrans; // 曲线 public Line line; void Start() { selfTrans = transform; StartCoroutine(FireLine()); } void Update() { // 地球自转 selfTrans.Rotate(Vector3.up * Time.deltaTime, Space.Self); } ////// 发射辐射射线 /// ///IEnumerator FireLine() { this.line.gameObject.SetActive(false); while (true) { // 循环生成曲线,这里只是演示效果,我就不是用对象池了 var line = Instantiate(this.line); line.gameObject.SetActive(true); line.transform.SetParent(selfTrans); // 半径 var radius = selfTrans.localScale.x / 2f; // 在地球表面随机一个起始点 var from = selfTrans.position + new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f)).normalized * radius; // 在地球表面随机一个终点 var to = selfTrans.position + new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f)).normalized * radius; var center = (from + to) / 2f; // 控制点1 var ctrlPoint1 = from + (center - selfTrans.position).normalized * (from - to).magnitude * 0.6f; // 控制点2 var ctrlPoint2 = to + (center - selfTrans.position).normalized * (from - to).magnitude * 0.6f; line.DrawRay(from, ctrlPoint1, ctrlPoint2, to); // 随机一个时间后销毁曲线 Destroy(line.gameObject, Random.Range(4, 7)); // 随机等待一个事件 yield return new WaitForSeconds(Random.Range(0.3f, 2f)); } } }
把Earth脚本挂到Earth节点上,并赋值Line成员,如下
运行Unity,基本的效果已经出来了,
我们要让曲线有动态的效果,把原来的DrawRay改成协程,动态设置点的坐标,如下,
// Line.cs ////// 绘制曲线 /// /// 起始坐标 /// 控制点1 /// 控制点2 /// 目标坐标 public IEnumerator DrawRay(Vector3 fromPos, Vector3 handPos1, Vector3 handPos2, Vector3 toPos) { for (int i = 0; i <= MAX_FRAG_CNT; ++i) { posList.Clear(); for (int j = 0; j <= i; ++j) { posList.Add(cubicBezier(fromPos, handPos1, handPos2, toPos, (float)j / MAX_FRAG_CNT)); } lineRenderer.positionCount = posList.Count; lineRenderer.SetPositions(posList.ToArray()); yield return new WaitForSeconds(0.02f); } yield return new WaitForSeconds(2); for (int i = 0; i <= MAX_FRAG_CNT; ++i) { posList.Clear(); for (int j = i; j <= MAX_FRAG_CNT; ++j) { posList.Add(cubicBezier(fromPos, handPos1, handPos2, toPos, (float)j / MAX_FRAG_CNT)); } lineRenderer.positionCount = posList.Count; lineRenderer.SetPositions(posList.ToArray()); yield return new WaitForSeconds(0.001f); } Destroy(gameObject); }
改下Earth脚本中的调用,如下
// Earth.cs ///2、运行效果/// 发射辐射射线 /// ///IEnumerator FireLine() { this.line.gameObject.SetActive(false); while (true) { ... // 启动协程 StartCoroutine(line.DrawRay(from, ctrlPoint1, ctrlPoint2, to)); // 随机等待一个事件 yield return new WaitForSeconds(Random.Range(0.1f, 0.5f)); } }
运行效果如下,可以,
制作个粒子效果,
包装成预设,挂到Line节点下,作为起始点和目标点的特效,如下
在Line脚本中加上粒子控制的逻辑,
// Line.cs public Transform startPoint; public Transform endPoint; ////// 绘制曲线 /// /// 起始坐标 /// 控制点1 /// 控制点2 /// 目标坐标 public IEnumerator DrawRay(Vector3 earthPos, Vector3 fromPos, Vector3 handPos1, Vector3 handPos2, Vector3 toPos) { // 起始位置粒子 startPoint.gameObject.SetActive(true); startPoint.forward = fromPos - earthPos; startPoint.localPosition = fromPos + startPoint.forward * 0.01f; for (int i = 0; i <= MAX_FRAG_CNT; ++i) { posList.Clear(); for (int j = 0; j <= i; ++j) { posList.Add(cubicBezier(fromPos, handPos1, handPos2, toPos, (float)j / MAX_FRAG_CNT)); } lineRenderer.positionCount = posList.Count; lineRenderer.SetPositions(posList.ToArray()); yield return new WaitForSeconds(0.02f); } // 目标位置粒子 endPoint.gameObject.SetActive(true); endPoint.forward = toPos - earthPos; endPoint.localPosition = toPos + endPoint.forward * 0.01f; yield return new WaitForSeconds(2); startPoint.gameObject.SetActive(false); for (int i = 0; i <= MAX_FRAG_CNT; ++i) { posList.Clear(); for (int j = i; j <= MAX_FRAG_CNT; ++j) { posList.Add(cubicBezier(fromPos, handPos1, handPos2, toPos, (float)j / MAX_FRAG_CNT)); } lineRenderer.positionCount = posList.Count; lineRenderer.SetPositions(posList.ToArray()); yield return new WaitForSeconds(0.001f); } Destroy(gameObject); }
最终运行效果如下
本文工程我已上传到GitCode,感兴趣的同学可自行下载学习,
地址:https://gitcode.net/linxinfa/UnityEarthRay
注:我使用的Unity版本是2021.1.7.f1c1
好了,就写到这里吧。
我是新发,https://blog.csdn.net/linxinfa
一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~



