我是靠谱客的博主 干净电源,最近开发中收集的这篇文章主要介绍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_EDITOR
mapData = 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_EDITOR
sourceData = 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_EDITOR
targetData = 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地图编辑器的数据拓展和动态生成的全部内容,希望文章能够帮你解决Unity2017新功能Tilemap地图编辑器的数据拓展和动态生成所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(102)

评论列表共有 0 条评论

立即
投稿
返回
顶部