本系列文章将会从一个初学者的脚步去讲解unity和leapmotion协同开发的一系列问题:
基础
环境搭建
场景创建
核心组件使用
sdk使用
脚本编写与功能实现方法
代码优化
UI交互部分
特:本人水平有限所以有错误请及时指正
本人也会引用一些代码如果有所侵犯请联系删除
自2015.11.22开始周更
首先我们必须对一些核心的脚本进行熟悉 明白他们的工作原理 :
1.重中之重simple.cs
作为官方编写的文档 几乎出现了所有的api 函数所有的返回数据 所有的方法 除去一些需要自己经验的写法 基本全部囊括其中了 但是 以下代码 基本见不到了simple.cs的影子
鉴于是教程 那我的定位是代码讲解 采取分立介绍最后汇总 不是铺陈代码
tip :
以下代码片段都不是直接复制粘贴就能运行的 小白还是回去看我之前的内容
以下代码片段都不是直接复制粘贴就能运行的 小白还是回去看我之前的内容
以下代码片段都不是直接复制粘贴就能运行的 小白还是回去看我之前的内容
头文件部分 因为设计到了一些list 或是一些UI绘制 所以头文件显得多了一些(当然以后会更多)
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System;
using Leap;
using System.Collections.Generic;
在脚本类中的定义工作 (以后的脚本基本基于以下定义工作,如不作特殊说明则默认为以下定义)
public HandController hc; //控制器
private HandModel hm; //手部模型
public GameObject cubePrefab; //可不定义
public Vector squizehand; //紧握得手vector
private GameObject cube = null;
Frame currentFrame = null;//定义当前帧
Frame lastFrame = null;//上一帧
函数部分
借助返回的数据 我们可以设计更多的手势 比如在大量测试后 我们发现35左右的值在leap设计中是关键的参数所以:
bool squize(float radius)//判断手势是否为握持 阀值为35
{
if (radius < 37)
return true;
else
return false;
}
另外 如果你有自己的想法 或是对返回值有所要求or不想用官方给的东西 那么可以自己写一个判断双手距离的函数:
double distance(Vector a,Vector b)//双手距离判断 退出机制触发阀值为35
{
double c=Math.Sqrt (Math.Pow ((a.x-b.x), 2) + Math.Pow ((a.y-b.y), 2) + Math.Pow ((a.z-b.z), 2));
Debug.Log ("TWO hand distance="+c);
return c;
}
以上两个非常简单的函数共同使用 就诞生了更多的用法 我们后续会介绍 并且经过测试 其稳定性甚至要高于某些官方的预制手势
那好既然说道预制手势 下面我们就来讨论一下 预制手势的用法:
首先需要在start()中定义 打开预制手势:
hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPECIRCLE); //环绕
hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPESWIPE); //挥动
hc.GetLeapController().EnableGesture(Gesture.GestureType.TYPE_SCREEN_TAP); // ?
hc.GetLeapController ().EnableGesture (Gesture.GestureType.TYPEKEYTAP); //点击
hc.GetLeapController ().EnableGesture (Gesture.GestureType.TYPEINVALID); //无效
在此之后我们就可以通过foreach来调用各种预制手势 但说实话 相比起来 预制手势能完成的工作实在是太少了
foreach (Gesture g in gestures) {
Debug.Log (g.Type);
//----code----//
//此中循环为调取手势列表中预制的手势
//leap在API中预置了手势即:
//Gesture.GestureType.TYPESWIPE
//Gesture.GestureType.TYPE_SCREEN_TAP
//Gesture.GestureType.TYPEKEYTAP
//Gesture.GestureType.TYPECIRCLE
//等
//以此预制手势来实现操作则在此编码
}
以上为预制手势使用的基本框架 下面就是预制手势完成的小demo : 返回发生keytap手势时手的位置
foreach (Hand hand in frame.Hands) {
foreach (Gesture g in gestures) {
Debug.Log (g.Type);
if (g.Type == Gesture.GestureType.TYPEKEYTAP) {
Debug.Log("tip position"+hand.PalmPosition);
}
}
}
那么讲完了预制手势,我们来讲讲返回值。首先,既然要完成手势的设计,能获取各种方法的返回值才是最重要的:
public void OnFrame (HandController controller)
{
//获取基本信息
Frame frame = controller.GetFrame(); //当前帧
Frame lastframe = controller.getlastframe ();//之前帧 星标1后续会重点讲这里记住
Debug.Log ("Frame id: " + frame.Id //帧ID
+ ", timestamp: " + frame.Timestamp //帧时间戳:从捕捉开始经过了多少毫秒
+ ", hands: " + frame.Hands.Count //有几只手
+ ", fingers: " + frame.Fingers.Count //有几只手指
+ ", tools: " + frame.Tools.Count //有几个工具
+ ", gestures: " + frame.Gestures ().Count); //手势计数
Debug.Log ("lastFrame id: " + lastframe.Id
+ ", lasttimestamp: " + lastframe.Timestamp
+ ", lasthands: " + lastframe.Hands.Count
+ ", lastfingers: " + lastframe.Fingers.Count
+ ", lasttools: " + lastframe.Tools.Count
+ ", lastgestures: " + lastframe.Gestures ().Count);
}
以上这个方法清楚的显示了leap代码构建的逻辑:
控制器下有一个记录每帧数据的容器 我们从每个数据帧中获得我们想要的信息 通过和之前帧对比来达到我们的目的
如果我们想启用以上函数打印一些基本信息
OnFrame(hc);//平时不必调用
(至于怎么修改获取容器中的值 我们稍后就会讲到 所以大家先看我讲的东西)
frame :就是我们所说的帧 之下的方法也就是在一帧之内我们能得到的 所有工作基本基于 frame展开 那么就是说 基本工作的展开都是从定义帧开始
Frame frame = hc.GetFrame ();
Frame lastframe = hc.getlastframe ();
this.currentFrame = hc.GetFrame ();
GestureList gestures = this.currentFrame.Gestures ();
在此基础上我们后续的工作才能展开 以下为在左手右手基础上的 代码框架
Vector lefthand = null;
Vector righthand = null;
bool lefthandexist = false;//判断左右手是否在场景中存在
bool righthandexist = false;
foreach (var h in hc.GetFrame().Hands) {
if (h.IsLeft) {
lefthandexist=true;
Debug.Log ("lefthand exist? =" + lefthandexist);
lefthand = h.PalmPosition;
//____code____//
/* 此中h即为左手,可按照手的一切操作方式来编码 */
}
if (h.IsRight) {
righthandexist=true;
Debug.Log ("righthandexist? =" + righthandexist);
righthand=h.PalmPosition;
foreach (Finger finger in righthand.Fingers) {
Finger.FingerType type=finger.Type();
Debug.Log (" Finger id: " + finger.Id
+ ", " + finger.Type().ToString()
+ ", length: " + finger.Length
+ "mm, width: " + finger.Width + "mm");
if(type==Finger.FingerType.TYPE_INDEX)// 手指类型的判断
Debug.Log("tip position="+finger.TipPosition+
"tip direction="+finger.Direction);
//____code____//
}
if(lefthandexist&&righthandexist)
{
//____code____//
//如果左右手同时存在情况下的代码放在如下
}
}
以上基本为手势部分代码设计框架
还有很多内容没法全部细讲 那么我就直接贴上来代码了
/*控制台输出debug。log与using system之中 console。writeline用法相同
* 代码使用foreach来循环以达到返回所有手部数据的要求因此设计中可以带来更多设计
* keytap可用来点选
* type circle可用来旋转目标
* 挥舞可用来滑动菜单
* UI检测方式可用跟踪手的位置 如果:palm_position<e(UI模块范围)&&gesture==keytap 则生成交互
*
* 在此基础上改变交互规则:
*
* 以下为可收集数据中的注释 全可在以上对帧的循环下执行
* WriteLine ("Frame id: " + frame.Id
+ ", timestamp: " + frame.Timestamp
+ ", hands: " + frame.Hands.Count
+ ", fingers: " + frame.Fingers.Count
+ ", tools: " + frame.Tools.Count
+ ", gestures: " + frame.Gestures ().Count);
* //pass
* WriteLine (" Hand id: " + hand.Id
+ ", palm position: " + hand.PalmPosition);
// Get the hand's normal vector and direction
Vector normal = hand.PalmNormal;
Vector direction = hand.Direction;
// 计算手的角度 翻滚,偏离角
WriteLine (" Hand pitch: " + direction.Pitch * 180.0f / (float)Math.PI + " degrees, "
+ "roll: " + normal.Roll * 180.0f / (float)Math.PI + " degrees, "
+ "yaw: " + direction.Yaw * 180.0f / (float)Math.PI + " degrees");
// 获得胳膊的骨骼
Arm arm = hand.Arm;
WriteLine (" Arm direction: " + arm.Direction
+ ", wrist position: " + arm.WristPosition
+ ", elbow position: " + arm.ElbowPosition);
// 获得手指 foreach也可嵌套
foreach (Finger finger in hand.Fingers) {
SafeWriteLine (" Finger id: " + finger.Id
+ ", " + finger.Type().ToString()
+ ", length: " + finger.Length
+ "mm, width: " + finger.Width + "mm");
// 获得手指的骨骼
Bone bone;
foreach (Bone.BoneType boneType in (Bone.BoneType[]) Enum.GetValues(typeof(Bone.BoneType)))
{
bone = finger.Bone(boneType);
SafeWriteLine(" Bone: " + boneType
+ ", start: " + bone.PrevJoint
+ ", end: " + bone.NextJoint
+ ", direction: " + bone.Direction);
}
}
*
*
*
* ——————————————————————————————————————————————————————————————————
* 以下为prefab手势的扩展与详解
*
* 旋转手势 顺时针与逆时针的判断规则:
* case Gesture.GestureType.TYPE_CIRCLE:
CircleGesture circle = new CircleGesture (gesture);
// Calculate clock direction using the angle between circle normal and pointable
String clockwiseness;
if (circle.Pointable.Direction.AngleTo (circle.Normal) <= Math.PI / 2) {
//Clockwise if angle is less than 90 degrees
clockwiseness = "clockwise";
} else {
clockwiseness = "counterclockwise";
}
* 计算旋转手势转过的角度:
* float sweptAngle = 0;//初始化角度为零
// 计算从上一帧到这一帧的角度
if (circle.State != Gesture.GestureState.STATE_START) {
CircleGesture previousUpdate = new CircleGesture (controller.Frame (1).Gesture (circle.Id));
sweptAngle = (circle.Progress - previousUpdate.Progress) * 360;
}
WriteLine (" Circle id: " + circle.Id
+ ", " + circle.State
+ ", progress: " + circle.Progress
+ ", radius: " + circle.Radius
+ ", angle: " + sweptAngle
+ ", " + clockwiseness);
*
*
*
*/
手势脚本部分引路工作基本完成
下面介绍另一个比较重要的脚本
2.HandController.cs
这个脚本是leapmotion核心之一 上面脚本中多数方法 从handcontroller这个脚本中调用所以
为了方便与满足我们的开发需求 对这个脚本的改动是最方便的 但是鉴于接近500line的体量 我不可能一一讲请 所以挑重点:也就是我们刚刚所提到的 frame
public Frame GetFrame() {
if (enableRecordPlayback && recorder_.state == RecorderState.Playing)
return recorder_.GetCurrentFrame();
return leap_controller_.Frame();
}
也就是说 我们使用的get frame() 原型在这里 所做的也就是从更深层的leap_controller()中获取
但是 我们细心就会发现 这是个方法 所以 我们可以举一反三
public Frame getlastframe(int i){return leap_controller_.Frame (i);}
通过这个定义 我们就可以在今后的脚本中直接使用自己的getlastframe()获取自己想要的帧
如果想要上一帧 那就hc.getlastframe(1)就好了
tips: 如果你想设计效果更好的手势建议去通读handcontroller 以及与之相关有继承关系的脚本
基于本节所讲的设计框架:
我们可以根据所给代码设计一系列的动作 去做一些触发 模拟 控制
至于剩下的内容 多着眼于 代码优化 动作设计
更多的是一些关于渲染器 着色器 触发器 等等的内容
本周内容较多 请多做练习
good bye next week (or next year)