使用查询系统

Gameware Navigation 提供了一组可针对已加载到内存的 NavData 使用的高性能运行时查询。您可以使用这些查询回答各种有关地形和角色移动机会的感知问题,如:

查询系统概述

查询类型

可用查询的类型包括:

  • CanGo 查询,用于测试光线、球体或分段是否可以从起始点传递到目标点,而不与 NavMesh 的边界发生碰撞。
  • Cast 查询,用于测试光线、球体或分段在不与 NavMesh 边界发生碰撞的情况下可以从起始点朝给定方向移动多远。
  • 用于在 NavMesh 内查找有效位置的查询。
  • 用于查找符合您指定的特定条件(即,距离给定位置最近的三角形、距离给定位置最近的边界等)的 NavData 元素的查询。
  • 寻径查询,例如 A* 查询,在 NavMesh 上计划从起始点到目标点的路径。
  • CollisionRayCastQuery,针对 NavData 进行碰撞测试。请参见使用碰撞系统
  • 其他。

每种类型的查询均封装在其自己的类中。有关完整列表,请参见可实现 IQuery 类的类。有关每个查询类型的完整说明(包括其输入和输出),请参见其类说明。

查询和数据库

每个查询均会考虑当前已加载到单个数据库中的完整 NavData 集。您既不能同时针对多个数据库进行单个查询(尽管可以针对不同的数据库并行执行查询的多个副本),也不能将一个查询限于已加载到数据库中的地块子集。

原子查询与时间片查询比较

每个查询类是原子查询或时间片查询

  • 原子查询可在单个帧内完成其计算。这些查询会在其计算期间阻止调用线程,保证查询结果在请求处理查询之后会立即可用。这些查询类源于 IAtomicQuery
  • 时间片查询的计算通常更密集,可以跨多个帧拆分其计算,以避开 CPU 使用高峰。这些查询类源于 ITimeSlicedQuery

WorkingMemory

每个查询均需要一些工作内存:在计算期间用于存储临时数据的缓冲区。例如,AStarQuery 需要使用其工作内存来存储在通过 NavData 查找路径时浏览到的候选节点列表。

每次处理某个查询时,您可以提供 WorkingMemory 类的实例,以供该查询在其计算期间使用。如果未提供实例,查询将使用由数据库保留的共享缓冲区(该查询配置为浏览该数据库)。

请注意,每个 WorkingMemory 实例仅可被一个查询随时使用。

立即查询与异步查询比较

您可以通过以下两种模式之一来运行创建的每个查询:

  • 在需要时立即运行。这会在处理查询时阻止调用线程,但可保证可以立即访问查询结果。
  • Gameware Navigation 的下一个更新阶段异步运行。这允许调用线程在计算期间继续执行其他任务,并利用世界中的内建系统避开 CPU 高峰。如果当前帧中用于处理查询的 CPU 预算已耗尽,则原子查询的处理可以推迟到下一帧,时间片查询可以暂停并在下一次更新时继续。但是,使用此方法意味着查询结果将至少对一帧(也可能对多帧)不可访问。

请注意,如果希望在游戏循环外部(如在数据生成过程的后处理阶段)启动查询,则必须使用阻止方法。

设置查询

设置查询:

  1. 创建要运行的查询类的实例。
  2. 调用其 BindToDatabase() 方法,将其关联到查询将在处理过程中使用其 NavData 的 Database。此调用还会重置所有可选配置参数的值,并将查询返回到默认状态。
  3. 通过调用您的查询类提供的取值函数方法,为查询设置任何可选配置参数。可以设置的配置参数种类取决于您使用的查询类型;有关详细信息,请参见您的查询类的类描述。
  4. 调用查询的 Initialize() 方法,使用需要的输入数据对其进行设置。有关详细信息,请参见查询类的类说明。请记住将输入数据从游戏的坐标系转换为 Gameware Navigation 坐标系(如有必要)。

    请注意,此调用不会重置任何其他配置参数的值,因此您可以在多次运行同一查询时重复使用相同的配置值,而无需在每次使用新的输入数据初始化查询时重新设置。

例如,以下代码设置了 RayCastQuery

Kaim::RayCastQuery<Kaim::DefaultTraverseLogic> rayCastQuery;
rayCastQuery.BindToDatabase(m_world->GetDatabase(0));

// Optional configuration ...
rayCastQuery.SetDynamicOutputMode(Kaim::QUERY_SAVE_TRIANGLES);

rayCastQuery.Initialize(start, move2D);

如果您想要使用相同的配置针对相同的 Database 重新运行某个查询,只需调用 Initialize() 以提供新的输入值。

测试 NavTag

许多类型的查询可以考虑与其浏览的 NavData 相关联的自定义 NavTag。这些查询都接受谓词作为模板参数。此查询检测到 NavTag 之间的过渡时会通过此谓词调用类方法,以确定是否应禁止新 NavTag,以及是否可以穿越过渡。

该谓词与 PathFollower 类接受的谓词相同,并与 AStarQuery 使用的自定义谓词命令大多数相同的方法相关。有关您必须在谓词类中实现的界面需求的详细信息,请参见禁止选择、避免选择和优先选择 NavTag

有关设置 NavTag 的详细信息,另请参见使用自定义数据进行标记

启动查询

根据您希望是立即运行查询还是异步运行查询,您可以采用不同的方式启动查询。

以立即模式启动查询

要在立即模式下启动查询,请调用其 PerformQueryBlocking() 方法。

例如:

rayCastQuery.PerformQueryBlocking();

只要您在世界更新时不启动任何查询,就可以安全地从任何线程启动立即查询。

异步启动查询

要在世界下次更新期间异步启动查询,请调用 World::PushAsyncQuery() 方法并传递查询对象。世界将查询放置在队列中,并在 CPU 预算允许后立即执行该查询。

例如:

m_world->PushAsyncQuery(&rayCastQuery);

使用自己的 QueryQueue

您可以使用自己的 CPU 预算设置自己的 QueryQueue 对象,以便管理查询的处理。处理 QueryQueue 时,它将按顺序从队列开头处执行查询,直到其 CPU 预算已耗尽。然后它将暂停计算,直到您再次处理(通常在下一帧中)。

有关详细信息,请参见 QueryQueue 类。

类似于以立即模式运行查询,只要不在世界更新时处理 QueryQueue,就可以安全地从任何线程对其进行处理。

但是,类似于以异步模式运行查询,不保证每个查询的结果会在当前帧中完成队列处理之后立即可用。

检索查询结果

您可以通过随时调用查询的 GetResult() 方法来检索其当前结果。

每个查询均可使用特定于查询类的枚举来表示其处理的状态和结果。此枚举与查询类同名,带有后缀 Result。例如,RayCastQuery 会生成 RayCastQueryResult

请注意,如果异步启动查询,则可以使用结果代码来确定查询是否已成功完成处理。通常,包括 DONE 的任何枚举值均表示查询已完成计算。例如,RayCastQuery 将返回 RAYCAST_NOT_INITIALIZEDRAYCAST_NOT_PROCESSED,直到它已完成处理。然后,它可能会返回 RAYCAST_DONE_START_OUTSIDERAYCAST_DONE_ARRIVALPOS_FOUND_MAXDIST_REACHEDRAYCAST_DONE_LACK_OF_WORKING_MEMORY 等中的任何一个。

根据查询类型和处理的结果,您还可以从查询的其他数据成员中检索其他输出。请参见以下内容。

检索交叉数据

通过 NavMesh 传播的某些类型的查询可以存储其跨越的数据。例如,查询可以存储其在传播期间跨越的三角形列表,或跨越不同 NavTag 的子分段列表。在查询之后,您可以检索交叉数据的列表并搜索查询跨越的地形的特性。例如,您的角色可以基于与三角形、海拔高度的变化等关联的 NavTag 做出决策。

这些查询都包含 SetDynamicOutputMode() 方法,您必须调用该方法以告诉查询应该记录的输出数据类型。默认情况下,不会记录交叉数据;您必须显式指示查询通过调用 SetDynamicOutputMode() 并将所需值从 DynamicOutputMode 枚举传递给它来记录交叉数据。

您可以通过调用 GetQueryDynamicOutput() 方法来检索交叉数据。这会将指针返回到 QueryDynamicOutput 对象,该对象的方法可提供对已保存数据的访问权限。请记住将输出数据转换回游戏的坐标系(如有必要)。

例如,以下代码显示了如何设置 RayCastQuery 以检索其跨越的 NavMesh 三角形。

Kaim::RayCastQuery<Kaim::DefaultTraverseLogic> rayCastQuery;
rayCastQuery.BindToDatabase(m_world->GetDatabase(0));
...
// enable saving triangles
rayCastQuery.SetDynamicOutputMode(Kaim::QUERY_SAVE_TRIANGLES);

rayCastQuery.Initialize(start, dir2D, maxdist);
rayCastQuery.PerformQueryBlocking();

// retrieve data
Kaim::QueryDynamicOutput* qoutput = rayCastQuery.GetQueryDynamicOutput();

// iterate through the list of triangles
KyUInt32 numberOfTriangles = qoutput->GetNavTrianglePtrCount();
for (KyUInt32 i_triangle = 0; i_triangle < numberOfTriangles; i_triangle++)
{
   const Kaim::NavTrianglePtr thisTriangle = qoutput->GetNavTrianglePtr(i_triangle);
   // handle the triangle here.
}

可视调试

通过在每一帧调用查询的 SendVisualDebug () 方法,您可以渲染任何查询的可视调试数据。当将 Navigation Lab 连接到游戏时,查询将自动构建显示列表,并将其发送到 Navigation Lab。

显示列表的内容以及相应在 Navigation Lab 中显示的数据随查询类型变化。要解释为查询显示的可视数据的重要性,请参见在 sdk\include\gwnavruntime\queries\blobs 目录中的 QueryDisplayListBuilder 类的内嵌实现。

接收查询完成通知

当异步执行查询或将查询放置在队列中以供处理时,在查询已完成时接收通知会很有帮助,您无需轮询查询结果以确定处理的当前状态。

获得查询完成的通知:

  1. 写入可实现 IOnDone 界面的自定义类。
  2. 在启动查询之前,创建自定义类的新实例,并将查询类的 m_onDone 成员设置为新实例的指针。

如果将查询放入到世界中以供执行,则在查询已处理之后,在世界更新期间会自动调用对象的 IOnDone::OnDone() 方法。如果使用自己的 QueryQueue,则在该查询已处理之后,在下次调用 QueryQueue::FlushQueries() 时会调用对象。

请注意,查询类的 m_onDone 成员会在调用其 IOnDone::OnDone() 方法之后被重置为 KY_NULL。如果希望重新启动相同的查询实例并再次接收通知,则需要再次设置 m_onDone 成员。

Navigation Lab 中的查询

Gameware Navigation API 中的许多查询也会显示在 Navigation Lab 中,Navigation Lab 将运行查询并提供有关查询结果的视觉反馈。这可用于测试针对地形生成的 NavData,并了解每个查询的不同输入和输出值。

有关在 Navigation Lab 中运行查询的介绍,请参见 Navigation Lab 快速入门