unity实现3d摇杆结合leapmotion控制实现VR场景移动

** unity实现3d摇杆 结合leapmotion控制实现VR场景移动 **

Created by miccall (转载请注明出处
  • 开发vr很恼火的就是场景的移动 真实地方本来就小,如果vr里面的场景也小,那还玩个啥,所以虚拟现实的场景是很大的 如何实现小空间对大空间的移动,这也是vr开发要解决的问题之一 。

  • 我们老大设想了摇杆操作 我就顺着思路做了一套 先看看基本效果

这里写图片描述
  • 当手移动中间的球时候 ,就像触碰摇杆一样 ,可以向四周摇动 。
    外面的环是一个指示器 当球触发到环的时候,给一个颜色的变化,让用户知道自己将朝哪一个方向运动 。
这里写图片描述
  • 下面是一个围棋场地 用来感受自己在环境中的移动。

  • 摄像机 包括controller都在一个游戏物体上面 也很好移植 ,下面就一步步来展示这个的制作过程 。

3d摇杆的基本框架

这里写图片描述
  • 首先建立一个MianPivot的空物体 它代表着游戏人物 到时候可以自己设定 。
  • 其下呢 ,一个是VR视角 可以选用官方的LMHeadMountedRig 这个预制体 。
  • 另一个就是我门要做的这个摇杆了 。
  • 摇杆分为三部分 BoundaryRing 就是外面的一个指示环 。root是一个中心点 用来归位 sphere是一个移动的球,用手来触碰它 让它在空间中移动,实现摇杆效果 。

实现思路

  • 看过摇杆的自动归位 ,一开始是拿弹簧力来实现,但是弹簧回弹的惯性极其的难掌控 ,来回摆动简直让人抓狂 。于是, 果断放弃了unity的弹簧力 改自己写插值移动 ,让它在手不触碰他的时候,就让它移动到root这个点 。

  • 大概一分析就是这么简单的东西了,但是写起来还是比较烦的,bug极其的多,如何判断手的hold 如何判断移动 回弹的触发条件 等等一系列 做出好的效果还是很不容易的。

  • 还有就是状态的类,球是否是移动的状态 手是否是hold的状态 这个也得写个类来管理

  • 那么物体上呢 ,就很简单了 加一个触发器 当触发的时候 改变他们的状态 。

  • 大家自己建立两个球(一个root点 一个移动的球)并把他们放到一个父物体下面,就可以开始写脚本了。

开始写脚本

  • 先在父类上面建立一个Controller脚本 用来管理游戏移动对象, root和sphere 。
    好 那就先这样写
         public class springController : MonoBehaviour
            {
                [Tooltip("Navigation Controls the walking game object")]
                public GameObject player;
                [Tooltip("Used to move the navigation ball")]
                public GameObject sphere;
                [Tooltip("The root node of the navigation (reset point)")]
                public GameObject root;
                Vector3 sphereposition;
                Vector3 rootposition;
                float speed = 5f;  // 导航球复位的速度
                // Use this for initialization
                void Start()
                {
                    //获取  初始位置
                    sphereposition = sphere.transform.localPosition;
                    rootposition = root.transform.localPosition;
                }
                // Update is called once per frame
                void Update()
                {
                    //更新   导航球  的位置 
                    sphereposition = sphere.transform.localPosition;
                    Movecheck();      //判断并控制player的移动
                    setstop();        //导航球归位
                }

                void setstop()
                {
                    if (HandState.Handstate == HandState.HandStateRelease)
                    {
                        float step = speed * Time.deltaTime;
                        sphere.GetComponent<Rigidbody>().velocity = Vector3.zero;
                        sphere.transform.localPosition = new Vector3(Mathf.Lerp(sphereposition.x, rootposition.x, step), Mathf.Lerp(sphereposition.y, rootposition.y, step), Mathf.Lerp(sphereposition.z, rootposition.z, step));//插值算法也可以
                        if (SphereState.Spherestate == SphereState.Spherestateclosed)
                            sphere.transform.localPosition = new Vector3(0, 0, 0);
                    }
                }
                
                void debugshpereposition()
                {
                    print("sphereposition" + sphereposition);
                }

                void Movecheck()
                {
                    if(HandState.Handstate == HandState.HandStateHold)
                    {
                        startfollow();
                    }
                    else if(SphereState.Spherestate == SphereState.Spherestateclosed)
                    {
                        Stopfollow();
                    }
                }
                
                void startfollow()
                {
                    transform.parent.GetComponent<test>().enabled = true;
                }
                void Stopfollow()
                {
                    transform.parent.GetComponent<test>().enabled = false;
                }
               
                void OnTriggerExit(Collider other)
                {
                    if (issphere(other))
                    {
                        HandState.Handstate = HandState.HandStateRelease;
                        setstop();
                    }
                }

                bool issphere(Collider other)
                {
                    return other.transform.name == "Sphere";
                }
            } 

然后就是root和sphere上面 各有一个triiger判断的类

        public class rootTrigger : SphereState
        {

            Collider currentcollider;
            enum currentstate
            {
                colse,move
            }
            currentstate state; 
            void OnTriggerEnter(Collider collider)
            {
                currentcollider = collider;
                state = currentstate.colse;
                //spheredebug(collider, "in");
            }
            void OnTriggerExit(Collider collider)
            {
                currentcollider = collider;
                state = currentstate.move;
                //spheredebug(collider, "out");
            }
            
            public override bool isclosed()
            {
                if (Isphere(currentcollider))
                {
                    if (state == currentstate.colse)
                        return true;
                    else return false;
                }
                return false;
            }

            private bool Isphere(Collider collider)
            {
                return collider.transform.name == "Sphere" ;
            }

            void spheredebug(Collider collider,string state)
            {
                if (Isphere(collider))
                {
                    print("Sphere"+"   "+ state);
                }
            }
        }

第二个

        public class HnadTrigger : HandState
        {
            int  handcount = 0 ;
            //public Text handcounttext ;
            void OnTriggerEnter(Collider other)
            {
                if(IShand(other))
                {
                   //handdebug(other, "in");
                   handcount++;
                }
            }

            void OnTriggerExit(Collider other)
            {
                if(IShand(other))
                {
                   //handdebug(other, "out");
                   handcount--;
                }
            }
            bool IShand(Collider other)
            {
                string Collidername = other.transform.parent.name;
                if (Collidername == "thumb") return true;
                else if (Collidername == "index") return true;
                else if (Collidername == "middle") return true;
                else if (Collidername == "thumb") return true;
                else if (Collidername == "RigidRoundHand_L" || Collidername == "RigidRoundHand_R") return true;
                else return false;
            }
            void handdebug(Collider other, string state)
            {
                print("hand" + state+" ++++++++++++" + Time.time);
            }
            
            public override bool ishold()
            {
                //print(" ishold() child ");
                //handcounttext.text = " count"+handcount;
                if (handcount != 0)
                {
                    //print("is  hold");
                    return true;
                }
                return false;
            }
        }
  • 这个hold的状态也是搞了我很久 没办法处理判断hold和release两个状态
    最后想到的用碰撞器的数量来判断吧 ,当进入trigger就加加 出去就减减 最后数量为0 那么就是松开状态了

  • 仅供参考

  • 状态脚本

        public class HandState : MonoBehaviour {

            public static int HandStateHold = 0 ;
            public static int HandStateRelease = 1;

            public static int Handstate = HandStateRelease ;
            public virtual bool ishold()
            {
                
                return true;
            }
            void setvalue()
            {
                if (ishold())
                {
                    Handstate = HandStateHold;
                }
                else
                   StartCoroutine(WaitAndPrint(0.5F));
                

            }
            IEnumerator WaitAndPrint(float waitTime)
            {
                yield return new WaitForSeconds(waitTime);
                //等待之后执行的动作 
                Handstate = HandStateRelease;
            }
            void Update()
            {
                setvalue();
            }
        }
        public class SphereState : MonoBehaviour {

            public static int Spherestateclosed = 0;
            public static int Spherestatemove = 1;

            public static int Spherestate = Spherestateclosed;

            public virtual bool isclosed()
            {
                return false;
            }
            void setvalue()
            {
                if (isclosed())
                {
                    Spherestate = Spherestateclosed;
                }
                else
                {
                    Spherestate = Spherestatemove;
                }
            }
            void Update()
            {
                setvalue();
            }
        }
  • 这个就比较简单了 也就不多解释了 有想要源码的 。
    地址 密码 1tcq

4月更新 -重新构建了代码 可能与上面介绍的很大不同 以后会更新介绍 下面是使用方法 -

导航球组件 使用说明

一.demo讲解

  1. cube 挂了一个自动化代码 ,可以生成一个楼梯的demo
  2. plane是地面 。
  3. newNave就是导航球了
  4. FPSController是游戏物体 ,全称第一人称游戏控制器

二 . 导航球组件使用说明

  1. 注意 : 初始化的时候 ,世界坐标必须为000,否侧初始化之后,导航球的位置会有所偏差,且偏差严重 。
  2. Creathuan (c#) : 默认开启 keep default。它用来生成导航球所必要的外部指示器 。需要的参数 ,Bb (一个prefabs 导航球的一个边) Bb1 (root中心点 集成在newNave 中 也许要赋值 ) Parent_ring (生成一圈Bb时,将会把他们移动到该gameobject下面 集成在newNave 中 也许要赋值)
  3. move (c#) : 默认关闭 有代码必要时启动 ,他只有一个start()方法 。用来将newNave移动到某个位置 。它有两个用法 ,你可以指定一个空物体,并给它赋值到init_position_obj参数中 ,这样在启用该脚本时,就可以初始化到这个空物体的位置 ,如果你不指定这个位置,他会根据第二个参数,Vector3 init_position来初始化到这个位置 ,为了避免初始化位置找不到,必须指定一个位置 。
    • 注意 move 这个脚本是由 Creathuan( c# )这个组件初始化环结束后启用 。你也可以自定义启用 。
  4. playercontroller ( c# ) : 默认关闭 ,keep default 。这个组件由代码启动。只需在没有赋值的情况下给他赋值 。 sp(导航球的滑动球 集成在newNave 中 也许要赋值) root(导航球的中心点 集成在newNave 中 也许要赋值 ) ALL (导航球的滑动时,所移动的所有物体 自定义赋值 )speed(导航球滑动时,所有物体移动的速度 默认 0.5f)

子物体介绍

  1. navigation 中心物体 包含一个sphere和一个root
    1. sphere 是leaphand可以滑动的一个球 参数 Sphere (他自己本身 )speed( 复位的速度 ) Root (中心点 )provider(leap )Ct(newNave的creathuan脚本,用来获取半径 )
    2. root是看不见的中心点
  2. ring 指示器 (具体部件 由代码生成 )
  • 好了 ,今天就将怎么多啦 ,祝自己新年快乐。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容