一、前言
嗨,大家伙,我是新發(fā)。
好久不見,這是2022年第一篇博客,今天有同學私信我,問我在Unity
中如何實現(xiàn)這種地球輻射線的效果,
這一看,我就想到了GitHub
主頁的地球射線,
那么,今天就來講講如何實現(xiàn)這個效果吧~
本文最終效果如下:
本文工程源碼見文章末尾~
二、實現(xiàn)思路
我們先把問題進行拆解,
現(xiàn)在挨個問題進行思考與解答。
1、曲線的本質(zhì)是什么?
一條曲線,它本質(zhì)上是由N
條直線段組成的,當N
足夠大的時候,曲線就會看起來很平滑,我用Blender
給大家演示一下,
2、如何繪制曲線?
我在之前的一些篇文章中有講過使用LineRenderer
來繪制曲線,這里我們是用LineRenderer
來實現(xiàn)就好啦。
往期相關文章:
《【游戲開發(fā)解答】教你在Unity中使用LineRenderer制作行軍螞蟻線(行軍 | 虛線 | 路徑 | 線段)》
《【游戲開發(fā)實戰(zhàn)】Unity實現(xiàn)水果忍者切水果的刀痕效果教程(兩種實現(xiàn)方式:TrailRenderer、LineRenderer)》
《【游戲開發(fā)實戰(zhàn)】TapTap物理畫線游戲,教你使用Unity實現(xiàn)2D物理畫線功能》
3、如何構造曲線的點?
上面我們說使用LineRenderer
來繪制曲線,而LineRenderer
需要我們告訴它點的坐標,那么我們?nèi)绾蝸順嬙烨€的點坐標呢?常用的曲線有B
樣條、貝塞爾曲線等,關于貝塞爾曲線,我之前也有專門寫過文章,
《【游戲開發(fā)進階】玩轉(zhuǎn)貝塞爾曲線,教你在Unity中畫Bezier貝塞爾曲線(二階、三階),手把手教你推導公式》
這里我們就用貝塞爾曲線即可,這里我打算使用三階貝塞爾曲線,三階貝塞爾曲線需要知道四個點坐標,現(xiàn)在問題變成了我們?nèi)绾未_定這四個點的坐標,其中起始點和終止點是在球的表面選取,中間兩個點我們通過一些幾何運算來獲得,現(xiàn)在問題變成了如何在球的表面獲取兩個點。
4、如何在球的表面選取兩個點?
這是一個幾何問題,我們已知球的球心坐標,想要在球的表面隨機選取兩個點,我們只需要在球心處隨機兩個方向向量,然后從球心出發(fā),分別沿著這兩個方向走一個半徑長度的距離即可到達球的表面,畫個圖方便大家理解,
寫成代碼大概是這樣子:
// 球心坐標
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;
我們使用上面的方法分別取到球表面的兩個點即可。
上面我們說使用三階貝塞爾曲線,現(xiàn)在還差中間兩個控制點的坐標,我們可以用下面這樣的方法來計算控制點的坐標:先求出球表面兩個點的連線的中點,然后從球心指向這個中點,得到一個方向向量,再分別從點1
和點2
朝這這個方向向量走一段距離,得到控制點1
和控制點2
的坐標,如下
5、如何讓曲線有動畫效果?
我們可以看到曲線是有一個從起始點飛向目標點的效果的,
這個我們可以在腳本中動態(tài)設置LineRenderer
的點來達到這個效果。
三、具體實操
講完了理論,下面我們就來具體實操吧~
1、創(chuàng)建Unity工程
工程名就叫UnityEarthRay
好了,工程模板使用3D
,點擊創(chuàng)建,
2、制作宇宙天空盒
2.1、天空盒貼圖
我們先去找6張
宇宙天空盒的圖片,導入到工程目錄中,如下
2.2、天空盒材質(zhì)球
創(chuàng)建一個材質(zhì)球,重命名為Skybox
,如下
設置材質(zhì)球的Shader
為Skybox/6 Sided
,并設置六個面的貼圖,如下,
2.3、設置場景天空盒
將Skybox
材質(zhì)球拖入Scene
窗口中,
我們也可以通過點擊菜單Window / Rendering / Lighting
,
然后點擊Environment
標簽頁,設置Skybox Material
來設置天空盒,
如果想改回原來的天空盒,只需把Skybox Material
設置為默認的天空盒材質(zhì)即可,如下
3、制作地球
3.1、創(chuàng)建球體
在Hierarchy
窗口空白處右鍵鼠標,點擊菜單3D Object / Sphere
,創(chuàng)建一個球體,
重命名為Earth
,作為地球的外形,
如下
3.2、地球貼圖
去找一下地球的貼圖,導入工程目錄中,我找了云層貼圖、地球貼圖、燈光貼圖,如下
3.3、制作地球材質(zhì)球
創(chuàng)建一個材質(zhì)球,重命名為Earth
,
設置材質(zhì)球的Shader
為Standard
,設置Albedo
貼圖,調(diào)節(jié)Matallic
(金屬度)和Smoothness
(光滑度),開啟Emission
(自發(fā)光),并設置發(fā)光貼圖,設置發(fā)光顏色為橘黃色,如下
然后把材質(zhì)球賦值給模型,如下
此時效果,
3.4、制作云層
同理,我們在Earth
子節(jié)點下再創(chuàng)建一個球體,重命名為Clouds
,如下
調(diào)節(jié)Clouds
的縮放,讓它比Earth
大一點點,
創(chuàng)建一個材質(zhì)球,重命名為Clouds
,
設置材質(zhì)球的Shader
為Standard
,設置Rendering Mode
為Transparent
(透明),設置Albedo
貼圖,設置顏色值的A
通道為0,調(diào)節(jié)Matallic
(金屬度)和Smoothness
(光滑度),開啟Emission
(自發(fā)光),并設置發(fā)光貼圖,設置發(fā)光顏色為灰色,如下
把Clouds
材質(zhì)球賦值給Clouds
節(jié)點,
此時效果
4、制作LineRenderer
4.1、創(chuàng)建LineRenderer
在Earth
節(jié)點右鍵鼠標,點擊菜單Effects / Line
,創(chuàng)建一個LineRenderer
,
如下
此時可以看到場景中多了一條粗粗的白色短線,它就是LineRenderer
本君了,
4.2、調(diào)節(jié)寬度
我們可以調(diào)節(jié)一下它的寬度,讓它細一點,
如下
4.3、設置材質(zhì)球
我們可以給他創(chuàng)建一個材質(zhì)球,
如下
5、Line腳本:曲線邏輯
創(chuàng)建一個C#
腳本,重命名為Line
,編寫曲線的邏輯代碼,
首先我們封裝一個三階貝塞爾曲線的函數(shù),如下
注:這里三階貝塞爾曲線代碼看不懂的建議先看我之前的這篇文章,《【游戲開發(fā)進階】玩轉(zhuǎn)貝塞爾曲線,教你在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;
}
接著我們定義曲線的最大點數(shù),
/// <summary>
/// 曲線最大點數(shù)
/// </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
節(jié)點上,如下
6、Earth腳本:地球邏輯
創(chuàng)建一個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()
{
// 地球自轉(zhuǎn)
selfTrans.Rotate(Vector3.up * Time.deltaTime, Space.Self);
}
/// <summary>
/// 發(fā)射輻射射線
/// </summary>
/// <returns></returns>
IEnumerator FireLine()
{
this.line.gameObject.SetActive(false);
while (true)
{
// 循環(huán)生成曲線,這里只是演示效果,我就不是用對象池了
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
節(jié)點上,并賦值Line
成員,如下
7、運行效果
運行Unity
,基本的效果已經(jīng)出來了,
四、動態(tài)效果
1、Line腳本:動態(tài)效果
我們要讓曲線有動態(tài)的效果,把原來的DrawRay
改成協(xié)程,動態(tài)設置點的坐標,如下,
// 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
腳本中的調(diào)用,如下
// Earth.cs
/// <summary>
/// 發(fā)射輻射射線
/// </summary>
/// <returns></returns>
IEnumerator FireLine()
{
this.line.gameObject.SetActive(false);
while (true)
{
...
// 啟動協(xié)程
StartCoroutine(line.DrawRay(from, ctrlPoint1, ctrlPoint2, to));
// 隨機等待一個事件
yield return new WaitForSeconds(Random.Range(0.1f, 0.5f));
}
}
2、運行效果
運行效果如下,可以,
五、加點粒子特效
制作個粒子效果,
包裝成預設,掛到Line
節(jié)點下,作為起始點和目標點的特效,如下
在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
文章來源:http://www.zghlxwxcb.cn/news/detail-459138.html
七、完畢
好了,就寫到這里吧。
我是新發(fā),https://blog.csdn.net/linxinfa
一個在小公司默默奮斗的Unity
開發(fā)者,希望可以幫助更多想學Unity
的人,共勉~文章來源地址http://www.zghlxwxcb.cn/news/detail-459138.html
到了這里,關于【游戲開發(fā)實戰(zhàn)】Unity實現(xiàn)類似GitHub地球射線的效果(LineRenderer | 貝塞爾曲線)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!