VR开发实战HTC Vive项目之说走就走的旅行

一、场景

二、虚拟空间内移动传送

1、实现位置点选取

Tourism_LaserPointer

using UnityEngine;
using System.Collections;


public class Tourism_LaserPointer : MonoBehaviour
{
    public bool active = true;
    public Color color;
    public float thickness = 0.002f;
    public GameObject holder;
    public GameObject pointer;
    bool isActive = false;
    public bool addRigidBody = false;
    public Transform reference;
    public event PointerEventHandler PointerIn;
    public event PointerEventHandler PointerOut;

    public Vector3 HitPoint;
    Material currentCubeMaterial;

    Transform previousContact = null;
    Parabola parabola;

    // Use this for initialization
    void Start ()
    {
        holder = new GameObject();
        holder.transform.parent = this.transform;
        holder.transform.localPosition = Vector3.zero;

        pointer = GameObject.CreatePrimitive(PrimitiveType.Cube);
        pointer.transform.parent = holder.transform;
        pointer.transform.localScale = new Vector3(thickness, thickness, 100f);
        pointer.transform.localPosition = new Vector3(0f, 0f, 50f);
        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);
            }
        }
        currentCubeMaterial = new Material(Shader.Find("Unlit/Color"));
        currentCubeMaterial.SetColor("_Color", color);
        pointer.GetComponent<MeshRenderer>().material = currentCubeMaterial;

        parabola = GetComponent<Parabola>();
    }

    public virtual void OnPointerIn(PointerEventArgs e)
    {
        if (PointerIn != null)
            PointerIn(this, e);
    }

    public virtual void OnPointerOut(PointerEventArgs e)
    {
        if (PointerOut != null)
            PointerOut(this, e);
    }


    // Update is called once per frame
    void Update ()
    {
        if (!isActive)
        {
            isActive = true;
            this.transform.GetChild(0).gameObject.SetActive(true);
        }

        float dist = 100f;

        SteamVR_TrackedController controller = GetComponent<SteamVR_TrackedController>();

        Ray raycast = new Ray(transform.position, transform.forward);
        RaycastHit hit;
        bool bHit = Physics.Raycast(raycast, out hit);

        if(previousContact && previousContact != hit.transform)
        {
            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;
            }
            argsIn.distance = hit.distance;
            argsIn.flags = 0;
            argsIn.target = hit.transform;
            OnPointerIn(argsIn);
            previousContact = hit.transform;
        }
        if(!bHit)
        {
            previousContact = null;
            currentCubeMaterial.SetColor("_Color", Color.black);
            parabola.EndPosion = Vector3.zero;
        }
        else
        {
            currentCubeMaterial.SetColor("_Color", Color.red);
            parabola.EndPosion = hit.point;
        }
        if (bHit && hit.distance < 100f)
        {
            dist = hit.distance;
        }
        if (bHit)
        {
            HitPoint = hit.point;
        }

        if (controller != null && controller.triggerPressed)
        {
            pointer.transform.localScale = new Vector3(thickness * 5f, thickness * 5f, dist);
        }
        else
        {
            pointer.transform.localScale = new Vector3(thickness, thickness, dist);
        }
        pointer.transform.localPosition = new Vector3(0f, 0f, dist/2f);
    }
}

SteamVR_TrackedController

using UnityEngine;
using Valve.VR;

public struct ClickedEventArgs
{
    public uint controllerIndex;
    public uint flags;
    public float padX, padY;
}

public delegate void ClickedEventHandler(object sender, ClickedEventArgs e);

public class SteamVR_TrackedController : MonoBehaviour
{
    public uint controllerIndex;
    public VRControllerState_t controllerState;
    public bool triggerPressed = false;
    public bool steamPressed = false;
    public bool menuPressed = false;
    public bool padPressed = false;
    public bool padTouched = false;
    public bool gripped = false;

    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()
    {
        if (this.GetComponent<SteamVR_TrackedObject>() == null)
        {
            gameObject.AddComponent<SteamVR_TrackedObject>();
        }

        if (controllerIndex != 0)
        {
            this.GetComponent<SteamVR_TrackedObject>().index = (SteamVR_TrackedObject.EIndex)controllerIndex;
            if (this.GetComponent<SteamVR_RenderModel>() != null)
            {
                this.GetComponent<SteamVR_RenderModel>().index = (SteamVR_TrackedObject.EIndex)controllerIndex;
            }
        }
        else
        {
            controllerIndex = (uint) this.GetComponent<SteamVR_TrackedObject>().index;
        }
    }

    public void SetDeviceIndex(int index)
    {
            this.controllerIndex = (uint) index;
    }

    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()
    {
        var system = OpenVR.System;
        if (system != null && system.GetControllerState(controllerIndex, ref controllerState))
        {
            ulong trigger = controllerState.ulButtonPressed & (1UL << ((int)EVRButtonId.k_EButton_SteamVR_Trigger));
            if (trigger > 0L && !triggerPressed)
            {
                triggerPressed = true;
                ClickedEventArgs e;
                e.controllerIndex = controllerIndex;
                e.flags = (uint)controllerState.ulButtonPressed;
                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 << ((int)EVRButtonId.k_EButton_Grip));
            if (grip > 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 << ((int)EVRButtonId.k_EButton_SteamVR_Touchpad));
            if (pad > 0L && !padPressed)
            {
                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)
            {
                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 << ((int)EVRButtonId.k_EButton_ApplicationMenu));
            if (menu > 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 << ((int)EVRButtonId.k_EButton_SteamVR_Touchpad));
            if (pad > 0L && !padTouched)
            {
                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)
            {
                padTouched = false;
                ClickedEventArgs e;
                e.controllerIndex = controllerIndex;
                e.flags = (uint)controllerState.ulButtonPressed;
                e.padX = controllerState.rAxis0.x;
                e.padY = controllerState.rAxis0.y;
                OnPadUntouched(e);
            }
        }
    }
}

2、实现游客传送
using UnityEngine;
using System.Collections;

public class Teleport : MonoBehaviour
{
    //手动引用左手柄物体
    public GameObject GoLeft;
    //设置全局变量存储数据
    Tourism_LaserPointer slLeft;
    SteamVR_TrackedController stLeft;
    ClickedEventHandler ce;
    Transform currentTransform;
    PointerEventArgs tp;
    void Start()
    {
        //在Start方法中初始化变量,以注册监听方法
        slLeft = GoLeft.GetComponent<Tourism_LaserPointer>();
        stLeft = GoLeft.GetComponent<SteamVR_TrackedController>();
        //注册监听事件LeftPointIn(手柄有物体指向事件)LeftPointOut(取消指向事件)TriggerClicked(扳机扣下事件)
        slLeft.PointerIn += LeftPointIn;
        slLeft.PointerOut += LeftPointOut;
        stLeft.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)
        {
            TeleportByPosition(slLeft.HitPoint);
        }
    }

    private void TeleportByPosition(Vector3 targetPosition)
    {
        Debug.Log("targetPosition:" + targetPosition.x + "_" + targetPosition.y + "_" + targetPosition.z);
        //根据之前所得公式计算目标位置移动实际空间
        this.gameObject.transform.position = new Vector3(targetPosition.x - GoLeft.transform.localPosition.x, targetPosition.y, targetPosition.z - GoLeft.transform.localPosition.z);
    }
}

三、多场景编辑

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

public class TouristCheck : MonoBehaviour
{
    //应用物体当检测条件达到时影藏该物体
    public GameObject DoorObject;
    //加载场景的名称
    public string SceneName;
    //场景异步加载操作控制器
    AsyncOperation asyncOperation;

    void OnTriggerEnter(Collider other)
    {
        //当检测到碰撞时查看碰撞物体是不是主相机,如果是进行场景加载并对asyncOperation赋值进行标记
        if (other.tag == "MainCamera" && asyncOperation == null)
        {
            asyncOperation = SceneManager.LoadSceneAsync(SceneName, LoadSceneMode.Additive);
        }
    }

    void FixedUpdate()
    {
        //通过asyncOperation.isDone来检测场景是否加载完成如果加载完成则将墙影藏并将该物体影藏来展现新场景,同时避免再度触发碰撞进行场景加载
        if (asyncOperation != null && asyncOperation.isDone)
        {
            SceneManager.SetActiveScene(SceneManager.GetSceneByName(SceneName));
            Manager.Instance.StartNewScene(this);
            asyncOperation = null;
            DoorObject.SetActive(false);
        }
    }

    public void Reset()
    {
        //首先将隐藏的墙显示出来
        DoorObject.SetActive(true);
        //然后根据场景名称卸载场景
        SceneManager.UnloadScene(SceneName);
    }
}


四、场景管理类

单例模式
using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;

public class Manager : MonoBehaviour {
    public static Manager Instance;
    public Light CurrentLight;
    TouristCheck currentCheck;
    void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
        }
        else
        {
            Debug.LogError("不能重复创建Manager类");
        }
    }

    public void StartNewScene(TouristCheck touristCheck)
    {
        //检测当前是否已经有场景加载,如果没有将调用对象设置为currentCheck
        if (currentCheck == null)
        {
            currentCheck = touristCheck;
        }
        //如果有则调用TouristCheck.Reset方法重置并更新currentCheck
        else if (currentCheck != touristCheck)
        {
            currentCheck.Reset();
            currentCheck = touristCheck;
        }
        CurrentLight = GameObject.Find("Directional Light").GetComponent<Light>();
    }
}

五、实现不同时段效果

TimeController

using UnityEngine;
using System.Collections;

public class TimeController : MonoBehaviour {
    SteamVR_TrackedController TrackedController;
    float padX, padY;
    bool isGripp;

    // Use this for initialization
    void Start () {
        //获取控制器脚本
        TrackedController = GetComponent<SteamVR_TrackedController>();
        //添加握和松开的监听
        TrackedController.Gripped += Gripped;
        TrackedController.Ungripped += Ungripped;
    }

    //当握时修改全局变量开关
    void Gripped(object sender, ClickedEventArgs e)
    {
        isGripp = true;
    }

    void Ungripped(object sender, ClickedEventArgs e)
    {
        isGripp = false;
    }


    // Update is called once per frame
    void Update () {
        if (isGripp)
        {
            //获取当前触摸位置,如果不为零则根据触摸的x轴来调整平行光朝向
            if (TrackedController.controllerState.rAxis0.x != 0 && TrackedController.controllerState.rAxis0.y != 0)
            {
                float angle = 90 + TrackedController.controllerState.rAxis0.x * 120;
                Manager.Instance.CurrentLight.transform.rotation = Quaternion.AngleAxis(angle, Vector3.right);
            }
        }
    }
}

六、创建传送面和不可传送面

1、区分传送面
2、修改传送点选取方式

实现抛物线效果

using UnityEngine;
using System.Collections;

public class Parabola : MonoBehaviour {
    //传入参数Speed方向shootTransfrom发射器位置StartPosition起始位置EndPosion结束位置GravitationalAcceleration重力加速度lineNodeNum绘制节点数量line绘制抛物线
    public Vector3 Speed;
    public Transform shootTransfrom;

    public Vector3 StartPosition;
    public Vector3 EndPosion;
    public float GravitationalAcceleration = 10;
    public int lineNodeNum = 10;
    public LineRenderer line;
    Vector3[] positions;

    // Use this for initialization
    void Start () {
        //初始化线段绘制节点
        positions = 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 () {
        //更新发射点位置
        shootTransfrom.position = this.transform.position;
        shootTransfrom.rotation = Quaternion.Euler(this.transform.rotation.eulerAngles.x - 30, this.transform.rotation.eulerAngles.y,0);

        //当结束点为0及没有结束点时将线段回复为直线
        if (EndPosion == Vector3.zero)
        {
            ResetLine();
            return;
        }
        StartPosition = shootTransfrom.position;

        //提前计算出在水平和竖直上的位移
        float Sx = Vector3.Distance(GetPlaneVector(EndPosion),GetPlaneVector(StartPosition));
        float Sy = StartPosition.y - EndPosion.y;
        //计算出竖直方向和水平方向上的初速度比值
        float tanA = -shootTransfrom.forward.y / Vector3.Distance(Vector3.zero, GetPlaneVector(shootTransfrom.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;
        positions[0] = StartPosition;
        for (int i = 1; i < lineNodeNum; i++)
        {
            float xz = GetX(Vx, firstLineNodeTime * (i + 1));
            float y = GetY(firstLineNodeTime * (i + 1), Vy);
            positions[i] = Vector3.Normalize(GetPlaneVector(shootTransfrom.forward)) * xz + Vector3.down * y + shootTransfrom.position;
        }

        line.SetPositions(positions);
    }

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

    /// <summary>
    /// 将线段重置为一条直线
    /// </summary>
    void ResetLine()
    {
        for (int i = 0; i < lineNodeNum; i++)
        {
            positions[i] = transform.forward * i + transform.position;
        }

        line.SetPositions(positions);
    }
}

七、展示效果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容