Unity3D結合輪廓顯示,實現完整的框選目標

Unity3D結合輪廓顯示,實現完整的框選目標demo下載


在unity裏實現,其實很簡單,因爲有兩個前提:
1.畫的方框始終是在屏幕空間進行的,而屏幕空間其實就是攝像機的視錐空間的投射了,不需要另外計算視錐。
2.unity攝像機內建的功能,可以方便的把屏幕座標和世界座標互換。

這樣最簡單的畫框選物體就簡化成了:
1.用GL在屏幕空間動態畫框。
2.把備選對象的transform.position用camera.WorldToScreenPoiont變換成屏幕座標。
3.判斷這些position的點是否落在畫的方框之內,如果是就把對象切換到畫邊框的層,呈現選中效果。

一.屏幕動態畫框
這個過程相對簡單 就是在鼠標按下的時候記下鼠標位置,然後在鼠標移動時在當前鼠標位置和按下的位置之間畫一個方形就行了。
  1. using UnityEngine;
  2. using System.Collections;

  3. public class DrawRectangle : MonoBehaviour {
  4.     public Color rectColor = Color.green;
  5.     
  6.     private Vector3 start = Vector3.zero;//記下鼠標按下位置
  7.     private Material rectMat = null;//畫線的材質 不設定系統會用當前材質畫線 結果不可控
  8.     private bool drawRectangle = false;//是否開始畫線標誌
  9.     
  10.     // Use this for initialization
  11.     void Start () {
  12.         rectMat =  new Material( "Shader \"Lines/Colored Blended\" {" +
  13.             "SubShader { Pass { " +
  14.             "    Blend SrcAlpha OneMinusSrcAlpha " +
  15.             "    ZWrite Off Cull Off Fog { Mode Off } " +
  16.             "    BindChannels {" +
  17.             "      Bind \"vertex\", vertex Bind \"color\", color }" +
  18.             "} } }" );//生成畫線的材質
  19.         rectMat.hideFlags = HideFlags.HideAndDontSave;
  20.         rectMat.shader.hideFlags = HideFlags.HideAndDontSave;    
  21.     }


  22. void Update () {
  23.         if(Input.GetMouseButtonDown(0)){
  24.             drawRectangle = true;//如果鼠標左鍵按下 設置開始畫線標誌
  25.             start = Input.mousePosition;//記錄按下位置
  26.         }else if (Input.GetMouseButtonUp(0)){
  27.             drawRectangle = false;//如果鼠標左鍵放開 結束畫線
  28.         }
  29.     }
  30.     
  31.     void OnPostRender() {//畫線這種操作推薦在OnPostRender()裏進行 而不是直接放在Update,所以需要標誌來開啓
  32.         if(drawRectangle){
  33.             Vector3 end = Input.mousePosition;//鼠標當前位置
  34.             GL.PushMatrix();//保存攝像機變換矩陣
  35.         
  36.             if (! rectMat)
  37.                 return;
  38.         
  39.                rectMat.SetPass( 0 );
  40.             GL.LoadPixelMatrix();//設置用屏幕座標繪圖
  41.             GL.Begin(GL.QUADS);
  42.             GL.Color( new Color(rectColor.r,rectColor.g,rectColor.b,0.1f) );//設置顏色和透明度,方框內部透明
  43.             GL.Vertex3( start.x,start.y,0);
  44.             GL.Vertex3( end.x,start.y,0);
  45.                GL.Vertex3( end.x,end.y,0 );
  46.             GL.Vertex3( start.x,end.y,0 );
  47.             GL.End();
  48.             GL.Begin(GL.LINES);
  49.             GL.Color(rectColor);//設置方框的邊框顏色 邊框不透明
  50.             GL.Vertex3( start.x,start.y,0);
  51.             GL.Vertex3( end.x,start.y,0);
  52.             GL.Vertex3( end.x,start.y,0);
  53.             GL.Vertex3( end.x,end.y,0 );
  54.             GL.Vertex3( end.x,end.y,0 );
  55.             GL.Vertex3( start.x,end.y,0 );
  56.             GL.Vertex3( start.x,end.y,0 );
  57.             GL.Vertex3(start.x,start.y,0);
  58.             GL.End();
  59.             GL.PopMatrix();//恢復攝像機投影矩陣
  60.         }
  61.     }
  62. }
效果如圖:

注意GL繪圖都是每幀進行的,所以不需要清除,直接不繪製方框就消失了。 

二.判斷物體是否選中
有了方框,要判斷物體是否在方框內,按照經典的數學算法可以根據直線方程和點的座標計算判斷點在線的左邊 右邊 還是線上

空間平面方程可表示爲:
Ax+By+Cz=0

對於點(x1, y1, z1),有
若 Ax1+By1+Cz1 = 0,則點在平面上;
若 Ax1+By1+Cz1 < 0,則點在平面的一側;
若 Ax1+By1+Cz1 > 0,則點在平面的另一側;

但是在這裏,因爲都是水平、垂直的線,我們並不需要考慮畫斜線、曲線框選物體........,所以只需要簡單的比大小就行了,土了那麼一點但其實效率更高,畢竟作遊戲不是算法比賽,怎麼簡單高效怎麼用。
如果:物體的屏幕position.x >方框左下角.x && 物體的屏幕position.y >方框左下角.y && 物體的屏幕position.x <方框右上角.x && 物體的屏幕position.y <方框右上角.y, 那麼這個物體就是在框選範圍裏了。至於z的判定,直接選擇攝像機的near和far距離就行了,小於near大於far都無視。因爲屏幕空間是左下角爲0,0,判斷是否在方框內要注意這個前提。
有Unity內置的世界座標->屏幕座標轉換,其實根本不需要什麼複雜的裁剪算法,也不需要搞一大堆參考物體,往外發射一堆射線什麼的,一次座標轉換加6個判斷條件的一條if語句,就完成全部選擇邏輯了,簡單高效。
  1. void checkSelection(Vector3 start,Vector3 end){
  2.         Vector3 p1 = Vector3.zero;
  3.         Vector3 p2 = Vector3.zero;
  4.         if(start.x>end.x){//這些判斷是用來確保p1的xy座標小於p2的xy座標,因爲畫的框不見得就是左下到右上這個方向的
  5.             p1.x = end.x;
  6.             p2.x = start.x;
  7.         }
  8.         else{
  9.             p1.x = start.x;
  10.             p2.x = end.x;
  11.         }
  12.         if(start.y>end.y){
  13.             p1.y = end.y;
  14.             p2.y = start.y;
  15.         }
  16.         else{
  17.             p1.y = start.y;
  18.             p2.y = end.y;
  19.         }            
  20.         foreach(GameObject obj in characters){//把可選擇的對象保存在characters數組裏
  21.             Vector3 location = camera.WorldToScreenPoint(obj.transform.position);//把對象的position轉換成屏幕座標
  22.             if(location.x<p1.x || location.x>p2.x ||location.y<p1.y ||location.y>p2.y    
  23.                 ||location.z<camera.nearClipPlane || location.z > camera.farClipPlane)//z方向就用攝像機的設定值,看不見的也不需要選擇了
  24.             {
  25. disselecting(obj);//上面的條件是篩選 不在選擇範圍內的對象,然後進行取消選擇操作,比如把物體放到default層,就不顯示輪廓線了
  26.             }
  27.             else
  28.             {
  29. selecting(obj);//否則就進行選中操作,比如把物體放到畫輪廓線的層去
  30.             }
  31.         }
  32.     }
效果如圖:


注意這種判定是以物體的position點爲基準,如果物體中心不在幾何中心可能會有奇怪的效果,但對作遊戲來說模型中心在幾何中心是基本要求,所以應該不是問題。當然也可以按照部分頂點相交來選中,但是那樣程序就會複雜很多,要先解構物體的mesh頂點,然後把頂點變換到屏幕座標,然後判定是否在方框範圍內。實際上對大多數遊戲來說劃過中心點算作選中條件完全可以滿足,足夠有說服力了。

附上一個自己作的小Demo,綜合了之前的顯示邊緣光、輪廓效果,按鈕遮罩效果,加上框選物體的效果。 
鼠標左鍵按下畫框 右鍵雙擊地面移動選中的羣體 按住滾輪移動鼠標拖動地圖,按住右鍵旋轉鼠標轉動視角,滾動滾輪縮放視角。
語音借用的是那啥3的人類語音,你懂的。

選擇:


雙擊右鍵移動目標,目標用一組粒子顯示,到達目標或者目標改變時自動消失


可以同時移動幾組,有點像1代的C&C......


點按鈕生成新的人物,選中按delete刪除人物


鼠標劃過顯示邊緣光,單擊目標或者畫框選中目標顯示輪廓線


選中的目標會始終對着鏡頭,未選中的目標會隨機行動



Unity的便利性顯現無疑,基本無需複雜的幾何、線代知識,也沒有繁瑣的外圍代碼,用幾句判斷語句搞定框選。加上所見即所得的集成開發環境,寫到這樣程度的Demo實際大概也就花了幾個小時的人時,真正框選部分不超過1小時,有點像遊戲開發的卡拉OK,優勢確實很明顯。

Have fun.

原文鏈接:http://blog.csdn.net/xv_ly15/article/details/8563068

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