在我来4399之前的上一家公司,我做了一个2D的对战游戏,地图编辑器的做法是用格子图片的预设一个个拼接成一张地图,每个格子上可以设置该格子的数据,比如图片名字,tile坐标,是否可通过,是否可销毁,是否是陷阱等等,具体做法就是把所有图片的预设都编一个id,然后在当前tile上填上对应的id,最后保存成一个json文件,在游戏里面动态生成。

以你们的想法,你们会觉得这种做法合不合适?反正在我看来,当时的做法确实不怎么好。首先我们需要手动的去制作tile图片的预设,而且没有一个好的可视化界面,只能拖到编辑器对应id上的FileInput,其实这里unity2017.2.0发布Tilemap后,TilePalette很好的解决了我的这个问题。再者,我们用图片的预设拼接成的地图,你们想想会有什么问题?性能!特别是一张超级大的地图,想想都可怕。做地图编辑器的初衷是为了方便策划自己去制作地图,结果反而策划在学习使用上觉得有点不便利了,这让我有点懊恼,想想也怪我。就在我思考怎么去优化地图编辑器的时候,unity2017.2.0发布了,新功能Tilemap的出现让我重新有了完善地图编辑器的想法。只不过我不会用在项目上了,因为我们项目不是用的unity2017的版本,而且我要辞职了。

项目源码  https://github.com/wuxiaomu/XMtileMap

好了,接下来我们来讲讲我重新做的这个地图编辑器吧。

先看看编辑器的界面,都有哪些功能。

1.基础数据结构。

// **********************************************************************
// Copyright (C) XM
// Author: 吴肖牧
// Date: 2018-02-15
// Desc:
// **********************************************************************using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;namespace XMtileMap
{/// <summary>/// 所有地图数据的列表/// </summary>[CreateAssetMenu]public class TileMapSerialize : ScriptableObject{[SerializeField]public List<TileMapDataList> Data = new List<TileMapDataList>();}/// <summary>/// 对应地图的数据列表/// </summary>[Serializable]public class TileMapDataList{public List<TileMapData> tileMapDataList = new List<TileMapData>();}/// <summary>/// 对应地图的Tilemap数据/// </summary>[Serializable]public class TileMapData{public string TilemapName = "Tilemap";public int SortOrderIndex = 0;public int SortingLayerIndex = 0;public int OrderInLayer = 0;public List<TileInfo> tileInfoList;}/// <summary>/// 基础数据/// </summary>[Serializable]public class TileInfo{public Tile tile;public Vector3 pos;public Vector3Int ipos;// DOTO other data}
}

根据项目需求可以自行在TileInfo里面拓展数据结构。

这里需要注意的是,场景上不一定只有一个Tilemap组件,可能我们需要多个层,创建了多个Tilemap,所以数据结构里面我们是用List来保存多个Tilemap的,这里指TileMapData,为了不和自带的Tilemap类混淆了。

2.数据和文件的操作。

// **********************************************************************
// Copyright (C) XM
// Author: 吴肖牧
// Date: 2018-02-15
// Desc:
// **********************************************************************using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Tilemaps;namespace XMtileMap
{public class XMMapData{public static string MapDataPath = "Assets/RefResources/ScriptableObjects/MapData.asset";public static string SourceDataPath = "Assets/XMtileMap/Data/SourceData.asset";public static string TargetDataPath = "Assets/XMtileMap/Data/TargetData.asset";private static TileMapSerialize mapData;/// <summary>/// 地图数据,游戏使用的数据/// </summary>public static TileMapSerialize MapData{get{if (mapData == null){// TODO runtime LoadData#if UNITY_EDITORmapData = UnityEditor.AssetDatabase.LoadAssetAtPath<TileMapSerialize>(MapDataPath);
#endif}return mapData;}}private static TileMapSerialize sourceData;/// <summary>/// 地图源数据,保存用/// </summary>public static TileMapSerialize SourceData{get{if (sourceData == null){// TODO runtime LoadData#if UNITY_EDITORsourceData = UnityEditor.AssetDatabase.LoadAssetAtPath<TileMapSerialize>(SourceDataPath);
#endif}return sourceData;}}private static TileMapSerialize targetData;/// <summary>/// 副本数据,编辑用/// </summary>public static TileMapSerialize TargetData{get{if (targetData == null){// TODO runtime LoadData#if UNITY_EDITORtargetData = UnityEditor.AssetDatabase.LoadAssetAtPath<TileMapSerialize>(TargetDataPath);
#endif}return targetData;}}/// <summary>/// 地图ID/// </summary>public static int MapID = 0;/// <summary>/// A星寻路的地图数据/// </summary>public static Dictionary<Vector2,Point> map;public static Vector2 tileOffset2 = new Vector2(0.5f, 0.5f);public static Vector3 tileOffset3 = new Vector3(0.5f, 0.5f, -0.5f);#if UNITY_EDITOR/// <summary>/// 载入地图数据/// </summary>/// <param name="path"></param>private static void LoadData(string path){TileMapSerialize targetData = UnityEditor.AssetDatabase.LoadAssetAtPath<TileMapSerialize>(path);if (targetData == null){string newPath = UnityEditor.EditorUtility.SaveFilePanelInProject("Save TileMapSerialize", "New TileMapSerialize", "asset", "Save TileMapSerialize", "Assets");if (newPath == "")return;UnityEditor.AssetDatabase.CreateAsset(ScriptableObject.CreateInstance<TileMapSerialize>(), newPath);targetData = UnityEditor.AssetDatabase.LoadAssetAtPath<TileMapSerialize>(path);}}public static void SaveData(List<TileMapDataList> target, List<TileMapDataList> source){target.Clear();foreach (var item in source){TileMapDataList list = new TileMapDataList();list.tileMapDataList = new List<TileMapData>();foreach (var item1 in item.tileMapDataList){TileMapData mapdata = new TileMapData();mapdata.OrderInLayer = item1.OrderInLayer;mapdata.SortingLayerIndex = item1.SortingLayerIndex;mapdata.SortOrderIndex = item1.SortOrderIndex;mapdata.TilemapName = item1.TilemapName;mapdata.tileInfoList = new List<TileInfo>();if (item1.tileInfoList == null || item1.tileInfoList.Count == 0){Debug.LogError(item1.TilemapName + " tileInfoList is null or count is zero");}else{foreach (var item2 in item1.tileInfoList){TileInfo tile = new TileInfo();tile.ipos = item2.ipos;tile.pos = item2.pos;tile.tile = item2.tile;mapdata.tileInfoList.Add(tile);}}list.tileMapDataList.Add(mapdata);}target.Add(list);}if (target == SourceData.Data){UnityEditor.EditorUtility.SetDirty(TargetData);UnityEditor.EditorUtility.SetDirty(SourceData);}else if (target == TargetData.Data){UnityEditor.EditorUtility.SetDirty(TargetData);}File.Copy(Application.dataPath + "/XMtileMap/Data/SourceData.asset", Application.dataPath + "/RefResources/ScriptableObjects/MapData.asset",true);UnityEditor.AssetDatabase.SaveAssets();UnityEditor.AssetDatabase.Refresh();}/// <summary>/// 保存json/// </summary>/// <param name="path"></param>/// <param name="data"></param>public static void SaveToJSON(string path, TileMapSerialize data){Debug.LogFormat("Saving config to {0}", path);System.IO.File.WriteAllText(path, JsonUtility.ToJson(data, true));}/// <summary>/// 添加地图数据/// </summary>/// <param name="pos">世界坐标</param>/// <param name="data">单位数据</param>public static void AddData(GameObject brushTarget, Vector3 pos, TileInfo data){Tilemap tile = brushTarget.GetComponent<Tilemap>();foreach (var item in TargetData.Data[MapID].tileMapDataList){if (item.TilemapName == tile.name){if (item.tileInfoList == null){item.tileInfoList = new List<TileInfo>();}bool isadd = true;for (int i = 0; i < item.tileInfoList.Count; i++){if (item.tileInfoList[i].pos == pos){isadd = false;item.tileInfoList[i] = data;break;}}if (isadd){item.tileInfoList.Add(data);}}}}/// <summary>/// 清除地图数据/// </summary>/// <param name="pos">世界坐标</param>public static void ClearData(GameObject brushTarget, Vector3 pos){Tilemap tile = brushTarget.GetComponent<Tilemap>();foreach (var item in TargetData.Data[MapID].tileMapDataList){if (item.TilemapName == tile.name){for (int i = 0; i < item.tileInfoList.Count; i++){if (item.tileInfoList[i].pos == pos){item.tileInfoList.RemoveAt(i);}}}}}public static void ClearDataForPos(Vector3 pos){foreach (var item in TargetData.Data[MapID].tileMapDataList){for (int i = 0; i < item.tileInfoList.Count; i++){if (item.tileInfoList[i].pos == pos){item.tileInfoList.RemoveAt(i);}}}}/// <summary>/// 清空数据/// </summary>public static void ClearAllData(){TargetData.Data[MapID].tileMapDataList.Clear();}
#endif}
}

这里的重点是AddData方法,用Brush画刷调用此方法来添加数据,我们保存成ScriptableObject文件,如图

3.Tile的制作。

// **********************************************************************
// Copyright (C) XM
// Author: 吴肖牧
// Date: 2018-02-15
// Desc:
// **********************************************************************using UnityEngine;
using System;
using UnityEngine.Tilemaps;namespace XMtileMap
{[Serializable][CreateAssetMenu]public class XMTile : Tile{[SerializeField]public bool walkable = false;[SerializeField]public bool destroable = false;[SerializeField]public Sprite[] m_RandomSprites;[SerializeField]public Sprite[] m_AnimatedSprites;[SerializeField]public float m_MinSpeed = 1f;[SerializeField]public float m_MaxSpeed = 1f;[SerializeField]public float m_AnimationStartTime;public override void GetTileData(Vector3Int location, ITilemap tileMap, ref TileData tileData){if (m_RandomSprites != null && m_RandomSprites.Length > 0 && m_AnimatedSprites != null && m_AnimatedSprites.Length > 0){Debug.LogError("RandomSprites and AnimatedSprites can't exist at the same time");return;}base.GetTileData(location, tileMap, ref tileData);if (m_RandomSprites != null && m_RandomSprites.Length > 0){long hash = location.x;hash = (hash + 0xabcd1234) + (hash << 15);hash = (hash + 0x0987efab) ^ (hash >> 11);hash ^= location.y;hash = (hash + 0x46ac12fd) + (hash << 7);hash = (hash + 0xbe9730af) ^ (hash << 11);UnityEngine.Random.InitState((int)hash);tileData.sprite = m_RandomSprites[(int)(m_RandomSprites.Length * UnityEngine.Random.value)];}if (m_AnimatedSprites != null && m_AnimatedSprites.Length > 0){tileData.sprite = m_AnimatedSprites[m_AnimatedSprites.Length - 1];}}public override bool GetTileAnimationData(Vector3Int location, ITilemap tileMap, ref TileAnimationData tileAnimationData){if (m_AnimatedSprites != null && m_AnimatedSprites.Length > 0){tileAnimationData.animatedSprites = m_AnimatedSprites;tileAnimationData.animationSpeed = UnityEngine.Random.Range(m_MinSpeed, m_MaxSpeed);tileAnimationData.animationStartTime = m_AnimationStartTime;return true;}return false;}}
}

我们自定义的一些变量,比如walkable(是否可通过),destroable(是否可销毁)等等,这些我们可以根据项目需求自行拓展,Tilemap本身的知识点这里不再详细解说,不明白的建议去看看官方的文档和例子。

4.Brush的制作。

// **********************************************************************
// Copyright (C) XM
// Author: 吴肖牧
// Date: 2018-02-15
// Desc:
// **********************************************************************using UnityEditor;
using UnityEngine;namespace XMtileMap
{[CreateAssetMenu][CustomGridBrush(false, true, false, "XM Brush")]public class XMBrush : GridBrush{public override void Paint(GridLayout gridLayout, GameObject brushTarget, Vector3Int position){base.Paint(gridLayout, brushTarget, position);AddTileMapData(gridLayout, brushTarget, position);}public override void Erase(GridLayout gridLayout, GameObject brushTarget, Vector3Int position){base.Erase(gridLayout, brushTarget, position);ClearTileMapData(gridLayout, brushTarget, position);}/// <summary>/// 添加地图数据/// </summary>/// <param name="grid"></param>/// <param name="position"></param>private void AddTileMapData(GridLayout gridLayout, GameObject brushTarget, Vector3Int position){TileInfo data = new TileInfo{//tile的中心点为四个顶点的其中一个点,默认左下角,我们偏移一下保证和其他游戏对象的中心点一致,这里是还原创建Grid时的偏移,保证对象刚好在tile的中心点pos = gridLayout.CellToWorld(position) + XMMapData.tileOffset3,ipos = position};for (int i = 0; i < cells.Length; i++){XMTile xmtile = (XMTile)cells[i].tile;data.tile = xmtile;}XMMapData.AddData(brushTarget, data.pos, data);}/// <summary>/// 清除地图数据/// </summary>/// <param name="position"></param>private void ClearTileMapData(GridLayout gridLayout,GameObject brushTarget, Vector3Int position){Vector3 pos = gridLayout.CellToWorld(position) + XMMapData.tileOffset3;XMMapData.ClearData(brushTarget, pos);}}[CustomEditor(typeof(XMBrush))]public class XMBrushEditor : GridBrushEditor{private XMBrushEditor prefabBrush { get { return target as XMBrushEditor; } }public override void PaintPreview(GridLayout grid, GameObject brushTarget, Vector3Int position){base.PaintPreview(grid, brushTarget, position);}public override void OnPaintInspectorGUI(){EditorGUILayout.LabelField(XMConst.CopyRight);}public override void OnPaintSceneGUI(GridLayout grid, GameObject brushTarget, BoundsInt position, GridBrushBase.Tool tool, bool executing){base.OnPaintSceneGUI(grid, brushTarget, position, tool, executing);Handles.Label(grid.CellToWorld(new Vector3Int(position.x, position.y, position.z)), new Vector3Int(position.x, position.y, position.z).ToString());}}
}

这里的重点是AddTileMapData方法,通过Brush画刷的来保存我们制作地图的数据,还需要注意的一点是,我这里只写了Paint的画刷方式,需要更加全面的画刷方式请自行拓展。

5.创建地图。

// **********************************************************************
// Copyright (C) XM
// Author: 吴肖牧
// Date: 2018-02-15
// Desc:
// **********************************************************************using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;namespace XMtileMap
{public class Map : MonoBehaviour{/// <summary>/// 设置Tile/// </summary>/// <param name="map"></param>/// <param name="pos"></param>/// <param name="tilebase"></param>public static void SetTile(Tilemap map, Vector3Int pos, TileBase tilebase){map.SetTile(pos, tilebase);}/// <summary>/// 设置TileMap/// </summary>/// <param name="map"></param>/// <param name="tileMapDataList"></param>public static void SetTileMap(Tilemap map, TileMapData tileMapData){if (tileMapData.tileInfoList != null){foreach (var tile in tileMapData.tileInfoList){map.SetTile(tile.ipos, tile.tile);}}}/// <summary>/// 创建TileMap/// </summary>/// <param name="tilemapData">地图ID</param>public static void CreateTileMap(int mapid){XMMapData.MapID = mapid;if (XMMapData.SourceData.Data.Count < mapid + 1){Debug.LogError("MapID " + mapid.ToString() + " is null");return;}List<TileMapData> tilemapData = XMMapData.SourceData.Data[XMMapData.MapID].tileMapDataList;if (tilemapData == null){Debug.LogError("MapID " + mapid.ToString() + " tileMapDataList is null");return;}GameObject grid = GameObject.Find("Grid");if (!grid){grid = new GameObject("Grid");grid.AddComponent<Grid>();}//tile的中心点为四个顶点的其中一个点,默认左下角,我们偏移一下保证和其他游戏对象的中心点一致grid.transform.position = new Vector3(-0.5f, -0.5f, 0);for (int i = 0; i < grid.transform.childCount; i++){Destroy(grid.transform.GetChild(i).gameObject);}for (int i = 0; i < tilemapData.Count; i++){GameObject tilemap = new GameObject(tilemapData[i].TilemapName);tilemap.transform.SetParent(grid.transform);tilemap.transform.localPosition = Vector3.zero;Tilemap map = tilemap.AddComponent<Tilemap>();TilemapRenderer render = tilemap.AddComponent<TilemapRenderer>();render.sortOrder = (TilemapRenderer.SortOrder)tilemapData[i].SortOrderIndex;render.sortingOrder = tilemapData[i].OrderInLayer;render.sortingLayerName = SortingLayer.layers[tilemapData[i].SortingLayerIndex].name;SetTileMap(map, tilemapData[i]);}//初始化地图,绑定寻路数据InitMap();}/// <summary>/// 初始化地图,绑定寻路数据/// </summary>public static void InitMap(){//Debug.Log(XMMapData.mapSize);XMMapData.map = new Dictionary<Vector2, Point>();List<TileMapData> tilemapData = XMMapData.SourceData.Data[XMMapData.MapID].tileMapDataList;foreach (var item in tilemapData){for (int i = 0; i < item.tileInfoList.Count; i++){int x = item.tileInfoList[i].ipos.x;int y = item.tileInfoList[i].ipos.y;//Debug.Log(x + " " + y);XMMapData.map.Add(new Vector2(x, y), new Point(x, y));bool walkable = ((XMTile)item.tileInfoList[i].tile).walkable;if (walkable){XMMapData.map[new Vector2(x, y)].Walkable = walkable;}}}}}
}

下图是整个文件的结构。

编辑器的脚本我就不给出来了,太繁杂了,直接看源码。反正思路大概就是根据需求制作Tile,然后通过Brush画刷把对应的Tile和数据添加到我们的文件里面,很简单吧。最后我们通过保存的数据文件,就可以在游戏里面动态的创建地图了,同时也可以根据地图的数据来做我们的网络同步数据,角色移动,碰撞检测等等。

Unity2017新功能Tilemap地图编辑器的数据拓展和动态生成相关推荐

  1. django通过ajax请求接口返回多条数据,并动态生成表格,请求表单后将表格数据并入库

    一.最近在做接口相关的开发,需求是这样的,通过一个接口所需要传递的参数,调用接口后,处理接口响应的参数,返回多条数据,并动态生成表格,请求表单后将表格的数据入库,下面是我改过的代码,跟实际代码有些出入 ...

  2. 根据数据和模板动态生成页面+列表的动态渲染

    非原创 本文转自https://github.com/a415432669/-front_end_notebook/tree/master/Node/day6/%E6%96%87%E6%A1%A3 根 ...

  3. “指标预警”新功能上线,智能实现数据监测

    双十一大促通宵订单数十万,分区服务器宕机大量用户付款失败: 周会 leader 需要数据报表,每周耗费大量时间做图写报告,耗时耗力: 电影看到高潮,calendar 提醒自己到了发日总结的时间,无奈打 ...

  4. Unity2020.1新功能探路:编辑器相关更新

    洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 接下来的几天呢,大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 首先是你日常使用的和编辑器相关的更 ...

  5. ONLYOFFICE 桌面编辑器 v7.3 新功能介绍

    ONLYOFFICE 桌面编辑器版本 7.3 已经可以在 Windows.Linux 和 macOS 上使用.桌面版的大部分新功能与在线编辑器的相同,但桌面编辑器也带来了一些独特的功能,例如,新的打印 ...

  6. SQL Server 2016的新功能–临时数据表

    There are many new features in SQL Server 2016, but the one we will focus on in this post is: SQL Se ...

  7. Unity2020.1新功能探路:Profiler相关更新

    洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 这一次Profiler的更新比较大,咱们专门用一篇 ...

  8. Unity2020.1新功能探路:脚本开发相关更新

    洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 接下来的几天呢,大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 这一次咱们来看看脚本编程方面的更新 ...

  9. D3D游戏编程系列(二):自己动手编写即时战略游戏之地图编辑器的制作

    说起即时战略游戏,我第一时间想起魔兽争霸,这个不知道陪伴我多少个日日夜夜,让我哭让我笑的游戏,让我想起了sky,moon,grubby等人牵动心弦的战斗历程,让我想起了当年日日守在电脑前专注的欣赏着w ...

最新文章

  1. 微信小程序web-view能发送ajax,微信小程序web-view组件的坑
  2. 统计一下你写过多少代码
  3. Spring学习(二)Spring IoC 和 DI 简介
  4. Html5里frameSet不在使用的替代方法,使用ifram
  5. 阿里P8面试官都说太详细了,面试资料分享
  6. 【转】C# DataTable使用方法详解
  7. 【深度学习】基于 Alluxio 数据缓存的性能优化
  8. 关于C++中的unordered_map和unordered_set不能直接以pair作为键名的问题
  9. Django在根据models生成数据库表时报 __init__() missing 1 required positional argument: 'on_delete'...
  10. 人工智能的下个十年在推理?
  11. Silverlight中 非UI线程更新UI 的几种方法
  12. PL/SQL详细的安装和配置教程(附带网盘下载链接,以及PL/SQL的基本操作与注意事项)
  13. 人工智能和自动化之间,主要有区别吗?
  14. html自动调音量,HTML5 音量调节控件
  15. 云计算+,如何推动建筑行业智慧化升级?
  16. Python海龟turtle画椭圆方法
  17. 代码画验证码图片(一)
  18. Stanford Named Entity Recognizer (NER) 斯坦福命名实体识别(NER)
  19. 信息时代碎片化学习的理解
  20. IDEA 2020奇怪的控制台中文乱码问题

热门文章

  1. 浅谈JS中常见的问题(三)
  2. Java C语言 输出n以内的所有素数 以及判断一个数是不是素数
  3. 蒲公英的虚拟服务器,蒲公英服务器端客户端 for Windows 使用教程
  4. 你在网上献的爱心,估值400亿
  5. Web前端最常用的技能整理,附最新前端学习资料和视频教程
  6. Redis set命令详解
  7. 刘铁锰老师C#语言入门详解(委托事件等部分有详细代码和注释)
  8. 在APP内实现顶层窗口,悬浮窗功能。
  9. Gazebo仿真UUV水下机器人
  10. 蛇打七寸,开源ERP携掌融宝再次出击