文章目錄
  1. 1. Introduction
  2. 2. 地图编辑器
    1. 2.1. 需求
    2. 2.2. 设计实现
    3. 2.3. 配置窗口
      1. 2.3.1. 配置定义
      2. 2.3.2. 配置窗口
        1. 2.3.2.1. 快捷按钮区域
        2. 2.3.2.2. 自定义地图数据区域
        3. 2.3.2.3. 地图对象配置区域
        4. 2.3.2.4. 地图埋点配置区域
    4. 2.4. 操作编辑Inspector
      1. 2.4.1. 基础数据配置区域
      2. 2.4.2. 快捷操作按钮区域
      3. 2.4.3. 地图对象编辑区域
      4. 2.4.4. 地图埋点编辑区域
      5. 2.4.5. 寻路烘培
      6. 2.4.6. 数据导出
    5. 2.5. 实战
      1. 2.5.1. 摄像机可见范围
      2. 2.5.2. 摄像机可见范围动态创建
      3. 2.5.3. 地图数据创建
      4. 2.5.4. 碰撞数据相交判定
  3. 3. 学习总结
  4. 4. Github
  5. 5. Reference

Introduction

游戏开发过程中,我们搭建关卡时需要一个工具去支持摆放场景物件和数据埋点编辑,从而支持快速搭建关卡和策划数据埋点。这一工具正是本章节需要实现的地图编辑器。

地图编辑器

需求

实现一个工具,首先第一步,我们还是需要理清需求:

  1. 纯Editor地图编辑器,用于场景编辑数据埋点
  2. 支持自定义场景对象数据和自由摆放场景对象进行场景编辑,场景对象支持静态摆放动态创建两种。
  3. 支持自定义埋点数据自由摆放埋点数据进行数据埋点编辑。
  4. 支持自定义调整场景大小以及场景地形指定和大小自动适配。
  5. 场景数据和埋点数据支持导出自定义数据格式(比如Lua,Json等)。
  6. 同一个场景支持编辑多个场景编辑和数据埋点。

设计实现

接下来针对前面提到的需求,我们一一分析我们应该用什么方案和技术来实现。

实现思路:

  1. 地图编辑器主要由地图编辑器配置窗口地图编辑器挂在操作脚本Inspector组成。
  2. 地图编辑器编辑数据分为两大类(1. 地图编辑 2. 数据埋点)。
  3. 继承EditorWindow实现场景编辑和数据埋点的基础数据配置编辑。
  4. 继承Editor自定义Inspector面板实现纯单个场景编辑和数据埋点操作。
  5. 地图编辑操作通过挂在脚本(Map.cs)的方式作为单个场景编辑和数据埋点单位,从而实现单个场景多个场景编辑和数据埋点支持。
  6. 场景对象编辑采用直接创建实体GameObject的方式,从而实现场景编辑完成后的场景可直接用于作为场景使用。
  7. 场景对象编辑通过自定义Inspector面板实现快速删除和还原动态场景对象GameObject实现静态和动态场景对象的编辑和数据导出。
  8. 数据埋点采用Gizmos(Monobehaviour:OnDrawGizmos()),Handles(Editor.OnSceneGUI())实现可视化编辑对象和相关数据显示,自定义场景大小配置网格显示也是用Gizmos实现。
  9. 地图编辑器配置窗口用于配置基础的场景数据和埋点数据配置,Map.cs的挂在脚本通过自定义数据结构和自定义面板显示实现自定义数据配置。
  10. 场景静态对象相关数据通过挂在MapObjectDataMono脚本存储相关对象数据。
  11. 场景动态对象通过导出时导出自定义配置数据实现自定义数据导出。
  12. 导出前通过Map.cs存储的数据构建自定义数据(MapExport.cs)实现自定义数据导出
  13. 大地图暂定通过分块品抽的方式组装按需加载。

核心思路和技术实现方案都在上面介绍了,这里就不一一介绍代码实现了,这里只放部分关键代码,让我们直接实战看效果,需要源码的直接在文章末尾Github链接去取即可。

配置窗口

配置窗口主要负责实现对地图编辑对象,地图编辑埋点的基础数据定义,一些相关操作按钮和全局配置。

配置定义

地图配置数据结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/// <summary>
/// MapSetting.cs
/// </summary>
public class MapSetting : ScriptableObject
{
#if UNITY_EDITOR
/// <summary>
/// Editor下的地图配置单例对象
/// </summary>
private static MapSetting EditorSingleton;

/// <summary>
/// 获取Editor地图配置单例对象
/// </summary>
/// <returns></returns>
public static MapSetting GetEditorInstance()
{

if(EditorSingleton == null)
{
EditorSingleton = MapUtilities.LoadOrCreateGameMapSetting();
}
return EditorSingleton;
}
#endif

/// <summary>
/// 默认地图横向大小
/// </summary>
[Header("默认地图横向大小")]
[Range(1, 1000)]
public int DefaultMapWidth = MapConst.DefaultMapWidth;

/// <summary>
/// 默认地图纵向大小
/// </summary>
[Header("默认地图纵向大小")]
[Range(1, 1000)]
public int DefaultMapHeight = MapConst.DefaultMapHeight;

/// <summary>
/// 地图对象配置数据
/// </summary>
[Header("地图对象配置数据")]
public MapObjectSetting ObjectSetting = new MapObjectSetting();

/// <summary>
/// 地图埋点配置数据
/// </summary>
[Header("地图埋点配置数据")]
public MapDataSetting DataSetting = new MapDataSetting();
}

地图对象配置定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/// <summary>
/// MapObjectType.cs
/// 地图对象类型枚举
/// </summary>
public enum MapObjectType
{
TREASURE_BOX = 0, // 宝箱
TRAP = 1, // 陷阱
DOOR = 2, // 门
}

/// <summary>
/// MapObjectSetting.cs
/// 地图对象设置数据
/// </summary>
[Serializable]
public class MapObjectSetting
{
/// <summary>
/// 所有地图对象配置数据
/// </summary>
[Header("所有地图对象配置数据")]
[SerializeReference]
public List<MapObjectConfig> AllMapObjectConfigs = new List<MapObjectConfig>();

******
}

/// <summary>
/// MapObjectConfig.cs
/// 地图对象数据配置
/// </summary>
[Serializable]
public class MapObjectConfig
{
/// <summary>
/// 唯一ID(用于标识地图对象配置唯一)
/// </summary>
[Header("唯一ID")]
public int UID;

/// <summary>
/// 地图对象类型
/// </summary>
[Header("地图对象类型")]
public MapObjectType ObjectType;

/// <summary>
/// 是否是动态地图对象
/// </summary>
[Header("是否是动态地图对象")]
public bool IsDynamic;

/// <summary>
/// 关联Id
/// </summary>
[Header("关联Id")]
public int ConfId;

/// <summary>
/// 资源Asset
/// </summary>
[Header("资源Asset")]
public GameObject Asset;

/// <summary>
/// 描述
/// </summary>
[Header("描述")]
public string Des;

******
}

地图埋点配置定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/// <summary>
/// MapDataType.cs
/// 地图数据埋点类型枚举
/// </summary>
public enum MapDataType
{
PLAYER_SPAWN = 0, // 玩家出生点位置数据
MONSTER = 1, // 怪物数据
MONSTER_GROUP = 2, // 怪物组数据
}

/// <summary>
/// MapDataSetting.cs
/// 地图数据配置数据
/// </summary>
[Serializable]
public class MapDataSetting
{
/// <summary>
/// 所有地图埋点数据配置
/// </summary>
[Header("所有地图埋点数据配置")]
[SerializeReference]
public List<MapDataConfig> AlllMapDataConfigs = new List<MapDataConfig>();

******
}

/// <summary>
/// MapDataConfig.cs
/// 地图埋点数据配置
/// </summary>
[Serializable]
public class MapDataConfig
{
/// <summary>
/// 唯一ID(用于标识地图埋点配置唯一)
/// </summary>
[Header("唯一ID")]
public int UID;

/// <summary>
/// 地图数据类型
/// </summary>
[Header("地图数据类型")]
public MapDataType DataType;

/// <summary>
/// 关联Id
/// </summary>
[Header("关联Id")]
public int ConfId;

/// <summary>
/// 场景球体颜色
/// </summary>
[Header("场景球体颜色")]
public Color SceneSphereColor = Color.red;

/// <summary>
/// 描述
/// </summary>
[Header("描述")]
public string Des;

******
}

配置定义设计如下:

  1. MapSetting是地图编辑器配置数据结构定义,继承至ScriptableObject,保存到Editor
  2. MapObjectSetting是地图对象的所有编辑器配置结构定义。MapObjectConfig是地图对象编辑器单条配置结构定义。MapObjectType是地图对象类型定义。
  3. MapDataSetting是地图埋点的所有编辑器配置结构定义。MapDataConfig是地图埋点编辑器单条配置结构定义。MapDataType是地图埋点类型定义。

配置窗口

可以看到在地图编辑器窗口主要分为4个区域:

  1. 快捷按钮区域
  2. 自定义地图基础数据区域(e.g. 默认地图宽高)
  3. 地图对象配置区域
  4. 地图埋点配置区域

以上配置数据会成为我们后面操作编辑Inspector里用户可以添加操作的基础数据来源,详细代码参见MapEditorWindow.cs

快捷按钮区域

效果图:

ShortcutOperationArea

可以看到目前提供了三个快捷按钮:

  1. 保存地图配置数据 — 用于确保我们在配置窗口的数据配置修改保存到本地ScriptableObject Asset
  2. 打开地图编辑场景 — 用于帮助我们快速打开辅助地图编辑的场景
  3. 快速选中地图地编对象 — 用于帮助我们快速选中当前场景里第一个带Map.cs脚本的对象,方便快速进入Map.cs的Inspector操作面板操作

自定义地图数据区域

效果图:

CustomDataSettingArea

自定义数据目前只支持了默认创建地图编辑宽高配置(挂在Map.cs脚本时默认创建的地图横向竖向大小数据来源)。

未来扩展更多基础配置数据在这里添加。

地图对象配置区域

效果图:

MapObjectConfigArea

地图对象配置设计如下:

  1. 以UID作为唯一配置Id标识(不允许重复),此Id会作为我们编辑器地图对象配置数据读取的依据。

  2. 通过MapObjectType指定地图对象类型。

  3. 通过定义ConfId(关联配置Id)实现关联游戏内动态对象Id的功能。

  4. 地图对象编辑通过定义Asset实现指定自定义预制件Asset作为实体对象的资源对象。

  5. 地图对象的是否动态是用于标识地图对象是静态摆放在场景里还是后续由程序动态创建的标识符。
  6. 描述用于方便用户自定义取名,方便识别不同的地图对象配置。

Note:

  1. 需要参与导出的地图基础配置数据如果修改了(比如是否动态,关联配置Id),对应用到此基础配置数据的关卡都需要重新导出。
  2. MapObjectType需要代码自定义扩展后,编辑器才有更多选项

地图埋点配置区域

效果图:

MapDataConfigArea

地图埋点配置设计如下:

  1. 以UID作为唯一配置Id标识(不允许重复),此Id会作为我们编辑器地图埋点配置数据读取的依据。
  2. 通过MapDataType指定地图埋点类型。
  3. 通过定义ConfId(关联配置Id)实现关联游戏内动态对象Id的功能。
  4. 场景球体颜色用于配置地图埋点的GUI球体绘制颜色配置。
  5. 初始旋转用于配置地图埋点添加时的初始旋转配置(方便自定义大部分用到的初始宣旋转数据)
  6. 描述用于方便用户自定义取名,方便识别不同的地图埋点配置。

Note:

  1. MapDataType需要代码自定义扩展后,编辑器才有更多选项

操作编辑Inspector

操作编辑Inspector主要负责实现对地图编辑对象和地图编辑埋点的一些相关操作面板,可视化GUI数据绘制,地图编辑自定义基础数据配置以及一些快捷操作按钮,详细代码参见Map.cs和MapEditor.cs

基础数据配置区域

基础数据配置区域用于GUI开关,地图大小,地图起始位置,自定义地形等配置。

效果图:

BasicDataConfigInspectorArea

自定义配置区域介绍:

  1. 场景GUI总开关 — 所有绘制GUI的总开关
  2. 地图线条GUI开关 — 控制地图N*N的线条GUI绘制开关
  3. 地图对象场景GUI开关 — 控制地图对象相关的GUI绘制开关
  4. 地图埋点场景GUI开关 — 控制地图埋点相关的GUI绘制开关
  5. 地图对象创建自动聚焦开关 — 控制地图对象创建后是否需要自动聚焦选中
  6. 地图横向大小和地图纵向大小 — 用于控制我们需要编辑的关卡地图的大小,会影响默认地图或自定义地图的大小和平铺
  7. 游戏地图起始位置 — 用于控制关卡的起始位置偏移,方便支持自定义不同起始位置的关卡设置
  8. 自定义地形Asset — 用于支持用户设置使用不同的地形资源

快捷操作按钮区域

快捷操作按钮区域用于支持自定义功能。

效果图:

ShortcutOperationInspectorArea

快捷按钮功能介绍:

  1. 清除动态对象显示按钮 — 用于关卡编辑烘焙完成后,销毁需要程序动态创建的对象,制作只剩关卡静态对象的关卡预制件
  2. 恢复动态对象显示按钮 — 用于关卡烘焙时,还原所有需要参与烘焙的对象,从而烘焙出正确的寻路数据
  3. 拷贝NavMesh Asset按钮 — NavMeshSurface默认烘焙Asset保存在对应场景同层目录,此按钮用于快速拷贝对应Asset到对应关卡预制件同层目录
  4. 一键重创地图对象按钮 — 用于我们更新了某些已经创建好的静态地图对象(脱离预制件关联)相关资源后一键确保使用最新资源创建
  5. 导出地图数据按钮 — 用于完成我们的关卡数据序列化导出
  6. 一键烘焙拷贝导出 — 用于一键完成恢复动态对象+烘培寻路+拷贝寻路+清除动态对象+导出地图数据操作

Note:

  1. 烘焙寻路时所有动态对象必须显示着参与烘焙,生成关卡预制件时不需清除动态对象显示后应用到关卡预制件上
  2. 场景对象是否参与寻路烘培,通过修改预制件Layer和NavMeshSurface的寻路烘培的Layer决定
  3. 大部分操作在实体对象做成预制件后需要进入预制件编辑模式,所以部分操作会根据脚本所在实体对象情况决定是否自动进入预制件编辑模式

地图对象编辑区域

效果图:

MapObjectConfigInspectorArea

地图对象数据定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/// <summary>
/// MapObjectData.cs
/// 地图对象数据
/// </summary>
[Serializable]
public class MapObjectData
{
/// <summary>
/// 唯一Id(用于标识地图对象配置唯一)
/// </summary>
[Header("唯一Id")]
public int UID;

/// <summary>
/// 实体对象
/// </summary>
[Header("实体对象")]
public GameObject Go;

/// <summary>
/// 埋点位置(地图对象可能删除还原,所以需要逻辑层面记录位置)
/// </summary>
[Header("埋点位置")]
public Vector3 Position;

/// <summary>
/// 旋转(地图对象可能删除还原,所以需要逻辑层面记录旋转)
/// </summary>
[Header("旋转")]
public Vector3 Rotation = Vector3.zero;

/// <summary>
/// 本地缩放(地图对象可能删除还原,所以需要逻辑层面记录缩放)
/// </summary>
[Header("缩放")]
public Vector3 LocalScale = Vector3.one;

/// <summary>
/// 碰撞器中心点
/// </summary>
[Header("碰撞器中心点")]
public Vector3 ColliderCenter = new Vector3(0, 0, 0);

/// <summary>
/// 碰撞器大小
/// </summary>
[Header("碰撞器大小")]
public Vector3 ColliderSize = new Vector3(1, 1, 1);

/// <summary>
/// 碰撞体半径
/// </summary>
[Header("碰撞体半径")]
public float ColliderRadius = 1;

/// <summary>
/// 带参构造函数
/// </summary>
/// <param name="uid"></param>
/// <param name="go"></param>
public MapObjectData(int uid, GameObject go)
{

UID = uid;
Go = go;
}
}

地图对象编辑通过选择地图对象类型地图对象选择决定要添加的地图对象配置。

地图对象编辑和地图对象选择后面的+默认是添加到地图对象数据列表尾部

地图对象数据列表后的操作可以对已经配置的地图对象数据进行相关操作,此处的+号是将选择的地图对象类型和地图对象选择插入的当前数据位置

Note:

  1. 动态对象最终由程序运行时创建,所以做成预制件时一定要记着清楚动态对象的显示
  2. 动态对象通过地图对象配置面板配置的关联id导出给程序实现动态对象的配置关联
  3. 静态对象的数据关联是通过挂在MapObjectDataMono.cs脚本实现

地图埋点编辑区域

效果图:

MapDataConfigInspectorArea

地图埋点数据定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/// <summary>
/// MapData.cs
/// 地图数据买点数据
/// </summary>
[Serializable]
public class MapData
{
/// <summary>
/// 唯一Id(用于标识地图对象配置唯一)
/// </summary>
[Header("唯一Id")]
public int UID;

/// <summary>
/// 埋点位置
/// </summary>
[Header("埋点位置")]
public Vector3 Position;

/// <summary>
/// 埋点旋转
/// </summary>
[Header("埋点旋转")]
public Vector3 Rotation;

/// <summary>
/// 批量操作开关
/// </summary>
[Header("批量操作开关")]
public bool BatchOperationSwitch;

public MapData(int uid)
{

UID = uid;
}

public MapData(int uid, Vector3 position, Vector3 rotation)
{

UID = uid;
Position = position;
Rotation = rotation;
}
}

地图埋点编辑通过选择地图埋点类型地图埋点选择决定要添加的地图埋点配置。

地图埋点编辑和地图埋点选择后面的+默认是添加到地图埋点数据列表尾部

地图埋点数据列表后的操作可以对已经配置的地图埋点数据进行相关操作,此处的+号是将选择的地图埋点类型和地图对象选择插入的当前数据位置

地图埋点支持批量操作位置,通过勾选批量选项决定哪些地图埋点数据要一起操作位置,然后操作(拖拽或者输入坐标)勾选了批量的地图埋点对象的位置,可以实现一起修改勾选了批量操作的地图埋点位置。

一键清除批量勾选按钮 — 用于快速取消所有已勾选批量的地图埋点数据

Note:

  1. 地图数据的编辑和导出是按MapEditor脚本为单位。
  2. 地图埋点数据时通过Editor GUI(e.g. Handles和Gimoz)绘制的
  3. 地图埋点的位置调整需要按住W按键,旋转调整需要按住E按键
  4. 地图埋点支持配置自定义数据,这部分可以参考Monster和MonsterGroup埋点的自定义数据配置
  5. 地图埋点通过地图埋点配置面板配置的关联id导出给程序实现地图埋点的配置关联
  6. 自定义数据埋点可视化可以通过扩展MapEditor.cs实现自定义绘制

寻路烘培

目前场景编辑是以Map.cs为单位。所以寻路烘培目前也是按Map.cs所在预制件为单位

通过每个Map.cs所在GameObject挂在NavMeshSurface组件实现对当前GameObject的寻路烘培。

效果图:

NavigationBakePreview

Note:

  1. NavMeshSurface设置Collect Objects为Children(只有子节点参与烘培),参与烘培的Include Layers设置成自定义的实现是否参与烘培的Layer规则。

数据导出

数据导出是通过点击导出地图数据按钮或者一键烘培拷贝导出按钮实现的。

为了支持多种不同数据格式的导出,在数据导出之前我进行导出数据的定义和抽象。

地图导出数据结构定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// MapExport.cs
/// 地图导出数据结构定义(统一导出结构定义,方便支持导出不同的数据格式)
/// </summary>
[Serializable]
public class MapExport
{
/// <summary>
/// 地图导出数据成员
/// </summary>
public MapDataExport MapData;

/// <summary>
/// 所有只包含基础数据的动态物体导出数据成员
/// </summary>
public List<BaseMapDynamicExport> AllBaseMapObjectExportDatas = new List<BaseMapDynamicExport>();

/// <summary>
/// 所有包含碰撞数据的动态物体导出数据成员
/// </summary>
public List<ColliderMapDynamicExport> AllColliderMapDynamicExportDatas = new List<ColliderMapDynamicExport>();

/// <summary>
/// 所有怪物组的怪物组导出数据列表
/// </summary>
public List<MonsterGroupMapDataExport> AllMonsterGroupMapDatas = new List<MonsterGroupMapDataExport>();

/// <summary>
/// 所有没有怪物组的怪物导出数据列表
/// </summary>
public List<MonsterMapDataExport> ALlNoGroupMonsterMapDatas = new List<MonsterMapDataExport>();

/// <summary>
/// 剩余其他(不含怪物和怪物组和出生点)地图埋点导出数据成员
/// </summary>
public List<BaseMapDataExport> AllOtherMapDatas = new List<BaseMapDataExport>();
}

地图对象导出数据基类定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// <summary>
/// BaseMapDynamicExport.cs
/// 地图动态物体数据基类导出定义
/// </summary>
[Serializable]
public class BaseMapDynamicExport
{
/// <summary>
/// 地图对象类型
/// </summary>
public MapObjectType MapObjectType;

/// <summary>
/// 关联配置Id
/// </summary>
public int ConfId;

/// <summary>
/// 位置信息
/// </summary>
public Vector3 Position;

/// <summary>
/// 旋转信息
/// </summary>
public Vector3 Rotation;

public BaseMapDynamicExport(MapObjectType mapObjectType, int confId, Vector3 position, Vector3 rotation)
{

MapObjectType = mapObjectType;
ConfId = confId;
Position = position;
Rotation = rotation;
}
}

碰撞体场景动态物体数据导出定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/// <summary>
/// ColliderMapDynamicExport.cs
/// 碰撞体场景动态物体数据导出
/// </summary>
[Serializable]
public class ColliderMapDynamicExport : BaseMapDynamicExport
{
/// <summary>
/// 碰撞体中心
/// </summary>
public Vector3 ColliderCenter;

/// <summary>
/// 碰撞体大小
/// </summary>
public Vector3 ColliderSize;

/// <summary>
/// 碰撞体半径
/// </summary>
public float ColliderRiduis;

public ColliderMapDynamicExport(MapObjectType mapObjectType, int confId, Vector3 position, Vector3 rotation,
Vector3 colliderCenter, Vector3 colliderSize, float colliderRiduis)

: base(mapObjectType, confId, position, rotation)
{

ColliderCenter = colliderCenter;
ColliderSize = colliderSize;
ColliderRiduis = colliderRiduis;
}
}

地图埋点导出数据基类定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// <summary>
/// BaseMapDataExport.cs
/// 地图埋点数据基类导出定义
/// </summary>
[Serializable]
public class BaseMapDataExport
{
/// <summary>
/// 埋点类型
/// </summary>
public MapDataType MapDataType;

/// <summary>
/// 关联Id
/// </summary>
public int ConfId;

/// <summary>
/// 位置信息
/// </summary>
public Vector3 Position;

/// <summary>
/// 旋转信息
/// </summary>
public Vector3 Roation;

public BaseMapDataExport(MapDataType mapDataType, int confId, Vector3 position, Vector3 rotation)
{

MapDataType = mapDataType;
ConfId = confId;
Position = position;
Roation = rotation;
}
}

怪物埋点数据导出定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/// <summary>
/// MonsterMapDataExport.cs
/// 怪物地图埋点数据导出
/// </summary>
[Serializable]
public class MonsterMapDataExport : BaseMapDataExport
{
/// <summary>
/// 怪物组Id
/// </summary>
public int GroupId;

public MonsterMapDataExport(MapDataType mapDataType, int confId, Vector3 position, Vector3 rotation, int groupId)
: base(mapDataType, confId, position, rotation)
{

GroupId = groupId;
}
}

怪物组数据导出定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/// <summary>
/// MonsterGroupMapDataExport.cs
/// 怪物组数据导出定义
/// </summary>
[Serializable]
public class MonsterGroupMapDataExport : BaseMapDataExport
{
/// <summary>
/// 怪物组Id
/// </summary>
public int GroupId;

/// <summary>
/// 怪物创建半径
/// </summary>
public float MonsterCreateRadius;

/// <summary>
/// 怪物警戒半径
/// </summary>
public float MonsterActiveRadius;

/// <summary>
/// 所有归属当前怪物组的怪物导出数据列表
/// </summary>
public List<MonsterMapDataExport> AllMonsterMapExportDatas = new List<MonsterMapDataExport>();

public MonsterGroupMapDataExport(MapDataType mapDataType, int confId, Vector3 position, Vector3 rotation, int groupId, float monsterCreateRadius, float monsterActiveRadius)
: base(mapDataType, confId, position, rotation)
{

GroupId = groupId;
MonsterCreateRadius = monsterCreateRadius;
MonsterActiveRadius = monsterActiveRadius;
}
}

地图导出数据预览Level1.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
{
"MapData": {
"Width": 8,
"Height": 30,
"StartPos": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"BirthPos": [
{
"x": 4.0,
"y": 0.0,
"z": 2.0
}

]
}
,

"AllBaseMapObjectExportDatas": [
{
"MapObjectType": 2,
"ConfId": 1,
"Position": {
"x": 4.0,
"y": 1.0,
"z": 3.7200000286102297
}
,

"Rotation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}

},

{
"MapObjectType": 2,
"ConfId": 1,
"Position": {
"x": 4.0,
"y": 1.0,
"z": 11.600000381469727
}
,

"Rotation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}

}

],

"AllColliderMapDynamicExportDatas": [
{
"MapObjectType": 1,
"ConfId": 2,
"Position": {
"x": 4.0,
"y": 0.25,
"z": 8.149999618530274
}
,

"Rotation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderCenter": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderSize": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderRiduis": 0.5
},

{
"MapObjectType": 0,
"ConfId": 3,
"Position": {
"x": 4.0,
"y": 0.5,
"z": 18.809999465942384
}
,

"Rotation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderCenter": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderSize": {
"x": 1.0,
"y": 1.0,
"z": 1.0
}
,

"ColliderRiduis": 0.0
},

{
"MapObjectType": 0,
"ConfId": 3,
"Position": {
"x": 4.0,
"y": 0.5,
"z": 14.899999618530274
}
,

"Rotation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderCenter": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"ColliderSize": {
"x": 1.0,
"y": 1.0,
"z": 1.0
}
,

"ColliderRiduis": 0.0
}

],

"AllMonsterGroupMapDatas": [
{
"MapDataType": 2,
"ConfId": 0,
"Position": {
"x": 4.0,
"y": 0.0,
"z": 8.0
}
,

"Roation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"GroupId": 1,
"MonsterCreateRadius": 4.0,
"MonsterActiveRadius": 3.0,
"AllMonsterMapExportDatas": [
{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 4.0,
"y": 0.0,
"z": 7.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 1
},

{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 5.0,
"y": 0.0,
"z": 9.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 1
},

{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 6.0,
"y": 0.0,
"z": 8.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 1
}

]
},

{
"MapDataType": 2,
"ConfId": 0,
"Position": {
"x": 4.0,
"y": 0.0,
"z": 25.0
}
,

"Roation": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
,

"GroupId": 2,
"MonsterCreateRadius": 6.0,
"MonsterActiveRadius": 4.0,
"AllMonsterMapExportDatas": [
{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 4.0,
"y": 0.0,
"z": 26.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 2
},

{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 5.0,
"y": 0.0,
"z": 26.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 2
},

{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 3.0,
"y": 0.0,
"z": 25.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 2
},

{
"MapDataType": 1,
"ConfId": 1,
"Position": {
"x": 5.0,
"y": 0.0,
"z": 23.0
}
,

"Roation": {
"x": 0.0,
"y": 90.0,
"z": 0.0
}
,

"GroupId": 2
}

]
}

],

"ALlNoGroupMonsterMapDatas": [],
"AllOtherMapDatas": []
}

上面的数据正对应我们导出填充的MapExport数据结构。

MapExport导出数据构建是通过GameMapEditorUtilities.GetMapExport()通过Map脚本获取所有相关数据去构建MapExport实现导出数据填充构建的。

Note:

  1. 因为静态地图对象是跟随预制件一起加载的,所以导出的地图对象数据只包含动态地图对象相关的
  2. 扩展自定义配置数据需要自行扩展或继承MapObjectData或MapData和MapEditor.cs面板显示
  3. 扩展自定义导出数据需要自行扩展或继承BaseMapDynamicExpor或BaseMapDataExportt定义
  4. 自定义更多的动态对象数据导出通过继承BaseMapDynamicExport定义。
  5. 自定义地图埋点数据导出通过继承BaseMapDataExport定义

实战

摄像机可见范围

待添加

摄像机可见范围动态创建

待添加

地图数据创建

待添加

碰撞数据相交判定

待添加

学习总结

  1. EditorWindow配置编辑器基础可配置数据,Inspector面板提供地图编辑器操作入口和数据序列化存储
  2. 地图对象通过序列化位置+旋转+碰撞等数据可以实现场景编辑后一键更新所有场景对象效果
  3. 地图对象数据存储可以挂在Mono脚本或自定义数据导出实现场景对象相关配置数据存储和导出
  4. 数据埋点显示和操作通过Gizmo和Handles可以实现自己设计的操作交互和显示

Github

MapEditor

Reference

文章目錄
  1. 1. Introduction
  2. 2. 地图编辑器
    1. 2.1. 需求
    2. 2.2. 设计实现
    3. 2.3. 配置窗口
      1. 2.3.1. 配置定义
      2. 2.3.2. 配置窗口
        1. 2.3.2.1. 快捷按钮区域
        2. 2.3.2.2. 自定义地图数据区域
        3. 2.3.2.3. 地图对象配置区域
        4. 2.3.2.4. 地图埋点配置区域
    4. 2.4. 操作编辑Inspector
      1. 2.4.1. 基础数据配置区域
      2. 2.4.2. 快捷操作按钮区域
      3. 2.4.3. 地图对象编辑区域
      4. 2.4.4. 地图埋点编辑区域
      5. 2.4.5. 寻路烘培
      6. 2.4.6. 数据导出
    5. 2.5. 实战
      1. 2.5.1. 摄像机可见范围
      2. 2.5.2. 摄像机可见范围动态创建
      3. 2.5.3. 地图数据创建
      4. 2.5.4. 碰撞数据相交判定
  3. 3. 学习总结
  4. 4. Github
  5. 5. Reference