请在前几讲的基础上进行

一、准备工作

1.在Assets>Scenes中新建场景,重命名为HandTrackingLearning,并打开。 2、打开Assets>NRSDK>Models>Hands>RightHand 3、将其另存为Paper.prefab、Rock.prefab和Scissors.prefab 4、打开Scissors,注意左侧的Hierarchy(层级)栏,下面解释一下各部分的含义(个人理解,并未找到官方说明,建议亲自实验):

wrist 手腕middle_1 中指从下数第1个关节middle_2 中指从下数第2个关节middle_3 中指第3个关节middle_end 中指指尖(作用不清楚) 接下来的各部分只是对应的指头不同,但含义相同pointer 食指ring 无名指thumb 大拇指pinky 小拇指 注意:pinky上方还有一级,名为:wristpadding,指的是小拇指下方、掌纹处的关节;同样,thumb_1实际上也不是通常意义上的大拇指第一节,而是大拇指的掌处关节。

5、将Scissors的手型调整为游戏“石头剪刀布”中“剪刀”的手型。 为了节约大家时间,大家可以通过本链接( 提取码:fs51)下载我调整好的手型,不过是在是有些难看,大家可以自行调整一下。 6、将Rock调整为“拳头”的手型。

二、正式开发

1、打开先前创建的HandTrackingLearning,删除场景中的Main Camera,新建NRCameraRig、NRInput和Canvas(注:前两个部件均位于Assets>NRSDK>Prefabs,拖入Hierarchy(层级)栏即可;新建Canvas的步骤为:在Hierarchy(层级)栏空白处右击,选择UI>Canvas(画布)。详见下面的若干图片。)。

删除和创建完成后,Hierarchy(层级)栏应如下图所示: 2、点开Canvas,将其Render Mode(渲染模式)改为World Space(世界坐标),然后重置其坐标。 4、打开NRInput,将Input Source Type改为Hands。 5、在游戏中,我们需要一个按钮来开始,所以在Canvas下面先创建一个空对象,命名为StartButton,再在空对象下创建一个按钮,方法如图:

创建好后如下图: 打开Text,修改文本和字体大小。(下图为推荐数据)

调整开始按钮的位置,使得能够在相机视角中看到它。(在我们完成整个项目后,认为将位置Z设为10,缩放设为0.05,0.05,1更为合适,所以请忽视下图的数据)。

6、与上述同理,在Canvas下再创建4个空对象,分别命名为Start、Win、Lose、Tie,将其位置改为x=0,y=30,z=350。四个对象下分别创建Text,内容改为Ready...3,2,1,Go!、You Win!、You Lost!、You’re Tied!,字体大小改为20,颜色改为白色(数据及颜色均为推荐数据和推荐颜色)。创建后如图所示:

7、分别打开Start、Win、Lose、Tie四个对象,在检查器一栏中取消勾选。效果如图所示。(注:在我们进行完全部开发后,我们认为将Start也取消勾选更为合适,但由于后面的教程图片已经完成,所以请您主动忽视跟此相关的问题)

编写脚本

1、在Assets下新建一个文件夹Scripts,并打开,新建C#脚本,重命名为MenuManager,用来控制这些提示语的出现。 打开该脚本,将以下代码编写到脚本中。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class MenuManager : MonoBehaviour

{

[SerializeField] GameObject[] menus;

public static MenuManager Instance;

private void Awake()

{

Instance = this;

}

public void OpenMenu(string menuName)

{

foreach(var item in menus)

{

item.SetActive(false);

}

foreach(var item in menus )

{

if(item.name==menuName)

{

item.SetActive(true);

break;

}

}

}

}

2、创建一个名为RPSHand的脚本,用来写判断类。编写以下代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using NRKernal;

public class RPSHand : MonoBehaviour

{

public string name;

public virtual string Judge(string _name)

{

return null;

}

}

3、创建一个名为RPSManager的脚本,用来编写我们需要的游戏逻辑。编写以下代码:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using NRKernal;

using UnityEngine.UI;

using System.Globalization;

public class RPSManager : MonoBehaviour

{

[SerializeField] GameObject[] hands;

[SerializeField] Text gestureTxt;

private bool isPlaying;

private GameObject currentHand;

public void OnClick()

{

if (isPlaying) return;

isPlaying = true;

foreach(var item in hands)

{

item.SetActive(false);

}

MenuManager.Instance.OpenMenu("Start");

StartCoroutine("DelayHand");

}

IEnumerator DelayHand()

{

yield return new WaitForSeconds(3);

currentHand = hands[Random.Range(0, hands.Length)];

currentHand.SetActive(true);

if(currentHand.GetComponent().Judge(gestureTxt.text)=="win")

{

MenuManager.Instance.OpenMenu("Win");

}

else if(currentHand.GetComponent().Judge(gestureTxt.text) == "lose")

{

MenuManager.Instance.OpenMenu("Lose");

}

else

{

MenuManager.Instance.OpenMenu("Tie");

}

isPlaying= false;

}

}

4、接下来是三种手势的判断逻辑,同样是在Scripts的文件夹下,分别创建三个脚本,RockHand,PaperHand和ScissorHand。三个脚本分别如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class RockHand : RPSHand

{

public override string Judge(string _name)

{

if (_name == "R:Paper")

{

return "win";

}

else if (_name == "R:Rock")

{

return "tie";

}

else return "lose";

}

}

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class PaperHand : RPSHand

{

public override string Judge(string _name)

{

if (_name == "R:Paper")

{

return "tie";

}

else if (_name == "R:Rock")

{

return "lose";

}

else return "win";

}

}

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

public class ScissorHand : RPSHand

{

public override string Judge(string _name)

{

if (_name == "R:Paper")

{

return "lose";

}

else if (_name == "R:Rock")

{

return "win";

}

else return "tie";

}

}

四、脚本使用

1、在Hierarchy(层级)栏中点开Canvas,将脚本MenuManager拖入,点击Menus,点击4次加号,将Hierarchy(层级)栏中Start、Win、Lose和Tie依次拖入Menus的四个元素。点击Add Component(添加组件),搜索组件Canvas Raycast Target并添加。 2、新建一个空对象并命名为GameManager,将先前创建的三个手势模型拖入GameManager下作为子对象,然后调整三个子对象的模型位置直至出现在合适的视角范围内。(注:我们建议将三个手掌的位置全部设为(x=0,y=0,z=0),这是一个更好的位置。由于这是我们在完成整个项目开发之后发现的,所以后面的图片全部都存在与此相关的问题,请您忽视)。 3、分别打开上述三个模型子对象,分别添加名为Animator的组件,再添加对应的控制脚本文件,最后取消勾选最上方的选项。 4、将RPSManager关联到GameManager这个对象上,并将三个手势模型添加到Hands下面。

5、在资产中搜索NRHand,将NRHand_L和NRHand_R分别拖入NRInput中作为其中Right和Left的子对象。

五、手势识别

1、在资产中,搜索HandTracking,将名为HandTracking的场景拖入层级栏中。将HandTracking场景的NRInput>Right(或Left)>NRHand_R(NRHand_L)>GestureSimpleTip_R(GestureSimpleTip_L)复制,粘贴为HandTrackingLearning场景中NRInput>Right(或Left)>NRHand_R(NRHand_L)的子对象。 2、在层级栏中移除整个HandTracking场景。 3、在Assets>Scripts中新建脚本GestureRPSTip,内容如下:

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using NRKernal;

using NRKernal.NRExamples;

using System.Runtime;

public class GestureRPSTip : GestureSimpleTip

{

public class GestureRPSName

{

public const string Gesture_Point = "Point";

public const string Gesture_Rock = "Rock";

public const string Gesture_Scissor = "Scissors";

public const string Gesture_Paper = "Paper";

}

public override void UpdateGestureTip()

{

var handState = NRInput.Hands.GetHandState(handEnum);

if (handState == null)

return;

switch (handState.currentGesture)

{

case HandGesture.Point:

gestureTxt.text = string.Empty;

break;

case HandGesture.Grab:

gestureTxt.text = GetHandEnumLabel() + GestureRPSName.Gesture_Rock;

break;

case HandGesture.Victory:

gestureTxt.text = GetHandEnumLabel() + GestureRPSName.Gesture_Scissor;

break;

case HandGesture.OpenHand:

gestureTxt.text = GetHandEnumLabel() + GestureRPSName.Gesture_Paper;

break;

default:

gestureTxt.text = string.Empty;

break;

}

if (handState.isTracked)

{

Pose palmPose;

if (handState.jointsPoseDict.TryGetValue(HandJointID.Palm, out palmPose))

{

UpdateAnchorTransform(palmPose.position);

}

tipAnchor.gameObject.SetActive(!string.IsNullOrEmpty(gestureTxt.text));

}

else

{

tipAnchor.gameObject.SetActive(false);

}

}

}

4、在资产中寻找GestureSimpleTip脚本并打开。 将代码第33行private改为virtual public,81、95行private改为virtual public。 5、打开两个GestureSimpleTip对象,将原有的Gesture Simple Tip组件移除,再将新创建好的GestureRPSTip文件关联上,调整Hand Fnum(左手选Left Hand,右手选Right Hand),再将层级栏中的NRInput>Right(Left)>NRHand_R(NRHand_L)>GestureSimpleTip_R(GestureSimpleTip_L)>TipAnchor和NRInput>Right(Left)>NRHand_R(NRHand_L)>GestureSimpleTip_R(GestureSimpleTip_L)>TipAnchor>Canvas>PanelBg>GestureTxt两个对象拖入Tip Anchor和Gesture Txt。 6、在Assets下新建文件夹Controller,进入后新建三个动画控制器,命名为Paper、Rock、Scissor。

7、将三个动画控制器拖入相应模型的Animator的控制器中。 8、打开Canvas>StartButton>Button,找到鼠标点击,点击+,将GameManager拖入,最后选择RPSManager.OnClick。

六、导出文件

像以往一样将文件导出,在Nreal Light中运行即可。

七、后记

由于我们教程的制作也是在学习他人教程的同时进行的,在本教程的制作过程中,我们参考的教程出现多处操作没有进行说明,导致我们最后的项目一度处于停滞不前的状态。在经过多日的研究、查阅资料后,我们才总算解决所有问题,利用markdown写下了这篇9100余字的教程。由于过程中我们也有部分步骤由于没有经验而出错,所以图片上会存在问题。如有任何需要帮助的地方,欢迎留言。

推荐文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。