SteamVR腳本解析

SteamVR各腳本的功能

SteamVR/Scripts/下腳本各功能的實現(xiàn)

Paste_Image.png

1按傅、SteamVR.cs 單例管理類,管理SteamVR程序的運行和終止。
2衷敌、SteamVR_Camera.cs 給場景添加一個最基本可運行的SteamVR組。
3拓瞪、SteamVR_CameraFlip.cs 使用Shader將屏幕圖像反轉(zhuǎn)得到最終圖像缴罗。
4、SteamVR_CameraMask.cs 將頭盔中看不到的屏幕像素遮蓋祭埂。
5面氓、SteamVR_Controller.cs 管理類,管理所有設備的輸入控制
6蛆橡、SteamVR_ControllerManager.cs 管理類舌界,管理場景中的設備活動
7、SteamVR_Ears.cs 控制Audio Listener的方向
8泰演、SteamVR_ExternalCamera.cs 用于渲染外部攝像機
9呻拌、SteamVR_Fade.cs 屏幕漸變功能
10、SteamVR_Frustum.cs 生成用于渲染的面片
11睦焕、SteamVR_GameView.cs 處理除眼圖像之外的渲染
12柏锄、SteamVR_IK.cs 手柄IK的控制
13、SteamVR_LoadLevel.cs 用于場景之間的平滑切換
14复亏、SteamVR_Menu.cs 給出一個范例菜單
15趾娃、SteamVR_Overlay.cs 提供和控制2D圖像的繪制
16、SteamVR_PlayArea.cs 對移動空間的設置
17缔御、SteamVR_Render.cs 控制眼圖像的渲染
18抬闷、SteamVR_RenderModel.cs 渲染手柄模型
19、SteamVR_Skybox.cs 設置天空盒
20、SteamVR_SphericalProjection.cs 應該是應用畸變投影矩陣
21笤成、SteamVR_Stats.cs 通過GUI Text顯示頭盔狀態(tài)
22评架、SteamVR_Status.cs 由事件控制的漸變效果的基類
23、SteamVR_StatusText.cs 繼承22的文字漸變
24炕泳、SteamVR_TestController.cs 測試手柄每個按鈕的輸入
25纵诞、SteamVR_TrackedCamera.cs 提供記錄相機的位置的功能
26、SteamVR_TrackedObject.cs 使場景中的物體和控制器的Pose保持一致
27培遵、SteamVR_UpdatePose.cs 當使用OpenVR接口時用此更新Pose
28浙芙、SteamVR_Utils.cs 一些公共方法和數(shù)據(jù)結(jié)構(gòu)

SteamVR/Extras/腳本下功能的實現(xiàn)


Paste_Image.png

SteamVR_GazeTracker.cs 提供凝視時的事件
SteamVR_LaserPointer.cs 應該是鐳射光線
SteamVR_Teleporter.cs 傳送功能
SteamVR_TestThrow.cs 投擲東西
SteamVR_TestTrackedCamera.cs 跟蹤相機測試
SteamVR_TrackedController.cs 手柄按鈕事件的接口

詳細腳本解析:

SteamVR_GazeTracker.cs腳本解析
這個腳本的作用是判斷當前物體是否被用戶(頭顯)所注視,進入注視和離開注視都會有回調(diào)籽腕。處于注視狀態(tài)的物體與實際注視點的距離范圍定義為小于0.15米嗡呼,而離開注視狀態(tài)的距離范圍為大于0.4米。之所以有一個大概的范圍皇耗,并且使用了一個平面來相交南窗,是因為注視這個動作是比較粗略的,玩家比較難能精確注視郎楼。
  Gaze回調(diào)的事件結(jié)構(gòu)體万伤,只有一個參數(shù),即距離呜袁,表示凝視點與物體(中心)的距離

public struct GazeEventArgs
{
    public float distance;
}

public delegate void GazeEventHandler(object sender, GazeEventArgs e);

public class SteamVR_GazeTracker : MonoBehaviour
{
    //當前是否處于gaze狀態(tài)
    public bool isInGaze = false;
    //入gaze狀態(tài)回調(diào)壕翩,使用者可以通過代碼添加自己的事件處理方法(在Inspector      中不會出現(xiàn))
    public event GazeEventHandler GazeOn;
    //離開gaze狀態(tài)回調(diào)
    public event GazeEventHandler GazeOff;
    //定義的進入gaze與離開gaze的距離范圍
    public float gazeInCutoff = 0.15f;
    public float gazeOutCutoff = 0.4f;

    // Contains a HMD tracked object that we can use to find the user's gaze
    //頭顯的transform對象
    Transform hmdTrackedObject = null;
    // Use this for initialization
    void Start ()
    {
    
    }
    public virtual void OnGazeOn(GazeEventArgs e)
    {
        //如果有注冊GazeOff回調(diào),調(diào)用它
        if (GazeOn != null)
            GazeOn(this, e);
    }
    public virtual void OnGazeOff(GazeEventArgs e)
    {
        
        //如果有注冊GazeOff回調(diào)傅寡,調(diào)用它
        if (GazeOff != null)
            GazeOff(this, e);
    }
    // Update is called once per frame
    void Update ()
    {
        // If we haven't set up hmdTrackedObject find what the user is looking at
        if (hmdTrackedObject == null)
        {
            //首次調(diào)用會去查找頭顯放妈,方法是查找所有SteamVR_TrackedObject對象。所有的跟蹤對象(比如頭顯荐操、手柄芜抒、基站)都是SteamVR_TrackedObject對象(相應的對象上附加了SteamVR_TrackedObject腳本)
            SteamVR_TrackedObject[] trackedObjects = FindObjectsOfType<SteamVR_TrackedObject>();
            foreach (SteamVR_TrackedObject tracked in trackedObjects)
            {
                if (tracked.index == SteamVR_TrackedObject.EIndex.Hmd)
                {
                    //找到頭顯設備,取其transform對象托启。頭顯設備的索引是0號索引
                    hmdTrackedObject = tracked.transform;
                    break;
                }
            }
        }
        if (hmdTrackedObject)
        {
            //構(gòu)造一條從頭顯正方向的射線
            Ray r = new Ray(hmdTrackedObject.position, hmdTrackedObject.forward);
            //構(gòu)造一個頭顯正方向宅倒、在當前物體位置的平面
            Plane p = new Plane(hmdTrackedObject.forward, transform.position);

            float enter = 0.0f;
            //射線與物體平面正向相交,返回的enter為沿射線的距離屯耸。如果不相交拐迁,或者反向相交,則下面的Raycast返回false
            if (p.Raycast(r, out enter))
            {
                //intersect為射線與物體平面在三維空間的交點
                Vector3 intersect = hmdTrackedObject.position + hmdTrackedObject.forward * enter;
                //計算空間兩點的距離疗绣,即物體當前位置與交點的距離
                float dist = Vector3.Distance(intersect, transform.position);
                //Debug.Log("Gaze dist = " + dist);
                if (dist < gazeInCutoff && !isInGaze)
                {
                    //當前物體與凝視點的距離小于0.15米线召,則認為進入gaze狀態(tài)
                    isInGaze = true;
                    GazeEventArgs e;
                    e.distance = dist;
                    OnGazeOn(e);
                }
                else if (dist >= gazeOutCutoff && isInGaze)
                {
                    //當前物體與凝視點的距離超過0.4米,則認為離開gaze狀態(tài)
                    isInGaze = false;
                    GazeEventArgs e;
                    e.distance = dist;
                    OnGazeOff(e);
                }
            }
        }
    }
}

SteamVR_LaserPointer.cs

這個腳本的作用與上面的SteamVR_GazeTracker相關及類似多矮。GazeTracker是通過頭顯的正視方向與物體相交來計算交點的缓淹。而這里是通過所謂的激光束來與物體相交的哈打。激光束就是手柄指向的方向,可以在游戲里面把這個方向渲染出一條激光束出來讯壶,特別是在通過手柄進行菜單的UI操作的時候料仗。在github openvr的sample目錄下的unity_teleport_sample示例有使用,它被加到右手柄上
 同上面的GazeTracker一樣伏蚊,觸發(fā)的事件所帶的參數(shù)

public struct PointerEventArgs
{
    //控制器(手柄)索引
    public uint controllerIndex;
    //目前好像并沒有用到
    public uint flags;
    //激光原點到命中點(交點)的距離
    public float distance;
    //命中物體的transform對象
    public Transform target;
}
public delegate void PointerEventHandler(object sender, PointerEventArgs e);


public class SteamVR_LaserPointer : MonoBehaviour
{
    //這個變量并未使用
    public bool active = true;
    // 激光的顏色
    public Color color;
    //激光束的粗細(創(chuàng)建了一個立方體立轧,按下面的scale,x躏吊、y是0.002氛改,z是100,就能看 到是一條很長的細線了)
    public float thickness = 0.002f;
    //一個空的GameObject颜阐,用于作激光束的parent
    public GameObject holder;
    //激光束本身,是用一個立方體拉長來模擬的(為啥不用圓柱體吓肋?顯然立方體要比圓柱體渲染簡單得多凳怨,在很細的情況下,用立方體是明智的選擇)
    public GameObject pointer;
    //用來判斷是否為第一次調(diào)用
    bool isActive = false;
    //這個是暴露在inspector中的屬性是鬼,用于控制是否給激光束(長方體)添加剛體肤舞。本身光是沒有重量的,沒有必要添加剛體吧均蜜。所以這里缺省是false
    public bool addRigidBody = false;
    //這個變量并未使用
    public Transform reference;
    //同上面的GazeTracker一樣李剖,用于觸發(fā)激光命中和離開事件
    public event PointerEventHandler PointerIn;
    public event PointerEventHandler PointerOut;
    //上次激光命中的物體的transform對象,用于判斷是否命中同一個物體
    Transform previousContact = null;
 

    // Use this for initialization
    void Start ()
    {
        //在腳本被加載的時候囤耳,做一些初始化
        //首先創(chuàng)建一個holder(即激光束的父物體)
        holder = new GameObject();
        //holder的transform的parent設為當前腳本所在的物體(通常這個腳本會加到控制器手柄上面)
        holder.transform.parent = this.transform;
        //位置設在0點(本地坐標系篙顺,相對于父親)
        holder.transform.localPosition = Vector3.zero;
        holder.transform.localRotation = Quaternion.identity;
        //創(chuàng)建激光束,用長方體模擬
        pointer = GameObject.CreatePrimitive(PrimitiveType.Cube);
        //將父親設為上面的holder
        pointer.transform.parent = holder.transform;
        //設置locale為(0.002,0.002,100)充择,看起來就是一條很長的線
        pointer.transform.localScale = new Vector3(thickness, thickness, 100f);
        //位置設在父親的(0,0,50)位置德玫,因為對于立方體(長方體),其中心在立方體中心椎麦,因為上面被放大到了100倍宰僧,那移動位置到(0,0,50)可以讓激光束的起點為父親
        pointer.transform.localPosition = new Vector3(0f, 0f, 50f);

        pointer.transform.localRotation = Quaternion.identity;
        // 如果指定了addRigidBody為true,則為激光束添加一個剛體观挎,對應的collider 則只設為觸發(fā)器(不會執(zhí)行碰撞琴儿,但會進入代碼)。否則嘁捷,會把collider銷毀掉造成,也就是不需要collider
        BoxCollider collider = pointer.GetComponent<BoxCollider>();
        if (addRigidBody)
        {
            if (collider)
            {
                collider.isTrigger = true;
            }
            Rigidbody rigidBody = pointer.AddComponent<Rigidbody>();
            rigidBody.isKinematic = true;
        }
        else
        {
            if(collider)
            {
                Object.Destroy(collider);
            }
        }
        //新建純色材質(zhì)并添加到MeshRender中。Color值通過inspector設置
        Material newMaterial = new Material(Shader.Find("Unlit/Color"));
        newMaterial.SetColor("_Color", color);
        pointer.GetComponent<MeshRenderer>().material = newMaterial;
    }

    public virtual void OnPointerIn(PointerEventArgs e)
    {
        //回調(diào)激光命中委托
        if (PointerIn != null)
            PointerIn(this, e);
    }

    public virtual void OnPointerOut(PointerEventArgs e)
    {
        //回調(diào)激光不再命中委托
        if (PointerOut != null)
            PointerOut(this, e);
    }


    // Update is called once per frame
    void Update()
    {
        if (!isActive)
        {
            //第一次調(diào)用時將holder設為active(當前物體transform的第一個child就是holder)
            isActive = true;
            this.transform.GetChild(0).gameObject.SetActive(true);
        }
        //命中物體(或者說激光束)的最遠距離記為100米
        float dist = 100f;
        //當前物體(手柄上)上還要掛一個SteamVR_TrackedController腳本
        SteamVR_TrackedController controller = GetComponent<SteamVR_TrackedController>();
        //  構(gòu)造一條射線
        Ray raycast = new Ray(transform.position, transform.forward);
        RaycastHit hit;
        //計算射線命中的場景中的物體
        bool bHit = Physics.Raycast(raycast, out hit);
       
        if(previousContact && previousContact != hit.transform)
        {
            // 如果之前已經(jīng)有一個命中的物體雄嚣,而當前命中的物體發(fā)生了變化谜疤,那么說明前一個命中的物體就要收到一個不再命中的通知
            PointerEventArgs args = new PointerEventArgs();
            if (controller != null)
            {
                args.controllerIndex = controller.controllerIndex;
            }
            args.distance = 0f;
            args.flags = 0;
            args.target = previousContact;
            OnPointerOut(args);
            previousContact = null;
            
        }
        if(bHit && previousContact != hit.transform)
        {
            //通知命中新的物體
            PointerEventArgs argsIn = new PointerEventArgs();
            if (controller != null)
            {
                argsIn.controllerIndex = controller.controllerIndex;
            }
            // hit.distance為射線原點到命中點的距離
            argsIn.distance = hit.distance;
            argsIn.flags = 0;
            //target記錄的是命中物體的transform
            argsIn.target = hit.transform;
            OnPointerIn(argsIn);
            // 記錄上一次命中的物體的transform
            previousContact = hit.transform;
        }
        if(!bHit)
        {
            previousContact = null;
        }
        if (bHit && hit.distance < 100f)
        {
            //如果命中物體距離小于100,則記錄下來,否則最遠就是100米
            dist = hit.distance;
        }

        if (controller != null && controller.triggerPressed)
        {
            //當按下扳機鍵時夷磕,將光束的粗細增大5倍履肃,同時長度會設為dist,這樣看起來光束就會到命中點截止坐桩,不會穿透物體
            pointer.transform.localScale = new Vector3(thickness * 5f, thickness * 5f, dist);
        }
        else
        {
            //按下扳機或者當前控制器沒有添加SteamVR_TrackedController時尺棋,顯示原始粗細的光束
            pointer.transform.localScale = new Vector3(thickness, thickness, dist);
        }
        //光束的位置總是設在光束長度的一半的位置,使得光束看起來總是從手柄發(fā)出來的
        pointer.transform.localPosition = new Vector3(0f, 0f, dist/2f);
    }
}

SteamVR_Teleporter.cs

這個腳本在github上的openvr的samples里面的unity_teleport_sample示例中有使用绵跷,從名字上看是用來做瞬移的膘螟。這個示例就是在場景中放了一些球,然后在右控制器上放了這個腳本碾局,也就是可以通過右控制器做瞬移荆残。做法是會從控制器上發(fā)出一個激光束,按下扳機鍵就可以瞬移到激光束指到的地方净当。通過觀察球的相對位置就能看到位置的變化内斯。這在游戲里面很常見,拿鼠標一點像啼,被操作的對象就會過去(當然過去有兩種方式俘闯,一種是慢慢走過去,另一種就是直接跳過去)忽冻,另一個見過的就是街景地圖真朗。

 {
  public enum TeleportType
  {
  //瞬移類型是根據(jù)與地形的交點來確定目的位置的
  TeleportTypeUseTerrain,
  //碰撞體類型是根據(jù)與任何帶碰撞體的物體的交點來確定目的位置的
  TeleportTypeUseCollider,
  //這個是與Y坐標為0的平面(通常就是地面)的交點來確定目的位置的
  TeleportTypeUseZeroY
  }
  public bool teleportOnClick = false;
  public TeleportType teleportType = TeleportType.TeleportTypeUseZeroY;
  Transform reference
  {
  get
  {
  //取的是最后渲染(depth最大)的相機(SteamVR_Camera)的原始點(這個origin實際就是將SteamVR_Camera添加到的原始場景中的Camera的位置)
  var top = SteamVR_Render.Top();
  //SteamVR相機的層次結(jié)構(gòu)是最上層是origin,然后下面有左右手柄和head僧诚,head下面有eye和ears
  return (top != null) ? top.origin: null;
  }
  }
  void Start ()
  {
//這個腳本所在的物體必要要添加SteamVR_TrackedController腳本(這個腳本的作用是將控制器的輸入轉(zhuǎn)換為事件回調(diào))遮婶,如果沒有添加,則自動添加湖笨。這說明這個腳本所在的物體需要是控制器(手柄)蹭睡,在unity_teleport_sample示例中,正是加到了右手柄上
   var trackedController = GetComponent();
  if (trackedController == null)
  {
  trackedController= gameObject.AddComponent();
  }
       // 添加扳機按下的回調(diào)
 
  trackedController.TriggerClicked+= new ClickedEventHandler(DoClick);
  if (teleportType== TeleportType.TeleportTypeUseTerrain)
  {
  // Start theplayer at the level of the terrain
  var t =reference;
  if (t != null)
                //如果是地形類型赶么,會將相機origin(基本上可以認為就是玩家的位置)的Y坐標先調(diào)整為地形的采樣高度(即相機origin所在位置的地形的實際Y坐標——即將相機origin放到地形表面——也就是在地形表面的垂直投影的位置)肩豁,這樣可以避免人鉆到地型里面了
 
  t.position = new Vector3(t.position.x, Terrain.activeTerrain.SampleHeight(t.position), t.position.z);
  }
  }
  void DoClick(object sender, ClickedEventArgs e)
  {
       //應該是通過這個變量來控制是否通過扳機鍵來瞬移。因為扳機鍵還可以用作其它用途辫呻,應該是在某種狀態(tài)下才能通過扳機鍵來瞬移清钥。比如,需要通過扳機鍵來瞬移時才需要將這個變量設為true
 
  if (teleportOnClick)
  {
  var t =reference;
  if (t == null)
  return;
  float refY =t.position.y;
}

創(chuàng)建了一個Y方向放闺,-refY位置的平面(是-refY而不是refY的原因是這個是Plane的distance參數(shù)祟昭,而Plane的distance是原點到Plane的距離,而距離的正負決定了在平面的哪一邊怖侦,為正表示原點在法線的正方向帖渠,為負表示原點在法線的反方向,這與通常的理解不一樣乾忱,所以這里為-refY)


Paste_Image.png
Plane plane = new Plane(Vector3.up, -refY);
//  當前腳本應該綁定在手柄上周瞎,因此才會有手柄方向的一條射線
Ray ray = new Ray(this.transform.position,transform.forward);
// hasGroudTarget是指是否射線與地面相交谐腰,或者說是否射到了地面上
bool hasGroundTarget = false;
// dist為射線原點(即手柄的原點)與相交點的距離

float dist = 0f;
  if (teleportType== TeleportType.TeleportTypeUseTerrain)
  {
                //與地形進行碰撞
 
  RaycastHit hitInfo;
  TerrainCollider tc = Terrain.activeTerrain.GetComponent();
  hasGroundTarget = tc.Raycast(ray, out hitInfo, 1000f);
  dist = hitInfo.distance;
  }
  else if (teleportType== TeleportType.TeleportTypeUseCollider)
  {
                //與場景中的碰撞體進行碰撞
 
  RaycastHit hitInfo;
  Physics.Raycast(ray, out hitInfo);
  dist = hitInfo.distance;
//這里并沒有設為hasGroundTarget為true,那后面的瞬移就無法完成。所以設置為TeleportTypeUseCollider應該就不能瞬移啊(實測確實不可以)女仰,那為什么要設置這種類型?
//從實際意義來說抡锈,確實是不能你扳 機指向哪就瞬移到哪疾忍,人不是什么地方都能去的
 
  }
  else
  {
                //與地面(Y方向的一個平面)相交
 
  hasGroundTarget = plane.Raycast(ray, out dist);
  }
  if (hasGroundTarget)
  {
              // headPosOnGround是head(head就是頭顯的位置)在地面(Y=0)的投 影,注意這里用的是localPosition
 
  Vector3 headPosOnGround = new Vector3(SteamVR_Render.Top().head.localPosition.x, 0.0f, SteamVR_Render.Top().head.localPosition.z);
 //這里就是將origin移動到扳機位置了床三。ray.origin + ray.direction*dist得到的就是射線與地形/地面交點的位置一罩。后面減去的兩個點分別是手柄(這里取的是第一個子物體,實際上就是左控制器)和
//頭顯相對于origin的位置(XZ平面)撇簿。感覺沒有必要減聂渊,按照所見即所得,玩家看到的激光束的交點补疑,就直接把位置定到那就好了歧沪,不需要考慮頭顯或者手柄的偏移
 
  t.position= ray.origin + ray.direction * dist - new Vector3(t.GetChild(0).localPosition.x, 0f, t.GetChild(0).localPosition.z)- headPosOnGround;
  }
  }
  }
  }

SteamVR_TestIK.unity

這是一個測試IK(更準確地說應該是SteamVR_IK.cs的歹撒,反向運動莲组,就是根據(jù)手柄的運動模擬帶動手臂的運動)的示例場景。這個場景里有一個模擬手臂:

Paste_Image.png

分左右手暖夭,分別在左右兩個手柄控制器下面锹杈,在場景中的樣子是這樣的:


Paste_Image.png

它這里只有兩個關節(jié)(肩關節(jié)和腕關節(jié),SteamVR_IK就只支持兩個關節(jié))迈着,然后有一個手指

SteamVR_TestThrow.unity

這個應該是測試通過手柄扔出一個物體的例子竭望,主要是測試下面的這個SteamVR_TestThrow腳本,可以看到這個腳本被添加到了左右兩個手柄上面:


Paste_Image.png

Template這個物體是被扔的物體裕菠,它由一個圓及一個子物體立方體組成咬清。通過扳機鍵創(chuàng)建一個物體并抓在手中,然后通過甩臂并同時松開扳機將物體扔出去

SteamVR_TestThrow.cs

這個腳本上面的測試場景的控制腳本奴潘,要與SteamVR_TrackedObject一起使用旧烧。實際上它會加到手柄上。

[RequireComponent(typeof(SteamVR_TrackedObject))]
 
  public>  {
  //要扔掉的物體画髓,并不是一個真正的prefab掘剪,而是一個場景中已經(jīng)創(chuàng)建好的物體
  public GameObject prefab;
  //這個是手柄上tip(手柄模型的一部分)下面的一個rigidbody。在手柄的模型中奈虾,所有的子組件(相對于父組件夺谁,即整個model)的位置都是(0,0,0)廉赔,
//但下面會再帶一   個attach的子對象,這個子對象是一個剛體匾鸥,然后真正的位置是通過它來確定的
  public Rigidbody attachPoint;
  //手柄這個跟蹤對象
  SteamVR_TrackedObject trackedObj;
  //固定關節(jié)
  FixedJoint joint;
  void Awake()
  {
   //Awake不管腳本是不是啟用都會調(diào)用
 
  trackedObj= GetComponent();
  }
  void FixedUpdate()
  {
   // 返回的Device對象(SteamVR_Controller內(nèi)部類)蜡塌,對當前跟蹤設備的輸入進行了一些封裝
 
  var device = SteamVR_Controller.Input((int)trackedObj.index);
  if (joint == null &&device.GetTouchDown(SteamVR_Controller.ButtonMask.Trigger))
  {
        //如果還沒有建立關節(jié),當按下扳機鍵時扫腺,建立物體與手柄的(關節(jié))關聯(lián)岗照,相當于就是抓起了物體
 
       // 先創(chuàng)建了物體(復制了場景中的一個物體)
 
  var go = GameObject.Instantiate(prefab);
        //創(chuàng)建的物體的位置位于tip的關聯(lián)點的位置。
 
  go.transform.position= attachPoint.transform.position;
        //添加固定關節(jié)笆环,這樣物體就能跟隨手柄動而動了
 
  joint= go.AddComponent();
        //將其與手柄tip的attach關聯(lián)
 
  joint.connectedBody= attachPoint;
  }
  else if (joint != null &&device.GetTouchUp(SteamVR_Controller.ButtonMask.Trigger))
  {
        //而如果已經(jīng)建立了關節(jié)攒至,再按扳機鍵時則會銷毀關節(jié),由于物體是一個剛體躁劣,就會自由下落迫吐。當然下面還會通過手柄給它一個初速度
 
  var go =joint.gameObject;
  var rigidbody =go.GetComponent();
        //銷毀關節(jié)
 
  Object.DestroyImmediate(joint);
  joint= null;
       //15秒后銷毀物體
 
  Object.Destroy(go, 15.0f);
  // We shouldprobably apply the offset between trackedObj.transform.position
  // anddevice.transform.pos to insert into the physics sim at the correct
  // location,however, we would then want to predict ahead the visual representation
  // by the sameamount we are predicting our render poses.
       /origin是SteamVR_TrackedObject中的一個變量,它大概就是SteamVR_Camera中的origin账忘,也就是CameraRig的頂層物體志膀,基本上它可以代表的是玩家的身體。
//因為除了頭部(頭顯)及手臂(手柄)會動以外鳖擒,身體本身也可以動溉浙。如果沒有指定,則直接使用父親的transform
 
  var origin =trackedObj.origin ? trackedObj.origin : trackedObj.transform.parent;
  if (origin != null)
  {
           //如果指定了origin蒋荚,因為是相對坐標(速度)戳稽,轉(zhuǎn)換成世界坐標(速度)
 
  rigidbody.velocity= origin.TransformVector(device.velocity);
  rigidbody.angularVelocity= origin.TransformVector(device.angularVelocity);
  }
  else
  {
            //如果沒有指定,則直接使用跟蹤設備的速度
 
  rigidbody.velocity= device.velocity;
  rigidbody.angularVelocity= device.angularVelocity;
  }
  rigidbody.maxAngularVelocity= rigidbody.angularVelocity.magnitude;
  }
  }
  }

SteamVR_TrackedController.cs

這個腳本對控制器的輸入做了一個簡單的封裝期升,將原始的輸入數(shù)據(jù)轉(zhuǎn)化成事件回調(diào)模式惊奇,它在SteamVR_LaserPointer.cs和SteamVR_Teleporter.cs中有使用,也就是在github上openvr中的teleporter示例中有使用播赁。在SteamVR_Teleporter.cs中颂郎,如果當前物體上沒有SteamVR_TrackedController.cs,會自動添加它(在老版的插件中容为,如果沒有添加乓序,會報錯)。直接看代碼看它干了些什么:
  點擊事件回調(diào)參數(shù)

public struct ClickedEventArgs
  {
  public uint controllerIndex;
  public uint flags;
  public float padX, padY;
  }
  //點擊事件處理委托方法
  public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);
  public>  {
  //控制器索引坎背,即跟蹤設備的索引
  public uint controllerIndex;
  //控制器的狀態(tài)替劈,比如按鍵是否按下,軸數(shù)據(jù)等
  public VRControllerState_t controllerState;
  //是否按了扳機鍵
  public bool triggerPressed = false;
  //是否按了steam鍵沼瘫?代碼中并沒有使用
  public bool steamPressed= false;
 //是否按了菜單鍵
  public bool menuPressed = false;
  //是否在觸控板上按下了
  public bool padPressed = false;
  //是否在觸控板上觸控
  public bool padTouched = false;
  //是否按下了拾取鍵
  public bool gripped = false;
  //各種按鍵的回調(diào)方法
  public event ClickedEventHandler MenuButtonClicked;
  public event ClickedEventHandler MenuButtonUnclicked;
  public event ClickedEventHandler TriggerClicked;
  public event ClickedEventHandler TriggerUnclicked;
  public event ClickedEventHandler SteamClicked;
  public event ClickedEventHandler PadClicked;
  public event ClickedEventHandler PadUnclicked;
  public event ClickedEventHandler PadTouched;
  public event ClickedEventHandler PadUntouched;
  public event ClickedEventHandler Gripped;
  public event ClickedEventHandler Ungripped;
  // Use this for initialization
  void Start()
  {
  //如果當前物體上沒有添加SteamVR_TrackedObject抬纸,則自動添加
  if (this.GetComponent() == null)
  {
  gameObject.AddComponent();
  }
  if (controllerIndex != 0)
  {
  //設置跟蹤設備的索引
  this.GetComponent().index =(SteamVR_TrackedObject.EIndex)controllerIndex;
  if (this.GetComponent() != null)
  {
     //如果當前物體(跟蹤設備)上還有SteamVR_RenderModel,也設置它的索引耿戚。在SteamVR的Unity插件中湿故,控制器本身上面并沒有SteamVR_RenderModel阿趁,
//但在其下面的Model上面有,然后就是其它的跟蹤設備(除hmd外)上都有坛猪。
 
  this.GetComponent().index =(SteamVR_TrackedObject.EIndex)controllerIndex;
  }
  }
  else
  {
 //如果沒有(通過inspector)指定控制器索引脖阵,則從SteamVR_TrackedObject中取索引。SteamVR插件的實際情況是墅茉,就只是有左右兩個控制器的 SteamVR_TrackedObject是沒有指定索引的命黔。但會在
//SteamVR_ControllerManager(通常掛在CameraRig頂層上面)中根據(jù)實際控制器的索引(比如可能只有一個控制器連接了)設置
 
  controllerIndex= (uint) this.GetComponent().index;
  }
  }
//還提供了接口設置控制器索引,這個也是會被SteamVR_ControllerManager廣播調(diào)  用的
 
  public void SetDeviceIndex(int index)
  {
  this.controllerIndex= (uint) index;
  }
//下面就是一些回調(diào)調(diào)用了
 
  public virtual void OnTriggerClicked(ClickedEventArgs e)
  {
  if (TriggerClicked != null)
  TriggerClicked(this, e);
  }
  public virtual void OnTriggerUnclicked(ClickedEventArgs e)
  {
  if (TriggerUnclicked != null)
  TriggerUnclicked(this, e);
  }
  public virtual void OnMenuClicked(ClickedEventArgs e)
  {
  if (MenuButtonClicked != null)
  MenuButtonClicked(this, e);
  }
  public virtual void OnMenuUnclicked(ClickedEventArgs e)
  {
  if (MenuButtonUnclicked != null)
  MenuButtonUnclicked(this, e);
  }
  public virtual void OnSteamClicked(ClickedEventArgs e)
  {
  if (SteamClicked!= null)
  SteamClicked(this, e);
  }
  public virtual void OnPadClicked(ClickedEventArgs e)
  {
  if (PadClicked!= null)
  PadClicked(this, e);
  }
  public virtual void OnPadUnclicked(ClickedEventArgs e)
  {
  if (PadUnclicked!= null)
  PadUnclicked(this, e);
  }
  public virtual void OnPadTouched(ClickedEventArgs e)
  {
  if (PadTouched!= null)
  PadTouched(this, e);
  }
  public virtual void OnPadUntouched(ClickedEventArgs e)
  {
  if (PadUntouched!= null)
  PadUntouched(this, e);
  }
  public virtual void OnGripped(ClickedEventArgs e)
  {
  if (Gripped != null)
  Gripped(this, e);
  }
  public virtual void OnUngripped(ClickedEventArgs e)
  {
  if (Ungripped != null)
  Ungripped(this, e);
  }
  // Update is called once per frame
  void Update()
  {
  // OpenVR.System即IVRSystem接口或者CVRSystem類
 
  var system = OpenVR.System;
  // Update是每幀調(diào)用就斤,而IVRSystem.GetControllerState是獲取即時的指定索引的控制器的狀態(tài)
 
  if (system != null &&system.GetControllerState(controllerIndex, ref controllerState))
  {
  ulong trigger =controllerState.ulButtonPressed & (1UL  0L &&!triggerPressed)
  {
            //按下了扳機鍵
 
  triggerPressed = true;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
           //padX/padY取的是軸輸入設備TrackPad的x悍募、y值。根據(jù)結(jié)構(gòu)體的定義洋机, rAxis0其實就是TrackPad
 
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnTriggerClicked(e);
  }
  else if (trigger == 0L &&triggerPressed)
  {
            //松開了扳機鍵
 
  triggerPressed = false;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnTriggerUnclicked(e);
  }
  ulong grip =controllerState.ulButtonPressed & (1UL  0L &&!gripped)
  {
            //按下了拾取鍵
 
  gripped = true;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnGripped(e);
  }
  else if (grip == 0L &&gripped)
  {
            //松開了拾取鍵
 
  gripped = false;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnUngripped(e);
  }
  ulong pad =controllerState.ulButtonPressed & (1UL  0L &&!padPressed)
  {
           //在TrackPad上按下了
 
  padPressed = true;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnPadClicked(e);
  }
  else if (pad == 0L &&padPressed)
  {
            //從TrackPad上松開了
 
  padPressed = false;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnPadUnclicked(e);
  }
  ulong menu =controllerState.ulButtonPressed & (1UL  0L &&!menuPressed)
  {
            //按下了菜單鍵
 
  menuPressed = true;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnMenuClicked(e);
  }
  else if (menu == 0L &&menuPressed)
  {
           // 松開了菜單鍵
 
  menuPressed = false;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnMenuUnclicked(e);
  }
  pad =controllerState.ulButtonTouched & (1UL  0L &&!padTouched)
  {
            //在TrackPad上觸摸
 
  padTouched = true;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnPadTouched(e);
  }
  else if (pad == 0L &&padTouched)
  {
           // 在TrackPad上取消觸摸
 
  padTouched = false;
  ClickedEventArgs e;
  e.controllerIndex = controllerIndex;
  e.flags = (uint)controllerState.ulButtonPressed;
  e.padX = controllerState.rAxis0.x;
  e.padY = controllerState.rAxis0.y;
  OnPadUntouched(e);
  }
  }
  }
  }

其他參考資料:
HTC VIVE的交互(凝視坠宴,瞬移,發(fā)出射線)
SteamVR(HTC Vive) Unity插件深度分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绷旗,一起剝皮案震驚了整個濱河市喜鼓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衔肢,老刑警劉巖庄岖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異角骤,居然都是意外死亡隅忿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門启搂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硼控,“玉大人刘陶,你說我怎么就攤上這事胳赌。” “怎么了匙隔?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵疑苫,是天一觀的道長。 經(jīng)常有香客問我纷责,道長捍掺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任再膳,我火速辦了婚禮挺勿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喂柒。我一直安慰自己不瓶,他們只是感情好禾嫉,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蚊丐,像睡著了一般熙参。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上麦备,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天孽椰,我揣著相機與錄音,去河邊找鬼凛篙。 笑死黍匾,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的呛梆。 我是一名探鬼主播膀捷,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼削彬!你這毒婦竟也來了全庸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤融痛,失蹤者是張志新(化名)和其女友劉穎壶笼,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雁刷,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡覆劈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沛励。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片责语。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖目派,靈堂內(nèi)的尸體忽然破棺而出坤候,到底是詐尸還是另有隱情,我是刑警寧澤企蹭,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布白筹,位于F島的核電站,受9級特大地震影響谅摄,放射性物質(zhì)發(fā)生泄漏徒河。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一送漠、第九天 我趴在偏房一處隱蔽的房頂上張望顽照。 院中可真熱鬧,春花似錦闽寡、人聲如沸代兵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奢人。三九已至谓媒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間何乎,已是汗流浹背句惯。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留支救,地道東北人抢野。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像各墨,于是被迫代替她去往敵國和親指孤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容