美文网首页
一种可灵活扩展外设的操作模块的实现方式

一种可灵活扩展外设的操作模块的实现方式

作者: sp958857 | 来源:发表于2018-03-30 15:38 被阅读0次

    背景:程序猿小王在项目中写了一个相机控制模块,通过键盘和鼠标达到第一人称和第三人称相机操控功能。Ok,做完了,很嗨皮。突然有一天。

    • 策划A :小王,日本有个版本需要用GearVR来操作,你改一下吧;
    • 小王 :Ok,没问题,看我加个设备驱动,然后if­else就搞定了;
    • 策划B :小王,香港那边有个版本需要用Htc Vive,要用手柄控制你这边改一改吧;
    • 小王 :你保证这是最后一次?好吧,我再加个设备驱动,然后再else就搞定了;
    • 策划C :小王,这个操控能在普罗米修斯的白板上用吗?
    • 小王 :%¥…1………
    • 主程:小王,你把相机控制模块抽取出来变成一个插件吧!

    小王心想 :这虽然不是什么大问题,如果变成一个单独的dll,可能需要依赖下GearVR和Htc的设备驱动,要是以后策划再“找麻烦”,那岂不是要不停的改动这个动态库,依赖越来越多的dll,越来越多的switch­case语句。有什么方式可以做到满足“开闭原则”,即对于扩展是开放的,对于修改是关闭的。并且这个模块不依赖于任何的第三方设备驱动,毕竟VR发展这么快,新设备会不停的冒出来。

    原来的实现方式

        Vector3 moveDirection = Vector3.zero;
        #if UNITY_STANDALONE_WIN
            moveDirection = ...;//使用Unity的Input类方法
        #endif
        #if UNITY_GEARVR
            moveDirection = ...;//使用Oculus的OVRInput类方法
        #endif
        #if UNITY_HTCVR
            moveDirection = ...;//使用HTC的类方法
        #endif
        …… //使用moveDirection控制相机运动
    

    这里使用宏控制执行逻辑,打包不同设备的包的时候,通过编译宏设置控制逻辑走向。
    缺点1:动态库需要随着设备的增加而更新。
    缺点2:动态库需要依赖除了UnityEngine外的其他设备驱动dll

    理想的使用方式

    • 相机控制动态库只依赖UnityEngine
    • 可以很方便的扩展第三方外设驱动
    • 可以同时使用多种设备同时进行操作,并不互相冲突

    总结下,例如要根据业务需要接入一款新的VR设备,那么首先需要在业务层依赖相机控制dll,以及VR设备的驱动dll;其次在业务层实现一套对接逻辑,告诉业务层使用新的设备来操控相机。至于怎么实现这套对接逻辑,需要仔细思考下。

    实现方式的思考

    参考Unity对输入的设计可以发现,他提供的都是原子级别颗粒的操作接口

        UnityEngine.Input.GetAxisRaw(axis);
        UnityEngine.Input.GetAxis(axis);
        UnityEngine.Input.GetButton(axis);
        UnityEngine.Input.GetButtonDown(axis);
        UnityEngine.Input.GetButtonUp(axis);
    

    再看下,Oculus设备驱动的设计,也可以发现类似的风格:


    这里写图片描述

    最后看下,触屏外设控制插件ControlFrek2的源码,也可以发现类似的接口设计:


    这里写图片描述

    我们在设计的时候保证操作的颗粒级别跟UnityEngine提供的一致,而且外设的操作,确实也不外乎按下,按住,弹起等状态,涉及轴的操作返回浮点数。基于这点考虑,我们可以设计相机模块的控制类RPGInput

    public static class RPGInput
    {
        public static float GetAxis(string axisName)
        {
            //待实现
        } 
        public static float GetAxisRaw(string axisName)
        {
            //待实现
        } 
        public static bool GetButton(string axisName)
        {
            //待实现
        } 
        public static bool GetButtonDown(string axisName)
        {
            //待实现
        } 
        static public bool GetButtonUp(string axisName)
        {
            //待实现
        }
    }
    

    将原来调用 Input.GetXXX 的地方直接改为 RPGInput.GetXXX 。这样对原来相机控制库的代码改动量也最少。之后,需要有一个外设管理类来协调外设输入与定义。

    外设输入定义与多种外设的关联

    基于之前第三点的考虑(可以同时使用多种设备同时进行操作,并不互相冲突),当使用 RPGInput.GetXXX() ,会根据当前连接的所有外设输入值取一个合适的值(求和?)。也就意味着外设输入定义不仅仅是针对一种外设,而是可能跟多种设备有关联,关系图如下:

    这里写图片描述

    1、可以看到 外设输入定义 需要跟多个外设 关联,把这种关联关系定义为绑定 ,即1个输入定义需要绑定N个外设的某个按键或者操作。
    2、当需要引入一种新的外设的时候,只要实现一个接口或者继承一个类,实现或者复写其中的方法,就可完成这种绑定关系。

    那么这里假设需要几个类来实现绑定关系。

    类功能表

    类名 功能 例子
    RPGAxisDefine 相机操作模块用到的轴定义类 RPGAxisDefine.MouseX对应原来代码中的 "MouseX"
    AxisConfig 轴配置类,关联外设绑定类 与RPGAxisDefine 1对1
    AxisBinding 外设绑定类,外设操作实现类的容器管理类 与AxisConfig1对1
    TargetElem 外设操作实现类,对接具体外设的输入值 与TargetElem1对N

    那么与上图对应后,各类所扮演的角色如图所示

    这里写图片描述
    具体的类图可以设计为:
    这里写图片描述

    代码走向:例如将要获取某个轴的输入值时,AxisConfig中的GetAxis()代码负责将AxisBinding中所有的外设绑定实现类TargetElem中的GetAxis()计算后,再做求和运算,得到最终值。
    遗留问题:
    1、这里只解决一个输入定义,即只有一个轴,需要一个类来管理所有轴
    2、如何添加更多外设,即继承TargetElem后,需要一个接口来把该外设纳入绑定中

    最终类图

    为了解决上述遗留问题,需要再定一个类来管理所有轴的输入定义,并且可以增加/删除自定义外设;

    public class RPGRig : MonoBehaviour
    {
        public static RPGRig Instance;
        public List<AxisConfig> axisconfigs;//初始化后获得所有轴的定义
        //增加自定义外设支持
        public void AddBindingTarget<T>() where T : TargetElem, new()
        {
            axisconfigs.ForEach(axisconfig =>
            {
                TargetElem instance = (TargetElem)Activator.Create
                Instance(typeof (T));
                axisconfig.axisBinding.AddTarget(instance).SetAxis(axisconfig.name);
            });
        } 
        //删除某种自定义外设支持
        public void RemoveBindingTarget<T>() where T : TargetElem,new()
        {
            axisconfigs.ForEach(axisconfig =>
            {
                axisconfig.axisBinding.RemoveTarget<T>();
            });
        }
        public float GetAxis(string axisName)
        {
            AxisConfig s = this.axisconfigs.Get(axisName);
            return ((s != null) ? s.GetAxis() : 0);
        } 
        …
    }
    

    其中GetAxis(string axisName)代码负责从所有轴定义中找到想要的轴配置实例axisConfig。
    那么完整的类图就是如下图所示:

    这里写图片描述

    最终扩展使用方式

    这里以GearVR为例,项目要求使用GearVR触控板前后滑动代替键盘WS前后移动功能。只需要2个步骤
    1、继承TargetElem,并复写其中的方法

    public class TargetElem4GearVR : TargetElem
    {
        public const float SENSITY = 0.005f;
        public override float GetAxisRaw()
        {
            float value = 0.0f;
            if (axis.Equals(RPGAxisDefine.Vertical.Value))
            { 
                // 使用GearVR驱动方法返回值
                Vector2 primaryTouchpad = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad, OVRInput.Controller.Touchpad);
                var gearVRTouchPadX = primaryTouchpad.x;
                var gearVRTouchPadY = primaryTouchpad.y;
                Vector2 primaryRTRpad = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad, OVRInput.Controller.RTrackedRemote);
                var gearVRRTRX = primaryRTRpad.x;
                var gearVRRTRY = primaryRTRpad.y;
                Vector2 primaryLTRpad = OVRInput.Get(OVRInput.Axis2D.PrimaryTouchpad, OVRInput.Controller.LTrackedRemote);
                var gearVRLTRX = primaryLTRpad.x;
                var gearVRLTRY = primaryLTRpad.y;
                return (Mathf.Abs(gearVRTouchPadX) > SENSITY && Mathf.Abs(gearVRTouchPadY) < Mathf.Abs(gearVRTouchPadX) ?
                    (gearVRTouchPadX > 0f ? 1f : -1f) : 0.0f) +
                    (Mathf.Abs(gearVRRTRY) > SENSITY && Mathf.Abs(gearVRRTRX) < Mathf.Abs(gearVRRTRY) ? (gearVRRTRY > 0f ? 1f : -1f) : 0.0f) +
                    (Mathf.Abs(gearVRLTRY) > SENSITY && Mathf.Abs(gearVRLTRX) < Mathf.Abs(gearVRLTRY) ? (gearVRRTRY > 0f ? 1f : -1f) : 0.0f);
            }
            ……
            return value;
        }
    }
    

    2、在相机控制模块初始化后,添加GearVR外设扩展绑定类

    RPGRig.Instance.AddBindingTarget<TargetElem4GearVR>();
    

    相关文章

      网友评论

          本文标题:一种可灵活扩展外设的操作模块的实现方式

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