HTC Vive_Teleporter雷射光瞬移效果程式功能實踐_雷射光計算演算法_RaycastHit和LayerMask兩個struct內部構造剖析_附贈詳細步驟(常用)
HTC Vive 瞬間移動 功能
習自立人老師
Part1. 發出雷射光光束
首先開啟目前專案
加好 SteamVR 套件
Step1.1 建一個 Cube
Step1.2 調整 Scale ---> X:0.002 Y:0.002 Z:0
Step1.3 變為 Prefab (拉回project ,刪除場景物)
Step1.4 命名為 「laser」
Step1.5 Material 預設為 Standard 會有影子 若要修正影子不出現 就改 Unlit | Color
Step1.6 射出的雷射光 是否要 碰到物體(Box Collider 的勾選有無)
程式腳本實作部分
Step2.1
將LaserPointer.cs套用在Controller上(左 or 右)
LaserPointer.cs
發出雷射光功能 (可推動物體 / 也可穿透)
參考link:
http://answers.unity3d.com/questions/225286/how-do-i-know-whether-variable-is-pass-by-value-or.html
幾個程式中變數的 特別說明
一開始宣告
Transform laserTransform;
放在 void Start()處
laser = Instantiate (laserPrefab);
這裡所宣告的 laser物件(型態是GameObject)
意思是指
Clones the object original and returns the clone
laserTransform = laser.transform;
//為了方便寫程式用的(縮短程式撰寫的物件變數屬性長度)
藉由 變數 laserTransform 去 存取 laser 物件中的 transform 屬性
宣告於 void FixedUpdate()處
Physics.Raycast (ejectPoint, transform.forward, out hit, 100)
ejectPoint : 發出雷射光座標點(手把控制器)
transform.forward : 手把的正前方Z軸
out hit : Raycast 偵測到有碰撞之後回傳給主程式,無須初始化
100 : 發出虛擬射線 100 公尺
RaycastHit 內部構造
它基本上是一個 Struct 型態
結構內部
此函數使用的是
C# 中 call by output 的技法
何謂 call by output 從字面上意思來講 就是 「輸出參數」
何謂 「輸出參數」
out 一般用在函數有多個返回值得情況
out 可以不用在外面先初始化,但是一定要在 函數內做初始化(賦予值)
更詳細 C# out 介紹
請參考
Laser 光 在執行會有推動物體的問題 !?!?!
將預置物拉回 Hierachy 中 查看 右側屬性
Step1.將 Box Collider 勾消 Disable
Step2.記得 Apply
Laser 光 有影子 !?!?!??!
Unity 中的 著色器 (Shader) 可以自行新創見一個 不受 光影響的 著色器
Material --> 選 Laser所配置的Shader ---> 選 Unlight
再拉給 Prefab 即可
Part2. 發出雷射光 添加標靶
設置標靶 Reticle
Step1.1 建一個 Cylinder
Step1.2 調整 Scale ---> X:0.4 Y:0.001 Z:0.4
Step1.3 變為 Prefab (拉回project ,刪除場景物)
Step1.4 命名為 「reticle」
Step1.5 去除 Collider (Disable它)
程式腳本實作部分
Step2.1
將 LaserReticle.cs 套用在Controller上(左 or 右)
LaserReticle.cs
發出標靶位置(讓 Recticle 跟著 雷射 一起移動)
概念 跟 雷射光 相同撰寫設計 !!!
VR 雷射光標靶常遇問題
Recticle 除了在地面出現之外 也在其他物體產生 !?!?
可利用 Layer 作解決
Step1.建立 一個 teleportLayer 將所有可teleport的物體加進指定圖層
右上角 Layer ---> Edit Layers
多加一個 瞬間移動圖層
比方將 Floor 加入圖層 (對地板做瞬移)
針對 地板設定為 teleportLayer 的 動作
或是 將 四幅 圖畫(進入點) 加入圖層 (對 圖畫做瞬移)
Raycast 多加一個參數(teleportMask用teleportLayer代入)
修改後的 腳本 (不會在非地板的目標位置產生標靶的修正!!!!)
非位在指定圖層的物件 會直接穿透!!!!!!
LayerMask 這個 public性質的 Struct
可以自己套用此屬性 !!!!!
內部構造
Part3. 瞬間移動功能實踐
CameraRig 瞬移背後原理
標靶所呈現的地方可以移動至該處
Unity 中 CameraRig 中有一個 head
人眼跟著頭 當盡興瞬移到命中的
頭部必須伴隨著整個身體進行移動
所以是移動 CameraRig 而非單純移動頭部喔!!!!
因此身體移動到後位置 需要經過精密計算!!!!
也就是
頭相對於 CameraRig 的 距離差
瞬移的最後功能程式腳本
程式腳本實作部分
Step2.1
將 LaserReticle.cs 套用在Controller上(左 or 右)
Step2.2
記得 將 public 性質的 laserPrefab 和 reticlePrefab 兩個公開欄位
拖拉指定一個
剛做的雷射光預置物 及 標靶預置物
在這裡多增加 兩個public性質的 Transform 的 Class
Teleporter.cs
習自立人老師
Part1. 發出雷射光光束
首先開啟目前專案
加好 SteamVR 套件
Step1.1 建一個 Cube
Step1.2 調整 Scale ---> X:0.002 Y:0.002 Z:0
Step1.3 變為 Prefab (拉回project ,刪除場景物)
Step1.4 命名為 「laser」
Step1.5 Material 預設為 Standard 會有影子 若要修正影子不出現 就改 Unlit | Color
Step1.6 射出的雷射光 是否要 碰到物體(Box Collider 的勾選有無)
程式腳本實作部分
Step2.1
將LaserPointer.cs套用在Controller上(左 or 右)
LaserPointer.cs
發出雷射光功能 (可推動物體 / 也可穿透)
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class LaserPointer : MonoBehaviour { SteamVR_TrackedObject trackedObj; SteamVR_Controller.Device device; public GameObject laserPrefab; GameObject laser; Transform laserTransform; Vector3 hitPoint,ejectPoint; void Awake () { trackedObj = GetComponent<SteamVR_TrackedObject> (); } void Start() { laser = Instantiate (laserPrefab); laserTransform = laser.transform; //縮短物件中調用屬性 程式的長度 } void FixedUpdate() { device=SteamVR_Controller.Input((int)trackedObj.index); RaycastHit hit; ejectPoint = trackedObj.transform.position; //取得手把位置 //當兩個條件皆成立 //TouchPad有被按下 且 發出雷射的正前方有東西被打中(有collider)
if (device.GetPress (SteamVR_Controller.ButtonMask.Touchpad) &&
Physics.Raycast (ejectPoint, transform.forward, out hit, 100))//call by output(reference) { Debug.Log ("touchpad"); hitPoint = hit.point; showLaser (hit); // call by value } else { laser.SetActive (false); } } void showLaser(RaycastHit hit) // call by value { Debug.Log ("show laser"); laser.SetActive (true); /*雷射光演算法 計算終點*/ // 找到controller與hitPoint的中點 laserTransform.position = (ejectPoint+hitPoint)/2; // z軸對準hitPoint laserTransform.LookAt (hitPoint); // 設定Laser的z軸長度 laserTransform.localScale = new Vector3 (laserTransform.localScale.x,laserTransform.localScale.y, hit.distance); } } |
參考link:
http://answers.unity3d.com/questions/225286/how-do-i-know-whether-variable-is-pass-by-value-or.html
幾個程式中變數的 特別說明
一開始宣告
public GameObject laserPrefab;
GameObject laser;Transform laserTransform;
放在 void Start()處
laser = Instantiate (laserPrefab);
這裡所宣告的 laser物件(型態是GameObject)
意思是指
Clones the object original and returns the clone
laserTransform = laser.transform;
//為了方便寫程式用的(縮短程式撰寫的物件變數屬性長度)
藉由 變數 laserTransform 去 存取 laser 物件中的 transform 屬性
宣告於 void FixedUpdate()處
RaycastHit hit;
Physics.Raycast (ejectPoint, transform.forward, out hit, 100)
ejectPoint : 發出雷射光座標點(手把控制器)
transform.forward : 手把的正前方Z軸
out hit : Raycast 偵測到有碰撞之後回傳給主程式,無須初始化
100 : 發出虛擬射線 100 公尺
RaycastHit 內部構造
它基本上是一個 Struct 型態
結構內部
#region Assembly UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// D:\Unity5.5\Editor\Data\Managed\UnityEngine.dll
#endregion
using System;
using UnityEngine.Scripting;
namespace UnityEngine
{
//
// Summary:
// ///
// Structure used to get information back from a raycast.
// ///
[UsedByNativeCodeAttribute]
public struct RaycastHit
{
//
// Summary:
// ///
// The barycentric coordinate of the triangle we hit.
// ///
public Vector3 barycentricCoordinate { get; set; }
//
// Summary:
// ///
// The Collider that was hit.
// ///
public Collider collider { get; }
//
// Summary:
// ///
// The distance from the ray's origin to the impact point.
// ///
public float distance { get; set; }
//
// Summary:
// ///
// The uv lightmap coordinate at the impact point.
// ///
public Vector2 lightmapCoord { get; }
//
// Summary:
// ///
// The normal of the surface the ray hit.
// ///
public Vector3 normal { get; set; }
//
// Summary:
// ///
// The impact point in world space where the ray hit the collider.
// ///
public Vector3 point { get; set; }
//
// Summary:
// ///
// The Rigidbody of the collider that was hit. If the collider is not attached to
// a rigidbody then it is null.
// ///
public Rigidbody rigidbody { get; }
//
// Summary:
// ///
// The uv texture coordinate at the impact point.
// ///
public Vector2 textureCoord { get; }
[Obsolete("Use textureCoord2 instead")]
public Vector2 textureCoord1 { get; }
//
// Summary:
// ///
// The secondary uv texture coordinate at the impact point.
// ///
public Vector2 textureCoord2 { get; }
//
// Summary:
// ///
// The Transform of the rigidbody or collider that was hit.
// ///
public Transform transform { get; }
//
// Summary:
// ///
// The index of the triangle that was hit.
// ///
public int triangleIndex { get; }
}
}
此函數使用的是
C# 中 call by output 的技法
何謂 call by output 從字面上意思來講 就是 「輸出參數」
何謂 「輸出參數」
out 一般用在函數有多個返回值得情況
out 可以不用在外面先初始化,但是一定要在 函數內做初始化(賦予值)
更詳細 C# out 介紹
請參考
C#_捨麼是 ref (傳址呼叫)捨麼是out(傳出呼叫) _這兩個參數用在哪裡很常看到有人寫他們
Laser 光 在執行會有推動物體的問題 !?!?!
將預置物拉回 Hierachy 中 查看 右側屬性
Step1.將 Box Collider 勾消 Disable
Step2.記得 Apply
Laser 光 有影子 !?!?!??!
Unity 中的 著色器 (Shader) 可以自行新創見一個 不受 光影響的 著色器
Material --> 選 Laser所配置的Shader ---> 選 Unlight
再拉給 Prefab 即可
Part2. 發出雷射光 添加標靶
設置標靶 Reticle
Step1.1 建一個 Cylinder
Step1.2 調整 Scale ---> X:0.4 Y:0.001 Z:0.4
Step1.3 變為 Prefab (拉回project ,刪除場景物)
Step1.4 命名為 「reticle」
Step1.5 去除 Collider (Disable它)
程式腳本實作部分
Step2.1
將 LaserReticle.cs 套用在Controller上(左 or 右)
Step2.2
記得 將 public 性質的 laserPrefab 和 reticlePrefab 兩個公開欄位 拖拉指定一個
剛做的雷射光預置物 及 標靶預置物
記得 將 public 性質的 laserPrefab 和 reticlePrefab 兩個公開欄位 拖拉指定一個
剛做的雷射光預置物 及 標靶預置物
LaserReticle.cs
發出標靶位置(讓 Recticle 跟著 雷射 一起移動)
概念 跟 雷射光 相同撰寫設計 !!!
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class LaserReticle : MonoBehaviour { SteamVR_TrackedObject trackedObj; SteamVR_Controller.Device device; public GameObject laserPrefab, reticlePrefab; //多增加 標靶預置物 GameObject laser,reticle; // 多宣告 標靶遊戲物件變數 Transform laserTransform; Vector3 hitPoint, ejectPoint; void Awake() { trackedObj = GetComponent<SteamVR_TrackedObject>(); } void Start() { laser = Instantiate(laserPrefab); laserTransform = laser.transform; reticle = Instantiate(reticlePrefab); //一樣產生其複本 } void FixedUpdate() { device = SteamVR_Controller.Input((int)trackedObj.index); RaycastHit hit; ejectPoint = trackedObj.transform.position; if (device.GetPress(SteamVR_Controller.ButtonMask.Touchpad) && Physics.Raycast(ejectPoint, transform.forward, out hit, 100)) //call by output(reference) { Debug.Log("touchpad"); hitPoint = hit.point; showLaser(hit); // reticle.transform.position = hitPoint;//標靶跟著雷射光打中位置 reticle.SetActive(true);//顯示出 } else { laser.SetActive(false); reticle.SetActive(false);//隱藏 } } void showLaser(RaycastHit hit) // call by value { Debug.Log("show laser"); laser.SetActive(true); // 找到ejectPoint與hitPoint的中點 laserTransform.position = (ejectPoint + hitPoint) / 2; // z軸對準hitPoint laserTransform.LookAt(hitPoint); // 設定Laser的z軸長度 laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance); } } |
VR 雷射光標靶常遇問題
Recticle 除了在地面出現之外 也在其他物體產生 !?!?
可利用 Layer 作解決
Step1.建立 一個 teleportLayer 將所有可teleport的物體加進指定圖層
右上角 Layer ---> Edit Layers
多加一個 瞬間移動圖層
比方將 Floor 加入圖層 (對地板做瞬移)
針對 地板設定為 teleportLayer 的 動作
或是 將 四幅 圖畫(進入點) 加入圖層 (對 圖畫做瞬移)
Raycast 多加一個參數(teleportMask用teleportLayer代入)
修改後的 腳本 (不會在非地板的目標位置產生標靶的修正!!!!)
非位在指定圖層的物件 會直接穿透!!!!!!
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class LaserReticleLayerMask : MonoBehaviour { SteamVR_TrackedObject trackedObj; SteamVR_Controller.Device device; public GameObject laserPrefab, reticlePrefab; GameObject laser, reticle; Transform laserTransform; Vector3 hitPoint, ejectPoint; public LayerMask teleportMask; void Awake() { trackedObj = GetComponent<SteamVR_TrackedObject>(); } void Start() { laser = Instantiate(laserPrefab); laserTransform = laser.transform; reticle = Instantiate(reticlePrefab); } void FixedUpdate() { device = SteamVR_Controller.Input((int)trackedObj.index); RaycastHit hit; ejectPoint = trackedObj.transform.position; if (device.GetPress(SteamVR_Controller.ButtonMask.Touchpad) && Physics.Raycast(ejectPoint, transform.forward, out hit, 100, teleportMask)) { Debug.Log("touchpad"); hitPoint = hit.point; showLaser(hit); reticle.transform.position = hitPoint; reticle.SetActive(true); } else { laser.SetActive(false); reticle.SetActive(false); } } void showLaser(RaycastHit hit) { Debug.Log("show laser"); laser.SetActive(true); // 找到ejectPoint與hitPoint的中點 laserTransform.position = (ejectPoint + hitPoint) / 2; // z軸對準hitPoint laserTransform.LookAt(hitPoint); // 設定Laser的z軸長度 laserTransform.localScale = new Vector3(laserTransform.localScale.x, laserTransform.localScale.y, hit.distance); } } |
public LayerMask teleportMask;
LayerMask 這個 public性質的 Struct
可以自己套用此屬性 !!!!!
內部構造
#region Assembly UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
// D:\Unity5.5\Editor\Data\Managed\UnityEngine.dll
#endregion
using UnityEngine.Scripting;
namespace UnityEngine
{
//
// Summary:
// ///
// LayerMask allow you to display the LayerMask popup menu in the inspector.
// ///
[UsedByNativeCodeAttribute]
public struct LayerMask
{
//
// Summary:
// ///
// Converts a layer mask value to an integer value.
// ///
public int value { get; set; }
//
// Summary:
// ///
// Given a set of layer names as defined by either a Builtin or a User Layer in
// the, returns the equivalent layer mask for all of them.
// ///
//
// Parameters:
// layerNames:
// List of layer names to convert to a layer mask.
//
// Returns:
// ///
// The layer mask created from the layerNames.
// ///
public static int GetMask(params string[] layerNames);
//
// Summary:
// ///
// Given a layer number, returns the name of the layer as defined in either a Builtin
// or a User Layer in the.
// ///
//
// Parameters:
// layer:
public static string LayerToName(int layer);
//
// Summary:
// ///
// Given a layer name, returns the layer index as defined by either a Builtin or
// a User Layer in the.
// ///
//
// Parameters:
// layerName:
public static int NameToLayer(string layerName);
public static implicit operator int(LayerMask mask);
public static implicit operator LayerMask(int intVal);
}
}
Part3. 瞬間移動功能實踐
CameraRig 瞬移背後原理
標靶所呈現的地方可以移動至該處
Unity 中 CameraRig 中有一個 head
人眼跟著頭 當盡興瞬移到命中的
頭部必須伴隨著整個身體進行移動
所以是移動 CameraRig 而非單純移動頭部喔!!!!
因此身體移動到後位置 需要經過精密計算!!!!
也就是
頭相對於 CameraRig 的 距離差
瞬移的最後功能程式腳本
程式腳本實作部分
Step2.1
將 LaserReticle.cs 套用在Controller上(左 or 右)
Step2.2
記得 將 public 性質的 laserPrefab 和 reticlePrefab 兩個公開欄位
拖拉指定一個
剛做的雷射光預置物 及 標靶預置物
在這裡多增加 兩個public性質的 Transform 的 Class
Teleporter.cs
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; public class Teleporter : MonoBehaviour { SteamVR_TrackedObject trackedObj; SteamVR_Controller.Device device; public GameObject laserPrefab; GameObject laser; Transform laserTransform; Vector3 hitPoint; public GameObject ReticlePrefab; public LayerMask teleportMask; GameObject reticle; Transform reticleTransform; public Transform cameraRigTransform; public Transform headTransform; bool shouldTeleport; //若為真現在此時間點可進行瞬移 (放開touchpad會射出雷射光) void Awake () { trackedObj = GetComponent<SteamVR_TrackedObject> (); } void Start() { laser = Instantiate (laserPrefab); laserTransform = laser.transform; reticle = Instantiate (ReticlePrefab); reticleTransform = reticle.transform; } void FixedUpdate() { device=SteamVR_Controller.Input((int)trackedObj.index); RaycastHit hit; if (device.GetPress (SteamVR_Controller.ButtonMask.Touchpad) && Physics.Raycast (trackedObj.transform.position, transform.forward, out hit, 100, teleportMask)) { Debug.Log ("touchpad"); hitPoint = hit.point; showLaser (hit); reticle.SetActive (true); reticleTransform.position = hitPoint; shouldTeleport = true; } else { laser.SetActive (false); reticle.SetActive (false); } if (shouldTeleport&& device.GetPressUp(SteamVR_Controller.ButtonMask.Touchpad)) Teleport(); } void showLaser(RaycastHit hit) { Debug.Log ("show laser"); laser.SetActive (true); // 找到controller與hitPoint的中點 laserTransform.position = (trackedObj.transform.position+hitPoint)/2; // z軸對準hitPoint laserTransform.LookAt (hitPoint); // 設定Laser的z軸長度 laserTransform.localScale = new Vector3 (laserTransform.localScale.x, laserTransform.localScale.y, hit.distance); } //計算diff的程式 void Teleport() { shouldTeleport = false; reticle.SetActive (false); Vector3 diff = cameraRigTransform.position - headTransform.position; diff.y = 0; cameraRigTransform.position = hitPoint + diff; } } |
留言
張貼留言