1 Star 0 Fork 0

CPU驱动 / Kick996

Create your Gitee Account
Explore and code with more than 12 million developers,Free private repositories !:)
Sign up
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
contribute
Sync branch
Cancel
Notice: Creating folder will generate an empty file .keep, because not support in Git
Loading...
README

这是我曾经参与做过的一个用Kinect 和 Unity 实现的一个Unity体感游戏, 我负责的是kinect部分

效果图如下 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

下面是项目的实现报告, 如果想看Kinect技术可以直接跳到 4.2 Kinect技术总结

项目地址

https://github.com/wyj16340227/Kick996

1 项目概况

1.1 项目简介

  • 1.1.1 游戏介绍

使用kinectuinity 3D引擎实现人机交互的AR闯关游戏:Kick996。 通过kinect获取玩家行为信息(包括动作、语音),通过算法转化为输入信息,控制角色动作完成交互。

  • 1.1.2游戏规则

玩家具有跳跃攻击移动等动作,每一关有不同的敌人,通过攻击动作消灭敌人,完成闯关。


1.2 项目目的

kinect是目前应用最广泛的VR外设之一,我们小组计划初步学习kinect,结合学过的Unity 3D知识,进行人机交互,实现一个具体的小游戏。通过这个学习过程了解目前Kinect应用的范围,瓶颈以及日后的应用空间,体会VR技术对于现实生活的影响。


1.3 主要技术

  • 1.3.1 kinect行为捕捉

通过kinect for windows固件完成用户行为信息读取,通过筛选数据获得交互信息,舍弃多余信息。并通过接口传输信息。

  • 1.3.2 unity 3D游戏设计

主要使用技术:刚体碰撞状态机工厂模式MVC模型消息发布模式

  • 1.3.3 语音识别

通过Speech Platform API完成识别,并通过算法进行筛选(正则表达式匹配),筛选出正确语音,完成输入。


2 设计

2.1 基本概念


2.2 框架

  • MVC模型

将基本框架分为ModelViewController三个部分。分别控制数据、视图、控制器。 Model单独存储数据结构,View记录视图中存在的对象及参数,Controller显示视图。


2.3 算法

  • 工厂模式

考虑到对象的销毁及产生需要占用大量资源,因此考虑使用对象工厂,通过控制生存状态,减少对象生产及销毁所造成的运算资源开销。 状态为死亡的对象存放在视图以外。

  • 消息发布模式

玩家将状态通报所有订阅对象,对象根据自己的状态选择将要执行的动作。


2.4 模型

  • 场景模型

使用Unity3D-Terrian组件进行场景构建。

  • 角色模型

角色模型选择在unity asset store上购买。

2.5 调查问卷

经过针对性的问卷调查(对象多为在校学生),约有73%的人(大三居多)认为学习压力比较繁重,其中有77%曾经过选择并有意向继续选择小游戏纾解压力。 通过对这些同学进行问询,普遍认为狂扁小朋友是一款有趣、能够纾解压力的游戏。 由于产品尚未制作完成,因此无法进行体验调查。


3 实现

3.1 技术难点

  • 3.1.1 实时性不强

kinect行为捕捉较为缓慢,很容易产生滞后感,当玩家动作过快,容易识别错误或无法识别动作,造成信息不准确及信息丢失的问题。很严重的影响游戏体验。

  • 3.1.2 游戏设计趣味性有待增强

由于游戏操作较为简单,因此需要优美的场景及丰富的剧情来增强可玩性。对于unity3D引擎来说,挑战巨大。

  • 3.1.3 语音识别

语音识别滞后性非常强,原则上来说,无法单独通过语音控制玩家动作。


3.2 解决方案

  • 3.2.1 语音识别
    • 通过语音与动作相结合,减少滞后性带来的不便。
    • 通过组合技来延长动作时间,为语音识别提供更多的识别时间,减少滞后性。
  • 3.2.2 游戏性增强
    • 丰富剧情。向其中加入丰富的幽默的字幕解说,来吸引玩家的注意力,其本质是通过解压游戏+看趣味故事来放松。
    • 压缩场景。过长的故事线只会让玩家愈发觉得游戏无聊,因此采取快速游戏的策略,在玩家的游戏热情尚未结束的时候结束游戏。避免越玩越无聊的情况的出现。

4 技术支持

4.1 游戏设计技术


4.1.1 MVC

  • 1.概念描述

Model-View-Controller三个部分。分别控制场景,显示以及数据。

  • 2.模式应用

在这里插入图片描述

对应关系为:

Object Character
Model Model
UIController View
Controller Controller
  • 3.优势

能够做到场景与对象完全分离,角色分工明确清晰。不会造成一个对象既要控制场景又要控制角色的情况,避免了很多设计上的冗余。


4.1.2 工厂模式

  • 1.概念描述

对于unity,重复的对象进行大量的DestroryCreate是一笔巨大的开销。因此,类似线程池,将对象存储在仓库中,在不使用时,将其SetActive(false);并放在摄像头的背面。使用时激活并放入场景中。

  • 2.实现逻辑
    • 得到对象

在这里插入图片描述

  • 销毁对象

在这里插入图片描述


4.1.3 单例模式

  • 1.概念描述

是unity中应用最为广泛的一种模式。顾名思义,即对某个对象,对于某个对象全局中只存在一个实例,其他队想要获得该对象的时候,都要通过它提供的方法GetInsitance来取得。

  • 2.模式应用

全局中有一个单例SceneController
事实上,Model SceneController UIController都应当作为单例。但是在设计中,ModelUIController放在了SceneController,中,因此可以通过在SceneController中添加Get方法来取得它们的实例。


4.1.4 多场景切换

  • 1.概念描述

由于游戏中需要多个场景,不同的场景需要不同的场景元素,因此在游戏设计中需要考虑多场景切换的问题。
有两种较为常用的场景切换方法。
第一种方法就是通过不停的切换摄像机的位置。通过将不同的布景放在各个不会互相干扰(摄像机不能同时拍摄到)的地方,由场景控制器来控制场景的变换。
第二种方法是创建多个场景,并通过方法Application.LoadLevel()来切换场景。这种方法设计起来更加的方便且实用,但是存在数据传递的问题。

  • 2.技术难点

采取第二种场景切换的方法,但是存在技术难点,即:当前场景的数据在切换到下一个场景的时候会销毁。
有两种解决方案。
第一种方案:在场景切换的时候将数据存放在本地文件中,在新场景加载出来后再从本地文件中读取数据。
第二种解决方案为:将数据挂载在一个对象身上,并使用DontDestroyOnLoad()方法使其在场景切换时不被销毁。只需将其放在摄像机看不到的地方即可。


4.1.5 发布者-订阅者模式

  • 1.概念描述

角色消息的交互是必要的。例如,在本游戏中,玩家的位置、状态信息就时刻影响着敌人的动作。如果在update里不断地获取玩家的状态,将是一件开销十分巨大的事情。因此,使用消息的发布-订阅模式。一方作为消息的发布方,一方作为消息的订阅方;只有在发布方状态更改了之后,才会向订阅者发布消息。

  • 2.模式应用

在本游戏中,有两组消息发布-接收者。分别是:

发布者 订阅者 消息内容
player enemy
SceneController
状态信息(位置、生命、魔法etc)
enemyFactory SceneController 敌人信息

在这里插入图片描述

由于场景控制器要时刻控制游戏进度,当消灭的敌人数目达到下一关要求的时候,才会跳转到下一关。因此,需要EnemyFactorySceneController发布信息。

  • 3.实现难点

由于C#语法中不能够继承多个类,因此无法让SceneController同时作为PlayerEnemyFactoryObserver,因此,在Suject中加入两个函数,实现分类订阅。

    //Player use it notify its observer
    public abstract void NotifyPlayer(PlayerState playerState);

    //EnemyFactory use it notify its observer
    public abstract void NotifyEnemy(int enemyNum, int bossNum);

4.2 Kinect体感技术

4.2.1 与Unity结合

使用 Kinect-package中间件,版本为2.13, 支持Kinect2.

这个包中间有很多Demo, 可以帮助快速上手项目, 其中重点Demo为:KinectGesturesDemo1 其中包括关于手势的检测。

其中脚本

  • Kinect Manager Kinect控制器, 当加载此脚本时, 将会连接kinect,处理数据流
  • Kinect Gesture Listener Kinect 姿势识别器, 当加载此脚本, 会进行姿势的监控
  • **CubeGestureListener.cs ** 这个是自己写的脚本, 用于处理Listener的消息

4.2.2 识别姿势

监听器介绍

Kinect 自带的识别器, 也就是 Kinect Gesture Listener

能被流畅的识别以下手势:

◎RaiseRightHand / RaiseLeftHand – 左手或右手举起过肩并保持至少一秒

◎Psi –双手举起过肩并保持至少一秒

◎Stop – 双手下垂.

◎Wave –左手或右手举起来回摆动

◎SwipeLeft – 右手向左挥.

◎SwipeRight – 左手向右挥.

◎SwipeUp / SwipeDown – 左手或者右手向上/下挥

◎ Click – 左手或右手在适当的位置停留至少2.5秒.

◎RightHandCursor / LeftHandCursor – 假手势,用来使光标随着手移动

◎ZoomOut – 手肘向下,左右手掌合在一起(求佛的手势),然后慢慢分开.

◎ZoomIn – 手肘向下,两手掌相聚至少0.7米,然后慢慢合在一起

◎Wheel –想象一下你双手握着方向盘,然后左右转动。

◎Jump –在1.5秒内髋关节中心至少上升10厘米

◎Squat -在1.5秒内髋关节中心至少下降10厘米

◎Push – 在1.5秒内将左手或右手向外推

◎Pull -在1.5秒内将左手或右手向里拉

接口介绍

在CubeGestureListener里需要这一些函数 进行姿势处理:

UserDetected()用于启动手势检测;

UserLost()用于清理变量或者占用的资源。你并不需要移除UserDetected()中添加的手势。这些将会在调用UserLost()前自动清除。

GestureInProgress()在一个手势开始但是还没有被结束或者取消时调用。

GestureCompleted()在一个手势结束时调用。你可以在这里添加自己的代码来处理手势

GestureCancelled()手势被取消时调用

实现监听

在此脚本中我们首先需要开启检测的手势: 我们需要用到的手势分别是:

离散动作: 右手向左挥、右手向左挥、左手或者右手向上挥,,T字型

连续动作: 前倾,后倾,左倾,右倾等动作

    public void UserDetected(long userId, int userIndex)
	{
		// the gestures are allowed for the primary user only
		KinectManager manager = KinectManager.Instance;
		if(!manager || (userIndex != playerIndex))
			return;
		
		// detect these user specific gestures
		manager.DetectGesture(userId, KinectGestures.Gestures.SwipeLeft);
		manager.DetectGesture(userId, KinectGestures.Gestures.SwipeRight);
		manager.DetectGesture(userId, KinectGestures.Gestures.SwipeUp);
        manager.DetectGesture(userId, KinectGestures.Gestures.Tpose);
        manager.DetectGesture(userId, KinectGestures.Gestures.LeanLeft);
        manager.DetectGesture(userId, KinectGestures.Gestures.LeanRight);
        manager.DetectGesture(userId, KinectGestures.Gestures.LeanForward);
        manager.DetectGesture(userId, KinectGestures.Gestures.LeanBack);
        
		
	}

这样我们监听器就只会监听这些动作, 而其他动作检测到也不会触发。

Kinect检测源码分析

官方的监听器是这样检测左倾的, 因为Kinect会把人判断为26个骨头节点, 左倾就是左肩膀的节点距离地面的高度要小于右肩膀节点的高度

// check for ShoulderRightFront
case Gestures.ShoulderRightFront:
switch(gestureData.state)
{
    case 0:  // 左肩膀的节点距离地面的高度要小于右肩膀节点的高度。
        if(jointsTracked[leftShoulderIndex] && jointsTracked[rightShoulderIndex] && jointsTracked[rightHipIndex] &&
           (jointsPos[leftShoulderIndex].z - jointsPos[rightHipIndex].z) < 0f &&
           (jointsPos[leftShoulderIndex].z - jointsPos[rightShoulderIndex].z) > -0.15f)
        {
            SetGestureJoint(ref gestureData, timestamp, leftShoulderIndex, jointsPos[leftShoulderIndex]);
            gestureData.progress = 0.5f;
        }
        break;

    case 1:  // gesture phase 2 = complete
        if((timestamp - gestureData.timestamp) < 1.5f)
        {
            bool isInPose = jointsTracked[leftShoulderIndex] && jointsTracked[rightShoulderIndex] && jointsTracked[rightHipIndex] &&
                (jointsPos[leftShoulderIndex].z - jointsPos[rightShoulderIndex].z) < -0.2f;

            if(isInPose)
            {
                Vector3 jointPos = jointsPos[gestureData.joint];
                CheckPoseComplete(ref gestureData, timestamp, jointPos, isInPose, 0f);
            }
        }
        else
        {
            // cancel the gesture
            SetGestureCancelled(ref gestureData);
        }
        break;
}
break;

			// check for LeanLeft

4.2.3 连续姿势和离散姿势

其中离散动作 例如攻击,在这个函数里进行处理 GestureCompleted, 因为这些动作需要在完成时调用。而连续动作,例如前倾后倾, 则在GestureInProgress 进行处理, 因为在动作完成过程中,动作会发生变化,

离散动作识别

	public bool GestureCompleted (long userId, int userIndex, KinectGestures.Gestures gesture, 
	                              KinectInterop.JointType joint, Vector3 screenPos)
	{
		// the gestures are allowed for the primary user only
		if(userIndex != playerIndex)
			return false;

        sGestureText = gesture + " detected";
        Debug.Log( sGestureText);
        

        if (gesture == KinectGestures.Gestures.SwipeLeft)
			swipeLeft = true;
		if(gesture == KinectGestures.Gestures.SwipeRight)
			swipeRight = true;
	    if(gesture == KinectGestures.Gestures.SwipeUp)
			swipeUp = true;
        if (gesture == KinectGestures.Gestures.Tpose)
        {
            Tpose = true;
        }
        return true;
	}

首先我们设置4个bool变量swipeLeft、swipeRight、swipeUp、Tpose, 判断检测的姿势, 如果是对应姿势则会令这些变量为true, 但是因为是离散动作, 这样会导致当判断成功一次后, bool变量会一直变为true, 所以写一个接口, 当bool变量为为true时, 令它为false, 但是返回为true。 类似于 检测到一个动作后,变量还原

例如判断是否是 SwipeUp(向上滑)接口如下, 只要其他部分调用此接口,就能知道是否进行SwipeUp操作

连续动作识别

连续动作识别则不需要重新写一个接口, 因为状态会一直变化。 不过会有倾斜程度的参(screenPos.z ),为了游戏体验,当没有倾斜太大的时候,将倾斜小的认为是直立, 这样更加便于操控, 不然结果就是只要稍微左移就会一直操纵任务左移, 而直到右移才会一直往右边, 我们需要一个中立的状态才好。 然后其他部分只要读取这四个变量就知道用户是否左倾,右倾了。

	public void GestureInProgress(long userId, int userIndex, KinectGestures.Gestures gesture, 
	                              float progress, KinectInterop.JointType joint, Vector3 screenPos)
	{
        // the gestures are allowed for the primary user only
       


        if (userIndex != playerIndex)
			return;

	
		 if((gesture == KinectGestures.Gestures.Wheel || gesture == KinectGestures.Gestures.LeanLeft || 
		         gesture == KinectGestures.Gestures.LeanRight || gesture == KinectGestures.Gestures.LeanForward ||
                 gesture == KinectGestures.Gestures.LeanBack) && progress > 0.01f)
		{
			if(gestureInfo != null)
			{
                
                if (gesture == KinectGestures.Gestures.LeanLeft && screenPos.z > detectSense)
                {
                    sGestureText = "左转";

                    LeanLeft = true;
                } else
                {
                    LeanLeft = false;
                }
                    
                if (gesture == KinectGestures.Gestures.LeanRight && screenPos.z > detectSense)
                {
                    sGestureText = "右转";
                    LeanRight = true;
                }else
                {
                    LeanRight = false;
                }

                if (gesture == KinectGestures.Gestures.LeanForward && screenPos.z > detectSense)
                {
                    sGestureText = "前进";
                    LeanForward = true;
                }
                else
                {
                    LeanForward = false;
                }


                if (gesture == KinectGestures.Gestures.LeanBack && screenPos.z > detectSense + 10)
                {
                    sGestureText = "停止/后退";
                    LeanBack = true;
                }
                else
                {
                    LeanBack = false;
                }

                progressDisplayed = true;
				progressGestureTime = Time.realtimeSinceStartup;
			}
		}
	
	}

和游戏操控结合, 实现同步

人物移动

    void Update()
    {
        float v = Input.GetAxisRaw("Vertical");
        float h = Input.GetAxisRaw("Horizontal");

        if (slideChangeWithGestures && gestureListener)
        {

            if (gestureListener.LeanLeft)
            {
                h = -1;   //水平移动大小  负数为左边
            }
            if (gestureListener.LeanRight)
            {
                h = 1;
            }
            if (gestureListener.LeanForward)
            {
                v = 1; //水平移动大小  负数为下边
            }
            if (gestureListener.LeanBack)
            {
                v = -1;
            }
     
        }
    }

按键操控

if (slideChangeWithGestures && gestureListener)
{
	//检测动作  映射陈实体按键
    if (gestureListener.IsSwipeUp())
    {
        return KeyCode.J;   //攻击1
    }

    if (gestureListener.IsSwipeLeft())
    {
        return KeyCode.K;  //攻击2
    }
    if (gestureListener.IsTpose())
    {
        return KeyCode.R;  // 人物旋转180度
    }
    if (gestureListener.IsSwipeRight())
    {
        return KeyCode.Q; //向前瞬移
    }

    else
    {
        return KeyCode.None;
    }
}

5. 参考文献

Empty file

About

No description expand collapse
Cancel

Releases

No release

Contributors

All

Activities

Load More
can not load any more
1
https://gitee.com/jiwenfie/Kick996.git
git@gitee.com:jiwenfie/Kick996.git
jiwenfie
Kick996
Kick996
master

Search