SteamVR各腳本的功能
SteamVR/Scripts/下腳本各功能的實現(xiàn)
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)
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)
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ù)手柄的運動模擬帶動手臂的運動)的示例場景。這個場景里有一個模擬手臂:
分左右手暖夭,分別在左右兩個手柄控制器下面锹杈,在場景中的樣子是這樣的:
它這里只有兩個關節(jié)(肩關節(jié)和腕關節(jié),SteamVR_IK就只支持兩個關節(jié))迈着,然后有一個手指
SteamVR_TestThrow.unity
這個應該是測試通過手柄扔出一個物體的例子竭望,主要是測試下面的這個SteamVR_TestThrow腳本,可以看到這個腳本被添加到了左右兩個手柄上面:
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插件深度分析