地图编辑器
Introduction
游戏开发过程中,我们搭建关卡时需要一个工具去支持摆放场景物件和数据埋点编辑,从而支持快速搭建关卡和策划数据埋点。这一工具正是本章节需要实现的地图编辑器。
地图编辑器
需求
实现一个工具,首先第一步,我们还是需要理清需求:
- 纯Editor地图编辑器,用于场景编辑和数据埋点。
- 支持自定义场景对象数据和自由摆放场景对象进行场景编辑,场景对象支持静态摆放和动态创建两种。
- 支持自定义埋点数据和自由摆放埋点数据进行数据埋点编辑。
- 支持自定义调整场景大小以及场景地形指定和大小自动适配。
- 场景数据和埋点数据支持导出自定义数据格式(比如Lua,Json等)。
- 同一个场景支持编辑多个场景编辑和数据埋点。
设计实现
接下来针对前面提到的需求,我们一一分析我们应该用什么方案和技术来实现。
实现思路:
- 地图编辑器主要由地图编辑器配置窗口和地图编辑器挂在操作脚本Inspector组成。
- 地图编辑器编辑数据分为两大类(1. 地图编辑 2. 数据埋点)。
- 继承EditorWindow实现场景编辑和数据埋点的基础数据配置编辑。
- 继承Editor自定义Inspector面板实现纯单个场景编辑和数据埋点操作。
- 地图编辑操作通过挂在脚本(Map.cs)的方式作为单个场景编辑和数据埋点单位,从而实现单个场景多个场景编辑和数据埋点支持。
- 场景对象编辑采用直接创建实体GameObject的方式,从而实现场景编辑完成后的场景可直接用于作为场景使用。
- 场景对象编辑通过自定义Inspector面板实现快速删除和还原动态场景对象GameObject实现静态和动态场景对象的编辑和数据导出。
- 数据埋点采用Gizmos(Monobehaviour:OnDrawGizmos()),Handles(Editor.OnSceneGUI())实现可视化编辑对象和相关数据显示,自定义场景大小配置网格显示也是用Gizmos实现。
- 地图编辑器配置窗口用于配置基础的场景数据和埋点数据配置,Map.cs的挂在脚本通过自定义数据结构和自定义面板显示实现自定义数据配置。
- 场景静态对象相关数据通过挂在MapObjectDataMono脚本存储相关对象数据。
- 场景动态对象通过导出时导出自定义配置数据实现自定义数据导出。
- 导出前通过Map.cs存储的数据构建自定义数据(MapExport.cs)实现自定义数据导出
- 大地图暂定通过分块品抽的方式组装按需加载。
核心思路和技术实现方案都在上面介绍了,这里就不一一介绍代码实现了,这里只放部分关键代码,让我们直接实战看效果,需要源码的直接在文章末尾Github链接去取即可。
配置窗口
配置窗口主要负责实现对地图编辑对象,地图编辑埋点的基础数据定义,一些相关操作按钮和全局配置。
配置定义
地图配置数据结构定义:
1 | /// <summary> |
地图对象配置定义:
1 | /// <summary> |
地图埋点配置定义:
1 | /// <summary> |
配置定义设计如下:
- MapSetting是地图编辑器配置数据结构定义,继承至ScriptableObject,保存到Editor
- MapObjectSetting是地图对象的所有编辑器配置结构定义。MapObjectConfig是地图对象编辑器单条配置结构定义。MapObjectType是地图对象类型定义。
- MapDataSetting是地图埋点的所有编辑器配置结构定义。MapDataConfig是地图埋点编辑器单条配置结构定义。MapDataType是地图埋点类型定义。
配置窗口
可以看到在地图编辑器窗口主要分为4个区域:
- 快捷按钮区域
- 自定义地图基础数据区域(e.g. 默认地图宽高)
- 地图对象配置区域
- 地图埋点配置区域
以上配置数据会成为我们后面操作编辑Inspector里用户可以添加操作的基础数据来源,详细代码参见MapEditorWindow.cs。
快捷按钮区域
效果图:
可以看到目前提供了三个快捷按钮:
- 保存地图配置数据 — 用于确保我们在配置窗口的数据配置修改保存到本地ScriptableObject Asset
- 打开地图编辑场景 — 用于帮助我们快速打开辅助地图编辑的场景
- 快速选中地图地编对象 — 用于帮助我们快速选中当前场景里第一个带Map.cs脚本的对象,方便快速进入Map.cs的Inspector操作面板操作
自定义地图数据区域
效果图:
自定义数据目前只支持了默认创建地图编辑宽高配置(挂在Map.cs脚本时默认创建的地图横向竖向大小数据来源)。
未来扩展更多基础配置数据在这里添加。
地图对象配置区域
效果图:
地图对象配置设计如下:
以UID作为唯一配置Id标识(不允许重复),此Id会作为我们编辑器地图对象配置数据读取的依据。
通过MapObjectType指定地图对象类型。
通过定义ConfId(关联配置Id)实现关联游戏内动态对象Id的功能。
地图对象编辑通过定义Asset实现指定自定义预制件Asset作为实体对象的资源对象。
- 地图对象的是否动态是用于标识地图对象是静态摆放在场景里还是后续由程序动态创建的标识符。
- 描述用于方便用户自定义取名,方便识别不同的地图对象配置。
Note:
- 需要参与导出的地图基础配置数据如果修改了(比如是否动态,关联配置Id),对应用到此基础配置数据的关卡都需要重新导出。
- MapObjectType需要代码自定义扩展后,编辑器才有更多选项
地图埋点配置区域
效果图:
地图埋点配置设计如下:
- 以UID作为唯一配置Id标识(不允许重复),此Id会作为我们编辑器地图埋点配置数据读取的依据。
- 通过MapDataType指定地图埋点类型。
- 通过定义ConfId(关联配置Id)实现关联游戏内动态对象Id的功能。
- 场景球体颜色用于配置地图埋点的GUI球体绘制颜色配置。
- 初始旋转用于配置地图埋点添加时的初始旋转配置(方便自定义大部分用到的初始宣旋转数据)
- 描述用于方便用户自定义取名,方便识别不同的地图埋点配置。
Note:
- MapDataType需要代码自定义扩展后,编辑器才有更多选项
操作编辑Inspector
操作编辑Inspector主要负责实现对地图编辑对象和地图编辑埋点的一些相关操作面板,可视化GUI数据绘制,地图编辑自定义基础数据配置以及一些快捷操作按钮,详细代码参见Map.cs和MapEditor.cs。
基础数据配置区域
基础数据配置区域用于GUI开关,地图大小,地图起始位置,自定义地形等配置。
效果图:
自定义配置区域介绍:
- 场景GUI总开关 — 所有绘制GUI的总开关
- 地图线条GUI开关 — 控制地图N*N的线条GUI绘制开关
- 地图对象场景GUI开关 — 控制地图对象相关的GUI绘制开关
- 地图埋点场景GUI开关 — 控制地图埋点相关的GUI绘制开关
- 地图对象创建自动聚焦开关 — 控制地图对象创建后是否需要自动聚焦选中
- 地图横向大小和地图纵向大小 — 用于控制我们需要编辑的关卡地图的大小,会影响默认地图或自定义地图的大小和平铺
- 游戏地图起始位置 — 用于控制关卡的起始位置偏移,方便支持自定义不同起始位置的关卡设置
- 自定义地形Asset — 用于支持用户设置使用不同的地形资源
快捷操作按钮区域
快捷操作按钮区域用于支持自定义功能。
效果图:
快捷按钮功能介绍:
- 清除动态对象显示按钮 — 用于关卡编辑烘焙完成后,销毁需要程序动态创建的对象,制作只剩关卡静态对象的关卡预制件
- 恢复动态对象显示按钮 — 用于关卡烘焙时,还原所有需要参与烘焙的对象,从而烘焙出正确的寻路数据
- 拷贝NavMesh Asset按钮 — NavMeshSurface默认烘焙Asset保存在对应场景同层目录,此按钮用于快速拷贝对应Asset到对应关卡预制件同层目录
- 一键重创地图对象按钮 — 用于我们更新了某些已经创建好的静态地图对象(脱离预制件关联)相关资源后一键确保使用最新资源创建
- 导出地图数据按钮 — 用于完成我们的关卡数据序列化导出
- 一键烘焙拷贝导出 — 用于一键完成恢复动态对象+烘培寻路+拷贝寻路+清除动态对象+导出地图数据操作
Note:
- 烘焙寻路时所有动态对象必须显示着参与烘焙,生成关卡预制件时不需清除动态对象显示后应用到关卡预制件上
- 场景对象是否参与寻路烘培,通过修改预制件Layer和NavMeshSurface的寻路烘培的Layer决定
- 大部分操作在实体对象做成预制件后需要进入预制件编辑模式,所以部分操作会根据脚本所在实体对象情况决定是否自动进入预制件编辑模式
地图对象编辑区域
效果图:
地图对象数据定义:
1 | /// <summary> |
地图对象编辑通过选择地图对象类型和地图对象选择决定要添加的地图对象配置。
地图对象编辑和地图对象选择后面的+默认是添加到地图对象数据列表尾部。
地图对象数据列表后的操作可以对已经配置的地图对象数据进行相关操作,此处的+号是将选择的地图对象类型和地图对象选择插入的当前数据位置。
Note:
- 动态对象最终由程序运行时创建,所以做成预制件时一定要记着清楚动态对象的显示
- 动态对象通过地图对象配置面板配置的关联id导出给程序实现动态对象的配置关联
- 静态对象的数据关联是通过挂在MapObjectDataMono.cs脚本实现
地图埋点编辑区域
效果图:
地图埋点数据定义:
1 | /// <summary> |
地图埋点编辑通过选择地图埋点类型和地图埋点选择决定要添加的地图埋点配置。
地图埋点编辑和地图埋点选择后面的+默认是添加到地图埋点数据列表尾部。
地图埋点数据列表后的操作可以对已经配置的地图埋点数据进行相关操作,此处的+号是将选择的地图埋点类型和地图对象选择插入的当前数据位置。
地图埋点支持批量操作位置,通过勾选批量选项决定哪些地图埋点数据要一起操作位置,然后操作(拖拽或者输入坐标)勾选了批量的地图埋点对象的位置,可以实现一起修改勾选了批量操作的地图埋点位置。
一键清除批量勾选按钮 — 用于快速取消所有已勾选批量的地图埋点数据
Note:
- 地图数据的编辑和导出是按MapEditor脚本为单位。
- 地图埋点数据时通过Editor GUI(e.g. Handles和Gimoz)绘制的
- 地图埋点的位置调整需要按住W按键,旋转调整需要按住E按键
- 地图埋点支持配置自定义数据,这部分可以参考Monster和MonsterGroup埋点的自定义数据配置
- 地图埋点通过地图埋点配置面板配置的关联id导出给程序实现地图埋点的配置关联
- 自定义数据埋点可视化可以通过扩展MapEditor.cs实现自定义绘制
寻路烘培
目前场景编辑是以Map.cs为单位。所以寻路烘培目前也是按Map.cs所在预制件为单位。
通过每个Map.cs所在GameObject挂在NavMeshSurface组件实现对当前GameObject的寻路烘培。
效果图:
Note:
- NavMeshSurface设置Collect Objects为Children(只有子节点参与烘培),参与烘培的Include Layers设置成自定义的实现是否参与烘培的Layer规则。
数据导出
数据导出是通过点击导出地图数据按钮或者一键烘培拷贝导出按钮实现的。
为了支持多种不同数据格式的导出,在数据导出之前我进行导出数据的定义和抽象。
地图导出数据结构定义:
1 | /// <summary> |
地图对象导出数据基类定义:
1 | /// <summary> |
碰撞体场景动态物体数据导出定义:
1 | /// <summary> |
地图埋点导出数据基类定义:
1 | /// <summary> |
怪物埋点数据导出定义:
1 | /// <summary> |
怪物组数据导出定义:
1 | /// <summary> |
地图导出数据预览Level1.json:
1 | { |
上面的数据正对应我们导出填充的MapExport数据结构。
MapExport导出数据构建是通过GameMapEditorUtilities.GetMapExport()通过Map脚本获取所有相关数据去构建MapExport实现导出数据填充构建的。
Note:
- 因为静态地图对象是跟随预制件一起加载的,所以导出的地图对象数据只包含动态地图对象相关的
- 扩展自定义配置数据需要自行扩展或继承MapObjectData或MapData和MapEditor.cs面板显示
- 扩展自定义导出数据需要自行扩展或继承BaseMapDynamicExpor或BaseMapDataExportt定义
- 自定义更多的动态对象数据导出通过继承BaseMapDynamicExport定义。
- 自定义地图埋点数据导出通过继承BaseMapDataExport定义
实战
摄像机可见范围
待添加
摄像机可见范围动态创建
待添加
地图数据创建
待添加
碰撞数据相交判定
待添加
学习总结
- EditorWindow配置编辑器基础可配置数据,Inspector面板提供地图编辑器操作入口和数据序列化存储
- 地图对象通过序列化位置+旋转+碰撞等数据可以实现场景编辑后一键更新所有场景对象效果
- 地图对象数据存储可以挂在Mono脚本或自定义数据导出实现场景对象相关配置数据存储和导出
- 数据埋点显示和操作通过Gizmo和Handles可以实现自己设计的操作交互和显示