本篇主要说的是Manomotion sdk的简单使用,包括简单的手势交互,平滑移动等内容。
Manomotion sdk的安装见另一篇博客:
Manomotion手势识别unity sdk安装与发布_充电ing...的博客-CSDN博客
本样例测试在windows端
在打开给出的Manomotiosdkpro场景,链接摄像头发现能运行,说明已完成导入,接下来进行简单的调用。
新建一个场景File->New Scene
将Assets下的ManomotionManager,ManomotionVisualization,ManomotionCanvas,GizmoCanvas,SkeletonManager拉到场景中
将场景中的Main Camera拉入到ManomotionManager下的Mano Utils中,并将ManomotionCanvas下的statusAnimator拉到ManomotionManager下的ManoEvents中
将Main Camera拉到ManoVisualization下的ManoVisualization中,并将ManomotionCanva下的杂七杂八的东西给勾掉
同样吧GizmoCanvas下的杂项也给勾掉,只留后面会用的SmoothingSlider,同时新建几个物体拉到适当的位置
新建脚本ManoObjInteraction挂载到Main Camera下,之后开始写脚本调用sdk与场景中的物体交互,首先进行物体的选择,这里采用射线检测的方法,再新建一个脚本BoxColliderGizmo
其中,运行时,场景左下角会显示摄像头输入信息,为了方便将手势识别的结果显示在这里,在这里会看到发出的射线,BoxColliderGizmo脚本主要是射线的材质,以及选中后对物体进行方框描边,代码如下:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//namespace
//{
/// <summary>
/// 描边框效果
/// </summary>
[RequireComponent(typeof(BoxCollider))]
public class BoxColliderGizmo : MonoBehaviour
{
public bool isDraw = true;
void OnRenderObject()
{
if (isDraw)
{
DrawOutLineGizmos();
}
}
public void DrawOutLineGizmos()
{
var colliders = gameObject.GetComponents<BoxCollider>();
if (colliders == null)
{
return;
}
CreateLineMaterial();
lineMaterial.SetPass(0);
GL.PushMatrix();
GL.MultMatrix(transform.localToWorldMatrix);
for (int i = 0; i < colliders.Length; i++)
{
var col = colliders[i];
var c = col.center;
var size = col.size;
float rx = size.x / 2f;
float ry = size.y / 2f;
float rz = size.z / 2f;
Vector3 p0, p1, p2, p3;
Vector3 p4, p5, p6, p7;
p0 = c + new Vector3(-rx, -ry, rz);
p1 = c + new Vector3(rx, -ry, rz);
p2 = c + new Vector3(rx, -ry, -rz);
p3 = c + new Vector3(-rx, -ry, -rz);
p4 = c + new Vector3(-rx, ry, rz);
p5 = c + new Vector3(rx, ry, rz);
p6 = c + new Vector3(rx, ry, -rz);
p7 = c + new Vector3(-rx, ry, -rz);
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p0);
GL.Vertex(p1);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p1);
GL.Vertex(p2);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p2);
GL.Vertex(p3);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p0);
GL.Vertex(p3);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p4);
GL.Vertex(p5);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p5);
GL.Vertex(p6);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p6);
GL.Vertex(p7);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p4);
GL.Vertex(p7);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p0);
GL.Vertex(p4);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p1);
GL.Vertex(p5);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p2);
GL.Vertex(p6);
GL.End();
GL.Begin(GL.LINES);
GL.Color(Color.red);
GL.Vertex(p3);
GL.Vertex(p7);
GL.End();
}
GL.PopMatrix();
}
static Material lineMaterial;
static void CreateLineMaterial()
{
if (!lineMaterial)
{
Shader shader = Shader.Find("Hidden/Internal-Colored");
lineMaterial = new Material(shader);
lineMaterial.hideFlags = HideFlags.HideAndDontSave;
lineMaterial.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
lineMaterial.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
lineMaterial.SetInt("_Cull", (int)UnityEngine.Rendering.CullMode.Off);
lineMaterial.SetInt("_ZWrite", 0);
}
}
}
//}
在ManoObjInteraction脚本中调用手势识别的结果并进行射线检测,为了直观,先画出整个手势识别的结果(文章最后会给出整个脚本的代码),这部分代码为
//OnEnable
for (int i = 0; i < 21; i++)
{
point[i] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
point[i].SetActive(true);
point[i].transform.localScale = new Vector3(0.005f, 0.005f, 0.005f);
Renderer render = point[i].GetComponent<Renderer>();
render.material.SetColor("_Color", Color.red);
}
//Update
//初始化坐标
for (int i = 0; i < 21; i++)
{
point[i].transform.position = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[i];
}
//根据该坐标划线
for (int i = 1; i < 21; i++)
{
if (i != 4 && i != 8 && i != 12 && i != 16 && i != 20)
Debug.DrawLine(point[i].transform.position, point[i + 1].transform.position, Color.green);
}
Debug.DrawLine(point[0].transform.position, point[1].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[5].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[9].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[13].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[17].transform.position, Color.black);
射线检测代码为
public GameObject GetRayHitObject()
{
//起始点
ray.origin = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[8];
Debug.Log("起始位置" + ray.origin);
//方向
ray.direction = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[6] - ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[5];
Debug.Log("8号位方向" + ray.direction);
Debug.DrawLine(ray.origin, ray.origin + ray.direction *1000, Color.red);
//是否碰撞
bool isCollider = Physics.Raycast(ray, out RaycastHit hit, 1000);
if (isCollider)
{
SetRayObject(hit.collider.gameObject);
}
else
{
ResetRayObject();
}
//返回检测到的物体
return hit.collider.gameObject;
}
public void SetRayObject(GameObject newGameObject)
{
SelectGameObject = newGameObject;
if (SelectGameObject != null)
{
if (SelectGameObject.GetComponent<BoxColliderGizmo>() == null)
{
SelectGameObject.AddComponent<BoxColliderGizmo>().isDraw = true;
}
else
{
SelectGameObject.GetComponent<BoxColliderGizmo>().isDraw = true;
}
}
}
public void ResetRayObject()
{
if (SelectGameObject != null)
{
if (SelectGameObject.GetComponent<BoxColliderGizmo>() == null)
{
SelectGameObject.AddComponent<BoxColliderGizmo>().isDraw = false;
}
else
{
SelectGameObject.GetComponent<BoxColliderGizmo>().isDraw = false;
}
SelectGameObject = null;
}
}
//Update
//当手姿为指向姿势时,获取指向的物体
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.POINTER_GESTURE)
{
mControObj = GetRayHitObject();
}
选中后对物体进行操作,拇指与食指点击姿势进行物体的显示与隐藏
void OnClick()
{
if (mControObj.activeInHierarchy)
{
mControObj.SetActive(false);
}
else
{
mControObj.SetActive(true);
}
}
//Update
//当触发点击手势时,触发点击事件
ManoGestureTrigger curState = ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_trigger;
if (curState != lastState)
{
if (curState == ManoGestureTrigger.CLICK)
{
OnClick();
}
lastState = curState;
}
保持食指与拇指闭合并移动,对物体进行旋转
public void OnDraging()
{
Vector3 vPos = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.poi;
Vector3 sPos = Camera.main.ViewportToScreenPoint(vPos);
if (lastHandPos == Vector3.zero)
{
lastHandPos = sPos;
return;
}
float offsetX = sPos.x - lastHandPos.x;
mControObj.transform.Rotate(-Vector3.up * offsetX * 0.5f, Space.Self);//绕Y轴进行旋转
lastHandPos = sPos;
}
//Update
//当前手势为捏住状态时,持续触发拖拽事件,否则结束拖拽
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.HOLD_GESTURE)
{
OnDraging();
}
握拳姿势并移动,对物体进行移动
public void OnMoving()
{
TrackingInfo tracking = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info;
Vector3 vPos = tracking.poi;
Vector3 sPos = Camera.main.ViewportToScreenPoint(new Vector3(vPos.x, vPos.y, tracking.depth_estimation * 1000));
if (lastHandPos == Vector3.zero)
{
lastHandPos = sPos;
return;
}
float offsetX = sPos.x - lastHandPos.x;
float offsetY = sPos.y - lastHandPos.y;
float offsetZ = sPos.z - lastHandPos.z;
mControObj.transform.position += new Vector3(offsetX, offsetY, offsetZ);
lastHandPos = sPos;
}
//Update
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.CLOSED_HAND_GESTURE)
{
OnMoving();
}
完整代码如下
using UnityEngine.UI;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ManoObjInteraction : MonoBehaviour
{
public string des;
public GameObject mControObj;
GameObject[] point = new GameObject[21];
private Ray ray;
private LineRenderer line;
private GameObject SelectGameObject = null;
Vector3 lastHandPos = Vector3.zero;
ManoGestureTrigger lastState;
private void Update()
{
ManomotionManager.Instance.ShouldCalculateSkeleton3D(true);
GizmoManager.Instance.ShouldDisplaySmoothingSlider(true);
for (int i = 0; i < 21; i++)
{
point[i].transform.position = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[i];
}
for (int i = 1; i < 21; i++)
{
if (i != 4 && i != 8 && i != 12 && i != 16 && i != 20)
Debug.DrawLine(point[i].transform.position, point[i + 1].transform.position, Color.green);
}
Debug.DrawLine(point[0].transform.position, point[1].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[5].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[9].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[13].transform.position, Color.black);
Debug.DrawLine(point[0].transform.position, point[17].transform.position, Color.black);
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.POINTER_GESTURE)
{
mControObj = GetRayHitObject();
}
//当触发点击手势时,触发点击事件
ManoGestureTrigger curState = ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_trigger;
if (curState != lastState)
{
if (curState == ManoGestureTrigger.CLICK)
{
OnClick();
}
lastState = curState;
}
//当前手势为捏住状态时,持续触发拖拽事件,否则结束拖拽
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.HOLD_GESTURE)
{
OnDraging();
}
if (ManomotionManager.Instance.Hand_infos[0].hand_info.gesture_info.mano_gesture_continuous == ManoGestureContinuous.CLOSED_HAND_GESTURE)
{
OnMoving();
}
}
private void OnEnable()
{
for (int i = 0; i < 21; i++)
{
point[i] = GameObject.CreatePrimitive(PrimitiveType.Sphere);
point[i].SetActive(true);
point[i].transform.localScale = new Vector3(0.005f, 0.005f, 0.005f);
Renderer render = point[i].GetComponent<Renderer>();
render.material.SetColor("_Color", Color.red);
}
}
private void OnDisable()
{
lastHandPos = Vector3.zero;
}
#region CallBack
void OnClick()
{
if (mControObj.activeInHierarchy)
{
mControObj.SetActive(false);
}
else
{
mControObj.SetActive(true);
}
}
public void OnDraging()
{
Vector3 vPos = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.poi;
Vector3 sPos = Camera.main.ViewportToScreenPoint(vPos);
if (lastHandPos == Vector3.zero)
{
lastHandPos = sPos;
return;
}
float offsetX = sPos.x - lastHandPos.x;
mControObj.transform.Rotate(-Vector3.up * offsetX * 0.5f, Space.Self);//绕Y轴进行旋转
lastHandPos = sPos;
}
public void OnMoving()
{
TrackingInfo tracking = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info;
Vector3 vPos = tracking.poi;
Vector3 sPos = Camera.main.ViewportToScreenPoint(new Vector3(vPos.x, vPos.y, tracking.depth_estimation * 1000));
if (lastHandPos == Vector3.zero)
{
lastHandPos = sPos;
return;
}
float offsetX = sPos.x - lastHandPos.x;
float offsetY = sPos.y - lastHandPos.y;
float offsetZ = sPos.z - lastHandPos.z;
mControObj.transform.position += new Vector3(offsetX, offsetY, offsetZ);
lastHandPos = sPos;
}
public void SetRayObject(GameObject newGameObject)
{
SelectGameObject = newGameObject;
if (SelectGameObject != null)
{
if (SelectGameObject.GetComponent<BoxColliderGizmo>() == null)
{
SelectGameObject.AddComponent<BoxColliderGizmo>().isDraw = true;
}
else
{
SelectGameObject.GetComponent<BoxColliderGizmo>().isDraw = true;
}
}
}
public void ResetRayObject()
{
if (SelectGameObject != null)
{
if (SelectGameObject.GetComponent<BoxColliderGizmo>() == null)
{
SelectGameObject.AddComponent<BoxColliderGizmo>().isDraw = false;
}
else
{
SelectGameObject.GetComponent<BoxColliderGizmo>().isDraw = false;
}
SelectGameObject = null;
}
}
public GameObject GetRayHitObject()
{
ray.origin = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[8];
Debug.Log("起始位置" + ray.origin);
ray.direction = ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[6] - ManomotionManager.Instance.Hand_infos[0].hand_info.tracking_info.skeleton.joints[5];
Debug.Log("8号位方向" + ray.direction);
Debug.DrawLine(ray.origin, ray.origin + ray.direction *1000, Color.red);
bool isCollider = Physics.Raycast(ray, out RaycastHit hit, 1000);
if (isCollider)
{
SetRayObject(hit.collider.gameObject);
}
else
{
ResetRayObject();
}
return hit.collider.gameObject;
}
#endregion
}
之后就可以运行测试了,而运行时会发现,game面板总是摄像头输入的信息,可以在ManoVisualization中将Show BackgroundLayer给勾掉就可以显示场景内容了
此时我们还会发现,抖动严重,这是因为每帧更新一次位置,而manomotion还内置了平滑移动,这就是之前拉到场景中的GizmCanvas中的内容了,就是显示的那个滑动条,拉动滑动条就可以控制缓慢的速度,让结果缓缓飘过去,就不会有抖动了。而代码也在前面给出的ManoObjInteration脚本中了,就是下面这行。
GizmoManager.Instance.ShouldDisplaySmoothingSlider(true);
在下载manomotion sdk时除了两个unity package外还有一个说明文档,里面还有更多内容,有兴趣的读者还请自行探索。