文章目錄
- 一、前言
- 二、實作思路
- 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物理畫線功能》

3、如何構造曲線的點?
上面我們說使用LineRenderer來繪制曲線,而LineRenderer需要我們告訴它點的坐標,那么我們如何來構造曲線的點坐標呢?常用的曲線有B樣條、貝塞爾曲線等,關于貝塞爾曲線,我之前也有專門寫過文章,
《【游戲開發進階】玩轉貝塞爾曲線,教你在Unity中畫Bezier貝塞爾曲線(二階、三階),手把手教你推導公式》

這里我們就用貝塞爾曲線即可,這里我打算使用三階貝塞爾曲線,三階貝塞爾曲線需要知道四個點坐標,現在問題變成了我們如何確定這四個點的坐標,其中起始點和終止點是在球的表面選取,中間兩個點我們通過一些幾何運算來獲得,現在問題變成了如何在球的表面獲取兩個點,
4、如何在球的表面選取兩個點?
這是一個幾何問題,我們已知球的球心坐標,想要在球的表面隨機選取兩個點,我們只需要在球心處隨機兩個方向向量,然后從球心出發,分別沿著這兩個方向走一個半徑長度的距離即可到達球的表面,畫個圖方便大家理解,

寫成代碼大概是這樣子:
// 球心坐標
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的坐標,如下

5、如何讓曲線有影片效果?
我們可以看到曲線是有一個從起始點飛向目標點的效果的,

這個我們可以在腳本中動態設定LineRenderer的點來達到這個效果,
三、具體實操
講完了理論,下面我們就來具體實操吧~
1、創建Unity工程
工程名就叫UnityEarthRay好了,工程模板使用3D,點擊創建,

2、制作宇宙天空盒
2.1、天空盒貼圖
我們先去找6張宇宙天空盒的圖片,匯入到工程目錄中,如下

2.2、天空盒材質球
創建一個材質球,重命名為Skybox,如下

設定材質球的Shader為Skybox/6 Sided,并設定六個面的貼圖,如下,

2.3、設定場景天空盒
將Skybox材質球拖入Scene視窗中,
我們也可以通過點擊選單Window / Rendering / Lighting,

然后點擊Environment標簽頁,設定Skybox Material來設定天空盒,

如果想改回原來的天空盒,只需把Skybox Material設定為默認的天空盒材質即可,如下

3、制作地球
3.1、創建球體
在Hierarchy視窗空白處右鍵滑鼠,點擊選單3D Object / Sphere,創建一個球體,

重命名為Earth,作為地球的外形,

如下

3.2、地球貼圖
去找一下地球的貼圖,匯入工程目錄中,我找了云層貼圖、地球貼圖、燈光貼圖,如下

3.3、制作地球材質球
創建一個材質球,重命名為Earth,

設定材質球的Shader為Standard,設定Albedo貼圖,調節Matallic(金屬度)和Smoothness(光滑度),開啟Emission(自發光),并設定發光貼圖,設定發光顏色為橘黃色,如下

然后把材質球賦值給模型,如下

此時效果,

3.4、制作云層
同理,我們在Earth子節點下再創建一個球體,重命名為Clouds,如下

調節Clouds的縮放,讓它比Earth大一點點,

創建一個材質球,重命名為Clouds,

設定材質球的Shader為Standard,設定Rendering Mode為Transparent(透明),設定Albedo貼圖,設定顏色值的A通道為0,調節Matallic(金屬度)和Smoothness(光滑度),開啟Emission(自發光),并設定發光貼圖,設定發光顏色為灰色,如下

把Clouds材質球賦值給Clouds節點,

此時效果

4、制作LineRenderer
4.1、創建LineRenderer
在Earth節點右鍵滑鼠,點擊選單Effects / Line,創建一個LineRenderer,

如下

此時可以看到場景中多了一條粗粗的白色短線,它就是LineRenderer本君了,

4.2、調節寬度
我們可以調節一下它的寬度,讓它細一點,

如下

4.3、設定材質球
我們可以給他創建一個材質球,

如下

5、Line腳本:曲線邏輯
創建一個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;
}
接著我們定義曲線的最大點數,
/// <summary>
/// 曲線最大點數
/// </summary>
public int MAX_FRAG_CNT = 100;
宣告一個List用于存盤點坐標
private List<Vector3> posList = new List<Vector3>();
我們宣告一個lineRenderer成員,并在Awake中獲取它,
[RequireComponent(typeof(LineRenderer))]
public class RadiationLine : MonoBehaviour
{
private LineRenderer lineRenderer;
...
void Awake()
{
lineRenderer = GetComponent<LineRenderer>();
}
...
}
封裝一個繪制曲線的方法,如下
// Line.cs
/// <summary>
/// 繪制曲線
/// </summary>
/// <param name="fromPos">起始坐標</param>
/// <param name="ctrlPoint1">控制點1</param>
/// <param name="ctrlPoint2">控制點2</param>
/// <param name="toPos">目標坐標</param>
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節點上,如下

6、Earth腳本:地球邏輯
創建一個C#腳本,重命名為Earth,撰寫地球邏輯代碼,

代碼很簡單,這里就不拆開講解了,我都有寫注釋,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 地球邏輯
/// </summary>
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);
}
/// <summary>
/// 發射輻射射線
/// </summary>
/// <returns></returns>
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成員,如下

7、運行效果
運行Unity,基本的效果已經出來了,

四、動態效果
1、Line腳本:動態效果
我們要讓曲線有動態的效果,把原來的DrawRay改成協程,動態設定點的坐標,如下,
// Line.cs
/// <summary>
/// 繪制曲線
/// </summary>
/// <param name="fromPos">起始坐標</param>
/// <param name="ctrlPoint1">控制點1</param>
/// <param name="ctrlPoint2">控制點2</param>
/// <param name="toPos">目標坐標</param>
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
/// <summary>
/// 發射輻射射線
/// </summary>
/// <returns></returns>
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));
}
}
2、運行效果
運行效果如下,可以,

五、加點粒子特效
制作個粒子效果,

包裝成預設,掛到Line節點下,作為起始點和目標點的特效,如下

在Line腳本中加上粒子控制的邏輯,
// Line.cs
public Transform startPoint;
public Transform endPoint;
/// <summary>
/// 繪制曲線
/// </summary>
/// <param name="fromPos">起始坐標</param>
/// <param name="ctrlPoint1">控制點1</param>
/// <param name="ctrlPoint2">控制點2</param>
/// <param name="toPos">目標坐標</param>
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的人,共勉~
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/402573.html
標籤:其他
