2D中激光束算是比較常見了误墓,實現(xiàn)起來也較為簡單,但為了讓它能真正達到照明的效果還是得花些功夫然想,這里記錄一下實現(xiàn)過程欣范。
整體思路:
- 使用Line Renderer制作激光束。
- 使用類型為Freeform的Light 2D實現(xiàn)光照恼琼,通過代碼動態(tài)修改光照形狀。
- 加一些特技蛙卤。
制作激光
激光束的實現(xiàn)思路基本參考油管上一個印度小哥的教程,有一些修改颤难。
材質
先進行連連看環(huán)節(jié),制作激光的材質行嗤。項目使用URP,新建一個Sprite Unlit Shader Graph,取名Laser艾扮。
圖形部分,對Voronoi節(jié)點在x軸上稍微拉伸甫恩,并且讓它隨時間在x軸上偏移酌予,這樣看起來會有一種電流的感覺:
Speed屬性控制運動速度,Scale屬性控制拉伸抛虫。這里也可以根據(jù)需要使用其他的噪聲圖,好看就行雕欺。
激光的邊緣需要有柔和漸變棉姐,通過sin(uv.y * PI)可以得到,再用指數(shù)函數(shù)控制邊緣的厚度即可:
將兩者相乘伞矩,再與顏色混合得到最終效果:
顏色模式為HDR,在Bloom后處理下會有不錯的效果苛让;另外個人覺得有透明度更好些侥袜,所以順便連接了Alpha。
Shader就做好了枫吧,以這個Shader新建一個材質Laser,調整各項參數(shù):
Line Renderer
場景中新建一個名為Laser的物體颁湖,添加Line Renderer組件,拖入剛才的Laser材質甥捺;Texture Mode改為Tile,以避免不同激光長度下拉伸不一致的問題皿曲。
可以順便在場景中新建一個Volume吴侦,開啟Bloom后處理:
臨時修改一下Positions,場景中可以看到效果:
交互
接下來讓它可以隨著角色施法而改變位置劫樟,這里角色使用的是Asset Store里的一個小魔女素材织堂,自帶骨骼動畫和控制腳本。
期望效果是玩家點擊鼠標左鍵易阳,角色舉起法杖,隨后激光向鼠標方向發(fā)射翅睛。編寫激光腳本黑竞,并掛在Laser物體下:
Laser2D.cs
[RequireComponent(typeof(LineRenderer))]
public class Laser2D : MonoBehaviour
{
LineRenderer line;
void Awake()
{
line = GetComponent<LineRenderer>();
SetEnable(false);
}
public void SetEnable(bool b)
{
line.enabled = b;
}
public void SetPositions(Vector3 start, Vector3 end)
{
line.SetPosition(0, start);
line.SetPosition(1, end);
}
}
之后將在角色控制腳本中調用這些方法。
激光發(fā)射需要一個發(fā)射起始點很魂,找到法杖的骨骼,在法杖頭上添加發(fā)射點FirePoint:
做一個施法的骨骼動畫法挨,并在最后一幀添加動畫事件幅聘,觸發(fā)角色腳本中的OnCastAnim
方法:
修改原有的角色控制腳本SimplePlayerController.cs,加入施法相關代碼:
PlayerController.cs
public class PlayerController : MonoBehaviour
{
...
public Transform firePoint;
public Laser2D laser;
public LayerMask laserBlockLayer;
...
private void Update()
{
...
if (alive)
{
...
Cast();
...
}
}
...
void Cast()
{
if (Input.GetMouseButton(0))
{
var mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
var direction = mousePos - firePoint.position;
var hit = Physics2D.Raycast(firePoint.position, direction, float.PositiveInfinity, laserBlockLayer);
if (hit)
{
laser.SetPositions(firePoint.position, hit.point);
anim.SetBool("isCasting", true);
}
}
else
{
laser.SetEnable(false);
anim.SetBool("isCasting", false);
}
}
public void OnCastAnim()
{
laser.SetEnable(true);
}
}
這部分比較簡單就不詳細說明了荐糜,總之就是先這樣這樣,然后再那樣那樣暴氏。
Laser物體放到角色之下,為各個變量賦好值答渔,可以看到初步效果:
光照
在Bloom效果下沼撕,這道激光看起來熠熠生輝,然而它并不能照亮周圍的物體务豺,為了讓激光具有照明效果,還需要添加光源冲呢。
動態(tài)修改光照形狀
給Laser物體添加一個Light 2D腳本招狸,Light Type為Freeform,點擊Edit Shape按鈕可以編輯它的形狀:
由于激光的形狀會不斷變化乘凸,固定的形狀不能滿足要求营勤,因此需要在代碼中根據(jù)激光的形狀動態(tài)修改Light 2D的形狀壹罚。
然而翻了一下API文檔,Unity似乎并沒有打算將形狀屬性開放給開發(fā)者修改猖凛,唯一和形狀相關的屬性只有一個shapePath,只允許get:
那么只能去Light 2D的源碼中找找蛛絲馬跡虱岂,在Light2DShape.cs中可以看到菠红,shapePath被定義在Light2D的一個部分類中:
m_ShapePath在Light2D.cs中的UpdateMesh方法中被使用:
可以看到當光照類型為Freeform時,它將根據(jù)m_ShapePath更新光照的mesh蔑滓。
繼續(xù)閱讀源碼可知,UpdateMesh方法在Awake烫饼、光照類型改變、Falloff改變荠耽、多邊形光形狀改變及Cookie的Sprite改變時會被調用,而光照類型為Freeform時形狀改變的情況下不會被調用铝量,這意味著更改m_ShapePath后银亲,必須要手動調用UpdateMesh方法,否則光照的形狀不會被更新务蝠。
回到Laser2D.cs,編寫一個SetShapePath方法馏段,之后將通過它更新Light 2D的形狀:
Laser2D.cs
void SetShapePath(Light2D light, Vector3[] path)
{
var field = light.GetType().GetField("m_ShapePath", BindingFlags.NonPublic | BindingFlags.Instance);
field?.SetValue(light, path);
var method = light.GetType().GetMethod("UpdateMesh", BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(light, null);
}
通過反射獲取到m_ShapePath,設值之后再調用UpdateMesh方法亡蓉。
繼續(xù)編寫喷舀,加入根據(jù)起點與終點更改Light2D形狀的處理:
Laser2D.cs
[RequireComponent(typeof(LineRenderer))]
public class Laser2D : MonoBehaviour
{
[Tooltip("光照半徑")]
public float lightRadius = .5f;
LineRenderer line;
Light2D lit;
void Awake()
{
line = GetComponent<LineRenderer>();
lit = GetComponent<Light2D>();
SetEnable(false);
}
public void SetEnable(bool b)
{
line.enabled = b;
lit.enabled = b;
}
public void SetPositions(Vector3 start, Vector3 end)
{
line.SetPosition(0, start);
line.SetPosition(1, end);
// 更改Light2D形狀
if (start != end)
{
var direction = end - start;
var localUp = Vector3.Cross(Vector3.forward, direction).normalized;
localUp = transform.InverseTransformDirection(localUp) * lightRadius;
var localStart = transform.InverseTransformPoint(start);
var localEnd = transform.InverseTransformPoint(end);
// 構造形狀路徑
var path = new Vector3[]
{
localStart - localUp,
localEnd - localUp,
localEnd + localUp,
localStart + localUp,
};
SetShapePath(lit, path);
}
}
...
}
這里將起點和終點轉化為本地坐標(Light 2D形狀使用本地坐標),分別給它們加爸邢、減一個方向相對于激光垂直向上、模長為光照半徑的向量localUp
甲棍,計算出四個頂點赶掖,且按逆時針順序排列。四個頂點形成一個矩形奢赂,運行可以看到初步效果:
白色粗框為動態(tài)生成的形狀膳灶,白色細框為Light 2D根據(jù)形狀自動生成的Falloff區(qū)域立由。
完善形狀
光是一個矩形還是難看了些序厉,光照的邊角看起來相當突兀。再給兩邊加上半圓弛房,形成一個類似膠囊的形狀。
畫圓本質上是畫多邊形文捶,先定義好圓的頂點數(shù)量:
Laser2D.cs
[RequireComponent(typeof(LineRenderer))]
public class Laser2D : MonoBehaviour
{
[Tooltip("圓的頂點數(shù)")]
public int circleVertices = 10;
...
刪去原有構造矩形代碼,改為構造膠囊形狀:
Laser2D.cs
public void SetPositions(Vector3 start, Vector3 end)
{
...
// 更改Light2D形狀
if (start != end)
{
var direction = end - start;
var localUp = Vector3.Cross(Vector3.forward, direction).normalized;
localUp = transform.InverseTransformDirection(localUp) * lightRadius;
var localStart = transform.InverseTransformPoint(start);
var localEnd = transform.InverseTransformPoint(end);
// 構造形狀路徑
Vector3[] path = new Vector3[circleVertices + 2];
float deltaAngle = 2 * Mathf.PI / circleVertices;
float axisAngleOffset = Vector2.SignedAngle(Vector2.right, direction);
// 當前圓上頂點對應角度
float theta = Mathf.PI / 2 + Mathf.Deg2Rad * axisAngleOffset;
int index = 0;
// 起點處的半圓
path[index] = localStart + localUp;
for (int i = 0; i < circleVertices / 2; i++)
{
theta += deltaAngle;
path[++index] = localStart + new Vector3(lightRadius * Mathf.Cos(theta), lightRadius * Mathf.Sin(theta), 0);
}
// 終點處的半圓
path[++index] = localEnd - localUp;
for (int i = 0; i < circleVertices / 2; i++)
{
theta += deltaAngle;
path[++index] = localEnd + new Vector3(lightRadius * Mathf.Cos(theta), lightRadius * Mathf.Sin(theta), 0);
}
SetShapePath(lit, path);
}
}
效果:
處理翻轉
當她轉身朝向另一面時种远,光照顯示會有錯誤:
繼續(xù)修改形狀生成部分坠敷,加入對翻轉的處理:
Laser2D.cs
public void SetPositions(Vector3 start, Vector3 end)
{
...
// 更改Light2D形狀
if (start != end)
{
...
// 構造形狀路徑
Vector3[] path = new Vector3[circleVertices + 2];
float deltaAngle = 2 * Mathf.PI / circleVertices;
float axisAngleOffset = Vector2.SignedAngle(Vector2.right, direction);
// 處理翻轉情況射富,改變角度計算方向
if (transform.lossyScale.x < 0)
{
deltaAngle = -deltaAngle;
axisAngleOffset = -axisAngleOffset;
}
// 當前圓上頂點對應角度
...
// 起點處的半圓
...
// 終點處的半圓
...
// 處理翻轉情況,將所有頂點倒序
if (transform.lossyScale.x < 0)
System.Array.Reverse(path);
SetShapePath(lit, path);
}
}
修復后:
加一些特技
再加上一些粒子:
至此就基本完成了,還可以繼續(xù)完善如發(fā)射時的粒子爆發(fā)茎辐、亮度變化、激光顏色設置等等拖陆。
項目中用到的素材:
Cute 2D Girl - Wizard by ClearSky
Demo項目地址:
2D-Laser