美文网首页
手柄握住阀门旋转

手柄握住阀门旋转

作者: TomasW | 来源:发表于2018-01-30 16:31 被阅读0次

    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;

            }

        }

    }

    相关文章

      网友评论

          本文标题:手柄握住阀门旋转

          本文链接:https://www.haomeiwen.com/subject/yppczxtx.html