Конвертация Tilemap в массив префабов

Как известно, в Unity встроен удобный инструмент для создания и редактирования Tilemap, при этом в рамках одной сетки, можно создать несколько Tilemap, на одном можно нарисовать проходимые участки карты, на другом непроходимые, например. Но, для создания проекта на тайлах, может понадобится еще одна вещь, размещение на клетках, вместо тайлов, префабов из таких же тайлов. Если таких префабв пара штук, то проблемы в этом конечно нет, однако, как быть если необходимо сотню префабов разместить на Tilemap, плюс, нужно учитывать возможность редактирования самой кары и соответственно этих отдельных префабов.

Для решения этой проблемы, мы предлагаем следующее решение. Логика в том, чтобы создать дополнительный Tilemap для карты проекта, и рисовать на нем тайлами те объекты, которые нужно заменить на префабы. А затем, сделать конвертацию данной Tilemap в префабы, где каждый тайл будет заменен, на основе его имени. То есть, мы считываем имена тайлов, находим одноименные префабы и меняем их местами, при этом заменяемые тайлы будут удалены с сетки Tilemap.

Условно "снимок" заменяемых тайлов, мы делаем в XML формате. Можно сразу заменить тайлы или сохранить снимок в файл. Два этих варианта сделаны для примера, для того, чтобы продемонстрировать как можно работать с Tilemap и считывать информацию о тайлах. Кроме того, вариант с XML может быть полезен, если надо изменить карту замены тайлов, просто подключив другой файл.

Итак, для начала работы создадим в проекте папку Resources, а в ней еще две: Maps - здесь будут храниться XML файлы; Prefab - здесь мы создаем префабы, с таким же именем как у заменяемых тайлов.

Теперь добавляем на сцену скрипт:

using UnityEngine.Tilemaps;
using UnityEngine;
using System.Collections.Generic;
using System.Xml;
using System.IO;

public class TilemapEditor : MonoBehaviour {

    public string mapName = "Demo"; // имя файла карты
    public Tilemap tilemap; // тайлмап для конвертации
    public Transform prefabParent; // родитель для префабов
    public GameObject[] prefab; // массив префабов, которые заменили тайлы
    private List<Data> data;

    struct Data
    {
        public string name;
        public Vector3 worldPosition;
        public Vector3Int tilemapPosition;
    }

    public void GetXML()
    {
        Create();
        SaveXML();
    }

    public void ReplaceTile()
    {
        Create();
        ConvertTilemap(false);
    }

    void Create()
    {
        data = new List<Data>();

        foreach (var position in tilemap.cellBounds.allPositionsWithin)
        {
            TileBase tile = tilemap.GetTile(position);

            if (tile != null)
            {
                Data part = new Data();
                part.name = tile.name;

                Vector2 pos = new Vector2(position.x * tilemap.cellSize.x + tilemap.cellSize.x / 2f, position.y * tilemap.cellSize.y + tilemap.cellSize.y / 2f);

                switch (tilemap.cellSwizzle)
                {
                    case GridLayout.CellSwizzle.XYZ:
                        part.worldPosition = new Vector3(pos.x, pos.y, 0);
                        break;

                    case GridLayout.CellSwizzle.XZY:
                        part.worldPosition = new Vector3(pos.x, 0, pos.y);
                        break;

                    case GridLayout.CellSwizzle.YXZ:
                        part.worldPosition = new Vector3(pos.y, pos.x, 0);
                        break;

                    case GridLayout.CellSwizzle.YZX:
                        part.worldPosition = new Vector3(pos.y, 0, pos.x);
                        break;

                    case GridLayout.CellSwizzle.ZXY:
                        part.worldPosition = new Vector3(0, pos.x, pos.y);
                        break;

                    case GridLayout.CellSwizzle.ZYX:
                        part.worldPosition = new Vector3(0, pos.y, pos.x);
                        break;
                }
                
                part.tilemapPosition = position;
                data.Add(part);
            }
        }
    }

    string FilePath()
    {
        return Application.dataPath + "/Resources/Maps/" + mapName + ".xml";
    }

    float Round(float f)
    {
        return Mathf.Round(f * 1000f) / 1000f;
    }

    void SaveXML()
    {
        XmlNode userNode;
        XmlDocument xmlDoc = new XmlDocument();
        XmlDeclaration declaration = xmlDoc.CreateXmlDeclaration("1.0", "UTF-8", null);
        xmlDoc.AppendChild(declaration);
        XmlNode rootNode = xmlDoc.CreateElement("map-data-list");
        xmlDoc.AppendChild(rootNode);

        for (int i = 0; i < data.Count; i++)
        {
            userNode = xmlDoc.CreateElement("block");
            userNode.InnerText = data[i].name + "|" + Round(data[i].worldPosition.x) + ":" + Round(data[i].worldPosition.y) + ":" + Round(data[i].worldPosition.z) + "|" + data[i].tilemapPosition.x + ":" + data[i].tilemapPosition.y;
            rootNode.AppendChild(userNode);
        }

        xmlDoc.Save(FilePath());

        Debug.Log(this + " --> создан фаил: " + FilePath());
    }

    void Start()
    {
        Generate(mapName); // загрузка XML на старте
    }

    public void ClearPrefab()
    {
        for (int i = 0; i < prefab.Length; i++)
        {
            if (prefab[i] != null && !Application.isPlaying)
            {
                DestroyImmediate(prefab[i]);
            }
            else if (prefab[i] != null && Application.isPlaying)
            {
                Destroy(prefab[i]);
            }
        }
    }

    void ConvertTilemap(bool clearTilemap)
    {
        ClearPrefab();

        prefab = new GameObject[data.Count];

        for (int i = 0; i < data.Count; i++)
        {
            GameObject obj = Resources.Load<GameObject>("Prefab/" + data[i].name);

            if (obj != null)
            {
                GameObject clone = Instantiate(obj, data[i].worldPosition, Quaternion.identity, prefabParent) as GameObject;
                if(clearTilemap) tilemap.SetTile(data[i].tilemapPosition, null);
                clone.name = "Tile(" + data[i].name + ")[" + i + "]";
                prefab[i] = clone;
            }
        }
    }
	
    public void Generate(string map)
    {
        data = new List<Data>();

        TextAsset asset = Resources.Load<TextAsset>("Maps/" + map);

        if (asset == null) return;

        XmlTextReader reader = new XmlTextReader(new StringReader(asset.text));

        while (reader.Read())
        {
            if (reader.IsStartElement("block"))
            {
                Data part = new Data();

                string[] value = reader.ReadString().Split(new char[] { '|' });

                if (value.Length == 3)
                {
                    part.name = value[0];
                    part.worldPosition = WorldPosition(value[1]);
                    part.tilemapPosition = TilemapPosition(value[2]);
                }
                
                data.Add(part);
            }
        }

        reader.Close();

        ConvertTilemap(true);
    }

    int ParseInt(string text)
    {
        int value;
        if (int.TryParse(text, out value)) return value;
        return 0;
    }

    float ParseFloat(string text)
    {
        float value;
        if (float.TryParse(text, out value)) return value;
        return 0;
    }

    Vector3 WorldPosition(string val)
    {
        string[] value = val.Split(new char[] { ':' });

        if (value.Length == 3)
        {
            return new Vector3(ParseFloat(value[0]), ParseFloat(value[1]), ParseFloat(value[2]));
        }

        return Vector2.zero;
    }

    Vector3Int TilemapPosition(string val)
    {
        string[] value = val.Split(new char[] { ':' });

        if (value.Length == 2)
        {
            return new Vector3Int(ParseInt(value[0]), ParseInt(value[1]), 0);
        }

        return Vector3Int.zero;
    }
}
Данный скрипт можно цеплять куда угодно, указываем в настройках необходимый Tilemap и имя для файла XML.

Дополнительно добавим кнопки:

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(TilemapEditor))]
public class TilemapEditorControl : Editor {

    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();
        TilemapEditor t = (TilemapEditor)target;
        GUILayout.Label("Управление:", EditorStyles.boldLabel);
        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Создать XML"))
        {
            t.GetXML();
        }
        if (GUILayout.Button("Конвертировать Tilemap"))
        {
            t.ReplaceTile();
        }
        GUILayout.EndHorizontal();
        if (GUILayout.Button("Очистить массив префабов"))
        {
            for (int i = 0; i < t.prefab.Length; i++)
            {
                if (t.prefab[i] != null)
                {
                    DestroyImmediate(t.prefab[i]);
                }
            }

            t.prefab = new GameObject[0];
        }
    }
}
#endif
Вот и всё. Можно работать с тайлами.

Скачать демо:

Внимание! Посетители, находящиеся в группе Гости, не могут скачивать файлы.
Тестировалось на: Unity 2018.2.14
Информация
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.
  • Дешевый хостинг
  • Яндекс.Метрика