欢迎来到 Magic Leap 的“闪避”课程

By | 2018年8月31日

这个课程是我们 Magic Kit 系列课程的一部分,通过学习这些课程会帮助您创建令人赞叹的混合现实内容。虽然这个领域还是一个从未有人探索的未知空间,但我们会通过分享我们的一些探索经验来帮助你更顺利地完成您的路程。在这里我们会分享一些有价值的经验和宝藏,可以让您的探索之旅更加安全和便利。

在这个课程中将会给你如何在屋里世界中使用网格头部姿态以及手势,当然在这个课程中我们专注于技术的实现,如果您对设计本身感兴趣,可以去设计日记里面寻找您需要的内容。

本课程的目标是给你的混合现实开发工具箱提供更多的工具,并在课程中确保您学习并会使用他们。在课程结束以后,你可以把内容放置到你房间的任何位置,并可以近距离的解除傀儡人,并尝试闪躲巨石。


准备

在使用 Magic Kit 之前,你需要先准备好在 Unity® 中配置 Magic Leap 工程。如果你有一台 Magic Leap One 设备,你需要确保已经生成 开发人员证书。当我们在设备中运行代码的时候,你需要此证书来 签署你的构建。如果你没有自己的设备,就去看下 Magic Leap 模拟器 教程。你可以把代码安装运行在 Magic Leap 模拟器上。

在阅读本教程之前,建议你可以先学习一下射线追踪,动态网格,以及手势操作。这些技能点会帮助你学习本课程。

当你准备好这些后,你就可以通过网络链接下载项目并在 Unity 中打开它。我们在将其构建到你的设备或者 Magic Leap 模拟器中,然后我们启动它。

你需要先拿到课程的源代码,请到网站 http://MrPP.com 上面,添加我们的微信公众号,然后回复“闪避”得到相应代码的下载地址。


我们将要做什么?

  1. 网格化房间
  2. 使用手势放置生成点
  3. 了解傀儡人
  4. 躲避巨石

网格化房间

Meshing the room

让我们从构建和运行这个课程来开始我们的工作,看看你房屋的四周, 会呈现出聚光灯效果网格。

                dodge mesh spotlight effect

这些特效除了很炫之外,还提供了关于世界网格当前状态的视觉反馈功能。你可以通过观察这些网格来了解房间内的哪些区域已经被扫描并闭合,哪些区域没有闭合(比如你发现视觉效果被切断或者消失的时候,可以断定这个区域并没有闭合),你可以通过查看设计日记,了解我们选择此效果的原因的详细信息。

如果你对构建网格的过程感到好奇,可以在游戏的场景中 [UTILITIES] 标题下找到 SpatialMapper 游戏对象。该对象是 Magic Leap Unity® 软件包的一部分,它可以在我们的项目中实现网格生成。也可以通过查看这个对象了解更多关于世界网格相关信息。

在这个时候,你也可以注意到才我们头的前方投射了一个大的立方体,这个是我们用于选择位置的光标。当这个光标和世界网格重合时侯,它的颜色会从红色转变为绿色。

                                                                                  image17

之后你可以学习下手势操作,能更清晰地了解现在我们所做的工作。但我们现在先从项目设置来开始这个课程。

程序状态

在应用程序里面,我们设置了三种主要状态:PlacementStateWaitState InteractionState。我们可以在场景中的[LOGIC] 节点下的 StateMachine 游戏对象中查找相应的代码组件。

  • 在 PlacementState 状态中,我们使用网格化和手势功能,在物理世界中放置三个 SpawnPoints (请注意,这是我们状态机处事的状态)。最终这些点将会被傀儡人所使用。
  • WaitState 是一个中间状态,和他的名字所说的一样,在准换到 InteractionState 状态之前等待几秒钟。
  • InteractionState 在状态中,我们的傀儡人会用他的巨石进行恶作剧,扔到物理世界的各个角落。

程序状态逻辑

如果你是一个完美主义者,一定想知道状态转换是如何工作的。这时候我们就需要看下 StateMachine State 这两个脚本的运作方式。在本课程中使用的脚本分别是 PlayspaceSetupCompleteTransition TimerTransition

1.TimerTransition 的作用是在延迟一段时间后转换到下一个状态,我们程序中的 WaitState 就是这个脚本起作用的。

                                                                                image11

2.InteractionState 是我们程序的最终状态,所以他没有后续状态,当这个状态被激活的时候,他通过GameObjectEnabler 组件来打开傀儡人。

                                                                                 image16

3.在这里最重要的状态是 PlacementState。一个 Placement 脚本负责处理了大量的工作。

                                                                                image22

                                                                                 image10

Placement 脚本

脚本是个可以完全定制的工具,用于帮助我们在运行期在物理世界上创建和放置物品。它将一个体积投射在世界网格中,以便我们找到一个可以放置该提及的合适表面。我们在组建上提供了丰富的设置参数,以允许用户根据自己的需要进行相应的定义。

这些设置包括(但不限于):

  • 光线投射的体积大小。
  • 是否可以放置在垂直表面。
  • 是否可以放置在水平表面。
  • 体积放置 ‘适合’ 和 ‘不适合’视觉效果。

                                                          image3

DodgePlacement 是我们挂接在 Placement 组件。他会监听 GestureManager 组件,并且在有手势操作的时候尝试放置虚拟物体。

PlayspaceSetupCompleteTransition 是用来监听 DodgePlacement 组件,来得合适放置了 SpawnPoint。 一旦PlayspaceSetupCompleteTransition 检测到已经放置了三个点,就会切换到下一个状态。

PlacementState 的另外一个任务是替换网格。还记得我们体验的网格特效么?这是使用了相应的网格材质的结果。我们在设计中只希望这个特效运行在 PlacementState 状态期间。如果你对我们如何在运行期替换材质感到好奇,可以看下MeshMaterialSwapper 组件。

如果想要离开 PlacementState 状态,我们需要提供相应的放置。所以让我们继续学习吧。

使用手势放置生成点

在运行这个课程代码的时候,观察一下当放置指示器成为绿色的时候,进行手势的啮合动作时会发生什么。回想一下,当我们观察一个世界上的平面的时候,指示器会成为绿色。

                                                                   FieldOfSensingExample

当你使用了捏合手势之后,你应该看到一个灰色的气球形指示器,周围还有一个红色的网格球体。这个灰色气球的指示器代表着你刚刚仿制的 SpawnPoint,红色的球体代表相应的 ProximityIndicator ,这个球体是为了保证所有生成点防止的物体不要重叠,具体的细节可以去查看下设计日记,以便了解更多的原理和相关的详细信息。

              dodge proximity indicator

接下来我们将要介绍更多关于生成点的内容,但首先让我们了解一些有趣的手势操作的知识。

    1.回到场景界面,在曾自结构中寻找 [LOGIC] 节点([LOGIC] > StateMachine > PlacementState > Placement)。你可以发现 DodgePlacement 组件包含着相关手的的掩码:Placement Request Hand Poses(放置请求手势)。我们的放置系统通过这个来确认是否放置。

    2.单机 Placement Request Hand Poses 选项,系统显示有哪些可以识别的手势,并且哪些手势在这个操作中有效。这里设置了 Pinch Ok 确认放置。看一下本课的设计日记,看看为什么我们选择这些手势。

                                                                   image8

    3.修改 Placement Request Hand Poses 选项,把 Thumb 设置为激活。

    4.重新部署课程。

恭喜你,现在可以通过竖起大拇指来放置生成点了。

值得一提的是,我们正在使用 ML 的手势跟踪 API 来实现相应的功能。如果你对这方面有更多的兴趣,可以查看下 Assets / Dodge / Helpers / Gestures 内的脚本。

生成点

现在我们已经了解了如何放置生成点,让我们先简单的了解下它们到底是什么东西。

据我了解,傀儡人是个内向的家伙,如果你企图靠近他,他就会躲到更远一点的地方。这些傀儡移动的点就是所谓的生成点。当傀儡人觉得你的存在威胁到他的时候,他会移动到距你较远的生成点上。

为了整个游戏更有趣,或者你希望挑战自我,你可以吧这些生成点放置在一些更加神奇的地方——不仅仅是墙壁和天花板——只要他不与另外一个生成点重合就可以了。你可以尝试放在书架的顶部,桌子下面,咖啡壶后面,以及任何你能想象得到的地方。

当我们设置生成点的工作完成之后,程序会跳转到WaitState状态中,少卿,进入 InteractionState 状态,是时候展示我们的技术了。


深入了解下傀儡人

如果有人接近你的私密空间之后,你会觉得不舒服么?当然傀儡人也有这个问题,他对你行为的反馈随着你和他的距离而改变。对于傀儡人而言,你和他的节能进去育有三个分类:舒适,不舒服,和请滚开。

  1. 当你站在距离傀儡人很远的地方的的时候,傀儡人向你抛投巨石。这时候他指导你并不构成任何威胁,因为他可以随时的逃脱你。
  2. 当你开始接近傀儡人的时候,他会感到害怕。
  3. 最后当你距离傀儡人太近,打破他的没一道防线,他会撤退到其他地方。

当你太过贴近的时候,我们让傀儡人离开的原因是放置你与虚拟影响物理交叉。因为这样会大大的损害傀儡人的物理完整性并影响你的沉浸感,相关的细节可以阅读平面处理的设计日记部分,以获得更深入的解释。

我们有两个参数可以用于判断傀儡人和我们的接近距离:头部位置手部位置。我们将在下一节(躲避巨石)中讨论如何追踪你的手,但是现在,只要了解傀儡人和你确定距离时候,是通过你的头部距离和手部距离加权平均计算出来的(例如,当傀儡人决定她是否要隐藏时候)。

public class GolemAi : MonoBehaviour

    {

        // …

        private bool InsideHideRange()

        {

            float playerDist = Vector3.Distance(_player.transform.position, transform.position);

            float playerLeftHandDist = Vector3.Distance(_player.leftHandPosition, transform.position);

            float playerRightHandDist = Vector3.Distance(_player.rightHandPosition, transform.position);

            float testDist = playerDist;

            testDist = Mathf.Min(testDist, playerLeftHandDist);

            testDist = Mathf.Min(testDist, playerRightHandDist);

            return testDist < _playerHideRange;

        }

        // …

    }

近视截面

关于头部位置的一个重要的特性就是近视截面,这代表着摄像机(头部位置)能显示的最小距离,如果再小雨这个距离的话,虚拟物体将无法显示。我们可以通过设置 Camera 组建的属性来调整这个距离。

需要注意的是,在本课程中,我们将近是界面距离设置为37厘米。而将头部和物体碰撞检测的距离设定为 47 厘米,我们可以在Player对象上([RENDERING] > Main Camera > Player)找到相应的碰撞盒。这样做是给我们提供了 1 个 10 厘米的缓冲区,可以在这个空间内产生一些内容(比如,播放被击中时候的伤害效果)。

                                                                                  neaclip

这是一个两难问题:如果我么们将近视截面距离设置更小,虽然可以让我们更贴近虚拟物品带来更好的沉浸感,但倘若我们的躯体穿透了虚拟物品时候,内容的破碎会忽然打破我们的沉浸感。为了避免这个现象,我们让傀儡人在足够远的距离(75 厘米)就采取躲藏行为来避免,以便让他有时间优雅的消失,在近视截面接近他之前就可以从容地行动。

现在你可以运行一下本课程,亲自测试一下,当你靠近或远离他时候,傀儡人有什么反应。也可以尝试用手去触摸傀儡人,或者把手收回来,注意不同距离阈值之间傀儡人的行为变化。当我们对这些都了然于心之后,再来进一步完成我们的课程。

傀儡人状态机

我们在 GolemAi 组件中使用了一个比较简单的状态机来管理傀儡人的行为。其中包含了五种状态,分别是: introidlethrowscared, 以及 hidden

Intro 是傀儡人的起始状态,他的目的是将傀儡人创建并放置到游戏空间。这个状态有两个推出条件:当初始动画播放完了进入到idle状态,或者你距离傀儡人太近他就进入到 hidden 状态。

 

                                olemIntro

Idle 当你在舒适距离之外并在投掷动作冷却的时候执行的状态。这个状态有两个推出条件,一个是投掷动作冷却时间到达,另外一个是你接近到威胁区域时候。

                                GolemIdleLoop

Throw 状态实在舒适距离之外,并且经历过抛掷动作冷却时间之后进入的状态。这个状态有两个推出条件:当一个抛掷动作指望完成之后,会进入一个隐藏状态(选择一个新的出生点让傀儡人弹出),另外是你进入他的威胁区域时候。

 

                              olemThrow

Scared 当你进入到一个不舒服距离(通过_playerScaredRange 定义)之后,就进入到这个被威胁状态。这个状态也有两个推出条件,一个是当你远了重新进入舒适距离之后,进入空闲状态。或者突破威胁距离,就进入了 hidden 状态。

                                GolemCowering

Hidden 当执行完投掷状态或者进入威胁距离(如通过定义_playerHideRange),此状态的目的就是为傀儡人SpawnPointManage 中找到一个新的出生点。我们会优先选择非当前出生点的距离最远的出生点。一旦明确的选择了一个出生点,会进入到这个隐藏状态,直到进入到下一个 idle 状态。

                                  GolemRetreat

再继续我们的成之前,在这里强调一个技巧。当傀儡人钻进地面隐藏时候,我们是如何让它消失的。这是通过在傀儡人的脚底下放置一个黑色材质的立方体来完成的。这里需要注意的是,这个立方体足够大到可以包围整个傀儡人,以便当他隐藏时候,从任何角度都不会露馅。你可以在 Project 中找到我们所使用的预设:Assets/Dodge/Prefabs/GroundRise.prefab。

现在我们设置了傀儡人的行为,现在让我们讨论一下如何对他的行为反馈。更具体地说,让我们如何处理他投掷的巨石。


躲避巨石

如果你一直在玩这个游戏,你可能已经被傀儡人投掷的巨石砸中几次了吧(如果你玩得太好了,还没有被杂种的话,那就尝试一下)。你可能已经注意到了在你的视野中产生的巨石伤害效果。如果你是一个有经验的有游戏玩家,你会觉得这个效果有点像传统游戏的摄像机特效,比如雨滴或者镜头光晕。

但事实上,这个效果并没有后绑定到你的视口。相反的,他是在世界被实例化。这意味着当你摄像机移动到其他地方的时候,这些特效不会跟随在你的眼前。虽然这些特效和你的头部位置无关,但我们仍然通过 Billboard 组件确保他可以是中的面向你,这样你从任何角度都能看到正确的表现。虽然这是一个很小的细节,但对沉浸感却至关重要。

虽然用你的脸接住巨石是一个有趣的事情,但是你可能仍然希望有其他几种闪避巨石的方法。

1. 其实你可以在石头即将砸到你头上的那一刻闪避开,这时候你能听到耳畔“嗖嗖”的风声,这种体验非常真实,让你有超级棒的临场感。这时候音效帮助我们展现在视场角之外的石头位置。

如果你对如何检测闪避事件和空间中精确的音频声音感兴趣的话,可以查看下 Projectile 脚本和 BoulderAudioBehavior 脚本。

2.另外一个策略是用手挡住巨石,我们可以通过 ML 手部跟踪 API 和 Unity 碰撞检测结合,来处理你用手挡住的大石头。

在这里我们需要重点关注的是,我们并没有像之前那样识别手势,而是直接用手不 API 进行了手部位置的跟踪。具体相关细节可以查看在 [CONTENT] 场景节点下的 PlayerLeftHandCollider  PlayerRightHandCollider

这些代码在 Player 对象中刷新,通过轮询 GestureManager 从得到手部的具体位置,我们也提供了一个示例,讲解如何在自己的项目中实现手部位置的跟踪。代码方面,我们通过将碰撞盒分别挂接在左右手掌中心来处理碰撞。这里需要特别注意的是,手部跟踪仅以 45Hz 的频率刷新。因为当前课程并不依赖于快速的手部操作,所以这个刷新率足够用了。

还需要注意一点,在这个课程中我们不会为手生成模型,所以也不能用手去遮挡虚拟世界的内容。但是如你所料,这是不合理的事情!所以我们将这个工作作为你离开这个课程后的第一个挑战,看看你是否可以使用手的空间位置和黑色材质来实现一个方案,以便把虚拟物体遮挡在你的手的后面。


注意

  • 允许用户尽可能地接近虚拟内容,但我们需要在他们进入近视截面前优雅的处理他们的消隐。
  • 在进行项目时候,要注意通过用户的躯体(头部和手部位置的追踪)来和整个程序互动。
  • 利用玩家头部和手部的碰撞时间,可以用于出发具体的逻辑行为。
  • 让玩家的手部能遮虚拟物体,这样才能带来更好的沉浸体验。

视觉特效应该绑定在世界位置上,而不是锁定到摄像机或者视口上。但同时也建议通过 billboard 让这个特效始终面向玩家。

空间音效有助于表现不在玩家视锥内的物体。

游戏内容应该和世界网格互动而不是直接忽略(例如在我们的项目中,巨石触碰到世界网格之后会被击碎,而不是穿过)。

我们可以使用黑色材质的立方体来在现实世界中隐藏虚拟世界的内容,我们要确保黑色立方体足够大以至于能包围你所需要隐藏的所有内容。


总结

感谢你今天阅读这个开发指南,这样你已经成为了我们开发者中的一员了。在这个课程中我们学会了如何使用世界网格和手势来在物理世界上放置虚拟内容,以及让内容如何在物理世界中和我们互动来带来完美的沉浸体验。

虽然本课程的内容并不是非常重要(你也可以用吐痰的骆驼来代替扔石头的傀儡人)。但仍然希望你可以把这些代码运用到你自己的项目中。我们最终的目标是为你的创作提供灵感,你也可以把思绪飞到更远的地方,我们期待看到你有趣的想法和建议。

我们游七团队已经开始开发 Magic Leap 的产品,并希望能有更多的机会和你交流。您可以到 http://MrPP.com 网站上找到更多的教程,也可以在上面添加我们的微信,然后回复“闪避”得到相关代码。

可以将你的作品发布到我们的论坛上,或者使用 #MadeWithMagicLeap 标签发布到任何社交媒体上。


 

发表评论

电子邮件地址不会被公开。 必填项已用*标注