Созданию простого 2D платформера посвящено немало публикаций на ресурсах для разработчиков игр. В качестве движка часто используют Unity, а для создания карт — Tiled Map Editor. Взаимодействие этих двух сред осуществляется с помощью бесплатной утилиты Tiled2Unity, которая создает из TME файлов префабы в Unity.

Как это работает, расскажем на примере разработки нашего платформера “X-Drums”.

Двигаться будем по следующей схеме:

  1. Настройка экспорта Tiled2Unity
  2. Настройка столкновений в Tiled Map Editor
  3. Настройка параметров, которые поддерживает Tiled2Unity
  4. Настройка пользовательских параметров в Tiled Map Editor

  • Пользовательские параметры для слоев (объектов)
  • Пользовательские параметры для объектов

1. Настройка экспорта


Итак, у нас есть проект в Unity и есть карта, созданная в Tiled Map Editor. Запускаем Unity и импортируем Tiled2Unity.unitypackage.



В Tiled Map Editor вызываем окно редактирования команд:



Добавляем новую команду, в которой прописан путь до исполняемого файла Tiled2Unity.exe. Вместо %mapfile Tiled автоматически подставит путь к файлу карты. Последний аргумент этой команды — это путь к папке Tiled2Unity, которая должна была появиться в проекте после импорта.



Запускаем команду и в появившемся окне нажимаем большую кнопку.



В результате экспорта получаем префаб карты, который находится в папке Tiled2Unity. Размещаем этот префаб на сцене в Unity. Теперь при работе с картой в TME и ее экспорте, префаб и объект на сцене будут изменяться в соответствии с изменениями в TME.



Так мы настроили экспорт карты из Tiled в Unity и создали игровую среду.

Теперь займемся настройкой объектов на нашей карте.

2. Редактор столкновений


В первую очередь, определим элементы игрового пространства, которые будут препятствиями для героя. Это пол, стены, софиты на потолке, а также барабаны, выступающие платформами, по которым и будет прыгать герой.

Запускаем редактор столкновений в Tiled:



Выбираем нужный нам тайл, к примеру, часть барабана, и редактируем полигоны:



Аналогично редактируем другие нужные нам тайлы (пол, стены и т.д.). В результате, при экспорте Tiled2Unity автоматически создаст коллайдеры PolygonCollider2D, которые и будут выступать для героя препятствиями.





Однако другие объекты игры, с которыми может взаимодействовать герой, являются более сложными. Поведение тарелок, фанаток, нот и других элементов контролируется с помощью компонентов и скриптов. Для экспорта таких объектов необходимо указывать им дополнительные параметры в Tiled и затем обрабатывать их в Unity.

3. Параметры, которые поддерживает Tiled2Unity


Впрочем есть список параметров, которые мы можем использовать в Tiled без настройки их в Unity, поскольку Tiled2Unity уже сделал это за нас. Среди них:
unity:layer
unity:sortingLayerName
unity:tag
unity:sortingOrder
unity:isTrigger
unity:ignore
The (physics) layer name in Unity
The Name of the Sorting Layer in Unity
The Name of the Tag in Unity
The `sorting in layer` value in Unity
Whether collider is a trigger or not
Ignore layer (or parts of it) during import(value = [false|true|collision|visual])

Зададим нужные нам параметры для барабанных палочек, по которым будет прыгать герой:



При экспорте в Unity получаем такой результат:



Слою задан параметр OneWayPlatform, он используется для объектов, которые являются проницаемыми снизу.



4. Пользовательские параметры в Tiled


Обработку других параметров, назовем их “пользовательскими”, и экспорт объектов с такими параметрами, мы будем настраивать в Unity. Задавать их в Tiled можно как для слоев (объектов), так и для объектов. В разных случаях может бы��ь удобным первый или второй вариант. Рассмотрим, как это реализовать.

4.1. Пользовательские параметры для слоев (объектов)

Зададим пользовательские параметры для слоя. Этот вариант мы используем для однотипных объектов. В нашей игре такими являются тарелки и ноты. Их особенность в том, что они одинаково взаимодействуют с героем и не нужно их отличать друг от друга.

Создаем слой объектов в Tiled и размещаем на нем наши тарелки. Затем, задаем параметр AddObject для всего слоя.



Поле своих параметров для каждой тарелки остается пустым.



Теперь переходим в Unity, где для тарелок мы создали префаб и привязали к нему скрипт, определя��щий их поведение при прыжке героя — они падают и возвращаются в исходное положение.

Напишем код, который при импорте тарелок для каждого объекта из Tiled создаст экземпляр нашего префаба. Для этого создаем скрипт CustomTiledImporterForHiHats с классом, который наследует интерфейс Tiled2Unity.ICustomTiledImporter и атрибутом [Tiled2Unity.CustomTiledImporter].

Этот скрипт Tiled2Unity будет вызывать для каждого импортируемого из Tiled слоя и объекта. А обрабатываться, соответственно, будут те слои или объекты, у которых среди параметров есть параметр AddObject со значением “Hi-hat”.

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[Tiled2Unity.CustomTiledImporter]
class CustomTiledImporterForHiHats : Tiled2Unity.ICustomTiledImporter
{
    public void HandleCustomProperties(GameObject gameObject, IDictionary<string, string> props)
    {
        if (!props.ContainsKey("AddObject"))
            return;

        if (props["AddObject"] == "Hi-hat")
        {
            //Загружаем префаб тарелок
            string prefabPath = "Assets/8 - Prefabs/Hi-hat.prefab";
            Object obj = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));
            if (obj == null)
                return;

            //Для каждого тайла, который будет загружен из Tiled, 
            //будет создан экземпляр префаба
            //Сами тайлы будут удалены со сцены
            List<GameObject> childrenToDestroy = new List<GameObject>();
            //Проходим по списку объектов слоя
            foreach (Transform child in gameObject.transform)
            {
                if (child.name == "TileObject")
                {
                    //Добавляем тайл для последующего удаления
                    childrenToDestroy.Add(child.gameObject); 

                    //Создаем экземпляр префаба, настраиваем его положение на сцене
                    GameObject objInstance = (GameObject)GameObject.Instantiate(obj);
                    objInstance.name = obj.name;
                    objInstance.transform.parent = gameObject.transform;
                    objInstance.transform.position = child.transform.position;
                }
            }
            //Удаляем тайлы со сцены
            childrenToDestroy.ForEach(child => GameObject.DestroyImmediate(child));   
        }
    }

    public void CustomizePrefab(GameObject prefab)
    {
    }
}


Скрипт готов, и мы можем экспортировать тарелки.



И вот так герой взаимодействует с ними.



Экспорт нот настроен точно так же как экспорт тарелок. Единственное отличие в том, что в игре есть несколько видов нот с разной стоимостью в зависимости от цвета. Поэтому для каждого типа нот со своим номиналом мы создали префаб в Unity и слой объектов в Tiled, указав ему в параметре AddObject тип ноты. Скрипт импорта, создающий экземпляры префабов, идентичен скрипту тарелок.

4.2. Пользовательские параметры для объектов

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

К примеру, фанатки различаются по цвету, они двигаются с разной скоростью на задаваемое расстояние. Они могут останавливаться, чтобы моргнуть, а при встрече с героем на выбор выдают определенные эмоций. Задаем эти параметры в Tiled для каждого объекта. “Свои параметры” для слоя, соответственно, остаются пустыми.



В Unity для фанаток созданы префабы, к которым присоединен скрипт GirlController, определяющий их поведение и реакцию на героя. Импорт будет аналогичен импорту тарелок, но просто создать экземпляры префаба в этом случае будет недостаточно. Нам нужно обратиться к скрипту GirlController и установить свойства, которые были заданы в Tiled индивидуально для каждой фанатки.

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

[Tiled2Unity.CustomTiledImporter]
class CustomTiledImporterForGirls : Tiled2Unity.ICustomTiledImporter
{
    public void HandleCustomProperties(GameObject gameObject, IDictionary<string, string> props)
    {
        if (!props.ContainsKey("GirlColor"))
            return;

        //Загружаем нужный префаб в зависимости от указанного в Tiled цвета
        string prefabPath = "Assets/8 - Prefabs/Girls/Girl_" + props["GirlColor"] + ".prefab";
        Object obj = AssetDatabase.LoadAssetAtPath(prefabPath, typeof(GameObject));

        //Создаем экземпляр префаба, настраиваем его положение на сцене
        GameObject objInstance = (GameObject)Object.Instantiate(obj);
        objInstance.name = obj.name;
        objInstance.transform.parent = gameObject.transform.parent;
        objInstance.transform.position = gameObject.transform.position;
        objInstance.transform.localScale = gameObject.transform.localScale;
        objInstance.transform.eulerAngles = gameObject.transform.eulerAngles;

        //Обращаемся к скрипту, который определяет поведение фанатки
        //Считываем параметры из Tiled и присваиваем переменным скрипта
        var girlController = objInstance.GetComponent<GirlController>();
        if (props.ContainsKey("OffsetRight"))
            float.TryParse(props["OffsetRight"], out girlController.OffsetMax);
        if (props.ContainsKey("OffsetLeft"))
            float.TryParse(props["OffsetLeft"], out girlController.OffsetMin);
        if (props.ContainsKey("Velocity"))
            float.TryParse(props["Velocity"], out girlController.Velocity);
        if (props.ContainsKey("Stop"))
            float.TryParse(props["Stop"], out girlController.StopCount);
        if (props.ContainsKey("Fatal"))
            bool.TryParse(props["Fatal"], out girlController.Fatal);
        if (props.ContainsKey("HeartType"))
        {
            string heartPrefabPath = "Assets/8 - Prefabs/Girls/" + props["HeartType"] + ".prefab";
            Object heartObj = AssetDatabase.LoadAssetAtPath(heartPrefabPath, typeof(GameObject));
            girlController.heartPrefab = heartObj;
        }

        //Удаляем тайл со сцены
        Object.DestroyImmediate(gameObject);
    }

    public void CustomizePrefab(GameObject prefab)
    {
    }
}

Выполняем экспорт в Unity и видим, что параметры, указанные в Tiled, были установлены переменным скрипта GirlController:





Повторяем описанную схему для объектов из других групп, и при экспорте в Unity получаем настроенные префабы большинства составляющих игры.

Описанный подход был использован нами для создания нашего платформера. Работа в связке Tiled Map Editor и Unity, именно в этой игре, ускорила процесс ее создания, позволив одновременно работать дизайнеру уровня в TME и программисту в Unity, не мешая друг другу. В проектах, подобных нашему, утилита Tiled2Unity может оказаться невероятно полезной, и, опираясь на статью, желающие могут применить любой из вариантов в своих игровых разработках.