嗨,大傢伙,我是新發。
好久不見,這是2022年第一篇部落格,今天有同學私信我,問我在Unity
中如何實現這種地球輻射線的效果,
這一看,我就想到了GitHub
主頁的地球射線,
那麼,今天就來講講如何實現這個效果吧~
本文最終效果如下:
本文工程原始碼見文章末尾~
我們先把問題進行拆解,
現在挨個問題進行思考與解答。
一條曲線,它本質上是由N
條直線段組成的,當N
足夠大的時候,曲線就會看起來很平滑,我用Blender
給大家演示一下,
我在之前的一些篇文章中有講過使用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
的點來達到這個效果。
講完了理論,下面我們就來具體實操吧~
工程名就叫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;
}
接著我們定義曲線的最大點數,
/// <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
節點上,如下
建立一個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
成員,如下
執行Unity
,基本的效果已經出來了,
我們要讓曲線有動態的效果,把原來的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));
}
}
執行效果如下,可以,
製作個粒子效果,
包裝成預設,掛到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
的人,共勉~