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 右)
Step2.2
記得 將 public 性質的 laserPrefab 公開欄位
拖拉指定一個
剛做的雷射光預置物(Prefab)




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(傳出呼叫) _這兩個參數用在哪裡很常看到有人寫他們




雷射光光束 物件位置  以  「中心座標」  表示



VR 雷射光常遇問題



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 兩個公開欄位 拖拉指定一個
剛做的雷射光預置物 及  標靶預置物




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;
 }
}




















留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header