TrinityCore 地图系统详解:从 ADT/MPQ 到运行时碰撞检测 原创
温馨提示:
本文最后更新于 2026-06-15,已超过 0 天没有更新。
若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我。
一、引言
在 TrinityCore 模拟器中,地图系统是最基础也是最复杂的子系统之一。它负责从游戏客户端数据文件(ADT、MPQ)中加载地图信息,并在运行时提供碰撞检测、视线检查、寻路计算等关键功能。
本文将深入剖析 TrinityCore 地图系统的完整链路,从数据格式解析到运行时碰撞检测的实现细节。
二、地图数据来源
2.1 数据文件类型
TrinityCore 使用魔兽世界客户端的地图数据,主要包括以下文件格式:
| 文件格式 | 用途 | 来源 |
|---|---|---|
| ADT | 地图区块数据(地形、物体、纹理) | 客户端 MPQ |
| WDT | 地图全局定义(区块索引、Liquid 信息) | 客户端 MPQ |
| WMO | 世界模型对象(建筑、洞穴等) | 客户端 MPQ |
| MMAPS | 导航网格(Recast/Detour 生成) | mmaps_generator 工具 |
| VMAPS | 碰撞检测网格(vmap_assembler 生成) | vmap_assembler 工具 |
三、地图加载流程
3.1 启动阶段
TrinityCore 在服务器启动时通过 MapManager 加载所有地图:
// MapManager.cpp - 简化流程
void MapManager::LoadMaps()
{
// 1. 加载地图定义
LoadMapDefinitions(); // 从 world 数据库读取 Map.dbc
// 2. 初始化地图容器
for (auto& mapEntry : sMapStore)
{
auto map = new Map(mapEntry->MapID, ...);
m_maps[mapEntry->MapID] = map;
}
// 3. 加载 VMap 数据
VMapFactory::createOrGetVMapManager()->loadVMaps();
// 4. 加载 MMAP 数据
MMAPFactory::createOrGetMMapManager()->loadMMaps();
// 5. 初始化地图更新线程
for (auto& [mapId, map] : m_maps)
{
map->InitVisibilityDistance();
}
}
四、ADT 文件解析
4.1 ADT 文件结构
每个 ADT 文件代表一个地图区块(tile),大小为 533.33×533.33 码,分为 16×16 个单元(cell),每个单元进一步分为 8×8 个子单元。ADT 文件内部使用 Chunk 结构存储数据:
// ADT Chunk 头
struct ChunkHeader
{
char magic[4]; // 四字符标识,如 "MVER", "MHDR", "MCNK"
uint32 size; // 数据大小
};
// 主要 Chunk 类型
// MVER - 版本信息
// MHDR - 文件头,包含偏移量
// MCNK - 地图区块数据(地形高度、纹理、法线)
// MCLQ - 液体数据(水、岩浆)
// MCRF - 引用对象列表
4.2 地形高度数据
MCNK Chunk 是 ADT 文件的核心,包含地形高度信息:
struct MCNK
{
uint32 flags;
uint32 indexX; // 区块 X 坐标 (0-15)
uint32 indexY; // 区块 Y 坐标 (0-15)
uint32 nLayers; // 纹理层数
uint32 nDoodadRefs; // 装饰物引用数
float heightMap[145]; // 9x9 + 8x8 高度网格
// ... 更多字段
};
// 高度插值:TrinityCore 使用双线性插值
float Map::GetHeight(float x, float y, float z, bool checkVMap)
{
float gridHeight = GetGridHeight(x, y);
float vmapHeight = checkVMap ? VMAP::GetHeight(x, y, z) : INVALID_HEIGHT;
// 取两者中较高的值(地面 vs 模型表面)
return std::max(gridHeight, vmapHeight);
}
五、VMap 碰撞检测系统
5.1 VMap 生成流程
VMap(碰撞网格)需要从客户端数据中离线生成:
# 生成 VMap 数据
cd /path/to/trinitycore/contrib/vmap_assembler
./vmap_assembler
# 生成后的文件结构
# ├── Buildings/ - 建筑模型碰撞数据
# ├── dir_bin/ - 地图目录索引
# └── vmaps/ - 最终碰撞网格文件
5.2 碰撞检测实现
TrinityCore 使用 BSP(二叉空间分割)树来加速碰撞检测:
// VMap 碰撞检测核心逻辑
bool VMapManager2::getObjectHitPos(uint32 mapId, float x1, float y1, float z1,
float x2, float y2, float z2,
float& rx, float& ry, float& rz, float modifyDist)
{
// 1. 构建射线
G3D::Ray ray(Vector3(x1, y1, z1), Vector3(x2 - x1, y2 - y1, z2 - z1));
// 2. 遍历地图上的所有模型
for (auto& model : m_models[mapId])
{
// 3. 使用 BSP 树快速检测
if (model->IntersectRay(ray, maxDist))
{
// 4. 计算碰撞点
Vector3 hitPoint = ray.origin() + ray.direction() * maxDist;
rx = hitPoint.x;
ry = hitPoint.y;
rz = hitPoint.z;
return true;
}
}
return false; // 无碰撞
}
5.3 视线检查
视线检查(Line of Sight, LoS)是碰撞检测的一个重要应用:
bool VMapManager2::isInLineOfSight(uint32 mapId, float x1, float y1, float z1,
float x2, float y2, float z2)
{
// 检查两点之间是否有障碍物
return !getObjectHitPos(mapId, x1, y1, z1, x2, y2, z2, ...);
}
// 在 Spell 系统中使用
bool Spell::CheckTargetLOS()
{
if (!sVMapMgr->isInLineOfSight(m_caster->GetMapId(),
m_caster->GetPositionX(), ...,
m_target->GetPositionX(), ...))
{
m_caster->SendSpellMiss(m_target, SPELL_MISS_LINE_OF_SIGHT);
return false;
}
return true;
}
六、MMAP 导航网格
6.1 Recast/Detour 集成
TrinityCore 使用 Recast 库生成导航网格,Detour 库进行路径搜索:
// 导航网格生成
void Map::GenerateMMap()
{
rcConfig cfg;
cfg.cs = 0.3f; // Cell 大小
cfg.ch = 0.2f; // Cell 高度
cfg.walkableSlopeAngle = 45.0f;
cfg.walkableHeight = 2.0f; // 玩家高度
cfg.walkableClimb = 0.9f; // 可攀爬高度
cfg.walkableRadius = 0.6f; // 角色半径
// 1. 光栅化三角形
rcHeightfield* solid = rcAllocHeightfield();
rcRasterizeTriangles(ctx, vertices, nvertices, triangles, ntriangles, solid, cfg);
// 2. 过滤可行走区域
rcFilterLowHangingWalkableObstacles(ctx, cfg.ch, solid);
rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, solid);
// 3. 生成距离场和区域
// 4. 生成导航网格多边形
// 5. 保存为 .mmap 文件
}
6.2 路径搜索
// Detour 路径搜索
bool Map::FindPath(float startX, float startY, float startZ,
float endX, float endY, float endZ,
std::vector<Vector3>& outPath)
{
dtNavMeshQuery query;
query.init(m_navMesh, 2048);
// 1. 查找起点和终点的导航多边形
dtPolyRef startPoly, endPoly;
float startPos[3] = {startX, startY, startZ};
float endPos[3] = {endX, endY, endZ};
query.findNearestPoly(startPos, extents, &filters, &startPoly, nullptr);
query.findNearestPoly(endPos, extents, &filters, &endPoly, nullptr);
// 2. 搜索路径
dtPolyRef pathPolys[MAX_POLYS];
int pathCount;
query.findPath(startPoly, endPoly, startPos, endPos, &filters,
pathPolys, &pathCount, MAX_POLYS);
// 3. 生成平滑路径
float straightPath[MAX_POLYS * 3];
unsigned char straightPathFlags[MAX_POLYS];
dtPolyRef straightPathPolys[MAX_POLYS];
int straightPathCount;
query.findStraightPath(startPos, endPos, pathPolys, pathCount,
straightPath, straightPathFlags,
straightPathPolys, &straightPathCount, MAX_POLYS);
// 4. 转换路径点
for (int i = 0; i < straightPathCount; ++i)
{
outPath.emplace_back(straightPath[i*3], straightPath[i*3+1], straightPath[i*3+2]);
}
return !outPath.empty();
}
七、性能优化
7.1 网格预加载
TrinityCore 使用延迟加载策略,只加载玩家附近的地图区块:
- 可见距离:默认 533 码(一个 ADT 区块大小)
- 网格缓存:LRU 缓存策略,最近使用的网格保留在内存中
- 异步加载:地图加载在后台线程进行,不阻塞主循环
7.2 碰撞检测优化
- 空间分区:使用网格索引快速定位需要检测的模型
- 层次化检测:先粗检测(AABB),再精检测(BSP 树)
- 缓存命中:频繁访问的碰撞结果缓存到临时表
八、总结
TrinityCore 的地图系统是一个多层次、高度优化的架构:
- 从客户端 ADT/MPQ 文件中解析地形和模型数据
- 通过 VMap 系统提供精确的碰撞检测和视线检查
- 通过 MMAP 系统(Recast/Detour)提供智能路径搜索
- 采用延迟加载、空间分区和缓存策略确保运行时性能
理解地图系统的工作原理,对于调试移动问题、优化 NPC 寻路、以及自定义地图内容都有重要意义。