UNITY虛擬實境控制(一){VR視線焦點計時觸發事件}

一般來說手機類型的VR遊戲模式,如果不連接上手把控制器,除了少部分手機支援Cardboard旁邊的磁力板機外,最基本的操控方式就是以視覺中心來當作觸發條件,因為我們唯一能控制的就是頭的旋轉(陀螺儀水平儀),本篇就試著用射線來實現,焦點計時觸發的功能。
googlecardboard
(上圖側邊的鐵環就是磁力板機)
1.首先準備一個簡易場景,把攝影機置中並稍微離開地面(將Y設為2)
,這點稍微有點重要性,攝影機的位置在世界中心便於GoogleVR控制(主要原因是啟動後預設將攝影機放置世界中心當起點),避免射線起始發射的位置有偏差最好讓起始點重合。unet-hit-093
2.接下來建立我們的3D UI Canvas重新命名為FocusCanvas並調整RenderMode為WorldSpace
unet-hit-094
隨後調整RectTransform的原始座標Pos的XY都先將他歸零Z些微增加放置攝影機前確保可以看見,並調整UI的寬高250確保清晰
unet-hit-095
3.接下來用PS(PhotoShop)準備我們的UI素材,一個計時進度用的圓圈,製作時用128或256正方白色即可,用白色可以方便日後染色改變。
unet-hit-096
4.再加入Image和Text的UI到Canvas下
unet-hit-097並且做全版撐開的設定讓大小的控制交由外層的Canvas
unet-hit-098
5.調整Image和Text的內容如下:
unet-hit-099
將做好的圖片轉成Sprite格式放入SourceImage、調整顏色、Type改成Filled。
unet-hit-100
文字部分可以先清空,調整適中的大小、Alignment對齊置中、變更顏色。為了文字不被遮蔽順便將TOP屬性-300至於圓圈上方(位置可以自行調整),看起來會像這樣:
unet-hit-101
6.由於是3D的UI,希望她能跟著攝影機移動直接將它塞入MainCamera下並且使用標準單位1的CUBE對照下UI的大小尺寸比例,然後調整Canvas的縮放(目的是為了算出物件和UI之間的比例關係)unet-hit-102
unet-hit-103
大約是0.01的XY縮放比例,Z可以保持不變。
7.接下來撰寫程式RayGun.cs掛載於主攝影機上

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class RayGun : MonoBehaviour {
	public GameObject focusCanvas;
	public Image focus;
	public Text secText;
	float focusTimer = 0f;
	Camera camera;
	RaycastHit hit;
	void Start () {
		camera = this.gameObject.GetComponent<Camera> ();
		focus.fillAmount = focusTimer;
	}
	void Update(){
	}
}

首先宣告放Canvas的GameObject以及底下的Image、Text的public欄位,還有計時用的float、要打出射線的Camera本體以及被擊中的物體,並且在Start初始化攝影機本身並且讓UI Image的fillAmount初始化為0,=focusTimer計時用的浮點數。
8.接下來將Update改為FixedUpdate在裡面增加射線的程式碼

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class RayGun : MonoBehaviour {
	public GameObject focusCanvas;
	public Image focus;
	public Text secText;
	float focusTimer = 0f;
	Camera camera;
	RaycastHit hit;
	void Start () {
		camera = this.gameObject.GetComponent<Camera> ();
		focus.fillAmount = focusTimer;
	}
	void FixedUpdate(){
		Ray ray = new Ray(this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 200)));
		Debug.DrawLine (this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 20)),Color.red);
	}
}

首先Ray的條件是透過兩個點畫出一條線的概念執行,所以言下之意我們需要兩個座標。一個是由攝影機的位置發出,那目標點呢?這部分比較複雜些,應該視攝影機前方一段距離的一個延伸,也就是她是一個不斷在改變的座標位置,所以我們透過攝影機的ScreenToWorldPoint取得螢幕正中央的XY座標然後把Z指定一個長度(這裡使用200),取得螢幕正前方遠處的一個虛擬的座標位置把設線產生,透過Debug.DrawLine我們可以在畫面上看到這條畫出的射線。
unet-hit-104
9.由於這條射線可以可以產生碰撞偵測的,透過Physics.Raycast物理的射線投放導出碰撞的物體

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class RayGun : MonoBehaviour {
	public GameObject focusCanvas;
	public Image focus;
	public Text secText;
	float focusTimer = 0f;
	Camera camera;
	RaycastHit hit;
	void Start () {
		camera = this.gameObject.GetComponent<Camera> ();
		focus.fillAmount = focusTimer;
	}
	void FixedUpdate(){
		Ray ray = new Ray(this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 200)));
		Debug.DrawLine (this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 20)),Color.red);
		if (hit.collider.gameObject.tag == "MONSTER") {
			Debug.Log (hit.collider.name);
		}
	}
}

可以放置一個帶有MONSTER標籤的Cube測試看看可否印出他的名子
10.接下來就可以寫一個FocusDo方法來執行想做的事情

	void FocusDo(float O,Collider G){
		if(G != null){
			focusCanvas.transform.position = G.transform.position;
			if(O >= 1f){
				Destroy (G.gameObject);
			}
		}
	}

執行時檢查是否有打擊到物體,有的話將UI的位置移動到該物體上,並透過引數O檢查我們的計時UI跑滿了沒有(大於等於1)就銷毀該物件。
11.FocusDo方法的引數O自然需要另個帶回傳值的方法計數器來輔助

	float FocusTimer(float f){
		if (f != 0f) {
			focusTimer += f * Time.deltaTime;
			secText.text = (focusTimer/f).ToString("f2");
		} else {
			focusTimer = 0f;
			secText.text = "";
		}
		focus.fillAmount = focusTimer;
		return focusTimer;
	}

FocusTimer的引數f用來決定計時器跑得快慢,數字越大增加的越快,而且可以使用文字輔助知道這次要多少時間,假設沒有打到物體時帶入0就可以淨空計時器和文字。不斷刷新環形UI百分比(focus.fillAmount = focusTimer),並回傳focusTimer讓FocusDo知道何時可以摧毀該物體。
12.最後補完打擊到MONSTER的方法條件可以得到如下效果:
unet-hit-105完整程式:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class RayGun : MonoBehaviour {
	public GameObject focusCanvas;
	public Image focus;
	public Text secText;
	float focusTimer = 0f;
	Camera camera;
	RaycastHit hit;
	void Start () {
		camera = this.gameObject.GetComponent<Camera> ();
		focus.fillAmount = focusTimer;
	}
	void FixedUpdate(){
		Ray ray = new Ray(this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 200)));
		Debug.DrawLine (this.transform.position, camera.ScreenToWorldPoint( new Vector3(Screen.width/2 , Screen.height/2 , 20)),Color.red);
		if (Physics.Raycast (ray, out hit)) {
			if (hit.collider.gameObject.tag == "MONSTER") {
				FocusDo (FocusTimer(2f),hit.collider);
			} else {
				FocusDo (FocusTimer(0f),null);
			}
		}
	}
	float FocusTimer(float f){
		if (f != 0f) {
			focusTimer += f * Time.deltaTime;
			secText.text = (focusTimer/f).ToString("f2");
		} else {
			focusTimer = 0f;
			secText.text = "";
		}
		focus.fillAmount = focusTimer;
		return focusTimer;
	}
	void FocusDo(float O,Collider G){
		if(G != null){
			focusCanvas.transform.position = G.transform.position;
			if(O >= 1f){
				Destroy (G.gameObject);
			}
		}
	}
}

 

在〈UNITY虛擬實境控制(一){VR視線焦點計時觸發事件}〉中有 1 則留言

  1. 您好,不好意思,想請問一下,使用了這個方法製作視線焦點觸發,但應用在Vive頭盔時,輸出成exe後射線的位置都會不準的話要怎麼調整,因為是使用射線去做觸發事件,所以射線不準的話就會觸發不到。另外在unity裡看原本射線位置都會是在(0,0),但只要戴上頭盔就會偏移,所以另外寫了調整射線位置的程式碼,調整到正確位置之後在unity裡預覽是可以觸發的,但也是只要輸出成exe,射線就會不準,不知道是否有方法可以解決。 希望能得到您的回應,謝謝~

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *