1. 程式人生 > >unity實現滑鼠框選和單選物體

unity實現滑鼠框選和單選物體

今天分享一下這兩天寫的一些程式碼,關於如何在unity中實現滑鼠框選物體,達到類似於在windows下選擇檔案的目標。

第一次接觸unity的時候做了一個簡單的遊戲,當時就是卡在了滑鼠選擇這一步上,後來由於時間關係也沒有去深入研究,這幾天有時間又拿出來看了一下,發現其實很簡單,只要判斷好滑鼠點選的座標和位置物體的位置關係即可。當然,經過這兩天的琢磨發現一開始其實是路線錯了,原本是將滑鼠在螢幕上的位置轉換到世界座標進行判斷的,後來發現這樣不可行,原因如下:

將滑鼠在螢幕空間的座標轉換到世界座標涉及座標深度的問題,也就是z值,轉換後的座標與z值有很大關係,如圖:


黃色線的端點和紅色線的端點在螢幕空間的座標都是相等的(0,screenheight/2),但是由於不同的深度值,轉換到世界座標後,兩者的座標相差了十萬八千里,因此如果想要利用誤差如此之大的座標與物體進行比較,當然會出現錯誤。因此唯一穩妥的方法是將物體的座標轉換到螢幕空間上,然後只使用x和y進行較:

gameObjectWorldPosition[i] = gameObjects[i].transform.position;
            gameObjectScreenPosition[i] = Camera.main.WorldToScreenPoint(gameObjectWorldPosition[i]);

因此整個框選的流程如下:

滑鼠點下:記錄第一個滑鼠點;

滑鼠擡起:記錄第二個滑鼠點;

將每一個物體的世界座標轉換為螢幕座標;

判斷每個世界座標與滑鼠點的位置關係;

進行業務運算。

完整程式碼如下:

void Update () {
        mousePosition = Input.mousePosition;//記錄滑鼠位置
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            mouseDown = true;//滑鼠落下標記
            Debug.Log("mouseDown");
            mouseStartPosition = Input.mousePosition;//記錄第一個點的位置
            for (int i = 0; i < gameObjects.Length; i++)
            {//將之前選擇的物體全部釋放
                Debug.Log("return");
                gameObjects[i].GetComponent<MeshRenderer>().material.shader = customShader;
                gameObjects[i].GetComponent<single>().isSelected = false;

            }
        }
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {//滑鼠擡起
            Debug.Log("mouseUp");
            mouseEndPosition = Input.mousePosition;
            mouseUp = true;
            mouseDown = false;

        }
        for(int i = 0; i < gameObjects.Length; i++)
        {//將物體世界座標轉換為螢幕座標,每幀都在計算
            gameObjectWorldPosition[i] = gameObjects[i].transform.position;
            gameObjectScreenPosition[i] = Camera.main.WorldToScreenPoint(gameObjectWorldPosition[i]);
        }
        if ( mouseUp)
        {//如果滑鼠擡起,進行判斷
            Debug.Log("judeg");

            mouseUp = false;
            judge();
        }
        
	}
    private void judge(){
        if (mouseStartPosition != mouseEndPosition)
        {
            for (int i = 0; i < gameObjects.Length; i++)
            {
                Vector2 po = gameObjectScreenPosition[i];
                Vector2 ms = mouseStartPosition;
                Vector2 me = mouseEndPosition;
                if ((po.x > ms.x && po.x < me.x && po.y > ms.y && po.y < me.y) ||
                    (po.x > ms.x && po.x < me.x && po.y < ms.y && po.y > me.y) ||
                    (po.x < ms.x && po.x > me.x && po.y > ms.y && po.y < me.y) ||
                    (po.x < ms.x && po.x > me.x && po.y < ms.y && po.y > me.y))
                {//判斷每個物體是否在滑鼠框選的範圍內
                    gameObjects[i].GetComponent<MeshRenderer>().material.shader = selectShader;
                    gameObjects[i].GetComponent<single>().isSelected = true;
                }
            }
        }
    }

但是,如果不想框選而是想點選一個物體進行單選呢,也很簡單,只需要利用射線即可,將滑鼠的螢幕座標轉換到世界座標內,深度值設為相機的最大深度,然後從相機發出射線到該點,中途如果有碰撞,將碰撞的物體設為選擇即可,程式碼如下:

Vector3 mouseWorldPosition = Camera.main.ScreenToWorldPoint(new Vector3(mouseStartPosition.x, mouseStartPosition.y, Camera.main.farClipPlane));
            Ray ray = new Ray(Camera.main.transform.position,mouseWorldPosition-Camera.main.transform.position);
            RaycastHit hit;
            if(Physics.Raycast(ray,out hit))
            {
                if (hit.transform.tag == "player")
                {
                    hit.transform.gameObject.GetComponent<MeshRenderer>().material.shader = selectShader;
                    hit.transform.gameObject.GetComponent<single>().isSelected = true;
                }
            }

另外,框選的時候如果沒有一個框顯示出來的時候,那對於框選的範圍沒有什麼直觀的概念,因此還需要在螢幕上畫出框。此處利用unity自帶的GL庫進行繪製,也很簡單,根據兩個點劃出四條線即可。注意,一定要在OnGUI函式中呼叫,因為在螢幕上繪製相當於是GUI,一開始我是在update中呼叫的,畫了半天都畫不出,呵呵。

    void OnGUI()
    {
        if (mouseDown)
        {
            Draw();
        }
    }
    void Draw()
    {
        drawMat.SetPass(0);
        GL.PushMatrix();//儲存攝像機變換矩陣  
        GL.LoadPixelMatrix();//設定用螢幕座標繪圖  
                             //透明框  
        Color clr = Color.black;

        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mousePosition.x, mouseStartPosition.y, 0);
        GL.End();

        //下  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mousePosition.y, 0);
        GL.Vertex3(mousePosition.x, mousePosition.y, 0);
        GL.End();

        //左  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mouseStartPosition.x, mousePosition.y, 0);
        GL.End();

        //右  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mousePosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mousePosition.x, mousePosition.y, 0);
        GL.End();

        GL.PopMatrix();//還原  
    }

完整的框選程式碼如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class selectTest : MonoBehaviour {
    public GameObject[] gameObjects;
    public Vector3[] gameObjectWorldPosition;
    public Vector2[] gameObjectScreenPosition;
    public bool[] gameObjectSelected;
    public Material[] gameObjectMaterial;

    public Shader selectShader;
    public Shader customShader;

    public Vector2 mouseStartPosition = new Vector2(0, 0);
    public Vector2 mouseEndPosition = new Vector2(0, 0);
    public Vector2 mousePosition = new Vector2(0, 0);

    public bool mouseUp = false;
    public bool mouseDrag = false;
    public bool mouseDown = false;

    public Material drawMat;

    void Start () {
        gameObjectWorldPosition = new Vector3[gameObjects.Length];
        gameObjectScreenPosition = new Vector2[gameObjects.Length];
        gameObjectSelected = new bool[gameObjects.Length];
        gameObjectMaterial = new Material[gameObjects.Length];
        for(int i = 0; i < gameObjects.Length; i++)
        {
            gameObjectMaterial[i] = gameObjects[i].GetComponent<MeshRenderer>().material;
        }
        drawMat.hideFlags = HideFlags.HideAndDontSave;
        drawMat.shader.hideFlags = HideFlags.HideAndDontSave;
    }
	
	// Update is called once per frame
	void Update () {
        mousePosition = Input.mousePosition;
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            mouseDown = true;
            Debug.Log("mouseDown");
            mouseStartPosition = Input.mousePosition;
            for (int i = 0; i < gameObjects.Length; i++)
            {
                Debug.Log("return");
                gameObjects[i].GetComponent<MeshRenderer>().material.shader = customShader;
                gameObjects[i].GetComponent<single>().isSelected = false;

            }
        }
        if (Input.GetKeyUp(KeyCode.Mouse0))
        {
            Debug.Log("mouseUp");
            mouseEndPosition = Input.mousePosition;
            mouseUp = true;
            mouseDown = false;

        }
        for(int i = 0; i < gameObjects.Length; i++)
        {
            gameObjectWorldPosition[i] = gameObjects[i].transform.position;
            gameObjectScreenPosition[i] = Camera.main.WorldToScreenPoint(gameObjectWorldPosition[i]);
        }
        if ( mouseUp)
        {
            Debug.Log("judeg");

            mouseUp = false;
            judge();
        }
        
	}
    private void judge(){
        if (mouseStartPosition != mouseEndPosition)
        {
            for (int i = 0; i < gameObjects.Length; i++)
            {
                Vector2 po = gameObjectScreenPosition[i];
                Vector2 ms = mouseStartPosition;
                Vector2 me = mouseEndPosition;
                if ((po.x > ms.x && po.x < me.x && po.y > ms.y && po.y < me.y) ||
                    (po.x > ms.x && po.x < me.x && po.y < ms.y && po.y > me.y) ||
                    (po.x < ms.x && po.x > me.x && po.y > ms.y && po.y < me.y) ||
                    (po.x < ms.x && po.x > me.x && po.y < ms.y && po.y > me.y))
                {
                    gameObjects[i].GetComponent<MeshRenderer>().material.shader = selectShader;
                    gameObjects[i].GetComponent<single>().isSelected = true;
                }
            }
        }
        else
        {
            Vector3 mouseWorldPosition = Camera.main.ScreenToWorldPoint(new Vector3(mouseStartPosition.x, mouseStartPosition.y, Camera.main.farClipPlane));
            Ray ray = new Ray(Camera.main.transform.position,mouseWorldPosition-Camera.main.transform.position);
            RaycastHit hit;
            if(Physics.Raycast(ray,out hit))
            {
                if (hit.transform.tag == "player")
                {
                    hit.transform.gameObject.GetComponent<MeshRenderer>().material.shader = selectShader;
                    hit.transform.gameObject.GetComponent<single>().isSelected = true;
                }
            }

        }
    }
    void OnGUI()
    {
        if (mouseDown)
        {
            Draw();
        }
    }
    void Draw()
    {
        drawMat.SetPass(0);
        GL.PushMatrix();//儲存攝像機變換矩陣  
        GL.LoadPixelMatrix();//設定用螢幕座標繪圖  
                             //透明框  
        Color clr = Color.black;

        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mousePosition.x, mouseStartPosition.y, 0);
        GL.End();

        //下  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mousePosition.y, 0);
        GL.Vertex3(mousePosition.x, mousePosition.y, 0);
        GL.End();

        //左  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mouseStartPosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mouseStartPosition.x, mousePosition.y, 0);
        GL.End();

        //右  
        GL.Begin(GL.LINES);
        GL.Color(clr);
        GL.Vertex3(mousePosition.x, mouseStartPosition.y, 0);
        GL.Vertex3(mousePosition.x, mousePosition.y, 0);
        GL.End();

        GL.PopMatrix();//還原  
    }
    
}

參考教程:

https://www.cnblogs.com/huangshiyu13/p/5634698.html

有關物體描邊參考如下:

https://blog.csdn.net/laipixiaoxi/article/details/53034756