Jekyll2019-08-08T14:57:15+08:00https://ihaibara.github.io/feed.xmlToyHanpc的个人博客HanpcDLmalloc内存分配器2019-06-16T00:00:00+08:002019-06-16T00:00:00+08:00https://ihaibara.github.io/2019/06/16/DLMalloc<h1 id="dlmalloc内存分配器">DLmalloc内存分配器</h1>
<p>作者: Doug Lea
[本文的德文改编和翻译存在于1996年12月的unix / mail中 。这篇文章现已过时,并未反映当前版本的malloc的详细信息。</p>
<h2 id="介绍">介绍</h2>
<p>内存分配器在基础架构软件工程中是一种有趣的案例研究。 我从1987年开始编写一个内存分配器,并一直维护着它(在许多志愿者贡献者的帮助下)。 此分配器实现了标准C的例程malloc() , free()和realloc() ,以及一些实用的辅助例程。 分配器从未被赋予特定名称, 大多数人只称它为Doug Lea的Malloc ,或简称为dlmalloc 。</p>
<p>此分配器的代码已放置在公共域中(可从ftp://g.oswego.edu/pub/misc/malloc.c获得 ),并且显然被广泛使用:它成为了某些版本的Linux的malloc的默认实现;它被编译成一些常用的软件包(用于覆盖原生malloc),并且已经在各种PC环境以及嵌入式系统中使用,当然还有许多我甚至都不知道的地方。</p>
<p>在编写了一些几乎完全依赖于分配动态内存的C ++程序之后,我编写了第一个分配器版本。 我发现它们的运行速度要慢得多,而且内存消耗总量比我预期的要多得多。 这是由于我运行的系统上的内存分配器的特性(主要是SunO和BSD的当时版本)。 为了解决这个问题,我首先在C ++中编写了许多专用分配器,通常是通过为各种类重载operator new 。 其中一些在关于C ++分配技术的论文中有所描述,该论文已被改编成1989 C ++ Report文章一些容器类的存储分配技术 。</p>
<p>但是,我很快就意识到,为每一个往往是动态分配和大量使用的类构建一个自己特殊的分配器不是一个好的策略。(从1986年到1991年,我是libg ++的主要作者,GNU C ++库。)需要一个更广泛的解决方案 - 在正常的C ++和C加载下编写一个足够好的分配器,这样程序员就不会想要编写专用分配器,除非在非常特殊的条件下。</p>
<p>本文介绍了此分配器的一些主要设计目标,算法和实现注意事项。 可以在代码分发中找到更详细的文档。</p>
<h2 id="目标">目标</h2>
<p>一个好的内存分配器需要平衡许多目标:</p>
<ul>
<li>
<p>最大化兼容性
分配器应与其他分配器兼容; 特别是它应该遵守ANSI / POSIX惯例。</p>
</li>
<li>
<p>最大化可移植性
依赖于尽可能少的依赖于系统的功能(例如系统调用),同时仍然为仅在某些系统上出现的其他有用功能提供可选支持;符合对齐和寻址规则的所有已知系统约束。</p>
</li>
<li>
<p>最小化空间
分配器不应该浪费空间:它应该从系统中获得尽可能少的内存,并且应该以最小化碎片的方式保持内存,碎片-程序未使用的连续内存块中的“漏洞”。</p>
</li>
<li>
<p>最大限度地缩短时间
在一般情况下, malloc() , free()和realloc例程应该尽可能快。</p>
</li>
<li>
<p>最大化可调性
用户可以静态地(通过#define等)或动态地(通过诸如mallopt控制命令)控制可选特征和行为。</p>
</li>
<li>
<p>最大化寻址
分配通常一起使用的内存块。 这有助于在程序执行期间最小化页面和缓存未命中。</p>
</li>
<li>
<p>最大化错误检测
通用分配器似乎并不能用作通用内存错误测试工具,如Purify 。 但是,分配器应该提供一些方法来检测由于覆盖内存,多个释放等而导致的损坏。</p>
</li>
<li>
<p>最小化异常
使用默认设置配置的分配器应该在高依赖于动态分配的实际负载中运行良好 – 这些程序包括窗口工具包,GUI应用程序,编译器,解释器,开发工具,网络(数据包)密集程序,图形密集型程序包, Web浏览器,字符串处理应用程序等。</p>
</li>
</ul>
<p>Paul Wilson及其同事撰写了一篇关于分配技术的优秀调查报告,更详细地讨论了其中的一些目标。 参见1995年9月的国际内存管理研讨会上的 Paul R. Wilson,Mark S. Johnstone,Michael Neely和David Boles,“动态存储分配:调查与评论”(也可通过ftp获得 )。 (请注意,他们描述的我的分配器版本不是最新版本。)</p>
<p>正如他们所讨论的那样,最大限度地通过减少浪费(通常是由于碎片)来减少空间占用必须是任何分配器的主要目标。</p>
<p>对于一个极端的例子, malloc()的最快版本中总是分配系统上可用的下一个顺序内存位置,而free()的相应最快版本是no-op。 但是,这样的实现几乎不可接受:它会导致程序快速耗尽内存,因为它永远不会回收未使用的空间。 在一些负载下,在实践中使用的一些分配器中看到的浪费几乎可以是极端的。 正如威尔逊还指出的那样,浪费可以通过货币方式来衡量:在全球范围内,糟糕的分配方案可能会让人们花费数十亿美元的内存芯片。</p>
<p>虽然时空问题占主导地位,但这种权衡和妥协几乎是无止境的。 以下是众多示例中的一小部分:</p>
<ul>
<li>通过强制分配器跳过字节以便对齐块来适应最坏情况的对齐要求会增加浪费。</li>
<li>大多数动态可调性的规定(例如设置调试模式)可能因为增加间接级别和增加分支数量而严重影响时间效率。</li>
<li>旨在捕获错误的一些规定限制了适用范围。 例如,以前的版本2.6.6,无论平台如何,malloc内部处理的分配大小参数和传递的有符号整型一样,并将非正参数视为大小为零的请求。 (但是,从V2.6.6开始,负参数会导致null失败返回,以符合POSIX标准。)</li>
<li>适应其他分配器的奇怪特性以保持与它们的插件兼容可能降低灵活性和性能。 对于最奇怪的例子,一些早期版本的Unix分配器允许程序员重新分配已经被释放内存。 直到1993年,为了兼容性,我允许这样做。 (然而,当这个“特征”被删除时,没有人抱怨过。)</li>
<li>一些(但绝不是全部)可以改善小型程序的时间和/或空间的启发性会导致目前在典型系统中占主导地位的大型程序的时间和/或空间特性更加不可接受。</li>
</ul>
<p>沿着这些方向的任何妥协都不是完美的。 然而,多年来,分配器已经发展到可以接受大多数用户认为可以接受的权衡。 继续影响这个malloc发展的驱动力包括:</p>
<ul>
<li>其他人对malloc表现的实证研究(包括Wilson等人的上述论文,以及其他人引用的论文)。 这些论文发现,这个malloc的版本越来越多地同时被列为最具时间和空间效率的内存分配器。 但是,每个都揭示了进一步改进的弱点或机会。</li>
<li>目标工作负载的变化。 对malloc实现最敏感的程序类型的性质不断变化。一个最主要的例子是, X或其他窗口系统的存储器特性日益占主导地位。</li>
<li>系统和处理器的变化。使代码易于为典型处理器优化的实现细节和微调会随时间而变化。 此外,操作系统(包括Linux和Solaris)本身也在不断发展,例如,使内存映射成为系统级分配的偶然选择。</li>
<li>来自用户和贡献者的建议,体验报告和代码。 该代码是在几位常规志愿者贡献者的帮助下发展起来的。 最近的大多数变化是由使用Linux提供的版本的人发起的,并且很大程度上由Wolfram Gloger为Linux版本实现,然后由我集成。</li>
</ul>
<h2 id="算法">算法</h2>
<p>自最早的版本以来,malloc算法的两个核心元素保持不变:</p>
<h3 id="边界标签">边界标签</h3>
<p>存储块在块之前和之后随身携带大小信息字段。 这允许两个重要的功能:</p>
<ul>
<li>两个边界未使用的块可以合并为一个更大的块。 这最小化了不可用的小块的数量。</li>
<li>可以从任何已知块开始前向或后向地遍历所有块。</li>
</ul>
<p>原始版本以这种方式完全实现了边界标记。 更新的版本省略了程序正在使用的块上的预告片字段。 这本身就是一个小小的权衡:当块是活动的时候不会使用这些字段,因此不需要存在。 消除它们可以减少开销和浪费。 但是,缺少这些字段会使得无法检查用户是否错误地覆盖应该具有已知值的字段,从而削弱了错误检测。</p>
<h3 id="分档">分档</h3>
<p>可用块保留在箱中,按大小分组。 存在很宽大的(128)固定宽度的箱,其大小呈对数间隔。 大小小于512字节的容器每个只能容纳一个大小(间隔8个字节,简化了8字节对齐的强制执行)。 搜索可用的块以最小优先, 最合适的顺序进行处理。 如Wilson等人所示,与其他一般方法(例如首次拟合)相比,最佳拟合方案(各种类型和近似)倾向于在实际负载上产生最少的碎片。</p>
<p>在1995年发布的版本之前,块在块中未被分类,因此最合适的策略只是近似的。 更新的版本改为按容器内的大小排序块,其中的关系由最久-第一规则打破。</p>
<p>因此,该算法的一般分类最好是首先进行合并 :释放的块与相邻的块合并,并保存在按大小顺序搜索的箱中。</p>
<p>这种方法导致每个块的固定簿记开销。 因为大小信息和bin链接都必须保存在每个可用块中,所以在具有32位指针的系统中最小可分配块是16字节,在具有64位指针的系统中是24字节。 这些最小尺寸比大多数人想要看到的要大 - 它们可能导致严重的浪费,例如在分配许多微小链表节点的应用程序中。 但是,至少16字节是任何需要8字节对齐的系统的特征,其中任何 malloc都存在簿记开销。</p>
<p>这个基本算法可以非常快。 即使它依赖于搜索机制来找到最佳匹配,使用索引技术,利用特殊情况和仔细编码导致平均情况只需要几十条指令,当然取决于机器和分配模式。</p>
<p>虽然通过边界标签进行合并并通过分箱进行最佳拟合表示算法的主要思想,但进一步的考虑会导致许多启发式改进。 它们包括地点保护,荒野保护,内存映射和缓存。</p>
<h2 id="寻址保护">寻址保护</h2>
<p>由程序在大约相同时间分配的块通常具有相似的参考模式和共存的寿命。 维护位置可以最大限度地减少页面错误和缓存未命中,从而对现代处理器的性能产生巨大影响。 如果寻址是唯一的目标,分配器可能总是将每个连续的块分配给尽可能接近前一个块。 然而,这种最近拟合 (通常近似拟合 )策略可能导致非常糟糕的碎片化。 在当前版本的malloc中,next-fit版本仅在受限制的上下文中使用,该上下文在与其他目标冲突最少的情况下维护位置:如果没有精确所需大小的块,则最近;如果它足够大,则使用(和resplit)分裂空间; 否则最佳使用。 这种限制使用消除了无法分配完全可用的现有块的情况; 从而消除了至少这种形式的碎片。 并且,因为这种下一个拟合形式比最适合的bin-search更快,所以它加速了平均malloc 。</p>
<h2 id="荒野保护">荒野保护</h2>
<p>“荒野”(由Kiem-Phong Vo命名)块表示与系统分配的最顶层地址接壤的空间。 因为它位于边界,所以它是唯一可以任意扩展(通过Unix中的sbrk )超过其原有大小的块(除非sbrk失败,因为所有内存已经耗尽)。</p>
<p>处理荒野块的一种方法是以与任何其他块相同的方式处理它。 (直到1994年,这种技术在大多数版本的malloc中使用)。 虽然这简化并加速了实现,但是它可以毫无顾虑地导致一些非常糟糕的最坏情况空间特征:在其他问题中,如果在存在另一个可用块时使用了wilderness块,则会增加后续请求导致无法避免的sbrk 。</p>
<p>目前使用更好的策略:将荒野块视为比其他所有块更“大”,因为它可以这样做(达到系统限制)并在最佳扫描中使用它。 这导致仅在没有其他块存在时才使用荒野块,进一步避免可预防的碎片。</p>
<h2 id="内存映射">内存映射</h2>
<p>除了通过sbrk扩展通用分配区域之外,大多数Unix版本支持系统调用,例如mmap ,它分配一个单独的非连续内存区域供程序使用。 这在malloc提供了第二个选项来满足内存请求。 请求并返回mmap块可以进一步减少下游碎片,因为释放的内存映射不会创建需要管理的“漏洞”。 但是,由于与mmap相关的内置限制和开销,在非常有限的情况下才值得这样做。 例如,在所有当前系统中,映射区域必须是页面对齐的。 此外,调用mmap和mfree比分割现有的内存块要慢得多。 由于这些原因,当前版本的malloc仅在(1)请求大于(动态可调)阈值大小(当前默认为1MB)和(2)请求的空间在现有领域尚不可用时才依赖mmap其必须通过sbrk获得。</p>
<p>部分原因是因为mmap并不总是适用于大多数程序,当前版本的malloc还支持修剪主要活动区域,这实现了内存映射的一种效果 - 将未使用的空间释放回系统。 当长期存在的程序包含分配大量内存的短暂峰值,然后是具有更适度要求的更长的山谷时,可以通过将荒野块的未使用部分释放回系统来改善整体系统性能。 (在几乎所有版本的Unix中, sbrk都可以与负参数一起使用以实现此效果。)释放空间允许底层操作系统减少交换空间要求并重用内存映射表。 但是,与mmap ,调用本身可能很昂贵,因此仅在尾随未使用的内存超过可调阈值时才会尝试。</p>
<h2 id="高速缓存">高速缓存</h2>
<p>在基本算法的最简单版本中,每个释放的块立即与邻居合并以形成最大可能的未使用块。 类似地,仅在明确请求时才创建块(通过拆分较大的块)。</p>
<p>拆分和合并块的操作需要时间。 通过使用两种缓存策略中的任何一种,有时可以避免这种时间开销:</p>
<h3 id="延期合并">延期合并</h3>
<p>而不是合并释放的块,而是将它们保持为当前大小,希望很快就会出现另一个相同大小的请求。 这样可以节省合并,后续拆分以及查找要拆分的非精确匹配块所需的时间。</p>
<h3 id="预分配">预分配</h3>
<p>不是逐个拆分新的块,而是一次预先拆分多个块。 这通常比一次一个地做得快。</p>
<p>因为分配器中的基本数据结构允许在任何时候,在malloc , free或realloc中的任何一个中进行合并,所以相应的高速缓存启发法很容易应用。</p>
<p>缓存的有效性显然取决于相对于跟踪缓存块所需工作的拆分,合并和搜索的成本。 此外,有效性不太明显取决于决定何时缓存而非合并它们所使用的策略。</p>
<p>在连续分配和释放仅几种大小的块的程序中,缓存可能是一个好主意。 例如,如果您编写一个分配和释放许多树节点的程序,您可能会认为值得缓存一些节点,假设您知道快速执行此操作的方法。 但是,在不了解程序的情况下, malloc无法知道为了满足更大的请求而合并缓存的小块是否是一个好主意,或者是否应该从其他地方获取更大的请求。 分配器很难对此事做出更明智的猜测。 例如,分配器确定通过合并块将获得多少总连续空间同样只需合并它们然后重新分离它们也是同样昂贵的。</p>
<p>以前版本的分配器使用了一些搜索顺序启发式算法,这些启发式算法对缓存进行了充分的猜测,尽管偶尔会出现最糟糕的最坏情况结果。 但是随着时间的推移,这些启发式算法在实际载荷下似乎越来越有效。 这可能是因为严重依赖malloc的实际程序越来越倾向于使用更多种类的块大小。 例如,在C ++程序中,这可能对应于程序使用越来越多的类的趋势。 不同的类往往有不同的规模。
[注意:更新版本的malloc DO缓存,但只有很小的块。</p>
<h2 id="lookasides">Lookasides</h2>
<p>在某些应用程序中仍然存在一种非常需要但在此分配器中未实现的缓存 - 非常小的块。 如上所述,基本算法强加了最小的块大小,对于非常小的请求可能非常浪费。例如,具有4字节指针的系统上的链表可能会分配仅包含两个指针的节点,只需要8个字节。 由于最小块大小为16字节,因此仅分配列表节点的用户程序将承受100%的开销。</p>
<p>在仍然保持便携式对齐的同时消除此问题将要求分配器不施加任何开销。 存在实现这一点的技术。 例如,可以通过地址比较来检查块以查看它们是否属于更大的聚合空间。 但是,这样做可能会带来巨大的成本; 事实上,这个分配器的成本是不可接受的。 块没有以其他方式跟踪地址,因此除非任意限制,否则检查可能会导致随机搜索内存。 此外,支持需要采用一个或多个策略来控制是否以及如何合并小块。</p>
<p>这些问题和限制导致程序员应该例行编写自己的专用内存管理例程的极少数情况之一(例如,通过C ++重载operator new() )。 依赖于大量但近似已知数量的非常小块的程序可能会发现构建非常简单的分配器是有利可图的。 例如,可以使用嵌入的freelist从固定数组中分配块,以及在阵列耗尽时依赖malloc作为备份的规定。 更灵活一点,这些可以基于GNU gcc和libg ++提供的C或C ++版本的obstack 。
道格利亚
最后修改时间:Tue Apr 4 06:57:17 EDT 2000</p>HanpcDLmalloc内存分配器在RTS风格的游戏中的组寻路和移动 Group Pathfinding & Movement in RTS Style Games2018-06-08T00:00:00+08:002018-06-08T00:00:00+08:00https://ihaibara.github.io/2018/06/08/Group%20Pathfinding%20&%20Movement%20in%20RTS%20Style%20Games<p>在过去的5年中,我(作者)一直致力于改进RTS风格的游戏<a href="https://maestrosgame.com/">The Maestros</a>中的分组单位移动。正如大约20年前Dave Pottinger 在他的“帝国时代”(Age of Empires)工作时<a href="https://www.gamasutra.com/view/feature/131720/coordinated_unit_movement.php">所指出的那样</a> ,运动中的“寻路”这部分内容引起了所有人的注意,但使得数十个单位有智慧地走上一条道路的确重要且相当困难。我很想告诉你关于我在这个领域的旅程。</p>
<p>接下来的决不是最先进的解决方案。二十多年来,这个行业在这个问题上有非常出色的想法,你和我都没有希望紧跟时代的步伐。因此,让我们专注于在实际的游戏约束条件下为玩家制作基本路径的基本细节。我们假定了你拥有3D数学的实际知识,并不需要你有博士学位。说实话,我可能会让你心烦意乱;)</p>
<blockquote>
<p>本文翻译自https://www.gamasutra.com/blogs/AndrewErridge/20180522/318413/Group_Pathfinding__Movement_in_RTS_Style_Games.php</p>
</blockquote>
<h1 id="目标">目标</h1>
<p>在RTS运动中,一些玩家需要象Company of Heroes(英雄连)那样的逼真,缓慢旋转的坦克和步兵小队。我们的游戏是在快速战斗中执行大型游戏,因此我们的优先事项是“响应超越现实”,以及“对协调阵型的直接控制”。比起帝国时代更像星际争霸。</p>
<h1 id="我们开始的地方">我们开始的地方</h1>
<p>UDK(虚幻引擎3的SDK)在基于导航网格的空间中支持A*路径查找,并且具有相当有效(如果很挑剔)的导航网格生成。不幸的是,寻路几乎完全是在不可修改的引擎代码中实现的,我们无法在UDK中修改它。总而言之,如果我选择了一个单位并右键点击一个可抵达的位置,我可以期待它最终到达那里。虽然UE4提供了更好的低级访问,但Unity和UE4今天的支持水平大致相同。我认为我们已经很好地覆盖功能了。伙计,我错了。</p>
<h2 id="问题1---停止组">问题1 - 停止组</h2>
<p>下一步是移动一个组。如果我选择我们的几个Doughboy单位并要求他们移动到完全相同的位置,那么他们中只有一个会实际上在那里。 最好的情况是,其他人将与那个制作它的人相邻。那么他们怎么知道他们什么时候完成了移动? 才走了两步,我们就遇到了我们的第一个问题!</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/8mfFxGS-201868172150.gif" alt="8mfFxGS-201868172150" /></p>
<p>我们想到的解决方法是一种消息传递系统。到达那里的第一个人被设定为已经到达了目的地,任何碰到他的并且也想去同一个地方的人,也会认为自己到达了目的地。然后那些人可以将这个信息传递给任何碰到他们的人。 我们称之为“传递性碰撞”。这种感觉非常聪明,并且适用于聚类组,但仍然有一些愚蠢的退化情况(例如,如果单元排成一行)。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/9zImMiX-201868172455.gif" alt="9zImMiX-201868172455" /></p>
<h2 id="问题2---穿越人群">问题2 - 穿越人群</h2>
<p>我们早期遇到的另一个问题是一个单元被另一个单元阻塞。尽管UDK的寻路支持在navmesh中创造新的障碍,但为数百个不断改变其位置的单位进行这项工作导致了无法游戏的表现。因为如此,单位总是试图穿过彼此而不是周围的空隙。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/eHSlP8q-201868172754.gif" alt="eHSlP8q-201868172754" /></p>
<p>我们的解决办法是允许单位们在某些条件下相互施加推力。这也需要像我们的停止消息一样在整个组中传播。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/DC119xj-201868172928.gif" alt="DC119xj-201868172928" /></p>
<p>一个更自然的解决方案可能是告诉单位将他们自己移开(星际争霸2),然后将自己移回去。在任何一种情况下,确定“推”另一个单位的确切状态/条件都非常复杂且容易出错。 “你可以推动盟军,但不是敌人,空闲单位而不是攻击单位。”在我们的例子中,它采用了大约10个独特的子句以及不同级别的嵌套来实现。哎呀!我很想在这里找到更通用的解决方案。</p>
<h2 id="问题3---留在你的车道">问题3 - 留在你的车道</h2>
<p>在2014年我们在GDC首次公开演示The Maestros之后,我收到了一位来自我的导师的反馈,指出游戏感觉“混乱”。当时有很多事情对此产生了影响,但最大的问题是即使是简单的直线运动,也有单位在同一条路线上争夺位置。 没有人会期望现实生活中的人群这样做,当然也不是一群受过军事训练的机器人。我们所有的单位仍然完全独立行事。当他们从一名玩家的点击中收到一个单一的共同目的地并试图以他们自己最快的路线到达那里时,他们通常会选择与他们旁边的人一样的路线。结果就像405高速公路的所有8车道一样优雅,瞬间倒塌成一条车道。</p>
<p>解决这个问题的一般办法并不难。计算当前组的中心点,从该中心点获取每个单元位置的差异,并为每个单元发出一个定制的移动命令,并使用它们与目标的偏移量。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-17-31-52-201868173153.png" alt="2018-06-08-17-31-52-201868173153" /></p>
<p>对于单元A,B和C以及点击的位置(红色十字线),对目的地进行偏移</p>
<p>这对于将单位集群从一个开放区域移动到另一个开放区域的基本情况非常有用,但正如您将在本文中开始学习的那样 - 大多数“一般”感觉解决方案都具有分解的条件。最明显的是如果你尝试移动到障碍旁边。正如你在下面看到的,中心点是好的,但是单元C会在巨石(灰色框)内。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-17-33-24-201868173324.png" alt="2018-06-08-17-33-24-201868173324" /></p>
<p>另一个问题是,如果你的单位分散开来,你在中心附近点击,你会期望它们向内聚拢。然而,使用纯粹的偏移,他们通常会保持距离。如果你的单位太分散,目的地的偏移也不能达到预期。例如,你的所有部队都在一个集群中,但是你的指挥官(A部队)单独离开了2个屏幕。当你移动到集群中心附近的一个点时,你会希望你的所有单位,包括你的指挥官,最终都会落在你的光标下面(红色十字线)。事实上,如果你纯粹地应用偏移,它们都不会在你的光标下停止。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-17-35-49-201868173550.png" alt="2018-06-08-17-35-49-201868173550" /></p>
<p>用一句话总结许多问题,“有些情况下,一些或所有单位应该聚集在一起,而不是维持它们与组织中心的偏移。”确定谁是否在一个组中的想法听起来有点令人生畏,当然,有一些复杂的聚类算法,可以在这里应用。我对这个问题的解决方案最终变得简单得多,并且在大量场景中出乎意料地有效。 这里是简介:</p>
<p>从我们的代码中借用词汇,我计算了该组的“SmartCenter”</p>
<ul>
<li>计算组中所有单位的平均位置</li>
<li>移除任何不在该平均值的1个标准差内的单位</li>
<li>重新计算该组的子集的平均位置</li>
</ul>
<p>如果我们试图达到的点在中心点的标准偏差范围内,我使用纯粹的独立运动。 这就保证了单位将紧紧地聚在一起,并且让玩家能够在The Maestros中以我们期待的形式直接控制单位们的组形态。</p>
<p>如果我没有找到一个有意义的“主要聚集(Primary Cluster)”,那么我的单位可能分布在整个地图上,在这种情况下,我只是希望他们尽可能地重组。纯粹的独立寻路的另一个胜利。 这是我在组的标准偏差大于特定的最大值时发现的。理想情况下,该最大值相当于该小组占用的面积,因此我使用所有单位半径的总和作为最大值。这是相当有效的。</p>
<p>如果我有一个“主要群集”,但有一个或多个单位与组中心的偏差超过1个标准差,我通过给他们一个偏移方向(即正常)的目的地,但离集团中心目的地的长度(即大小)为一个标准的偏差的长度。这具有“倒塌”的效果,并且感觉更自然。</p>
<h2 id="问题4---粘在一起">问题4 - 粘在一起</h2>
<p>在直线移动时,总体上对每个单位的目的地应用相对偏移对于我们游戏内的移动“简洁度”来说是巨大的胜利。尽管如此,绕过障碍仍然很糟糕。首先,单位将围绕障碍物走自己最短的路线,并不总是与团队团结在一起。 其次,我们的8车道至1车道的交通堵塞在我们到达目的地之前的每个中间点再次发生(见第二幅图)。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/hLbCn28-201868175034.gif" alt="hLbCn28-201868175034" /><br />
没有一起寻路</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/qphCYss-201868175142.gif" alt="qphCYss-201868175142" /><br />
在中间点上交通堵塞</p>
<p>我没有一个好的答案就坐在这个问题上很长时间了。在第一天,我想选择1个单元的路径,并将偏移应用到每个中间点。当你认为你解决的首要问题是你的寻路紧紧盯着一个障碍时,这个算法很快就会崩溃。应用偏移量会让你的单位中有50%试图进入岩石路径,并且纯粹的独立寻路将导致永久性的僵局,甚至在你接近最终目的地的时候。</p>
<p>我对这个挫折的概念答案也不是非常聪明(如下所示)。我会将路径从角落移开,大约一个半径宽度。另一方面,从数学角度确定这一点证明对我来说是难以置信的难以捉摸。我如何确定我的路线是靠近障碍物还是偏远?如果我接近,我左边还是右边有障碍物?在我的道路上的某个点上,我的左边或右边的坐标轴是什么?</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-17-58-28-201868175828.png" alt="2018-06-08-17-58-28-201868175828" /></p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-17-59-17-201868175917.png" alt="2018-06-08-17-59-17-201868175917" />
在某些时候,我将不得不进行光线投射来定位障碍物体积。也许我可以在每个点周围放射状投射(如下图所示)?不幸的是,它很容易完全错过障碍。这个解决方案的准确性直接与我在路径上每点所做的光线投射数量成正比,而且这种效果非常低效。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-00-53-20186818053.png" alt="2018-06-08-18-00-53-20186818053" /></p>
<p>我真正需要的是给定转角的左右轴,假说是转弯的角度告诉你你的障碍可能在哪里。我的大部分障碍将直接进入我的载体的“肘部”,偶尔会在它外面。 当我通过以下操作找到轴时,我遇到了突破:</p>
<ul>
<li>为点之间的相对运动生成矢量 - 对于每个寻路点B,减去其前驱A,从A→B得到矢量</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-03-16-20186818316.png" alt="2018-06-08-18-03-16-20186818316" /></p>
<p>对于每对随后的向量,A和B,添加它们以获得从A的开始到B的结束的向量C</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-03-45-20186818346.png" alt="2018-06-08-18-03-45-20186818346" /></p>
<p>用上/下矢量交叉C得到矢量P,该矢量平分A和B之间的区域。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-04-06-2018681847.png" alt="2018-06-08-18-04-06-2018681847" /></p>
<p>向量P是轮到我的右/左轴!我检查两侧的障碍物,并将我的寻路点从障碍物移开大于标准偏差一点。结果从直接在我的障碍旁边得到一个舒适地偏离它的路径(绿色)。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-06-08-2018681868.png" alt="2018-06-08-18-06-08-2018681868" /></p>
<p>之前</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-18-07-55-20186818755.png" alt="2018-06-08-18-07-55-20186818755" /></p>
<p>之后</p>
<p>现在,我可以沿着我的路线在更新的点上应用我的偏移,这样我的团队就可以在路径上粘在一起,并且不会堵塞交通堵塞。它并没有涵盖所有情况,但在约90%的情况下,我们可以在没有交通阻塞的情况下获得。改善是巨大的。 这是一个转角的前后对比。</p>
<p>之前</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/qphCYss-201868175142.gif" alt="qphCYss-201868175142" /></p>
<p>之后</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/WBqZkvs-201868181011.gif" alt="WBqZkvs-201868181011" /></p>
<h1 id="学习收获">学习收获</h1>
<p>我最大的收获是,像A *这样的“广义”寻路算法不太可能成为你游戏的整个运动指导,尤其是如果你想要一个协调的团队的运动。我学到的第二件事是复杂性在这里是真正的敌人。寻路并不困难,因为寻路算法很复杂。紧凑的A *实现很容易少于100行可读代码,并且对于大多数游戏来说都是完美的服务。寻找路径很困难,因为实时的和空间相互移动多个单元会产生令人难以置信的大量情景,而且人们对于很多情况下应该发生的情况具有非常特定的期望。</p>
<h1 id="评论">评论</h1>
<blockquote>
<p>Robert Basler:RTS小组运动是一个棘手的问题。我在Miranda的工作时发现的继续BOIDS的工作的GDC论文对于RTS单元群体的一般移动情况非常有帮助。http://www.red3d.com/cwr/steer/gdc99/ 这里有更多关于RTS单元移动的链接:http://onemanmmo.com/index.php?cmd=newsitem&comment=news.1.179.0</p>
</blockquote>
<blockquote>
<p>Marco Castorina:我已经研究了一段时间的寻路和路径,我仍然觉得这个主题很迷人,谢谢你分享你的发现!我没有看到你提到的另一种经常使用的方法Geraerts和Overmars的走廊映射。它在清除角落的情况下可能特别有用:https://www.researchgate.net/publication/224705309_The_Corridor_Map_Method_Real-Time_High-Quality_Path_Planning 和 https://pdfs.semanticscholar.org/a921/86a365a2e5c407f98efdbd95cf412f91a6e5.pdf</p>
</blockquote>
<h1 id="翻译后记">翻译后记</h1>
<p>一个月前在Gama上看到的文章,感兴趣,但是没卯月啊,又不开发RTS游戏,也不开发moba游戏。直到最近(准确地说是月初),因为要坐火车,下了开罗的赛车物语,玩的时候就想着,我靠这个赛车的AI是怎么构建的呢,感觉赛车在赛道上飞驰,需要一种很特别的寻路算法啊。然后,灵光一现,好像,好像这个文章以及其引用的boids好像可以用到的样子,就翻译了一下文章,好好看看。</p>
<p>实际上,本文的精彩部分不在于作者解决问题的结果,反倒是作者遇到的各种复杂的问题场景才是真正值得借鉴的。</p>Hanpc在过去的5年中,我(作者)一直致力于改进RTS风格的游戏The Maestros中的分组单位移动。正如大约20年前Dave Pottinger 在他的“帝国时代”(Age of Empires)工作时所指出的那样 ,运动中的“寻路”这部分内容引起了所有人的注意,但使得数十个单位有智慧地走上一条道路的确重要且相当困难。我很想告诉你关于我在这个领域的旅程。揭秘神秘海域2的敌人AI The Secrets Of Enemy AI In Uncharted 22018-06-07T00:00:00+08:002018-06-07T00:00:00+08:00https://ihaibara.github.io/2018/06/07/Uncharted%202%20Ai<p>在最新一期的Uncharted 2战斗设计系列中,Naughty Dog战斗设计师Benson Russell深入研究了该工作室为其战斗AI设计的技术,以及这些技术是如何从该系列的原始游戏中演变而来的。<a href="https://www.gamasutra.com/view/feature/134566/the_secrets_of_enemy_ai_in_.php?page=1">第一部分</a>讨论了团队如何从最初的Uncharted演变出战斗设计<a href="https://www.gamasutra.com/view/feature/5883/a_deeper_look_into_the_combat_.php">第二部分</a>看看战斗触发设计。</p>
<blockquote>
<p>本文的原文地址在
https://www.gamasutra.com/view/feature/134566/the_secrets_of_enemy_ai_in_.php?page=1 是系列的第一部分</p>
</blockquote>
<h1 id="螺母和螺丝">螺母和螺丝</h1>
<p>我想分享我们AI系统的一些基本技术细节,以深入了解我们如何解决当我们作为开发人员时都会面临的一些常见AI问题。大多数游戏都有某种形式的AI,不管游戏的类型如何,这是相当普遍的要求而且必须得到满足。所以我认为你应该能够将这里的很多方面与你正在使用的系统联系起来,并且可能启发你可以去尝试一两个新事物。
我们的AI包含一系列非常模块化的系统,可处理我们所需要的不同方面。从高级角度来看:</p>
<ul>
<li>有设计师界面组件可以让我们为玩法定义角色和行为。</li>
<li>有美学元件可以处理它们的外观和动画。</li>
<li>有决策组件可以选择去哪里,如何到达那里,以及沿途和前进的方向。</li>
</ul>
<p>我们尽量保持系统尽可能模块化,因为它可以让我们快速实验并用新想法和游戏机制原型进行原型设计。当系统需要交叉沟通时,它可能会导致一些问题,但好处远大于坏处。
就本文而言,我想坚持从设计师的角度来处理我们最主要的问题,所以这就是我最关注的问题。</p>
<h1 id="你是大脑是中枢神经系统">你是大脑…是中枢神经系统…</h1>
<p>我们定义AI类型的方法是为所有不同的AI系统创建一组默认数据。这是非常开放的,让我们有能力创建我们想要的任何类型的行为,而无需通过编程支持将它们硬编码到引擎中(尽管我们可以在特殊情况下使用该路线)。我们使用我们的自定义脚本语言作为设置这些参数的主要方法,以及在游戏过程中与它们进行交互。让我们仔细看看我们如何设置AI:</p>
<h2 id="原型">原型</h2>
<p>因此,在定义这些设置的最高级别中,我们有所谓的原型。这形成了在游戏中产生这种特殊类型的AI时使用的默认数据集的起点。我们创建原型,然后将其分配给放置在关卡中的AI游戏对象。例如,这里是定义神秘海域2中轻型手枪士兵的原型(使用Defender手枪的浅灰色制服的家伙):</p>
<blockquote>
<p>(define-ai-archetype light-pistol<br />
:skills (ai-archetype-skill-mask<br />
script<br />
hunting<br />
cover<br />
open-combat<br />
alert<br />
patrol<br />
idle<br />
turret<br />
cap<br />
grenade-avoidance<br />
dodge<br />
startle<br />
player-melee-new<br />
)<br />
:health 100<br />
:character-class ‘light<br />
:damage-receiver-class ‘light-class<br />
:weapon-skill-id (id ‘pistol-basic-light)<br />
:targeting-params-id (id ‘default)<br />
)</p>
</blockquote>
<p>这是一个简单的结构,其中包含了了解AI的能力所需的所有数据。除了这个例子显示的内容,还有更多的选项可以包含在这些内容中,但是这些数据是让AI执行的“肉”和“土豆”。</p>
<h2 id="得到技能">得到技能</h2>
<p>您可以看到其中一项数据(可能是最重要的一项)是技能列表(skills)。就他们的能力而言,这是我们的AI的核心。它的设计是模块化的,因此可以简单地添加和删除技能。以下是本示例中所用技能的一些说明:</p>
<ul>
<li>
<p>脚本:这允许AI通过我们的脚本语言而被接管。它基本上关闭了他们的大脑,并允许他们响应脚本命令。如果我们需要AI来播放特定的动画,或者我们希望命令它们移动到特定的点,我们就会使用它。这是需要的,因为如果没有它,他们仍然会思考并试图自己采取行动,因此他们最终会反对执行向他们发出的命令。</p>
</li>
<li>
<p>掩护:这让他们了解掩护以及如何使用掩护(稍后我会详细介绍掩护系统)。对于我们不想让他们有掩护的角色,我们可以简单地删除这个技能。</p>
</li>
<li>
<p>开展战斗:这告诉AI如何在掩护外开展战斗。</p>
</li>
<li>
<p>炮塔:这让AI可以在游戏玩法上和技术上(即播放什么动画)使用安装的炮塔。</p>
</li>
<li>
<p>空闲:这让他们了解空闲行为(即当该地区没有敌人时)。</p>
</li>
<li>
<p>巡逻:这使得AI可以了解如何使用我们在自定义编辑器Charter中布置的巡逻路线。</p>
</li>
<li>
<p>CAP:这是Cinematic Action Pack的缩写。 行动包(AP)通常是我们如何让AI针对特定情况执行某些动画(例如跳过墙壁或跳墙)。 CAP是一种让AI执行所需表现(如抽烟)的方式,但他们的大脑仍然有它们的大脑,因此它们可以响应刺激。 CAP将定义所有需要的动画,以及过渡动画以使AI进出不同的状态。</p>
</li>
</ul>
<p>除了我在这里展示的技能之外,还有更多的技能,每个技能都被设计为模块化的组件来告诉AI如何针对各种游戏玩法作出反应。 这使得向AI添加功能非常容易,因为程序员只需编写插入列表的新技能即可。 我们也可以选择通过我们的脚本语言在游戏过程中动态添加和移除技能。</p>
<h2 id="生命值health">生命值(Health)</h2>
<p>生命值参数应该是自我解释的。我们特别从中神秘海域1使用的浮点值切换到神秘海域2使用的整数值。原因是我们在浮点系统中出现了一些不可预知的行为(即检查值为1.0,但是它的值为0.999999)。</p>
<p>对玩家来说,最终的结果是有时敌人需要额外的子弹才能击倒。同样为了保持简单,我们尝试给所有对象提供100 Health(AI和玩家)的基础。当我们想要一个真正强大的敌人(比如重型连锁枪手)时,有一些例外情况,但是大多数情况下我们想要一致性,所以很容易平衡(在这一点上)。</p>
<h2 id="ai和类">AI和类</h2>
<p>角色类决定了动画集中设置的角色的外观。这些类是程序员和动画师之间定义的。 对于神秘海域2我们有轻型,中型,重型,装甲,盾牌,村民和SLA等多个类(一个有趣的额外说明,SLA代表香格里拉军团,它们是和德雷克最后在生命之树战斗的)。</p>
<h2 id="伤害">伤害</h2>
<p>伤害接受者类是我们根据敌人从不同的玩家武器中受到多少伤害来平衡游戏的主要方式。这是一个开放式的系统,因为设计师有权力建立所有的东西,而不必依赖程序员。</p>
<p>在神秘海域2中,因为我们不断遇到我们想要针对不同类型的敌人调整不同武器的问题,我们设计了这个系统。</p>
<p>原本在神秘海域1中(就像我工作过的大多数游戏一样),武器对一切物件都造成了一定程度的破坏,我们通过设置物件的生命值来调整,但当涉及到平衡时,这往往会令人头痛的问题。</p>
<p>例如,如果你决定改变武器的伤害,那么就必须重新调整几乎所有的东西,因为你必须重新调整所有敌人的生命值,这意味着改变其他武器伤害等…</p>
<p>这也造成了很多混乱,因在检查调整时,敌人看起来会有随机的生命值(例如,敌人A有90生命值,但敌人B有400生命值!)</p>
<p>为了解决这个问题,我们创建了Damage Receiver Class系统。 它从设计人员可以设置的简单电子表格开始,然后运行Python脚本将其转换为我们的游戏脚本。 它看起来像这样:</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-17-14-05-20186717146.png" alt="2018-06-07-17-14-05-20186717146" /></p>
<p>这些列表示武器名称(这些是设计者在别处设置的底层名称),而这些行代表不同的Damage Receiver Classes。每行和每列的交集表示特定的武器对特定的Damage Receiver Classes会造成多少伤害。
因此,以电子表格为例,“pistol-semi-a”(即Defender手枪)将对任何具有“light-class”的Damage Receiver Class造成34的伤害,对任何具有“medium-class” 的Damage Receiver Class造成17的伤害。我们可以根据需要创建尽可能多的Damage Receiver Classes,并相应地分配它们(而不仅仅是AI)。这使得平衡游戏非常直接和简单。</p>
<h2 id="武器技能id">武器技能ID</h2>
<p>这就是AI如何理解他们获得的武器以及它应该如何运作。这是一个简单的结构,用于定义武器在精度,损坏和发射模式方面的表现。我们有目的地分离了玩家的这些参数版本,以便我们可以定制敌人对任何特定武器的有效性。以下是它看起来像什么:</p>
<blockquote>
<p>(new ai-weapon-skill<br />
:weapon ‘assault-rifle-b<br />
:type (ai-weapon-anim-type machine-gun)<br />
;; damage parms<br />
:character-shot-damage 5<br />
:object-shot-damage 120<br />
;; rate of fire parms<br />
:initial-sequence-delay (rangeval 0.0 0.0)<br />
:num-bursts-per-sequence (rangeval-int 10000 10000)<br />
:auto-burst-delay (rangeval 0.4 0.8)<br />
:auto-burst-shot-count (rangeval-int 3 5) <br />
:single-burst-chance 0.33<br />
:single-burst-delay (rangeval 0.4 0.8)<br />
:single-burst-shot-count (rangeval-int 1 3)<br />
:single-burst-fire-rate (rangeval 0.16 0.20)<br />
;; accuracy parms<br />
:accuracy-curve <em>accuracy-assault-rifle-upgrade</em><br />
:time-to-accurate-cover 3.5<br />
:accuracy-cover-best 1.0<br />
:accuracy-cover-worst 0.0<br />
:accuracy-running 0.9<br />
:accuracy-rolling 0.5<br />
:accuracy-standing 1.0 <br />
:master-accuracy-multiplier 1.0<br />
:master-accuracy-additive 0.1<br />
)</p>
</blockquote>
<p>顶部有一些关于视觉效果的关键信息。Weapon参数引用武器的内部游戏名称,以便让它知道引入的美术,FX和声音。Type参数设置要使用的动画(手枪,机关枪或霰弹枪)。 我认为的伤害部分是自我解释的,我们再次使用100的基本生命值。</p>
<p>Rate Of Fire参数中 定义AI将如何使用武器,正是在这里,我们设置了射击模式。 在设置这些时,我们必须记住几个关键问题。第一个是子弹的实际体积,因为这会影响玩家的损伤程度。其次是武器的实际行为(即确保全自动武器的行为等)。</p>
<p>第三个是如何在战斗中感知音频。这是一个我们经常试图平衡的问题。我们希望AI能够像普通人那样以现实中的方式发射武器,但是我们也不希望战斗场景中的每个AI都会在没有任何停顿的情况下发射全自动武器。</p>
<p>它不仅感觉不对,而且打破了沉浸感,它听起来简直太可怕了!许多武器的嗡嗡声一下子充斥着整个系统,这不是激烈的交火,你会听到扭曲的噪音,这也使得玩家更难以确定敌人使用什么武器。</p>
<p>Rate Of Fire参数的工作方式是,在每个序列之间存在着一个有着可选的延迟时间的开火序列(在这种情况下延迟没有被使用)。 一个开火序列由多个爆发组成(在这种情况下数量设置为10000),并且每个爆发可以是全自动的(在这种情况下它使用武器设置的发射速率)或者一组具有手动输入的单次发射频率。</p>
<p>因此,在上述情况下,每次爆发有33%的几率成为一组单发点射,其中以0.16 - 0.2秒的速率点射,并在下一次爆发之前延迟0.4-0.8秒。否则它将是一个由3-5个子弹组成的全自动爆发,并且在下一次爆发之前延迟0.4-8秒。精度曲线指向一个预定义的结构,如下所示:</p>
<blockquote>
<p>(define <em>accuracy-assault-rifle-upgrade</em><br />
(make-ai-accuracy-curve<br />
([meters 60.0] [chance-to-hit 0.0])<br />
([meters 30.0] [chance-to-hit 0.3])<br />
([meters 20.0] [chance-to-hit 0.4])<br />
([meters 12.0] [chance-to-hit 0.4])<br />
([meters 8.0] [chance-to-hit 0.5])<br />
([meters 4.0] [chance-to-hit 0.9])<br />
)<br />
)</p>
</blockquote>
<p>它读取的方式是图形上的一组点,用于定义武器在指定的期望距离处的精度,并在每个点之间建立线性斜坡。 所以在这种情况下,从0到4米有90%的击中机会,然后下降到8米,有50%的击中机会,然后下降到12米,这有40%的几率击中等……我们有意地单独设置这些精度曲线,以便我们可以轻松地将它们重复用于我们选择的任意数量的武器技能。</p>
<p>其余的参数是影响准确性的修饰符,应该大部分是自我解释的。我确信你想知道的是Time To Accurate Cover部分。 我们这样做可以给玩家,在他们探出掩体射击时,一个小小的机会窗口。它将按Accuracy Cover Worst(本例中为0%)中列出的百分比降低AI的当前精度,然后在Time To Accurate Cover(在此情况下为3.5秒)中设置的持续时间内,将精度提高Accuracy Cover Best(在这种情况下为100%)。</p>
<p>因此,在这种情况下,玩家的有效效果是当玩家探出掩体射击时,他们有3.5秒的时间,在这段时间内AI的准确性将从0%返回到当前条件的基本水平。</p>
<p>通过使用这些武器技能定义,我们能够使AI的不同类型和类别或多或少具有挑战性。 举个例子,对于低级别士兵总体而言,我们给了他们稍慢射击频率和更长的时间Time To Accurate Cover,以使它们更容易。</p>
<p>而中等级别的士兵,我们会增加他们的射击频率,通过调整Master Accuracy Additive增加他们的准确率10%,并减少他们的Time To Accurate Cover,使他们更坚强。我们还会尝试调整不同武器的发射速率,使它们听起来更独特,以便玩家更容易识别(即FAL将具有与AK和M4不同的模式)。</p>
<h2 id="瞄准">瞄准</h2>
<p>AI有一个权重系统来确定如何选择最佳可用目标。权重在我们的一个结构化脚本文件中设置。在游戏中,根据这些权重评估所有潜在目标,并选择权重最高的目标。以下是默认设置的样式,以及有关基本参数的一些注释以及它们的含义:</p>
<blockquote>
<p>(new ai-targeting-params<br />
:min-distance 0.0 ;; minimum distance to look for a target<br />
:max-distance 200.0 ;; maximum distance to look for a target<br />
:distance-weight 50.0 ;; weight to apply, scaled according to distance away</p>
<p>:target-eval-rate 1.0 ;; how often to evaluate targets, in seconds</p>
<p>:visibility-weight 10.0 ;; weight applied if the target is visible</p>
<p>:sticky-factor 10.0 ;; weight applied to the existing target<br />
:player-weight 10.0 ;; weight applied if the target is the player<br />
:last-shot-me-weight 20.0 ;; weight applied if the target shot me<br />
:targeting-me-weight 0.0 ;; weight applied if the target is targeting me</p>
<p>:close-range-distance 11.0 ;; weight applied if target is within the specified distance<br />
:close-range-bonus 0.0</p>
<p>:relative-distance-weight 10.0 ;; explained later<br />
:relative-search-radius 100.0</p>
<p>:relative-dog-pile-weight -1.0 ;; explained later</p>
<p>;; weights for specific vulnerability states, explained later<br />
:vuln-weight-cover 0.0<br />
:vuln-weight-peek 0.0<br />
:vuln-weight-blind-fire-cover 0.0<br />
:vuln-weight-roll 0.0<br />
:vuln-weight-run 10.0<br />
:vuln-weight-aim-move 20.0<br />
:vuln-weight-standing 20.0</p>
<p>:in-melee-bonus 0.0 ;; weight applied if target is in melee<br />
:preferred-target-weight 100.0 ;; explained later<br />
)</p>
</blockquote>
<p>游戏中的大部分AI都使用这一默认的瞄准参数。这些设置用简单的术语表达的意思就是这样:</p>
<p>他们将寻找远至200米的目标,偏向更接近的目标。他们也会更偏向:他们可以看到的目标(visibility-weight);射中他们的目标(last-shot-me-weight)或者是玩家(player-weight)。他们也会偏向他们已经瞄准的目标(sticky-factor),并且会重新评估他们是否应该每秒选择一个不同的目标(target- eval -rate)。</p>
<p>我们可以使用一些参数来帮助分散多个敌人的瞄准。例如,当多个派别之间发生更大的战斗时,我们不希望每个人都瞄准同一个人。</p>
<p>根据同一团队中有多少其他AI瞄准了同一个特定敌人,应用 relative-dog-pile-weight(因此为什么这是一个负值)。指定的值会针对当前针对该敌人的每个团队成员的权重相乘得到总权重(因此,针对该敌人的团队成员越多,整体权重就越强)。</p>
<p>relative-distance-weight用于帮助根据与所选目标的距离确定优先级。它搜索在指定的relative-search-radius内瞄准相同目标的AI成员团队成员,并根据最接近该目标的人员缩放relative-distance-weight。 这有助于解决同一团队挑选他们距离最远的目标AI的问题,因为其他团队成员更接近。</p>
<p>vulnerability weights用于帮助根据特定条件提高定位。 所以在某些情况下会有加权,如当目标在空旷处奔跑,在空旷处瞄准射击(这只会影响玩家)以及在空旷处站出来。</p>
<p>preferred-target-weight是设计人员指定特定目标的一种方法。它的工作方式是我们随时可以选择指定某项作为任何特定AI的首选目标。这意味着当AI执行目标评估时,标记为该AI的首选目标的任何内容都将获得此额外权重作为评估的一部分。</p>
<p>所以你可以看到我们有很多选择来影响AI如何选择他们的目标。我们可以根据自己的喜好设置多种定位参数,并将它们分配给各种原型,甚至可以将其分配给不同的原型,甚至关卡的指定AI。 此外,许多这些权重可以设置为负值,因此根据场景的上下文给予我们更多的灵活性。</p>
<h2 id="这是一个原型">这是一个原型!</h2>
<p>所有这些都构成了AI原型!还有其他参数在本文中未涉及(如测试或特定于原型的参数),但这些参数是所使用的大部分参数。通过创建这些参数的组合,我们可以创建行为不同的AI类型,并快速创建新想法的原型。当我们需要新的技能或特定的行为时,大多数决定权也在设计人员手中,只需要程序员干预(这对原型也很快,感谢技能列表)。 那么让我们继续讨论如何设置其余部分。</p>
<h1 id="功能美学">功能美学</h1>
<p>当设置AI的外观(包括视觉和动画)时,我们也有很大的灵活性.对于视觉效果,我们有一个基于零件的“外观”系统,可以让我们按照自己的意愿划分角色。</p>
<p>例如,对于轻型和中型敌人,我们有一个基本的身体(没有手或头),各种头部(包括双手以保持肤色匹配),以及我们可以附带的一系列装备,但我们希望。在脚本文件中,我们会将“外观”定义为这些项目的特定集合,然后将它们分配给原型。</p>
<p>你也可以创建一个集合,然后将整个集合附加到另一个集合。我们广泛使用这个特性来创建可以随机替换的头元素。举例来说,我们有几个级别的人具有特定的装备(如特定类型的帽子或面具)。我们会为他们的装备创建几个集合,然后在AI产生时会随机选择其中一个组合用于外观。</p>
<p>对于动画,我们有一个系统,可以让我们随时重新映射任何动画,这样我们就可以混合搭配任何我们需要的动画。这意味着当AI系统调用某些动作时,我们可以将任何动作映射到我们选择的任何动画。</p>
<p>这是非常强大和灵活的,因为您不必为每个角色创建一个完整的动画集。相反,你可以将特定操作重新映射到新动画。</p>
<p>作为一个例子,在神秘海域2我们在轻型和中型士兵之间在如何从掩体中射击存在着差异,轻型家伙会暴露自己更多,而中型家伙则不会。我们只是使用了一个重映射,这样当AI系统发出一个命令从掩体射击时,它将为轻中型士兵使用不同的动画。</p>
<p>我们使用这个系统的其他例子是护送受伤的摄影师杰夫穿过小巷的顺序以及在沼泽地的古庙里,当德雷克举着一个燃烧着的火炬走动时。</p>
<h1 id="思维机器">思维机器</h1>
<p>所以让我们来看看决策过程是如何工作的,以及我们作为设计师与他们交互以获得我们想要的行为。</p>
<h2 id="乔治去了哪条路">乔治去了哪条路?</h2>
<p>AI必须了解环境,才能让他们四处走动并做出酷炫的事情。为此,我们创建了我们所谓的导航网格(简称navmesh),它定义了AI的世界边界。这是一个在Charter中创建的多边形网格,使用一组特殊的工具来使其快速直观。</p>
<blockquote>
<p>你可以在<a href="https://www.gamasutra.com/view/feature/5945/designing_combat_encounters_in_.php">前一篇文章中</a>找到更多关于Charter的信息</p>
</blockquote>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-11-44-03-20186811445.png" alt="2018-06-08-11-44-03-20186811445" /></p>
<p>当我们最初在Charter内部放置一个navmesh时,这是基本navmesh的开始。我们对 navmeshes有一个特殊的编辑模式,你可以在每个顶点看到黄色光盘。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-11-46-14-201868114615.png" alt="2018-06-08-11-46-14-201868114615" /></p>
<p>这些光盘让我们只需在水平面上拖动一个顶点,同时保持其与地面的高度关系。如果我们需要调整高度,每个顶点上的绿色箭头可以让我们垂直移动它。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-11-46-37-201868114637.png" alt="2018-06-08-11-46-37-201868114637" /></p>
<p>我们可以通过点击连接现有顶点的红线来创建新的顶点。 我们也可以通过点击黄色圆盘中心的橙色圆圈来删除一个顶点。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-11-47-09-201868114710.png" alt="2018-06-08-11-47-09-201868114710" /></p>
<p>我们可以通过在navmesh中间点击并拖动来切出一个洞。然后可以通过拖拽它的顶点并正常添加或移除顶点来修改该洞的形状。
使用这些工具,我们可以创建和操作navmesh,以符合我们希望AI导航的区域。我们还可以通过将多个顶点彼此咬合将多个导航链接在一起,编译时将它们组合到一个navmesh中 。</p>
<p>虽然navmesh定义了可导航空间的边界,但AI依赖于第二个与此导航结合使用的系统。 所述 navmap 是在围绕AI横轴二维网格投影。它考虑了navmesh以及动态对象。</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/2018-06-08-11-50-50-201868115050.png" alt="2018-06-08-11-50-50-201868115050" /></p>
<p>在这张图片中,您可以看到所选AI的navmap(靠近中心的,周围有圆圈)。 你可以看到世界上由栅格所覆盖,并且一些栅格的正方形以黄色着色,黄色代表不可导航的空间。 使用navmesh来查找世界边界(例如物理结构周围的区域),以及动态对象(如其他AI )的navblockers。</p>
<p>navblocker是navmap专门读取的并且可动态改变。它通常来自对象的碰撞边界,但也可以手动放置在Charter中,也可以通过代码中的特定设置(例如使用其他AI)生成。 这使设计师能够在游戏进行中灵活地动态阻隔游戏区域。同样,通过使用这种系统,我们节省了计算开支,因为AI不需要持续探测环境中的动态障碍物。</p>
<p>这个系统处理了大部分让AI在环境中正确导航的繁重工作,但是当你希望AI能够在更复杂的景观中跟随玩家时该怎么办? 为此,我们有所谓的遍历动作包(TAP)。 我们创建符合环境要求的动画(例如攀登墙壁,跳过空隙,爬梯等),然后插入TAPs系统并将其放入Charter中。然后,AI可以使用这些从navmesh导航到navmesh,能够较好地跟随玩家。</p>
<h2 id="影响之下">影响之下</h2>
<p>虽然navmesh和navmap给了AI对环境的理解,但他们仍需要一种方法来评估空间,并挑选最适合当前情形的指定位置。为此,我们使用我们的Combat Behaviors 系统。这是选择最佳指定点的位置的加权影响图,以每AI每秒一次的速率进行评估。系统考虑的整套要点基于几件事的集合:</p>
<ul>
<li>一个固定的网格点,将自己应用于世界并通过navmesh进行过滤</li>
<li>掩体点</li>
<li>它们的中心Zone Marker点(我稍后会介绍这些)</li>
</ul>
<p>如何确定这些点的权重取决于我们在脚本中设置的参数。这里有一个来自神秘海域2的例子,里面有关于参数的评论:</p>
<blockquote>
<p>(new ai-combat-behavior<br />
:dist-target-attract 15.0 ;; stay this close to your target <br />
:dist-enemy-repel 5.0 ;; do not get closer than this to your target <br />
:dist-friend-repel 2.0 ;; do not get this close to your friends</p>
<p>:cover-weight 10.0 ;; prefer points that are cover<br />
:cover-move-range 15.0 ;; how far you’ll move to get to cover<br />
:cover-target-exclude-radius 8.0 ;; ignore covers this close to your target
:cover-sticky-factor 1.0 ;; prefer a cover you’re already in</p>
<p>:flank-target-front 0.0 ;; prefer to be in front of your target<br />
:flank-target-side 0.0 ;; prefer to be on the side of your target<br />
:flank-target-rear 0.0 ;; prefer to be at the rear of your target</p>
<p>:target-visibility-weight 5.0 ;; prefer points that can see your target<br />
)</p>
</blockquote>
<p>尽管这些并不是我们可以使用的全部数值,但它们是最常用的一些。这是我们中距离Combat Behaviors的一个缩减例子。为了解决这个问题,使用它的AI将希望与目标保持5到15米距离,同时试图与朋友保持2米距离。他们更喜欢掩体,并可能移动15米以进入掩体,但他们不希望选择距目标8米内的掩护点。他们也会喜欢他们已经进入过的掩体,以及挑选可以看到他们目标的点。</p>
<p>Combat Behaviors让我们在AI的行为方式上有很大的灵活性。通过创建一个库并根据需要切换它们,我们可以考虑大多数我们需要设置的任何类型的场景。当我们有很大的战斗空间时,他们也非常方便,玩家可以采取多种路线,因为他们不依赖于玩家采取线性路径,因为他们总是评估情况并根据设置的参数选择最佳地点。但是有一些缺点需要注意我们必须解决的问题。</p>
<p>因为这是一个模糊的系统,所以可能会发生错误的读数,您必须小心解决。例如,根据条件,系统可能为这一次评估选择一个不同的点,然后返回到之前的点进行下一次评估。 必须做一些过滤才能使AI做出相应的反应,否则它们可能会不必要地往返。我们可以将一些额外的参数放入行为中,以帮助提高稳定性并帮助拒绝这种行为。</p>
<p>另一个需要牢记的重要问题是路径查找系统与Combat Behaviors 系统完全分开。行为只会选择目的地,而路径发现则会指出如何到达目的地。</p>
<p>例如,如果行为有要求击退敌人5米的距离,则AI仍然可以在目标5米内行进,以抵达他们的位置。 此外,该系统可能会挑选需要奇特路径的点(例如,该点恰好位于墙的对面,需要AI需要运行一段长的很绕距离才能到达)。</p>
<p>虽然我们已经提出了一些方法来解决这类案件,但仍然是我们努力改进的一个问题。这是一个独立的分立系统的缺点,我们必须让他们更好地共享数据。</p>
<h2 id="集中">集中</h2>
<p>我们掌握的另一个与Combat Behaviors结合使用的工具是区域。区域由称为Zone Marker层的中的特定点指定,并且它包含半径和高度值以定义区域的边界。 任何具有区域设置的AI只会考虑落入区域内的Combat Behaviors 位置。 区域可以随时动态设置,包括设置在对象(如玩家)上。 这使我们可以根据战斗情况根据需要将AI集中到特定位置和对象。</p>
<h1 id="感官视觉">感官视觉</h1>
<p>AI用来制定决策的最后一个系统是它们的感官系统(视觉和听觉)。我们想改变的神秘海域1的一件事是防止AI始终知道他们的目标是在哪里。我们需要这个来介绍新的隐形游戏机制,并且让AI在战斗中看起来不那么全知。</p>
<p>我们提出的视觉系统是基于一组锥体和定时器。有一个外圆锥体和一个内直圆锥体。每个锥体有4个参数来定义它,它们是垂直角,水平角,范围和采集时间。</p>
<p>角度值决定了锥体的形状,而范围决定了它的延伸距离(超过这个距离,AI看不到)。 获取时间决定敌人必须在视锥内多久才能启动计时器。该值也沿着锥体的范围缩放,因此范围的最外边缘将是满值,并且它以线性变化越接近越短。</p>
<p>两个锥体和它们的计时器在发现目标时一起工作。当敌人进入外围锥体时,计时器开始倒计时(记住这是根据距离缩放的)。如果计时器到期,这表示AI认为他们可能看到了某些东西。然后他们转向干扰并尝试将干扰的位置置于其直视锥体内。如果敌方目标进入直接锥形,其计时器将开始倒计时。如果直接锥的计时器到期,那么AI将识别目标。</p>
<p>这个系统让AI能够脱离接触,并且让玩家能够在战斗时更具战略性。这也增加了他们像人类一样自然行为的感觉。一个很好的例子就是,当AI在攻击玩家时,他们进入一段长长的低掩体时。</p>
<p>在掩体内时,玩家会到达另一端,这样做时他们不会被看到。AI仍然会朝他们最后一次看到玩家的地方进行射击,因此在玩家脱离掩体时,允许玩家有一些额外的时间进行射击,而AI需要重新获得并转向玩家。</p>
<p>AI的单一视觉定义包含3组锥体,每组在玩游戏时用于不同的背景环境。第一组用于环境情况,第二组用于特殊情况称为预占,第三组用于战斗中。</p>
<p>环境情况是AI不知道该地区的敌人。这主要是AI开始遭遇潜行的(玩家)。环境被设置为具有更小的直接锥体和更长的获取时间,以允许玩家潜入周围环境。专注是在环境状态期间可以使用的一种特殊情况。</p>
<p>这个想法是,如果AI正在做一些他们将专注的事情,那么他们不会对周围发生的事情给予足够的关注。它们的距离相当短,而且取得时间很长。战斗是AI与他们的目标互动时使用的集合。它的设置使得获取时间要短得多,并且直接锥体的体积要大得多,以提高感知。</p>
<h1 id="呼">呼!</h1>
<p>因此,我们如何使用一系列AI系统进行了深入的技术研究, 这 是本文的结尾。下一部分将讨论我们使用的游戏技巧和哲学,以及一些经验教训。下次见(希望不会太久!)</p>
<h1 id="翻译后记">翻译后记</h1>
<p>不得不说,这是一篇质量超高的文章,作者对神海2中的敌人AI的设计的描述十分详细,特别是一个射击游戏中敌人AI设计中所具有的参数到今天看也不过时。而且,收获了一些有意思的事情,不过这个也只能说是我的自嗨。比如,当初用谷歌的视频通关神海1的时候,记得谷歌就吐槽过神海1的敌人是透视眼,一个被惊动了,所有的敌人就都知道你在哪里了。</p>Hanpc在最新一期的Uncharted 2战斗设计系列中,Naughty Dog战斗设计师Benson Russell深入研究了该工作室为其战斗AI设计的技术,以及这些技术是如何从该系列的原始游戏中演变而来的。第一部分讨论了团队如何从最初的Uncharted演变出战斗设计第二部分看看战斗触发设计。给初学者的游戏任务入门 The Quest for the Beginner2018-06-06T00:00:00+08:002018-06-06T00:00:00+08:00https://ihaibara.github.io/2018/06/06/The%20Quest%20fo%20the%20Beginner<p>这是Jacob Laurits Besenbacher Kjeldsen对于其公司开发的Custom Quest游戏任务子系统过程中的一些设计感悟,其展现了一些常见的任务系统在设计方面的技术路线,对于游戏的任务系统的设计有一定的指导作用。</p>
<blockquote>
<p>原文链接 <br />
https://www.gamedev.net/articles/game-design/game-design-and-theory/the-quest-for-the-custom-quest-system-r4728/</p>
</blockquote>
<h1 id="前言-动态系统设计的挑战">前言-动态系统设计的挑战</h1>
<p>Custom Quest从用于我们自己的游戏产品 <a href="http://randomdragongames.com/quest-accepted-poster/">Quest Accepted</a>中满足我们自身需求的小型任务系统,经过开发迭代,逐渐演变为更加动态的,可定制化的系统,直到现在最终发布。下面这些都是我们对任务设计和开发独立子系统的想法。</p>
<p>对于一支小型独立团队,将产品的主要功能细分成一个个小部分,比如一个任务系统,我们认为的一个好主意,这样我们就可以制作一些版本,并加快我们游戏的开发。但是建立一个适合你自己的系统是一回事,建立一个统一插件,让其他开发者创建任务,活动或者目标是另一回事,你永远不会把所有事情考虑周全。</p>
<p>我们必须意识到的第一件事情是,在构建任务系统时,目标不是设计好的任务,而是要让用户创造出伟大的任务。</p>
<p>这仍然意味着我们必须找出好的任务设计和任务真正的追求。</p>
<p>我们的任务是创建一个系统,让用户可以自由地为他们的玩家创造富有创造力的、有吸引力的任务体验。</p>
<h1 id="什么是任务-切入核心">什么是任务-切入核心</h1>
<p>首先,我们需要知道什么是真正的任务。</p>
<p>任务是追求,搜寻,探险,作业或工作,可以找到、增加或获得某些东西。在游戏中,任务和行动根据类型的不同,以许多不同的方式起作用。</p>
<p>一个单一的游戏可以包含多种不同类型的任务。 例如,在MMO中,任务是故事和玩家进步的载体。 在许多情况下,它们都是公式化的和简单的,有些甚至可以重复,数量上百,而且每个人都可以完成。 在其他游戏中,任务可能仅限于单人战役,和玩家的等级相符合,让玩家有目的感。</p>
<p>任务可以跨越整个游戏,或者它只是一个很小的可选任务。由于设计原则和创造性的任务设计繁多,我们必须专注,以真正地切入好的任务设计所需的核心。</p>
<p>所有任务的共同之处在于任务的工作,成功完成任务的标准、任务的奖励、任务的目标和玩家从做我们所要求的事情中得到的结果。</p>
<p>任务涵盖了令人难以置信多的各种工作,因此我们必须将决策建立在最纯粹的研究基础上。在我们的研究中,我们发现有三个层次来探索设计。</p>
<blockquote>
<p><strong>类型,模式和上层建筑</strong></p>
</blockquote>
<p>任务类型存在于任务模式中,并且任务模式存在于任务上层结构中。</p>
<p>我们发现有8种基本类型的任务,这些是玩家为完成具体任务必须完成的各种任务/标准。</p>
<p>我们发现有12种任务模式。它们是设计师可以设计他们的任务的方式。比如,以吸引人的方式进行设置并连接多个任务,或者教导玩家如何与游戏世界进行互动,并从游戏世界中获得最大收益,从而创造多样化并吸引玩家。</p>
<p>包含任务模式是任务的上层建筑,游戏中任务的总体结构。我们发现构建你的任务有两种主要方式。</p>
<p>经验告诉我们,任务需要有任务提供者,它们可以是NPC或对象,来告知玩家任务,以及玩家需要做什么、任务背后的故事、甚至是玩家完成任务后的奖励。</p>
<h1 id="任务类型---做到这一点做到那一点">任务类型 - “做到这一点,做到那一点”</h1>
<p>每个任务的核心都包括一个完成部分或全部任务的标准。下面这些是我们希望Custom Quest能够处理的任务类型。</p>
<ul>
<li>杀<br />
可能是最基本的任务类型,任务是杀死游戏中的某些东西;例如,杀死10个哥布林。</li>
<li>收集<br />
又一个非常简单的,任务是收集游戏世界中的x件事物;例如,收集浆果等。</li>
<li>护航<br />
玩家必须在保持安全的同时护送或追踪从A点到B点的人或物体。</li>
<li>FedX<br />
玩家是送货员,他们必须将物品交付给某个人或某个点。</li>
<li>保卫<br />
玩家必须在某个位置防御迎面而来的敌人,通常为一定数量的波次或时间。</li>
<li>利润<br />
玩家必须拥有一定数量的资源才能完成任务,与收集任务相反,这些资源是玩家本可以使用的资源。</li>
<li>启用<br />
玩家的任务是激活/与游戏世界中的一个或多个物体交互或与多个NPC交谈。在某些情况下,这必须按照一定的顺序进行,以达到拼图效果。</li>
<li>搜索<br />
搜索一个区域,发现游戏世界的一个区域。这对于向玩家介绍地图的某些区域非常有用,并且让他们立即感受到成就感,向他们展示新的任务集合等。</li>
</ul>
<h1 id="任务模式---一次吸引人的体验">任务模式 - “一次吸引人的体验”</h1>
<p>任务就是这样,在许多游戏中,任务可能有很多。但我们希望能够定制任务,让用户创建任务链,具体化它们,并以吸引玩家参与体验的方式进行设置。现在有许多模式能够达成此目的。</p>
<ul>
<li>
<p>箭头(Arrowhead) <br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-06-18-26-13-201866182614.png" alt="2018-06-06-18-26-13-201866182614" /> <br />
最基本的任务模式,在任务链开始,任务宽松且容易完成,玩家必须杀死一些低级别的小怪。下一个任务更窄,玩家必须杀死更少但更强硬的敌人,比如BOSS之前的门神。最后的任务是BOSS的战斗,玩家之前杀了这个BOSS下的所有小弟,现在可以杀死BOSS。这种任务模式非常简单,并且效果很好,在每个阶段或者只有当BOSS死亡时才会给予奖励。</p>
</li>
<li>
<p>侧桩(Side stub) <br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-06-18-31-39-201866183140.png" alt="2018-06-06-18-31-39-201866183140" /> <br />
侧桩是重叠任务的可选部分。比如说任务A导向任务C,但是有一个选择来完成一个副目标B,这使得完成C变得更容易,或者任务C改变奖励。举个例子,玩家必须逃离监狱,在这个例子中,侧桩是“释放其他囚犯”,所有囚犯都是自愿逃跑的,但他们可能使玩家更容易压制警卫,或者囚犯可能会在玩家出去时奖励玩家。侧桩不同于通用支线任务,因为它直接与主要任务相关联。</p>
</li>
<li>
<p>连续的支线任务<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-09-52-10-20186795220.png" alt="2018-06-07-09-52-10-20186795220" /><br />
这些是在整个游戏中发展的支线任务,一个解锁下一个,但它们也受外部需求的影响,例如故事进展。 这种模式的任务通常在RPG游戏中与派别人员一起出现,玩家必须与派别成员保持友好来解开他们的故事任务。</p>
</li>
<li>
<p>限时<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-09-54-17-20186795417.png" alt="2018-06-07-09-54-17-20186795417" /><br />
顾名思义,这些任务是时间敏感的。 任务可以是任何类型,重要的是如果时间用完,任务就会失败。这也可以用于一个任务的支线,即追求额外奖励的限时任务,但任务的主要目标不是追求时间。</p>
</li>
<li>
<p><a href="https://zh.wikipedia.org/wiki/既視感">Deja-vu</a>任务<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-09-57-27-20186795727.png" alt="2018-06-07-09-57-27-20186795727" /><br />
这种任务模式为玩家提供了他们之前完成或见过的任务。 在某些情况下,这个“新”任务会有一些转折或者被分开。它也可以是存在于游戏世界中不同区域的同一类任务,比如也许有不止一个地精营地?或者玩家可能每天都要采集浆果。</p>
</li>
<li>
<p>延迟的影响<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-01-28-20186710128.png" alt="2018-06-07-10-01-28-20186710128" /><br />
先前决定的延迟后果。常用于故事部分很重要,并且玩家的选择会产生一定影响的游戏中。一些任务被捆绑在一起,玩家并不知道。 比方说,玩家可以选择给乞丐一些黄金来养活自己。当他下次遇到乞丐时,乞丐就会变得富有,并以十倍的奖赏回报玩家的好意。</p>
</li>
<li>
<p>其中之一<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-04-28-20186710428.png" alt="2018-06-07-10-04-28-20186710428" /><br />
玩家将获得许多任务,他们必须选择完成哪一个,他们只能选择一个。其他将不可用。</p>
</li>
<li>
<p>隐藏的任务<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-05-42-20186710542.png" alt="2018-06-07-10-05-42-20186710542" /><br />
隐藏的任务乍看之下并不是明显的任务,或者只有最强悍的玩家才能找到隐藏的任务。比如玩家获得了一个拥有铭文的物品,并且玩家找到了铭文相关的人,那么这个玩家可能因为运送了物品而获得奖励。这是一个很好的任务模式,这些类型的任务可以真正让游戏世界变得活跃起来,变得更有吸引力,让玩家发现秘密、复活节彩蛋乃至于发现为他们创造的整个世界。</p>
</li>
<li>
<p>道德困境<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-11-16-201867101116.png" alt="2018-06-07-10-11-16-201867101116" /><br />
惩罚偷面包来喂养家人的小偷?通常用于对玩家具有善良或邪恶评判水准的游戏中,这些类型的任务使得玩家可以选择他们想玩什么样的角色,他们可以选择角色是好还是坏。</p>
</li>
<li>
<p>支线任务<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-13-04-20186710134.png" alt="2018-06-07-10-13-04-20186710134" /><br />
可选任务,这些任务常常在基于级别的游戏中找到,游戏中必须完成整个任务才能达到下一个级别,玩家可以选择执行一些额外任务以获得更多分数。 重要的部分是这些是可选的,但是它们给了玩家一个奖励,让玩家能够从游戏中获得能够获得的一切。</p>
</li>
<li>
<p>锦标赛<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-14-43-201867101443.png" alt="2018-06-07-10-14-43-201867101443" /><br />
锦标赛风格任务,随着玩家进步而变得更加艰难的一系列任务。 一个例子可能是角斗场,玩家一个接一个地击败五个敌人而获得奖励成为竞技场的冠军,但是,如果他在第三次挑战失败,则整个比赛失败并且他必须从头开始。</p>
</li>
<li>
<p>车辆任务<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-16-16-201867101617.png" alt="2018-06-07-10-16-16-201867101617" /><br />
这些任务不仅限于和车辆有关,它们只是玩家控制方案的更改以完成任务的任务。一个例子可能是,在游戏世界中从奔跑状态变成驾驶坦克状态来摧毁堡垒。</p>
</li>
</ul>
<h1 id="任务上层建筑---包罗万象">任务上层建筑 - “包罗万象”</h1>
<p>在任务上层结构中,我们的探究进入到了一般游戏设计。上层建筑是玩家被允许在游戏世界完成任务的方式。这基本上是游戏是“开放世界”还是线性体验的问题。</p>
<ul>
<li>
<p>钻石结构<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-21-24-201867102124.png" alt="2018-06-07-10-21-24-201867102124" /><br />
开放的世界模式,例如像上古卷轴V:天际,玩家通过一个任务引导到游戏中,但之后,他们可以去任何地方,做任何他们想要的任务。游戏中上述类型和模式的任务数量很多,玩家可以自由选择。把游戏世界交给玩家。然而,玩家仍然可以通过不同的选择来通关整个游戏,甚至能够达成多种结局。任务可以集中到任务中心,即有很多事情要做的城镇,但任务不必以线性方式完成。</p>
</li>
<li>
<p>线性集中结构<br />
<img src="http://otsjxkilz.bkt.clouddn.com/2018-06-07-10-26-01-20186710261.png" alt="2018-06-07-10-26-01-20186710261" /><br />
这种结构包含许多需要完成的“桥梁”任务,以便解锁下一个区域或“枢纽”。每个枢纽可以有任意数量的任务,这可能是一个充满着麻烦的人的城镇。每个任务随着他们自己的任务和任务链完成,当他们完成后,玩家通过另一个桥的任务移动到下一个枢纽。限制枢纽上的任务规模将使得任务结构感觉更线性,创建更大的更开放的枢纽可以使玩家感觉更自由。</p>
</li>
</ul>
<h1 id="结果---这么多选择">结果 - “这么多选择!”</h1>
<p>定制任务的开发一直是为了让游戏开发者创建使用这些类型的任务和活动。但是,无论我们研究得如何,都会有人提出一种新的创造性的做任务的方式。</p>
<p>对我们来说,解决方案是让系统更具可定制性。让用户将他们的任务预制转换为自动继承核心功能的任务脚本,以便用户可以在现有核心之上自由添加他们自己的附加功能。</p>
<h1 id="价值产出作为推动力---学习体验">价值产出作为推动力 - “学习体验”</h1>
<p>以这种方式开发,将生产分解成可以独立运作并且可以被其他人使用的子系统并不是一件应该被轻视的事情,但是如果你能够建立持久的东西,其他人可以在使用中找到价值,那么最终的产品会更好。Custom Quest作为一个我们一开始认为可以在几个月内完成的项目,最终花费了7个月。</p>
<p>部分原因是我们意识到,如果我们要发布系统,那么我们需要做对的事,这意味着要创建一个可定制和强大的系统,这个系统可以添加到用户游戏中,它是我们可以引以为傲的系统。</p>
<p>为其他开发人员开发的体验与开发游戏完全不同。一个让程序员和公司更加强大的公司,迫使我们以新的方式思考,以创建一个动态的,可定制的解决方案。Custom Quest已经从我们可以在Quest Accepted中使用的资产演变为其他人可用来创建独特游戏体验的工具。总而言之,体验是一个很好的体验,Random Dragon因此更强大,但是我会建议在开始开发之前考虑一下你的插件的额外开发时间。</p>
<blockquote>
<p>资料来源:<br />
www.pcgamesn.com - “我们知道你不愚蠢” - CD Projekt RED的大师级任务设计
http://www.pcgamesn.com/the-witcher-3-wild-hunt/the-witcher-quest-design-cd-projekt-masterclass<br />
http://www.gamasutra.com/ - 游戏设计精华:20款RPG游戏 - http://www.gamasutra.com/view/feature/4066/game_design_essentials_20_rpgs.php?print=1</p>
</blockquote>Hanpc这是Jacob Laurits Besenbacher Kjeldsen对于其公司开发的Custom Quest游戏任务子系统过程中的一些设计感悟,其展现了一些常见的任务系统在设计方面的技术路线,对于游戏的任务系统的设计有一定的指导作用。在不弹出UAC提示的情况下以管理员权限运行程序2017-07-28T00:00:00+08:002017-07-28T00:00:00+08:00https://ihaibara.github.io/2017/07/28/How%20to%20elevate%20an%20EXE%20without%20promt<p><em>本文描述了如何使用计划任务在不弹出UAC提示的情况下以管理员权限运行程序</em></p>
<h1 id="在不弹出uac提示的情况下以管理员权限运行程序">在不弹出UAC提示的情况下以管理员权限运行程序</h1>
<h2 id="背景">背景</h2>
<p>用户帐户控制(UAC)是与Microsoft Windows Vista和Windows Server 2008操作系统一起引入的技术亦即安全基础设施,Windows 7,Windows Server 2008 R2,Windows 8,Windows Server 2012和Windows 10等均继承了这项技术。它旨在通过将应用程序软件限制为标准用户权限,直到管理员授权增加或升高来提高Microsoft Windows的安全性。 以这种方式,只有用户信任的应用程序才会拥有管理权限,恶意软件应该不会损害操作系统。</p>
<p><strong>UAC的确有效地保护了用户的操作系统,作为一个正常运行的应用程序,在任何情况下都不应该尝试绕过系统的UAC保护</strong></p>
<p><em>在某种特殊情况下我们可以通过创建、运行任务计划来以不弹出UAC提示的情况下提权运行程序</em></p>
<h2 id="创建计划任务">创建计划任务</h2>
<ul>
<li>我们需要进入到<em>计划任务</em>管理的界面,比如我们从控制面板中进入</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728164655271.png" alt="enter-schedule-from-control-panel" /></p>
<blockquote>
<p>可以看到,计划任务旁边是有小盾牌图标的,也就是说,你在更改计算任务时候是需要拥有管理员权限的,不过这个是所谓的<em>Windows设置</em>,默认UAC设置下不会有UAC权限提示
<img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728164914536.png" alt="" /></p>
</blockquote>
<ul>
<li>在<em>任务计划程序</em>中,选择左侧的<em>任务计划程序库</em>,可以看到许多任务计划,比如我们的大LM,360。选择右边的<em>创建任务</em>可以开始创建一个计划任务</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728165052274.png" alt="schedule-task-window" /></p>
<ul>
<li>在<em>创建任务</em>界面中的<em>常规</em>选项卡中,我们需要设置任务的名称,比如NotePad,然后在下面勾选<em>使用最高权限运行</em></li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728165654229.png" alt="Create-task-general-tab" /></p>
<ul>
<li>在<em>创建任务</em>界面中的<em>触发器</em>选项卡中,我们可以为任务添加一个触发的条件,<strong><em>如果设置了登录时选项,那么每次用户登录的时候,就会触发我们设定的任务计划了</em></strong>,不过为了测试方面,我们就不添加任何触发器了</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728170002978.png" alt="Create-task-trigger-tab" /></p>
<ul>
<li>在<em>创建任务</em>界面中的<em>操作</em>选项卡中,在左下角我们可以新建一个任务的动作,比如我这里设置了打开notepad++</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728170314302.png" alt="Create-task-operation-tab" /></p>
<ul>
<li>
<p>其他的设置目前和本文的目的无关,读者可以自行查看、设置。</p>
</li>
<li>
<p>确定创建任务之后,我们就可以在任务列表中看到我们创建的任务计划了</p>
</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728170638408.png" alt="create-task-done" /></p>
<h2 id="运行计划任务">运行计划任务</h2>
<ul>
<li>在桌面或者文件夹空白处右击,新建快捷方式</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728170928706.png" alt="new-shortcut" /></p>
<ul>
<li>在<em>对象的位置</em>中输入以下字符串,其中NotePad需要替换成刚刚的计划任务名字</li>
</ul>
<blockquote>
<p>C:\Windows\System32\schtasks.exe /run /tn NotePad</p>
</blockquote>
<ul>
<li>点击<em>下一步</em>,给快捷方式起个名字,确定就好了,比如下面这个样子</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728171328360.png" alt="new-shortcut-name" /></p>
<ul>
<li>然后启动这个快捷方式,程序会在一个黑框<del>schtasks.exe:怪我咯</del>闪过之后以权限提升方式运行</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728171643862.png" alt="exe-run-elevated" /></p>
<p><strong><em>本文完</em></strong></p>Hanpc本文描述了如何使用计划任务在不弹出UAC提示的情况下以管理员权限运行程序在Windows上部署git pages本地环境2017-07-23T00:00:00+08:002017-07-23T00:00:00+08:00https://ihaibara.github.io/2017/07/23/Start%20git%20pages%20with%20jekyll%20in%20Windows<p><em>本文描述了这个博客的搭建过程中遇到的坑坑洼洼</em></p>
<h1 id="使用jekyll在git-page上搭建网站">使用jekyll在git page上搭建网站</h1>
<p>可以看到,本博客的链接是<em>github.io</em>。这个是github提供的一个静态网站托管服务,实际上就是个网络主机,但是从零开始搭建网站实在有点在造轮子之嫌,所以github就在pages的<a href="https://pages.github.com/">主页</a>上推荐大家使用jekyll这个静态网站生成工具。</p>
<p>jekyll这个工具支持主题,很多都是非常漂亮的开源的主题,这对于我这个前段白痴简直就是福音,而且jekyll网站还有非常细致的<a href="http://jekyllrb.com/docs/home/">文档</a>,国内也有Bootstrap中文网翻译整理的<a href="http://jekyll.com.cn/">中文文档</a>,git上也有step-by-step的<a href="https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/">教程</a>。</p>
<p>一个正常的博客搭建流程就是,fork,clone到本地,删删改改,commit,push就可以了。照理我这篇博客大概率就是个无病呻吟贴了o(╯□╰)o。</p>
<p>可是,开源也有开源的坏处,比如</p>
<ul>
<li>好多开源主题的jekyll版本和github上的版本并不兼容,直接fork可能出错,单纯的在线使用比较麻烦。</li>
<li>jekyll中文文档翻译不全,特别是没有翻译windows上安装jekyll运行环境。</li>
</ul>
<blockquote>
<p>在 Windows 下使用 Jekyll
你可以使用 Jekyll running on Windows, 但是官方文档并不建议你在 Windows 平台上安装 Jekyll。</p>
</blockquote>
<ul>
<li>jekyll的英文文档提供了<a href="http://jekyllrb.com/docs/windows/">Windows上安装jekyll</a>的教程,但是这个教程光安装jekyll本身就提供了3种方法,莫衷一是,更不要提安装git-pages支持的jekyll了。</li>
</ul>
<p>所以,我希望把我自己的踩坑经历分享一下,希望后人能少踩坑。</p>
<h1 id="整体的技术路线">整体的技术路线</h1>
<ol>
<li><a href="https://labs.sverrirs.com/jekyll//2-jekyll-gem.html">Jekyll 3 on Windows</a> 安装好ruby环境</li>
<li>使用<a href="http://gems.ruby-china.org/">RubyGems 镜像 - Ruby China</a>,并解决SSL链接问题<a href="https://gist.github.com/fnichol/867550">解决的方法</a></li>
<li>使用<a href="https://help.github.com/articles/setting-up-your-github-pages-site-locally-with-jekyll/">Setting up your GitHub Pages site locally with Jekyll</a>进行最后的设置</li>
</ol>
<p>上面教程中,1、3以及jekyll的官方文档可能有步骤重叠,而且每个人的具体开始和结束的状态都不一样。因此我先介绍下我自身的状态。</p>
<p>本文基于的操作系统是Windows 7 x64系统,Win 10也肯定支持<del>算是博文这次,我都装了3遍了</del>。本地上已经存在了博客的git版本库,但是没办法本地运行的状态。
最终状态是本地生成通过,localhost看到网页。</p>
<p>下面就是简要的构建说明,建议本文和技术路线中的教程对照着看。</p>
<h1 id="在windows上安装jekyll本地环境">在Windows上安装jekyll本地环境</h1>
<p>这个安装过程参考了<a href="https://labs.sverrirs.com/jekyll/">Jekyll 3 on Windows</a> 这个帖子,这个是很全很傻瓜的step-by-step的安装教程。具体可以参考原作者的帖子,我这里简单说一下需要注意的地方。</p>
<h2 id="install-ruby-and-the-ruby-devkit">Install Ruby and the Ruby DevKit</h2>
<p>这一步中就是要安装Ruby和Ruby的Devkit,需要注意的是。</p>
<ul>
<li>现在Ruby官网上最新的Stable版本变成了2.2.6,而不是原帖中的2.2.5。还有就是操作系统版本要区分x86和x64。</li>
</ul>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/ruby-version.png" alt="ruby-version" /></p>
<ul>
<li>一定要记得把ruby的路径加入到windows系统的path路径中,默认是没有勾选的。</li>
</ul>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/ruby-system-path.png" alt="ruby-system-path" /></p>
<ul>
<li>Ruby DevKit其实就是一个zip压缩包,自定好解压路径就好</li>
</ul>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/ruby-dev-kit-path.png" alt="ruby-dev-kit-path" /></p>
<p>安装好ruby可以检查一下</p>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/ruby-check-version.png" alt="ruby-check-version" /></p>
<p>如果在<em>ruby dk.rb review</em> 中出现了<em>Invalid configuration. Please fix ‘config.yml.’</em> 错误的话,在config.yml中添加Ruby的root路径即可</p>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/ruby-add-root-dic.png" alt="ruby-add-root-dic" /></p>
<h2 id="install-the-jekyll-gem">Install the Jekyll Gem</h2>
<p>通过上文的安装过程后,本机应该就具有使用gem安装ruby包的能力了,可以先测试一下</p>
<blockquote>
<p>gem -v</p>
</blockquote>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/gem-v.png" alt="gem-v" /></p>
<p>在开始安装jekyll之前,建议国内伙伴们,切换源到ruby china的gem镜像站 http://gems.ruby-china.org/</p>
<ul>
<li>先升级gem到2.6版本以上,jekyll官网对这个还真有解释</li>
</ul>
<blockquote>
<p>Updates in the infrastructure of Ruby may cause SSL errors when attempting to use gem install with versions of the RubyGems package older than 2.6. (The RubyGems package installed via the Chocolatey tool is version 2.3) If you have installed an older version, you can update the RubyGems package using the directions <a href="http://guides.rubygems.org/ssl-certificate-update/#installing-using-update-packages">here</a>.</p>
</blockquote>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/gem-update.png" alt="gem-update" />
使用<em>gem update –system</em>可能慢一点,也可以使用上面那个here的链接本地升级,升级完了就是上面那个样子</p>
<ul>
<li><del>然后切换gem源</del></li>
</ul>
<blockquote>
<p>好吧,这里报错了,安装windows jekyll经常会碰到的ssl错误</p>
</blockquote>
<blockquote>
<p>ERROR: SSL verification error at depth 1: unable to get local issuer certificat
e (20)
<img src="https://ihaibara.github.io/assets/images/2017-07-23/ssl-erro.png" alt="ssl-erro" /></p>
</blockquote>
<blockquote>
<p>报错的根源是openssl的验证问题,<a href="https://gist.github.com/fnichol/867550">解决的方法</a>也很简单,下载个认证文件,然后添加变量SSL_CERT_FILE,比如像这里
<img src="https://ihaibara.github.io/assets/images/2017-07-23/ssl-file-pat.png" alt="ssl-file-pat" /></p>
</blockquote>
<blockquote>
<p>然后重启动命令行窗口,</p>
</blockquote>
<ul>
<li>然后切换gem源</li>
</ul>
<blockquote>
<p>gem sources –add https://gems.ruby-china.org/ –remove https://rubygems.org/</p>
</blockquote>
<p>一切正常的话是这样的
<img src="https://ihaibara.github.io/assets/images/2017-07-23/change-gem-source.png" alt="change-gem-source" /></p>
<p>切换源实测速度上、稳定上都是有提升的。</p>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/jekyll-install.png" alt="jekyll-install" /></p>
<p>此外,原帖中也提供了一些有用的gems,我也不知道有什么用,也都装上了。</p>
<h2 id="install-a-syntax-highlighter">Install a Syntax Highlighter</h2>
<p>这个没什么说的,装就是了。。。</p>
<h2 id="另外的步骤">另外的步骤</h2>
<p>如果你尝试像git-page上的wiki执行命令<em>bundler exec jekyll install</em>错误的话可以照着提示来。
<img src="https://ihaibara.github.io/assets/images/2017-07-23/bundler-install.png" alt="bundler-install" /></p>
<h2 id="安装完毕">安装完毕</h2>
<p>至此,jekyll在你的电脑上就安装完毕了。</p>
<h1 id="windows下的git-pages">Windows下的git-pages</h1>
<p>这里主要是一些配置可能需要注意,比如在网站根目录下的Gemfile需要添加以下3行配置</p>
<blockquote>
<p>source ‘http://gems.ruby-china.org/’</p>
</blockquote>
<blockquote>
<p>gem ‘github-pages’, group: :jekyll_plugins</p>
</blockquote>
<blockquote>
<p>gem ‘tzinfo-data’, platforms: [:mingw, :mswin, :x64_mingw]</p>
</blockquote>
<p>前2行的配置是用来<em>Ruby will use the contents of your Gemfile to build your Jekyll site.</em>
后一行是配置时区的。
其他的问题我暂时没有遇到,如果遇到了可以看gitpage的<a href="https://help.github.com/articles/troubleshooting-github-pages-builds/">trouble-shooting</a></p>
<h1 id="最终状态">最终状态</h1>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/final-state.png" alt="final-state" /></p>
<p><img src="https://ihaibara.github.io/assets/images/2017-07-23/final-page-state.png" alt="final-page-state" /></p>Hanpc本文描述了这个博客的搭建过程中遇到的坑坑洼洼检测Windows进程是否是提权运行状态2017-07-23T00:00:00+08:002017-07-23T00:00:00+08:00https://ihaibara.github.io/2017/07/23/How%20to%20check%20if%20a%20process%20is%20running%20as%20administrator%20(elevated)%20in%20Windows<p><em>本文描述了如何使用任务管理器查看某个正在运行的进程是否是处于提权运行状态</em></p>
<h1 id="检测windows进程是否是提权运行状态">检测Windows进程是否是提权运行状态</h1>
<blockquote>
<p>本文主要引用了<a href="http://winaero.com/blog/how-to-check-if-a-process-is-running-as-administrator-elevated-in-windows/">How to check if a process is running as administrator (elevated) in Windows</a></p>
</blockquote>
<h2 id="背景">背景</h2>
<p>自从Windows Vista引入了用户帐户控制以来,有必要偶尔作为管理员运行一些程序进行一些功能。 如果UAC设置设置为Windows中的最高级别,那么当您以管理员身份打开应用程序时,您将获得UAC提示。 但是,当UAC设置处于较低级别时,签名的Windows EXE将以静默方式提升自身的运行权限(elevated)。 此外,还有一些作为管理员运行的计划任务,<em>您甚至可以创建自己的快捷方式来在没有UAC提示的情况下提升运行</em>。 在本文中,我们将看到如何确定进程是否以管理员身份运行。</p>
<h2 id="windows-10windows-81windows-8">Windows 10/Windows 8.1/Windows 8</h2>
<ul>
<li>打开任务管理器,切换到详细信息tab页面,一般情况下如图所示</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-2017072815565359.png" alt="win10-taskmgr-normal" /></p>
<ul>
<li>在任意列上右击,在弹出的”选择列”中下拉选择“特权”列</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-2017072816025301.png" alt="win10-taskmgr-select" /></p>
<ul>
<li>然后就可以看到目前处于提权状态的进程了,比如我们的360</li>
</ul>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728160431928.png" alt="win10-task-mgr-elevated" /></p>
<h2 id="windows-7-or-windows-vista">Windows 7 or Windows Vista</h2>
<p>在Win 7中,上面提到的选择列中,并没有“特权”列,这个时候我们选择“UAC虚拟化”</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-2017072816081552.png" alt="win7-uac-vitual" /></p>
<p>我没有在Win 7上测试过,不过在Win10 上,多数的结果和特权的显示是一致的</p>
<p><img src="http://otsjxkilz.bkt.clouddn.com/markdown-img-paste-20170728160939904.png" alt="win10-win7-uav-virtual" /></p>
<p><strong><em>本文完</em></strong></p>Hanpc本文描述了如何使用任务管理器查看某个正在运行的进程是否是处于提权运行状态Welcome2017-07-21T00:00:00+08:002017-07-21T00:00:00+08:00https://ihaibara.github.io/2017/07/21/Welcome<h1 id="welcome-to-toy">Welcome to Toy</h1>
<p>写博客不是在写软件,软件的写作目的是工作、学习或者科研,写博客的目的是为了分享。所以写软件的时候,第一句可以是<code class="highlighter-rouge">Hello World!</code>,因为那是我自己给自己<em>Say Hi</em>。但是写博客就不同了,既然看我博客的读者才是我的博客的真正的用户,那么我的第一篇博客自然就是<code class="highlighter-rouge">Welcome</code>,欢迎来到我折腾玩具的地方。</p>HanpcWelcome to Toy