11、其他资源

11. 其他资源

11.1 与 Epic 游戏公司的 Dave Ratti 的问答

11.1.1 社区问答1

Dave Ratti 对 Unreal Slackers Discord Server 社区关于 GAS 的问题的回答:

  1. 如何在不依赖GameplayAbilities的情况下创建按需的预测窗口?例如,当一个“发射即忘”的投射物击中敌人时,如何在本地预测伤害GameplayEffect

PredictionKey系统并不是为了处理这种情况而设计的。从根本上说,这个系统通过客户端发起预测操作,使用一个键将其告知服务器,然后客户端和服务器执行相同的操作,并将预测副作用与给定的预测键关联。例如,“我正在预测性地激活一个能力”或“我已经生成了目标数据,并准备在WaitTargetData任务之后预测性地运行能力图的下一部分”。

在这种模式下,预测键会“反弹”回服务器,并通过UAbilitySystemComponent::ReplicatedPredictionKeyMap(已复制属性)返回给客户端。一旦键从服务器复制回客户端,客户端就能够撤销所有本地预测的副作用(如GameplayCues和GameplayEffects):复制的版本会在那里,如果没有,则说明是错误的预测。在此,准确知道何时撤销预测副作用至关重要:如果过早撤销,将看到缺失;如果过晚撤销,则会出现“双重效果”。(注意,这里指的是有状态的预测,例如持续时间基础的GameplayEffect的循环GameplayCue,“Burst”GameplayCues和即时GameplayEffects从不被“撤销”或回滚。如果与它们相关联的预测键存在,则客户端会跳过这些效果)。

为了进一步强调这一点:至关重要的是,预测性操作是服务器在客户端告知它时才会执行的,而不是服务器自己做的。因此,像“按需创建键并告诉服务器,我可以运行某些东西”这样的做法是行不通的,除非“某些东西”是服务器只在客户端告知后才会执行的。

回到原始问题:类似于“发射即忘”的投射物。Paragon和Fortnite都有使用GameplayCues的投射物演员类。然而,我们并没有使用预测键系统来做这些。相反,我们有一个概念,叫做“非复制的GameplayCues”。这些GameplayCues只是本地触发,并完全跳过服务器。基本上,所有这些都是对UGameplayCueManager::HandleGameplayCue的直接调用。它们不通过UAbilitySystemComponent,因此没有进行预测键检查或提前返回。

非复制的GameplayCues的缺点是,它们并没有被复制。因此,确保调用这些函数的代码路径在所有客户端上都能运行就完全依赖于投射物类/蓝图。我们有用于启动(在BeginPlay中调用)、爆炸、撞墙/角色等的cue。

这些类型的事件已经在客户端生成,所以调用非复制的GameplayCue并不是什么大问题。复杂的蓝图可能会有点棘手,且作者需要确保了解哪些内容在哪些地方运行。

  1. 使用WaitNetSync AbilityTaskOnlyServerWait来在本地预测的GameplayAbility中创建一个有范围的预测窗口时,玩家是否可能通过延迟他们的网络包来控制GameplayAbility的时间?因为服务器正在等待他们的RPC和预测键。这个问题在Paragon或Fortnite中曾经出现过吗?如果出现过,Epic是如何解决的?

是的,这是一个有效的担忧。任何在服务器上运行的能力蓝图,等待客户端的“信号”,都可能容易受到延迟开关类型的利用。

Paragon有一个类似于UAbilityTask_WaitTargetData的自定义目标任务。在这个任务中,我们设置了超时,或者一个“最大延迟”,在客户端即时确认目标模式时等待。如果目标模式需要用户确认(按钮按下),则会忽略这个延迟,因为用户可以自行决定时间。但对于即时确认目标的能力,我们只会等一段时间后,要么A) 在服务器端生成目标数据,要么B) 取消能力。

我们对于WaitNetSync并没有使用类似的机制,且使用得也非常少。

我不认为Fortnite有使用类似的机制。Fortnite中的武器能力专门进行了批处理,使用单一的Fortnite特定RPC:一个RPC来激活能力,提供目标数据并结束能力。所以在Battle Royale中,武器能力本质上并不容易受到这个问题的影响。

我的看法是,这个问题可能可以在系统级别解决,但我不认为我们会很快做出这样的更改。针对您提到的情况,为WaitNetSync增加最大延迟的修复可能是合理的任务,但同样——我们不太可能在近期内做出这样的修改。

  1. Paragon和Fortnite使用了哪种EGameplayEffectReplicationMode,Epic对于何时使用每种模式有什么建议?

两个游戏基本上对于玩家控制的角色使用的是Mixed模式,对于AI控制的角色(AI小兵、丛林怪物、AI僵尸等)使用Minimal模式。这是我建议大多数多人游戏使用系统的人选择的模式。越早在项目中设置这些,效果越好。

Fortnite在优化方面走得更远。它实际上并没有对模拟代理(simulated proxies)复制UAbilitySystemComponent。在::ReplicateSubobjects()中,组件和属性子对象被跳过。我们将Ability System Component中的最小复制数据推送到Pawn本身的结构体中(基本上是属性值的子集和我们在位图中复制的标签的白名单子集)。我们称之为“代理”(proxy)。在接收端,我们将这些数据从Pawn复制到Ability System Component上。所以在FNBR中,每个玩家都有一个ASC,它只是没有直接复制:而是通过Pawn上的最小代理结构进行复制,然后路由回接收端的ASC。这是有优势的,因为它A) 复制的数据量更小,B) 利用Pawn的相关性。

我不确定随着其他服务器端优化(如Replication Graph等)的出现,这种做法是否仍然必要,而且它也不是最易维护的模式。

  1. 由于我们不能预测GameplayEffects的移除(根据GameplayPrediction.h),是否有任何策略可以缓解延迟对移除GameplayEffects的影响?例如,在移除移动速度减慢效果时,我们目前必须等待服务器复制GameplayEffect的移除,从而导致玩家角色位置发生瞬移。

这是一个棘手的问题,我没有一个很好的答案。我们通常通过容忍度和平滑处理来避免这些问题。我完全同意能力系统和角色移动系统之间的精确同步目前处于一个不太理想的状态,这是我们希望修复的部分。

我曾经尝试允许预测性地移除GameplayEffects,但在解决所有边界情况之前就不得不放弃了。不过,这并不能解决所有问题,因为角色移动仍然有一个内部的保存移动缓冲区,它并不知道能力系统和可能的移动速度修正等内容。即便不考虑不能预测GameplayEffects的移除,仍然有可能进入修正反馈回路。

如果你认为有一个非常紧急的情况,你可以预测性地添加一个GameplayEffect来抑制你的移动速度GameplayEffect。我自己从未做过,但曾经理论化过这个方法。它可能有助于解决某些问题。

  1. 我们知道在Paragon和Fortnite中,AbilitySystemComponent位于PlayerState上,而在Action RPG示例中,它位于Character上。Epic的内部规则、指南或建议是什么,关于AbilitySystemComponent应该放在哪里,Owner应该是什么?

一般来说,我认为不需要重新生成的对象应该将OwnerAvatar设为相同的对象,比如AI敌人、建筑、世界道具等。

任何需要重新生成的对象,OwnerAvatar应该是不同的,这样AbilitySystemComponent就不需要在重新生成后保存/重新创建/恢复。PlayerState是一个逻辑上的选择,它会被复制到所有客户端(而PlayerController则不会)。缺点是PlayerState总是相关的,所以在100人游戏中可能会遇到问题(参见问题#3中Fortnite的做法)。 以下是您的文本翻译成中文并以Markdown格式呈现的内容:

  1. 是否可以让多个 AbilitySystemComponents 拥有相同的所有者,但有不同的化身(例如在Pawn和武器/物品/投射物上,Owner 设置为 PlayerState)?

我看到的第一个问题是如何在拥有的演员上实现 IGameplayTagAssetInterfaceIAbilitySystemInterface。前者可能可行:只需从所有 ASC 聚合标签(但要小心——HasAllMatchingGameplayTags 可能需要通过跨 ASC 聚合来满足。仅仅将调用转发到每个 ASC 并将结果“或”起来是不够的)。但后者更棘手:哪个 ASC 是权威的?如果有人想应用 GE,应该由哪个 ASC 接收?也许你能解决这些问题,但这一方面的问题将是最难的:所有者下面有多个 ASC。

但在Pawn和武器上分开 ASC 可能是合理的。例如,区分描述武器的标签和描述拥有的Pawn的标签。也许确实有道理,授予武器的标签也“应用”于拥有者而不是其他任何东西(例如,属性和 GE 是独立的,但拥有者会聚合它们拥有的标签,像我上面描述的那样)。这应该可以解决,我很确信。但拥有多个具有相同所有者的 ASC 可能会变得复杂。


  1. 有没有办法阻止服务器覆盖拥有客户端的本地预测能力的冷却时间?在高延迟场景中,这将允许拥有客户端“尝试”再次激活该能力,当它的本地冷却时间过期时,但它仍然在服务器上处于冷却状态。当拥有客户端的激活请求通过网络到达服务器时,服务器可能已经冷却完毕,或者服务器可能能够排队剩余几毫秒的激活请求。否则,像现在这样,具有较高延迟的客户端在重新激活能力时会比延迟较低的客户端有更长的延迟。这在冷却时间极短的能力(例如基本攻击,冷却时间不到一秒)上最为明显。如果没有办法阻止服务器覆盖本地预测能力的冷却时间,Epic 针对高延迟对重新激活能力的影响有何缓解策略?换句话说,Epic 是如何设计《Paragon》的基本攻击和其他能力,以便高延迟玩家能够与低延迟玩家一样快速地攻击或激活(本地预测)?

简短的回答是没有办法防止这种情况,Paragon 确实存在这个问题。较高延迟的连接会导致基本攻击的 ROF 较低。 我曾试图通过添加“GE 协调”来解决这个问题,即在计算 GE 持续时间时考虑延迟。实际上,允许服务器消耗一些总的 GE 时间,使得 GE 客户端时间在任何延迟下都能与服务器时间保持一致(尽管波动仍然可能导致问题)。然而,我从未能将其做得足够完善以供发布,项目进展很快,我们也没能完全解决这个问题。 Fortnite 对武器射击速率进行了自己的账务处理:它不使用 GE 来处理武器的冷却时间。如果这是你游戏的关键问题,我建议你也采用这种方式。


  1. Epic 对 GameplayAbilitySystem 插件的路线图是什么?Epic 计划在 2019 年及以后添加哪些功能?

我们认为总体上这个系统目前非常稳定,我们没有人专门致力于开发新的主要功能。偶尔会为 Fortnite 或来自 UDN/拉取请求进行错误修复和小的改进,但现在就只有这些。 从长远来看,我认为我们最终会做一个“V2”或进行一些大规模的更改。我们从编写这个系统中学到了很多,感觉做对了很多事情,也犯了不少错误。我很想有机会纠正这些错误,改进一些上面提到的致命缺陷。 如果真的出现 V2,提供升级路径将是至关重要的。我们绝不会做一个 V2 然后让 Fortnite 永远停留在 V1:会有一些路径或程序来尽可能自动迁移,尽管仍然几乎肯定需要手动重新构建。 高优先级的修复包括:

  • 更好的与角色移动系统的互操作性。统一客户端预测。
  • GE 移除预测(问题 #4)。
  • GE 延迟协调(问题 #7)。
  • 一般的网络优化,如批处理 RPC 和代理结构。主要是我们为 Fortnite 做的工作,但寻找方法将其分解成更通用的形式,至少这样游戏就能更容易地编写自己的游戏特定优化。

我考虑进行的一些更一般的重构类型的更改包括:

  • 我想从根本上考虑不再让 GE 直接引用电子表格中的数值,而是让它们能够发出参数,这些参数可以由绑定到电子表格值的更高级别对象填充。当前模型的问题是,GE 因为与曲线表行紧密耦合,变得不可共享。我认为可以编写一个通用的参数化系统,并作为 V2 系统的基础。
  • 减少 UGameplayAbility 上的“策略”数量。我会去掉 ReplicationPolicyInstancingPolicy。我认为复制几乎永远不需要,并且会造成困惑。InstancingPolicy 应该被通过将 FGameplayAbilitySpec 变成可以子类化的 UObject 来替代。这应该是“非实例化的能力对象”,具有事件并可以通过蓝图实现。UGameplayAbility 应该是“每次执行时实例化”的对象。如果你需要真正实例化的话,它是可选的:相反,“非实例化”的能力将通过新的 UGameplayAbilitySpec 对象来实现。
  • 系统应该提供更多“中层”构造,比如“过滤后的 GE 应用容器”(通过数据驱动将 GE 应用到哪些演员,采用更高层次的游戏逻辑)、“重叠体积支持”(基于碰撞原语重叠事件应用“过滤后的 GE 应用容器”)等。这些是每个项目最终都会以自己方式实现的构建块。做对它们并非小事,所以我认为我们应该提供一些基本实现。
  • 一般来说,减少启动项目所需的模板代码。可能开发一个独立的模块,如“Ex 库”或类似的东西,能够提供诸如被动能力或基础命中扫描武器等功能。这个模块是可选的,但可以让你快速启动项目。
  • 我希望将 GameplayCues 移到一个与能力系统解耦的独立模块中。我认为这里有很多改进的空间。

这仅是我的个人观点,并不是任何人的承诺。我认为最现实的做法是,随着新的引擎技术的推进,能力系统将需要更新,到那个时候就会有机会进行这些更改。这些技术可能涉及脚本、网络或物理/角色移动。不过这都属于远期的规划,所以我无法给出承诺或时间预估。

11.1.2 社区问答 2

社区成员 iniside 与 Dave Ratti 的问答:

  1. 是否有计划支持解耦的固定时钟?我希望能够让游戏线程保持固定(如 30/60fps),而渲染线程可以自由运行。我想知道这是否是未来的计划,以便对游戏玩法的工作方式做出一些假设。我主要提这个问题,是因为现在物理系统有了固定的异步时钟,这让我想知道系统的其他部分未来会如何工作。我不隐瞒,如果能够拥有固定时钟的游戏线程而不需要固定引擎其余部分的时钟,那将是非常棒的。

目前没有计划解耦渲染帧率和游戏线程的时钟帧率。我认为这个机会已经错过了,因为这些系统的复杂性,以及需要保持与之前引擎版本的向后兼容性。

相反,我们的方向是拥有一个异步的“物理线程”,它以固定的时钟速率运行,独立于游戏线程。需要以固定速率运行的东西可以在这里运行,游戏线程/渲染可以像以前一样运行。

值得澄清的是,网络预测支持所谓的独立时钟和固定时钟模式。我的长期计划是保持网络预测中的独立时钟模式大致保持现在的状态,即在游戏线程上以可变帧率运行,并且没有“组/世界”预测,只是经典的“客户端预测自己的棋子和已拥有的演员”模式。固定时钟将使用异步物理功能,允许你预测非客户端控制/拥有的演员,如物理对象、其他客户端/棋子/车辆等。

  1. 有没有计划如何将网络预测与能力系统集成?例如,固定帧能力激活(这样服务器就能接收到能力激活的帧以及任务执行的帧,而不是预测键)?

是的,计划是重写/移除能力系统的预测键,并用网络预测的构造替代它们。NetworkPredictionExtras 中的 MockAbility 示例展示了这可能如何工作,但它们比 GAS 需要的更“硬编码”。

主要的想法是,我们将移除 ASC 的 RPCs 中显式的客户端->服务器预测键交换。将不再有预测窗口或作用域预测键。相反,一切都将围绕网络预测帧进行锚定。重要的是客户端和服务器能够在事情发生时达成一致。例子包括:

  • 何时激活/结束/取消能力
  • 何时应用/移除游戏效果
  • 属性值(属性在帧 X 时的值)

我认为这可以在能力系统层面上通用地完成。但实际上使 UGameplayAbility 中的用户定义逻辑完全可回滚,仍然需要更多的工作。我们可能会有一个 UGameplayAbility 的子类,它是完全可回滚的,并且只访问一个更有限的功能集,或者只有标记为回滚友好的能力任务。类似这样的东西。动画事件、根运动和它们的处理方式也有很多影响。

希望我能给出一个更明确的答案,但在重新处理 GAS 之前,我们必须确保基础部分正确。运动和物理必须稳固,才能改变更高层次的系统。

  1. 是否有计划将网络预测开发转移到主分支?不骗你,我真的很想查看最新的代码,不管它的状态如何。

我们正在朝着这个方向努力。系统工作仍然全部在 NetworkPrediction 中进行(参见 NetworkPhysics.h),而底层的异步物理功能应该都可以使用(例如 RewindData.h 等)。但我们在 Fortnite 中有一些用例,显然不能公开。我们正在处理错误、性能优化等问题。

更多背景:在这个系统的早期版本中,我们非常专注于“前端”部分——如何定义和编写状态和模拟。在这一过程中我们学到了很多东西。但随着异步物理的上线,我们更多地专注于让这个系统中的某些东西真正工作,而放弃了一些早期的抽象。目标是当真正的东西工作后,回过头来统一这些东西。例如,回到“前端”,在我们现在正在工作的核心技术之上,制作最终版本。

  1. 一段时间以来,主分支上有一个用于发送游戏消息的插件(看起来像事件/消息总线),但它被移除了。有没有计划恢复它?考虑到游戏功能/模块化游戏插件,拥有一个通用的事件总线分发器会非常有用。

我想你指的是 GameplayMessages 插件。它可能会在某个时点回来——API 还没有真正定稿,作者并不打算让它公开。我同意它对于模块化游戏设计应该非常有用。但这不是我的领域,所以我没有更多的信息。

  1. 最近我一直在尝试异步固定物理,结果很有希望,尽管如果将来有网络预测更新,我可能会等一下再继续玩,因为要使它正常工作,我仍然需要将整个引擎设置为固定时钟,而另一方面,我尝试保持物理在 33ms,这样如果一切都在 30fps 下运行,体验就不好了。

我注意到有一些关于异步 CharacterMovementComponent 的工作,但不确定它是否会使用网络预测,还是独立的工作?

既然注意到了这一点,我也去尝试实现了自定义的异步运动,以固定时钟速率运行,效果还不错,但在此基础上我还需要为插值添加单独的更新。设置是将模拟时钟以固定 33ms 更新在单独的工作线程上运行,进行计算,保存结果,并在游戏线程上进行插值,以匹配当前的帧率。不是完美,但完成了工作。

我的问题是,这将来可能会更容易设置吗?因为现在需要写很多样板代码(插值部分),并且为每个移动对象单独插值也不太高效。

异步的东西非常有趣,因为它将允许你以固定更新率运行游戏模拟(这将使固定线程不再必要),并且结果更加可预测。未来会朝着这个方向发展,还是更倾向于选择性系统的好处?据我记得,演员变换并不是异步更新的,蓝图也并不是完全线程安全的。换句话说,这是计划在更高层次的框架中支持,还是每个游戏需要自己解决?

异步 CharacterMovementComponent

这基本上是将 CMC 以其当前形式移植到物理线程的早期原型/实验。我并不认为它是 CMC 的未来,但它可能会发展成那样。目前没有网络支持,所以我不建议过多关注。做这件事的人主要关注的是衡量该系统增加的输入延迟,以及如何缓解这种问题。

我仍然需要将整个引擎设置为固定时钟速率,同时保持物理在 33ms。这样如果一切都在 30fps 下运行,体验就不好了(:)。

异步的东西非常有趣,因为它将允许你以固定更新率运行游戏模拟(这将使固定线程不再必要)

是的。这里的目标是,启用异步物理后,你可以在可变时钟速率下运行引擎,而物理和“核心”游戏模拟可以以固定的速率运行(如角色运动、车辆、GAS 等)。

目前需要设置的 cvar 如下:(我想你已经弄明白了) p.DefaultAsyncDt=0.03333 p.RewindCaptureNumFrames=64

Chaos 确实提供了物理状态的插值(例如,推送回 UPrimitiveComponent 并在游戏代码中可见的变换)。现在有一个 cvar,p.AsyncInterpolationMultiplier,如果你想查看它,你应该能够看到物理体的平滑连续运动,而不需要写额外的代码。

如果你想对非物理状态进行插值,目前还是需要自己做。例子就是,如果你想更新(时钟)异步物理线程上的冷却时间,但希望在游戏线程上看到平滑连续的插值,这样每个渲染帧都会更新冷却时间的可视化。我们最终会解决这个问题,但目前没有相关示例。

需要写大量样板代码

是的,这是到目前为止系统的一个大问题。我们希望提供一个接口,让有经验的程序员可以用它来最大化性能和安全性(能够编写“只会正常工作”的预测游戏代码,而不是冒着大量风险)。所以,像 CharacterMovement 这样可能会做很多自定义的事情以优化性能——例如编写模板化代码并进行批量更新、并行处理、将更新循环拆分为不同的阶段等等。我们希望提供一个良好的“低级”接口,以便在异步线程和回滚系统中进行这一用例的开发。就这个例子来说——合理的情况是,角色运动系统本身也可以以某种方式进行扩展。例如,提供一种方法来为自定义运动模式提供蓝图支持,并提供一个线程安全的蓝图 API。

但我们也认识到,对于一些不需要自己“系统”的简单游戏对象来说,这种方式并不合适。更接近 Unreal 的方式才是需要的。例如,使用反射系统,提供通用的蓝图支持等等。现在确实有一些蓝图在其他线程上的使用示例(参见 BlueprintThreadSafe 关键字,以及动画系统在这方面的工作)。所以我认为将来会有某种形式的支持。但是我们现在还没有到达这个阶段。

我明白你只是在问插值问题,但这是我的总体答案:目前我们要求你手动完成所有操作,比如 NetSerialize、ShouldReconcile、Interpolate 等,但最终我们会提供一种方式,像“如果你只想使用反射系统,你就不需要手动编写这些东西”。我们只是不想 强制 所有人都使用反射系统,因为那会引入其他限制,而我们认为在系统的最低层次上不想承担这些限制。

然后,回到我之前说的——现在我们真的专注于让一些非常具体的示例工作且高效,之后我们会回过头来专注于前端,使事情变得更易于使用和迭代,减少样板代码,为其他所有人提供方便。