[Unity]帶照明效果的2D激光束

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ù)控制邊緣的厚度即可:

將兩者相乘伞矩,再與顏色混合得到最終效果:

激光混合.png

顏色模式為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

2D DarkCave Assets by Maaot

Demo項目地址:
2D-Laser

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乎串,隨后出現(xiàn)的幾起案子速警,更是在濱河造成了極大的恐慌叹誉,老刑警劉巖闷旧,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匠襟,居然都是意外死亡,警方通過查閱死者的電腦和手機酸舍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忽舟,“玉大人,你說我怎么就攤上這事萧诫≈λ唬” “怎么了帘饶?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵及刻,是天一觀的道長竞阐。 經常有香客問我,道長骆莹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任丢氢,我火速辦了婚禮先改,結果婚禮上,老公的妹妹穿的比我還像新娘仇奶。我一直安慰自己,他們只是感情好该溯,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弦撩,像睡著了一般论皆。 火紅的嫁衣襯著肌膚如雪益楼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天悯周,我揣著相機與錄音陪竿,去河邊找鬼。 笑死族跛,一個胖子當著我的面吹牛,可吹牛的內容都是我干的礁哄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼夺脾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了咧叭?” 一聲冷哼從身側響起烁竭,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎派撕,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腥刹,經...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡衔峰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年蛙粘,在試婚紗的時候發(fā)現(xiàn)自己被綠了垫卤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穴肘。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡舔痕,死狀恐怖豹缀,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情邢笙,我是刑警寧澤侍匙,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站想暗,受9級特大地震影響,放射性物質發(fā)生泄漏说莫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一告唆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擒悬,春花似錦稻艰、人聲如沸懂牧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躯保。三九已至,卻和暖如春途事,著一層夾襖步出監(jiān)牢的瞬間擅羞,已是汗流浹背尸变。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工减俏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人奏夫。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像桶蛔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子仔雷,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容

  • 做一個類似于《INK》的2D顏料潑濺效果: 表面與顏料 利用模板測試,讓顏料污漬能在物體表面上重疊顯示且不超出物體...
    pamisu閱讀 2,044評論 0 3
  • 簡單的介紹與起步 Unity的Playground是一個用來制作擁有物理引擎的2D游戲開發(fā)框架(framework...
    超級超級小天才閱讀 5,959評論 0 4
  • 一:什么是協(xié)同程序蝙叛? 在主線程運行的同時開啟另一段邏輯處理,來協(xié)助當前程序的執(zhí)行借帘,協(xié)程很像多線程,但是不是多線程肺然,...
    胤醚貔貅閱讀 2,069評論 0 13
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月腿准,有人笑有人哭际起,有人歡樂有人憂愁吐葱,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,523評論 28 53
  • 信任包括信任自己和信任他人 很多時候灾前,很多事情,失敗哎甲、遺憾、錯過烧给,源于不自信喝噪,不信任他人 覺得自己做不成,別人做不...
    吳氵晃閱讀 6,181評論 4 8