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 寻路、以及自定义地图内容都有重要意义。