【仅用于个人复习使用,顺序较乱】
[Detection SDK]
关于手势检测的官方SDK主要有这么几个脚本。
Detector是所有检测器的父类,主要保存的属性是IsActive。
DetectorLogicGate继承自Detector,主要提供多个Detector的逻辑操作,比如AND/OR/NAND/NOR。
PinchDetector是用来检测手是否握紧,继承自AbstractHoldDetector。而AbstractHoldDetector又继承自Detector。
FingerDirectionDetector用来检测某个手指是否沿某个指定的方向。
PalmDirectionDetector用来检测手掌是否沿某个指定的方向。
ExtendedFingerDetector用来检测手指是否展开。
ProximityDetector用来进行距离检测。
(LeapMotion脚本代码思路教程:http://blog.csdn.net/admintan/article/details/50319757)
(LeapMotion的名字空间:http://blog.csdn.net/eagle_pre21/article/details/51776830)
这两个链接还是值得一看的。
我们先对官方提供的一些demo和源码进行解析,从而了解LeapMotion提供了哪些主要的接口或者说我们应该怎么编写LeapMotion的代码。
五、SDK的使用 之 PinchDetector
我们这里将以LeapMotion Detection Module中的PinchDrawDemo场景为例。这是PinchDrawDemo的结构:
LMHeadMountedRig是CoreAsset中的一个预制件,应该对应的是HeadMounted模式。PinchDrawing是执行绘制功能的object。DirectionLight则是场景中的灯光。
我们对这个场景做简单的分析,来了解LM SDK的使用方法。
(1)PinchDraw.cs
这是PinchDrawing对象上的唯一的脚本,具体代码如下:
using UnityEngine;
using System.Collections.Generic;
namespace Leap.Unity.DetectionExamples {
public class PinchDraw : MonoBehaviour {
//使用Tooltip,当我们在Inspector面板中,把鼠标停留在PinchDetector变量上,就会显示这一句字符串。这里的PinchDetector是一个DetectionUtility文件夹中的自定义类,左右手模型上各有一个。
[Tooltip("Each pinch detector can draw one line at a time.")]
[SerializeField]
//左右手的PinchDetector
private PinchDetector[] _pinchDetectors;
[SerializeField]
//mesh的材质
private Material _material;
[SerializeField]
//mesh的颜色
private Color _drawColor = Color.white;
[SerializeField]
//SmoothedVector3的平滑系数
private float _smoothingDelay = 0.01f;
[SerializeField]
//绘制的Ring的半径
private float _drawRadius = 0.002f;
[SerializeField]
//绘制一个Ring所使用的顶点数
private int _drawResolution = 8;
[SerializeField]
//判断是否需要添加Ring的最短距离
private float _minSegmentLength = 0.005f;
private DrawState[] _drawStates;
//get/set,前面已经介绍了,这里不再多提
public Color DrawColor {
get {
return _drawColor;
}
set {
_drawColor = value;
}
}
public float DrawRadius {
get {
return _drawRadius;
}
set {
_drawRadius = value;
}
}
//当Inspector面板中的值被修改时触发,用于保证值的合法性。类似的函数还有Reset(),当面板中脚本的值被reset时触发。
//具体可参考:http://www.tuicool.com/articles/AfqY32
void OnValidate() {
_drawRadius = Mathf.Max(0, _drawRadius);
_drawResolution = Mathf.Clamp(_drawResolution, 3, 24);
_minSegmentLength = Mathf.Max(0, _minSegmentLength);
}
void Awake() {
//至少要有一个PinchDetector被传入
if (_pinchDetectors.Length == 0) {
Debug.LogWarning("No pinch detectors were specified! PinchDraw can not draw any lines without PinchDetectors.");
}
}
//初始化DrawState数组,并加入新建DrawState对象。DrawState是这个脚本中定义的内部类,具体参见下方的注释。
void Start() {
_drawStates = new DrawState[_pinchDetectors.Length];
for (int i = 0; i < _pinchDetectors.Length; i++) {
//把this作为参数传入DrawState,保存在parent变量中。
_drawStates[i] = new DrawState(this);
}
}
void Update() {
for (int i = 0; i < _pinchDetectors.Length; i++) {
var detector = _pinchDetectors[i];
var drawState = _drawStates[i];
//以下三个是PinchDetector类的方法,根据名字很容易猜到他们的功能。捏成拳头就开始画线,拳头松开就结束画线。画线调用的则是内部类DrawState封装的函数。
//DidStartHold——手刚捏成拳头
if (detector.DidStartHold) {
drawState.BeginNewLine();
}
//DidRelease——手从拳头松开
if (detector.DidRelease) {
drawState.FinishLine();
}
//IsHolding——手保持拳头的姿势
if (detector.IsHolding) {
drawState.UpdateLine(detector.Position);
}
}
}
//期待已久的DrawState类~
private class DrawState {
private List _vertices = new List();
private List _tris = new List();
private List _uvs = new List();
private List _colors = new List();
//外部类在初始化DrawState数组时(Start()函数),会将this(外部类对象的引用)传入新建的DrawState。
private PinchDraw _parent;
private int _rings = 0;
private Vector3 _prevRing0 = Vector3.zero;
private Vector3 _prevRing1 = Vector3.zero;
private Vector3 _prevNormal0 = Vector3.zero;
private Mesh _mesh;
//SmoothedVector3是LM CoreAsset中Algorithm文件夹里的自定义类,大致就是让Vector3的值在改变时更平滑。
private SmoothedVector3 _smoothedPosition;
public DrawState(PinchDraw parent) {
_parent = parent;
//初始化SmoothedVector3对象
_smoothedPosition = new SmoothedVector3();
_smoothedPosition.delay = parent._smoothingDelay;
_smoothedPosition.reset = true;
}
//开始画线,为后面的Update做一些初始化的准备工作
public GameObject BeginNewLine() {
_rings = 0;
_vertices.Clear();
_tris.Clear();
_uvs.Clear();
_colors.Clear();
_smoothedPosition.reset = true;
//每次BeginNewLine会新建一个mesh
_mesh = new Mesh();
_mesh.name = "Line Mesh";
_mesh.MarkDynamic();//标记为动态,这样mesh就会使用动态的buffer,在渲染时的性能更高。
//用脚本动态添加GameObject
GameObject lineObj = new GameObject("Line Object");
//初始化transform
lineObj.transform.position = Vector3.zero;
lineObj.transform.rotation = Quaternion.identity;
lineObj.transform.localScale = Vector3.one;
//添加MeshFilter以及材质
lineObj.AddComponent().mesh = _mesh;
lineObj.AddComponent().sharedMaterial = _parent._material;
return lineObj;
}
public void UpdateLine(Vector3 position) {
//更新SmoothedPosition,这里传入的position是PinchDetector.position。
_smoothedPosition.Update(position, Time.deltaTime);
bool shouldAdd = false;
//当vertices为空 或者 position与prevRing0的距离超过minSegmentLength的时候,需要调用addRing()去添加ring。
shouldAdd |= _vertices.Count == 0;
shouldAdd |= Vector3.Distance(_prevRing0, _smoothedPosition.value) >= _parent._minSegmentLength;
//addRing添加ring,updateMesh去更新mesh的数据。
if (shouldAdd) {
addRing(_smoothedPosition.value);
updateMesh();
}
}
public void FinishLine() {
;
//通过vertices、normals、triangles等可以通过脚本绘制mesh,mesh的data会被标记为modified,在下一次传给GraphicsAPI去渲染。而调用UploadMeshData可以强制将data立刻传给API渲染。
//而markNoLongerReadable参数则表示是否清空mesh data在内存中占用的空间。由于这里是FinishLine,所以设置为true。
_mesh.UploadMeshData(true);
}
//将顶点等数组的数据给刷新到mesh中去,从而更新场景中的mesh。
private void updateMesh() {
_mesh.SetVertices(_vertices);
_mesh.SetColors(_colors);
_mesh.SetUVs(0, _uvs);
//以Triangles的方式设置mesh的sub-mesh
_mesh.SetIndices(_tris.ToArray(), MeshTopology.Triangles, 0);
//重新计算边界和法向量
_mesh.RecalculateBounds();
_mesh.RecalculateNormals();
}
//添加Ring,函数中做了一堆计算,没太看懂,note一下~
private void addRing(Vector3 ringPosition) {
_rings++;//ring的个数
if (_rings == 1) {
//为一个Ring在顶点数组中新建8个顶点,初始化uv和color
addVertexRing();
addVertexRing();
//通过顶点数组去创建三角面片,用顶点数组的三个index来表示构成三角面片的三个顶点
addTriSegment();
}
addVertexRing();
addTriSegment();
//后面就是一些看不懂的计算了,大致就是为Ring去计算法向量,以及通过UpdateRingVerts()更新顶点数组中的顶点坐标之类的。。。花里胡哨
Vector3 ringNormal = Vector3.zero;
if (_rings == 2) {
Vector3 direction = ringPosition - _prevRing0;
float angleToUp = Vector3.Angle(direction, Vector3.up);
if (angleToUp < 10 || angleToUp > 170) {
ringNormal = Vector3.Cross(direction, Vector3.right);
} else {
ringNormal = Vector3.Cross(direction, Vector3.up);
}
ringNormal = ringNormal.normalized;
_prevNormal0 = ringNormal;
} else if (_rings > 2) {
Vector3 prevPerp = Vector3.Cross(_prevRing0 - _prevRing1, _prevNormal0);
ringNormal = Vector3.Cross(prevPerp, ringPosition - _prevRing0).normalized;
}
if (_rings == 2) {
updateRingVerts(0,
_prevRing0,
ringPosition - _prevRing1,
_prevNormal0,
0);
}
if (_rings >= 2) {
updateRingVerts(_vertices.Count - _parent._drawResolution,
ringPosition,
ringPosition - _prevRing0,
ringNormal,
0);
updateRingVerts(_vertices.Count - _parent._drawResolution * 2,
ringPosition,
ringPosition - _prevRing0,
ringNormal,
1);
updateRingVerts(_vertices.Count - _parent._drawResolution * 3,
_prevRing0,
ringPosition - _prevRing1,
_prevNormal0,
1);
}
_prevRing1 = _prevRing0;
_prevRing0 = ringPosition;
_prevNormal0 = ringNormal;
}
private void addVertexRing() {
for (int i = 0; i < _parent._drawResolution; i++) {
_vertices.Add(Vector3.zero); //Dummy vertex, is updated later
_uvs.Add(new Vector2(i / (_parent._drawResolution - 1.0f), 0));
_colors.Add(_parent._drawColor);
}
}
//Connects the most recently added vertex ring to the one before it
private void addTriSegment() {
for (int i = 0; i < _parent._drawResolution; i++) {
int i0 = _vertices.Count - 1 - i;
int i1 = _vertices.Count - 1 - ((i + 1) % _parent._drawResolution);
_tris.Add(i0);
_tris.Add(i1 - _parent._drawResolution);
_tris.Add(i0 - _parent._drawResolution);
_tris.Add(i0);
_tris.Add(i1);
_tris.Add(i1 - _parent._drawResolution);
}
}
private void updateRingVerts(int offset, Vector3 ringPosition, Vector3 direction, Vector3 normal, float radiusScale) {
direction = direction.normalized;
normal = normal.normalized;
for (int i = 0; i < _parent._drawResolution; i++) {
float angle = 360.0f * (i / (float)(_parent._drawResolution));
Quaternion rotator = Quaternion.AngleAxis(angle, direction);
Vector3 ringSpoke = rotator * normal * _parent._drawRadius * radiusScale;
_vertices[offset + i] = ringPosition + ringSpoke;
}
}
}
}
}
总结这段代码,就是通过LM官方提供的PinchDetector去识别当前手势的Pinch信息,主要就是开始握紧、保持握紧以及结束握紧三个状态。下面是对应的函数:
通过对三个状态的判断,去执行DrawState相应的函数进行画线。
(2)LMHeadMountedRig对象
多了两个脚本Leap VR Camera Controller.cs和Leap VR Temporal Wraping.cs,猜测这个可能是用于VR场景下。
由于我们暂时不考虑VR模式下使用,所以尝试把这个替换成普通的LeapHandController,也就是桌面模式下的控制器。
当然我们直接把这个场景中的LeapHandController拿出来,而把LMHeadMountedRig给删掉就ok了。这里为了记录一下场景搭建的过程,就从头开始吧。
先新建一个Scene,新建一个叫Hands的空对象,里面拖入LeapHandController预制件作为子对象。再新建一个名为HandModels的空子对象,将模型CapsuleL和Capsule_R拖进来。
在LeapHandController的HandPool组件中,把ModelPool的size置为1,会出来一个空的组。将组命名为gfx,因为这里不需要pfx,所以一个组就够了。再把之前的Capsule L和R这两个模型作为参数传入gfx。
最后再把Camera拖入Hands对象就大功告成~
当然这只是最基本的搭建,由于我们需要Pinch的识别,所以将PinchDetector.cs拖给CapsuleL和CapsuleR。新建一个PinchDrawing,将PinchDraw脚本拖给他,并将两个PinchDetector作为参数传给PinchDraw即可。
给出最后的示意图:
示意图1
示意图2
六、SDK的使用 之 OtherDetector
我们这里以FingerDetectorDemo为例进行介绍。
在看代码之前,我们首先需要了解以下内容:
1、C#中的IEnumerable、IEnumerate(这个教程看完以后就舒服):http://blog.csdn.net/byondocean/article/details/6871881
2、C#中的yield:http://www.cnblogs.com/kingcat/archive/2012/07/11/2585943.html
3、unity3d中的yield return:http://blog.csdn.net/huang9012/article/details/29595747
这是场景中的Hierarchy:
LMHeadMountedRig是VR下的预制件,我们可以只保留内部的LeapHandController。把CenterEyeEntered的其他内容给去掉。当然你还得自己加一个Camera,具体搭建请参照前文。。。
识别的代码主要在CapsuleL和CapsuleR上,这个稍后一点讲。而Environment是整个游戏的环境,当然其他的东西都属于花里胡哨,只有BlueBall以及BallSpawner是有用的。我们先看BlueBall上的SpawnBalls.cs脚本。
(1)SpawnBalls.cs
using UnityEngine;
using System.Collections;
public class SpawnBalls : MonoBehaviour {
public GameObject BallPrefab;//预制件,用于实例化小球
public float delayInterval = .15f; // seconds
public int BallLimit = 100;//最大球数
public Vector3 BallSize = new Vector3(0.1f, 0.1f, 0.1f);//产生的球的半径
//IEnumerator对象,用于协程
private IEnumerator _spawnCoroutine;
void Awake () {
//设置协程,这里并不会去执行协程的代码
_spawnCoroutine = AddBallWithDelay(BallPrefab);
}
public void StartBalls(){
//开启协程
StartCoroutine(_spawnCoroutine);
}
public void StopBalls(){
//关闭协程
StopAllCoroutines();
}
private IEnumerator AddBallWithDelay (GameObject prefab) {
while (true) {
addBall(prefab);//生成一个小球
//每帧在调用LateUpdate()之后,会调用协程的MoveNext,如果满足条件,则从上次yield的位置继续执行,否则就跳过协程。
yield return new WaitForSeconds(delayInterval);
}
}
private void addBall (GameObject prefab) {
if (transform.childCount > BallLimit) removeBalls(BallLimit / 10);//超过最大球数就销毁1/10
GameObject go = GameObject.Instantiate(prefab);
go.transform.parent = transform;//生成的小球作为此脚本所在对象的子对象
go.transform.localPosition = Vector3.zero;//相对位置
go.transform.localScale = BallSize;//相对大小
Rigidbody rb = go.GetComponent();
rb.AddForce(Random.value * 3, -Random.value * 13, Random.value * 3, ForceMode.Impulse);//给一个冲量
}
private void removeBalls (int count) {
if (count > transform.childCount) count = transform.childCount;
for (int b = 0; b < count; b++) {
Destroy(transform.GetChild(b).gameObject);//删除1/10的子对象
}
}
}
(2)Detector.cs
//这个注释其实源文件已经很详细了,这里就简要概括一下好了。
//Detector作为所有检测器的父类,核心就是IsActive这个属性。当派生类例如FingerDirectionDetector继承它后,如果手指方向与指定方向匹配,则调用父类的Avtivate()方法。该方法会将IsActive置为true,方便外部获取信息(比如DetectorLogicGate就是用IsActive来做逻辑计算的)。同时调用OnActivate事件对应的回调函数。
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using Leap;
namespace Leap.Unity {
/**
* Base class for detectors.
*
* A Detector is an object that observes some aspect of a scene and reports true
* when the specified conditions are met. Typically these conditions involve hand
* information, but this is not required.
*
* Detector implementations must call Activate() when their conditions are met and
* Deactivate() when those conditions are no longer met. Implementations should
* also call Deactivate() when they, or the object they are a component of become disabled.
* Implementations can call Activate() and Deactivate() more often than is strictly necessary.
* This Detector base class keeps track of the IsActive status and only dispatches events
* when the status changes.
*
* @since 4.1.2
*/
public class Detector : MonoBehaviour {
/** The current detector state.
* @since 4.1.2
*/
public bool IsActive{ get{ return _isActive;}}
private bool _isActive = false;
/** Dispatched when the detector activates (becomes true).
* @since 4.1.2
*/
[Tooltip("Dispatched when condition is detected.")]
public UnityEvent OnActivate;
/** Dispatched when the detector deactivates (becomes false).
* @since 4.1.2
*/
[Tooltip("Dispatched when condition is no longer detected.")]
public UnityEvent OnDeactivate;
/**
* Invoked when this detector activates.
* Subclasses must call this function when the detector's conditions become true.
* @since 4.1.2
*/
public virtual void Activate(){
if (!IsActive) {
_isActive = true;
OnActivate.Invoke();
}
}
/**
* Invoked when this detector deactivates.
* Subclasses must call this function when the detector's conditions change from true to false.
* @since 4.1.2
*/
public virtual void Deactivate(){
if (IsActive) {
_isActive = false;
OnDeactivate.Invoke();
}
}
//Gizmo colors
protected Color OnColor = Color.green;
protected Color OffColor = Color.red;
protected Color LimitColor = Color.blue;
protected Color DirectionColor = Color.white;
protected Color NormalColor = Color.gray;
}
}
(3)DetectorLogicGate.cs
using UnityEngine;
using UnityEngine.Events;
using System.Collections.Generic;
namespace Leap.Unity {
/**
* The DetectorLogicGate detector observes other detectors and activates when
* these other detectors match the specified logic.
*
* A DetectorLogicGate can be configured as an AND gate or an OR gate. You can also
* negate the output (creating a NAND or NOR gate).
*
* Since a DetectorLogicGate is a Detector, it can observe other DetectorLogicGate instances.
* However, before constructing complex logic chains, you should consider whether it is better
* to put such logic into a normal script.
*
* @since 4.1.2
*/
public class DetectorLogicGate : Detector {
[SerializeField]
[Tooltip("The list of observed detectors.")]
//需要监听的Detectors
private List Detectors;
/**
* When true, all Detector components of the same game object
* are added to the list of watched detectors on Awake. When false,
* you must manually add the desired detectors.
*
* If you have more than one DetectorLogicGate component on a game object,
* do not enable this option on both.
* @since 4.1.2
*/
[Tooltip("Add all detectors on this object automatically.")]
//这个选项如果勾选,在Awake的时候,会自动将所有的兄弟Detector全都加进来。所以如果一个object上有两个本脚本,建议不要勾选这个选项。
public bool AddAllSiblingDetectorsOnAwake = true;
/**
* The type of logic for this gate: AND or OR.
* @since 4.1.2
*/
[Tooltip("The type of logic used to combine detector state.")]
//AND / OR
public LogicType GateType = LogicType.AndGate;
/**
* Whether to negate the output of the gate. AND becomes NAND; OR becomes NOR.
* @since 4.1.2
*/
[Tooltip("Whether to negate the gate output.")]
//NAND / NOR
public bool Negate = false;
/**
* Adds the specified detector to the list of observed detectors.
*
* The same detector cannot be added more than once.
* @param Detector the detector to watch.
* @since 4.1.2
*/
public void AddDetector(Detector detector){
if(!Detectors.Contains(detector)){
Detectors.Add(detector);//添加Detector到监听数组中
activateDetector(detector);//激活Detector,也就是为该Detector的OnActivate和OnDeactivate设置监听器(回调函数)
}
}
/**
* Removes the specified detector from the list of observed detectors;
*
* @param Detector the detector to remove.
* @since 4.1.2
*/
//移除监听的Detector,没什么好说的。。
public void RemoveDetector(Detector detector){
detector.OnActivate.RemoveListener(CheckDetectors);
detector.OnDeactivate.RemoveListener(CheckDetectors);
Detectors.Remove(detector);
}
/**
* Adds all the other detectors on the same GameObject to the list of observed detectors.
*
* Note: If you have more than one DetectorLogicGate instance on a game object, make sure that
* both objects don't observe each other.
* @since 4.1.2
*/
public void AddAllSiblingDetectors(){
//遍历所有兄弟Detector
Detector[] detectors = GetComponents();
for(int g = 0; g < detectors.Length; g++){
if ( detectors[g] != this && detectors[g].enabled) {
AddDetector(detectors[g]);
}
}
}
private void Awake(){
for (int d = 0; d < Detectors.Count; d++) {
activateDetector(Detectors[d]);//激活手动设置的监听数组中的Detector
}
if (AddAllSiblingDetectorsOnAwake) {
AddAllSiblingDetectors();//自动激活兄弟Detector
}
}
private void activateDetector(Detector detector){
//为了避免连续添加两次监听器,所以先remove
detector.OnActivate.RemoveListener(CheckDetectors); //avoid double subscription
detector.OnDeactivate.RemoveListener(CheckDetectors);
detector.OnActivate.AddListener(CheckDetectors);
detector.OnDeactivate.AddListener(CheckDetectors);
}
private void OnEnable() {
CheckDetectors();
}
private void OnDisable () {
Deactivate();
}
/**
* Checks all the observed detectors, combines them with the specified type of logic
* and calls the Activate() or Deactivate() function as appropriate.
* @since 4.1.2
*/
protected void CheckDetectors(){
if (Detectors.Count < 1)
return;
//遍历所有监听数组中的Detector,取他们的IsActive变量进行计算
bool state = Detectors[0].IsActive;
for(int a = 1; a < Detectors.Count; a++){
if(GateType == LogicType.AndGate){//与计算,需要全部Active
state = state && Detectors[a].IsActive;
} else {//或计算,只需要有一个Actice
state = state || Detectors[a].IsActive;
}
}
if(Negate){//非计算
state = !state;
}
if(state){
Activate();//调用本脚本中的OnActivate回调函数
} else {
Deactivate();//调用本脚本中的OnDeactivate回调函数
}
}
}
/** The type of logic used to combine the watched detectors. */
public enum LogicType{ AndGate, OrGate }
}
(4)FingerDirectionDetector.cs
using UnityEngine;
using System.Collections;
using Leap.Unity.Attributes;
namespace Leap.Unity {
/**
* Detects when specified fingers are pointing in the specified manner.
*
* Directions can be specified relative to the global frame of reference, relative to
* the camera frame of reference, or using a combination of the two -- relative to the
* camera direction in the x-z plane, but not changing relative to the horizon.
*
* You can alternatively specify a target game object.
*
* If added to a IHandModel instance or one of its children, this detector checks the
* finger direction at the interval specified by the Period variable. You can also specify
* which hand model to observe explicitly by setting handModel in the Unity editor or
* in code.
*
* @since 4.1.2
*/
//检测特定手指是否沿特定方向
public class FingerDirectionDetector : Detector {
/**
* The interval at which to check finger state.
* @since 4.1.2
*/
[Units("seconds")]
[Tooltip("The interval in seconds at which to check this detector's conditions.")]
[MinValue(0)]
//每隔Period秒检测一次
public float Period = .1f; //seconds
/**
* The IHandModel instance to observe.
* Set automatically if not explicitly set in the editor.
* @since 4.1.2
*/
[AutoFind(AutoFindLocations.Parents)]
[Tooltip("The hand model to watch. Set automatically if detector is on a hand.")]
//手部模型,如果detector在手模型上则自动添加
public IHandModel HandModel = null;
/**
* The finger to compare to the specified direction.
* @since 4.1.2
*/
[Tooltip("The finger to observe.")]
//需要检测的手指,index对应的是食指
public Finger.FingerType FingerName = Finger.FingerType.TYPE_INDEX;
/**
* Specifies how to interprete the direction specified by PointingDirection.
*
* - RelativeToCamera -- the target direction is defined relative to the camera's forward vector, i.e. (0, 0, 1) is the cmaera's
* local forward direction.
* - RelativeToHorizon -- the target direction is defined relative to the camera's forward vector,
* except that it does not change with pitch.
* - RelativeToWorld -- the target direction is defined as a global direction that does not change with camera movement. For example,
* (0, 1, 0) is always world up, no matter which way the camera is pointing.
* - AtTarget -- a target object is used as the pointing direction (The specified PointingDirection is ignored).
*
* In VR scenes, RelativeToHorizon with a direction of (0, 0, 1) for camera forward and RelativeToWorld with a direction
* of (0, 1, 0) for absolute up, are often the most useful settings.
* @since 4.1.2
*/
[Header("Direction Settings")]
[Tooltip("How to treat the target direction.")]
//pointing的种类,这里用到了AtTarget(指向目标点)和RelativeToWorld(相对于世界坐标系)
public PointingType PointingType = PointingType.RelativeToHorizon;
/**
* The target direction as interpreted by the PointingType setting.
* Ignored when Pointingtype is "AtTarget."
* @since 4.1.2
*/
[Tooltip("The target direction.")]
[DisableIf("PointingType", isEqualTo: PointingType.AtTarget)]
//Pointing的方向,只有当不是AtTarget才可以设置
public Vector3 PointingDirection = Vector3.forward;
/**
* The object to point at when the PointingType is "AtTarget." Ignored otherwise.
*/
[Tooltip("A target object(optional). Use PointingType.AtTarget")]
[DisableIf("PointingType", isNotEqualTo: PointingType.AtTarget)]
//目标物体,只有当是AtTarget才可以设置
public Transform TargetObject = null;
/**
* The turn-on angle. The detector activates when the specified finger points within this
* many degrees of the target direction.
* @since 4.1.2
*/
[Tooltip("The angle in degrees from the target direction at which to turn on.")]
[Range(0, 180)]
//判定为Active的角度范围
public float OnAngle = 15f; //degrees
/**
* The turn-off angle. The detector deactivates when the specified finger points more than this
* many degrees away from the target direction. The off angle must be larger than the on angle.
* @since 4.1.2
*/
[Tooltip("The angle in degrees from the target direction at which to turn off.")]
[Range(0, 180)]
//判定为Deactive的角度范围
public float OffAngle = 25f; //degrees
/** Whether to draw the detector's Gizmos for debugging. (Not every detector provides gizmos.)
* @since 4.1.2
*/
[Header("")]
[Tooltip("Draw this detector's Gizmos, if any. (Gizmos must be on in Unity edtor, too.)")]
public bool ShowGizmos = true;
private IEnumerator watcherCoroutine;
private void OnValidate(){
if( OffAngle < OnAngle){
OffAngle = OnAngle;
}
}
private void Awake () {
watcherCoroutine = fingerPointingWatcher();
}
private void OnEnable () {
StartCoroutine(watcherCoroutine);
}
private void OnDisable () {
StopCoroutine(watcherCoroutine);
Deactivate();
}
//协程
private IEnumerator fingerPointingWatcher() {
Hand hand;
Vector3 fingerDirection;
Vector3 targetDirection;
int selectedFinger = selectedFingerOrdinal();
while(true){
if(HandModel != null && HandModel.IsTracked){
//从HandModel中获得Hand类型的对象
hand = HandModel.GetLeapHand();
if(hand != null){
//目标方向
targetDirection = selectedDirection(hand.Fingers[selectedFinger].TipPosition.ToVector3());
//当前手指方向
fingerDirection = hand.Fingers[selectedFinger].Bone(Bone.BoneType.TYPE_DISTAL).Direction.ToVector3();
float angleTo = Vector3.Angle(fingerDirection, targetDirection);
//IsTracked表示这个手是否正在被追踪,也就是是否合法
if(HandModel.IsTracked && angleTo <= OnAngle){
Activate();
} else if (!HandModel.IsTracked || angleTo >= OffAngle) {
Deactivate();
}
}
}
//每隔Period秒执行一次
yield return new WaitForSeconds(Period);
}
}
//根据手指的位置以及PointingType,计算出世界坐标系下的方向
private Vector3 selectedDirection(Vector3 tipPosition){
switch(PointingType){
case PointingType.RelativeToHorizon:
Quaternion cameraRot = Camera.main.transform.rotation;
float cameraYaw = cameraRot.eulerAngles.y;
Quaternion rotator = Quaternion.AngleAxis(cameraYaw, Vector3.up);
return rotator * PointingDirection;
case PointingType.RelativeToCamera:
return Camera.main.transform.TransformDirection(PointingDirection);
case PointingType.RelativeToWorld:
return PointingDirection;
case PointingType.AtTarget:
return TargetObject.position - tipPosition;
default:
return PointingDirection;
}
}
//计算手指对应的序号
private int selectedFingerOrdinal(){
switch(FingerName){
case Finger.FingerType.TYPE_INDEX:
return 1;
case Finger.FingerType.TYPE_MIDDLE:
return 2;
case Finger.FingerType.TYPE_PINKY:
return 4;
case Finger.FingerType.TYPE_RING:
return 3;
case Finger.FingerType.TYPE_THUMB:
return 0;
default:
return 1;
}
}
#if UNITY_EDITOR
private void OnDrawGizmos () {
if (ShowGizmos && HandModel != null && HandModel.IsTracked) {
Color innerColor;
if (IsActive) {
innerColor = OnColor;
} else {
innerColor = OffColor;
}
Finger finger = HandModel.GetLeapHand().Fingers[selectedFingerOrdinal()];
Vector3 fingerDirection = finger.Bone(Bone.BoneType.TYPE_DISTAL).Direction.ToVector3();
Utils.DrawCone(finger.TipPosition.ToVector3(), fingerDirection, OnAngle, finger.Length, innerColor);
Utils.DrawCone(finger.TipPosition.ToVector3(), fingerDirection, OffAngle, finger.Length, LimitColor);
Gizmos.color = DirectionColor;
Gizmos.DrawRay(finger.TipPosition.ToVector3(), selectedDirection(finger.TipPosition.ToVector3()));
}
}
#endif
}
}
(5)ExtendedFingerDetector.cs
using UnityEngine;
using System.Collections;
using System;
using Leap.Unity.Attributes;
namespace Leap.Unity {
/**
* Detects when specified fingers are in an extended or non-extended state.
*
* You can specify whether each finger is extended, not extended, or in either state.
* This detector activates when every finger on the observed hand meets these conditions.
*
* If added to a IHandModel instance or one of its children, this detector checks the
* finger state at the interval specified by the Period variable. You can also specify
* which hand model to observe explicitly by setting handModel in the Unity editor or
* in code.
*
* @since 4.1.2
*/
//基本的结构与上一个脚本类似,只不过检测的是手指手否展开
public class ExtendedFingerDetector : Detector {
/**
* The interval at which to check finger state.
* @since 4.1.2
*/
[Tooltip("The interval in seconds at which to check this detector's conditions.")]
[Units("seconds")]
[MinValue(0)]
public float Period = .1f; //seconds
/**
* The IHandModel instance to observe.
* Set automatically if not explicitly set in the editor.
* @since 4.1.2
*/
[AutoFind(AutoFindLocations.Parents)]
[Tooltip("The hand model to watch. Set automatically if detector is on a hand.")]
public IHandModel HandModel = null;
/** The required thumb state. */
[Header("Finger States")]
[Tooltip("Required state of the thumb.")]
public PointingState Thumb = PointingState.Either;
/** The required index finger state. */
[Tooltip("Required state of the index finger.")]
public PointingState Index = PointingState.Either;
/** The required middle finger state. */
[Tooltip("Required state of the middle finger.")]
public PointingState Middle = PointingState.Either;
/** The required ring finger state. */
[Tooltip("Required state of the ring finger.")]
public PointingState Ring = PointingState.Either;
/** The required pinky finger state. */
[Tooltip("Required state of the little finger.")]
public PointingState Pinky = PointingState.Either;
/** How many fingers must be extended for the detector to activate. */
[Header("Min and Max Finger Counts")]
[Range(0,5)]
[Tooltip("The minimum number of fingers extended.")]
public int MinimumExtendedCount = 0;
/** The most fingers allowed to be extended for the detector to activate. */
[Range(0, 5)]
[Tooltip("The maximum number of fingers extended.")]
public int MaximumExtendedCount = 5;
/** Whether to draw the detector's Gizmos for debugging. (Not every detector provides gizmos.)
* @since 4.1.2
*/
[Header("")]
[Tooltip("Draw this detector's Gizmos, if any. (Gizmos must be on in Unity edtor, too.)")]
public bool ShowGizmos = true;
private IEnumerator watcherCoroutine;
void OnValidate() {
int required = 0, forbidden = 0;
PointingState[] stateArray = { Thumb, Index, Middle, Ring, Pinky };
for(int i=0; i= MinimumExtendedCount);
if(HandModel.IsTracked && fingerState){
Activate();
} else if(!HandModel.IsTracked || !fingerState) {
Deactivate();
}
}
} else if(IsActive){
Deactivate();
}
yield return new WaitForSeconds(Period);
}
}
//判断finger的state是否匹配
private bool matchFingerState (Finger finger, PointingState requiredState) {
return (requiredState == PointingState.Either) ||
(requiredState == PointingState.Extended && finger.IsExtended) ||
(requiredState == PointingState.NotExtended && !finger.IsExtended);
}
#if UNITY_EDITOR
void OnDrawGizmos () {
if (ShowGizmos && HandModel != null && HandModel.IsTracked) {
PointingState[] state = { Thumb, Index, Middle, Ring, Pinky };
Hand hand = HandModel.GetLeapHand();
int extendedCount = 0;
int notExtendedCount = 0;
for (int f = 0; f < 5; f++) {
Finger finger = hand.Fingers[f];
if (finger.IsExtended) extendedCount++;
else notExtendedCount++;
if (matchFingerState(finger, state[f]) &&
(extendedCount <= MaximumExtendedCount) &&
(extendedCount >= MinimumExtendedCount)) {
Gizmos.color = OnColor;
} else {
Gizmos.color = OffColor;
}
Gizmos.DrawWireSphere(finger.TipPosition.ToVector3(), finger.Width);
}
}
}
#endif
}
/** Defines the settings for comparing extended finger states */
public enum PointingState{Extended, NotExtended, Either}
}
OK,到目前为止,CoreAsset中所有的Detection类基本都介绍了。我们可以通过这些脚本检验手是否握拳、手指是否展开、手指是否沿指定方向、手掌是否沿指定方向等。