手柄握住阀门旋转

HTC vive设备结合unity开发手柄转动阀门功能

现在需求是:使用手柄握住一个阀门,进行旋转。

如下图:

所有的交互都是要在两个互动的物体之间做文章,VIVE里也是一样,所有要在手柄和阀门两个方面进行“加工”。

先看手柄需要做哪些“加工”

程序现在都在走“短小快”的路线。所以插件VRTK肯定是很好的选择

在手柄上加上VRTK里的交互必要的脚本,这些脚本插件里都有,如下图(蓝色箭头标记为必须加的脚本)。

在本案例中我使用的是Grab的方式进行转动阀的,所以添加的是VRTK_Interact Grab的脚本。也可以根据需求自己修改。修改方法为在Events脚本里有各种触发方式的进行对应按键的选择。如下图:


有了这些脚本手柄的交互功能就已经具备了。只剩下被触碰的物体了。

接受触碰的物体需要进行的准备:

因为需要交互所以collider是必不可少的,还有rigidbody,记住不要勾选重力选项。因为这个要配合下面的VRTK_Knob脚本使用。Device_Value是我自己写的传值脚本,此处只讲转动方法不需要添加该脚本。如下图:


上图中的Clickpress脚本继承了VRTK_InteractableObject脚本,这个脚本也是VRTK插件里的。如果只是单纯实现本案例的转动功能完全可以使用VRTK_InteractableObject脚本。此处要注意转动的原理是采用unity里的铰链的方法,所以在该脚本里有一次选择抓取机制方法的地方要选择Spring_Joint的方法。同样既然是要抓取那肯定要勾选抓取的选项 ,如下图:


如果要添加其他功能,需要继承该脚本重写某些方法。下面的代码是最常用 的几个方法也是我的脚本Clickpress里用的方法:


VRTK_Knob脚本是一个用来转动跟随的脚本。

既然转动那可得要选择转动的物体和轴向,如图:


DIrection就是要转动的轴向,下面的两个参数是转动最大小的限度,step size是转动数值的精确度。

根据需求本案例选择Y轴,如图:


GO物体就是要被旋转的物体,使用时直接拖动过来就可以。这个GO物体原本脚本是没有的,我把原本的脚本稍稍做了加工。


代码如下:

namespaceVRTK

{

    usingUnityEngine;


    publicclassVRTK_Knob : VRTK_Control

    {



        publicGameObject go;


        publicenumKnobDirection

        {

            x, y, z // TODO: autodetect not yet done, it's a bit more difficult to get it right

        }


        publicKnobDirection direction = KnobDirection.x;

        publicfloatmin = 0f;

        publicfloatmax = 100f;

        publicfloatstepSize = 1f;


        privatestaticfloatMAX_AUTODETECT_KNOB_WIDTH = 3; // multiple of the knob width


        privateKnobDirection finalDirection;

        privateQuaternion initialRotation;

        privateVector3 initialLocalRotation;

        privateRigidbody rb;

        privateVRTK_InteractableObject io;


        protectedoverridevoidInitRequiredComponents()

        {

            initialRotation = transform.rotation;

            initialLocalRotation = transform.localRotation.eulerAngles;

            InitRigidBody();

            InitInteractable();

            SetContent(go,false);//cdl

        }


        protectedoverrideboolDetectSetup()

        {

            finalDirection = direction;

            SetConstraints(finalDirection);


            returntrue;

        }


        protectedoverrideControlValueRange RegisterValueRange()

        {

            returnnewControlValueRange() { controlMin = min, controlMax = max };

        }


        protectedoverridevoidHandleUpdate()

        {


            value = CalculateValue();

        }


        privatevoidInitRigidBody()

        {

            rb = GetComponent();

            if(rb == null)

            {

                rb = gameObject.AddComponent();

            }

            rb.isKinematic = false;

            rb.useGravity = false;

            rb.angularDrag = 10; // otherwise knob will continue to move too far on its own

        }


        privatevoidSetConstraints(KnobDirection direction)

        {

            if(!rb) return;


            rb.constraints = RigidbodyConstraints.FreezeAll;

            switch(direction)

            {

                caseKnobDirection.x:

                    rb.constraints -= RigidbodyConstraints.FreezeRotationX;

                    break;

                caseKnobDirection.y:

                    rb.constraints -= RigidbodyConstraints.FreezeRotationY;

                    break;

                caseKnobDirection.z:

                    rb.constraints -= RigidbodyConstraints.FreezeRotationZ;

                    break;

            }

        }


        privatevoidInitInteractable()

        {

            io = GetComponent();

            if(io == null)

            {

                io = gameObject.AddComponent();

            }

            io.isGrabbable = true;

            io.precisionSnap = true;

            io.grabAttachMechanic = VRTK_InteractableObject.GrabAttachType.Spring_Joint;


        }


        privateKnobDirection DetectDirection()

        {

            KnobDirection direction = KnobDirection.x;

            Bounds bounds = Utilities.GetBounds(transform);


            // shoot rays in all directions to learn about surroundings

            RaycastHit hitForward;

            RaycastHit hitBack;

            RaycastHit hitLeft;

            RaycastHit hitRight;

            RaycastHit hitUp;

            RaycastHit hitDown;

            Physics.Raycast(bounds.center, Vector3.forward, outhitForward, bounds.extents.z * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);

            Physics.Raycast(bounds.center, Vector3.back, outhitBack, bounds.extents.z * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);

            Physics.Raycast(bounds.center, Vector3.left, outhitLeft, bounds.extents.x * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);

            Physics.Raycast(bounds.center, Vector3.right, outhitRight, bounds.extents.x * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);

            Physics.Raycast(bounds.center, Vector3.up, outhitUp, bounds.extents.y * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);

            Physics.Raycast(bounds.center, Vector3.down, outhitDown, bounds.extents.y * MAX_AUTODETECT_KNOB_WIDTH, Physics.DefaultRaycastLayers, QueryTriggerInteraction.UseGlobal);


            // shortest valid ray wins

            floatlengthX = (hitRight.collider != null) ? hitRight.distance : float.MaxValue;

            floatlengthY = (hitDown.collider != null) ? hitDown.distance : float.MaxValue;

            floatlengthZ = (hitBack.collider != null) ? hitBack.distance : float.MaxValue;

            floatlengthNegX = (hitLeft.collider != null) ? hitLeft.distance : float.MaxValue;

            floatlengthNegY = (hitUp.collider != null) ? hitUp.distance : float.MaxValue;

            floatlengthNegZ = (hitForward.collider != null) ? hitForward.distance : float.MaxValue;


            // TODO: not yet the right decision strategy, works only partially

            if(Utilities.IsLowest(lengthX, newfloat[] { lengthY, lengthZ, lengthNegX, lengthNegY, lengthNegZ }))

            {

                direction = KnobDirection.z;

            }

            elseif(Utilities.IsLowest(lengthY, newfloat[] { lengthX, lengthZ, lengthNegX, lengthNegY, lengthNegZ }))

            {

                direction = KnobDirection.y;

            }

            elseif(Utilities.IsLowest(lengthZ, newfloat[] { lengthX, lengthY, lengthNegX, lengthNegY, lengthNegZ }))

            {

                direction = KnobDirection.x;

            }

            elseif(Utilities.IsLowest(lengthNegX, newfloat[] { lengthX, lengthY, lengthZ, lengthNegY, lengthNegZ }))

            {

                direction = KnobDirection.z;

            }

            elseif(Utilities.IsLowest(lengthNegY, newfloat[] { lengthX, lengthY, lengthZ, lengthNegX, lengthNegZ }))

            {

                direction = KnobDirection.y;

            }

            elseif(Utilities.IsLowest(lengthNegZ, newfloat[] { lengthX, lengthY, lengthZ, lengthNegX, lengthNegY }))

            {

                direction = KnobDirection.x;

            }


            returndirection;

        }


        privatefloatCalculateValue()

        {

            floatangle = 0;

            switch(finalDirection)

            {

                caseKnobDirection.x:

                    angle = transform.localRotation.eulerAngles.x - initialLocalRotation.x;

                    break;

                caseKnobDirection.y:

                    angle = transform.localRotation.eulerAngles.y - initialLocalRotation.y;

                    break;

                caseKnobDirection.z:

                    angle = transform.localRotation.eulerAngles.z - initialLocalRotation.z;

                    break;

            }

            angle = Mathf.Round(angle * 1000f) / 1000f; // not rounding will produce slight offsets in 4th digit that mess up initial value


            // Quaternion.angle will calculate shortest route and only go to 180

            floatvalue = 0;

            if(angle > 0 && angle <= 180)

            {

                value = 360 - Quaternion.Angle(initialRotation, transform.rotation);

            }

            else

            {

                value = Quaternion.Angle(initialRotation, transform.rotation);

            }


            // adjust to value scale

            value = Mathf.Round((min + Mathf.Clamp01(value / 360f) * (max - min)) / stepSize) * stepSize;

            if(min > max && angle != 0)

            {

                value = (max + min) - value;

            }


            returnvalue;

        }

    }

}


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