本次解析案例所需工具:XBOX2代,UnitykinectSDK版本:Kinect for unity sdk v2.9,unity版本:2017.4.35
1:新建unity工程,其他插件看项目需求需要再导入:2:新建场景,并创建空物体:GameManager,并在该物体上挂载图中对应的脚本:KinectManager是初始化体感设备接入数据的,必须要挂载。
3:获取体感设备得到的图像,在Update中实时检测(可能发生出界丢失图像的后果,所以需要实时监测)
if ( KinectManager.Instance.IsInitialized())
{
//获取体感设备监测到的实时图像
// GetComponent<UnityEngine.UI.RawImage>().texture = KinectManager.Instance.GetUsersClrTex();
//获取体感设备监测到的彩色图像 ,显示的非真是图像,表达能力有限,具体插上设备自己看!
GetComponent<UnityEngine.UI.RawImage>().texture = KinectManager.Instance.GetUsersLblTex();
}
4:手势识别检测代码:
using System;
using System.Collections.Generic;
using UnityEngine;
public class HandTrailRender : MonoBehaviour
{
private RectTransform canvasRectTransform;
public bool isHandLeft ;
float handTimer = 0.5f;
private float maxHandTimer = 0.5f;
#region 变量
private TrailRenderer trailrenderer;
Transform mSelfTrans;
/// <summary>
/// 线段的z轴坐标
/// </summary>
const float LINE_POS_Z = 10;
Vector2 maxPos;
#endregion
//定义跟踪的骨骼
private KinectInterop.JointType handRightType = KinectInterop.JointType.HandRight;
private KinectInterop.JointType handLeftType = KinectInterop.JointType.HandLeft;
//手部跟踪平滑度
private float distanceToCamera = 10f;
void Awake()
{
trailrenderer = transform.GetComponent<TrailRenderer>();
mSelfTrans = transform;
}
void Update()
{
//得到左右手的屏幕位置
Vector3 handrightPos = GetjointPos((int)handRightType);
Vector3 handLeftPos = GetjointPos((int)handLeftType);
transform.position = isHandLeft ? handLeftPos : handrightPos;
}
//获取手势的在屏幕上的坐标
private Vector3 GetjointPos(int handType)
{
if (KinectManager.Instance!=null&&KinectManager.Instance.IsInitialized())
{
if (KinectManager.Instance.IsUserDetected())
{
long userID= KinectManager.Instance.GetPrimaryUserID();
if (GameManager.instance.kinectUserIDs!=null&&GameManager.instance.kinectUserIDs.Count>0)
{
userID = GameManager.instance.kinectUserIDs[0];
}
else
{
userID = KinectManager.Instance.GetPrimaryUserID();
}
if (KinectManager.Instance.IsJointTracked(userID, handType))
{
//得到手势坐标
Vector3 handPos = KinectManager.Instance.GetJointKinectPosition(userID, handType);
if (handPos != Vector3.zero)
{
// 3 d位置深度
Vector2 posDepth = KinectManager.Instance.MapSpacePointToDepthCoords(handPos);
ushort depthValue = KinectManager.Instance.GetDepthForPixel((int)posDepth.x, (int)posDepth.y);
if (depthValue > 0)
{
// 颜色pos深度pos
Vector2 posColor = KinectManager.Instance.MapDepthPointToColorCoords(posDepth, depthValue);
float xNorm = (float)posColor.x / KinectManager.Instance.GetColorImageWidth();
float yNorm = 1.0f - (float)posColor.y / KinectManager.Instance.GetColorImageHeight();
Vector3 vPosOverlay = Camera.main.ViewportToWorldPoint(new Vector3(xNorm, yNorm, distanceToCamera));
return vPosOverlay;
}
}
}
}
}
return Vector3.zero;
}
}
5:防止手势在不动的情况下被检测到碰到了物体可以在FixUpdate和Update中分别得到手势的坐标,当两个值在比较近的范围内视为没有动,值越大则在运动,可能有更好的方法判断是否在挥手,只是我没找到,反正我用自己的这个方法还行,至少达到我想要的效果;
Vector3 updatehandPos;
Vector3 fixupdatehandPos;
private KinectInterop.JointType handRightType = KinectInterop.JointType.HandRight;
void FixedUpdate()
{
fixupdatehandPos = GetVecDis();
}
void Update()
{
updatehandPos = GetVecDis();
if (Vector3.Distance(updatehandPos, fixupdatehandPos) > 0.05f)
print("手在动");
else
print("手未动");
}
private Vector3 GetVecDis()
{
if (KinectManager.Instance != null && KinectManager.Instance.IsInitialized())
{
if (KinectManager.Instance.IsUserDetected())
{
long userID = KinectManager.Instance.GetPrimaryUserID();
if (GameManager.instance.kinectUserIDs != null && GameManager.instance.kinectUserIDs.Count > 0)
{
userID = GameManager.instance.kinectUserIDs[0];
}
else
{
userID = KinectManager.Instance.GetPrimaryUserID();
}
if (KinectManager.Instance.IsJointTracked(userID, (int)handRightType))
{
//得到手势坐标
var handPos = KinectManager.Instance.GetJointKinectPosition(userID, (int)handRightType);
return handPos;
}
}
}
return Vector3.zero;
}
6:KinectSDK核心的代码都在KinectManager中,可以具体详看,这里不做解析,再看下实例话的代码如下,比较粗糙,能达到效果就行。。。。
using System.Collections;
using System.Collections.Generic;
using DG.Tweening;
using UnityEngine;
using UnityEngine.Diagnostics;
public class GameManager : MonoBehaviour
{
public GameObject handImage;
/// <summary>
/// 获得的积分
/// </summary>
[HideInInspector]
public float mGetScore { get; set; }
/// <summary>
/// 毒品元素实例化对象的父级
/// </summary>
public GameObject SpawnManager;
//画板边界定义
private int maxX = 800;
private int minX = -800;
private int minY = -650;
//定义水果上抛的力
private int forceX = 8;
private int forceY = 15;
int fruitCount ;
/// <summary>
/// [Header("炸弹的预设")]
/// </summary>
[Header("炸弹的预设")]
public GameObject bomb;
/// <summary>
/// 产生时间
/// </summary>
float spawnerTimer = ItemConfig.PRODUCEITEM_TIME;
bool isPlaying = true;
#region 单例以及游戏资源加载变量
/// <summary>
/// 单例对象
/// </summary>
public static GameManager instance = null;
/// <summary>
/// 读取游戏状态
/// </summary>
[HideInInspector]
public E_GameState mGameState;
[HideInInspector]
public List<DataModen> datasModen = null;
[HideInInspector]
public bool isHandMoveCut = false;
[HideInInspector]
public GameObject panelStar = null;
#endregion
private void Awake()
{
instance = this;
}
// Start is called before the first frame update
void Start()
{
fruitCount = Random.Range(1, 2);
Screen.SetResolution(1920,1080,true);
MusicMgr.Instance.PlayBkMusic("待机");
InitResetGame(true);
if (panelStar==null)
panelStar = Instantiate(Resources.Load<GameObject>("Prefabs/PanelStart"), SpawnManager.transform);
if (!System.IO.Directory.Exists(ItemConfig.LoadPath))
System.IO.Directory.CreateDirectory(ItemConfig.LoadPath);
mGameState = E_GameState.ready;
datasModen = UtilsJson<DataModen>.UseNetFrameReadJsonConfigFiles(ItemConfig.LoadPath, "ItemData.json");
if (datasModen == null || datasModen.Count <= 0)
print("MJ =======> 对象为空或者对象数量为0");
var timer = UtilsJson<GameDataTimer>.ReadJsonConfigFile(ItemConfig.LoadPath, "GameTimer.json");
#if UNITY_EDITOR
ItemConfig.MAX_GAMETIME = 10;
#elif UNITY_STANDALONE_WIN
ItemConfig.MAX_GAMETIME = timer.mTimer;
#endif
ItemConfig.COUNTDOWNTIME = timer.mCountdown;
Invoke("SetFruitCout",5f);
}
public void InitResetGame(bool isActive)
{
handImage.SetActive(isActive);
mGameState = isActive ? E_GameState.ready : E_GameState.inGame;
}
public void SetFruitCout()
{
fruitCount = Random.Range(1,5);
}
/// <summary>
/// 产生水果 炸弹 ,也能控制销毁
/// </summary>
private void OnSpwaner(bool isFruit)
{
//播放音乐
// audioSource.Play();
//实例化水果
int fruitIndex = Random.Range(0, datasModen.Count);
GameObject go;
int fruitX = Random.Range(minX, maxX);
go = Instantiate(bomb, new Vector3(fruitX, minY, 0), bomb.transform.rotation);
go.transform.SetParent(SpawnManager.transform);
//14是炸弹
int fIndex = isFruit ? Random.Range(0, 14) :14;
go.GetComponent<FruitItem>().SetType(fIndex);
RectTransform fruitRT = go.transform as RectTransform;
fruitRT.anchoredPosition3D = new Vector3(0, 0, 0);
fruitRT.anchoredPosition = new Vector2(fruitX, minY);
fruitRT.localScale = new Vector3(1, 1, 1);
//水果速度
// Vector3 velocity = new Vector3(-x * Random.Range(0.2f, 0.5f), -Physics.gravity.y * Random.Range(0.8f, 1.5f), 0);
Rigidbody2D rigidbody = go.transform.GetComponent<Rigidbody2D>();
float xxx = fruitX > 0 ? -Random.Range(1, forceX) : Random.Range(1, forceX);
rigidbody.velocity = new Vector2(xxx, Random.Range(forceY -3, forceY));
}
public List<long> kinectUserIDs;
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.Escape))
{
Application.Quit();
}
try
{
if (mGetScore <= 0)
{
mGetScore = 0;
}
if (KinectManager.Instance.IsInitialized())
{
kinectUserIDs = KinectManager.Instance.GetAllUserIds();
for (int i = 0; i < kinectUserIDs.Count; i++)
{
print("当前用户列表=;;;;;" + kinectUserIDs[i]);
}
}
if (mGameState == E_GameState.inGame)
{
if (!isPlaying)
{
return;
}
spawnerTimer -= Time.deltaTime;
if (0 >= spawnerTimer)
{
//到时间就开始产生水果,产生多个水果
for (int i = 0; i < fruitCount; i++)
OnSpwaner(true);
spawnerTimer = ItemConfig.PRODUCEITEM_TIME;
//随机产生炸弹
int bombNum = Random.Range(0, 100);
if (bombNum > 70)
{
OnSpwaner(false);
}
}
}
}
catch (System.Exception)
{
}
}
}
7:sdk中手势识别有个枚举类型,定义了相当多的手势:
public enum Gestures
{
None = 0,
RaiseRightHand,//右手举起过肩并保持至少一秒
RaiseLeftHand,//左手举起过肩并保持至少一秒
Psi,//双手举起过肩并保持至少一秒
Tpose,//触摸
Stop,//双手下垂
Wave,//左手或右手举起来回摆动
Click,//左手或右手在适当的位置停留至少2.5秒
SwipeLeft,//右手向左挥
SwipeRight,//左手向右挥
SwipeUp,//左手或者右手向上挥
SwipeDown,//左手或者右手向下挥
RightHandCursor,//假手势,用来使光标随着手移动
LeftHandCursor,//假手势,用来使光标随着手移动
ZoomIn,//手肘向下,两手掌相聚至少0.7米,然后慢慢合在一起
ZoomOut,//手肘向下,左右手掌合在一起(求佛的手势),然后慢慢分开
Wheel,//想象一下你双手握着方向盘,然后左右转动
Jump,//在1.5秒内髋关节中心至少上升10厘米 (跳)
Squat,//在1.5秒内髋关节中心至少下降10厘米 (下蹲)
Push,//在1.5秒内将左手或右手向外推
Pull,//在1.5秒内将左手或右手向里拉
ShoulderLeftFront,//左肩前倾
ShoulderRightFront,//右肩前倾
LeanLeft, //身体向左倾斜
LeanRight, //身体向右倾斜
LeanForward,//身体向前倾斜
LeanBack,//身体向后倾斜
KickLeft,//踢左脚
KickRight,//踢右脚
Run,//跑
RaisedRightHorizontalLeftHand,//左手平举
RaisedLeftHorizontalRightHand,//右手平举
//自定义手势
UserGesture1 = 101,
UserGesture2 = 102,
UserGesture3 = 103,
UserGesture4 = 104,
UserGesture5 = 105,
UserGesture6 = 106,
UserGesture7 = 107,
UserGesture8 = 108,
UserGesture9 = 109,
UserGesture10 = 110,
}
8:打包注意事项,工程中测试没问题,打包出来不行,可能原因有很多,首先检查下打包后的目录与运行程序同级的目录中是否导入了Kinect相关的Dll文件,这些需要手动导入:文件在工程目录下,Assets的同级目录中可以找到。
9:体感设备的KinectSDK(非Unity)出现如下错误的解决方案:查看服务项是否开启,或查看电源以及线路是否正常;多次查到该问题的原因基本都是这两个问题。
最后该作品已经部署到湖南岳阳禁毒博物馆中,体感切水果互动项目,有想法的可以去体验体验,觉得有用的点个赞再走,有宝贵意见的欢迎大家留言提示,这里会针对性的进行更改并更新,感谢!!!