本系列文章将会从一个初学者的脚步去讲解unity和leapmotion协同开发的一系列问题:
基础
环境搭建
场景创建
核心组件使用
sdk使用
脚本编写与功能实现方法
代码优化
UI交互部分
特:本人水平有限所以有错误请及时指正
本人也会引用一些代码如果有所侵犯请联系删除
自2015.11.22开始周更
首先
经过了上周我们 讲解的代码框架 以及设计规则 我们都有了足够的手法去设计一些自己想要的动作 但是 在一番编写之后 你肯定会发现 有很多方面并不能达到你设想的那样 其中涉及的一些很隐性的问题 表述起来比较繁杂 因此 我将会更多的以图片来展示问题 那些比较动态的问题 将会很难理解 所以 你应该尝试着跟着我的描述去做
其次
一些有问题的代码 因为我们的开发过程没有版本更替 所以一些有问题的代码没有保存 我只能从比较显眼的问题入手
世界坐标
我们之前提到过的世界坐标的问题 我也附上了代码 想了解的可以去看之前的博客 但是没有细讲 下面着重来说这个问题
根据上次提供的脚本 新建一个脚本将代码复制附在摄像机上 然后新建一个cube 将物体与代码重定义的物体绑定
我们将两个方块一个初始化在(000)点,一个初始化在(020)点
下面我们修改脚本
if(Input.GetKey(KeyCode.UpArrow))
{
Debug.Log ("up");
//this.cube.transform.Translate(0,0.1f,0,Space.World);
this.cube.transform.Translate(0f,0.1f,0f);
}
以此类推 将带space.world的脚本注释 将不带spac.world的语句取消注释
下面我们根据所定义的按键来操作 发现上下左右都没问题 下面来旋转
同样的旋转系数 同样的操作
他们的坐标系根本不一致!!!这时候 我们再去操纵他们前后左右
我们分别点选坐标系就能看出端倪 世界坐标的作用是统一采用世界的坐标系来规定物体的旋转 如果不指定 物体的变换则是根据自身的坐标来的 所以经过旋转的物体 他们的运动在自己的坐标系内 很可能出现和你的坐标系相反 你输入的指令 是左 他却往右跑 正所以 世界坐标是规范物体运动的最好方法
这点其实没什么好展示 因为这是unity开发中常会遇到的问题
脚本执行顺序说明:
先执行所有子脚本中的awake();
在执行所有脚本中的start();
然后执行所有update();
当所有脚本中的update();执行一遍之后,则执行fixupdate();和lateupdate();
即unity中没有多线程概念
且如果在awake中掺杂了关于获取对象的定义
那么脚本之间执行顺序不同就有可能出现空指针
我们从图片上可知 仅仅是触发网格 也是有一个一个简单的网格拼凑而成
与之相比较我们再来看看 模型的网格的结构
所以当我们设计捏取这个动作的时候 触发网格的误差足以让你发疯 所以 如果你想设计捏取动作 那么最好不要牵扯捏取的手指对物体做误差触发
否则 就和官方程序里 那个下国际象棋的demo和 把头拼在机器人身上那个demo体验一样差
我的做法是 每当捏取的两个手指 发生捏取手势的时候 在捏取点生成一个半径固定(不能太大)球体触发网格 每当物体与球体发生触发 求出触发列表 把列表中每一个物体的触发点到球心的距离 求出
最后把最距离最小的那个 物体设为 捏取物体
至于代码 :
nothing ~ i can’t show that
相信这点困扰了一些人 因为鼠标键盘 体系的UI 乃至触屏体系的UI 都是直接集成在unity的
我们可以方便的调用 但是 如果设计这种三维空间内的交互设计还是比较复杂的 如果没有相关文档支持 或者例子来借鉴 那么设计工作确实有些 难以展开
但是我们自己探索中就会发现wight这个东西:
在搜索框中搜索 wight然后加载其中的设置
当我们运行了wight之后就可以发现两种可手势交互的组件 一个是按钮 一个是滑动条
我们仔细观察他们的组织结构 就能清晰的发现 手势设计和 UI的组织结构 设计方法 组织方法
**
经过观察
using UnityEngine;
using System.Collections;
using LMWidgets;
我们可以首先发现 这个系列的脚本有着多层的继承关系
protected override void Start()
{
base.Start();
}
protected override void FixedUpdate()
{
base.FixedUpdate();
UpdateGraphics();
}
再往源头去
using UnityEngine;
using System;
using System.Collections;
namespace LMWidgets
{
public abstract class SliderBase : LeapPhysicsSpring, AnalogInteractionHandler<float>, IDataBoundWidget<SliderBase, float>
{
protected DataBinderSlider m_dataBinder;
// Binary Interaction Handler - Fires when interaction with the widget starts.
public event EventHandler<LMWidgets.EventArg<float>> StartHandler;
// Analog Interaction Handler - Fires while widget is being interacted with.
public event EventHandler<LMWidgets.EventArg<float>> ChangeHandler;
// Binary Interaction Handler - Fires when interaction with the widget ends.
public event EventHandler<LMWidgets.EventArg<float>> EndHandler;
public GameObject upperLimit;
public GameObject lowerLimit;
protected float m_localTriggerDistance;
源头上LMxxxx的这些代码 的含义其实我们不必去细究 如果你没有涉及高层次的变动 复制粘贴 就好了 这种UI设计工作 说起来是个繁杂的工作 因此往往脚本的编写更加要求成体系 结构明了 所以要比功能编写付出充分的耐心 这些问题 不难 但是极其细致 繁复
我们要掌握的是设计思想 即 我们需要 熟悉button/slider的脚本 以及相关UI元素的使用
我们以button为例看一下这个设计该怎么展开
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class ButtonDemoGraphics : MonoBehaviour
{
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;
}
}
}
在以上这个命名空间里 我们可以看出 他的思路是 这个脚本中完成和定义的工作 都是为了 button的绘图做准备 设置颜色 设置样式如何如何
再看下一个
using UnityEngine;
using System.Collections;
using LMWidgets;
public class ButtonDemoToggle : ButtonToggleBase
{
public ButtonDemoGraphics onGraphics;
public ButtonDemoGraphics offGraphics;
public ButtonDemoGraphics midGraphics;
public ButtonDemoGraphics botGraphics;
public Color MidGraphicsOnColor = new Color(0.0f, 0.5f, 0.5f, 1.0f);
public Color BotGraphicsOnColor = new Color(0.0f, 1.0f, 1.0f, 1.0f);
public Color MidGraphicsOffColor = new Color(0.0f, 0.5f, 0.5f, 0.1f);
public Color BotGraphicsOffColor = new Color(0.0f, 0.25f, 0.25f, 1.0f);
public override void ButtonTurnsOn()
{
TurnsOnGraphics();
}
public override void ButtonTurnsOff()
{
TurnsOffGraphics();
}
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);
}
private void UpdateGraphics()
{
Vector3 position = transform.localPosition;
position.z = Mathf.Min(position.z, m_localTriggerDistance);
onGraphics.transform.localPosition = position;
offGraphics.transform.localPosition = position;
Vector3 bot_position = position;
bot_position.z = Mathf.Max(bot_position.z, m_localTriggerDistance - m_localCushionThickness);
botGraphics.transform.localPosition = bot_position;
Vector3 mid_position = position;
mid_position.z = (position.z + bot_position.z) / 2.0f;
midGraphics.transform.localPosition = mid_position;
}
protected override void Start()
{
base.Start();//
}
protected override void FixedUpdate()
{
base.FixedUpdate();//
UpdateGraphics();//
}
}
我们通过鼠标停留就可以发现 这些继承关系的源头
protected override void Start() {
if ( m_dataBinder != null ) {
setButtonState(m_dataBinder.GetCurrentData(), true); // Initilize widget value
}
else {
setButtonState(false, true);
}
}
所以我们可以用这种方法探究整个UIwight的工作机制 在这种情况下 我们可以清楚的认识到 整个UI的脚本资源之间的组织形式 和生动了解 总结编写方法 甚至官方这些 方法可以被我们复用以达到 真正的设计我们的新的 手势交互UI
以上图来看 在世界坐标视野中 我们可以看到 UI都被拥有box cllider 不同形式的UI设计 有着 不同的触发器样式 思路一下子就变得很清晰 在 三维的组织形式下 触发器的 工作形式需要好好调整 普遍是一个拥有冗余度的触发器 并且触发器的位置可以被推动 很好的模拟了现实世界的情况
在2016年三月 左右 我会写一套 手势识别 UI设计框架 所以一些 代码能力稍弱的童鞋 尽请期待
因为这个问题是个可以无限被讨论的问题 所以剩下的 更多的探索工作留给读者 我只是 指引一条方向供大家参考因此 如果有更好的方式也请加入qq群我们一起讨论
343074971
我对这个功能的编写出现了很多延伸版本
一开始的想法是 调用上一帧 和这一帧的双手位置进行计算 差值为正则放大 差值为负则缩小
但是我显然高估了我对执行顺序的把控能力 基本上这个思路需要脚本的控制能力达到炉火纯青的地步才会写对 后期 对数据的检查发现 这个差距实在是太小以及上一帧的数据重复性导致的判断进程根本无法控制
后来又想了一个双手向量相加取模的类似的想法 但是也因为精度等等 原因 失败
经过多次的 实验 测试 记录 我发现 270是一个比较合适的 取值
因此 针对双手的绝对距离 来设计这个缩放手势
最后又发现 这个方式不符合人的操作 又返回对比相对值的思路 最后 更改了 leap的传值方式 在当前帧 计算 前一帧得手的距离 和当前帧 手的距离 进行对比 最后实现了一个 比较好的结果
定义
Hand lefthand=null;
Hand righthand = null;
Hand last_lefthand = null;
Hand last_righthand = null;
bool lefthandexist = false;//判断左右手是否在场景中存在
bool righthandexist = false;
double scale=0f;
double dis=0f;
double last_dis= 0f;
double handdis(Hand a,Hand b)
{
double c=Math.Sqrt (Math.Pow ((a.PalmPosition.x-b.PalmPosition.x), 2) + Math.Pow ((a.PalmPosition.y-b.PalmPosition.y), 2) + Math.Pow ((a.PalmPosition.z-b.PalmPosition.z), 2));
return c;
}
代码
//左右手模块_________________________________________________________________________________________________________
foreach (var h in hc.GetFrame().Hands) {
if (h.IsLeft) {
lefthandexist = true;
lefthand = h;
Debug.Log ("lefthand exist? =" + lefthandexist);
Debug.Log ("lefthand position=" + lefthand.PalmPosition);
Debug.Log ("lefthand squize?=" + squize (lefthand.SphereRadius));
}
if (h.IsRight) {
righthandexist = true;
righthand = h;
Debug.Log ("righthandexist? =" + righthandexist);
Debug.Log ("righthand position=" + righthand.PalmPosition);
Debug.Log ("righthand squize?=" + squize (righthand.SphereRadius));
}
}
if((lefthandexist)&&(righthandexist))
{
if((squize(lefthand.SphereRadius))&&(squize(righthand.SphereRadius)))
{
foreach (Hand hand in frame.Hands)//当前帧的手
{
foreach(Hand lastframe_hand in lastframe.Hands)
{
if(hand.IsLeft){lefthand=hand;}
if(hand.IsRight){righthand=hand;}
if(lastframe_hand.IsLeft){last_lefthand=lastframe_hand;}
if(lastframe_hand.IsRight){last_righthand=lastframe_hand;}
}
}
dis=handdis(lefthand,righthand);
last_dis=handdis(last_lefthand,last_righthand);
if(dis>last_dis)
{Debug.Log("++++++++++");
this.cube.transform.localScale+=(new Vector3(1f,1f,1f));}
if(dis<last_dis)
{Debug.Log("__________");
this.cube.transform.localScale+=(new Vector3(-1f,-1f,-1f));}
}
}
我想说一些总结的设计原则 能保证你 做出来的东西至少是有实用性的:
最后:某宝lp现在90块就一个 别心疼
最最后:
2016年 我们将开新专题新系列:
1.GPU编程系列(OpenGL&Cuda)
2.虚拟现实oculus&leap开发系列
3.手势识别UI设计框架系列
4.unity shader系列