彈道實現

 

轉自:https://www.gameres.com/677540.html

上篇

今天談談一個有趣的內容——u3d實現子彈彈道。當然這個完整的說,也非常複雜,還是由淺入深,先說說最核心的原理。

一、定義

我將子彈分爲至少兩種:

1、實體型。即發射後,生成一個帶剛體的gameobject,可以通過u3d的物理引擎實現碰撞檢測。比如機關槍子彈、彈道等等。

2、射線型。即子彈的運動和碰撞不由剛體實現,彈道控制由代碼實現,通過Physics2D下函數或自己計算物體檢測。比如定向激光、AOE傷害。

本文分別講兩種的實現。

二、理解U3D的物理引擎

知其所以然,才能更好、更快的進行代碼實現。這個章節從運用和現象入手,說明一些有關u3d物理引擎的“坑”

Collider和Rigidbody

如果完全不瞭解U3D的Collider(碰撞體)、Rigidbody(剛體)可以先找下相關的文章看下,網上很多很好的文章。

這裏用自己的理解簡單說下(針對2D):

Collider描繪物體的碰撞形狀,有Box、Cirlce等,是碰撞檢測的基礎,比如鼠標點擊的碰撞判斷,都是依賴於定義的Collider來知曉物體的“碰撞形狀”

Rigidbody是給物體賦予一個接受物理規律的屬性,比如給一個小球加上剛體,默認的小球就會收到重力加速度往下掉。

剛體必須有collider,不然就沒有“形狀”。

在u3d的2D物理效果的設計理念是:

不會動的物體,只設Collider,例如大地、樹木;

會動的,需要有物理運動效果的,設置Collider和Rigidbody,Collider說明“外形”,Rigidbody說明物理屬性,如質量、碰撞屬性等。

注意這個理念,因爲我首次使用2D物理到遊戲中的時候,有很多地方很費解,不明白爲什麼要這麼設計,如果理解這個理念了,很多東西就想明白了。

碰撞檢測的相關參數

U3D的2D物理引擎,可以不用做任何編碼實現剛體的運動碰撞效果。

但如果需要代碼獲取碰撞信息,相關的屬性有:

Collider的Is Trigger,Rigidbody的Is Kinematic
 

至於其他剛體參數的作用,可以擺個場景逐個調整參數看看效果,就基本明白了。

因爲我主要是想通過u3d的2D物理引擎獲得的碰撞信息,而不關注碰撞後的物理效果,所以其他參數都可以不管或設置爲0。

特別的,由於遊戲是一個top-down視角的遊戲,而u3d的2D物理引擎默認y軸是2D世界的上下方向,所以Gravity Scale要設置爲0。
 

對應的相關的函數有:

OnTriggerEnter2D、OnTriggerExit2D、OnTriggerStay2D(Collider發生碰撞時被調用)

OnCollisionEnter2D、 OnCollisionExit2D、OnCollisionStay2D(Rigidbody發生碰撞時被調用)

下面將解釋這些屬性和函數的作用。

碰撞檢測的推薦實現方式——Rigidbody的Collision

需要用u3d的2d物理引擎實現碰撞檢測時,需要這麼設置(假定檢測A和B之間的碰撞):

1、A和B都添加collider,不勾選Is Trigger

2、A或B至少一個添加rigidbody,不勾選IsKenematic

3、對A或B添加腳本,添加OnCollisionEnter2D、 OnCollisionExit2D或OnCollisionStay2D函數獲取碰撞信息。

以本文的實體型子彈爲例:

1、  對遊戲單位和子彈都添加collider

2、  對子彈添加rigidbody

3、  對子彈添加OnCollisionEnter2D方法,編寫造成傷害的邏輯代碼,並銷燬子彈對象。

關於Rigidbody的Is Kinematic的屬性:勾選後,2D物理引擎對這個剛體不起作用,只能代碼去實現物體的運動。同時,OnCollisionEnter2D也不會被觸發。

另外一種碰撞檢測的實現——Collider的Trigger

通過Collider的Is Trigger的,也能實現“碰撞檢測”。

Collider的Is Trigger:顧名思義,這個屬性說明是否觸發,勾選後,則會有“碰撞時”OnTriggerEnter2D、OnTriggerExit2D, OnTriggerStay2D函數。

例如,檢測A和B之間的碰撞:

1、  A和B都添加collider,A勾選Is Trigger,B不勾選

2、  A添加rigidbody

3、  對A腳本添加OnTriggerEnter2D

A和B的collider發生接觸時,則A的OnTriggerEnter2D被調用。如果B的腳本也有OnTriggerEnter2D,也會被調用,儘管B沒有勾選Is Trigger。

這種“碰撞檢測”,依靠Collider的trigger機制,在Collider層面就可以完成,其原理應該和鼠標點擊事件的觸發類似。但有以下問題:

1、  這個觸發機制的碰撞檢測頻率和Update一樣,而上文中推薦方式(利用OnCollisionEnter2D)是和FixedUpdate一樣,後者是專門是做剛體物理運算,其計算頻率更好,碰撞檢測更準確。如果使用OnTriggerEnter2D的方式,檢測到碰撞發生時可能兩個碰撞的物體已經相互嵌入很久了,如果其中一個物體運動速度過快,可能已經“穿”過去了

2、  OnCollisionEnter2D的參數提供的碰撞信息更豐富,而OnTriggerEnter2D只有一個碰撞對方collider的信息,得不到更精確的點。

3、  雖然碰撞是在Collider層面完成,感覺跟Rigidbody沒有什麼關係(1、2兩點的想象也側面印證了我這個想法),但A和B之間必須有一個是Rigidbody,不然碰撞事件觸發不了。Physics2D中IsTouching等函數也有這樣。

4、  設置Trigger後,所有的碰撞事件被Trigger攔截,OnCollisionEnter2D不會再被調用。

基於以上因素,這種碰撞檢測,不能稱之爲有效的“碰撞檢測”,在實際運用中要根據實際情況判斷是否合適。

作爲遊戲物體和物體的碰撞檢測,不推薦使用Collider的Trigger方式。

關於碰撞檢測的總結

1、如果想使用物理引擎實現碰撞,包括Collider的Trigger,rigidbody的Collision,Physics 2D的IsTouching等方法,除了碰撞雙方都有Collider,必須有1個有rigidbody。(此點讓我無力吐槽)。

2、使用Collider的Trigger(勾選Is Trigger),可以使用OnTriggerEnter2D、OnTriggerExit2D, OnTriggerStay2D監聽碰撞,但沒有碰撞物理效果,rigidbody的collision無法使用。Collider的Trigger不是在物理引擎層面上工作的,不管是碰撞檢測的更新頻率、是碰撞結果都不好,且它直接“阻止”了物理引擎的對物體的作用。

3、使用rigidbody的collision(不勾選Is Kinematic),使用OnCollisionEnter2D、 OnCollisionExit2D、OnCollisionStay2D監聽碰撞,物理引擎會影響剛體的運動,會有碰撞反彈的物理效果。最好在OnCollisionEnter2D只獲取狀態而不更新物體運動,因爲物理引擎這是也在控制它的運動。

綜上,u3d物理引擎的使用限制還是很多的,實現很多邏輯功能都有障礙。由於對於實體子彈的實現,子彈打擊單位後,子彈自我銷燬,和以上第3點正好滿足,可以使用u3d的collision,而非實體子彈顯然不能使用。

三、實體型子彈

如果認真閱讀上面的分析且理解了原理,應該對u3d的2D碰撞(3D類似)的套路應該很清楚,實現實體子彈打擊效果,僅僅是點點、配配的事。

現實方式爲:

1、  對遊戲單位和子彈都添加collider

2、  對子彈添加rigidbody

3、  對子彈添加OnCollisionEnter2D方法,編寫造成傷害的邏輯代碼,並銷燬子彈對象。

關鍵代碼:
  1. void Update ()
  2.     {
  3.         if (Common.pause)
  4.             return;
  5.  
  6.         m_Anim.OnUpdate (GetComponent<SpriteRenderer> ());
  7.  
  8.         float l = Time.deltaTime * m_Info.speed;
  9.         transform.position += m_Direction * l;
  10.         m_LeftDistance -= l;
  11.         if (m_LeftDistance < 0 || m_DestroySelf)
  12.         {
  13.             FlyerManager.Free(gameObject);
  14.         }
  15.  
  16.     }
  17.  
  18.  
  19.     public virtual void OnCollisionEnter2D (Collision2D coll)
  20.     {
  21.         if (m_DestroySelf)
  22.             return;
  23.  
  24.         TargetPick pick = TargetPick.From (coll);
  25.         m_Info.AttackOn (pick,m_Direction, m_myUnit,hitEffectType);
  26.         m_DestroySelf = true;
  27.  
  28.     }
複製代碼


其中有很多類和函數已經封裝,例如:

FlyerManager.Free(),內部實現了子彈的回收,便於再利用。

再如TargetPick和AttackOn,實現了拾取最合適的遊戲單位和計算打擊傷害的功能。

 

 

 

 

 

中篇

 

文說明在彈道實現中,關於u3d的物理引擎的一些相關要點,並跟給了實體性子彈的關鍵實現代碼。

本篇中,將繼續說明射線型彈道的實現的。

四、射線型子彈

本章先講子彈的碰撞邏輯實現,由於射線型子彈用u3d的sprite是繪製不出來的,所有需要特殊的技巧,繪製方法在下一章中說明。

使用Physic2D庫,進行非自動碰撞檢測

區別於實體子彈,在遊戲中,我需要實現類似於激光射線、範圍傷害(AOE)的攻擊類型。

這種情況下,就不能依靠rigidbody來實現碰撞的檢測,前文中又說了,u3d的物理引擎不支持兩個collider的碰撞檢測。

所以,沒有辦法“自動”做碰撞檢測(這裏所謂自動,就是去實現一個函數,然後等着u3d在碰撞發生時自動調用)

我依然使用u3d的2D物理庫,在每一幀(每個Update)中,對目標collider進行檢測。

例如,AOE傷害:

RaycastHit2D[] hits = Physics2D.CircleCastAll(transform.position, m_Info.aoeRadius, Vector2.zero,
     LayerManager.GetLayer(m_Faction).oppUnitMask );

使用Physics2D的CircleCastAll、RaycastAll、BoxCastAll,對指定layer中的所有collider,做圓形、射線、長方形的碰撞檢測。

即,這裏不再用collider和collider,而是判斷指定的圓形、射線、長方形,和哪些collider碰撞(Cast),包括相交和包含。

這些函數有一個All和非All的版本,返回所有檢測到collider或者最近的collider。

返回值RaycastHit包含collider、point(碰撞點)、normal(碰撞法線)等,具體請參考u3d api,這裏不再累述。

和上文說的Collider的碰撞檢測一樣,碰撞的代碼實現放在Update裏,可能出現“嵌入過多”或“穿過”的情況。

但由於,需求本身是針對非實體子彈的,其面積、範圍比子彈大很多,所有沒有太嚴重的影響。

(如果放在FixedUpdate會精確很多,但消耗太大)。

幾種彈道類型的攻擊邏輯實現

穿透激光

穿透激光,可以對射線上的單位造成傷害。

其攻擊的實現代碼:

複製代碼

IEnumerator _TakeAttack ()
{
        yield return new WaitForSeconds (RAY_TAKE_ATTACK); 

        Vector3 v = Utils.Up (transform);
        Vector3 worldCenter = transform.position + v * m_Info.attackDistance / 2;
        Vector2 size = new Vector2 (width, m_Info.attackDistance);

        RaycastHit2D[] hits = 
            Physics2D.BoxCastAll (worldCenter, size, 
                Utils.DirToAngle(v), new Vector2 (0, 0),
                Mathf.Infinity,LayerManager.GetLayer(m_Faction).oppUnitMask);  

      m_Filter.Clear();
      for (int s = 0; s < hits.Length; ++s)
      {
        TargetPick pick = TargetPick.From (ref hits [s]);
        pick.ToShield();
        if( pick )
          if( m_Filter.Test(ref pick) )
        m_Info.AttackOn (pick, v, m_myUnit,hitEffectType,null);
      }

}

複製代碼

注意,攻擊使用StartCoroutine做一個延時,這是因爲,爲了動畫效果更真實,在做激光的繪製時,有一個很快的激光射線變長的過程,所以攻擊的實際效果要和增長時間配合。

代碼裏面有很多遊戲邏輯相關的東西,不用太關注,關鍵是要獲取到一個box的形狀描述,需要知道Up,Center,Size。

這裏把激光看成一個很長的box,長度是激光的最大攻擊距離。

最後,由於有些單位有多個Collider,所有需要過濾一下(即m_Filter),原理很簡單:

1、找hit或collider對應的單位(TargetPick.From)

2、檢查單位在過濾器中是否已經存在。存在就不在處理,不存在就繼續,並添加到過濾器中(m_Filter.test)

3、進行傷害的計算邏輯(m_Info.Attack)

定向激光(持續)

定向激光對指定的單位進行攻擊。

定向激光不需要通過碰撞檢測去“探測”激光與哪些collider相交的,因爲定向激光是對已指定的單位進行持續攻擊。

需要解決的問題,激光與單位的碰撞點到底在哪。根據這個碰撞點,繪製激光的形狀。

其攻擊的實現代碼:

複製代碼

    public bool _TakeAttack (out Vector3 point)
    {
        point = Vector3.zero;
        Vector3 v = Utils.Up (transform);
        // The colliders in the array are sorted in order of distance from the origin point
        RaycastHit2D[] hits = Physics2D.RaycastAll (transform.position, v, m_Info.attackDistance,
            LayerManager.GetLayer(m_Faction).oppUnitMask);

        // 是否有target的hit
        TargetPick pick = TargetPick.none;
        for (int s = 0; s < hits.Length; ++s)
        {
            pick = TargetPick.IsTarget (target, ref hits [s]);
            if (pick)
                break;
        }

        if (pick)
        {
            point = pick.point;
            m_Info.AttackOn (pick, v, m_myUnit, Const.NONE_EFFECT, this);
            if (pick.unit && pick.unit.curHp <= 0)
                return false;

            if (pick.part && pick.part.unit.curHp <= 0)
                return false;

            return true;
        }
        return false;
    }

複製代碼

攻擊函數返回是否攻擊到對象單位(target),並返回攻擊點。和穿透激光的區別,使用RayCastAll目的只是找到要攻擊的對象是否在其中,並確定碰撞點。

定向激光(單次攻擊)

類似於圖中的閃電效果。原理持續的定向激光基本一致,區別是單次攻擊時播放閃電(或其他效果)動畫。

同持續定向激光一樣,在攻擊過程中不停的用RayCast判斷閃電是否“打”到了目標上,

如果沒有需要立即中斷攻擊和攻擊動畫,否則攻擊單位在出現突然轉身的是否,閃電會隨着攻擊攻擊單位移動,出現bug。

不再給出代碼。

範圍攻擊

典型的是噴火器或者爆炸,在一定範圍內所有單位收到傷害。

一時找不到圖。後面補。

複製代碼

    bool _TakeAttack ()
    {
        Vector3 dir = Utils.Up (transform);

        m_Filter.Clear ();
        RaycastHit2D[] hits = Physics2D.CircleCastAll (transform.position, m_Info.attackDistance, Vector2.zero,
                                  Mathf.Infinity, LayerManager.GetLayer(m_Faction).oppUnitMask);
        
        bool f = false;
        // 是否有target的hit
        for (int s = 0; s < hits.Length; ++s)
        {
            Vector3 v = Utils.V2toV3 (hits [s].point) - transform.position;
            if (Mathf.Abs (Vector3.Angle (dir, v)) < m_Info.attackArc / 2)
            {
                f = true;
                TargetPick pick = TargetPick.From (ref hits [s]);
                // AOE CircleCastAll 可能選不到shield
                pick.ToShield ();
                if (pick)
                if (m_Filter.Test (ref pick))
                    m_Info.AttackOn (pick, Vector3.zero, null, Const.NONE_EFFECT ,this);
            }

        }

        return f;
    }

複製代碼

原理很簡單,先找到圓形範圍內的所有collider,再判斷是否在噴火器的扇形角度內。

 

攻擊、彈道這塊內容遊戲邏輯是遊戲的一個重點,其實很難寫一輛篇文章說清,其實我很想把從最下層的u3d的物理、繪製到最上層代碼邏輯架構 全部說清楚,

但發現寫一篇文博耗費的時間比我想象長,長到我寫代碼實現的一個功能的時間還沒寫一篇文章長。

所以,我考慮了下,不能指望所有的東西全部說清楚,最要還是講原理,不同於網上大部分的教程講的是最基礎的內容,甚至是解釋api,

而是建立讀者有一定基礎上,講原理、講結構,講自認爲的難點,有助於自己梳理遊戲代碼,也是對關鍵的技術點做一個備忘。

下篇預告:彈道的圖形效果實現、攻擊邏輯的結構和要點(比如本文中展示代碼中定義的類的意義)

 

 

下篇

 

EffectRTGenerator
EffectRTGenerator是用來做渲染到紋理的輔助類。

通過Inst方法來訪問EffectRTGenerator,Inst內部會在首次調用時創建一個實例,不需要在Scene預先放一個RTCamera礙眼,讓開發人員完全不用去理會rt生成的細節
CreateOutterline方法會創建一個輪廓的rendertexture。CreateOutterline的邏輯框架是:
1、_Begin(),生成rt,移動target
2、RenderWithShader
3、_End(),還原target
4、根據需求處理紋理
類的定位:EffectRTGenerator放在場景中“非常遠”的位置,專門用來做渲染到紋理,其他對象有rt的相關需求時,只需要調用相關的方法即可生成rt。這個類的定位不僅僅是做輪廓rt的生成,而是擴展並支持各種類似rt的生成。例如,可以加入生成“發光”、“煙霧“等貼圖,只要按CreateOutterline的模式進行改造就行了。
**注意:**CreateOutterline不能在target的Awake和Start裏調用。
代碼:

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class EffectRTGenerator : MonoBehaviour
{
    //public int RTGenLayer = 31;

    static EffectRTGenerator m_Instance;

    public static EffectRTGenerator Inst()
    {
        if (m_Instance == null)
        {
            GameObject rtCamera = new GameObject("RTCamera");
            rtCamera.transform.position = new Vector3(9999999, 9999999, -10);
            Camera c = rtCamera.AddComponent<Camera>();
            c.clearFlags = CameraClearFlags.Color;
            c.backgroundColor = Color.black;
            c.orthographic = true;
            c.orthographicSize = 5;
            c.nearClipPlane = 0.3f;
            c.farClipPlane = 1000f;
            c.enabled = false;
            m_Instance = rtCamera.AddComponent<EffectRTGenerator>();    
        }
        return m_Instance;
    }

    Material m_SolidMat = null;
    Material m_BlurMaterial = null;
    Material m_CutoffMaterial = null;
    Material m_CompositeMaterial = null;

    Vector3 m_TargetOldPosition;
    Vector3 m_TargetOldEulerAngles;
    Vector3 m_TargetOldScale;
    int m_TargetOldLayer;

    public void Awake()
    {
        m_Instance = this;

        m_SolidMat = new Material(Shader.Find("Outterline/Solid"));
        m_SolidMat.hideFlags = HideFlags.HideAndDontSave;
        //m_SolidMat.shader.hideFlags = HideFlags.None;

        m_BlurMaterial = new Material(Shader.Find("Outterline/ShapeBlur"));
        m_BlurMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_BlurMaterial.shader.hideFlags = HideFlags.None;

        m_CutoffMaterial = new Material(Shader.Find("Outterline/CutoffBody"));
        m_CutoffMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_CutoffMaterial.shader.hideFlags = HideFlags.None;

        m_CompositeMaterial = new Material(Shader.Find("Outterline/Composer"));
        m_CompositeMaterial.hideFlags = HideFlags.HideAndDontSave;
        //m_CompositeMaterial.shader.hideFlags = HideFlags.None;

        // 設置到一個極其遠的地方,以便於只渲染目標
        transform.position = new Vector3(99999, 99999, -10);
        //GetComponent<Camera>().cullingMask = 1 >> RTGenLayer;

    }

    void OnDestroy()
    {
        DestroyImmediate(m_SolidMat);
        DestroyImmediate(m_BlurMaterial);
        DestroyImmediate(m_CutoffMaterial);
        DestroyImmediate(m_CompositeMaterial);

    }

    public RenderTexture _Begin(GameObject target, float x, float y)
    {
        // 根據輸入的x和y,創建一個rt
        RenderTexture rt = new RenderTexture((int)x, (int)y,0);

        rt.useMipMap = true;
        rt.filterMode = FilterMode.Bilinear;
        rt.depth = 0;
        rt.generateMips = true;
        rt.Create();
        GetComponent<Camera>().targetTexture = rt;

        // 調整cemara的範圍以適應rt
        float cameraSize = y / 2;
        GetComponent<Camera>().orthographicSize = cameraSize;
        //float r = (float)Screen.width / Screen.height;
        //float r_x =  x / ( y * 1);
        //GetComponent<Camera>().orthographicSize = cameraSize;
        //GetComponent<Camera>().rect = new Rect(0, 0, r_x, 1);

        // 保持舊的位置
        m_TargetOldPosition = target.transform.position;
        m_TargetOldEulerAngles = target.transform.eulerAngles;
        m_TargetOldScale = target.transform.localScale;
        //m_TargetOldLayer = target.layer;

        // 移動目標到攝像機處,保證只有目標在攝像機的範圍內
        // 由於此用一個極遠的位置,此處只有目標,所有可以不用設置layer
        Vector3 pos = transform.position;
        pos.z = 0;
        target.transform.localScale = Vector3.one;
        target.transform.position = pos;
        target.transform.eulerAngles = Vector3.zero;
        //target.layer = RTGenLayer;

        return rt;
    }

    void _End(GameObject target)
    {
        // 還原
        target.transform.position = m_TargetOldPosition;
        target.transform.eulerAngles = m_TargetOldEulerAngles;
        target.transform.localScale = m_TargetOldScale;
        //target.layer = m_TargetOldLayer;
    }

    public RenderTexture CreateOutterline(GameObject target, float x, float y)
    {
        int iterations = 5;
        float spread = 0.5f;

        RenderTexture rt = _Begin(target, x, y);

        GetComponent<Camera>().RenderWithShader(m_SolidMat.shader, "");

        _End(target);

        iterations = Mathf.Clamp(iterations, 0, 15);
        spread = Mathf.Clamp(spread, 0.5f, 6.0f);

        RenderTexture buffer = RenderTexture.GetTemporary(rt.width, rt.height, 0);
        RenderTexture buffer2 = RenderTexture.GetTemporary(rt.width, rt.height, 0);
        //buffer2.filterMode = FilterMode.Bilinear;
        //buffer.filterMode = FilterMode.Bilinear;
        //rt.filterMode = FilterMode.Bilinear;
        Graphics.Blit(rt, buffer);

        bool oddEven = true;
        for (int i = 0; i < iterations; i++)
        {
            if (oddEven)
                _FourTapCone(buffer, buffer2, i, spread);
            else
                _FourTapCone(buffer2, buffer, i, spread);
            oddEven = !oddEven;
        }
        if (oddEven)
        {
            Graphics.Blit(rt, buffer, m_CutoffMaterial);
            Graphics.Blit(buffer, rt, m_CompositeMaterial);
        }
        else
        {
            Graphics.Blit(rt, buffer2, m_CutoffMaterial);
            Graphics.Blit(buffer2, rt, m_CompositeMaterial);
        }       

        RenderTexture.ReleaseTemporary(buffer);
        RenderTexture.ReleaseTemporary(buffer2);
        return rt;
    }

    void _FourTapCone(RenderTexture source, RenderTexture dest, int iteration, float spread)
    {
        float off = 0.5f + iteration * spread;
        Graphics.BlitMultiTap(source, dest, m_BlurMaterial,
            new Vector2(off, off),
            new Vector2(-off, off),
            new Vector2(off, -off),
            new Vector2(-off, -off)
        );
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
OutterlineRenderer
OutterlineRenderer是目標GameObject的子GameObject,用來繪製輪廓。

提供CreateOutlineRenderer靜態方法,target調用該靜態方法來創建一個OutterlineRenderer子節點。
FindOrCreateOutlineRenderer靜態方法是對CreateOutlineRenderer的封裝,對於已存在OutterlineRenderer則不再創建。
通過color屬性,設置輪廓顏色
代碼:

using UnityEngine;
using System.Collections;

public class OutterlineRenderer : MonoBehaviour
{
    Material m_Material;
    public RenderTexture m_RT;

    public static OutterlineRenderer FindOrCreateOutlineRenderer(Unit target)
    {
        Transform t =  target.transform.Find("OutterlineRenderer");
        if (t == null)
            return CreateOutlineRenderer(target);
        else
            return t.GetComponent<OutterlineRenderer>();
    }

    public static OutterlineRenderer CreateOutlineRenderer(Unit target)
    {
        float x = target.bound.size.x * 2;
        float y = target.bound.size.y * 2;
        RenderTexture rt = EffectRTGenerator.Inst().CreateOutterline(target.gameObject, x, y);

        string path = "Misc/OutterlineRenderer";
        GameObject type = Resources.Load<GameObject>(path);
        if (type == null)
        {
            rt.Release();
            return null;
        }
        GameObject go = GameObject.Instantiate(type);
        go.name = type.name;

        go.GetComponent<OutterlineRenderer>().Init(rt, x, y, Color.white);

        go.transform.parent = target.transform;
        go.transform.localPosition = new Vector3(0, 0, 1f);
        go.transform.localScale = Vector3.one;
        go.transform.localEulerAngles = Vector3.zero;

        return go.GetComponent<OutterlineRenderer>();
    }

    public Color color
    {
        get{
            return gameObject.GetComponent<MeshRenderer>().material.color;
        }
        set{
            gameObject.GetComponent<MeshRenderer>().material.color = value;  
        }

    }

    void Init(RenderTexture rt, float x, float y ,Color color)
    {
        Mesh mesh; 

        MeshRenderer renderer = gameObject.GetComponent<MeshRenderer>(); 
        mesh = GetComponent<MeshFilter>().mesh;

        /* 6 7
         * 4 5
         * 2 3
         * 0 1
         */

        int sectionCount = 1;
        int[] triangles = new int[ sectionCount * 6];  
        Vector3[] vertices = new Vector3[ sectionCount * 4];
        Color[] colors = new Color[ sectionCount * 4];
        Vector2[] uv = new Vector2[ sectionCount * 4];
        for (int i = 0; i < sectionCount; i++)
        {  
            vertices[4 * i] = new Vector2(-x / 2, -y / 2);
            vertices[4 * i + 1] = new Vector2(x / 2, -y / 2);
            vertices[4 * i + 2] = new Vector2(-x / 2, y / 2);
            vertices[4 * i + 3] = new Vector2(x / 2, y / 2);
            colors[4 * i] = Color.white;
            colors[4 * i + 1] = Color.white;
            colors[4 * i + 2] = Color.white;
            colors[4 * i + 3] = Color.white;
            uv[4 * i] = new Vector2(0, 0);
            uv[4 * i + 1] = new Vector2(1, 0);
            uv[4 * i + 2] = new Vector2(0, 1);
            uv[4 * i + 3] = new Vector2(1, 1);
        }  

        for (int i = 0; i < sectionCount; i++)
        {  
            triangles[6 * i] = (i * 4) + 0;  
            triangles[6 * i + 1] = (i * 4) + 3;  
            triangles[6 * i + 2] = (i * 4) + 1; 
            triangles[6 * i + 3] = (i * 4) + 0;  
            triangles[6 * i + 4] = (i * 4) + 2;  
            triangles[6 * i + 5] = (i * 4) + 3; 
        }

        mesh.vertices = vertices;  
        mesh.triangles = triangles;
        mesh.colors = colors;
        mesh.uv = uv;

        m_RT = rt;
        m_Material = new Material(Shader.Find("Outterline/Render"));
        m_Material.hideFlags = HideFlags.HideAndDontSave;
        renderer.material = m_Material;
        renderer.material.color = color;
        renderer.material.mainTexture = rt;

    }

    void OnDestroy()
    {
        DestroyImmediate(m_Material);
        DestroyImmediate(m_Material);
    }

    public void Hide()
    {
        gameObject.SetActive(false);
    }

    public void Show()
    {
        gameObject.SetActive(true);
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章