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