[洪流学堂]Hololens开发高级篇2:手势(Gesture)
来源:互联网 发布:剑侠情缘数据互通 编辑:程序博客网 时间:2024/05/06 17:56
本教程基于Unity2017.2及Visual Studio 2017
本教程编写时间:2017年12月7日
本文内容提要
- 当跟踪到用户的手时提供反馈
- 使用导航手势旋转hologram
- 当用户的手要离开视线时提供反馈
- 使用操作事件(manipulation events)让用户使用手势移动hologram
预备知识
- 开发环境
- 已完成入门篇的学习
- 已完成高级篇1凝视的学习
资源下载
本文使用了官方教程的资源
原地址
如果下载有困难,百度云地址
0 创建工程
- 将下载资源的解压出来
- 打开Unitiy,选择Open,选择HolographicAcademy-Holograms-211-Gesture\Starting\ModelExplorer目录并打开
- 在Project面板中Scenes目录下,双击打开ModelExplorer场景
- 打开菜单File > Build Settings,选择Universal Windows Platform,点击Switch Platform按钮
- Target device设置为Hololens,选中Unity C# Projects
- 在Build Settings面板,点击Build,新建
App
文件夹并选择该文件夹 - Build完成后,打开App文件夹下的ModelExplorer.sln,将Debug改为Release,ARM改为x86,并选中Hololens Emulator
- 点击调试 > 开始执行(不调试)或者Ctrl+F5(注意:模拟器启动慢可能会引起部署超时,这时候不要关闭模拟器,直接再次Ctrl+F5即可)
1 手部检测反馈
- 在Hierarchy面板中选择Managers物体,在Inspector面板中点击Add Component按钮,搜索Hands Manager并添加
- 在Hierarchy面板中选择Cursor物体,在Inspector面板中点击Add Component按钮,搜索Cursor Feedback并添加
- 将Project面板Assets\HoloToolkit-Gesture-211\Input\Prefabs目录下的HandDetectedFeedback资源拖到Inspector面板的Hand Detected Asset属性上
- 在Hierarchy面板中,展开Cursor物体,将CursorBillboard物体拖到Cursor Feedback (脚本)的Feedback Parent属性上
- build看一下效果!
在模拟器中,右键/空格/回车/alt按下并保持(不抬起),模拟Ready手势
2 导航
- 打开HandsManager.cs脚本,按照2.a注释提示补全代码,最终代码参考如下:
using UnityEngine;using UnityEngine.XR.WSA.Input;namespace Academy.HoloToolkit.Unity{ /// <summary> /// HandsManager keeps track of when a hand is detected. /// </summary> public class HandsManager : Singleton<HandsManager> { [Tooltip("Audio clip to play when Finger Pressed.")] public AudioClip FingerPressedSound; private AudioSource audioSource; /// <summary> /// Tracks the hand detected state. /// </summary> public bool HandDetected { get; private set; } // Keeps track of the GameObject that the hand is interacting with. public GameObject FocusedGameObject { get; private set; } void Awake() { EnableAudioHapticFeedback(); InteractionManager.InteractionSourceDetected += InteractionManager_InteractionSourceDetected; InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost; /* TODO: DEVELOPER CODE ALONG 2.a */ // 2.a: Register for SourceManager.SourcePressed event. InteractionManager.InteractionSourcePressed += InteractionManager_InteractionSourcePressed; // 2.a: Register for SourceManager.SourceReleased event. InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased; // 2.a: Initialize FocusedGameObject as null. FocusedGameObject = null; } private void EnableAudioHapticFeedback() { // If this hologram has an audio clip, add an AudioSource with this clip. if (FingerPressedSound != null) { audioSource = GetComponent<AudioSource>(); if (audioSource == null) { audioSource = gameObject.AddComponent<AudioSource>(); } audioSource.clip = FingerPressedSound; audioSource.playOnAwake = false; audioSource.spatialBlend = 1; audioSource.dopplerLevel = 0; } } private void InteractionManager_InteractionSourceDetected(InteractionSourceDetectedEventArgs obj) { HandDetected = true; } private void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs obj) { HandDetected = false; // 2.a: Reset FocusedGameObject. ResetFocusedGameObject(); } private void InteractionManager_InteractionSourcePressed(InteractionSourcePressedEventArgs hand) { if (InteractibleManager.Instance.FocusedGameObject != null) { // Play a select sound if we have an audio source and are not targeting an asset with a select sound. if (audioSource != null && !audioSource.isPlaying && (InteractibleManager.Instance.FocusedGameObject.GetComponent<Interactible>() != null && InteractibleManager.Instance.FocusedGameObject.GetComponent<Interactible>().TargetFeedbackSound == null)) { audioSource.Play(); } // 2.a: Cache InteractibleManager's FocusedGameObject in FocusedGameObject. FocusedGameObject = InteractibleManager.Instance.FocusedGameObject; } } private void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs hand) { // 2.a: Reset FocusedGameObject. ResetFocusedGameObject(); } private void ResetFocusedGameObject() { // 2.a: Set FocusedGameObject to be null. FocusedGameObject = null; // 2.a: On GestureManager call ResetGestureRecognizers // to complete any currently active gestures. GestureManager.Instance.ResetGestureRecognizers(); } void OnDestroy() { InteractionManager.InteractionSourceDetected -= InteractionManager_InteractionSourceDetected; InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost; // 2.a: Unregister the SourceManager.SourceReleased event. InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased; // 2.a: Unregister for SourceManager.SourcePressed event. InteractionManager.InteractionSourcePressed -= InteractionManager_InteractionSourcePressed; } }}
- 在Hierarchy面板中点击Cursor物体
- 将HoloToolkit\Input\Prefabs目录下的ScrollFeedback资源拖到Cursor Feedback (Script) 组件的Scroll Detected Asset属性上
- 在Hierarchy面板中,点击AstroMan物体,在Inspector面板中点击Add Component按钮,搜索Gesture Action并添加
- 打开GestureManager.cs脚本,按照代码中注释2.b的提示补全代码,最终代码参考如下:
using UnityEngine;using UnityEngine.XR.WSA.Input;namespace Academy.HoloToolkit.Unity{ public class GestureManager : Singleton<GestureManager> { // Tap and Navigation gesture recognizer. public GestureRecognizer NavigationRecognizer { get; private set; } // Manipulation gesture recognizer. public GestureRecognizer ManipulationRecognizer { get; private set; } // Currently active gesture recognizer. public GestureRecognizer ActiveRecognizer { get; private set; } public bool IsNavigating { get; private set; } public Vector3 NavigationPosition { get; private set; } public bool IsManipulating { get; private set; } public Vector3 ManipulationPosition { get; private set; } void Awake() { /* TODO: DEVELOPER CODING EXERCISE 2.b */ // 2.b: Instantiate the NavigationRecognizer. NavigationRecognizer = new GestureRecognizer(); // 2.b: Add Tap and NavigationX GestureSettings to the NavigationRecognizer's RecognizableGestures. NavigationRecognizer.SetRecognizableGestures( GestureSettings.Tap | GestureSettings.NavigationX); // 2.b: Register for the Tapped with the NavigationRecognizer_Tapped function. NavigationRecognizer.Tapped += NavigationRecognizer_Tapped; // 2.b: Register for the NavigationStarted with the NavigationRecognizer_NavigationStarted function. NavigationRecognizer.NavigationStarted += NavigationRecognizer_NavigationStarted; // 2.b: Register for the NavigationUpdated with the NavigationRecognizer_NavigationUpdated function. NavigationRecognizer.NavigationUpdated += NavigationRecognizer_NavigationUpdated; // 2.b: Register for the NavigationCompleted with the NavigationRecognizer_NavigationCompleted function. NavigationRecognizer.NavigationCompleted += NavigationRecognizer_NavigationCompleted; // 2.b: Register for the NavigationCanceled with the NavigationRecognizer_NavigationCanceled function. NavigationRecognizer.NavigationCanceled += NavigationRecognizer_NavigationCanceled; // Instantiate the ManipulationRecognizer. ManipulationRecognizer = new GestureRecognizer(); // Add the ManipulationTranslate GestureSetting to the ManipulationRecognizer's RecognizableGestures. ManipulationRecognizer.SetRecognizableGestures( GestureSettings.ManipulationTranslate); // Register for the Manipulation events on the ManipulationRecognizer. ManipulationRecognizer.ManipulationStarted += ManipulationRecognizer_ManipulationStarted; ManipulationRecognizer.ManipulationUpdated += ManipulationRecognizer_ManipulationUpdated; ManipulationRecognizer.ManipulationCompleted += ManipulationRecognizer_ManipulationCompleted; ManipulationRecognizer.ManipulationCanceled += ManipulationRecognizer_ManipulationCanceled; ResetGestureRecognizers(); } void OnDestroy() { // 2.b: Unregister the Tapped and Navigation events on the NavigationRecognizer. NavigationRecognizer.Tapped -= NavigationRecognizer_Tapped; NavigationRecognizer.NavigationStarted -= NavigationRecognizer_NavigationStarted; NavigationRecognizer.NavigationUpdated -= NavigationRecognizer_NavigationUpdated; NavigationRecognizer.NavigationCompleted -= NavigationRecognizer_NavigationCompleted; NavigationRecognizer.NavigationCanceled -= NavigationRecognizer_NavigationCanceled; // Unregister the Manipulation events on the ManipulationRecognizer. ManipulationRecognizer.ManipulationStarted -= ManipulationRecognizer_ManipulationStarted; ManipulationRecognizer.ManipulationUpdated -= ManipulationRecognizer_ManipulationUpdated; ManipulationRecognizer.ManipulationCompleted -= ManipulationRecognizer_ManipulationCompleted; ManipulationRecognizer.ManipulationCanceled -= ManipulationRecognizer_ManipulationCanceled; } /// <summary> /// Revert back to the default GestureRecognizer. /// </summary> public void ResetGestureRecognizers() { // Default to the navigation gestures. Transition(NavigationRecognizer); } /// <summary> /// Transition to a new GestureRecognizer. /// </summary> /// <param name="newRecognizer">The GestureRecognizer to transition to.</param> public void Transition(GestureRecognizer newRecognizer) { if (newRecognizer == null) { return; } if (ActiveRecognizer != null) { if (ActiveRecognizer == newRecognizer) { return; } ActiveRecognizer.CancelGestures(); ActiveRecognizer.StopCapturingGestures(); } newRecognizer.StartCapturingGestures(); ActiveRecognizer = newRecognizer; } private void NavigationRecognizer_NavigationStarted(NavigationStartedEventArgs obj) { // 2.b: Set IsNavigating to be true. IsNavigating = true; // 2.b: Set NavigationPosition to be Vector3.zero. NavigationPosition = Vector3.zero; } private void NavigationRecognizer_NavigationUpdated(NavigationUpdatedEventArgs obj) { // 2.b: Set IsNavigating to be true. IsNavigating = true; // 2.b: Set NavigationPosition to be obj.normalizedOffset. NavigationPosition = obj.normalizedOffset; } private void NavigationRecognizer_NavigationCompleted(NavigationCompletedEventArgs obj) { // 2.b: Set IsNavigating to be false. IsNavigating = false; } private void NavigationRecognizer_NavigationCanceled(NavigationCanceledEventArgs obj) { // 2.b: Set IsNavigating to be false. IsNavigating = false; } private void ManipulationRecognizer_ManipulationStarted(ManipulationStartedEventArgs obj) { if (HandsManager.Instance.FocusedGameObject != null) { IsManipulating = true; ManipulationPosition = Vector3.zero; HandsManager.Instance.FocusedGameObject.SendMessageUpwards("PerformManipulationStart", ManipulationPosition); } } private void ManipulationRecognizer_ManipulationUpdated(ManipulationUpdatedEventArgs obj) { if (HandsManager.Instance.FocusedGameObject != null) { IsManipulating = true; ManipulationPosition = obj.cumulativeDelta; HandsManager.Instance.FocusedGameObject.SendMessageUpwards("PerformManipulationUpdate", ManipulationPosition); } } private void ManipulationRecognizer_ManipulationCompleted(ManipulationCompletedEventArgs obj) { IsManipulating = false; } private void ManipulationRecognizer_ManipulationCanceled(ManipulationCanceledEventArgs obj) { IsManipulating = false; } private void NavigationRecognizer_Tapped(TappedEventArgs obj) { GameObject focusedObject = InteractibleManager.Instance.FocusedGameObject; if (focusedObject != null) { focusedObject.SendMessageUpwards("OnSelect"); } } }}
- 打开 GestureAction.cs脚本,按照代码中注释2.c的提示补全代码,最终代码参考如下:
using Academy.HoloToolkit.Unity;using UnityEngine;/// <summary>/// GestureAction performs custom actions based on /// which gesture is being performed./// </summary>public class GestureAction : MonoBehaviour{ [Tooltip("Rotation max speed controls amount of rotation.")] public float RotationSensitivity = 10.0f; private Vector3 manipulationPreviousPosition; private float rotationFactor; void Update() { PerformRotation(); } private void PerformRotation() { if (GestureManager.Instance.IsNavigating && (!ExpandModel.Instance.IsModelExpanded || (ExpandModel.Instance.IsModelExpanded && HandsManager.Instance.FocusedGameObject == gameObject))) { /* TODO: DEVELOPER CODING EXERCISE 2.c */ // 2.c: Calculate rotationFactor based on GestureManager's NavigationPosition.X and multiply by RotationSensitivity. // This will help control the amount of rotation. rotationFactor = GestureManager.Instance.NavigationPosition.x * RotationSensitivity; // 2.c: transform.Rotate along the Y axis using rotationFactor. transform.Rotate(new Vector3(0, -1 * rotationFactor, 0)); } } void PerformManipulationStart(Vector3 position) { manipulationPreviousPosition = position; } void PerformManipulationUpdate(Vector3 position) { if (GestureManager.Instance.IsManipulating) { /* TODO: DEVELOPER CODING EXERCISE 4.a */ Vector3 moveVector = Vector3.zero; // 4.a: Calculate the moveVector as position - manipulationPreviousPosition. // 4.a: Update the manipulationPreviousPosition with the current position. // 4.a: Increment this transform's position by the moveVector. } }}
- build测试一下吧!
按下alt键,同时鼠标右键按下并移动,可以模拟此操作
3 手部引导
- 在Hierarchy面板中选中Managers,在Inspector面板中点击Add Component按钮,搜索Hand Guidance并添加
- 将Project面板下 HoloToolkit-Gesture-211\Input\Prefabs 文件夹下的HandGuidanceFeedback 资源拖到Inspector面板的Hand Guidance Indicator属性上
- 在Hierarchy面板中展开Cursor物体
- 在Hierarchy面板中选中Managers物体,将Cursor的子物体CursorBillboard 拖到Inspector面板中的Indicator Parent 属性上
- build测试一下吧!
按住alt键,再按下鼠标右键并移动可以模拟此操作,但是模拟器中并不能模拟此功能
4 移动
使用使用手势来移动宇航员
移动手势可以使用时光标提供反馈
1. 在Hierarchy面板中选中Managers,在Inspector面板中点击Add Component按钮,搜索Astronaut Manager并添加
2. 在Hierarchy面板中选中 Cursor
3. 将Project面板中的 HoloToolkit-Gesture-211\Input\Prefabs文件夹下的PathingFeedback 资源拖到Inspector面板的Pathing Detected Asset属性上
4. 编辑 GestureAction.cs 脚本,按照代码中注释4.a的提示补全代码,最终代码参考如下:
using Academy.HoloToolkit.Unity;using UnityEngine;/// <summary>/// GestureAction performs custom actions based on /// which gesture is being performed./// </summary>public class GestureAction : MonoBehaviour{ [Tooltip("Rotation max speed controls amount of rotation.")] public float RotationSensitivity = 10.0f; private Vector3 manipulationPreviousPosition; private float rotationFactor; void Update() { PerformRotation(); } private void PerformRotation() { if (GestureManager.Instance.IsNavigating && (!ExpandModel.Instance.IsModelExpanded || (ExpandModel.Instance.IsModelExpanded && HandsManager.Instance.FocusedGameObject == gameObject))) { /* TODO: DEVELOPER CODING EXERCISE 2.c */ // 2.c: Calculate rotationFactor based on GestureManager's NavigationPosition.X and multiply by RotationSensitivity. // This will help control the amount of rotation. rotationFactor = GestureManager.Instance.NavigationPosition.x * RotationSensitivity; // 2.c: transform.Rotate along the Y axis using rotationFactor. transform.Rotate(new Vector3(0, -1 * rotationFactor, 0)); } } void PerformManipulationStart(Vector3 position) { manipulationPreviousPosition = position; } void PerformManipulationUpdate(Vector3 position) { if (GestureManager.Instance.IsManipulating) { /* TODO: DEVELOPER CODING EXERCISE 4.a */ Vector3 moveVector = Vector3.zero; // 4.a: Calculate the moveVector as position - manipulationPreviousPosition. moveVector = position - manipulationPreviousPosition; // 4.a: Update the manipulationPreviousPosition with the current position. manipulationPreviousPosition = position; // 4.a: Increment this transform's position by the moveVector. transform.position += moveVector; } }}
- build测试一下吧!
使用“Move Astronaut”语音控制从旋转切换为移动手势
5 模型展开/爆炸
将模型展开成碎片,每个小块都可以交互
每个碎片都可以单独旋转和移动
1. 编辑 AstronautManager.cs 脚本,按照注释5.a提示补全代码,最终代码如下:
using Academy.HoloToolkit.Unity;using System.Collections.Generic;using System.Linq;using UnityEngine;using UnityEngine.Windows.Speech;public class AstronautManager : Singleton<AstronautManager>{ float expandAnimationCompletionTime; // Store a bool for whether our astronaut model is expanded or not. bool isModelExpanding = false; // KeywordRecognizer object. KeywordRecognizer keywordRecognizer; // Defines which function to call when a keyword is recognized. delegate void KeywordAction(PhraseRecognizedEventArgs args); Dictionary<string, KeywordAction> keywordCollection; void Start() { keywordCollection = new Dictionary<string, KeywordAction>(); // Add keyword to start manipulation. keywordCollection.Add("Move Astronaut", MoveAstronautCommand); // Add keyword Expand Model to call the ExpandModelCommand function. keywordCollection.Add("Expand Model", ExpandModelCommand); // Add keyword Reset Model to call the ResetModelCommand function. keywordCollection.Add("Reset Model", ResetModelCommand); // Initialize KeywordRecognizer with the previously added keywords. keywordRecognizer = new KeywordRecognizer(keywordCollection.Keys.ToArray()); keywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized; keywordRecognizer.Start(); } private void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args) { KeywordAction keywordAction; if (keywordCollection.TryGetValue(args.text, out keywordAction)) { keywordAction.Invoke(args); } } private void MoveAstronautCommand(PhraseRecognizedEventArgs args) { GestureManager.Instance.Transition(GestureManager.Instance.ManipulationRecognizer); } private void ResetModelCommand(PhraseRecognizedEventArgs args) { // Reset local variables. isModelExpanding = false; // Disable the expanded model. ExpandModel.Instance.ExpandedModel.SetActive(false); // Enable the idle model. ExpandModel.Instance.gameObject.SetActive(true); // Enable the animators for the next time the model is expanded. Animator[] expandedAnimators = ExpandModel.Instance.ExpandedModel.GetComponentsInChildren<Animator>(); foreach (Animator animator in expandedAnimators) { animator.enabled = true; } ExpandModel.Instance.Reset(); } private void ExpandModelCommand(PhraseRecognizedEventArgs args) { // Swap out the current model for the expanded model. GameObject currentModel = ExpandModel.Instance.gameObject; ExpandModel.Instance.ExpandedModel.transform.position = currentModel.transform.position; ExpandModel.Instance.ExpandedModel.transform.rotation = currentModel.transform.rotation; ExpandModel.Instance.ExpandedModel.transform.localScale = currentModel.transform.localScale; currentModel.SetActive(false); ExpandModel.Instance.ExpandedModel.SetActive(true); // Play animation. Ensure the Loop Time check box is disabled in the inspector for this animation to play it once. Animator[] expandedAnimators = ExpandModel.Instance.ExpandedModel.GetComponentsInChildren<Animator>(); // Set local variables for disabling the animation. if (expandedAnimators.Length > 0) { expandAnimationCompletionTime = Time.realtimeSinceStartup + expandedAnimators[0].runtimeAnimatorController.animationClips[0].length * 0.9f; } // Set the expand model flag. isModelExpanding = true; ExpandModel.Instance.Expand(); } public void Update() { if (isModelExpanding && Time.realtimeSinceStartup >= expandAnimationCompletionTime) { isModelExpanding = false; Animator[] expandedAnimators = ExpandModel.Instance.ExpandedModel.GetComponentsInChildren<Animator>(); foreach (Animator animator in expandedAnimators) { animator.enabled = false; } } }}
- build来测试一下!
说“Expand Model”来展开模型
可以旋转单独的碎片
说“Move Astronaut”来移动碎片
说“Reset Model”来恢复模型整体
洪流学堂,最科学的Unity3d学习路线,让你快人一步掌握Unity3d开发核心技术!
阅读全文
0 0
- [洪流学堂]Hololens开发高级篇2:手势(Gesture)
- [洪流学堂]Hololens开发高级篇1:凝视(Gaze)
- [洪流学堂]Hololens开发高级篇3:语音(Voice)
- [洪流学堂]Hololens开发高级篇4:立体音效(Spatial sound)
- [洪流学堂]Hololens开发高级篇5:空间映射(Spatial mapping)
- [洪流学堂]Hololens开发入门篇2之Hello World
- [洪流学堂]Hololens开发入门篇1之模拟器开发环境配置
- [洪流学堂]Hololens开发入门篇3:使用基本功能开发一个小应用
- [洪流学堂]Hololens开发:Unity3d与Visual Studio最佳实践
- [洪流学堂]Hololens修改图标icon
- Hololens官方教程精简版 - 04. Gesture(手势)
- HoloLens开发笔记-资料整理-Input-Gesture
- 手势(Gesture)
- 手势(Gesture)
- Hololens开发(2)
- Hololens开发之手势输入
- WINCE 6.0 R3开发体验2-gesture手势识别
- ios开发学习-手势交互(Gesture)效果源码分享
- 2018年用户体验设计的10个趋势
- 介绍Hadoop家族产品
- 12周作业
- php打印输出
- wdatapicker 时间限制
- [洪流学堂]Hololens开发高级篇2:手势(Gesture)
- jpa多对多映射时(@ManyToMany)时堆溢出(java.lang.StackOverflowError: null )
- LoRa 凭什么传的远、信噪比高、误码率低?
- LaTex 公式
- Self-Supervised Learning for Stereo Matching with Self-Improving Ability
- php超强校验身份证类库,验证身份证有效性,根据国家标准GB 11643-1999 15位和18位通用
- 简易旋转倒立摆设计报告
- Linux常用操作
- Python----字符串操作与列表使用