Gameware Navigation 包含一个复杂性较低的优化系统,可用于针对层几何体在空间中的两个点之间执行高性能碰撞测试(光线投射)。
通常,这些测试由物理子系统执行。然而,对于许多游戏(通常为 MMO),网络通信延迟、技术复杂性和/或运行时性能开销可能使一个完善的物理系统的集成出现问题或受到禁止。如果您的项目出现这种情况,但仍然需要针对游戏关卡中的几何体进行这些种类的碰撞测试,那么您或许可以利用 Gameware Navigation 碰撞系统。
内置的碰撞系统依赖于预生成的数据,该数据独立于您的地形的 NavData。因此,它只能检测与您在数据生成过程中提供的地形的静态元素的碰撞。它不能处理您在运行时添加的动态对象,即使您将这些对象集成到您的地形的 NavData 中也是如此。
为了生成碰撞数据,使用 C++ NavData 生成 API。如果尚未将 NavData 生成 API 集成到您的游戏编辑器、关卡设计工具或构建过程,请参见集成阶段 6:使用 NavData 生成 API。
在 NavData 生成代码中,需要为地形中每个所需的地块激活碰撞数据生成。
当您为该地块生成 NavData 时,其碰撞数据也将从传递给该地块的 NavData 生成系统的三角形自动生成。每个地块的数据将写入到磁盘上的 .ColData 文件中,该文件的保存位置和文件名与为该地块生成的 .NavData 文件的保存位置和文件名相同。
// Create a sector... Kaim::GeneratorSectorConfig sectorConfig(Kaim::KyGuid::GetDefaultGuid(), generationName); Kaim::GeneratorSector* sector = env.m_generatorInputOutput.AddSector(sectorConfig); ... // Activate collision data generation sector->SetColDataBuildMode(Kaim::GenFlags::SECTOR_COLDATA_BUILD_ENABLED); ... // Generate NavData and collision data KyResult result = m_generator->Generate(m_generatorInputOutput);
您可以为 NavData 生成系统提供 heightfield 和三角形网格,而不是为其提供构成地形每个地块的单个三角形。
heightfield 是一种优化的方式,用于表达简单单层地形的海拔高度。它由单个原点以及一组围绕原点在常规栅格中采样的海拔高度值组成,而不是包含构成地形网格的所有三角形。由于与完整三角形网格相比,数据更加精简、复杂性更低,因此其在碰撞测试中占用的运行时内存量更少,并且性能更高。
使用 heightfield 的负面影响是,它们通常对于在栅格中的采样间距内发生的高度局部变化也会不太精确。这会导致从 heightfield 生成的 NavMesh 以及针对它们执行的碰撞测试不太精确。为了抵消此缺乏精确性问题,您也可以为地形的较复杂区域(这些区域需要针对地形细小特征提供较接近的逼真度)提供三角形网格。
为了获得效率和精确度之间的最佳折衷结果,请使用 heightfield 表示完全开放的区域(例如田地),使用三角形网格来表示需要更高精度的区域(例如城市街区)。例如:
若要为地块提供 heightfield 和索引网格,必须更新 GeneratorInputProducer 中的 GeneratorInputProducer::Produce() 实现(在 NavData 生成管线中使用以将地形几何体传递给 NavData 生成系统)。
请注意,如果使用此方法,将在 heightfield 和三角形网格上生成 NavData。因此,无需直接向 ClientInputConsumer 提供单个三角形。
每个 World 都在其 World::m_collisionWorld 类成员中维护 CollisionWorld 类的一个实例。该类负责聚合您加载到内存中的碰撞数据,以及管理针对该数据执行的碰撞测试。它通过调用实现 ICollisionInterface 的类实例的方法(您需要在初始化 World 之后提供)来执行此操作。
Gameware Navigation 包含依赖于开源 Bullet 物理和碰撞测试系统的 ICollisionInterface 实现。有关详细信息,请参见 www.bulletphysics.org。
使用默认的 ICollisionInterface 实现:
m_navigationWorld = *KY_NEW Kaim::World(databaseCount); Kaim::Ptr<Kaim::ICollisionInterface> visInterface = *KY_NEW CollisionInterface(m_navigationWorld); m_navigationWorld->m_collisionWorld->SetCollisionInterface(visInterface);
如果需要针对您为地形创建的碰撞数据执行碰撞测试,您需要将碰撞数据加载到内存中,并将其添加到碰撞世界。通常,您应该在将地形每一地块所需的 NavData 和其他种类的数据以流式加载方式载入和清除出内存时,为相应地块执行此操作。该过程非常类似于将 NavData 以流式加载方式载入和清除出 Database。
Kaim::Ptr<Kaim::CollisionData> colData = *KY_NEW Kaim::CollisionData; if (Kaim::Result::Fail(colData->Load(GetAbsoluteInputFileName(fileName).c_str(), &m_fileOpener))) return KY_NULL; GetWorld()->m_collisionWorld->AddCollisionData(colData);
若要针对您加载到 World 中的碰撞数据运行碰撞测试,请设置并启动 CollisionRayCastQuery 类的一个实例。
例如,以下代码测试沿点 A 和点 B 之间的直线路径的碰撞:
Kaim::CollisionRayCastQuery query; query.BindToWorld(m_navigationWorld); query.Initialize(pointA, pointB); query.PerformQuery(); if (query.GetResult() == Kaim::RayHit) ... // A collision was found if (query.GetResult() == Kaim::RayDidNotHit) ... // No collision was detected
您还可以利用查询系统提供的机制在 World 维护的队列中或在使用每帧最大时间预算创建和设置的自定义队列中异步运行 CollisionRayCastQuery。如果您的游戏可能需要在同一帧中请求多次碰撞测试,则这些方法可帮助避免 CPU 峰值。
有关详细信息,请参见使用查询系统。