Unity2017新功能Tilemap地图编辑器的数据拓展和动态生成
在我来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地图编辑器的数据拓展和动态生成相关推荐
- django通过ajax请求接口返回多条数据,并动态生成表格,请求表单后将表格数据并入库
一.最近在做接口相关的开发,需求是这样的,通过一个接口所需要传递的参数,调用接口后,处理接口响应的参数,返回多条数据,并动态生成表格,请求表单后将表格的数据入库,下面是我改过的代码,跟实际代码有些出入 ...
- 根据数据和模板动态生成页面+列表的动态渲染
非原创 本文转自https://github.com/a415432669/-front_end_notebook/tree/master/Node/day6/%E6%96%87%E6%A1%A3 根 ...
- “指标预警”新功能上线,智能实现数据监测
双十一大促通宵订单数十万,分区服务器宕机大量用户付款失败: 周会 leader 需要数据报表,每周耗费大量时间做图写报告,耗时耗力: 电影看到高潮,calendar 提醒自己到了发日总结的时间,无奈打 ...
- Unity2020.1新功能探路:编辑器相关更新
洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 接下来的几天呢,大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 首先是你日常使用的和编辑器相关的更 ...
- ONLYOFFICE 桌面编辑器 v7.3 新功能介绍
ONLYOFFICE 桌面编辑器版本 7.3 已经可以在 Windows.Linux 和 macOS 上使用.桌面版的大部分新功能与在线编辑器的相同,但桌面编辑器也带来了一些独特的功能,例如,新的打印 ...
- 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 ...
- Unity2020.1新功能探路:Profiler相关更新
洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 这一次Profiler的更新比较大,咱们专门用一篇 ...
- Unity2020.1新功能探路:脚本开发相关更新
洪流学堂,让你快人几步.你好,我是你的技术探路者郑洪智,你可以叫我大智. 接下来的几天呢,大智作为探路者带你一块探索一下Unity2020.1里面有什么好玩的东西. 这一次咱们来看看脚本编程方面的更新 ...
- D3D游戏编程系列(二):自己动手编写即时战略游戏之地图编辑器的制作
说起即时战略游戏,我第一时间想起魔兽争霸,这个不知道陪伴我多少个日日夜夜,让我哭让我笑的游戏,让我想起了sky,moon,grubby等人牵动心弦的战斗历程,让我想起了当年日日守在电脑前专注的欣赏着w ...
最新文章
- 微信小程序web-view能发送ajax,微信小程序web-view组件的坑
- 统计一下你写过多少代码
- Spring学习(二)Spring IoC 和 DI 简介
- Html5里frameSet不在使用的替代方法,使用ifram
- 阿里P8面试官都说太详细了,面试资料分享
- 【转】C# DataTable使用方法详解
- 【深度学习】基于 Alluxio 数据缓存的性能优化
- 关于C++中的unordered_map和unordered_set不能直接以pair作为键名的问题
- Django在根据models生成数据库表时报 __init__() missing 1 required positional argument: 'on_delete'...
- 人工智能的下个十年在推理?
- Silverlight中 非UI线程更新UI 的几种方法
- PL/SQL详细的安装和配置教程(附带网盘下载链接,以及PL/SQL的基本操作与注意事项)
- 人工智能和自动化之间,主要有区别吗?
- html自动调音量,HTML5 音量调节控件
- 云计算+,如何推动建筑行业智慧化升级?
- Python海龟turtle画椭圆方法
- 代码画验证码图片(一)
- Stanford Named Entity Recognizer (NER) 斯坦福命名实体识别(NER)
- 信息时代碎片化学习的理解
- IDEA 2020奇怪的控制台中文乱码问题