https://blog.csdn.net/sky1466181491/article/details/86628384
准备
根据官方介绍,使用leapmotion需要两个东西:
- Ultraleap Hand Tracking Camera(就是你的手势追踪设备)
- Hand Tracking Software(官方提供的软件)
开发文档
官方DEMO
官方unity插件 走包管理下载也行自己看官方介绍
步骤
Unity Plugin 5.5.0 + 2019.4.30
1.下载并安装官方Hand Tracking Software
2.连上手势追踪设备并启动软件
3.unity工程导入官方插件
- 案例
- 先行版预览案例(不保证支持)
- 先行版预览功能(不保证支持)
- 核心包This includes Core, Interaction Engine, and the Hands Module.
4.Service Provider(类型)预制体拖入-(程序可以连接并获取到追踪数据了)
5.HandModels预制体拖入 (可以运行看到手模型的运动了)
交互
交互三件套:
- InteractionManager :交互功能计算实现管理
- InteractionController :两个实体类:InteractionHand 和 InteractionXRController(XR用) 手的控制器
- InteractionBehaviour:可交互物体挂载,使物体可以参与交互 需要刚体和碰撞
Interaction Engine找到Interaction Manager预制体拖入场景,给需要交互的物体添加InteractionBehaviour即可。
注意:它会改变unity物理的重力大小-4.905来达到好的交互效果,可以自己改回去
更多交互例子
手部模型
模型拖入场景,建立个父物体,挂上HandBinder,可以自动挂载数据点和对应模型节点,也能手动更改
手势解析
- LeapProvider类里包含了每帧获取到的追踪数据(世界坐标)
LeapProvider.CurrentFrame 当前帧数据 - Frame类里包含了手部和手指的每帧数据(位置,朝向,姿势等)
.Hand:获取手状态集合
官方现在好像没有手势解析的直接API调用,只能自己根据手指位置等数据提取自己想要的手势
手势提取代码
自己写的一个手势提取工具。使用时保证获取到leapprovider后,给左右手注册事件即可,也能够在外部访问一些数据(双手,手掌方向等)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Leap.Unity;
using Leap;
public class LeapGestureTool : MonoBehaviour
{
//当前帧手数量
public enum HandState
{
none,
leftHand,
rightHand,
bothHands
}
public HandState _HandState = HandState.none;
LeapProvider _lp;
void Start()
{
_lp = GetComponent<LeapProvider>();
}
void Update()
{
//获取追踪帧数据
if(_lp!=null)
AnalyseLeapFrameData(_lp.CurrentFrame);
}
public GestureHelper l_helper = new GestureHelper();
public GestureHelper r_helper = new GestureHelper();
/// <summary>
/// 解析leapmotion的帧数据
/// </summary>
/// <param name="frame_data"></param>
private void AnalyseLeapFrameData(Frame frame_data)
{
//左右手判断 并更新手
_HandState = GetHandState(frame_data.Hands);
//手势信息更新
l_helper.UpdateInf();
r_helper.UpdateInf();
//双手距离获取
BothHandScale(frame_data);
}
#region 基础
/// <summary>
/// 当前使用手判断 并添加helper
/// </summary>
/// <param name="lpHands"></param>
/// <returns></returns>
private HandState GetHandState(List<Hand> lpHands)
{
if(lpHands.Count == 1)//单手
{
if (lpHands[0].IsLeft)
{
l_helper.ChangeHandData(lpHands[0]);
return HandState.leftHand;
}
if (lpHands[0].IsRight)
{
r_helper.ChangeHandData(lpHands[0]);
return HandState.rightHand;
}
l_helper.ChangeHandData(null);
r_helper.ChangeHandData(null);
return HandState.none;
}else if(lpHands.Count == 2)//双手
{
foreach(var x in lpHands)
{
if (x.IsLeft)
{
l_helper.ChangeHandData(x);
}
if (x.IsRight)
{
r_helper.ChangeHandData(x);
}
}
return HandState.bothHands;
}
//无效数据
l_helper.ChangeHandData(null);
r_helper.ChangeHandData(null);
return HandState.none;
}
#endregion
#region 功能
/// <summary>
/// 双手离远或拉近趋势
/// </summary>
public float HandsZoom = 0;
/// <summary>
/// 双手距离
/// </summary>
public float lrHandDistance = 0;
/// <summary>
/// 【双手】获取双手之间的距离
/// </summary>
/// <param name="lpframe"></param>
private void BothHandScale(Frame lpframe)
{
if (_HandState == HandState.bothHands)
{
//双手不动时不计算
if(l_helper.isStayPos && r_helper.isStayPos)
{
lrHandDistance = 0;
HandsZoom = 0;
}
if (lrHandDistance == 0)
lrHandDistance = Vector3.Distance(l_helper.handPos, r_helper.handPos);
float tempdis = Vector3.Distance(l_helper.handPos, r_helper.handPos);
HandsZoom = tempdis - lrHandDistance;
lrHandDistance = tempdis;
}
else
{
lrHandDistance = 0;
HandsZoom = 0;
}
}
#endregion
}
public class GestureHelper
{
/// <summary>
/// 获取当前手掌位置
/// </summary>
public Vector3 handPos
{
get
{
if (m_hand != null)
return m_hand.PalmPosition.ToVector3();
return Vector3.zero;
}
}
//手掌朝向
public Vector3 palmNormal
{
get
{
if (m_hand != null)
return m_hand.PalmNormal.ToVector3();
return Vector3.zero;
}
}
private Hand m_hand;//手部数据
public GestureHelper() { }
public GestureHelper(Hand hand)
{
m_hand = hand;
}
public void ChangeHandData(Hand hand)
{
m_hand = hand;
}
/// <summary>
/// 更新手部 数据 提取 手势
/// </summary>
public void UpdateInf()
{
if (m_hand == null)
{
//丢失数据重置信息
NullReset();
return;
}
GetHandGesture();
}
//手势触发事件执行
public delegate void GestureEventHandle();
public delegate void GestureEventHandle<T>(T value);//带返回数据的
public GestureEventHandle GestureEvent_Grab;//握拳
public GestureEventHandle GestureEvent_Open;//张开
public GestureEventHandle GestureEvent_Pinch;//捏
public GestureEventHandle GestureEvent_Stay;//原地不动
public GestureEventHandle GestureEvent_OnlyIndexFinger;//仅食指伸出
public GestureEventHandle<Hand> GestureEvent_OnlyIndexFinger_BackHand;
public GestureEventHandle GestureEvent_JianDao;
public GestureEventHandle<float> GestureEvent_Bending;//手掌弯曲(程度)
public GestureEventHandle<float> GestureEvent_MoveX;//x轴移动(左右)
public GestureEventHandle<float> GestureEvent_MoveY;//y轴移动(上下)
public GestureEventHandle<float> GestureEvent_MoveZ;//z轴移动(前后)
public bool isGrabing = false;
public bool isStayPos = false;
public string JSB = string.Empty;
/// <summary>
/// 姿势提取
/// </summary>
private void GetHandGesture()
{
//grab测试的是 除大拇指外的四个手指的 平均 值 (依据指向方向来)
//position是相对于LeapProvider物体的位置(单位缩小10)
if (m_hand.GrabStrength == 1)
{
isGrabing = true;
if (GestureEvent_Grab != null)
GestureEvent_Grab();
// Debug.Log("握拳");
JSB = "石头";
}
else if (m_hand.GrabStrength == 0)//只要不是紧握
{
isGrabing = false;
if (GestureEvent_Open != null)
GestureEvent_Open();
// Debug.Log("张开");
JSB = "布";
}
else// 0-1之间,手指在不断弯曲
{
isGrabing = false;
if (GestureEvent_Bending != null)
GestureEvent_Bending(m_hand.GrabStrength);
//Debug.Log("正在握紧"+ lpHand.GrabStrength);
}
//捏 和 握拳 会有交叉 必须用到大拇指
if (m_hand.PinchStrength == 1)
{
if (GestureEvent_Pinch != null)
GestureEvent_Pinch();
//Debug.Log("捏");
}
//剪刀手的判断
//1.先判断打开的手指数
//2.判断是否是 中指 食指
//
//判断中指
//Debug.Log(m_hand.GetMiddle().IsExtended);
int tt = 0;
foreach(var t in m_hand.Fingers)
{
if (t.IsExtended)
tt++;
}
if (tt == 2)
{
if(m_hand.GetMiddle().IsExtended && m_hand.GetIndex().IsExtended)
{
if (GestureEvent_JianDao != null)
GestureEvent_JianDao();
JSB = "剪刀";
}
}
if(tt == 1)
{
if (m_hand.GetIndex().IsExtended)
{
//箭头 手势
if (GestureEvent_OnlyIndexFinger != null)
GestureEvent_OnlyIndexFinger();
//返回手部数据
if (GestureEvent_OnlyIndexFinger_BackHand != null)
GestureEvent_OnlyIndexFinger_BackHand(m_hand);
}
}
//手掌方向
//手的【原地不动】【移动方向】
GetMoveDir();
}
Vector3 oldMovePosition;
//原地不动判断条件:手的三方向上的速度和阈值
static float smallestVelocity = 0.3f;
float move_X;
float move_Y;
float move_Z;
//手部移动方向获取
private void GetMoveDir()
{
//原地不动
if(m_hand.PalmVelocity.Magnitude < smallestVelocity)
{
if (GestureEvent_Stay != null)
GestureEvent_Stay();
isStayPos = true;
//位置重置
oldMovePosition = Vector3.zero;
move_X = move_Y = move_Z = 0;
}
else//
{
isStayPos = false;
if (oldMovePosition == Vector3.zero)
oldMovePosition = m_hand.PalmPosition.ToVector3();
//效果不好的话加阈值
move_X += m_hand.PalmPosition.ToVector3().x - oldMovePosition.x;
move_Y += m_hand.PalmPosition.ToVector3().y - oldMovePosition.y;
move_Z += m_hand.PalmPosition.ToVector3().z - oldMovePosition.z;
if (move_X > 0)//右移
{
if (GestureEvent_MoveX != null)
GestureEvent_MoveX(move_X);
// Debug.LogError("→");
//move_X = 0;
}
if(move_X < 0)//左移
{
if (GestureEvent_MoveX != null)
GestureEvent_MoveX(move_X);
// Debug.LogError("←");
//move_X = 0;
}
if (move_Y > 0)//上
{
if (GestureEvent_MoveY != null)
GestureEvent_MoveY(move_Y);
// Debug.LogError("↑");
}
if (move_Y < 0)//下
{
if (GestureEvent_MoveY != null)
GestureEvent_MoveY(move_Y);
// Debug.LogError("↓");
}
if (move_Z > 0)//前
{
if (GestureEvent_MoveZ != null)
GestureEvent_MoveZ(move_Z);
// Debug.LogError("前");
}
if (move_Z < 0)//后
{
if (GestureEvent_MoveZ != null)
GestureEvent_MoveZ(move_Z);
// Debug.LogError("后");
}
//替换当前位置
oldMovePosition = m_hand.PalmPosition.ToVector3();
}
}
private void NullReset()
{
//抓握
isGrabing = false;
//上一帧位置
oldMovePosition = Vector3.zero;
//移动趋势
move_X = move_Y = move_Z = 0;
}
}
扩展
unity插件里的核心:
- Core. 获取追踪到的数据,模型和数据绑定等接口提供
- The Interaction Engine.交互功能,类似物理引擎
- Hands. 手部模型渲染相关
核心组件:
- LeapServiceProvider:功能入口,连接并获取追踪数据(XR项目用XR版的)
- Attachment Hands:协助自动绑定追踪数据到手部模型
- InteractionManager:通过FixedUpdate 和 交互逻辑来实现交互功能 控制交互数据(交互功能必须存在此组件)