1. 程式人生 > >HTC VIVE 基礎開發1

HTC VIVE 基礎開發1

此文章用於自己學習過程中的記錄,以便日後翻閱

開發HTC VIVE 首先需要下載Steam 平臺,然後在Steam的商店裡搜尋Steam VR下載安裝就可以了。

建立新的Unity專案

建立一個新的Unity工程


下載Steam VR外掛

開啟unity的 Asset Store 




在Unity中的商店裡搜尋Steam vr外掛


下載完成後點選Import按鈕



將下載好的Steam VR外掛全部匯入到新的Unity工程中



匯入中會有綠色的讀條,如果匯入完成後彈出API Update Required,這是在提示你API更新,可以不用管他,點選No Thanks即可。(原因應該是Steam VR 外掛的版本不是最新的)


等待SteamVr_Settings彈出後點擊Accept All。到這裡匯入就完成了

實現對手柄的控制

開啟SteamVR_LaserPointer,在這裡實現對鐳射筆的控制功能,首先把SteamVR_LaserPointer

Ctrl+d 複製一份,然後再該指令碼的基礎上進行修改


新建一個資料夾命名為Script,把SteamVR_LaserPointer指令碼重新命名為LaserPointer,然後拖入新建的Script資料夾中


進入LaserPointer指令碼,然後修改類名為LaserPointer


刪除掉選中的藍色部分,然後儲存



製作鐳射筆選中的目標點


在void Update 找到程式碼判斷的語句進行擴充,並執行Hit判斷,把Hit到的點存到剛剛建立的HitPoint變數中


在SteamVr資料夾中找到Prefabs資料夾然後把CameraRig放到場景中去


在場景中用Cube搭建一個簡單的房間,然後把CameraRig的位置進行調整


展開CameraRig,選擇Controller(left)對左手的手柄進行控制操作


給Controller(left)新增修改的Laser Point 賦予給CameraRig和SteamVR_TrackedController

然後去掉TrackSteamVR_Tracked Object


到了這一步,如果你的HTC VIVE裝置除錯正確,那就能在Unity的場景中看到他們了。


建立傳送的指令碼

建立Teleport

複製指令碼
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Teleport : MonoBehaviour {
    //手柄的引用
    public GameObject Left;
    //儲存一些變數
    LaserPointer LP_Left;
    SteamVR_TrackedController ST_Left;
    Transform CurrentTransform;

    
	// Use this for initialization
	void Start () {
        //初始化變數  註冊監聽  獲取鐳射筆和控制器
        LP_Left = Left.GetComponent<LaserPointer>();
        ST_Left = Left.GetComponent<SteamVR_TrackedController>();
        LP_Left.PointerIn += LeftPointIn;//手柄指向事件
        LP_Left.PointerOut += LeftPointOut;//手柄取消事件
        ST_Left.TriggerClicked += TriggerClicked;//手柄扳機事件

    }
	
    //手柄有物體指向
    void  LeftPointIn(object sender,PointerEventArgs e)
    {//當有物體指向  設定變數標識
        CurrentTransform = e.target;
    }

    //取消指向
    void LeftPointOut(object sender,PointerEventArgs e)
    {
        //取消指向事件 變數標識設定為空
        CurrentTransform = null;
    }


    void TriggerClicked(object sender,ClickedEventArgs e)
    {//當指向不為空的時候,進行移動
        if (CurrentTransform != null)
        {
            TeleportPosition(LP_Left.HitPoint);
        }
    }

    //移動的方法
    private void TeleportPosition(Vector3 targetPosition)
    {
        this.gameObject.transform.position = new Vector3(targetPosition.x - Left.transform.localPosition.x, targetPosition.y, targetPosition.z - Left.transform.localPosition.z);
    }
    // Update is called once per frame
    void Update () {
		
	}
}

賦予Teleport指令碼給CameraRig元件,然後把Controller(left)賦值到Left


到這一步就完成了傳送功能

實現多場景載入

建立一個新的Check指令碼用來檢測

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class Check : MonoBehaviour {

    //檢測 條件完成 達到時 隱藏
    public GameObject _Object;
    // 載入場景名字
    public string SceneName;
    //非同步載入控制器
    AsyncOperation _AsyncOperation;

    private void OnTriggerEnter(Collider other)
    {
        //當檢測到碰撞時 檢查碰撞物體是不是主相機  如果是進行場景載入  對__AsyncOperation賦值  進行標記
        if (other.tag == "MainCamera" && _AsyncOperation == null)
        {
            _AsyncOperation = SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive);
        }
    }

    // Use this for initialization
    void Start () {
		
	}
	
	// Update is called once per frame
	void FixedUpdate () {

        //通過_AsyncOperation.isDone 來檢測場景是否載入完成  如果載入完成 就隱藏特殊物體 來展現新的場景 同時避免再度觸發碰撞進行場景載入
        if (_AsyncOperation!=null && _AsyncOperation.isDone)
        {
            _AsyncOperation = null;
            _Object.SetActive(false);
        }
	}
}

把Check 賦予給一面牆(建立一個CUBE拉成一面牆的樣子,既點選牆面之後跳轉的場景),然後對這個牆壁新增BoxCollider元件,BoxCollider元件大小為包裹住牆壁。然後勾選IsTrigger(勾上代表觸發器)



在CameraRig上的Camera(eye)上新增BoxCollider 元件,同意勾選上IsTrigger 選項


然後新增Rigidbody元件取消勾選UseGravity


建立場景管理

建立一個新的指令碼 Manager 用來管理你的場景

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Manager : MonoBehaviour {

    public static Manager Instance;

    Check CurrentCheck;

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Debug.LogError("不能重複建立Manager");
        }
    }

    public void StartNewScene(Check _Check)
    {
        //檢測當前是否有場景載入 如果沒有將呼叫物件設定為currentCheck

        if (CurrentCheck == null)
        {
            CurrentCheck = _Check;
        }else if (CurrentCheck != _Check)//如果有  就呼叫CurrentCheck.Reset方法重置 並且更新CurrentCheck呼叫
        {
            CurrentCheck.Reset();
            CurrentCheck = _Check;
        }
    }
 
}

回到Check指令碼,新增兩行新的程式碼


然後在Check指令碼中新增Reset方法


回到Unity裡建立一個GameObject,然後把它的位置Reset,重新命名為Manager,然後把Manager指令碼掛上去


現在不會兩個場景一起顯示了,只會顯示其中一個

到這裡就完成了場景解除安裝的功能

製作不可傳送區域

需要對傳送進行限制,例如不能傳送到界外,或者不能傳送到屋頂或者水裡了

例如把水面設定為不可傳送區域,在搜尋欄中搜索命名為water的所有模型(前提是你的模型的名字為water,如果是別的就搜尋你模型的名字)


然後把Layer層的Default全部設定為Ignore Raycast


到這裡就無法選擇水面進行移動了。但是還沒有明顯的禁止移動提示。

接下來需要把禁止移動的提示製作的更明顯

開啟LaserPoint指令碼進行編輯,需要先新增一個Material 來控制射線的顏色


因為上面定義了新的Material ,所以把下面的刪掉


在指向判斷這裡新增新的更改,把可以只想設定為綠色,否則設定為紅色


到這裡就完成了點選不可傳送地區射線變紅的功能

製作拋物線---把鐳射筆的直線更改為拋物線

建立一個新的指令碼parabola

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Parabola : MonoBehaviour {

    //發射器位置
    public Transform ShootTransform;
    //起點
    public Vector3 StartPosition;
    //終點
    public Vector3 EndPosition;
    //重力加速度
    public float GravitationalAcceleration = 10;
    //繪製節點數量
    public int LineNodeNum = 10;
    //繪製拋物線
    public LineRenderer Line;

    Vector3[] Position;


	// Use this for initialization
	void Start () {
        //初始化線段繪製節點
        Position = new Vector3[LineNodeNum];
        //設定定點數量
        Line.SetVertexCount(LineNodeNum);
	}

    Vector3 GetPlaneVector(Vector3 v3)
    {
        return new Vector3(v3.x, 0, v3.z);
    }
	
	// Update is called once per frame
	void FixedUpdate () {
        //更新發射點的位置(曲線)
        ShootTransform.position = this.transform.position;
        ShootTransform.rotation = Quaternion.Euler(this.transform.rotation.eulerAngles.x - 30, this.transform.rotation.eulerAngles.y, 0);
        //當結束點為0但沒有結束點的時候 將線段恢復為直線
        if (EndPosition == Vector3.zero)
        {
            ResetLine();
            return;
        }
        StartPosition = ShootTransform.position;
        //計算出水平和垂直上的位移
        float Sx = Vector3.Distance(GetPlaneVector(EndPosition), GetPlaneVector(StartPosition));
        float Sy = StartPosition.y - EndPosition.y;
        //計算出垂直方向和水平方向上的初始速度比值
        float tanA = -ShootTransform.forward.y / Vector3.Distance(Vector3.zero, GetPlaneVector(ShootTransform.forward));
        //計算運動時間
        float t = Mathf.Sqrt((2 * Sy - 2 * Sx * tanA) / GravitationalAcceleration);
        if(float.IsNaN(t))
        {
            ResetLine();
            return;
        }
        //推匯出水平和垂直的初速度
        float Vx = Sx / t;
        float Vy = Vx * tanA;
        //繪製出線段
        float FirstLineNodeTime = t / LineNodeNum;
        Position[8] = StartPosition;
        for (int i = 1; i < LineNodeNum; i++)
        {
            float xz = GetX(Vx, FirstLineNodeTime * (i + 1));
            float y = GetY(FirstLineNodeTime * (i + 1), Vy);
            Position[i] = Vector3.Normalize(GetPlaneVector(ShootTransform.forward)) * xz + Vector3.down * y + ShootTransform.position;


        }
        Line.SetPositions(Position);

    }
    /// <summary>
    /// 計算水平方向的位移
    /// </summary>
    /// <param name="Speed">水平方向初速度</param>
    /// <param name="time">時間</param>
    /// <returns></returns>
    private float GetX(float Speed,float time)
    {
        float X = Speed * time;
        return X;
    }

    /// <summary>
    /// 計算垂直方向的位移
    /// </summary>
    /// <param name="time">時間</param>
    /// <param name="SpeedDownFloat">垂直方向的初速度</param>
    /// <returns></returns>
    private float GetY(float time,float SpeedDownFloat)
    {
        float Y = (float)(SpeedDownFloat * time + 0.5 * GravitationalAcceleration * time * time);
        return Y;
    }
    void  ResetLine()
    {
        for (int i = 0; i < LineNodeNum; i++)
        {
            Position[i] = transform.forward * i + transform.position;
            Line.SetPositions(Position);
        }
    }

}

回到LaserPointer腳本里去拋物線的處理




建立一個GameObject命名為Shooter,再建立一個命名為ShootLine


對ShootLine新增Line Renderer元件,然後建立一個材質球賦予它(顏色隨意)


找到Controller (left)新增剛剛寫好的拋物線指令碼,然後把剛剛的Shooter、ShootLine指定過來


可以增加LineNodeNum的節點數讓拋物線更平滑

到這裡就實現了拋物線

這時由於鐳射筆自帶的射線和製作的拋物線同時存在,需要把內建的直線取消掉,開啟LaserPointer指令碼登出掉所有pointer相關的程式碼



全部登出後鐳射筆內建的直線就消失了,只剩下拋物線。

啟用單例模式製作拋物線顏色

在Parabola指令碼中新增一個新的單例模式 用來改變拋物線的顏色



回到LaserPointer指令碼,在指向判斷處新增控制顏色的程式碼


回到Unity執行測試,如果可以移動的拋物線則為綠色,否則為紅色直線-------文章結束