【Unity】游戏寻路系统—NavMesh入门(个人翻译)

以下内容笔者个人翻译自:http://www.theknightsofunity.com/

游戏中的许多角色经常需要绕过关卡种中的障碍物。正是因为这是一个游戏中非常常见的情形,Unity提供了内置的寻路解决方案,叫做NavMesh。在这篇教程中,你将通过NavMesh实现点击移动。

本教程使用版本:Unity 2020.1.17


游戏中的寻路系统

游戏中的AI角色往往需要找到一条最佳路径来到达它们的目的地。有非常多的寻路算法可供你选择。不过,除了一些少数情况外,A* 算法会是一个很好的选择。

A* (or A-star)

A算法通常用于各种计算环境下的寻路 —— 它不是Unity特有的,下面这篇文章讨论了A算法在IOS开发环境下的内容:https://www.raywenderlich.com/3016-introduction-to-a-pathfinding。如果更深入的了解,这篇文章提供了一个图解式交互指南:https://www.redblobgames.com/pathfinding/a-star/introduction.html。另外还有一篇文章解释了独立与任何技术栈的A*算法的步骤:https://gist.github.com/jcward/45afd22560939aaae5c75e68f1e57505

Navigation Meshes

上面提到的A文章中讨论了在方形网格上的寻路问题,但是该算法则不需要像这样的地图。A也可以在任何形状中实现。该算法所需要的仅仅是一张连通节点图,而方形网格只是其中一种。你甚至可以在真实世界地图中使用A*:道路的交叉口都是节点,而道路是节点与节点之间的连接。

游戏中通常用网格来表示寻路图。就像3D模型一样,用于寻路的图可以由边连接顶点来表示【官方解释:https://docs.unity3d.com/Manual/nav-InnerWorkings.html】。诸如这样的网格被称为 Navigation Mesh,因此Unity使用了一个缩写名 NavMesh。

Unity内置了基础的NavMesh功能,但有一个叫做NavMesh Components的开源项目提供了更健壮的网格生成与寻路功能。当你设置你的项目时,你可以从GitHub上下载它。现在我们看看如何使用它们吧。


生成NavMesh

首先,创建一个空的GameObject并命名为NavMesh。导航网格将从该对象上生成,并附加到该对象上。接下来,添加NavMeshSurface组件到这个对象上。在Inspector面板你可以看到一系列的导航设置,以及一些管理网格生成的按钮。

点击Bake按钮,Unity将使用默认设置来创建一个导航网格。

在这里插入图片描述

理解NavMeshSurface

上面发生了什么呢?虽然底层代码是相当复杂的,但是主要的步骤却不难理解。NavMeshSurface首先使用自己GameObject的旋转角度来决定向上的方向。然后它定义了场景面向方向的表面。(在该案例中,它们都是平坦的,但是NavMeshSurface同样也作用于地形或者其他不规则的网格。)

NavMesh系统必须确定哪些表面是可以行走的。游戏角色有不同的形状和大小。比如小猫的可行走区域不同于一个人类或巨型机器人。这就是NavMeshSurface顶部图标的作用所在。
在这里插入图片描述

该图说明了将在这个网格上行走的特定类型代理的属性,例如其半径和高度。(你可以在代理类型下拉列表中访问这些设置,但本教程不需要这些设置。)

NavMesh系统使用这些设置来确定代理的去向。当系统发现一个足够平、宽、高的区域时,它将该区域标记为可行走的区域。然后,它生成一个平面导航网格,在场景中以蓝色区域显现。


调整NavMesh的层级

首先,玩家周围的网格有一个洞。默认情况下,场景中的一切都被认为是导航的潜在障碍。你需要明确告诉NavMeshSurface不要将玩家作为一个障碍物。

NavMeshSurface有一个下拉菜单叫做Include Layers。如果你在下拉菜单中取消选择一个图层,网格生成系统会忽略该图层上的对象。按照以下步骤将玩家放到一个新图层上,然后将该图层从网格中移除:

  1. Hierarchy面板中选中Player
  2. Layer下拉菜菜单中,选择Add Layer
  3. 添加一个新的Layer命名为Player
  4. 再次选中Player,修改其LayerPlayer
  5. 如果出现一个弹窗问你是否对子物体也进行修改,选择是。
  6. 选中NavMesh
  7. 在Inspector面板中,打开Include Layers下啦菜单,点击Player进行反选。

现在Unity明白了Player不是导航障碍物了。点击Bake来重新生成导航网格,空洞就消失了!

从NavMesh中移除一个对象

但还有一个问题。选中NavMesh,看看右上角区域。顶部的蓝色方块意味着导航系统将其视为地板的一部分,也就是说,它会被作为有效路径的一部分。然而玩家应当只能在地板上行走,不能在墙上行走!但你不能像对待玩家那样忽略那个方块。它仍然需要被当做一个障碍物,这样NavMesh就不会生成它的路径。

幸运的是,你可以用另一个组件NavMeshModifier来处理这个问题。下面是设置它的步骤:

  1. 在面板中选中那个角落区域的物体。
  2. 添加一个NavMeshModifier组件。
  3. 在这个组件中,选择启用Overrid Area
  4. 将Area Type改为Not Walkable

在这里插入图片描述

现在Unity明白那个角落是一个障碍而不是一个可行走的区域了,重新bake问题就解决了!

通过这些调整,你已经正确地设定了导航,可以让玩家在迷宫中跑起来了。

移动角色

在导航网格上移动一个对象是NavMeshAgent组件的工作。它计算着导航网格中的路径,从当前位置到你给它的任何目的地(或者最近的点如果它不在网格上)。

设置NavMeshAgent组件

首先,选择玩家并添加一个NavMeshAgent组件。默认设置的移动速度有点迟缓,但是你可以将其调整得更敏捷。在NavMeshAgent上,将速度提升到5,角速度提升到360,加速度提升到10。

在这里插入图片描述

现在你只需要一个脚本来设置NavMeshAgent的目的地,NavMesh会处理接下来的事情。

设置NavMeshAgent的目的地

在Assets/RW/Scripts目录下创建一个PlayerMovement.cs的脚本。添加一个命名空间引用:

using UnityEngine.AI;

该库引入了NavMesh相关类。

接下来在类顶部声明两个变量:

private Camera cam;
private NavMeshAgent agent;

然后在Start方法中添加如下两行:

cam = Camera.main;
agent = GetComponent<NavMeshAgent>();

Camera类有一个方便的方法,可以将鼠标在屏幕上的位置转换为游戏世界中的位置。将该位置传递给代理作为其目的地。

在Update方法中添加:

if (Input.GetMouseButtonDown(0))
{
    Ray ray = cam.ScreenPointToRay(Input.mousePosition);

}

这段代码检查每一帧,看看鼠标按钮是否被按下。如果是,它构造一条从相机到鼠标位置的射线。你将在下一步使用射线。

接下来在if语句中添加这些行:

if (Physics.Raycast(ray, out RaycastHit hit))
{
    agent.SetDestination(hit.point);
}

这段代码将射线发送到游戏世界中,并检查它是否击中了任何东西。(注意射线只会击中带有碰撞器的物体。)如果光线确实击中了什么东西,它会把击中的位置存储在hit.point。然后,您可以简单地将该位置提供给agent 作为其目的地。


优化Navigation Mesh

现在角色已经成功地在迷宫中移动了,你可以通过指定导航区域来让导航网格变得更有趣。当你把角落块设置为不可行走时,你实际上已经调整了一个导航区域。但你能做的远不止打开或关闭某个区域。

为了表示更复杂的环境,你可以指定不同的区域,这些区域具有不同权重,定义该区域移动的困难程度(或不受欢迎程度)。NavMesh代理将倾向于避开成本高的区域,即使从技术上讲穿越该区域的路径较短。想象一下在不同地形上移动的军事单位:如果他们抄近路穿过山脉,路径可能会更短,但他们更喜欢穿过开阔的田野绕山而行。在导航网格中使用不同权重的区域来表示这种情况很简单。

分配导航区域

首先,向导航系统添加一个新区域。选择WindowAINavigation。然后,选择Areas选项卡来查看这个项目中定义的所有导航区域。在第一个可用行中键入名称Water,并将其Cost设置为5。
在这里插入图片描述

回到场景视图。在迷宫的子物体中寻找并选择Water。(如果它在导航栏后面,您可能需要选择Inspector选项卡再次查看它。)添加一个NavMeshModifier组件,打开Override Area并将Area Type改为Water。然后再把网格烘焙一遍。
在这里插入图片描述

启动游戏,点击网格,看看玩家的寻径方式是如何变化的。例如,尝试将玩家放在右上角,然后点击角落方块的下方。到达目的地的最短路径是穿过水。相反地,玩家需要在较长的路径中进行导航。
在这里插入图片描述

你现在知道了在Unity中使用NavMesh寻路的基础知识!记住三个主要的组件:NavMeshSurface, NavMeshModifierNavMeshAgent

使用NavMesh系统,你可以做更多的事情。你可以使用NavMeshAgent设置定义不同类型的代理,创建在墙壁或天花板上行走的角色,或者探索将多个网格连接在一起。或者你可以尝试运行时生成。运行时生成指的是在游戏运行时创建一个导航网格,而不是像你在本教程中所做的那样提前烘烤一个导航网格。

尽管NavMesh免费又好用,但是许多开发者更喜欢A*寻径方式。如果游戏需要,你也可以尝试一下。

<think>好的,用户问的是如何区分PC和H5获取录音权限。首先,我需要明确用户的需求。他们可能是在开发一个需要录音功能的Web应用,想要了解在桌面浏览器(PC)和移动浏览器(H5)上获取麦克风权限的不同方法。需要从技术实现、API使用、用户交互、兼容性等方面进行分析。 首先,回忆一下Web Audio API的相关知识。在Web开发中,获取用户麦克风权限通常使用navigator.mediaDevices.getUserMedia方法。这个方法在PC和移动端的基本调用方式是一样的,但实际应用中可能会有差异。需要分步骤说明两者的不同点。 接下来,权限请求的触发方式。在PC端,大多数浏览器要求用户必须在用户交互(比如点击事件)中触发权限请求,否则会被阻止。而在移动端,尤其是H5页面,可能同样需要用户交互,但不同移动浏览器的行为可能不同。需要确认这一点,是否有例外情况。 然后,用户提示的差异。PC端浏览器通常会在地址栏显示权限请求的图标,而移动端可能会以弹窗或底部提示的方式出现。这里可能需要具体举例,比如Chrome在PC和手机上的不同表现。 安全上下文的要求。无论是PC还是移动端,使用getUserMedia都需要在HTTPS环境下,本地开发环境(localhost)除外。这一点是否一致?需要确认。 设备检测方面,移动端可能需要处理更多设备适配问题,比如前后麦克风的选择,而PC端可能通常只有一个麦克风。但这里可能需要更准确的描述,比如移动设备可能有多个麦克风,但通常由系统管理,而开发者可能不需要特别处理。 兼容性问题。需要列举不同浏览器在PC和移动端对录音权限的支持情况。比如Safari在iOS上的限制,或者某些安卓浏览器的不同行为。 接下来,代码示例部分。需要提供一个基本的请求麦克风权限的代码示例,并指出在PC和H5中的注意事项。比如在移动端可能需要处理自动播放策略,或者用户拒绝后的不同处理方式。 另外,权限持久化方面,PC端浏览器可能会记住用户的选择,而移动端可能每次都需要重新请求,或者有不同的会话管理方式。需要确认不同浏览器的具体行为。 最后,总结关键点,帮助用户区分两者的主要差异,比如触发方式、用户提示、兼容性处理等。确保回答结构清晰,步骤明确,用户能够逐步理解和应用。 需要验证一些不确定的点,比如移动端是否真的每次都需要用户交互触发,或者不同浏览器是否有不同的策略。例如,iOS上的Safari对getUserMedia的调用是否有严格的用户手势要求,而Chrome on Android是否允许在某些情况下自动触发。此外,权限请求的UI展示在不同平台上的具体表现,可能需要进一步查证。 另外,设备检测方面,是否需要使用mediaDevices.enumerateDevices来列出麦克风设备,这在移动端是否有必要,或者是否通常只有一个可用麦克风。这部分可能需要简化,避免复杂化问题。 最后,确保回答符合用户可见层的要求,结构清晰,逐步解决问题,并且内容真实可靠。避免技术错误,比如错误的API名称或不兼容的浏览器示例。</think>区分PC端和移动端H5获取录音权限的实现差异,主要涉及浏览器API调用方式、用户交互流程及兼容性处理。以下是关键点分析: --- ### **一、核心API调用** 无论PC或H5均使用Web API `navigator.mediaDevices.getUserMedia()`,但实现细节不同: ```javascript // 通用代码框架 async function requestMicrophone() { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 成功获取音频流 } catch (error) { // 处理权限拒绝或错误 } } ``` --- ### **二、用户交互差异** | **场景** | **PC端** | **H5(移动端)** | |----------------|----------------------------------------|---------------------------------------| | **触发条件** | 需用户主动交互(如点击按钮) | 同样需用户手势触发,部分浏览器更严格 | | **权限弹窗** | 浏览器顶部地址栏提示 | 系统级弹窗或底部提示栏 | | **持久化策略** | 浏览器可记住用户选择 | 部分移动浏览器每次刷新需重新授权 | --- ### **三、兼容性注意事项** 1. **PC端重点问题** - 旧版Edge/Firefox 需处理 `getUserMedia` 前缀 - Safari 要求 HTTPS 环境 2. **H5移动端特殊处理** - **iOS Safari**:首次拒绝后需手动开启(设置 > Safari > 麦克风) - **微信浏览器**:需配置JS接口安全域名 - **安卓WebView**:需应用层声明录音权限 --- ### **四、关键代码适配技巧** 1. **检测设备类型** ```javascript const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); ``` 2. **错误处理优化** ```javascript catch (error) { if (error.name === 'NotAllowedError') { // 提示用户手动开启权限 if (isMobile) alert('请前往系统设置启用麦克风权限'); } } ``` 3. **自动播放策略(H5重点)** ```javascript // 移动端需在用户交互后立即播放音频,避免静音阻塞 audioElement.play().catch(() => { /* 处理自动播放失败 */ }); ``` --- ### **五、调试建议** 1. **PC端**:使用Chrome `chrome://settings/content/microphone` 管理权限 2. **H5移动端**: - Android Chrome:长按页面 > 站点设置 - iOS Safari:需完全关闭页面后重新进入 --- ### **总结** - **本质相同**:均依赖 `getUserMedia` API,但交互流程和系统限制不同 - **核心差异**:移动端涉及更多系统级权限管控和浏览器兼容问题 - **适配关键**:结合设备检测优化提示逻辑,优先在HTTPS环境部署
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值