zoukankan      html  css  js  c++  java
  • LEAPMOTION开发UI专题(1)


    非常

    抱歉的是,之前说的LEAP/UI框架设计可能仅仅有两篇 由于个人时间实在是不同意 这个问题假设展开去写的话 那么说写本书都是不为过的 且由于内容改动非常是杂乱 所以我第一篇文章用来介绍LEAP预置UI的结构第二篇用来讲How to design&build~


    鉴于直接涉及交互问题 因此这篇文章的受众显得非常尴尬 可是相信 认真依照我之前博客学习的同学都能够理解当中的意思

    关于leap这个东西 我在第一篇文章中就提到过 ——just a toy.
    所以仅仅是用来开发的练手,根本别指望交互效果能够非常好


    依据我的经验来讲 手势识别的交互UI依据交互方式大概分为三种

    1。触发式操作

    2。手势操作(下一期)

    3,映射性操作(下一期)

    所以 我从最主要的触发操作ui開始入手


    预警:这篇文章very very long~~

    入门:

    从leap coreAsset当中找到

    widght这个目录
    widght
    那先找个demo执行一下吧

    SENCES

    下的执行一下咯~

    效果

    好 我们就由这个開始 unity UI和leap交互的前导
    首先 我们来熟悉 官方预设的四种UI样式 各自是:

    dial滚轮菜单

    scrolltext可滑动的字体

    slider滑动条

    toggle buttonutton


    sence


    当我们把这四种prefab拖入场景加以调整

    再加上控制器 就算已经完整地展示了 全部的 官方预制形式

    那么我们就来一个一个说吧

    1.最简单的scrolltext可滑动的字体

    这个组件的主要控制过程在这里
    scrollhandle
    scroll


    我们能够看到他的结构相当简单 boxcollider检測触发 文本上下边界 和显示框上下边界 当text的上边界高于显示框的时候会以一个速度贴合回来 而且由于使用localposition 这个组件的工作不会由于坐标的颠倒而出错

     protected virtual void ResetPivots()
        {
          m_pivot = transform.localPosition;
          if (m_target != null)
            m_targetPivot = transform.parent.InverseTransformPoint(m_target.transform.position);
        }

    text


    针对 TEXT组件的改动 我们能够直接在inspectors中改动文字等等操作 而直接生成一个相似阅读器之类的应用

    在脚本中我们能够看到来龙去脉

    首先是这个类LeapPhysicsBase

    相信有一定基础的小伙伴都知道 既然 设置了触发器 那么肯定会有 检測函数

     protected virtual void OnTriggerEnter(Collider collider)
        {
        //检測是否是手
          if (m_target == null && IsHand(collider) && State != LeapPhysicsState.Disabled)
          {
    
            State = LeapPhysicsState.Interacting;
            m_target = collider.gameObject;
            ResetPivots();
          }
        }
    
    
        protected virtual void OnTriggerExit(Collider collider)
        {
    
          if (collider.gameObject == m_target)
          {
            State = LeapPhysicsState.Reflecting;
            m_target = null;
          }
        }
    
    

    以上两个函数清晰的写了触发执行的过程 那既然是检測不仅仅是要推断是否出发 还要对造成出发的对象进行推断 是否为手于是调用了以下这个函数

        private bool IsHand(Collider collider)
        {
          return collider.transform.parent && collider.transform.parent.parent &&       collider.transform.parent.parent.GetComponent<HandModel>();
        }

    以上三个部分共同工作。就生成了最主要的触发事件

    而整个组建的状态更改 控制 识别都放在一个FixedUpdate();里面

     protected virtual void FixedUpdate() 
        {
          if (m_target == null && State == LeapPhysicsState.Interacting)
          {
            State = LeapPhysicsState.Reflecting;
          }
    
          switch (State)
          {
            case LeapPhysicsState.Interacting://交互
              ApplyInteractions();
              break;
            case LeapPhysicsState.Reflecting://反映
              ApplyPhysics();
              break;
            case LeapPhysicsState.Disabled://无
              break;
            default:
              break;
          }
          ApplyConstraints();
        }

    代码清晰易懂 对组件的3种状态做了规整的编写 代码结构十分清晰

    public enum LeapPhysicsState
      {
        Interacting, // Responsible for moving the widgets with the fingers
        Reflecting, // Responsible for reflecting widget information and simulating the physics
        Disabled // State in  which the widget is disabled
      }
    

    在不同的状态下 相应不同的执行过程 非常规整、 值得一提的是 leap官方的代码风格非常适合我们去细致钻研 当中不乏一些亮点 对于我们来说非常值得借鉴 对于没有形成良好代码风格的新手来说十分值得学习

     protected virtual void Awake()
        {
          if (GetComponent<Collider>() == null)
          {
            Debug.LogWarning("This Widget lacks a collider. Will not function as expected.");
          }//碰撞其检測与error/warning输出
        }

    在这个基类的脚本中 我们基本了解了这个组件的执行机理以下就来看第二个脚本ScrollBase

    这个脚本中有几个重要的常数
    1。弹簧力(SnapSpringForce);
    2。阻力(drag);
    3,交互比例(InteractionScale)。


    这几个參数决定着你出发这个组件并滑动之后 多久能停下来 惯性执行的时间 等等效果
    当中甚至运用到了一些公式运算

    m_dampingForce = Mathf.Sqrt(4.0f * SnapSpringForce);

    (阻尼/力)
    相似这样的运算 但最重要的还是定义了文本组件的上下界和显示框的上下界
    text

    我们能够清晰地看到 在这个组件的下方 文明本下界要长的多


    特效t

    文本的属性来看 我们也能够清晰地看到纵横比也能够改动文字

    而从脚本层面我们能够看到很多其它的这样的继承关系

    这是基类中的

     protected virtual void ResetPivots()
        {
          m_pivot = transform.localPosition;
          if (m_target != null)
            m_targetPivot = transform.parent.InverseTransformPoint(m_target.transform.position);
        }

    这是scrollbase中的

     protected override void ResetPivots() {
        base.ResetPivots();
        m_contentPivot = ContentTransform.localPosition;
      }
    

    最后不得不佩服的是 为了实现 惯性这样的细微的操作感代码的复杂程度高了不少 以至于催生了非常多的计算方法

      //计算一维弹簧力
       protected float calculate1DSpringForce(float offsetVector) {
        float springForce = offsetVector * SnapSpringForce;
        float dampingForce = m_dampingForce * (m_velocity);
        return springForce - dampingForce;
      }

     protected float calculateOverrunMagnitude() {
     //计算超过量(文本边界超过显示边界的量)
        float overrunDistance = 0.0f;
    
        // Put all positions in object space.
        Vector3 localContentTop = transform.InverseTransformPoint(ContentTopBound.position);
        Vector3 localContentBottom = transform.InverseTransformPoint(ContentBottomBound.position);
        Vector3 localContainerTop = transform.InverseTransformPoint(ContainerTopBound.position);
        Vector3 localContainerBottom = transform.InverseTransformPoint(ContainerBottomBound.position);
    
        if (localContentTop.y < localContainerTop.y) {
          overrunDistance = localContainerTop.y - localContentTop.y;
        }
        else if (localContentBottom.y > localContainerBottom.y) {
          overrunDistance = localContainerBottom.y - localContentBottom.y;
        }
    
        return overrunDistance;
      }
    

    至于一些 基类中的状态推断循环所採用的应用方法 我仅仅贴一个样例

    ApplyInteractions();

    基类中定义了它的抽象方法在状态循环中 当状态为

    case LeapPhysicsState.Interacting:

    时调用了ApplyInteractions(); 而在scrollbase类中 整个重载了这种方法

    
      protected override void ApplyInteractions() {
    
        Vector3 targetInteractorPositionChange = transform.parent.InverseTransformPoint(m_target.transform.position) - m_targetPivot;
        targetInteractorPositionChange *= InteractionScale; 
        targetInteractorPositionChange.x = 0.0f;
        targetInteractorPositionChange.z = 0.0f;
        Vector3 contentCurrentPosition = ContentTransform.localPosition;
        Vector3 newContentPosition = m_contentPivot + targetInteractorPositionChange;
        Vector3 velocity = (newContentPosition - contentCurrentPosition) / Time.deltaTime;
        m_velocity = velocity.y;
    
        ContentTransform.localPosition = newContentPosition;
      }

    在这样的结构下  我们事实上假设仅仅是想实现一个交互  那么还是非常easy的  可是假设想加强操控感受 改善交互效果 能够说难度非常大 已经超出了新手的能力范围  由于当中涉及到了太多的 UI交互设计技巧  经验和逻辑。


    OK~ NEXT

    2.button

    button
    照旧 我们先看他的构成 非常清晰有没有
    四种图像分别相应

    打开状态
    关闭状态
    转移状态
    通用元素

    接下来我们抛开他的素材去看代码
    这两个类的规模就要小非常多了 由于没有非常复杂的交互优化 整个组件非常清爽
    ButtonDemoGraphics脚本中

    public void SetActive(bool status)
        {
            Renderer[] renderers = GetComponentsInChildren<Renderer>();
            Text[] texts = GetComponentsInChildren<Text>();
            Image[] GUIimages = GetComponentsInChildren<Image>();
            foreach (Renderer renderer in renderers)
            {
                renderer.enabled = status;
            }
            foreach(Text text in texts){
                text.enabled = status;
            }
            foreach(Image image in GUIimages){
                image.enabled = status;
            }
    
        }

    设置激活的方法显得非常干练包括对材质,图片。文字的遍历

    public void SetColor(Color color)
        {
            Renderer[] renderers = GetComponentsInChildren<Renderer>();
            Text[] texts = GetComponentsInChildren<Text>();
            Image[] GUIimages = GetComponentsInChildren<Image>();
            foreach (Renderer renderer in renderers)
            {
                renderer.material.color = color;
            }
            foreach (Text text in texts){
                text.color = color;
            }
            foreach(Image image in GUIimages){
                image.color = color;
            }
        }

    包括对颜色更改也是

    而在对此基类调用的时候ButtonDemoToggle类这样的代码结构异常的清晰明了(真的好棒!!啊)

     private void TurnsOnGraphics()
      {
        onGraphics.SetActive(true);
        offGraphics.SetActive(false);
        midGraphics.SetColor(MidGraphicsOnColor);
        botGraphics.SetColor(BotGraphicsOnColor);
      }
    
      private void TurnsOffGraphics()
      {
        onGraphics.SetActive(false);
        offGraphics.SetActive(true);
        midGraphics.SetColor(MidGraphicsOffColor);
        botGraphics.SetColor(BotGraphicsOffColor);
      }

    假设你想使用这个button 做一个状态推断的话

    
      public override void ButtonTurnsOn()
      {
        TurnsOnGraphics();
      }
    
      public override void ButtonTurnsOff()
      {
        TurnsOffGraphics();
      }

    写在这两个方法下 的话 就能达成你的目的啦
    比方 我想button显示关闭的时候的时候程序暂停执行 那就

    
    
      public override void ButtonTurnsOff()
      {
        TurnsOffGraphics();
        Debug.Break();
      }

    至于替换原本的ON/OFF的UI纹理 那就不用我教了 相信 能看到这里的都会操作 我们去怎样设计按键触发的过程
    就有了一个清晰的方案

    自己定义button样式替换元素 ->继承以重用脚本设计->自己定义触发操作->完毕

    总结:

    button为什么这里就总结了?
    答:button的类继承关系非常复杂 可是 组件本身有着非常好的可改动性 重用性 因此 对于这样的良心 组件 我们也不用想着从 脚本角度去改动 替换button的外观 和样式 保留官方这样的成熟的继承风格 对于我们生成 的软件的稳定性至关重要;

    public abstract class ButtonToggleBase : ButtonBase, BinaryInteractionHandler < bool > , IDataBoundWidget < ButtonToggleBase, bool>

    任意感受一下这个继承

    3.滑动条(slider)

    先看图
    slider


    我们能够看到 为了达成滑动条这个组件 所须要的 步骤 就多得多了
    尽管在hierarchy里面全部展开以后非常吓人的样子可是事实上 仅仅有三类

    1.top
    2.line
    3.dot

    我们先从top開始看
    事实上 他就是按键的马甲而已,SliderDemoGraphics负责控制这个按键的图像
    top

    top的结构从这个拆分能清晰的看清每一个部分
    toplayer:圆点button中心
    midlayer:填充材质(slidersecondary)
    botlayer:选定高亮边框

    所以在这个类当中 我们会看到button中出现过的套路

     public void SetActive(bool status)
      {
        Renderer[] renderers = GetComponentsInChildren<Renderer>();
        foreach (Renderer renderer in renderers)
        {
          renderer.enabled = status;
        }
      }
     public void SetColor(Color color)
      {
        Renderer[] renderers = GetComponentsInChildren<Renderer>();
        foreach (Renderer renderer in renderers)
        {
          renderer.material.color = color;
        }
      }

    Top的构成非常easy 可是整个滑动条还是比較复杂的 就是由于这个类SliderDemo的继承SliderBase又继承于 LeapPhysicsSpring 这样的继承关系导致我们想从自上而下的改动功能变得不是那么easy
    而我们仅仅能从底层向上寻找
    Sliderdemo:

      protected override void sliderPressed()
      {
      //按下
        base.sliderPressed();
        PressedGraphics();
      }
    
      protected override void sliderReleased()
      {
      //释放
        base.sliderReleased();
        ReleasedGraphics();
      }

    这两个函数描写叙述了按下后和释放后的指令 所以从这里来看 我们能够把一切我们想要的滑动条的button触发释放 来激活的事件 写在这两个方法里

    最重要的检查触发被放在了高一级的类Sliderbase中的CheckTrigger()方法:

    private void CheckTrigger()
        {
          if (State == LeapPhysicsState.Interacting) { 
          //状态确定
            fireSliderChanged (GetSliderFraction ());
            if (m_dataBinder != null) {
              m_dataBinder.SetCurrentData (GetSliderFraction ());
            }
          }
        }

    而最根本的监听来自于一个诡异的方法

    private void onStateChanged(object sender, EventArg<LeapPhysicsState> arg) {
          if ( arg.CurrentValue == LeapPhysicsState.Interacting ) {
            sliderPressed();//按下
          }
          else if ( arg.CurrentValue == LeapPhysicsState.Reflecting ) {
            sliderReleased();//释放
          }
        }

    EventArg是包括事件数据的类的基类,而onStateChanged()方法中前者是一个对象(事实上这里传递的是对象的引用,如button的click事件则sender就是button,相信有过c#/xaml/winfrom/编程经验得同学都见到过这个使用方法)。后面是包括事件数据的类的基类。
    而在这个代码中sender就是leap中的一个对象 后面的基类将状态參数CurrentValue 表达出来


    讲完了触发那么如今该进一步了
    arg.CurrentValue同一时候将改动State的值 这个值相当于整个类中的状态參量 代表button是否被按下

    而在这个脚本中 还要依据state的值来进行很多其它的操作

     public enum LeapPhysicsState
      {
        Interacting, //手指等  触发button
        Reflecting, //模拟物理特性 从触发被改变回到预置位置
        Disabled // 关闭(正常)状态
      }

    .Enum 类型是全部枚举类型的抽象基类(它是一种与枚举类型的基础类型不同的独特类型)
    这里用到enum来准确描写叙述状态 使得代码清晰易懂 易于维护

    所以 当我们找到FixedUpdate中的UpdateGraphics()方法后

    private void UpdateGraphics()
      {
        float handleFraction = GetHandleFraction();
        Vector3 topPosition = transform.localPosition;
        topPosition.x = 0f;
        topPosition.y = 0f;
        topPosition.z -= (1.0f - handleFraction) * 0.25f;
        topPosition.z = Mathf.Min(topPosition.z, -0.003f); // -0.003 为保证dots和top永不相交的中间层
        topLayer.transform.localPosition = topPosition;
    
        Vector3 botPosition = transform.localPosition;
        botPosition.x = 0f;
        topPosition.y = 0f;
        botPosition.z = -0.001f;
        botLayer.transform.localPosition = botPosition;
    
        midLayer.transform.localPosition = (topPosition + botPosition) / 2.0f;
    //___________________________________________________________________
        if (activeBar)
        {
          UpdateActiveBar();//激活
        }
        //______________________________________________________________________
        if (numberOfDots > 0)
        {
          UpdateDots();//依据bot的位置推断
        }
      }
    

    在此我们仅仅从Dot展开去讲 由于其它的过程基本上是 八九不离十

    private void UpdateDots()
      {
        for (int i = 0; i < dots.Count; ++i)
        {//dot的数量  依据此位置dot的x坐标 和 top的x轴自身坐标对照推断 来逐个绘制 小于高亮  大于常亮
          if (dots[i].transform.localPosition.x < transform.localPosition.x)
          {
            Renderer[] renderers = dots[i].GetComponentsInChildren<Renderer>();
            foreach (Renderer renderer in renderers)
            {
              renderer.material.color = DotsOnColor;
              renderer.material.SetFloat("_Gain", 3.0f);//高亮
            }
          }
          else
          {
            Renderer[] renderers = dots[i].GetComponentsInChildren<Renderer>();
            foreach (Renderer renderer in renderers)
            {
              renderer.material.color = DotsOffColor;
              renderer.material.SetFloat("_Gain", 1.0f);//常亮
            }
          }
        }
      }

    而我们在先前的inspector中也看到了DotsOnColor/DotsOffColor
    dot

    在其它部分的调用中 和Dots的绘制如出一辙 基本都是结合状态參量来进行推断 比如

     public void SetWidgetValue(float value) {
          if ( State == LeapPhysicsState.Interacting || State == LeapPhysicsState.Disabled ) { return; } // 使得状态在交互过程中稳定
          SetPositionFromFraction (value);
        }

    那么最后来看这个LeapPhysicsBase类中state终极目的:

        protected virtual void FixedUpdate() 
        {
          if (m_target == null && State == LeapPhysicsState.Interacting)
          {
            State = LeapPhysicsState.Reflecting;
          }
    
          switch (State)
          {
            case LeapPhysicsState.Interacting://交互状态
              ApplyInteractions();
              break;
            case LeapPhysicsState.Reflecting://从交互状态返回正常状态
              ApplyPhysics();
              break;
            case LeapPhysicsState.Disabled://关闭(正常)状态
              break;
            default:
              break;
          }
          ApplyConstraints();
        }

    这是一个大写的清晰明了 之前对State枚举类型在这里一下就亮了
    用Switch来确定状态执行相应状态的方法集

    至此 总分总式的把Solider说完了 已经 洋洋洒洒的说了1w字了 那么 我就继续吧

    滚轮/表盘(Dial)

    照例先看图:
    dial


    从trigger的网格来看就非常的复杂 所以要是自己想设计一个这样的手势操作的UI组件 能够说难度非常
    而且 坦白说 这个组件假设不经过自己定义或者改动非常的华而不实 由于 他的选项实在是太多了 多到你手退出操作区域的时候 都会产生误操作

    那么我们就先从可见外观结构说起

    dial2


    1.picker//选择器
    2.maskpanel//荫罩面
    3.backpanel //背景面

    更进一步:

    dial3

    从这開始就開始有意思了起来

    首先 这是两个collider 小的这个被设置为滚轮上的字在这个区域被显示为高亮(HighLight)
    HilightTextVolume这个类中 清晰的表明了这一点 通过 触发器trigger的三个函数 巧妙的控制了字体的高亮显示

       //进入
       void OnTriggerEnter(Collider other) {
            Text text = other.GetComponentInChildren<Text>();
          if (text == null) { return; }
            text.color = Color.white;
        }
      //保持  
        void OnTriggerStay(Collider other){
            Text text = other.GetComponentInChildren<Text>();
          if (text == null) { return; }
            text.color = Color.white;
            CurrentHilightValue = text.text;
        }
        //离开
        void OnTriggerExit(Collider other) {
          Text text = other.GetComponentInChildren<Text> ();
          if (text == null) { return; }
          text.color = textColor;
        }
    

    这给我们提供了一个设计技巧 对于UI设计提升期的童鞋来说 是个相当实用的模式
    进入->保持->离开
    |________________|

    而之后的用于隐藏的collider原理差点儿一致在DatePickerHideVolume类中:

    
        void OnTriggerEnter(Collider other) {
        Text text = other.GetComponentInChildren<Text> ();
        if (text == null) { return; }
        text.enabled = false;//设置隐藏
        }
    
        void OnTriggerExit(Collider other) {
        Text text = other.GetComponentInChildren<Text> ();
        if (text == null) { return; }
        text.enabled = true;//设置显示
        }

    那个大的collider 被放在转盘的圆心位置 所以转盘总有一半是被隐藏的 这样就保证了前视的时候后面的文字不会对前面的造成干扰

    都是利用触发器来实现 可是还有个

    比較有趣

    的问题 再来看图
    fade

    我们发现 从下到上 文字的透明度越高这是怎么做到的呢?

    首先 定义了一条曲线

    public AnimationCurve FadeCurve;

    curve


    在曲线之后通过曲线来计算 透明值

        float opacityMod = FadeCurve.Evaluate(referenceDotDirection);//计算不透明度模
        float goalOpacity = m_originalLabelOpacity * opacityMod;//目标透明度
    
          foreach(Text textComponent in m_textLabels) {
          //遍历设定
          Color textColor = textComponent.color;
          textColor.a = goalOpacity;//对文字颜色的Alpha通道进行改动
          textComponent.color = textColor;
        }

    讲完了这个外在 再来讲内在的控制部分

    准备好受虐吧 这部分我也不太懂了 所以我尽量说我的理解 有错误请指出

    先是生成label

    public List< string > DialLabels;

    labe


    依据字符串List的数量来生成label 这个数量我们当然能够确定 所以 我们想使用这个控件 的话
    当然 应该使得这个list.count的数量趋近合理 这样不仅使得控件简洁明了 还是得交互变得更easy 准确性更高

     private void generateAndLayoutLabels() {
          float currentLayoutXAngle = LabelAngleRangeStart;
    
          for( int i=1; i<=DialLabels.Count; i++ ) {
            Transform labelPrefab = Instantiate(LabelPrefab, DialCenter.transform.position, transform.rotation) as Transform;
    
            //生成
    
            labelPrefab.Rotate(currentLayoutXAngle, 0f, 0f);
            LabelAngles.Add (-currentLayoutXAngle);     
            labelPrefab.parent = DialCenter;
            labelPrefab.localScale = new Vector3(1f, 1f, 1f);
            Text labelText = labelPrefab.GetComponentInChildren<Text>();
            labelText.text = DialLabels[i - 1];
            DialLabelAngles.Add(DialLabels[i - 1], -currentLayoutXAngle);
            labelText.transform.localPosition = new Vector3(0f, 0f, -DialRadius);
            currentLayoutXAngle = ((Mathf.Abs(LabelAngleRangeStart) + Mathf.Abs(LabelAngleRangeEnd))/(DialLabels.Count)) * -i;
    
            //调整
          }
    
          LabelPrefab.gameObject.SetActive(false); // Turn off the original prefab that was copied.
        }
    

    開始句柄
    更改句柄
    结束句柄

     //模拟交互的句柄
        public event EventHandler<EventArg<int>> ChangeHandler;
        public event EventHandler<EventArg<int>> StartHandler;
        public event EventHandler<EventArg<int>> EndHandler;

    当然触发检測是不可缺少的

            void OnTriggerEnter (Collider other)
            {
              if (target_ == null && IsHand (other)) {
              //像之前一样确定触发体是手
                target_ = other.gameObject;//旋转角
                pivot_ = transform.InverseTransformPoint (target_.transform.position) - transform.localPosition;//旋转轴心
                if (GetComponent<Rigidbody>().isKinematic == false)
                  transform.GetComponent<Rigidbody>().angularVelocity = Vector3.zero;
                interacting_ = true;
                if (dialGraphics)
                  dialGraphics.HilightDial ();
              }
            }
    
            void OnTriggerExit (Collider other)
            {
              if (other.gameObject == target_) {
                EndInteraction ();//结束交互
              }
            }

    从触发检測部分计算出的目标角度和旋转轴心 然后我们找到应用的方法

        protected virtual void ApplyRotations ()
        {
        //旋转至目标角度
          Vector3 curr_direction = transform.InverseTransformPoint (target_.transform.position) - transform.localPosition;
          //轴心
          transform.localRotation = Quaternion.FromToRotation (pivot_, curr_direction) * transform.localRotation;
        }

    而这一切都在基类的FixedUpdate()中执行

        void FixedUpdate ()
        {
          if (target_ == null && interacting_) {
            // 当交互时手已经被销毁
    
            EndInteraction ();//结束交互
          }
          if (target_ != null) {  
            ApplyRotations ();//应用旋转
          }
          ApplyConstraints ();//应用约束以保证交互结束后 返回相应位置
        }

    至此 一些零碎的约束我已经无力再写下去了 就来归纳一下组件共有的一些特性:

    1.触发检測用于检測手和组件的交互 同一时候一般返回 :
    触发初始信息
    触发过程信息
    触发结束信息

    2.约束条件:
    一般含有交互产生的改变量
    交互后依据规则的对改变量的推断
    对推断结果的执行

    3.精巧状态:
    一般含有系统用于初始化的初始量
    对于不产生交互状态下的静态量
    一般为产生触发后约束条件的结果


    至此

    我们讲完了全部四个预置控件的结构与应用方法
    为我们在unity3D引擎中设计 VR/AR环境下的交互逻辑铺平了道路;
    可是非常明显的是 在VR环境中设计交互操作明显有一定的方法论 触发设计。特效设计,逻辑设计每一个部分单独拿出来都能够写一本书 而且在讲求经验的情况下 这和曾经的UI设计相去甚远 平面UI的难度由于操控体系的健全而变得较为easy 但在三维环境下 不管是对手的模拟 还是对现实环境的模拟 都使得UI设计变得没有统一规则
    你能够虚拟出一个平面实现来类触屏操作而套用平面UI的设计方法
    更能够通过虚拟物体来体现虚拟现实技术的优势 这就使得设计变得无限可能
    这两种方式并没有优劣之分 各个应用环境之下 恰当的採用 效果才是最佳的。


    最后 由于这系列文章过长 因此下次更新可能会在较长之后了(都是眼泪)。

    Bye~ See you next month~

  • 相关阅读:
    5.JavaSE之数据类型详解
    4.JavaSE之标识符
    2.Java程序运行机制
    1.HelloWorld 仪式感
    10.安装开发环境
    【模板】后缀数组
    Luogu P3808 【模板】AC自动机(简单版)
    Luogu P3375 【模板】KMP字符串匹配
    LNSY集训
    Luogu P2580 于是他错误的点名开始了 (Trie树模板)
  • 原文地址:https://www.cnblogs.com/yjbjingcha/p/8431421.html
Copyright © 2011-2022 走看看