Files
zeling_v2/Docs/Guides/06_MapSystem_Authoring_Guide.md
2026-06-05 18:41:33 +08:00

20 KiB
Raw Permalink Blame History

地图系统制作手册(策划 / 开发)

文件位置:Docs/Guides/06_MapSystem_Authoring_Guide.md 版本1.0 · 适用项目zeling_v2 · 对标丝之歌Hollow Knight: Silksong的全屏大地图

本手册指导策划与开发人员完成银河恶魔城地图系统的全流程制作:从环境装配、单个房间的地图数据制作、世界布局,到本地化 / 输入提示 / 资源合规,并附运行时验证与常见问题排查。


目录

  1. 架构概览
  2. 一次性环境准备(每个工程做一次)
  3. 快速开始:为一个房间制作地图(端到端)
  4. 工具速查表
  5. MapRoomDataSO 字段详解
  6. 世界布局与格子校准
  7. 底图美术管线
  8. 文字本地化接入
  9. 输入提示图标接入
  10. 资源管理合规(必须经 AssetLoader
  11. 运行时验证
  12. 常见问题排查

1. 架构概览

地图系统是事件驱动 + ServiceLocator 解耦的,分三层:

数据层Edit Time 配置)
  ├── MapRoomDataSO        每个房间一个RoomId / RegionId / GridPosition / GridSize / RoomOutlineTex / Exits / RoomFlags
  └── MapDatabaseSO        全局房间索引(所有 MapRoomDataSO 的数组 + O(1) 空间索引)

服务层Persistent 场景 [Services]boot 注册)
  ├── MapManager           IMapService —— 探索状态Unknown/Explored/Mapped、ISaveable
  ├── MapPinManager        IPinService —— 玩家自定义标记
  ├── TeleportService      ITeleportService —— 传送点解锁/传送
  └── MapPlayerTracker     IPlayerPositionProvider —— 挂在【玩家】上,世界坐标→房间格;进房广播 EVT_RoomEntered

UI 层Persistent 场景UIManager PanelStack 管理)
  └── Map Canvas / MapPanel  全屏大地图:平移/缩放、房间格、当前房间高亮、玩家点、Pin、迷雾、探索进度、区域名、传送选点、关闭提示

核心数据流:

玩家移动
  └─ MapPlayerTracker挂玩家按世界坐标算出所在房间格
       ├─ OnRoomChanged → 小地图/UI 刷新(如启用)
       └─ Raise EVT_RoomEntered(roomId)
            ├─ MapManager → 标记该房间 Explored地图揭示
            ├─ RoomStreamingManager → 重算流式集
            └─ EventChainManager → 触发房间条件

按地图键
  └─ Raise EVT_MapOpen
       └─ UIManager.OpenPanel(PanelId.Map)
            └─ MapPanel 渲染所有房间格(按 MapDatabase + 各房间可见性)

不使用右上角小地图:本项目按需求只保留全屏大地图,地图 UI 脚手架不再搭建 MinimapHUD。


2. 一次性环境准备(每个工程做一次)

地图系统的服务与 UI 都需先装配进 Persistent 场景,且必须保存到磁盘。打开 Persistent.unity 后依次执行:

2.1 创建事件频道与基础资产(若尚未创建)

菜单栏 → BaseGames → Scene → Setup → Create All Event Channel Assets
菜单栏 → BaseGames → Setup → Create Project Assets

确认存在以下事件频道(Assets/_Game/Data/Events/EVT_RoomEnteredEVT_MapUpdatedEVT_RegionChangedEVT_MapOpenEVT_LanguageChangedEVT_InputDeviceChanged

2.2 装配地图运行时管理器

菜单栏 → BaseGames → Scene → Setup → Scaffold Map Managers

[Persistent]/[Services] 下创建并绑定:MapManager(绑 MapDatabase + EVT_RoomEntered/MapUpdated/RegionChangedMapPinManagerTeleportService

2.3 装配本地化与输入图标服务

菜单栏 → BaseGames → Scene → Setup → Scaffold Localization & Input Services

[Services] 下创建并绑定:LocalizationManager(文字本地化)、InputDeviceDetector + InputIconService(按键提示图标,绑 EVT_InputDeviceChanged

没有这一步,运行时所有 UI 文字会显示成本地化 KeyMAP_CLOSE_HINT)、按键图标不显示。

2.4 装配地图 UI

菜单栏 → BaseGames → Scene → Setup → Scaffold Map UI

创建 Map Canvas → MapPanel含探索进度、关闭提示、Tooltip、传送确认框 + MapTeleportConfirmController、HUD 下区域名横幅),并登记 UIManager._panels[Map]。同时生成占位预制 UI_Map_RoomCell / UI_Map_Pin / UI_Map_ExitConnectorMapPinConfig.asset

2.5 给【玩家】挂 MapPlayerTracker

地图玩家定位与进房自动揭示依赖 MapPlayerTracker,它挂在玩家对象上(如 Assets/_Game/Prefabs/Player/Player.prefab 根节点):

字段
_playerTransform 玩家根 Transform
_databaseOverride 与 MapManager 同一个 MapDatabaseSO(可留空,运行时从 IMapService 取)
_onRoomEntered EVT_RoomEntered(进房自动揭示的关键)
_worldUnitsPerCell 1 格对应的世界单位(默认 18§6 校准
_worldOriginOffset 世界原点偏移(默认 (0,0)

2.6 ★ 保存 Persistent 场景(务必)

File → Save或 Ctrl+S保存 Persistent.unity

极重要:上面装配的服务/UI 都是场景对象,必须保存到磁盘。否则任何"重载场景"的操作(包括 Room Capture Baker 烘焙时的场景切换/恢复)都会丢失未保存的改动,导致运行时服务消失、地图与文字失效。


3. 快速开始:为一个房间制作地图(端到端)

TestRoomA 为例。前提:房间场景已搭好(含 RoomControllerCameraArea且环境准备§2已完成并保存。

步骤一览

# 操作 工具 产出
1 创建房间数据 SO 见下 Assets/_Game/Data/Map/Rooms/{RoomId}.asset
2 派生格子尺寸 / 布局位置 Room Capture Baker / Map Layout Editor GridPosition / GridSize
3 烘焙底图 Room Capture Baker PNG + RoomOutlineTex
4 加入数据库 Map Layout Editor / 手动 MapDatabase.AllRooms
5 设场景房间 ID 房间场景 Inspector RoomController._roomId
6 实测 Debug 入口 + Play 全屏地图渲染

3.1 创建 MapRoomDataSO

  • 路径 / 命名:Assets/_Game/Data/Map/Rooms/{RoomId}.assetRoomId 必须与场景文件名一致(如 TestRoomA)。
  • 创建方式:Project 窗口右键 → Create → BaseGames → World → Map → RoomData,或由 SceneScaffoldTools.ScaffoldGameRoom 搭房间时自动生成模板。
  • 填写:RoomIdRegionId(如 FengXian / Test)。GridPosition/GridSize 下一步自动派生。

3.2 打开地图制作工具并指定数据库

菜单栏 → BaseGames → Map → Room Capture Baker

MapDatabaseAssets/_Game/Data/World/Map/MapDatabase.asset)拖入顶部字段。窗口列出库内所有房间。

3.3 派生格子尺寸GridSize

在窗口「格子布局派生」区,设 世界单位/格世界原点偏移与 MapPlayerTracker 一致(默认 18 / (0,0)),然后:

  • 点某房间行的 「派生格子」,或顶部 「派生全部房间格子布局」
  • 工具会打开房间场景,取 CameraArea.VisibleBounds 并集 ÷ 世界单位/格floor 下界、ceil 上界),写入 GridPosition / GridSize

派生得到的 GridSize尺寸可直接用GridPosition位置仅在房间场景本身就摆在世界真实坐标时才正确。多个房间若各自摆在场景原点附近,派生位置会重叠——此时位置改用 §6 世界布局 手动摆放。

3.4 烘焙底图RoomOutlineTex

在 Room Capture Baker

  • 确认参数:输出目录(默认 Assets/_Game/Art/Map/RoomCaptures)、每世界单位像素透明背景(推荐)、临时全局光(推荐,避免 URP 2D 离屏渲染偏黑)、回填 RoomOutlineTex(推荐)。
  • 点房间行 「烘焙」 或顶部 「烘焙全部房间」
  • 工具会逐房间打开场景、用正交相机按可视范围渲染,输出 {RoomId}.png 并回填到 MapRoomDataSO.RoomOutlineTex

这张 PNG 是给美术加工的底图(不是最终美术)。美术在其上描绘 / 风格化成清晰的地图块后,覆盖同名 PNG 即自动回到游戏。

3.5 加入 MapDatabase

  • Map Layout Editor(见 §4或在 MapDatabase Inspector 的 AllRooms 数组中加入该 SO。
  • 新建房间 SO 时,若勾选了某个 MapDatabase 为默认库,MapRoomAutoRegister 会自动注册。

3.6 设置场景房间 ID进房自动揭示

打开房间场景,选中 RoomController,在 _roomId 填写与 RoomId 一致的值(如 TestRoomA)。保存该场景。

缺这一步:进入该房间不会自动标记为已探索(地图上显示为未知/迷雾)。

3.7 实测

§11 运行时验证


4. 工具速查表

工具 菜单 用途
Room Capture Baker BaseGames → Map → Room Capture Baker 烘焙房间底图 + 派生 GridPosition/GridSize
Map Layout Editor BaseGames → Map → Map Layout Editor 全局俯视布局:拖拽摆放房间、按区域着色、红色高亮重叠/重复、画出口连线、Play 时叠玩家位置
MapRoomDataSO 编辑器 选中 MapRoomDataSO Scene View 中拖拽角点改 GridPosition/GridSize一键居中
MapDatabase 编辑器 选中 MapDatabaseSO 房间列表、ValidateAll(重复 ID / 格子重叠 / 出口悬空)、打开布局编辑器
Scaffold Map Managers BaseGames → Scene → Setup → Scaffold Map Managers 装配 MapManager/MapPinManager/TeleportService
Scaffold Localization & Input Services BaseGames → Scene → Setup → Scaffold Localization & Input Services 装配 LocalizationManager/InputDeviceDetector/InputIconService
Scaffold Map UI BaseGames → Scene → Setup → Scaffold Map UI 搭建 MapPanel 全屏地图 + 传送确认 + 区域名横幅
资源用法校验 BaseGames → Tools → Validation → Validate Resource Usage 扫描禁止的 Resources.Load / 散落 Addressables.*
调试进首关 BaseGames → Debug → Enter First Room (Play) Play 模式下绕过菜单直接进首关,用于测地图

5. MapRoomDataSO 字段详解

字段 类型 说明
RoomId string 与场景文件名一致,全库唯一。空格会被自动修剪
RegionId string 所属区域(用于区域名横幅与区域探索进度)。建议本地化(见 §8
DisplayName string 地图 Tooltip 显示名。填本地化 Key(如 ROOM_TESTROOMA_NAMEUI 自动解析;非 Key 则原样显示
GridPosition Vector2Int 房间在世界地图格上的左下角坐标(单位:格)。可派生或手动布局
GridSize Vector2Int 房间占据的格数(宽×高),每轴最小 1。建议由工具派生
RoomOutlineTex Texture2D 房间在地图上显示的底图(截图或美术加工后的图)。空则回退到纯色格
RoomFlags RoomType[Flags] 房间类型可多选BossRoom/SavePoint/Shop/Merchant/Challenge/TeleportStation。决定地图图标
MapIconOverride Sprite 自定义图标,覆盖按 RoomFlags 的自动选择
Exits RoomExitData[] 出口(目标房间 ID / 方向 / 格子位置 / 过渡类型),用于全屏地图画连接线
EstimatedMemoryKB int 流式内存预算估值(与地图渲染无关)

6. 世界布局与格子校准

6.1 GridSize 与 GridPosition 的来源

  • GridSize尺寸:来自房间实际大小,建议用 Room Capture Baker 的「派生格子」自动算。
  • GridPosition位置:是世界地图布局的设计决策——决定房间在整张地图上相对其它房间的位置。
    • 若房间场景内容就摆在世界真实坐标,派生即正确。
    • 否则(各房间都摆在场景原点附近)→ 用 Map Layout Editor 拖拽摆放,或在 SO/Scene View 中手动设置,确保房间之间不重叠(用 MapDatabase 的 ValidateAll 检查)。

6.2 worldUnitsPerCell 校准

_worldUnitsPerCellMapPlayerTracker 上,默认 18决定"多少世界单位 = 1 个地图格",直接影响房间在地图上的占格大小与玩家点定位精度。

  • 必须三处一致MapPlayerTracker、Room Capture Baker 的派生参数、以及实际关卡设计。
  • 房间占格"偏大/偏小"时,调大/调小该值后重新派生格子即可校准。
  • _worldOriginOffset 用于关卡世界原点不在地图 (0,0) 时对齐。

7. 底图美术管线

策划:搭房间场景(含 CameraArea / Ground Tilemap
  → Room Capture Baker「烘焙」正交相机渲染场景 → 透明底 PNG给美术的底图
    → 美术:在 PNG 上描绘 / 风格化成清晰的地图块(对标丝之歌手绘观感)
      → 覆盖同名 PNG → 运行时由 RoomOutlineTex 自动显示
  • 底图默认透明背景,便于抠出房间形状。
  • 截图偏暗时,提高 Baker 的「全局光强度」。
  • 大房间提高「每世界单位像素」或调大「最大边像素」以保证清晰度(注意纹理内存)。

8. 文字本地化接入

项目使用自研本地化系统BaseGames.Localization禁止硬编码面向玩家的文字

8.1 数据位置与格式

  • JSON 表:Assets/_Game/Data/Localization/{语言}/{表}.json(经 Addressables地址 Localization/{语言}/{表}不在 Resources)。
  • 语言:ChineseSimplified / English / Japanese / Korean。表:UI / Dialogue / Quest
  • 格式:{ "entries": [ { "key": "MAP_CLOSE_HINT", "value": "关闭地图" } ] }Key 用 UPPER_SNAKE_CASE

8.2 地图相关 Key已内置于 UI 表,按需补全各语言)

Key 含义
MAP_PROGRESS_GLOBAL 全局探索进度格式(如 已探索 {0:P0}
MAP_PROGRESS_REGION 区域进度格式(如 {1}{0:P0}
MAP_CLOSE_HINT 关闭地图提示标签
TELEPORT_CONFIRM_TITLE / TELEPORT_CONFIRM_BODY 传送确认框标题 / 正文前缀
CONFIRM_YES / CONFIRM_NO 确认 / 取消按钮
REGION_{REGIONID}_NAME 区域显示名(区域名横幅 / 进度)
ROOM_{ROOMID}_NAME 房间显示名Tooltip填到 MapRoomDataSO.DisplayName

8.3 接入方式

  • 静态 UI 文本:在 TMP_Text 上挂 LocalizedText 组件,填 Key/Table,语言切换自动刷新。
  • 代码动态文本LocalizationManager.Get(key, table) / GetFormat(key, table, args)
  • 房间 / 区域名:把本地化 Key 填进 MapRoomDataSO.DisplayName / RegionDefinitionSOUI 自动解析。

8.4 中文字体CJK

⚠ 中文显示成 □ 方块时,是缺中文 TMP 字体所致(英文不受影响)。需导入 CJK 字体生成 TMP SDF 字体、创建 LanguageFontConfigSOBaseGames → Localization → Language Font Config),并在 LocalizedText._fontConfig 引用。这是字体资产配置,非地图逻辑。


9. 输入提示图标接入

地图里的操作提示(关闭 / 缩放 / 放置标记等)须用输入图标系统(随键鼠/手柄自适应),不硬编码"按 Tab"之类文字

  • 提示行结构:InputIconImage(按键图标,随设备自适应) + LocalizedText(本地化标签)。地图脚手架的关闭提示已是此结构(动作名 Cancel)。
  • InputIconImageByActionName 模式)填 InputActions 中的动作名(如 Cancel / Interact),运行时由 InputIconService 解析当前设备的按键 Sprite。
  • 图标资源4 套 InputDeviceIconSetSO(键鼠 / Xbox / PlayStation / SwitchInput Icon Studio 编辑器工具配置 绑定路径 → Sprite,并赋给 InputIconService 的对应字段。

⚠ 未配置图标集 SO 时,图标位置显示为空白/占位框(标签仍正确)。这是美术资产配置。


10. 资源管理合规(必须经 AssetLoader

项目统一用 Addressables禁止 Resources.Load,且不直接散落调用 Addressables.*——一律经门面 BaseGames.Core.Assets.AssetLoader

  • 地图相关资源(房间底图、本地化表、配置)均已 Addressable 化、经 AssetLoader 加载。
  • 提交前运行 BaseGames → Tools → Validation → Validate Resource Usage 确认 0 违规
  • 设计师在 Inspector 拖拽的 AssetReference 引用属正常用法,不算违规。

11. 运行时验证

11.1 用调试入口快速进房(推荐)

  1. 编辑器进入 Play(默认从 Persistent 启动到主菜单)。
  2. 菜单 BaseGames → Debug → Enter First Room (Play) —— 绕过新游戏菜单,直接加载首关(Scene_Game_Chapter1)并生成玩家。
  3. 玩家进房 → MapPlayerTracker 自动广播 EVT_RoomEntered → 房间标记为已探索。
  4. 触发 EVT_MapOpen(或对应输入)打开全屏地图,应看到:房间格(带底图)、玩家点、探索进度、关闭提示。

11.2 通过正式流程

主菜单 → New Game → 选存档槽/模式 → 进入首关 → 游玩进房 → 按地图键看全屏地图(房间随探索逐格揭示)。

11.3 检查清单

  • 进房后该房间在地图上由迷雾变为已探索。
  • 房间格显示底图RoomOutlineTex玩家点位置正确。
  • 探索进度文字正确(如 已探索 X%),非 Key。
  • 关闭/操作提示显示按键图标 + 本地化标签。
  • 多房间时相对位置正确、无重叠(ValidateAll 通过)。

12. 常见问题排查

Q运行时 UI 文字显示成 MAP_CLOSE_HINT / 进度只显示 100% 没有前缀

原因: Persistent 场景里没有 LocalizationManager(本地化服务未装配或未保存)。 解决: 执行 Scaffold Localization & Input Services 装配,并保存 Persistent。确认 [Services] 下存在 LocalizationManager


Q装配过的服务运行时又消失了

原因: 装配后未保存 Persistent 场景;随后任何重载场景的操作(如 Room Capture Baker 烘焙时切换/恢复场景)会从磁盘重载,丢失未保存改动。 解决: 装配后立即保存 Persistent§2.6)。这是最容易踩的坑。


Q中文显示成 □ 方块(英文正常)

原因: 缺中文 TMP 字体(与本地化逻辑无关)。 解决: 导入 CJK 字体生成 TMP SDFLanguageFontConfigSO,在 LocalizedText._fontConfig 引用§8.4)。


Q地图打开后房间是黑的 / 不显示底图

可能原因 1 房间未探索 → 显示未知(黑/迷雾)。确认 RoomController._roomId 已填且进过该房间。 可能原因 2 RoomOutlineTex 未赋值 → 显示纯色格。用 Room Capture Baker 烘焙并回填。 可能原因 3 底图本身偏暗、单房间在地图上占比小。由美术加工底图,或调高 Baker 全局光。


Q多个房间在地图上叠在一起

原因: 各房间的 GridPosition 重叠(常见于"派生位置"时各房间场景都摆在原点附近)。 解决:Map Layout Editor 拖拽摆放到不重叠的位置;MapDatabaseValidateAll 会红色高亮重叠房间。


Q房间在地图上占格过大/过小

原因: worldUnitsPerCell 与关卡尺度不匹配。 解决: 调整 MapPlayerTracker 与 Room Capture Baker 的 世界单位/格重新派生格子§6.2)。


Q按键提示图标不显示只有空白框 + 文字)

原因: InputDeviceIconSetSO 图标集未创建/未配置 SpriteInputIconService 未装配。 解决: 装配输入服务§2.3);用 Input Icon Studio 配置 4 套图标集并赋给 InputIconService§9


Q打开全屏地图没反应

可能原因 1 UIManager._panels 未登记 Map。重跑 Scaffold Map UI(会自动登记)。 可能原因 2 EVT_MapOpen 未被任何输入触发。确认输入绑定会 Raise 该频道UIManager 已订阅)。